allowly 0.2.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.
- allowly-0.2.0/.github/workflows/publish.yml +45 -0
- allowly-0.2.0/.github/workflows/verify.yml +24 -0
- allowly-0.2.0/.gitignore +9 -0
- allowly-0.2.0/LICENSE +21 -0
- allowly-0.2.0/PKG-INFO +120 -0
- allowly-0.2.0/README.md +98 -0
- allowly-0.2.0/allowly/__init__.py +55 -0
- allowly-0.2.0/allowly/client.py +792 -0
- allowly-0.2.0/allowly/error.py +34 -0
- allowly-0.2.0/allowly/identifiers.py +47 -0
- allowly-0.2.0/allowly/mcp.py +129 -0
- allowly-0.2.0/allowly/types.py +175 -0
- allowly-0.2.0/allowly/verify.py +154 -0
- allowly-0.2.0/pyproject.toml +39 -0
- allowly-0.2.0/tests/test_client.py +1696 -0
- allowly-0.2.0/tests/test_identifiers.py +39 -0
- allowly-0.2.0/tests/test_mcp.py +123 -0
- allowly-0.2.0/tests/test_verify.py +315 -0
- allowly-0.2.0/uv.lock +1791 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
name: Publish Python SDK
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
environment:
|
|
16
|
+
name: pypi
|
|
17
|
+
url: https://pypi.org/p/allowly
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
- name: Require tag commit on main
|
|
23
|
+
run: |
|
|
24
|
+
git fetch origin main
|
|
25
|
+
git merge-base --is-ancestor "$GITHUB_SHA" origin/main
|
|
26
|
+
- uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.12"
|
|
29
|
+
- name: Install package and build tools
|
|
30
|
+
run: python -m pip install ".[dev]" build twine
|
|
31
|
+
- run: pytest -q
|
|
32
|
+
- name: Check tag matches package version
|
|
33
|
+
run: |
|
|
34
|
+
python - <<'PY'
|
|
35
|
+
import os
|
|
36
|
+
import tomllib
|
|
37
|
+
|
|
38
|
+
with open("pyproject.toml", "rb") as file:
|
|
39
|
+
version = tomllib.load(file)["project"]["version"]
|
|
40
|
+
assert os.environ["GITHUB_REF_NAME"] == f"v{version}"
|
|
41
|
+
PY
|
|
42
|
+
- run: python -m build
|
|
43
|
+
- run: python -m twine check dist/*
|
|
44
|
+
- name: Publish to PyPI
|
|
45
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: verify
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
verify:
|
|
13
|
+
name: Verify Python SDK
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
- name: Install package and build tools
|
|
21
|
+
run: python -m pip install ".[dev]" build twine
|
|
22
|
+
- run: pytest -q
|
|
23
|
+
- run: python -m build
|
|
24
|
+
- run: python -m twine check dist/*
|
allowly-0.2.0/.gitignore
ADDED
allowly-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Allowly
|
|
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.
|
allowly-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: allowly
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python SDK for the Allowly API
|
|
5
|
+
Project-URL: Repository, https://github.com/Allowly-AI/allowly-sdk-python
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: httpx>=0.27.0
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: allowly-receipt-format<4.0.0,>=3.0.0; extra == 'dev'
|
|
12
|
+
Requires-Dist: cryptography>=42; extra == 'dev'
|
|
13
|
+
Requires-Dist: fastmcp<3,>=2.9; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
17
|
+
Provides-Extra: fastmcp
|
|
18
|
+
Requires-Dist: fastmcp<3,>=2.9; extra == 'fastmcp'
|
|
19
|
+
Provides-Extra: verifier
|
|
20
|
+
Requires-Dist: allowly-receipt-format<4.0.0,>=3.0.0; extra == 'verifier'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Allowly Python SDK
|
|
24
|
+
|
|
25
|
+
Async Python client for the Allowly runtime API.
|
|
26
|
+
|
|
27
|
+
MCP middleware ships inside this SDK: `pip install 'allowly[fastmcp]'`, then `from allowly.mcp import AllowlyMCPMiddleware`. In TypeScript, it lives in the separate `@allowly/mcp` package.
|
|
28
|
+
|
|
29
|
+
## Subject authorization pattern
|
|
30
|
+
|
|
31
|
+
Do not send raw user/customer PII to Allowly receipts unless you intentionally
|
|
32
|
+
want it in your audit trail. Create one authorization per subject, store the
|
|
33
|
+
returned authorization ID in your own app database, and use that ID for later checks.
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import asyncio
|
|
37
|
+
import os
|
|
38
|
+
|
|
39
|
+
from allowly import Allowly
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def main() -> None:
|
|
43
|
+
async with Allowly(
|
|
44
|
+
api_key=os.environ["ALLOWLY_API_KEY"],
|
|
45
|
+
base_url=os.getenv("ALLOWLY_API_URL", "https://api.allowly.ai"),
|
|
46
|
+
) as allowly:
|
|
47
|
+
# Your app creates a stable internal subject ID.
|
|
48
|
+
subject_id = "subject_abc123"
|
|
49
|
+
|
|
50
|
+
# Store this in your app table, for example:
|
|
51
|
+
# allowly_authorizations(subject_id, policy_id, allowly_authorization_id, status)
|
|
52
|
+
authorization = await allowly.authorizations.create(
|
|
53
|
+
user_id=f"subject:{subject_id}",
|
|
54
|
+
policy_id="research_agent",
|
|
55
|
+
metadata={"source": "import"},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Before the agent acts, check whether this action is allowed.
|
|
59
|
+
decision = await allowly.check(
|
|
60
|
+
authorization_id=authorization.authorization_id,
|
|
61
|
+
actions=["web.search"],
|
|
62
|
+
resource=f"subject:{subject_id}",
|
|
63
|
+
context={"stage": "research"},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if decision.results["web.search"].decision != "allow":
|
|
67
|
+
raise RuntimeError("Action is not authorized")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
asyncio.run(main())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Local development against the documented Caddy endpoint requires the edge
|
|
74
|
+
token that Cloudflare injects for public traffic. Pass it explicitly:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
Allowly(
|
|
78
|
+
api_key=os.environ["ALLOWLY_API_KEY"],
|
|
79
|
+
base_url="http://localhost:8443",
|
|
80
|
+
dangerously_allow_insecure_base_url=True,
|
|
81
|
+
edge_token=os.environ["ALLOWLY_EDGE_TOKEN"],
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The token is only sent when provided; never set it for the public API.
|
|
86
|
+
|
|
87
|
+
Inline authorization creation requires `agent_id`, `actions`, and `expires_at`.
|
|
88
|
+
Policy-based creation uses `policy_id` instead and rejects inline action or
|
|
89
|
+
decision-override fields.
|
|
90
|
+
|
|
91
|
+
Unavailable checks fail closed unless an action is explicitly mapped to
|
|
92
|
+
`"fail_open"` with `fallback_by_action`. Unmapped actions always fail closed.
|
|
93
|
+
|
|
94
|
+
For actions that need third-party approval, define the escalation rule on the
|
|
95
|
+
agent policy, create the authorization from that `policy_id`, and then resolve
|
|
96
|
+
returned escalation results with
|
|
97
|
+
`await allowly.escalations.approve(escalation_id, resolved_by="manager:123")`
|
|
98
|
+
or `reject(...)`, then re-check before running the action.
|
|
99
|
+
|
|
100
|
+
If you need lookup by email later, import `from_email` from
|
|
101
|
+
`allowly.identifiers` and store `from_email(email, pepper=APP_PII_PEPPER)`.
|
|
102
|
+
The helper trims and lowercases only, prefixes the result with `email_hmac:v1`,
|
|
103
|
+
and never sends the raw email or pepper to Allowly. Keep the pepper stable and
|
|
104
|
+
backed up; changing it changes derived user IDs. Keep raw names, emails,
|
|
105
|
+
documents, and profile URLs out of Allowly receipts unless those fields are
|
|
106
|
+
intentionally part of your audit record.
|
|
107
|
+
|
|
108
|
+
Do not add raw HTTP fallbacks in application code for APIs the SDK is missing.
|
|
109
|
+
Patch this SDK first, then use the typed client from the app. That keeps the
|
|
110
|
+
integration examples honest and makes SDK gaps visible early.
|
|
111
|
+
|
|
112
|
+
## Offline receipt verification
|
|
113
|
+
|
|
114
|
+
Install `allowly[verifier]` to verify signed receipts locally. The extra uses
|
|
115
|
+
`allowly-receipt-format>=3.0.0,<4.0.0`, which verifies receipt wire format 3 (the package major equals the wire format). `alg` and
|
|
116
|
+
`key_id` are signed top-level fields, and `signature` is the base64url string.
|
|
117
|
+
|
|
118
|
+
Key-document fetching requires HTTPS by default. For the documented local
|
|
119
|
+
Caddy endpoint only, pass `dangerously_allow_insecure_base_url=True` and its
|
|
120
|
+
`edge_token` to `fetch_keys_doc`, matching the client options above.
|
allowly-0.2.0/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Allowly Python SDK
|
|
2
|
+
|
|
3
|
+
Async Python client for the Allowly runtime API.
|
|
4
|
+
|
|
5
|
+
MCP middleware ships inside this SDK: `pip install 'allowly[fastmcp]'`, then `from allowly.mcp import AllowlyMCPMiddleware`. In TypeScript, it lives in the separate `@allowly/mcp` package.
|
|
6
|
+
|
|
7
|
+
## Subject authorization pattern
|
|
8
|
+
|
|
9
|
+
Do not send raw user/customer PII to Allowly receipts unless you intentionally
|
|
10
|
+
want it in your audit trail. Create one authorization per subject, store the
|
|
11
|
+
returned authorization ID in your own app database, and use that ID for later checks.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import asyncio
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
from allowly import Allowly
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def main() -> None:
|
|
21
|
+
async with Allowly(
|
|
22
|
+
api_key=os.environ["ALLOWLY_API_KEY"],
|
|
23
|
+
base_url=os.getenv("ALLOWLY_API_URL", "https://api.allowly.ai"),
|
|
24
|
+
) as allowly:
|
|
25
|
+
# Your app creates a stable internal subject ID.
|
|
26
|
+
subject_id = "subject_abc123"
|
|
27
|
+
|
|
28
|
+
# Store this in your app table, for example:
|
|
29
|
+
# allowly_authorizations(subject_id, policy_id, allowly_authorization_id, status)
|
|
30
|
+
authorization = await allowly.authorizations.create(
|
|
31
|
+
user_id=f"subject:{subject_id}",
|
|
32
|
+
policy_id="research_agent",
|
|
33
|
+
metadata={"source": "import"},
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Before the agent acts, check whether this action is allowed.
|
|
37
|
+
decision = await allowly.check(
|
|
38
|
+
authorization_id=authorization.authorization_id,
|
|
39
|
+
actions=["web.search"],
|
|
40
|
+
resource=f"subject:{subject_id}",
|
|
41
|
+
context={"stage": "research"},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if decision.results["web.search"].decision != "allow":
|
|
45
|
+
raise RuntimeError("Action is not authorized")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
asyncio.run(main())
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Local development against the documented Caddy endpoint requires the edge
|
|
52
|
+
token that Cloudflare injects for public traffic. Pass it explicitly:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
Allowly(
|
|
56
|
+
api_key=os.environ["ALLOWLY_API_KEY"],
|
|
57
|
+
base_url="http://localhost:8443",
|
|
58
|
+
dangerously_allow_insecure_base_url=True,
|
|
59
|
+
edge_token=os.environ["ALLOWLY_EDGE_TOKEN"],
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The token is only sent when provided; never set it for the public API.
|
|
64
|
+
|
|
65
|
+
Inline authorization creation requires `agent_id`, `actions`, and `expires_at`.
|
|
66
|
+
Policy-based creation uses `policy_id` instead and rejects inline action or
|
|
67
|
+
decision-override fields.
|
|
68
|
+
|
|
69
|
+
Unavailable checks fail closed unless an action is explicitly mapped to
|
|
70
|
+
`"fail_open"` with `fallback_by_action`. Unmapped actions always fail closed.
|
|
71
|
+
|
|
72
|
+
For actions that need third-party approval, define the escalation rule on the
|
|
73
|
+
agent policy, create the authorization from that `policy_id`, and then resolve
|
|
74
|
+
returned escalation results with
|
|
75
|
+
`await allowly.escalations.approve(escalation_id, resolved_by="manager:123")`
|
|
76
|
+
or `reject(...)`, then re-check before running the action.
|
|
77
|
+
|
|
78
|
+
If you need lookup by email later, import `from_email` from
|
|
79
|
+
`allowly.identifiers` and store `from_email(email, pepper=APP_PII_PEPPER)`.
|
|
80
|
+
The helper trims and lowercases only, prefixes the result with `email_hmac:v1`,
|
|
81
|
+
and never sends the raw email or pepper to Allowly. Keep the pepper stable and
|
|
82
|
+
backed up; changing it changes derived user IDs. Keep raw names, emails,
|
|
83
|
+
documents, and profile URLs out of Allowly receipts unless those fields are
|
|
84
|
+
intentionally part of your audit record.
|
|
85
|
+
|
|
86
|
+
Do not add raw HTTP fallbacks in application code for APIs the SDK is missing.
|
|
87
|
+
Patch this SDK first, then use the typed client from the app. That keeps the
|
|
88
|
+
integration examples honest and makes SDK gaps visible early.
|
|
89
|
+
|
|
90
|
+
## Offline receipt verification
|
|
91
|
+
|
|
92
|
+
Install `allowly[verifier]` to verify signed receipts locally. The extra uses
|
|
93
|
+
`allowly-receipt-format>=3.0.0,<4.0.0`, which verifies receipt wire format 3 (the package major equals the wire format). `alg` and
|
|
94
|
+
`key_id` are signed top-level fields, and `signature` is the base64url string.
|
|
95
|
+
|
|
96
|
+
Key-document fetching requires HTTPS by default. For the documented local
|
|
97
|
+
Caddy endpoint only, pass `dangerously_allow_insecure_base_url=True` and its
|
|
98
|
+
`edge_token` to `fetch_keys_doc`, matching the client options above.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .client import Allowly
|
|
4
|
+
from .error import AllowlyAPIError, AllowlyProtocolError, FieldError
|
|
5
|
+
from .types import (
|
|
6
|
+
ActionCheckResult,
|
|
7
|
+
ActionCheckResultAllow,
|
|
8
|
+
ActionCheckResultConfirm,
|
|
9
|
+
ActionCheckResultDeny,
|
|
10
|
+
ActionCheckResultEscalate,
|
|
11
|
+
ActionEntry,
|
|
12
|
+
CheckResponse,
|
|
13
|
+
ConfirmationApproveResponse,
|
|
14
|
+
AuthorizationCreateResponse,
|
|
15
|
+
AuthorizationRevokeResponse,
|
|
16
|
+
BudgetInfo,
|
|
17
|
+
BudgetSettlementResponse,
|
|
18
|
+
Decision,
|
|
19
|
+
EscalationInfo,
|
|
20
|
+
EscalationResolveResponse,
|
|
21
|
+
FallbackMode,
|
|
22
|
+
PolicyConditionEvidence,
|
|
23
|
+
PolicyEvalInfo,
|
|
24
|
+
ReceiptEnvelope,
|
|
25
|
+
ReceiptEnvelopePending,
|
|
26
|
+
ReceiptEnvelopeSigned,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"Allowly",
|
|
31
|
+
"AllowlyAPIError",
|
|
32
|
+
"AllowlyProtocolError",
|
|
33
|
+
"FieldError",
|
|
34
|
+
"ActionCheckResult",
|
|
35
|
+
"ActionCheckResultAllow",
|
|
36
|
+
"ActionCheckResultConfirm",
|
|
37
|
+
"ActionCheckResultDeny",
|
|
38
|
+
"ActionCheckResultEscalate",
|
|
39
|
+
"ActionEntry",
|
|
40
|
+
"CheckResponse",
|
|
41
|
+
"BudgetInfo",
|
|
42
|
+
"BudgetSettlementResponse",
|
|
43
|
+
"EscalationInfo",
|
|
44
|
+
"EscalationResolveResponse",
|
|
45
|
+
"AuthorizationCreateResponse",
|
|
46
|
+
"AuthorizationRevokeResponse",
|
|
47
|
+
"ConfirmationApproveResponse",
|
|
48
|
+
"PolicyConditionEvidence",
|
|
49
|
+
"PolicyEvalInfo",
|
|
50
|
+
"ReceiptEnvelope",
|
|
51
|
+
"ReceiptEnvelopePending",
|
|
52
|
+
"ReceiptEnvelopeSigned",
|
|
53
|
+
"Decision",
|
|
54
|
+
"FallbackMode",
|
|
55
|
+
]
|