Skip to content

Get client

Deduce how to initialize or retrieve the client.

This is meant to be a zero configuration for the user.

Create and set a client globally
from ydata.sdk.client import get_client
get_client(set_as_global=True)

Parameters:

Name Type Description Default
client_or_creds Optional[Union[Client, dict, str, Path]]

Client to forward or credentials for initialization

None
set_as_global bool

If True, set client as global

False
wait_for_auth bool

If True, wait for the user to authenticate

True

Returns:

Type Description
Client

Client instance

Source code in ydata/sdk/common/client/utils.py
def get_client(client_or_creds: Optional[Union[Client, Dict, str, Path]] = None, set_as_global: bool = False, wait_for_auth: bool = True) -> Client:
    """Deduce how to initialize or retrieve the client.

    This is meant to be a zero configuration for the user.

    Example: Create and set a client globally
            ```py
            from ydata.sdk.client import get_client
            get_client(set_as_global=True)
            ```

    Args:
        client_or_creds (Optional[Union[Client, dict, str, Path]]): Client to forward or credentials for initialization
        set_as_global (bool): If `True`, set client as global
        wait_for_auth (bool): If `True`, wait for the user to authenticate

    Returns:
        Client instance
    """
    client = None
    global WAITING_FOR_CLIENT
    try:

        # If a client instance is set globally, return it
        if not set_as_global and Client.GLOBAL_CLIENT is not None:
            return Client.GLOBAL_CLIENT

        # Client exists, forward it
        if isinstance(client_or_creds, Client):
            return client_or_creds

        # Explicit credentials
        ''' # For the first version, we deactivate explicit credentials via string or file for env var only
        if isinstance(client_or_creds, (dict, str, Path)):
            if isinstance(client_or_creds, str):  # noqa: SIM102
                if Path(client_or_creds).is_file():
                    client_or_creds = Path(client_or_creds)

            if isinstance(client_or_creds, Path):
                client_or_creds = json.loads(client_or_creds.open().read())

            return Client(credentials=client_or_creds)

        # Last try with environment variables
        #if client_or_creds is None:
        client = _client_from_env(wait_for_auth=wait_for_auth)
        '''
        credentials = environ.get(TOKEN_VAR)
        if credentials is not None:
            client = Client(credentials=credentials)

    except ClientHandshakeError as e:
        wait_for_auth = False  # For now deactivate wait_for_auth until the backend is ready
        if wait_for_auth:
            WAITING_FOR_CLIENT = True
            start = time()
            login_message_printed = False
            while client is None:
                if not login_message_printed:
                    print(
                        f"The token needs to be refreshed - please validate your token by browsing at the following URL:\n\n\t{e.auth_link}")
                    login_message_printed = True
                with suppress(ClientCreationError):
                    sleep(BACKOFF)
                    client = get_client(wait_for_auth=False)
                now = time()
                if now - start > CLIENT_INIT_TIMEOUT:
                    WAITING_FOR_CLIENT = False
                    break

    if client is None and not WAITING_FOR_CLIENT:
        sys.tracebacklimit = None
        raise ClientCreationError
    return client

Main Client class used to abstract the connection to the backend.

A normal user should not have to instanciate a Client by itself. However, in the future it will be useful for power-users to manage projects and connections.

Parameters:

Name Type Description Default
credentials Optional[dict]

(optional) Credentials to connect

None
project Optional[Project]

(optional) Project to connect to. If not specified, the client will connect to the default user's project.

