cobrala 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ *.egg-info/
2
+ *.py[cod]
3
+ .cache/
4
+ .coverage
5
+ .env
6
+ .env.*
7
+ .mypy_cache/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .venv*/
11
+ .venv/
12
+ __pycache__/
13
+ build/
14
+ dist/
15
+ htmlcov/
16
+ venv/
@@ -0,0 +1,87 @@
1
+ # CI del paquete. La raíz del repositorio es el paquete.
2
+ #
3
+ # Publicar es empujar un tag:
4
+ #
5
+ # git tag -a v1.0.0 -m "1.0.0" && git push --tags
6
+ #
7
+ # El tag corre lint, tipos y tests; recién si pasan se construye y se sube a
8
+ # PyPI. La subida usa trusted publishing (OIDC), así que no hay ningún token
9
+ # guardado en el repositorio.
10
+
11
+ stages:
12
+ - check
13
+ - build
14
+ - publish
15
+
16
+ default:
17
+ image: python:3.12
18
+ cache:
19
+ key:
20
+ files:
21
+ - pyproject.toml
22
+ paths:
23
+ - .cache/pip
24
+ before_script:
25
+ - pip install -e ".[dev]"
26
+
27
+ variables:
28
+ PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
29
+
30
+ # Verificación
31
+
32
+ lint:
33
+ stage: check
34
+ script:
35
+ - ruff check src tests
36
+ - mypy
37
+
38
+ # Los tests no usan la red ni a Cobrala: un servidor local contesta lo que pide
39
+ # cada test, y las rutas se comparan con el OpenAPI de tests/fixtures/.
40
+ test:
41
+ stage: check
42
+ parallel:
43
+ matrix:
44
+ - PYTHON: ["3.9", "3.10", "3.11", "3.12", "3.13"]
45
+ image: python:$PYTHON
46
+ script:
47
+ - pytest -q
48
+
49
+ # Empaquetado y publicación, solo con tag
50
+
51
+ build:
52
+ stage: build
53
+ rules:
54
+ - if: $CI_COMMIT_TAG
55
+ before_script:
56
+ - pip install build twine
57
+ script:
58
+ # El tag y la versión del paquete tienen que decir lo mismo: si no, se
59
+ # publicaría una versión distinta de la que quedó marcada en el historial.
60
+ - |
61
+ VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
62
+ if [ "$CI_COMMIT_TAG" != "v$VERSION" ]; then
63
+ echo "El tag es $CI_COMMIT_TAG y pyproject.toml dice $VERSION."
64
+ echo "Esperaba el tag v$VERSION."
65
+ exit 1
66
+ fi
67
+ - python -m build
68
+ - twine check dist/*
69
+ artifacts:
70
+ paths:
71
+ - dist/
72
+ expire_in: 1 month
73
+
74
+ publish:
75
+ stage: publish
76
+ rules:
77
+ - if: $CI_COMMIT_TAG
78
+ dependencies:
79
+ - build
80
+ id_tokens:
81
+ PYPI_ID_TOKEN:
82
+ aud: pypi
83
+ before_script:
84
+ - pip install -U twine
85
+ # Sin usuario ni contraseña: twine detecta el token OIDC y lo canjea solo.
86
+ script:
87
+ - twine upload dist/*
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ Los cambios notables de cada versión. El formato sigue
4
+ [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/) y el versionado es
5
+ [semántico](https://semver.org/lang/es/).
6
+
7
+ ## [1.0.0] - 2026-11-14
8
+
9
+ Primera versión publicada.
10
+
11
+ ### Agregado
12
+
13
+ - Una función por cada ruta de la Access API (`/v1`): el catálogo (planes,
14
+ productos y features), los clientes (crearlos, editarlos y cambiarles el
15
+ código, y el link del portal), los permisos (verificar, consumir sin contar dos
16
+ veces, fijar el consumo y sincronizar), las suscripciones (bajas, cambios de
17
+ plan y su vista previa), los cobros (el link de pago y el comprobante), los
18
+ documentos y el checkout de suscripciones y de productos.
19
+ - `every`, para recorrer una lista página por página.
20
+ - `verify_webhook`, que verifica la firma de un webhook sobre el cuerpo crudo, y
21
+ `entitlement_rows`, que les pone nombre a las filas de los permisos.
22
+ - `CobralaError` con el código estable de cada rechazo; `unreachable` cuando
23
+ Cobrala no contesta.
24
+ - Los `429` se reintentan dos veces, esperando lo que dice `Retry-After`.
25
+ - `AsyncCobrala`, con los mismos métodos para `await`.
26
+ - Sin dependencias: solo la biblioteca estándar, Python 3.9 o más.
cobrala-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Araitek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
cobrala-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.5
2
+ Name: cobrala
3
+ Version: 1.0.0
4
+ Summary: Python SDK for the Access API of Cobrala: plans, entitlements, usage and checkout
5
+ Project-URL: Homepage, https://cobrala.com.py/
6
+ Project-URL: Documentación de la API, https://access-api.cobrala.com.py/docs
7
+ Author: Araitek
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: checkout,cobrala,cobros,entitlements,paraguay,suscripciones
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Natural Language :: Spanish
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.9
22
+ Provides-Extra: dev
23
+ Requires-Dist: mypy>=1.8; extra == 'dev'
24
+ Requires-Dist: pytest>=7; extra == 'dev'
25
+ Requires-Dist: ruff>=0.4; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # cobrala (Python)
29
+
30
+ SDK de la Access API de Cobrala, para Python 3.9 o más y sin dependencias. Envuelve cada ruta de la API, que además publica su OpenAPI en `/docs`, y se autentica con `X-API-Key: sk_...`. La versión para TypeScript y JavaScript tiene las mismas funciones: `npm install cobrala`.
31
+
32
+ ```bash
33
+ pip install cobrala
34
+ ```
35
+
36
+ ```python
37
+ from cobrala import Cobrala, CobralaError, verify_webhook
38
+
39
+ cobrala = Cobrala(api_key="sk_...", base_url="https://access-api.cobrala.com.py")
40
+
41
+ # Mostrar los planes en tu sitio
42
+ for plan in cobrala.every(cobrala.plans):
43
+ print(plan["name"], plan["intervals"][0]["prices"][0]["price"])
44
+
45
+ # Vender: abrí la url que vuelve
46
+ sale = cobrala.checkout_subscription(
47
+ price_id=17, client_code="USR-42", legal_name="Ana", email="ana@example.com",
48
+ return_url="https://example.com/gracias",
49
+ )
50
+
51
+ # Verificar y consumir
52
+ if cobrala.verify("USR-42", "facturas")["allowed"]:
53
+ cobrala.consume("USR-42", "facturas", idempotency_key="factura-9921")
54
+
55
+ # Un rechazo tiene su código
56
+ try:
57
+ cobrala.change_plan(sale["subscription_id"], price_id=18, actor="customer")
58
+ except CobralaError as error:
59
+ if error.code == "grace_pay_first":
60
+ ...
61
+
62
+ # Un webhook, sobre el cuerpo crudo
63
+ valid = verify_webhook(secret, raw_body, headers["X-Webhook-Timestamp"], headers["X-Webhook-Signature"])
64
+ ```
65
+
66
+ `AsyncCobrala` tiene los mismos métodos, para `await`. El SDK no guarda respuestas: lo que dice la API tiene que estar bien siempre.
67
+
68
+ ## Lo que resolvés en tu sistema
69
+
70
+ **La clave va solo en tu servidor.** Nunca en el navegador ni en una app móvil: quien la tenga hace todo lo que sus permisos dejan. Cada clave es de un servicio.
71
+
72
+ **Qué campo es el `client_code`.** Elegí un dato que tu sistema ya tenga, único y que no cambie: el id de tu usuario, por ejemplo `USR-42`. Es lo que une a tu usuario con su cliente en Cobrala.
73
+ - Si tu usuario ya existe, mandalo en el checkout. Si el cliente no existe en Cobrala, se crea en la misma llamada con el nombre y el correo que mandes.
74
+ - Si todavía no tiene cuenta (la página de precios abierta), no mandes código: Cobrala crea un cliente con un código generado (`CLI-8F3A21BC`) y lo devuelve junto con la dirección. Guardalo al abrirle la cuenta, o cambialo por el tuyo con `PATCH /v1/clients/{code}`.
75
+ - Cobrala nunca une clientes por correo: el mismo correo con otro código es otro cliente, porque dos personas pueden compartirlo. Para unir a uno que ya estaba cargado, buscalo con `GET /v1/clients?email=` y cambiale el código.
76
+
77
+ **Verificar en vivo o con una copia.** Las dos cosas son válidas:
78
+ - En vivo, `POST /v1/entitlements/verify` en cada acción sensible: siempre dice lo correcto, y cuenta contra el límite de 50 pedidos por segundo de tu clave.
79
+ - Con copia, `GET /v1/entitlements/sync` para armar menús y pantallas sin un pedido por feature. Cada fila trae `resets_at` y el vencimiento más cercano: un reinicio o un vencimiento no es un cambio y no te llega como tal, así que tu copia tiene que mirar esas fechas.
80
+
81
+ **Cada cuánto sincronizar.** Lo normal: el webhook `entitlements.changed` en el momento y, por las dudas, una sincronización cada 15 minutos con `since` igual al `as_of` de la anterior. Sin webhooks, sincronizá más seguido y al entrar cada usuario.
82
+
83
+ **Qué hacer si Cobrala no contesta.** Lo decidís vos según tu negocio. Con copia local, seguí con lo último que sabés. Sin copia, elegí entre dejar pasar (mejor para el usuario) o cortar (mejor para una feature cara), y dejalo escrito en tu código. El SDK reintenta solo los `429`, dos veces, esperando lo que dice `Retry-After`; si Cobrala no contesta, da un error con el código `unreachable`.
84
+
85
+ **Registrar consumo sin contar dos veces.** Mandá `idempotency_key` en cada consumo, con un valor propio del evento (`factura-9921`). Si reintentás con la misma clave, la API contesta lo que contestó y no suma.
86
+
87
+ **Vender.** Tu servidor pide el checkout con el `price_id` y abre la `url` que vuelve: es la página de Cobrala, donde el cliente paga. El resultado nunca lo dice la vuelta del navegador: confirmalo por webhook o verificando. El `return_url` es solo el botón "Volver a" de esa página.
88
+
89
+ **Cambiar de plan.** Con `operator` rige en el acto o se programa, y la diferencia sigue el flujo normal. Con `customer`, subir primero se paga por la `url` que devuelve y bajar se programa. `POST /v1/subscriptions/{id}/change-plan/preview` dice qué va a pasar antes de confirmar.
90
+
91
+ **Webhooks.** Verificá la firma sobre el cuerpo crudo, antes de parsearlo; el SDK trae la función. Las filas de `entitlements.changed` y de la sincronización tienen la misma forma, `[incluida, límite, resets_at, vencimiento]`, y el SDK trae otra función que les pone nombre.
92
+
93
+ ## Lo que necesita cada función
94
+
95
+ | Qué hace | Ruta | Permiso |
96
+ |---|---|---|
97
+ | Planes, productos, features | `GET /v1/plans`, `/v1/products`, `/v1/features` | `plans`, `products`, `features` |
98
+ | Clientes | `GET /v1/clients`, `/v1/clients/{code}` | `clients` |
99
+ | Crear y editar clientes | `POST /v1/clients`, `PATCH /v1/clients/{code}` | `clients_write` |
100
+ | Verificar y sincronizar | `POST /v1/entitlements/verify`, `GET /v1/entitlements/sync` | `entitlements` |
101
+ | Consumir o fijar el consumo | `POST /v1/entitlements/consume`, `/set` | `entitlements_write` |
102
+ | Ver suscripciones y preview del cambio | `GET /v1/subscriptions`, `POST .../change-plan/preview` | `subscriptions` |
103
+ | Bajas y cambios de plan | `POST .../cancel`, `.../cancel/revert`, `.../change-plan`, `.../change-plan/cancel` | `subscriptions_write` |
104
+ | Ver cobros y pedir su link | `GET /v1/payments`, `POST /v1/payments/{id}/pay-link` | `payments` |
105
+ | Subir un comprobante | `POST /v1/payments/{id}/receipt` | `payments_receipt` |
106
+ | Documentos y su PDF | `GET /v1/documents` | `documents` |
107
+ | Vender | `POST /v1/checkout/subscription`, `/product` | `checkout` |
108
+ | Abrir el portal ya autenticado | `POST /v1/clients/{code}/portal-link` | `portal` |
109
+
110
+ Los rechazos son siempre `{"code": ..., "message": ..., "data": {...}}`, y el `code` es estable. Con la cuenta congelada, la clave lee pero no escribe (`account_frozen`); sin la API en el plan, contesta `plan_without_api`.
111
+
112
+ ## Licencia
113
+
114
+ MIT. Ver [`LICENSE`](LICENSE).
@@ -0,0 +1,87 @@
1
+ # cobrala (Python)
2
+
3
+ SDK de la Access API de Cobrala, para Python 3.9 o más y sin dependencias. Envuelve cada ruta de la API, que además publica su OpenAPI en `/docs`, y se autentica con `X-API-Key: sk_...`. La versión para TypeScript y JavaScript tiene las mismas funciones: `npm install cobrala`.
4
+
5
+ ```bash
6
+ pip install cobrala
7
+ ```
8
+
9
+ ```python
10
+ from cobrala import Cobrala, CobralaError, verify_webhook
11
+
12
+ cobrala = Cobrala(api_key="sk_...", base_url="https://access-api.cobrala.com.py")
13
+
14
+ # Mostrar los planes en tu sitio
15
+ for plan in cobrala.every(cobrala.plans):
16
+ print(plan["name"], plan["intervals"][0]["prices"][0]["price"])
17
+
18
+ # Vender: abrí la url que vuelve
19
+ sale = cobrala.checkout_subscription(
20
+ price_id=17, client_code="USR-42", legal_name="Ana", email="ana@example.com",
21
+ return_url="https://example.com/gracias",
22
+ )
23
+
24
+ # Verificar y consumir
25
+ if cobrala.verify("USR-42", "facturas")["allowed"]:
26
+ cobrala.consume("USR-42", "facturas", idempotency_key="factura-9921")
27
+
28
+ # Un rechazo tiene su código
29
+ try:
30
+ cobrala.change_plan(sale["subscription_id"], price_id=18, actor="customer")
31
+ except CobralaError as error:
32
+ if error.code == "grace_pay_first":
33
+ ...
34
+
35
+ # Un webhook, sobre el cuerpo crudo
36
+ valid = verify_webhook(secret, raw_body, headers["X-Webhook-Timestamp"], headers["X-Webhook-Signature"])
37
+ ```
38
+
39
+ `AsyncCobrala` tiene los mismos métodos, para `await`. El SDK no guarda respuestas: lo que dice la API tiene que estar bien siempre.
40
+
41
+ ## Lo que resolvés en tu sistema
42
+
43
+ **La clave va solo en tu servidor.** Nunca en el navegador ni en una app móvil: quien la tenga hace todo lo que sus permisos dejan. Cada clave es de un servicio.
44
+
45
+ **Qué campo es el `client_code`.** Elegí un dato que tu sistema ya tenga, único y que no cambie: el id de tu usuario, por ejemplo `USR-42`. Es lo que une a tu usuario con su cliente en Cobrala.
46
+ - Si tu usuario ya existe, mandalo en el checkout. Si el cliente no existe en Cobrala, se crea en la misma llamada con el nombre y el correo que mandes.
47
+ - Si todavía no tiene cuenta (la página de precios abierta), no mandes código: Cobrala crea un cliente con un código generado (`CLI-8F3A21BC`) y lo devuelve junto con la dirección. Guardalo al abrirle la cuenta, o cambialo por el tuyo con `PATCH /v1/clients/{code}`.
48
+ - Cobrala nunca une clientes por correo: el mismo correo con otro código es otro cliente, porque dos personas pueden compartirlo. Para unir a uno que ya estaba cargado, buscalo con `GET /v1/clients?email=` y cambiale el código.
49
+
50
+ **Verificar en vivo o con una copia.** Las dos cosas son válidas:
51
+ - En vivo, `POST /v1/entitlements/verify` en cada acción sensible: siempre dice lo correcto, y cuenta contra el límite de 50 pedidos por segundo de tu clave.
52
+ - Con copia, `GET /v1/entitlements/sync` para armar menús y pantallas sin un pedido por feature. Cada fila trae `resets_at` y el vencimiento más cercano: un reinicio o un vencimiento no es un cambio y no te llega como tal, así que tu copia tiene que mirar esas fechas.
53
+
54
+ **Cada cuánto sincronizar.** Lo normal: el webhook `entitlements.changed` en el momento y, por las dudas, una sincronización cada 15 minutos con `since` igual al `as_of` de la anterior. Sin webhooks, sincronizá más seguido y al entrar cada usuario.
55
+
56
+ **Qué hacer si Cobrala no contesta.** Lo decidís vos según tu negocio. Con copia local, seguí con lo último que sabés. Sin copia, elegí entre dejar pasar (mejor para el usuario) o cortar (mejor para una feature cara), y dejalo escrito en tu código. El SDK reintenta solo los `429`, dos veces, esperando lo que dice `Retry-After`; si Cobrala no contesta, da un error con el código `unreachable`.
57
+
58
+ **Registrar consumo sin contar dos veces.** Mandá `idempotency_key` en cada consumo, con un valor propio del evento (`factura-9921`). Si reintentás con la misma clave, la API contesta lo que contestó y no suma.
59
+
60
+ **Vender.** Tu servidor pide el checkout con el `price_id` y abre la `url` que vuelve: es la página de Cobrala, donde el cliente paga. El resultado nunca lo dice la vuelta del navegador: confirmalo por webhook o verificando. El `return_url` es solo el botón "Volver a" de esa página.
61
+
62
+ **Cambiar de plan.** Con `operator` rige en el acto o se programa, y la diferencia sigue el flujo normal. Con `customer`, subir primero se paga por la `url` que devuelve y bajar se programa. `POST /v1/subscriptions/{id}/change-plan/preview` dice qué va a pasar antes de confirmar.
63
+
64
+ **Webhooks.** Verificá la firma sobre el cuerpo crudo, antes de parsearlo; el SDK trae la función. Las filas de `entitlements.changed` y de la sincronización tienen la misma forma, `[incluida, límite, resets_at, vencimiento]`, y el SDK trae otra función que les pone nombre.
65
+
66
+ ## Lo que necesita cada función
67
+
68
+ | Qué hace | Ruta | Permiso |
69
+ |---|---|---|
70
+ | Planes, productos, features | `GET /v1/plans`, `/v1/products`, `/v1/features` | `plans`, `products`, `features` |
71
+ | Clientes | `GET /v1/clients`, `/v1/clients/{code}` | `clients` |
72
+ | Crear y editar clientes | `POST /v1/clients`, `PATCH /v1/clients/{code}` | `clients_write` |
73
+ | Verificar y sincronizar | `POST /v1/entitlements/verify`, `GET /v1/entitlements/sync` | `entitlements` |
74
+ | Consumir o fijar el consumo | `POST /v1/entitlements/consume`, `/set` | `entitlements_write` |
75
+ | Ver suscripciones y preview del cambio | `GET /v1/subscriptions`, `POST .../change-plan/preview` | `subscriptions` |
76
+ | Bajas y cambios de plan | `POST .../cancel`, `.../cancel/revert`, `.../change-plan`, `.../change-plan/cancel` | `subscriptions_write` |
77
+ | Ver cobros y pedir su link | `GET /v1/payments`, `POST /v1/payments/{id}/pay-link` | `payments` |
78
+ | Subir un comprobante | `POST /v1/payments/{id}/receipt` | `payments_receipt` |
79
+ | Documentos y su PDF | `GET /v1/documents` | `documents` |
80
+ | Vender | `POST /v1/checkout/subscription`, `/product` | `checkout` |
81
+ | Abrir el portal ya autenticado | `POST /v1/clients/{code}/portal-link` | `portal` |
82
+
83
+ Los rechazos son siempre `{"code": ..., "message": ..., "data": {...}}`, y el `code` es estable. Con la cuenta congelada, la clave lee pero no escribe (`account_frozen`); sin la API en el plan, contesta `plan_without_api`.
84
+
85
+ ## Licencia
86
+
87
+ MIT. Ver [`LICENSE`](LICENSE).
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "cobrala"
7
+ version = "1.0.0"
8
+ description = "Python SDK for the Access API of Cobrala: plans, entitlements, usage and checkout"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [{ name = "Araitek" }]
13
+ keywords = ["cobrala", "suscripciones", "cobros", "entitlements", "checkout", "paraguay"]
14
+ classifiers = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Typing :: Typed",
24
+ "Natural Language :: Spanish",
25
+ ]
26
+ dependencies = []
27
+
28
+ [project.urls]
29
+ Homepage = "https://cobrala.com.py/"
30
+ "Documentación de la API" = "https://access-api.cobrala.com.py/docs"
31
+
32
+ [project.optional-dependencies]
33
+ dev = [
34
+ "pytest>=7",
35
+ "mypy>=1.8",
36
+ "ruff>=0.4",
37
+ ]
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["src/cobrala"]
41
+
42
+ [tool.ruff]
43
+ line-length = 125
44
+ target-version = "py39"
45
+
46
+ [tool.ruff.lint]
47
+ select = ["E", "F", "I", "B", "SIM"]
48
+
49
+ [tool.mypy]
50
+ strict = true
51
+ files = ["src/cobrala"]
52
+ # The SDK returns the JSON of the API as it arrived, without its own types.
53
+ warn_return_any = false
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests"]
@@ -0,0 +1,18 @@
1
+ """
2
+ cobrala: the Python SDK of the Access API of Cobrala.
3
+
4
+ from cobrala import Cobrala
5
+
6
+ cobrala = Cobrala(api_key="sk_...", base_url="https://access-api.cobrala.com.py")
7
+ verdict = cobrala.verify("USR-42", "invoices")
8
+ if verdict["allowed"]:
9
+ cobrala.consume("USR-42", "invoices", idempotency_key="factura-1")
10
+
11
+ It keeps no answers: what the API says has to be right every time. Only the
12
+ standard library is needed. ``AsyncCobrala`` has the same methods, to
13
+ ``await``.
14
+ """
15
+ from .client import AsyncCobrala, Cobrala, CobralaError, entitlement_rows
16
+ from .webhooks import verify_webhook
17
+
18
+ __all__ = ["AsyncCobrala", "Cobrala", "CobralaError", "entitlement_rows", "verify_webhook"]