Connecting and using a client

To connect to a running Sugar server, use one of the provided client classes. These clients act as a middleware between some HTTP library and the other Zucker components. That means that every client type uses a different underlying transport library. You can use this to create your own client if you are already using another requests library that isn’t natively supported by Zucker yet. Further, the client implementation will determine whether actions will be synchronous or asychronous. In general, all clients need to be initialized with the URL of the server and a pair of OAuth credentials:

>>> crm = SomeClient("https://sugar.local", "username", "password")

Note

By default, this will connect using the zucker API platform. Make sure to create it in the admin section on the server or provide another platform name using the client_platform parameter. See the constructor __init__() documentation for more parameters.

See the bottom of this page for a full list of clients. Once the client has been created, you can go ahead and define a data model that matches the server’s. This will be the main way to use Zucker’s ORM system.

Client API

Clients are declared as follows. This is the definition of the base superclass for all clients, synchronous and asynchronous:

class zucker.client.base.BaseClient(base_url: str, username: str, password: str, *, client_platform: str = 'zucker', verify_ssl: bool = True)

Connection handler that handles communicating with a SugarCRM instance.

This is the main entry point for interfacing with models. After creating a new instance, the server’s modules are available as attributes on the client. Concrete client implementations (for different transport mechanisms) should implement one of SyncClient or AsyncClient.

__init__(base_url: str, username: str, password: str, *, client_platform: str = 'zucker', verify_ssl: bool = True)

Construct a client instance.

This may take a few seconds, as it performs initial authentication as well as querying the server for metadata about it’s configuration.

Parameters:
  • base_url – The URL of the SugarCRM installation to connect to.

  • username – Username to authenticate with.

  • password – Password to authenticate with.

  • client_platform – OAuth platform string.

  • verify_ssl – Set this to false to disable verification of the server’s SSL certificate. This should only be used while testing!

Closing

abstract BaseClient.close() Union[None, Awaitable[None]]

Perform cleanup operations.

This method should be called when the client is no longer used. Further operations on the client or any of its modules are consider undefined behavior afterwards. Some clients may be able to reinitialize transparently after closing while others may be unusable.

Note

This method may be synchronous or asychronous, depending on the client.

Metadata

The Metadata API yields information about the Sugar server’s configuration and data model. If you would like to query this API, you might also want to read this blog post from Sugar. Since metadata fetching can take quite a while, it needs to be initiated explicitly:

abstract BaseClient.fetch_metadata(*types: str) Union[None, Awaitable[None]]

Make sure server metadata for the given set of types is available.

Example. Pass a list of metadata types that should be cached to this method:

>>> crm.fetch_metadata("server_info", "full_module_list")
>>> await async_crm.fetch_metadata("server_info")

Hint

Here is a non-exhaustive list of these type names: hidden_subpanels, currencies, module_tab_map, labels, ordered_labels, config, relationships, server_info, logo_url, languages, full_module_list, modules, modules_info, fields, filters, views, layouts, datas

Like close(), this method may block or be awaitable, depending on the client. The other following methods and properties (concerning server metadata) only parse the information fetched here and are therefore synchronous. In general, cached metadata entries can be retrieved like this:

BaseClient.get_metadata_item(type_name: str) JsonMapping

Return the cached value of a given metadata item.

Parameters:

type_name – Name of the metadata’s type, as defined by Sugar.

Raises:

UnfetchedMetadataError – This error will be raised when the corresponding metadata has not been fetched yet. In that case, call fetch_metadata() to populate the cache.

Parts of the metadata API are also exposed via their own properties:

property BaseClient.module_names: Iterator[str]

List of all modules that are available.

This requires fetching the full_module_list metadata item.

property BaseClient.server_info: Tuple[str, str, str]

Sever information tuple.

This requires fetching the server_info metadata item.

Returns:

A 3-tuple that follows the syntax (flavor, version, build).

Supported modules

Clients expose the list of all supported modules under the module_names property:

>>> crm.fetch_metadata("full_module_list")  # Otherwise an UnfetchedMetadataError is raised
>>> list(crm.module_names)
["Leads", "Contacts", ...]

To check if a given module is supported, use the in operator:

>>> "Contacts" in crm
True
>>> "NonexistentModule" in crm
False

This call supports both modules names as strings as well as actual Module classes.

Generic requests

