paiment 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.
paiment-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: paiment
3
+ Version: 0.2.0
4
+ Summary: Official Python SDK for the Paiment billing API
5
+ Author: Paiment
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://paiment.tech
8
+ Project-URL: Documentation, https://paiment.tech
9
+ Keywords: paiment,billing,sdk,llm,usage
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: httpx>=0.27.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=8.0; extra == "dev"
24
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
25
+ Requires-Dist: mypy>=1.9; extra == "dev"
26
+ Requires-Dist: build; extra == "dev"
27
+ Requires-Dist: twine; extra == "dev"
28
+
29
+ # paiment (Python SDK)
30
+
31
+ Official Python SDK for the [Paiment](https://paiment.tech) billing API.
32
+
33
+ > **npm users:** install `@paiment/sdk` instead. This package is the Python equivalent.
34
+
35
+ Use it in your **backend** to register subscribers, report LLM usage, and manage plans.
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install paiment
41
+ ```
42
+
43
+ Python 3.9+.
44
+
45
+ ## Setup
46
+
47
+ Get an API key from **Settings → API Keys**.
48
+
49
+ | Key format | Notes |
50
+ |------------|--------|
51
+ | `tenantId:workspaceId:secret` | **Recommended** |
52
+ | `tenantId:secret` | Pass `workspace_id` on the client |
53
+
54
+ ```python
55
+ from paiment import PaimentClient
56
+
57
+ client = PaimentClient(api_key="tenantId:workspaceId:secret")
58
+ # client = PaimentClient(api_key="...", base_url="http://localhost:5246")
59
+ ```
60
+
61
+ ## Quick start
62
+
63
+ ```python
64
+ from paiment import PaimentClient
65
+
66
+ with PaimentClient(api_key="tenantId:workspaceId:secret") as client:
67
+ sub = client.subscriptions.create(
68
+ user_id="user_123",
69
+ plan_name="Pro",
70
+ price=2999,
71
+ currency="USD",
72
+ interval="monthly",
73
+ )
74
+
75
+ client.usage.report(
76
+ user_id="user_123",
77
+ input_tokens=1250,
78
+ output_tokens=340,
79
+ provider="openai",
80
+ model="gpt-4o",
81
+ plan_id=sub.plan_id,
82
+ )
83
+
84
+ client.subscriptions.end(sub.id)
85
+ ```
86
+
87
+ ### Async
88
+
89
+ ```python
90
+ from paiment import AsyncPaimentClient
91
+
92
+ async with AsyncPaimentClient(api_key="tenantId:workspaceId:secret") as client:
93
+ sub = await client.subscriptions.create(
94
+ user_id="user_123",
95
+ plan_name="Pro",
96
+ price=2999,
97
+ currency="USD",
98
+ interval="monthly",
99
+ )
100
+ await client.usage.report(
101
+ user_id="user_123",
102
+ input_tokens=1250,
103
+ output_tokens=340,
104
+ provider="openai",
105
+ model="gpt-4o",
106
+ )
107
+ ```
108
+
109
+ ## API reference
110
+
111
+ ### Subscriptions
112
+
113
+ | Method | Description |
114
+ |--------|-------------|
115
+ | `create(user_id, plan_name, price, currency, interval, ...)` | New subscription |
116
+ | `list(user_id=..., status=...)` | List subscriptions |
117
+ | `get(subscription_id)` | Get by id |
118
+ | `update(subscription_id, data)` | Patch subscription |
119
+ | `end(subscription_id)` | Close subscription |
120
+ | `cancel(subscription_id)` | End immediately |
121
+
122
+ ### Users
123
+
124
+ | Method | Description |
125
+ |--------|-------------|
126
+ | `create(user_id, plan_name, price, currency, interval, ...)` | User + subscription shortcut |
127
+ | `get(user_id)` | Get by your user id |
128
+ | `list(plan_name=..., page=..., limit=...)` | List users |
129
+ | `update(user_id, ...)` | Partial update |
130
+ | `delete(user_id)` | Delete user |
131
+
132
+ ### Usage
133
+
134
+ | Method | Description |
135
+ |--------|-------------|
136
+ | `report(user_id, input_tokens, output_tokens, provider, model, ...)` | Record usage |
137
+ | `list(user_id=..., page=..., limit=...)` | List usage rows |
138
+
139
+ ### Plans
140
+
141
+ | Method | Description |
142
+ |--------|-------------|
143
+ | `create(name, currency, list_price_cents=..., ...)` | Create plan |
144
+ | `list()` / `get(id)` / `update(id, ...)` / `delete(id)` | CRUD |
145
+
146
+ ## Fields
147
+
148
+ | Field | Meaning |
149
+ |-------|---------|
150
+ | `user_id` | Your identifier for the end-user |
151
+ | `plan_name` | Plan catalog name |
152
+ | `price` | Subscription price in **cents** |
153
+ | `list_price_cents` | Catalog list price on plans only |
154
+
155
+ ## Errors
156
+
157
+ ```python
158
+ from paiment import PaimentAuthError, PaimentNotFoundError, PaimentValidationError, PaimentError
159
+ ```
160
+
161
+ | Class | HTTP status |
162
+ |-------|-------------|
163
+ | `PaimentAuthError` | 401 |
164
+ | `PaimentNotFoundError` | 404 |
165
+ | `PaimentValidationError` | 400 |
166
+ | `PaimentError` | other |
@@ -0,0 +1,138 @@
1
+ # paiment (Python SDK)
2
+
3
+ Official Python SDK for the [Paiment](https://paiment.tech) billing API.
4
+
5
+ > **npm users:** install `@paiment/sdk` instead. This package is the Python equivalent.
6
+
7
+ Use it in your **backend** to register subscribers, report LLM usage, and manage plans.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install paiment
13
+ ```
14
+
15
+ Python 3.9+.
16
+
17
+ ## Setup
18
+
19
+ Get an API key from **Settings → API Keys**.
20
+
21
+ | Key format | Notes |
22
+ |------------|--------|
23
+ | `tenantId:workspaceId:secret` | **Recommended** |
24
+ | `tenantId:secret` | Pass `workspace_id` on the client |
25
+
26
+ ```python
27
+ from paiment import PaimentClient
28
+
29
+ client = PaimentClient(api_key="tenantId:workspaceId:secret")
30
+ # client = PaimentClient(api_key="...", base_url="http://localhost:5246")
31
+ ```
32
+
33
+ ## Quick start
34
+
35
+ ```python
36
+ from paiment import PaimentClient
37
+
38
+ with PaimentClient(api_key="tenantId:workspaceId:secret") as client:
39
+ sub = client.subscriptions.create(
40
+ user_id="user_123",
41
+ plan_name="Pro",
42
+ price=2999,
43
+ currency="USD",
44
+ interval="monthly",
45
+ )
46
+
47
+ client.usage.report(
48
+ user_id="user_123",
49
+ input_tokens=1250,
50
+ output_tokens=340,
51
+ provider="openai",
52
+ model="gpt-4o",
53
+ plan_id=sub.plan_id,
54
+ )
55
+
56
+ client.subscriptions.end(sub.id)
57
+ ```
58
+
59
+ ### Async
60
+
61
+ ```python
62
+ from paiment import AsyncPaimentClient
63
+
64
+ async with AsyncPaimentClient(api_key="tenantId:workspaceId:secret") as client:
65
+ sub = await client.subscriptions.create(
66
+ user_id="user_123",
67
+ plan_name="Pro",
68
+ price=2999,
69
+ currency="USD",
70
+ interval="monthly",
71
+ )
72
+ await client.usage.report(
73
+ user_id="user_123",
74
+ input_tokens=1250,
75
+ output_tokens=340,
76
+ provider="openai",
77
+ model="gpt-4o",
78
+ )
79
+ ```
80
+
81
+ ## API reference
82
+
83
+ ### Subscriptions
84
+
85
+ | Method | Description |
86
+ |--------|-------------|
87
+ | `create(user_id, plan_name, price, currency, interval, ...)` | New subscription |
88
+ | `list(user_id=..., status=...)` | List subscriptions |
89
+ | `get(subscription_id)` | Get by id |
90
+ | `update(subscription_id, data)` | Patch subscription |
91
+ | `end(subscription_id)` | Close subscription |
92
+ | `cancel(subscription_id)` | End immediately |
93
+
94
+ ### Users
95
+
96
+ | Method | Description |
97
+ |--------|-------------|
98
+ | `create(user_id, plan_name, price, currency, interval, ...)` | User + subscription shortcut |
99
+ | `get(user_id)` | Get by your user id |
100
+ | `list(plan_name=..., page=..., limit=...)` | List users |
101
+ | `update(user_id, ...)` | Partial update |
102
+ | `delete(user_id)` | Delete user |
103
+
104
+ ### Usage
105
+
106
+ | Method | Description |
107
+ |--------|-------------|
108
+ | `report(user_id, input_tokens, output_tokens, provider, model, ...)` | Record usage |
109
+ | `list(user_id=..., page=..., limit=...)` | List usage rows |
110
+
111
+ ### Plans
112
+
113
+ | Method | Description |
114
+ |--------|-------------|
115
+ | `create(name, currency, list_price_cents=..., ...)` | Create plan |
116
+ | `list()` / `get(id)` / `update(id, ...)` / `delete(id)` | CRUD |
117
+
118
+ ## Fields
119
+
120
+ | Field | Meaning |
121
+ |-------|---------|
122
+ | `user_id` | Your identifier for the end-user |
123
+ | `plan_name` | Plan catalog name |
124
+ | `price` | Subscription price in **cents** |
125
+ | `list_price_cents` | Catalog list price on plans only |
126
+
127
+ ## Errors
128
+
129
+ ```python
130
+ from paiment import PaimentAuthError, PaimentNotFoundError, PaimentValidationError, PaimentError
131
+ ```
132
+
133
+ | Class | HTTP status |
134
+ |-------|-------------|
135
+ | `PaimentAuthError` | 401 |
136
+ | `PaimentNotFoundError` | 404 |
137
+ | `PaimentValidationError` | 400 |
138
+ | `PaimentError` | other |
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from ._errors import PaimentAuthError, PaimentError, PaimentNotFoundError, PaimentValidationError
6
+ from ._http import AsyncHttpClient, HttpClient
7
+ from ._models import FixedPrice, Page, Plan, Pagination, Subscription, Usage, UsageBasedPrice, User
8
+ from .resources.plans import AsyncPlansResource, PlansResource
9
+ from .resources.subscriptions import AsyncSubscriptionsResource, SubscriptionsResource
10
+ from .resources.usage import AsyncUsageResource, UsageResource
11
+ from .resources.users import AsyncUsersResource, UsersResource
12
+
13
+ __all__ = [
14
+ "PaimentClient",
15
+ "AsyncPaimentClient",
16
+ "PaimentError",
17
+ "PaimentAuthError",
18
+ "PaimentNotFoundError",
19
+ "PaimentValidationError",
20
+ "User",
21
+ "Subscription",
22
+ "Plan",
23
+ "UsageBasedPrice",
24
+ "FixedPrice",
25
+ "Usage",
26
+ "Page",
27
+ "Pagination",
28
+ ]
29
+
30
+ _DEFAULT_BASE_URL = "https://api.paiment.tech"
31
+
32
+
33
+ class PaimentClient:
34
+ def __init__(
35
+ self,
36
+ *,
37
+ api_key: str,
38
+ base_url: str = _DEFAULT_BASE_URL,
39
+ workspace_id: str | None = None,
40
+ ) -> None:
41
+ self._http = HttpClient(api_key=api_key, base_url=base_url, workspace_id=workspace_id)
42
+ self.users = UsersResource(self._http)
43
+ self.subscriptions = SubscriptionsResource(self._http)
44
+ self.plans = PlansResource(self._http)
45
+ self.usage = UsageResource(self._http)
46
+
47
+ def close(self) -> None:
48
+ self._http.close()
49
+
50
+ def __enter__(self) -> "PaimentClient":
51
+ return self
52
+
53
+ def __exit__(self, *_: Any) -> None:
54
+ self.close()
55
+
56
+
57
+ class AsyncPaimentClient:
58
+ def __init__(
59
+ self,
60
+ *,
61
+ api_key: str,
62
+ base_url: str = _DEFAULT_BASE_URL,
63
+ workspace_id: str | None = None,
64
+ ) -> None:
65
+ self._http = AsyncHttpClient(api_key=api_key, base_url=base_url, workspace_id=workspace_id)
66
+ self.users = AsyncUsersResource(self._http)
67
+ self.subscriptions = AsyncSubscriptionsResource(self._http)
68
+ self.plans = AsyncPlansResource(self._http)
69
+ self.usage = AsyncUsageResource(self._http)
70
+
71
+ async def aclose(self) -> None:
72
+ await self._http.aclose()
73
+
74
+ async def __aenter__(self) -> "AsyncPaimentClient":
75
+ return self
76
+
77
+ async def __aexit__(self, *_: Any) -> None:
78
+ await self.aclose()
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ class PaimentError(Exception):
7
+ def __init__(self, message: str, status_code: int | None = None, body: Any = None) -> None:
8
+ super().__init__(message)
9
+ self.status_code = status_code
10
+ self.body = body
11
+
12
+
13
+ class PaimentAuthError(PaimentError):
14
+ def __init__(self, body: Any = None) -> None:
15
+ super().__init__("Authentication failed. Check your API key.", 401, body)
16
+
17
+
18
+ class PaimentNotFoundError(PaimentError):
19
+ def __init__(self, resource: str = "Resource") -> None:
20
+ super().__init__(f"{resource} not found.", 404)
21
+
22
+
23
+ class PaimentValidationError(PaimentError):
24
+ def __init__(self, body: Any = None) -> None:
25
+ super().__init__("Validation failed.", 400, body)
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from ._errors import PaimentAuthError, PaimentError, PaimentNotFoundError, PaimentValidationError
8
+
9
+ _DEFAULT_TIMEOUT = 30.0
10
+
11
+
12
+ def _raise_for_status(response: httpx.Response) -> None:
13
+ if response.is_success:
14
+ return
15
+ try:
16
+ body: Any = response.json()
17
+ except Exception:
18
+ body = response.text
19
+
20
+ status = response.status_code
21
+ if status == 401:
22
+ raise PaimentAuthError(body)
23
+ if status == 404:
24
+ raise PaimentNotFoundError()
25
+ if status == 400:
26
+ raise PaimentValidationError(body)
27
+ raise PaimentError(f"Request failed with status {status}", status, body)
28
+
29
+
30
+ class HttpClient:
31
+ def __init__(self, api_key: str, base_url: str, workspace_id: str | None = None) -> None:
32
+ headers: dict[str, str] = {"X-Api-Key": api_key, "Content-Type": "application/json"}
33
+ if workspace_id:
34
+ headers["X-Workspace-Id"] = workspace_id
35
+ self.workspace_id = workspace_id
36
+ self._client = httpx.Client(
37
+ base_url=base_url.rstrip("/"),
38
+ headers=headers,
39
+ timeout=_DEFAULT_TIMEOUT,
40
+ )
41
+
42
+ def close(self) -> None:
43
+ self._client.close()
44
+
45
+ def __enter__(self) -> "HttpClient":
46
+ return self
47
+
48
+ def __exit__(self, *_: Any) -> None:
49
+ self.close()
50
+
51
+ def get(self, path: str, params: dict[str, Any] | None = None) -> Any:
52
+ r = self._client.get(path, params={k: v for k, v in (params or {}).items() if v is not None})
53
+ _raise_for_status(r)
54
+ return r.json() if r.status_code != 204 else None
55
+
56
+ def post(self, path: str, body: Any = None) -> Any:
57
+ r = self._client.post(path, json=body)
58
+ _raise_for_status(r)
59
+ return r.json() if r.status_code != 204 else None
60
+
61
+ def patch(self, path: str, body: Any = None) -> Any:
62
+ r = self._client.patch(path, json=body)
63
+ _raise_for_status(r)
64
+ return r.json() if r.status_code != 204 else None
65
+
66
+ def put(self, path: str, body: Any = None) -> Any:
67
+ r = self._client.put(path, json=body)
68
+ _raise_for_status(r)
69
+ return r.json() if r.status_code != 204 else None
70
+
71
+ def delete(self, path: str) -> None:
72
+ r = self._client.delete(path)
73
+ _raise_for_status(r)
74
+
75
+
76
+ class AsyncHttpClient:
77
+ def __init__(self, api_key: str, base_url: str, workspace_id: str | None = None) -> None:
78
+ headers: dict[str, str] = {"X-Api-Key": api_key, "Content-Type": "application/json"}
79
+ if workspace_id:
80
+ headers["X-Workspace-Id"] = workspace_id
81
+ self.workspace_id = workspace_id
82
+ self._client = httpx.AsyncClient(
83
+ base_url=base_url.rstrip("/"),
84
+ headers=headers,
85
+ timeout=_DEFAULT_TIMEOUT,
86
+ )
87
+
88
+ async def aclose(self) -> None:
89
+ await self._client.aclose()
90
+
91
+ async def __aenter__(self) -> "AsyncHttpClient":
92
+ return self
93
+
94
+ async def __aexit__(self, *_: Any) -> None:
95
+ await self.aclose()
96
+
97
+ async def get(self, path: str, params: dict[str, Any] | None = None) -> Any:
98
+ r = await self._client.get(path, params={k: v for k, v in (params or {}).items() if v is not None})
99
+ _raise_for_status(r)
100
+ return r.json() if r.status_code != 204 else None
101
+
102
+ async def post(self, path: str, body: Any = None) -> Any:
103
+ r = await self._client.post(path, json=body)
104
+ _raise_for_status(r)
105
+ return r.json() if r.status_code != 204 else None
106
+
107
+ async def patch(self, path: str, body: Any = None) -> Any:
108
+ r = await self._client.patch(path, json=body)
109
+ _raise_for_status(r)
110
+ return r.json() if r.status_code != 204 else None
111
+
112
+ async def put(self, path: str, body: Any = None) -> Any:
113
+ r = await self._client.put(path, json=body)
114
+ _raise_for_status(r)
115
+ return r.json() if r.status_code != 204 else None
116
+
117
+ async def delete(self, path: str) -> None:
118
+ r = await self._client.delete(path)
119
+ _raise_for_status(r)