cyclos-sdk-py 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.
Files changed (76) hide show
  1. cyclos_sdk_py-0.1.0/.env.example +23 -0
  2. cyclos_sdk_py-0.1.0/.github/workflows/publish.yml +63 -0
  3. cyclos_sdk_py-0.1.0/.gitignore +45 -0
  4. cyclos_sdk_py-0.1.0/CHANGELOG.md +19 -0
  5. cyclos_sdk_py-0.1.0/LICENSE +21 -0
  6. cyclos_sdk_py-0.1.0/PKG-INFO +298 -0
  7. cyclos_sdk_py-0.1.0/README.md +246 -0
  8. cyclos_sdk_py-0.1.0/docs/cyclos-api-mapping.md +71 -0
  9. cyclos_sdk_py-0.1.0/openapi.yaml +85182 -0
  10. cyclos_sdk_py-0.1.0/pyproject.toml +98 -0
  11. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/__init__.py +40 -0
  12. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/__main__.py +82 -0
  13. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/accounts/__init__.py +6 -0
  14. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/accounts/client.py +109 -0
  15. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/accounts/client_async.py +102 -0
  16. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/accounts/models.py +57 -0
  17. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/async_client.py +74 -0
  18. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/auth/__init__.py +6 -0
  19. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/auth/client.py +68 -0
  20. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/auth/client_async.py +48 -0
  21. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/auth/models.py +27 -0
  22. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/client.py +77 -0
  23. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/config.py +85 -0
  24. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/constants.py +37 -0
  25. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/exceptions.py +103 -0
  26. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/members/__init__.py +23 -0
  27. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/members/client.py +209 -0
  28. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/members/client_async.py +183 -0
  29. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/members/models.py +75 -0
  30. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/models.py +185 -0
  31. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/operators/__init__.py +23 -0
  32. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/operators/client.py +295 -0
  33. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/operators/client_async.py +231 -0
  34. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/operators/models.py +67 -0
  35. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/organizations/__init__.py +15 -0
  36. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/organizations/client.py +90 -0
  37. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/organizations/client_async.py +70 -0
  38. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/organizations/models.py +19 -0
  39. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/permissions/__init__.py +6 -0
  40. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/permissions/client.py +124 -0
  41. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/permissions/client_async.py +107 -0
  42. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/permissions/models.py +31 -0
  43. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/transactions/__init__.py +6 -0
  44. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/transactions/client.py +69 -0
  45. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/transactions/client_async.py +60 -0
  46. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/transactions/models.py +34 -0
  47. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/transfers/__init__.py +6 -0
  48. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/transfers/client.py +168 -0
  49. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/transfers/client_async.py +135 -0
  50. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/transfers/models.py +75 -0
  51. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/transport/__init__.py +11 -0
  52. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/transport/http.py +715 -0
  53. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/transport/retry.py +99 -0
  54. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/users/__init__.py +6 -0
  55. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/users/client.py +67 -0
  56. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/users/client_async.py +65 -0
  57. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/users/models.py +20 -0
  58. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/webservices/__init__.py +15 -0
  59. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/webservices/client.py +167 -0
  60. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/webservices/client_async.py +128 -0
  61. cyclos_sdk_py-0.1.0/src/angstrom_cyclos/webservices/models.py +57 -0
  62. cyclos_sdk_py-0.1.0/tests/__init__.py +1 -0
  63. cyclos_sdk_py-0.1.0/tests/conftest.py +20 -0
  64. cyclos_sdk_py-0.1.0/tests/integration/__init__.py +1 -0
  65. cyclos_sdk_py-0.1.0/tests/integration/test_integration.py +48 -0
  66. cyclos_sdk_py-0.1.0/tests/test_accounts.py +48 -0
  67. cyclos_sdk_py-0.1.0/tests/test_async.py +64 -0
  68. cyclos_sdk_py-0.1.0/tests/test_auth.py +77 -0
  69. cyclos_sdk_py-0.1.0/tests/test_members.py +111 -0
  70. cyclos_sdk_py-0.1.0/tests/test_operators.py +61 -0
  71. cyclos_sdk_py-0.1.0/tests/test_organizations.py +44 -0
  72. cyclos_sdk_py-0.1.0/tests/test_transactions.py +49 -0
  73. cyclos_sdk_py-0.1.0/tests/test_transfers.py +121 -0
  74. cyclos_sdk_py-0.1.0/tests/test_transport.py +89 -0
  75. cyclos_sdk_py-0.1.0/tests/test_users.py +31 -0
  76. cyclos_sdk_py-0.1.0/tests/test_webservices.py +59 -0
