scrippy_api
REST API client for the Scrippy framework (https://codeberg.org/scrippy).
Requirements
- Python 3.11 or later
Python modules
The modules listed below will be automatically installed.
- requests
- PyYAML
- openapi-core
Installation
Manual
git clone https://codeberg.org/scrippy/scrippy-api.git
cd scrippy-api
python -m pip install -r requirements.txt
make install
With pip
pip install scrippy-api
Usage
The scrippy_api.api module provides:
- the
Clientobject, which queries any REST API uniformly: - the
OpenApiobject, which loads an OpenAPI specification, builds operation URLs and validates requests.
Client.request() returns a requests.Response object (see the documentation) that must be handled by the script.
Both parameter keys and values are automatically encoded when necessary.
Error handling
In case of error (HTTP code >= 400, network error, timeout, refused redirection...), the client raises a scrippy_api.ScrippyApiError exception. When the server responded, the requests.Response object is available in the response attribute of the exception (None otherwise):
from scrippy_api import ScrippyApiError
from scrippy_api.api import Client
client = Client()
try:
response = client.request(method="GET", url="https://montypython.org/user")
except ScrippyApiError as err:
if err.response is not None and err.response.status_code == 404:
print("Unknown user")
This behavior can be inhibited during the client instantiation by setting the exit_on_error parameter to False:
from scrippy_api.api import Client
client = Client(exit_on_error=False)
In this case, any encountered errors will appear in the log file as warning and Client.request() returns:
- the
requests.Responseobject if the server responded, even with an HTTP error code, Noneif no response was received (network error, timeout...).
A requests.Response object with an HTTP error code evaluates to False, so both cases can be handled at once:
response = client.request(method="GET", url="https://montypython.org/user")
if not response:
print("No response or HTTP error")
Whatever the value of exit_on_error, a ScrippyApiError exception is always raised, and nothing is sent, when:
- the HTTP method is not supported,
- the request is not valid against the loaded OpenAPI specification.
SSL certificate verification
The verification of the remote server SSL certificate can be disabled by passing the optional verify parameter of the Client object to False:
from scrippy_api.api import Client
client = Client(verify=False)
Note: Disabling certificate verification is discouraged because it presents real security risks.
Parameters
Parameters of the Client.request() method:
| Parameter | Type | Description | Default value |
|---|---|---|---|
method |
String | HTTP method (see HTTP methods). Mandatory | |
url |
String | URL to reach. Mandatory | |
params |
Dictionary | Query parameters, concatenated to the URL | None |
data |
Dictionary, string or bytes | Data to be sent in the request body (form encoded when a dictionary) | None |
json |
Any JSON serializable object | Data to be sent in the request body in JSON format. Not to be used with data or files |
None |
files |
Dictionary | Files to be uploaded in multipart, in the form {<field name>: <file>} or {<field name>: (<file name>, <file>)} |
None |
headers |
Dictionary | Headers to be sent with the request | None |
cookies |
Dictionary | Cookies to be sent with the request | None |
auth |
Tuple | Username and password for BASIC AUTH authentication | None |
timeout |
Number | Maximum waiting time in seconds to connect and between two received data packets (None: no limit) |
300 |
proxies |
Dictionary | Proxy servers by protocol, e.g. {"https": "http://proxy:3128"} |
None |
allow_redirects |
Boolean | Follow redirections (see Redirections) | None |
validate |
Boolean | Validate the request against the loaded OpenAPI specification (see OpenAPI) | None |
HTTP methods
| HTTP method | Description |
|---|---|
GET |
Retrieve a resource or list of resource URIs |
POST |
Create a resource |
PUT |
Replace or create a resource |
PATCH |
Partially update a resource |
DELETE |
Delete a resource |
HEAD |
Retrieve the headers of a resource, without its content |
OPTIONS |
Retrieve the communication options of a resource (e.g. allowed methods) |
TRACE |
Diagnostic: the server sends back the request as received (rarely enabled) |
Method names are case sensitive ("GET", not "get").
Redirections
By default (allow_redirects=None):
GETfollows redirections,HEADandTRACEdo not follow redirections,POST,PUT,PATCH,DELETEandOPTIONSonly follow307and308redirections, which preserve method and body. Any other redirection (301,302,303) is considered as an error (Redirection on <METHOD>), since following it would silently turn the request into aGETor drop its body.
allow_redirects=True follows all redirections (requests behavior), allow_redirects=False follows none.
Examples
URL with parameters
from scrippy_api.api import Client
params = {"name": "Luiggi Vercotti", "password": "dead/parrot"}
client = Client()
response = client.request(method="GET", url="https://montypython.org/user", params=params)
The called URL will be:
https://montypython.org/user?name=Luiggi+Vercotti&password=dead%2Fparrot
Basic authentication (BASIC AUTH)
from scrippy_api.api import Client
auth = ("Luiggi Vercotti", "dead/parrot")
client = Client()
response = client.request(method="POST", url="https://montypython.org", auth=auth)
Sending data
Creating the Luiggi Vercotti user with the password dead/parrot:
from scrippy_api.api import Client
data = {"name": "Luiggi Vercotti", "password": "dead/parrot"}
client = Client()
response = client.request(method="POST", url="https://montypython.org/user", data=data)
Same request with a JSON body:
response = client.request(method="POST", url="https://montypython.org/user", json=data)
File upload
Upload of the two files ./images/dead_parrot.png and ./images/flying_circus.mp4:
from scrippy_api.api import Client
client = Client()
with open("./images/dead_parrot.png", "rb") as image, open("./images/flying_circus.mp4", "rb") as video:
files = {"image": ("dead_parrot.png", image), "video": ("flying_circus.mp4", video)}
response = client.request(method="POST", url="https://montypython.org/upload", files=files)
Resource modification
Replacing the password of the Luiggi Vercotti user:
from scrippy_api.api import Client
auth = ("Luiggi Vercotti", "dead/parrot")
data = {"password": "live/parrot"}
params = {"name": "Luiggi Vercotti"}
client = Client()
response = client.request(method="PATCH",
url="https://montypython.org/user",
params=params,
data=data,
auth=auth)
File download
Client.download() saves the file in the local_path directory, under the filename name (by default, the last part of the URL). It accepts the timeout, params, headers, cookies, proxies and auth parameters of Client.request().
It returns True on success. On error, it raises a ScrippyApiError exception, or returns False when exit_on_error is False.
from scrippy_api.api import Client
url = "https://montypython.org/inquisition.zip"
local_dir = "/home/luiggi.vercotti"
local_filename = "spanish_inquisition.zip"
client = Client()
if client.download(url, local_dir, local_filename):
print("No one expects the Spanish inquisition")
OpenAPI
The client can load an OpenAPI 3.x specification (JSON or YAML) and validate each request against it before sending it.
Note: Swagger 2.0 specifications are not supported. Convert them to OpenAPI 3 first, with https://converter.swagger.io or npx swagger2openapi.
Loading a specification
from scrippy_api.api import Client
client = Client()
# From an URL: relative server URLs are resolved against the specification URL
client.load_openapi("https://montypython.org/api/openapi.json")
# From a file: base_url is needed only if the specification server URLs are relative
client.load_openapi("./openapi.yaml", base_url="https://montypython.org/")
A specification can also be given at instantiation: Client(openapi=OpenApi.from_file("./openapi.yaml")).
Calling an operation
Client.call() executes an operation of the specification, identified by its operationId (or by "<METHOD> <path>" for operations without operationId). The HTTP method and the URL come from the specification, path parameters are given with path_params and any other Client.request() parameter can be used:
response = client.call("updateUser", path_params={"user": "luiggi"}, json={"password": "live/parrot"})
Validation
A request is validated when a specification is loaded and its URL belongs to one of the specification servers, whether it is sent with Client.call() or Client.request(). The validate parameter forces (True) or disables (False) the validation.
An invalid request is never sent, and a ScrippyApiError exception is raised whatever the value of exit_on_error:
from scrippy_api import ScrippyApiError
try:
client.call("updateUser", path_params={"user": "luiggi"}, json={"password": 42})
except ScrippyApiError as err:
print(err)
# Invalid request: PUT https://montypython.org/api/user/luiggi: Invalid request body: password: 42 is not of type 'string'
To avoid leaking secrets in logs, the error message never contains the request body nor string values.
Requests followed after a 307/308 redirection and Client.download() requests are not validated.
The OpenApi object
The OpenApi object can also be used on its own:
from scrippy_api.api import OpenApi
api = OpenApi.from_file("./openapi.yaml", base_url="https://montypython.org/")
api.get_operation("updateUser")
# {'method': 'PUT', 'path': '/user/{user}', 'servers': ['https://montypython.org/api']}
api.build_url("updateUser", {"user": "luiggi"})
# 'https://montypython.org/api/user/luiggi'
api.matches("https://montypython.org/api/user/luiggi")
# True
OpenApi.validate() validates a requests.PreparedRequest and raises a ScrippyApiError exception if it is not valid.
Metadata
Release files for scrippy-api 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| scrippy_api-2.0.0.tar.gz | 23.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| scrippy_api-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.9 kB
Release files / scrippy_api-2.0.0.tar.gz
| Download URL | scrippy_api-2.0.0.tar.gz |
|---|---|
| Size | 23.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
77c218579c66b25fc759372039405f0c68f5a79a7fe881766d7e440d02eb41fd
|
|
BLAKE2b-256 checksum How to use checksums |
30f38337be70c60f051230d33b2d8f2a93f2dcb496ea1487d00ed7370176b584
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.17
|
Release files / scrippy_api-2.0.0-py3-none-any.whl
| Download URL | scrippy_api-2.0.0-py3-none-any.whl |
|---|---|
| Size | 13.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f2955acd37a1ab95491cf7787f5e74d8b3e15978b9dd1724d5ada697757b48e2
|
|
BLAKE2b-256 checksum How to use checksums |
8c92dd90f1ee94621816c44359400b675cc556c42f60dc7a6610fb950ba67785
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.17
|