Resources 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 corresponding HTTP verb. These are the many different ways of interacting with the said resource.
For example, you can define a resource as follows:
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. Pass it via the
config argument:
...
import httpx
from arrest._config import ArrestConfig
my_client = httpx.AsyncClient(...)
user = Resource(
route="/users",
handlers=[
("GET", "/"),
("POST", "/"),
("PUT", "/{user_id:int}"),
("DELETE", "/{user_id:int}"),
],
response_model=UserResponse,
config=ArrestConfig(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 the ArrestConfig passed to Service. You can
use it to set a service-wide shared client instance.
Using httpx arguments¶
You can pass most httpx client arguments via the config argument on Resource.
This gives you fine-grained control over the underlying httpx client.
from arrest._config import ArrestConfig
user = Resource(
route="/users",
handlers=[...],
config=ArrestConfig(
timeout=60,
follow_redirects=True,
verify=False,
http2=True,
),
)
For the full list of available fields, see ArrestConfig.
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
- config - an optional
ArrestConfigfor default settings
Using a client directly¶
You can choose to run with your own httpx.AsyncClient instance. Pass it via the
config argument:
...
import httpx
from arrest._config import ArrestConfig
my_client = httpx.AsyncClient(...)
myservice = Service(
name="myservice",
url="http://example.com/api/v1",
resources=[user],
config=ArrestConfig(client=my_client),
)
As stated previously, you are in charge of closing the client.
Using httpx arguments¶
You can pass most httpx client arguments via the config argument on Service.
This will override these fields if also set from any resource under this service.
from arrest._config import ArrestConfig
myservice = Service(
name="myservice",
url="http://example.com/api/v1",
resources=[user],
config=ArrestConfig(timeout=120, verify=False),
)
For the full list of available fields, see ArrestConfig.
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
To call the endpoints of root resource, you call the HTTP method on the .root resource of the service, only specifying the path.
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.root.get("") # calls #1
await myservice.root.get("/") # calls #2
await myservice.root.get("/health") # calls #3