None
Source code in ydata/sdk/common/client/client.py
@typechecked
class Client(metaclass=SingletonClient):
    """Main Client class used to abstract the connection to the backend.

    A normal user should not have to instanciate a [`Client`][ydata.sdk.common.client.Client] by itself.
    However, in the future it will be useful for power-users to manage projects and connections.

    Args:
        credentials (Optional[dict]): (optional) Credentials to connect
        project (Optional[Project]): (optional) Project to connect to. If not specified, the client will connect to the default user's project.
    """

    codes = codes

    def __init__(self, credentials: Optional[Union[str, Dict]] = None, project: Optional[Project] = None, set_as_global: bool = False):
        self._base_url = environ.get("YDATA_BASE_URL", DEFAULT_URL)
        self._scheme = 'https'
        self._headers = {'Authorization': credentials}
        self._http_client = httpClient(
            headers=self._headers, timeout=Timeout(10, read=None))

        self._handshake()

        self._project = project if project is not None else self._get_default_project(
            credentials)
        self.project = project
        if set_as_global:
            self.__set_global()

    def post(self, endpoint: str, data: Optional[Dict] = None, json: Optional[Dict] = None, files: Optional[Dict] = None, raise_for_status: bool = True) -> Response:
        """POST request to the backend.

        Args:
            endpoint (str): POST endpoint
            data (Optional[dict]): (optional) multipart form data
            json (Optional[dict]): (optional) json data
            files (Optional[dict]): (optional) files to be sent
            raise_for_status (bool): raise an exception on error

        Returns:
            Response object
        """
        url_data = self.__build_url(endpoint, data=data, json=json, files=files)
        response = self._http_client.post(**url_data)

        if response.status_code != Client.codes.OK and raise_for_status:
            self.__raise_for_status(response)

        return response

    def get(self, endpoint: str, params: Optional[Dict] = None, cookies: Optional[Dict] = None, raise_for_status: bool = True) -> Response:
        """GET request to the backend.

        Args:
            endpoint (str): GET endpoint
            cookies (Optional[dict]): (optional) cookies data
            raise_for_status (bool): raise an exception on error

        Returns:
            Response object
        """
        url_data = self.__build_url(endpoint, params=params, cookies=cookies)
        response = self._http_client.get(**url_data)

        if response.status_code != Client.codes.OK and raise_for_status:
            self.__raise_for_status(response)

        return response

    def get_static_file(self, endpoint: str, raise_for_status: bool = True) -> Response:
        """Retrieve a static file from the backend.

        Args:
            endpoint (str): GET endpoint
            raise_for_status (bool): raise an exception on error

        Returns:
            Response object
        """
        url_data = self.__build_url(endpoint)
        url_data['url'] = f'{self._scheme}://{self._base_url}/static-content{endpoint}'
        response = self._http_client.get(**url_data)

        if response.status_code != Client.codes.OK and raise_for_status:
            self.__raise_for_status(response)

        return response

    def _handshake(self):
        """Client handshake.

        It is used to determine is the client can connect with its
        current authorization token.
        """
        response = self.get('/profiles', params={}, raise_for_status=False)
        if response.status_code == Client.codes.FOUND:
            parser = LinkExtractor()
            parser.feed(response.text)
            raise ClientHandshakeError(auth_link=parser.link)

    def _get_default_project(self, token: str):
        response = self.get('/profiles/me', params={}, cookies={'access_token': token})
        data: Dict = response.json()
        return data['myWorkspace']

    def __build_url(self, endpoint: str, params: Optional[Dict] = None, data: Optional[Dict] = None, json: Optional[Dict] = None, files: Optional[Dict] = None, cookies: Optional[Dict] = None) -> Dict:
        """Build a request for the backend.

        Args:
            endpoint (str): backend endpoint
            params (Optional[dict]): URL parameters
            data (Optional[Project]): (optional) multipart form data
            json (Optional[dict]): (optional) json data
            files (Optional[dict]): (optional) files to be sent
            cookies (Optional[dict]): (optional) cookies data

        Returns:
            dictionary containing the information to perform a request
        """
        _params = params if params is not None else {
            'ns': self._project
        }

        url_data = {
            'url': f'{self._scheme}://{self._base_url}/api{endpoint}',
            'headers': self._headers,
            'params': _params,
        }

        if data is not None:
            url_data['data'] = data

        if json is not None:
            url_data['json'] = json

        if files is not None:
            url_data['files'] = files

        if cookies is not None:
            url_data['cookies'] = cookies

        return url_data

    def __set_global(self) -> None:
        """Sets a client instance as global."""
        # If the client is stateful, close it gracefully!
        Client.GLOBAL_CLIENT = self

    def __raise_for_status(self, response: Response) -> None:
        """Raise an exception if the response is not OK.

        When an exception is raised, we try to convert it to a ResponseError which is
        a wrapper around a backend error. This usually gives enough context and provides
        nice error message.

        If it cannot be converted to ResponseError, it is re-raised.

        Args:
            response (Response): response to analyze
        """
        try:
            response.raise_for_status()
        except HTTPStatusError as e:
            with suppress(Exception):
                e = ResponseError(**response.json())
            raise e