You can also use the client instance to perform generic authenticated HTTP requests against the server:

abstract BaseClient.request(method: str, endpoint: str, *, params: Optional[Mapping[str, str]] = None, data: Optional[JsonMapping] = None, json: Optional[JsonMapping] = None) Union[JsonMapping, Awaitable[JsonMapping]]

Handle a request to the CRM’s REST API.

This will make sure that authentication is set up and the correct URL is built. Parameters are akin to those defined by popular HTTP libraries (although not all are supported).

Parameters:
  • method – HTTP verb to use.

  • endpoint – Desired endpoint name (the part after /rest/v??_?/).

  • params – Dictionary of query parameters to add to the URL.

  • data – Request form data.

  • json – Request body that will be JSON-encoded. This is mutually exclusive with data.

Note

Depending on the client implementation, this method will be synchronous or asynchronous.

The first request will cause the authentication flow to run. Use this property if you need to check for that case:

property BaseClient.authenticated: bool

Shows whether initial authentication with the server has already been performed.

Bulking

Asynchronous client implementations support batching together arbitrary requests into a single HTTP call:

async AsyncClient.bulk(action_1: Awaitable[_T1], /) Tuple[_T1]
async AsyncClient.bulk(action_1: Awaitable[_T1], action_2: Awaitable[_T2], /) Tuple[_T1, _T2]
async AsyncClient.bulk(action_1: Awaitable[_T1], action_2: Awaitable[_T2], action_3: Awaitable[_T3], /) Tuple[_T1, _T2, _T3]
async AsyncClient.bulk(action_1: Awaitable[_T1], action_2: Awaitable[_T2], action_3: Awaitable[_T3], action_4: Awaitable[_T4], /) Tuple[_T1, _T2, _T3, _T4]
async AsyncClient.bulk(action_1: Awaitable[_T1], action_2: Awaitable[_T2], action_3: Awaitable[_T3], action_4: Awaitable[_T4], action_5: Awaitable[_T5], /) Tuple[_T1, _T2, _T3, _T4, _T5]
async AsyncClient.bulk(action_1: Awaitable[_T1], action_2: Awaitable[_T2], action_3: Awaitable[_T3], action_4: Awaitable[_T4], action_5: Awaitable[_T5], action_6: Awaitable[_T6], /) Tuple[_T1, _T2, _T3, _T4, _T5, _T6]
async AsyncClient.bulk(*actions: Awaitable[Any]) Tuple[Any, ...]

Run a sequence of actions that require server communication together.

This will use Sugar’s Bulk API to batch all actions together and send them as a single HTTP request. It works similarly to asyncio.gather() in that this method will resolve once the result for all provided awaitables is available and return them as a tuple. You can also use this method to gather together multiple awaitables where only one of them uses the server. If an action doesn’t resolve after the first request (for example because it needs a second one), a second batch will be started.

Do not use this is threaded environments – the implementation is not thread-safe.

Here is an example usage:

first_record, _, last_record = await crm.bulk(
  MyModel.find()[0],
  some_record.delete(),
  MyModel.find()[-1]
)

Note

Due to shortcomings of the Python typing system, this method will only yield the correct types for up to 6 arguments. Bulking more actions will work, but the returned type will be Any. In that case, you will need to use typing.cast() to force the correct types.

Bundled clients

Zucker currently bundles two client implementations:

RequestsClient (synchronous)

class zucker.RequestsClient(base_url: str, username: str, password: str, *, client_platform: str = 'zucker', verify_ssl: bool = True)

Synchronous client implementation using requests.

AioClient (asynchronous)

class zucker.AioClient(base_url: str, username: str, password: str, *, client_platform: str = 'zucker', verify_ssl: bool = True)

Asynchronous client implementation using aiohttp.

Implementing your own client

If you are already using an HTTP library that isn’t supported by Zucker yet, you can write your own client. This isn’t much work — you basically only need to implement the raw_request() method. Choose one of SyncClient or AsyncClient as the base class. As a reference, have a look at the provided client implementations — they also inherit from the two aforementioned base classes.

class zucker.client.base.SyncClient(base_url: str, username: str, password: str, *, client_platform: str = 'zucker', verify_ssl: bool = True)
class zucker.client.base.AsyncClient(base_url: str, username: str, password: str, *, client_platform: str = 'zucker', verify_ssl: bool = True)