Resources and Services
Resources¶
A resource can be thought of as an entity that provides one or more HTTP verb accessors and / or subresources.
Resources have a base route
, a name
, (or derived from route
), and a list of handlers.
Handlers are a list of paths and their correspnding HTTP verb. These are the many different ways of interacting with the said resource.
For example, you can define a resource as followed:
user = Resource(
route="/users",
handlers=[
("GET", "/"),
("POST", "/"),
("PUT", "/{user_id:int}"),
("DELETE", "/{user_id:int}"),
("GET", "/posts"),
("POST", "/posts"),
]
)
Resource-wide response model¶
You can define a resource-level response model if you want to streamline the output structure for all the handlers to the same response model.
from pydantic import BaseModel
from arrest import Resource
class UserResponse(BaseModel):
...
user = Resource(
route="/users",
handlers=[
("GET", "/"),
("POST", "/"),
("PUT", "/{user_id:int}"),
("DELETE", "/{user_id:int}"),
],
response_model=UserResponse
)
Using a client directly¶
You can choose to run with your own httpx.AsyncClient
instance. Simply set the client
field to your resource.
...
import httpx
my_client = httpx.AsyncClient(...)
user = Resource(
route="/users",
handlers=[
("GET", "/"),
("POST", "/"),
("PUT", "/{user_id:int}"),
("DELETE", "/{user_id:int}"),
],
response_model=UserResponse,
client=my_client
)
You can also use any instance that is a subclass of httpx.AsyncClient
(e.g. AsyncOauth2Client from authlib)
The caveat is that you have to manually close the client after you are done. Usually by await client.aclose()
or something else.
Note
There is also a client
field in Services
. You can also use it to set a service-wide shared client instance
Using httpx arguments¶
You can directly pass most of the httpx client arguments as kwargs for the Resource instance. This allows you to have a more fine-grained control on configuring the httpx client.
For the full list of available arguments, please check here
Services¶
Services are the main entrypoint to your API calls. A service is a single url endpoint of a server whose REST APIs you are going to interface. A service has the following core fields.
- name - name of the service
- url - URl of the service (without any trailing slashes)
- resources - a list of resources for this service
Using a client directly¶
You can choose to run with your own httpx.AsyncClient
instance. Simply set the client
field to your service.
This client will override any client set by any resource, and will be shared across all the http calls.
...
import httpx
my_client = httpx.AsyncClient(...)
user = Resource(
route="/users",
handlers=[
("GET", "/"),
("POST", "/"),
("PUT", "/{user_id:int}"),
("DELETE", "/{user_id:int}"),
],
response_model=UserResponse,
client=my_client
)
As stated previously, you are in charge of closing the client.
Using httpx arguments¶
You can directly pass most of the httpx client arguments as kwargs for the Service instance.
This will override these fields if also set from any resource under this service.
For the full list of available arguments, please check here
Root resources¶
Root resources are special resource definitions that have an empty (root) route (""
) or ("/"
).
These are usually top-level endpoints usually used for ping or healthcheck.
We use the reserved name root
to identify these root resources.
If you want to integrate a root resource in your service, simply add it to the list of resources.
from arrest import Service, Resource
service = Service(
name="myservice",
url="http://example.com",
resources=[
Resource(
route="",
handlers=[
("GET", ""),
("GET", "/health")
]
)
]
)
Note
You can only have one root resource. A resource that has its base route ""
, and another having base route of "/"
are both root resources and one will override the other.
If you want to have both ""
and "/"
routes accessible, specify them as separate handlers in your root resource
Resource(
route="",
handlers=[
("GET", ""),
("GET", "/"),
("GET", "/health")
]
)
To call the endpoints of root resource, you call the HTTP method on the service directly, only specifying the path.
Alternatively, you can use .root
to explicitly specify the root resource and call its handlers by path and method.
Example
from arrest import Resource, Service
root_resource = Resource(
route="",
handlers=[
("GET", ""), # 1
("GET", "/"), # 2
("GET", "/health") # 3
]
)
myservice = Service(
name="myservice",
url="http://example.com",
resources=[root_resource]
)
await myservice.get("") # calls #1
await myservice.get("/") # calls #2
await myservice.get("/health") # calls #3
await myservice.root.get("/") # also works