@@ -0,0 +1,23 @@
1
+ # Cyclos API configuration
2
+ # The base URL must include the API path. The OpenAPI spec reports /api, but your
3
+ # installation may use a different prefix such as /uwallet/api.
4
+ CYCLOS_BASE_URL=https://wallet.angstrom-technologies.ug/uwallet/api
5
+
6
+ # Credentials for HTTP Basic authentication during login.
7
+ # Prefer configuring these through a secrets manager rather than .env in production.
8
+ CYCLOS_USERNAME=
9
+ CYCLOS_PASSWORD=
10
+
11
+ # HTTP client settings
12
+ CYCLOS_TIMEOUT=30
13
+ CYCLOS_VERIFY_SSL=true
14
+
15
+ # Retry settings for transient failures only (never applied to non-idempotent financial POSTs)
16
+ CYCLOS_MAX_RETRIES=3
17
+ CYCLOS_RETRY_BACKOFF=0.5
18
+
19
+ # Optional integration test configuration
20
+ CYCLOS_INTEGRATION_TESTS=false
21
+ CYCLOS_TEST_BASE_URL=https://wallet.angstrom-technologies.ug/uwallet/api
22
+ CYCLOS_TEST_USERNAME=
23
+ CYCLOS_TEST_PASSWORD=
@@ -0,0 +1,63 @@
1
+ name: Publish Python Package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+ workflow_dispatch:
8
+ inputs:
9
+ version:
10
+ description: "Version tag to publish (optional)"
11
+ required: false
12
+ type: string
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ build:
19
+ name: Build distribution
20
+ runs-on: ubuntu-latest
21
+
22
+ steps:
23
+ - name: Checkout
24
+ uses: actions/checkout@v4
25
+
26
+ - name: Set up Python
27
+ uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.12"
30
+
31
+ - name: Install build tools
32
+ run: python -m pip install --upgrade build
33
+
34
+ - name: Build package
35
+ run: python -m build
36
+
37
+ - name: Upload distributions
38
+ uses: actions/upload-artifact@v4
39
+ with:
40
+ name: dist
41
+ path: dist/
42
+ if-no-files-found: error
43
+
44
+ publish:
45
+ name: Publish to PyPI
46
+ needs: build
47
+ runs-on: ubuntu-latest
48
+ environment:
49
+ name: pypi
50
+ url: https://pypi.org/project/angstrom-cyclos/
51
+ permissions:
52
+ id-token: write
53
+ steps:
54
+ - name: Download distributions
55
+ uses: actions/download-artifact@v4
56
+ with:
57
+ name: dist
58
+ path: dist/
59
+
60
+ - name: Publish package to PyPI
61
+ uses: pypa/gh-action-pypi-publish@release/v1
62
+ with:
63
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,45 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ .Python
6
+ build/
7
+ develop-eggs/
8
+ dist/
9
+ downloads/
10
+ eggs/
11
+ .eggs/
12
+ lib/
13
+ lib64/
14
+ parts/
15
+ sdist/
16
+ var/
17
+ wheels/
18
+ *.egg-info/
19
+ .installed.cfg
20
+ *.egg
21
+
22
+ # Virtual environments
23
+ .env
24
+ .venv
25
+ env/
26
+ venv/
27
+ ENV/
28
+
29
+ # IDE
30
+ .vscode/
31
+ .idea/
32
+ *.swp
33
+ *.swo
34
+
35
+ # Testing
36
+ .coverage
37
+ .pytest_cache/
38
+ .mypy_cache/
39
+ .ruff_cache/
40
+ htmlcov/
41
+ .tox/
42
+
43
+ # OS
44
+ .DS_Store
45
+ Thumbs.db
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (Unreleased)
4
+
5
+ ### Added
6
+ - Initial production-grade Python SDK for Cyclos 4.16 REST API.
7
+ - Synchronous `CyclosClient` and asynchronous `AsyncCyclosClient`.
8
+ - Environment-based configuration via `CyclosConfig` / `pydantic-settings`.
9
+ - Modular sub-clients: `auth`, `members`, `users`, `organizations`, `operators`, `permissions`, `webservices`, `accounts`, `transfers`, `transactions`.
10
+ - Pydantic v2 request/response models for members, operators, accounts, payments, transfers, and transactions.
11
+ - Structured, secret-safe logging with MSISDN masking.
12
+ - HTTP transport layer with retries, correlation IDs, timeouts, and connection pooling.
13
+ - Tenacity-based retry policy for transient errors only.
14
+ - Idempotency guard for financial `POST` operations.
15
+ - Generic `PaginatedResult[T]` pagination model.
16
+ - Comprehensive error hierarchy mapping Cyclos HTTP status codes.
17
+ - Unit tests using `pytest`, `pytest-asyncio`, and `respx`.
18
+ - Optional integration tests gated by `CYCLOS_INTEGRATION_TESTS`.
19
+ - `mypy` and `ruff` configuration in `pyproject.toml`.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Angstrom Technologies
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.
@@ -0,0 +1,298 @@
1
+ Metadata-Version: 2.5
2
+ Name: cyclos-sdk-py
3
+ Version: 0.1.0
4
+ Summary: Production-grade Python SDK for the Cyclos 4.16 REST API
5
+ Project-URL: Homepage, https://github.com/Angstrom-Technologies/cyclos-sdk-py
6
+ Project-URL: Repository, https://github.com/Angstrom-Technologies/cyclos-sdk-py
7
+ Project-URL: Issues, https://github.com/Angstrom-Technologies/cyclos-sdk-py/issues
8
+ Author-email: Angstrom Technologies <dev@angstrom-technologies.ug>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Angstrom Technologies
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: cyclos,fintech,payments,sdk,wallet
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Programming Language :: Python :: 3.13
39
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
40
+ Requires-Python: >=3.11
41
+ Requires-Dist: httpx<1.0,>=0.27
42
+ Requires-Dist: pydantic-settings<3.0,>=2.1
43
+ Requires-Dist: pydantic<3.0,>=2.5
44
+ Requires-Dist: tenacity<9.0,>=8.2
45
+ Provides-Extra: dev
46
+ Requires-Dist: mypy<2.0,>=1.7; extra == 'dev'
47
+ Requires-Dist: pytest-asyncio<1.0,>=0.23; extra == 'dev'
48
+ Requires-Dist: pytest<9.0,>=7.4; extra == 'dev'
49
+ Requires-Dist: respx<1.0,>=0.21; extra == 'dev'
50
+ Requires-Dist: ruff>=0.6.0; extra == 'dev'
51
+ Description-Content-Type: text/markdown
52
+
53
+ # cyclos-sdk-py
54
+
55
+ Production-grade Python SDK for the Cyclos 4.16 REST API.
56
+
57
+ Published on PyPI as `cyclos-sdk-py`, imported as `angstrom_cyclos`.
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ pip install cyclos-sdk-py
63
+ ```
64
+
65
+ For development:
66
+
67
+ ```bash
68
+ git clone https://github.com/Angstrom-Technologies/cyclos-sdk-py.git
69
+ cd cyclos-sdk-py
70
+ pip install -e ".[dev]"
71
+ ```
72
+
73
+ ## Configuration
74
+
75
+ Set environment variables or pass a `CyclosConfig` object:
76
+
77
+ ```python
78
+ from angstrom_cyclos import CyclosClient, CyclosConfig
79
+
80
+ config = CyclosConfig(
81
+ base_url="https://wallet.angstrom-technologies.ug/uwallet/api",
82
+ username="service_client",
83
+ password="...",
84
+ )
85
+
86
+ client = CyclosClient(config)
87
+ ```
88
+
89
+ ### Environment variables
90
+
91
+ ```env
92
+ CYCLOS_BASE_URL=https://wallet.angstrom-technologies.ug/uwallet/api
93
+ CYCLOS_USERNAME=service_client
94
+ CYCLOS_PASSWORD=...
95
+ CYCLOS_TIMEOUT=30
96
+ CYCLOS_VERIFY_SSL=true
97
+ CYCLOS_MAX_RETRIES=3
98
+ CYCLOS_RETRY_BACKOFF=0.5
99
+ ```
100
+
101
+ ## Authentication
102
+
103
+ ```python
104
+ from angstrom_cyclos import CyclosClient, CyclosConfig
105
+
106
+ config = CyclosConfig.from_env()
107
+ client = CyclosClient(config)
108
+
109
+ client.auth.login("customer", "password")
110
+ print(client.auth.is_authenticated())
111
+
112
+ client.auth.logout()
113
+ ```
114
+
115
+ ## Creating members
116
+
117
+ ### Individual
118
+
119
+ ```python
120
+ customer = client.members.create_individual(
121
+ username="256772123456",
122
+ first_name="John",
123
+ last_name="Doe",
124
+ email="john@example.com",
125
+ mobile="256772123456",
126
+ custom_fields={"kycStatus": "VERIFIED"},
127
+ )
128
+ ```
129
+
130
+ ### Merchant
131
+
132
+ ```python
133
+ merchant = client.members.create_merchant(
134
+ username="merchant001",
135
+ name="Merchant One",
136
+ email="merchant@example.com",
137
+ mobile="256772123456",
138
+ group="merchant",
139
+ )
140
+ ```
141
+
142
+ ### Organization
143
+
144
+ ```python
145
+ organization = client.organizations.create(
146
+ group="corporate",
147
+ username="abc_limited",
148
+ name="ABC Limited",
149
+ custom_fields={"businessRegistrationNumber": "800200012345"},
150
+ )
151
+ ```
152
+
153
+ ## Operators
154
+
155
+ ```python
156
+ operator = client.operators.create(
157
+ organization_id=organization.user.id,
158
+ username="operator001",
159
+ first_name="Jane",
160
+ last_name="Doe",
161
+ )
162
+
163
+ client.operators.assign_permissions(
164
+ organization_id=organization.user.id,
165
+ operator_id=operator.user.id,
166
+ permissions=["VIEW_MEMBER", "PERFORM_TRANSFER"],
167
+ )
168
+ ```
169
+
170
+ ## Web service client
171
+
172
+ ```python
173
+ client.webservices.send_activation_code(
174
+ {"mobilePhone": "256772123456"}
175
+ )
176
+ ws_client = client.webservices.create_client(
177
+ name="Angstrom Wallet API",
178
+ activation_code="123456",
179
+ )
180
+ ```
181
+
182
+ ## Accounts and balances
183
+
184
+ ```python
185
+ accounts = client.accounts.list(member_id="alice")
186
+ account = client.accounts.get(member_id="alice", account_type="mobileWallet")
187
+ balance = client.accounts.get_balance(member_id="alice", account_type="mobileWallet")
188
+ print(balance.balance)
189
+ ```
190
+
191
+ ## Transfers
192
+
193
+ ```python
194
+ from decimal import Decimal
195
+
196
+ transfer = client.transfers.create(
197
+ transfer_type="walletTransfer",
198
+ from_account="alice",
199
+ to_account="bob",
200
+ amount=Decimal("50000"),
201
+ currency="UGX",
202
+ description="P2P transfer",
203
+ idempotency_key="TXN-20260909-000001",
204
+ )
205
+ ```
206
+
207
+ ## Transfer history
208
+
209
+ ```python
210
+ history = client.transfers.history(
211
+ member_id="alice",
212
+ account_type="mobileWallet",
213
+ page=0,
214
+ page_size=50,
215
+ )
216
+ ```
217
+
218
+ ## Transaction lookup
219
+
220
+ ```python
221
+ transaction = client.transactions.get("transaction-id")
222
+ ```
223
+
224
+ ## Error handling
225
+
226
+ ```python
227
+ from angstrom_cyclos import CyclosAuthenticationError, CyclosValidationError, CyclosNotFoundError
228
+
229
+ try:
230
+ client.auth.login("user", "wrong")
231
+ except CyclosAuthenticationError as exc:
232
+ print(exc.status_code)
233
+ except CyclosValidationError as exc:
234
+ print(exc.property_errors)
235
+ except CyclosNotFoundError as exc:
236
+ print(exc.endpoint)
237
+ ```
238
+
239
+ ## Async usage
240
+
241
+ ```python
242
+ import asyncio
243
+ from angstrom_cyclos import AsyncCyclosClient, CyclosConfig
244
+
245
+ async def main() -> None:
246
+ config = CyclosConfig.from_env()
247
+ async with AsyncCyclosClient(config) as client:
248
+ await client.auth.login("user", "pass")
249
+ member = await client.members.get("alice")
250
+ print(member)
251
+
252
+ asyncio.run(main())
253
+ ```
254
+
255
+ ## CLI
256
+
257
+ ```bash
258
+ cyclos --help
259
+ cyclos members get <id>
260
+ cyclos members search <query>
261
+ cyclos accounts balance <member>
262
+ cyclos transfers get <id>
263
+ cyclos transfers history <member>
264
+ ```
265
+
266
+ ## Architecture
267
+
268
+ ```
269
+ Mobile App
270
+ |
271
+ Web App
272
+ |
273
+ SMS / USSD
274
+ |
275
+ Wallet API
276
+ |
277
+ Angstrom Cyclos SDK <-- this package
278
+ |
279
+ Cyclos
280
+ |
281
+ PostgreSQL
282
+ ```
283
+
284
+ This SDK is only an API client. Mobile money integrations (MTN/Airtel) belong
285
+ in the wallet middleware, not in this package.
286
+
287
+ ## Testing
288
+
289
+ ```bash
290
+ pytest
291
+ mypy src
292
+ ruff check src
293
+ ruff format src
294
+ ```
295
+
296
+ ## License
297
+
298
+ MIT License - see [LICENSE](LICENSE).
@@ -0,0 +1,246 @@
1
+ # cyclos-sdk-py
2
+
3
+ Production-grade Python SDK for the Cyclos 4.16 REST API.
4
+
5
+ Published on PyPI as `cyclos-sdk-py`, imported as `angstrom_cyclos`.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install cyclos-sdk-py
11
+ ```
12
+
13
+ For development:
14
+
15
+ ```bash
16
+ git clone https://github.com/Angstrom-Technologies/cyclos-sdk-py.git
17
+ cd cyclos-sdk-py
18
+ pip install -e ".[dev]"
19
+ ```
20
+
21
+ ## Configuration
22
+
23
+ Set environment variables or pass a `CyclosConfig` object:
24
+
25
+ ```python
26
+ from angstrom_cyclos import CyclosClient, CyclosConfig
27
+
28
+ config = CyclosConfig(
29
+ base_url="https://wallet.angstrom-technologies.ug/uwallet/api",
30
+ username="service_client",
31
+ password="...",
32
+ )
33
+
34
+ client = CyclosClient(config)
35
+ ```
36
+
37
+ ### Environment variables
38
+
39
+ ```env
40
+ CYCLOS_BASE_URL=https://wallet.angstrom-technologies.ug/uwallet/api
41
+ CYCLOS_USERNAME=service_client
42
+ CYCLOS_PASSWORD=...
43
+ CYCLOS_TIMEOUT=30
44
+ CYCLOS_VERIFY_SSL=true
45
+ CYCLOS_MAX_RETRIES=3
46
+ CYCLOS_RETRY_BACKOFF=0.5
47
+ ```
48
+
49
+ ## Authentication
50
+
51
+ ```python
52
+ from angstrom_cyclos import CyclosClient, CyclosConfig
53
+
54
+ config = CyclosConfig.from_env()
55
+ client = CyclosClient(config)
56
+
57
+ client.auth.login("customer", "password")
58
+ print(client.auth.is_authenticated())
59
+
60
+ client.auth.logout()
61
+ ```
62
+
63
+ ## Creating members
64
+
65
+ ### Individual
66
+
67
+ ```python
68
+ customer = client.members.create_individual(
69
+ username="256772123456",
70
+ first_name="John",
71
+ last_name="Doe",
72
+ email="john@example.com",
73
+ mobile="256772123456",
74
+ custom_fields={"kycStatus": "VERIFIED"},
75
+ )
76
+ ```
77
+
78
+ ### Merchant
79
+
80
+ ```python
81
+ merchant = client.members.create_merchant(
82
+ username="merchant001",
83
+ name="Merchant One",
84
+ email="merchant@example.com",
85
+ mobile="256772123456",
86
+ group="merchant",
87
+ )
88
+ ```
89
+
90
+ ### Organization
91
+
92
+ ```python
93
+ organization = client.organizations.create(
94
+ group="corporate",
95
+ username="abc_limited",
96
+ name="ABC Limited",
97
+ custom_fields={"businessRegistrationNumber": "800200012345"},
98
+ )
99
+ ```
100
+
101
+ ## Operators
102
+
103
+ ```python
104
+ operator = client.operators.create(
105
+ organization_id=organization.user.id,
106
+ username="operator001",
107
+ first_name="Jane",
108
+ last_name="Doe",
109
+ )
110
+
111
+ client.operators.assign_permissions(
112
+ organization_id=organization.user.id,
113
+ operator_id=operator.user.id,
114
+ permissions=["VIEW_MEMBER", "PERFORM_TRANSFER"],
115
+ )
116
+ ```
117
+
118
+ ## Web service client
119
+
120
+ ```python
121
+ client.webservices.send_activation_code(
122
+ {"mobilePhone": "256772123456"}
123
+ )
124
+ ws_client = client.webservices.create_client(
125
+ name="Angstrom Wallet API",
126
+ activation_code="123456",
127
+ )
128
+ ```
129
+
130
+ ## Accounts and balances
131
+
132
+ ```python
133
+ accounts = client.accounts.list(member_id="alice")
134
+ account = client.accounts.get(member_id="alice", account_type="mobileWallet")
135
+ balance = client.accounts.get_balance(member_id="alice", account_type="mobileWallet")
136
+ print(balance.balance)
137
+ ```
138
+
139
+ ## Transfers
140
+
141
+ ```python
142
+ from decimal import Decimal
143
+
144
+ transfer = client.transfers.create(
145
+ transfer_type="walletTransfer",
146
+ from_account="alice",
147
+ to_account="bob",
148
+ amount=Decimal("50000"),
149
+ currency="UGX",
150
+ description="P2P transfer",
151
+ idempotency_key="TXN-20260909-000001",
152
+ )
153
+ ```
154
+
155
+ ## Transfer history
156
+
157
+ ```python
158
+ history = client.transfers.history(
159
+ member_id="alice",
160
+ account_type="mobileWallet",
161
+ page=0,
162
+ page_size=50,
163
+ )
164
+ ```
165
+
166
+ ## Transaction lookup
167
+
168
+ ```python
169
+ transaction = client.transactions.get("transaction-id")
170
+ ```
171
+
172
+ ## Error handling
173
+
174
+ ```python
175
+ from angstrom_cyclos import CyclosAuthenticationError, CyclosValidationError, CyclosNotFoundError
176
+
177
+ try:
178
+ client.auth.login("user", "wrong")
179
+ except CyclosAuthenticationError as exc:
180
+ print(exc.status_code)
181
+ except CyclosValidationError as exc:
182
+ print(exc.property_errors)
183
+ except CyclosNotFoundError as exc:
184
+ print(exc.endpoint)
185
+ ```
186
+
187
+ ## Async usage
188
+
189
+ ```python
190
+ import asyncio
191
+ from angstrom_cyclos import AsyncCyclosClient, CyclosConfig
192
+
193
+ async def main() -> None:
194
+ config = CyclosConfig.from_env()
195
+ async with AsyncCyclosClient(config) as client:
196
+ await client.auth.login("user", "pass")
197
+ member = await client.members.get("alice")
198
+ print(member)
199
+
200
+ asyncio.run(main())
201
+ ```
202
+
203
+ ## CLI
204
+
205
+ ```bash
206
+ cyclos --help
207
+ cyclos members get <id>
208
+ cyclos members search <query>
209
+ cyclos accounts balance <member>
210
+ cyclos transfers get <id>
211
+ cyclos transfers history <member>
212
+ ```
213
+
214
+ ## Architecture
215
+
216
+ ```
217
+ Mobile App
218
+ |
219
+ Web App
220
+ |
221
+ SMS / USSD
222
+ |
223
+ Wallet API
224
+ |
225
+ Angstrom Cyclos SDK <-- this package
226
+ |
227
+ Cyclos
228
+ |
229
+ PostgreSQL
230
+ ```
231
+
232
+ This SDK is only an API client. Mobile money integrations (MTN/Airtel) belong
233
+ in the wallet middleware, not in this package.
234
+
235
+ ## Testing
236
+
237
+ ```bash
238
+ pytest
239
+ mypy src
240
+ ruff check src
241
+ ruff format src
242
+ ```
243
+
244
+ ## License
245
+
246
+ MIT License - see [LICENSE](LICENSE).