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

    DEFAULT_PROJECT: Optional[Project] = environ.get("DEFAULT_PROJECT", None)

    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).removesuffix('/')
        self._verify_ssl = bool(int(environ.get('YDATA_VERIFY_SSL', 1)))
        self._headers = {'Authorization': credentials}

        if self._verify_ssl is False:
            self._http_client = httpClient(
                headers=self._headers, timeout=Timeout(10, read=None), verify=self._verify_ssl)
        else:
            self._http_client = httpClient(
                headers=self._headers, timeout=Timeout(10, read=None))

        self._handshake()

        self._default_project = project or Client.DEFAULT_PROJECT or self._get_default_project(
            credentials)
        if set_as_global:
            self.__set_global()

    @property
    def project(self) -> Project:
        return Client.DEFAULT_PROJECT or self._default_project

    @project.setter
    def project(self, value: Project):
        self._default_project = value

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

        Args:
            endpoint (str): POST endpoint
            content (Optional[RequestContent])
            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, project=project)
        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 patch(
        self, endpoint: str, content: Optional[RequestContent] = None, data: Optional[Dict] = None,
        json: Optional[Dict] = None, project: Optional[Project] = None, files: Optional[Dict] = None,
        raise_for_status: bool = True
    ) -> Response:
        """PATCH request to the backend.

        Args:
            endpoint (str): POST endpoint
            content (Optional[RequestContent])
            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, project=project)
        response = self._http_client.patch(**url_data, content=content)

        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, project: Optional[Project] = 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, project=project)
        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, project: Optional[Project] = None, 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
        """
        from urllib.parse import urlparse
        url_data = self.__build_url(endpoint, project=project)
        url_parse = urlparse(self._base_url)
        url_data['url'] = f"""{
            url_parse.scheme}://{url_parse.netloc}/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, project: Optional[Project] = 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': project or self._default_project
        }

        url_data = {
            'url': f"""{self._base_url}/{endpoint.removeprefix("/")}""",
            '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

__build_url(endpoint, params=None, data=None, json=None, project=None, files=None, cookies=None)

Build a request for the backend.

Parameters:

Name Type Description Default
endpoint str

backend endpoint

required
params Optional[dict]

URL parameters

None
data Optional[Project]

(optional) multipart form data

None
json Optional[dict]

(optional) json data

None
files Optional[dict]

(optional) files to be sent

None
cookies Optional[dict]

(optional) cookies data

None

Returns:

Type Description
Dict

dictionary containing the information to perform a request

Source code in ydata/sdk/common/client/client.py
def __build_url(self, endpoint: str, params: Optional[Dict] = None, data: Optional[Dict] = None,
                json: Optional[Dict] = None, project: Optional[Project] = 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': project or self._default_project
    }

    url_data = {
        'url': f"""{self._base_url}/{endpoint.removeprefix("/")}""",
        '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

__raise_for_status(response)

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.

Parameters:

Name Type Description Default
response Response

response to analyze

required
Source code in ydata/sdk/common/client/client.py
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

__set_global()

Sets a client instance as global.

Source code in ydata/sdk/common/client/client.py
def __set_global(self) -> None:
    """Sets a client instance as global."""
    # If the client is stateful, close it gracefully!
    Client.GLOBAL_CLIENT = self

get(endpoint, params=None, project=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, project: Optional[Project] = 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, project=project)
    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, project=None, 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, project: Optional[Project] = None, 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
    """
    from urllib.parse import urlparse
    url_data = self.__build_url(endpoint, project=project)
    url_parse = urlparse(self._base_url)
    url_data['url'] = f"""{
        url_parse.scheme}://{url_parse.netloc}/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

patch(endpoint, content=None, data=None, json=None, project=None, files=None, raise_for_status=True)

PATCH 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 patch(
    self, endpoint: str, content: Optional[RequestContent] = None, data: Optional[Dict] = None,
    json: Optional[Dict] = None, project: Optional[Project] = None, files: Optional[Dict] = None,
    raise_for_status: bool = True
) -> Response:
    """PATCH request to the backend.

    Args:
        endpoint (str): POST endpoint
        content (Optional[RequestContent])
        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, project=project)
    response = self._http_client.patch(**url_data, content=content)

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

    return response

post(endpoint, content=None, data=None, json=None, project=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, content: Optional[RequestContent] = None, data: Optional[Dict] = None,
    json: Optional[Dict] = None, project: Optional[Project] = None, files: Optional[Dict] = None,
    raise_for_status: bool = True
) -> Response:
    """POST request to the backend.

    Args:
        endpoint (str): POST endpoint
        content (Optional[RequestContent])
        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, project=project)
    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