foldset-fastapi 0.1.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,40 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules
|
|
3
|
+
.pnpm-store
|
|
4
|
+
|
|
5
|
+
# Build outputs
|
|
6
|
+
dist
|
|
7
|
+
# TODO: Remove these exceptions once packages are published to npm.
|
|
8
|
+
# Currently committing dist/ because Vercel doesn't run prepare scripts
|
|
9
|
+
# for git dependencies properly.
|
|
10
|
+
!packages/typescript/nextjs/dist
|
|
11
|
+
!packages/typescript/core/dist
|
|
12
|
+
build
|
|
13
|
+
.next
|
|
14
|
+
out
|
|
15
|
+
|
|
16
|
+
# Environment files
|
|
17
|
+
.env
|
|
18
|
+
.env.local
|
|
19
|
+
.env.*.local
|
|
20
|
+
|
|
21
|
+
# IDE
|
|
22
|
+
.idea
|
|
23
|
+
.vscode
|
|
24
|
+
*.swp
|
|
25
|
+
*.swo
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
30
|
+
|
|
31
|
+
# Debug logs
|
|
32
|
+
npm-debug.log*
|
|
33
|
+
pnpm-debug.log*
|
|
34
|
+
|
|
35
|
+
# TypeScript
|
|
36
|
+
*.tsbuildinfo
|
|
37
|
+
|
|
38
|
+
# Misc
|
|
39
|
+
*.log
|
|
40
|
+
.cache
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: foldset-fastapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FastAPI middleware for Foldset payment gating
|
|
5
|
+
Project-URL: Homepage, https://foldset.com
|
|
6
|
+
Project-URL: Documentation, https://docs.foldset.com
|
|
7
|
+
Project-URL: Repository, https://github.com/foldset/sdks
|
|
8
|
+
Author-email: Foldset <team@foldset.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: agents,ai,fastapi,foldset,micropayments,middleware
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Framework :: FastAPI
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: fastapi>=0.100.0
|
|
21
|
+
Requires-Dist: foldset>=0.1.0
|
|
22
|
+
Requires-Dist: starlette>=0.27.0
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from foldset.types import RequestAdapter
|
|
6
|
+
from starlette.requests import Request
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FastAPIAdapter(RequestAdapter):
|
|
10
|
+
def __init__(self, request: Request) -> None:
|
|
11
|
+
self._request = request
|
|
12
|
+
self._body: Any | None = None
|
|
13
|
+
|
|
14
|
+
def get_ip_address(self) -> str | None:
|
|
15
|
+
forwarded = self.get_header("x-forwarded-for")
|
|
16
|
+
if forwarded:
|
|
17
|
+
return forwarded.split(",")[0].strip()
|
|
18
|
+
if self._request.client:
|
|
19
|
+
return self._request.client.host
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
def get_header(self, name: str) -> str | None:
|
|
23
|
+
return self._request.headers.get(name)
|
|
24
|
+
|
|
25
|
+
def get_method(self) -> str:
|
|
26
|
+
return self._request.method
|
|
27
|
+
|
|
28
|
+
def get_path(self) -> str:
|
|
29
|
+
return self._request.url.path
|
|
30
|
+
|
|
31
|
+
def get_url(self) -> str:
|
|
32
|
+
return str(self._request.url)
|
|
33
|
+
|
|
34
|
+
def get_host(self) -> str:
|
|
35
|
+
return self._request.url.hostname or ""
|
|
36
|
+
|
|
37
|
+
def get_accept_header(self) -> str:
|
|
38
|
+
return self.get_header("accept") or ""
|
|
39
|
+
|
|
40
|
+
def get_user_agent(self) -> str:
|
|
41
|
+
return self.get_header("user-agent") or ""
|
|
42
|
+
|
|
43
|
+
def get_query_params(self) -> dict[str, str | list[str]] | None:
|
|
44
|
+
result: dict[str, str | list[str]] = {}
|
|
45
|
+
for key, value in self._request.query_params.multi_items():
|
|
46
|
+
existing = result.get(key)
|
|
47
|
+
if existing is None:
|
|
48
|
+
result[key] = value
|
|
49
|
+
elif isinstance(existing, list):
|
|
50
|
+
existing.append(value)
|
|
51
|
+
else:
|
|
52
|
+
result[key] = [existing, value]
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
def get_query_param(self, name: str) -> str | list[str] | None:
|
|
56
|
+
params = self.get_query_params()
|
|
57
|
+
if params:
|
|
58
|
+
return params.get(name)
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
async def get_body(self) -> Any:
|
|
62
|
+
if self._body is None:
|
|
63
|
+
try:
|
|
64
|
+
self._body = await self._request.json()
|
|
65
|
+
except Exception:
|
|
66
|
+
self._body = None
|
|
67
|
+
return self._body
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from foldset import WorkerCore, report_error
|
|
7
|
+
from foldset.types import FoldsetOptions
|
|
8
|
+
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
|
9
|
+
from starlette.requests import Request
|
|
10
|
+
from starlette.responses import Response
|
|
11
|
+
|
|
12
|
+
from .adapter import FastAPIAdapter
|
|
13
|
+
|
|
14
|
+
from importlib.metadata import version as _pkg_version
|
|
15
|
+
|
|
16
|
+
PACKAGE_VERSION = _pkg_version("foldset-fastapi")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _set_headers(response: Response, headers: dict[str, str]) -> None:
|
|
20
|
+
for key, value in headers.items():
|
|
21
|
+
response.headers[key] = value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FoldsetMiddleware(BaseHTTPMiddleware):
|
|
25
|
+
def __init__(self, app: Any, options: FoldsetOptions) -> None:
|
|
26
|
+
super().__init__(app)
|
|
27
|
+
self._options = FoldsetOptions(
|
|
28
|
+
api_key=options.api_key,
|
|
29
|
+
redis_credentials=options.redis_credentials,
|
|
30
|
+
platform="fastapi",
|
|
31
|
+
sdk_version=PACKAGE_VERSION,
|
|
32
|
+
)
|
|
33
|
+
self._disabled = not options.api_key
|
|
34
|
+
if self._disabled:
|
|
35
|
+
import warnings
|
|
36
|
+
warnings.warn("[foldset] No API key provided, middleware disabled")
|
|
37
|
+
|
|
38
|
+
async def dispatch(
|
|
39
|
+
self, request: Request, call_next: RequestResponseEndpoint
|
|
40
|
+
) -> Response:
|
|
41
|
+
if self._disabled:
|
|
42
|
+
return await call_next(request)
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
core = await WorkerCore.from_options(self._options)
|
|
46
|
+
adapter = FastAPIAdapter(request)
|
|
47
|
+
result = await core.process_request(adapter)
|
|
48
|
+
|
|
49
|
+
if result.type == "health-check":
|
|
50
|
+
response = Response(
|
|
51
|
+
content=result.response.body,
|
|
52
|
+
status_code=result.response.status,
|
|
53
|
+
media_type="application/json",
|
|
54
|
+
)
|
|
55
|
+
_set_headers(response, result.response.headers)
|
|
56
|
+
return response
|
|
57
|
+
|
|
58
|
+
if result.type == "no-payment-required":
|
|
59
|
+
response = await call_next(request)
|
|
60
|
+
if result.headers:
|
|
61
|
+
_set_headers(response, result.headers)
|
|
62
|
+
return response
|
|
63
|
+
|
|
64
|
+
if result.type == "payment-error":
|
|
65
|
+
response = Response(
|
|
66
|
+
content=result.response.body,
|
|
67
|
+
status_code=result.response.status,
|
|
68
|
+
)
|
|
69
|
+
_set_headers(response, result.response.headers)
|
|
70
|
+
return response
|
|
71
|
+
|
|
72
|
+
if result.type == "payment-verified":
|
|
73
|
+
response = await call_next(request)
|
|
74
|
+
|
|
75
|
+
settlement = await core.process_settlement(
|
|
76
|
+
adapter,
|
|
77
|
+
result.payment_payload,
|
|
78
|
+
result.payment_requirements,
|
|
79
|
+
response.status_code,
|
|
80
|
+
result.metadata.request_id,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if settlement.success:
|
|
84
|
+
_set_headers(response, settlement.headers)
|
|
85
|
+
else:
|
|
86
|
+
response = Response(
|
|
87
|
+
content=json.dumps({
|
|
88
|
+
"error": "Settlement failed",
|
|
89
|
+
"details": settlement.error_reason,
|
|
90
|
+
}),
|
|
91
|
+
status_code=402,
|
|
92
|
+
media_type="application/json",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return response
|
|
96
|
+
|
|
97
|
+
except Exception as error:
|
|
98
|
+
# On any error, allow the request through rather than blocking the user.
|
|
99
|
+
await report_error(self._options.api_key, error, FastAPIAdapter(request))
|
|
100
|
+
return await call_next(request)
|
|
101
|
+
|
|
102
|
+
return await call_next(request)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "foldset-fastapi"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "FastAPI middleware for Foldset payment gating"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
authors = [{ name = "Foldset", email = "team@foldset.com" }]
|
|
8
|
+
keywords = ["foldset", "fastapi", "middleware", "micropayments", "ai", "agents"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 3 - Alpha",
|
|
11
|
+
"License :: OSI Approved :: MIT License",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Programming Language :: Python :: 3.11",
|
|
14
|
+
"Programming Language :: Python :: 3.12",
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
"Framework :: FastAPI",
|
|
17
|
+
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"foldset>=0.1.0",
|
|
21
|
+
"fastapi>=0.100.0",
|
|
22
|
+
"starlette>=0.27.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://foldset.com"
|
|
27
|
+
Documentation = "https://docs.foldset.com"
|
|
28
|
+
Repository = "https://github.com/foldset/sdks"
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["hatchling"]
|
|
32
|
+
build-backend = "hatchling.build"
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.wheel]
|
|
35
|
+
packages = ["foldset_fastapi"]
|