Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ Contents

* `Configuring the httpx client`_

* `Serializing URL search params`_

* `Development and Testing`_

* `Quickstart`_
Expand Down Expand Up @@ -518,6 +520,58 @@ precedence over the defaults the SDK sets:
},
)

Serializing URL search params
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The Seam API parses URL search params as complex types.
If you call it with your own HTTP client,
``serialize_url_search_params`` is exported for that purpose:

.. code-block:: python

import httpx
from seam import serialize_url_search_params

httpx.get(
"https://connect.getseam.com/devices/list",
params=serialize_url_search_params({"device_ids": ["device1", "device2"]}),
headers={"Authorization": "Bearer your-api-key"},
)

The serialization defines the name and value of each search param,
where every value is a string.
``UrlSearchParams`` holds those pairs and renders the query string,
as `URLSearchParams`_ does for the `reference implementation`_:

.. code-block:: python

from seam import UrlSearchParams, update_url_search_params

search_params = UrlSearchParams()

update_url_search_params(search_params, {"device_ids": ["device1", "device2"]})

list(search_params)
# => [('device_ids', 'device1'), ('device_ids', 'device2')]

str(search_params)
# => 'device_ids=device1&device_ids=device2'

Pass either the query string or the pairs to your HTTP client.
A client may percent-encode a few characters differently than
``URLSearchParams`` does, e.g. httpx escapes ``*`` and unescapes ``~``,
which the Seam API reads as the same params either way.

A param set to ``None`` is omitted, while a param set to ``seam.NULL``
is serialized to an empty value, which the Seam API reads as null.
A param that cannot be represented raises a ``seam.UnserializableParamError``.

The Seam API parses these params with the corresponding `parser`_.

.. _URLSearchParams: https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
.. _reference implementation: https://github.com/seamapi/url-search-params-serializer
.. _parser: https://github.com/seamapi/url-search-params-parser

Development and Testing
-----------------------

Expand Down
7 changes: 7 additions & 0 deletions seam/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@
)
from .seam_webhook import SeamWebhook
from svix.webhooks import WebhookVerificationError as SeamWebhookVerificationError
from .null import NULL, Null
from .url_search_params_serializer import (
UnserializableParamError,
UrlSearchParams,
serialize_url_search_params,
update_url_search_params,
)
62 changes: 62 additions & 0 deletions seam/null.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""The explicit null sentinel used by request params.

Python has a single absence value, ``None``, but the Seam API distinguishes
an omitted param from a param explicitly set to null. For example, in an
update request, an omitted param leaves the current value unchanged,
while a null param unsets the current value.

Since sending null is rarely intended and unsetting a value cannot be undone,
``None`` means the safe option of omitting the param.
Sending null is explicit and always spelled :data:`NULL`.
"""

from typing import Any


class Null:
"""Type of the :data:`NULL` sentinel."""

_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

def __repr__(self):
return "NULL"

def __bool__(self):
return False


NULL = Null()
"""Sentinel for a param explicitly set to null.

Params set to this sentinel are serialized to null,
whereas params set to ``None`` are omitted:

.. code-block:: python

from seam import NULL, serialize_url_search_params

serialize_url_search_params({"name": NULL, "limit": 20})
# => 'limit=20&name='

serialize_url_search_params({"name": None, "limit": 20})
# => 'limit=20'

Use it wherever the Seam API documents null as a meaningful value, e.g.,
to unset a value in an update request, or to filter by an unset value.
"""


def is_null(value: Any) -> bool:
"""Returns whether a value is the :data:`NULL` sentinel.

:param value: The value to check
:type value: Any

:returns: Whether the value is the ``NULL`` sentinel"""

return isinstance(value, Null)
Loading
Loading