get(endpoint, params=None, cookies=None, raise_for_status=True)

GET request to the backend.

Parameters:

Name Type Description Default
endpoint str

GET endpoint

required
cookies Optional[dict]

(optional) cookies data

None
raise_for_status bool

raise an exception on error

True

Returns:

Type Description
Response

Response object

Source code in ydata/sdk/common/client/client.py
def get(self, endpoint: str, params: Optional[Dict] = None, cookies: Optional[Dict] = None, raise_for_status: bool = True) -> Response:
    """GET request to the backend.

    Args:
        endpoint (str): GET endpoint
        cookies (Optional[dict]): (optional) cookies data
        raise_for_status (bool): raise an exception on error

    Returns:
        Response object
    """
    url_data = self.__build_url(endpoint, params=params, cookies=cookies)
    response = self._http_client.get(**url_data)

    if response.status_code != Client.codes.OK and raise_for_status:
        self.__raise_for_status(response)

    return response

get_static_file(endpoint, raise_for_status=True)

Retrieve a static file from the backend.

Parameters:

Name Type Description Default
endpoint str

GET endpoint

required
raise_for_status bool

raise an exception on error

True

Returns:

Type Description
Response

Response object

Source code in ydata/sdk/common/client/client.py
def get_static_file(self, endpoint: str, raise_for_status: bool = True) -> Response:
    """Retrieve a static file from the backend.

    Args:
        endpoint (str): GET endpoint
        raise_for_status (bool): raise an exception on error

    Returns:
        Response object
    """
    url_data = self.__build_url(endpoint)
    url_data['url'] = f'{self._scheme}://{self._base_url}/static-content{endpoint}'
    response = self._http_client.get(**url_data)

    if response.status_code != Client.codes.OK and raise_for_status:
        self.__raise_for_status(response)

    return response

post(endpoint, data=None, json=None, files=None, raise_for_status=True)

POST request to the backend.

Parameters:

Name Type Description Default
endpoint str

POST endpoint

required
data Optional[dict]

(optional) multipart form data

None
json Optional[dict]

(optional) json data

None
files Optional[dict]

(optional) files to be sent

None
raise_for_status bool

raise an exception on error

True

Returns:

Type Description
Response

Response object

Source code in ydata/sdk/common/client/client.py
def post(self, endpoint: str, data: Optional[Dict] = None, json: Optional[Dict] = None, files: Optional[Dict] = None, raise_for_status: bool = True) -> Response:
    """POST request to the backend.

    Args:
        endpoint (str): POST endpoint
        data (Optional[dict]): (optional) multipart form data
        json (Optional[dict]): (optional) json data
        files (Optional[dict]): (optional) files to be sent
        raise_for_status (bool): raise an exception on error

    Returns:
        Response object
    """
    url_data = self.__build_url(endpoint, data=data, json=json, files=files)
    response = self._http_client.post(**url_data)

    if response.status_code != Client.codes.OK and raise_for_status:
        self.__raise_for_status(response)

    return response