Consuming API#

Now the client has been instanciated, registered resources can be consumed.

Consume what is registered#

Reusing the previously registered resource below, an instanciated client can consume only the registered route of that resource.

import blacksmith


class SearchItem(blacksmith.Request):
    name_like = blacksmith.QueryStringField(alias="~name")


class Item(blacksmith.Response):
    name: str


blacksmith.register(
    client_name="api",
    resource="item",
    service="datastore",
    version="v1",
    path="/search",
    collection_contract={
        "GET": (SearchItem, Item),
    },
)

It means that here that che client can consume the collection_get and the get method of the resource. The typing system will proposed other methods, such as post but the "POST" schema has not been regitered, and will raise an error.

In REST, the ~ is widely used to do a like operator, to produce a querystring such as ?~name=…, the parameter SearchItem as below:

items = await cli.item.collection_get({"~name": "..."})

Note

The kwargs syntax SearchItem(**{"~name": "..."}) can also be used.

Using default#

All fields may use a default or a default factory that are serialized in http request.

class Dummy(Request):
   x_message_type: str = HeaderField(default="Foo", alias="X-Message-Type")
   created_at: float = PostBodyField(default_factory=time.time, alias="X-Timestamp")

In this code, the header X-Timestamp will contains float of, time.time() result. And the X-Message-Type is sent with foo.

Dealing with null values#

Lots of api will use an explicit null to patch a resource, and missing values are not patched at all. In blacksmith, explicit None are converted with null, but implicit None are not serialized to http request.

See example below:

class PatchDummy(Request):
   name: str = PathInfoField()
   state: Optional[str] = PostBodyField(None)
   country: Optional[str] = PostBodyField(None)

blacksmith.register(
   client_name="api",
   resource="dummy",
   service="api",
   version="v1",
   path="/dummies/{name}",
   contract={
      "PATCH": (PatchDummy, None),
   },
)

sd = SyncStaticDiscovery({("api", None): "http://srv:8000/"})
cli = SyncClientFactory(sd)
api = cli("api")

# this call api call will patch the state to `null`
# but the ``country`` is not send in the request.

api.patch({"name": "foo", "state": None})

Important

Default values are always sent, so imagine the following example

class PatchDummy(Request):
   name: str = PathInfoField()
   state: Optional[str] = PostBodyField()
   country: Optional[str] = PostBodyField(default="FR")

In that case, the PATCH will always sent a default country.

api.patch({"name": "foo", "state": None, "country": None}) will sent a None value for country.

default values has to be used sparingly.