Skip to main content

Build Status License Language

Scrippy, my scrangourou friend

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 Client object, which queries any REST API uniformly:
    • Client.request(): executes an HTTP request,
    • Client.download(): downloads a file,
    • Client.load_openapi() and Client.call(): load an OpenAPI specification, then call its operations with validation of each request (see OpenAPI),
  • the OpenApi object, 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.Response object if the server responded, even with an HTTP error code,
  • None if 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):

  • GET follows redirections,
  • HEAD and TRACE do not follow redirections,
  • POST, PUT, PATCH, DELETE and OPTIONS only follow 307 and 308 redirections, 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 a GET or 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)

Source distribution for scrippy-api 2.0.0
File Size Uploaded
scrippy_api-2.0.0.tar.gz 23.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for scrippy-api 2.0.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 release files

1.1.86

2 release files

1.1.85

2 release files

1.1.84

2 release files

1.1.79

2 release files

1.1.78

2 release files

1.1.76

2 release files

1.1.75

2 release files

1.1.74

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page