aiofarmad 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.
- aiofarmad-0.1.0/LICENSE +21 -0
- aiofarmad-0.1.0/PKG-INFO +169 -0
- aiofarmad-0.1.0/README.md +146 -0
- aiofarmad-0.1.0/pyproject.toml +153 -0
- aiofarmad-0.1.0/pyproject.toml.orig +126 -0
- aiofarmad-0.1.0/src/aiofarmad/__init__.py +82 -0
- aiofarmad-0.1.0/src/aiofarmad/_auth.py +243 -0
- aiofarmad-0.1.0/src/aiofarmad/_endpoints.py +554 -0
- aiofarmad-0.1.0/src/aiofarmad/_oauth.py +57 -0
- aiofarmad-0.1.0/src/aiofarmad/_tokens.py +201 -0
- aiofarmad-0.1.0/src/aiofarmad/_transport.py +157 -0
- aiofarmad-0.1.0/src/aiofarmad/client.py +455 -0
- aiofarmad-0.1.0/src/aiofarmad/const.py +40 -0
- aiofarmad-0.1.0/src/aiofarmad/exceptions.py +45 -0
- aiofarmad-0.1.0/src/aiofarmad/models.py +294 -0
- aiofarmad-0.1.0/src/aiofarmad/parsers.py +444 -0
- aiofarmad-0.1.0/src/aiofarmad/py.typed +0 -0
aiofarmad-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daan Vervacke
|
|
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.
|
aiofarmad-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aiofarmad
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial asynchronous Python library to interact with the Mijn Farmad Apotheek API
|
|
5
|
+
Keywords: farmad,apotheek,pharmacy,belgium,api,asyncio,aiohttp,home-assistant
|
|
6
|
+
Author: Daan Vervacke
|
|
7
|
+
Author-email: Daan Vervacke <daan.vervacke@proton.me>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Framework :: AsyncIO
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Topic :: Home Automation
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Dist: aiohttp>=3.14.3
|
|
17
|
+
Requires-Python: >=3.14
|
|
18
|
+
Project-URL: Homepage, https://github.com/DaanVervacke/aiofarmad
|
|
19
|
+
Project-URL: Repository, https://github.com/DaanVervacke/aiofarmad
|
|
20
|
+
Project-URL: Issues, https://github.com/DaanVervacke/aiofarmad/issues
|
|
21
|
+
Project-URL: Changelog, https://github.com/DaanVervacke/aiofarmad/blob/main/CHANGELOG.md
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# aiofarmad
|
|
25
|
+
|
|
26
|
+
[](https://github.com/DaanVervacke/aiofarmad/actions/workflows/check.yml)
|
|
27
|
+
[](https://pypi.org/project/aiofarmad/)
|
|
28
|
+
[](https://pypi.org/project/aiofarmad/)
|
|
29
|
+
[](https://opensource.org/licenses/MIT)
|
|
30
|
+
|
|
31
|
+
Unofficial asynchronous Python library to interact with the Mijn Farmad Apotheek API. Requires Python >= 3.14.
|
|
32
|
+
|
|
33
|
+
This is a reverse-engineered client and the Farmad API may change or withdraw access without notice. It is not affiliated with or endorsed by Farmad.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv add aiofarmad
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Login
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import asyncio
|
|
45
|
+
|
|
46
|
+
from aiofarmad import FarmadClient
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def main() -> None:
|
|
50
|
+
async with FarmadClient(
|
|
51
|
+
email="you@example.com",
|
|
52
|
+
password="your-password",
|
|
53
|
+
) as client:
|
|
54
|
+
await client.async_login()
|
|
55
|
+
account = await client.async_get_account()
|
|
56
|
+
print(account.full_name, account.entitled_pharmacies)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
asyncio.run(main())
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The login scripts the same authorization-code flow the web app uses: PKCE against the hosted login form on `signin.procura.farmad.be`, then a token exchange for an access token and a rotating refresh token. Accounts with multi-factor authentication are not supported and raise `FarmadMfaRequiredError`.
|
|
63
|
+
|
|
64
|
+
## Token persistence
|
|
65
|
+
|
|
66
|
+
Log in once, store the pair that `async_login` returns, and pass it back on the next start. The client refreshes before the access token expires and calls `on_token_refresh` whenever Farmad rotates the pair.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from aiofarmad import FarmadClient
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def run(stored_access: str, stored_refresh: str) -> None:
|
|
73
|
+
async def save_tokens(access_token: str, refresh_token: str | None) -> None:
|
|
74
|
+
await write_tokens_somewhere(access_token, refresh_token)
|
|
75
|
+
|
|
76
|
+
async with FarmadClient(
|
|
77
|
+
access_token=stored_access,
|
|
78
|
+
refresh_token=stored_refresh,
|
|
79
|
+
on_token_refresh=save_tokens,
|
|
80
|
+
) as client:
|
|
81
|
+
scheme = await client.async_get_medication_day_scheme(
|
|
82
|
+
"343602",
|
|
83
|
+
from_=start_of_today,
|
|
84
|
+
until=end_of_today,
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Pharmacists and permissions
|
|
89
|
+
|
|
90
|
+
Every pharmacy-scoped call takes an apb number, the identifier of one pharmacy. An account only has roles at pharmacies it has been linked to through the app's self-onboarding, which the library exposes as well:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
linked = await client.async_link_pharmacy("343602")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Calls against a pharmacy the account has no role at raise `FarmadAuthorizationError`.
|
|
97
|
+
|
|
98
|
+
## Reading data
|
|
99
|
+
|
|
100
|
+
| Method | Returns |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `async_get_account()` | The account with its pharmacy memberships |
|
|
103
|
+
| `async_get_patient()` | The patient record per pharmacy |
|
|
104
|
+
| `async_get_organization(apb)` | Public pharmacy information |
|
|
105
|
+
| `async_get_pharmacy_preferences(apb)` | What the pharmacy allows for orders |
|
|
106
|
+
| `async_get_medication_day_scheme(apb, from_=..., until=...)` | Every intake per day in the window |
|
|
107
|
+
| `async_get_medication_nondaily_products(apb, day=...)` | Medications taken outside the daily scheme |
|
|
108
|
+
| `async_get_conversations(apb)` | Conversations with the pharmacy |
|
|
109
|
+
| `async_get_conversation_messages(apb, customer_account_id)` | Messages in one conversation |
|
|
110
|
+
| `async_get_baskets(apb)` | Submitted orders |
|
|
111
|
+
| `async_get_draft_basket(apb)` | The current draft basket, or `None` |
|
|
112
|
+
|
|
113
|
+
## Ordering
|
|
114
|
+
|
|
115
|
+
Write calls build a draft, submit it, and can cancel a submitted order before the pharmacy processes it:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from aiofarmad import DraftProduct, FarmadClient
|
|
119
|
+
|
|
120
|
+
products = (DraftProduct(product_cnk="1234567", quantity=1),)
|
|
121
|
+
|
|
122
|
+
async with FarmadClient(access_token=..., refresh_token=...) as client:
|
|
123
|
+
draft_id = await client.async_save_draft_basket("343602", products=products)
|
|
124
|
+
await client.async_submit_basket(
|
|
125
|
+
"343602",
|
|
126
|
+
draft_id,
|
|
127
|
+
products=products,
|
|
128
|
+
unit_prices=(("1234567", 4.95),),
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Orders are paid at pickup by default. Most pharmacies do not allow online payments, and asking for one there answers 400. Cancelling a submitted order is the pharmacy's decision: customer accounts regularly get `FarmadAuthorizationError` from `async_cancel_basket`.
|
|
133
|
+
|
|
134
|
+
## Prescriptions
|
|
135
|
+
|
|
136
|
+
Prescriptions sit behind the Belgian eHealth platform, and Farmad gates the eHealth session to the browser that completed the itsme consent. Tested against the platform: replaying a live consent session from any non-browser client, with the exact cookies, the exact tokens, and a browser TLS fingerprint, answers 401 every time. No cookie transfer, token pairing, or fingerprint trick carries the session out of the browser.
|
|
137
|
+
|
|
138
|
+
The library models this honestly:
|
|
139
|
+
|
|
140
|
+
- `async_get_prescriptions` and `async_get_prescription` carry the verified wire paths.
|
|
141
|
+
- Every eHealth call from a non-browser client raises `FarmadEhealthAuthorizationRequiredError`, without burning a refresh token cycle.
|
|
142
|
+
- `ehealth_cookie` exists to pass a consent session through, so the library works the day Farmad relaxes the gate. It currently does not open the prescriptions.
|
|
143
|
+
|
|
144
|
+
Until Farmad changes the platform, prescriptions work in the web app only.
|
|
145
|
+
|
|
146
|
+
## Errors
|
|
147
|
+
|
|
148
|
+
| Exception | Meaning |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| `FarmadAuthenticationError` | Credentials or tokens were rejected |
|
|
151
|
+
| `FarmadMfaRequiredError` | The account needs multi-factor authentication |
|
|
152
|
+
| `FarmadAuthorizationError` | The account has no role at this pharmacy |
|
|
153
|
+
| `FarmadEhealthAuthorizationRequiredError` | The eHealth consent is missing |
|
|
154
|
+
| `FarmadCommunicationError` | The API is unreachable or answered with a failure |
|
|
155
|
+
| `FarmadTimeoutError` | A request exceeded the configured timeout |
|
|
156
|
+
| `FarmadInvalidResponseError` | A response payload was unusable |
|
|
157
|
+
| `FarmadNotFoundError` | The requested object does not exist |
|
|
158
|
+
| `FarmadClientClosedError` | The client was closed |
|
|
159
|
+
|
|
160
|
+
## Development
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
uv sync
|
|
164
|
+
uv run python -m scripts.check
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# aiofarmad
|
|
2
|
+
|
|
3
|
+
[](https://github.com/DaanVervacke/aiofarmad/actions/workflows/check.yml)
|
|
4
|
+
[](https://pypi.org/project/aiofarmad/)
|
|
5
|
+
[](https://pypi.org/project/aiofarmad/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
Unofficial asynchronous Python library to interact with the Mijn Farmad Apotheek API. Requires Python >= 3.14.
|
|
9
|
+
|
|
10
|
+
This is a reverse-engineered client and the Farmad API may change or withdraw access without notice. It is not affiliated with or endorsed by Farmad.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
uv add aiofarmad
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Login
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import asyncio
|
|
22
|
+
|
|
23
|
+
from aiofarmad import FarmadClient
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def main() -> None:
|
|
27
|
+
async with FarmadClient(
|
|
28
|
+
email="you@example.com",
|
|
29
|
+
password="your-password",
|
|
30
|
+
) as client:
|
|
31
|
+
await client.async_login()
|
|
32
|
+
account = await client.async_get_account()
|
|
33
|
+
print(account.full_name, account.entitled_pharmacies)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
asyncio.run(main())
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The login scripts the same authorization-code flow the web app uses: PKCE against the hosted login form on `signin.procura.farmad.be`, then a token exchange for an access token and a rotating refresh token. Accounts with multi-factor authentication are not supported and raise `FarmadMfaRequiredError`.
|
|
40
|
+
|
|
41
|
+
## Token persistence
|
|
42
|
+
|
|
43
|
+
Log in once, store the pair that `async_login` returns, and pass it back on the next start. The client refreshes before the access token expires and calls `on_token_refresh` whenever Farmad rotates the pair.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from aiofarmad import FarmadClient
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def run(stored_access: str, stored_refresh: str) -> None:
|
|
50
|
+
async def save_tokens(access_token: str, refresh_token: str | None) -> None:
|
|
51
|
+
await write_tokens_somewhere(access_token, refresh_token)
|
|
52
|
+
|
|
53
|
+
async with FarmadClient(
|
|
54
|
+
access_token=stored_access,
|
|
55
|
+
refresh_token=stored_refresh,
|
|
56
|
+
on_token_refresh=save_tokens,
|
|
57
|
+
) as client:
|
|
58
|
+
scheme = await client.async_get_medication_day_scheme(
|
|
59
|
+
"343602",
|
|
60
|
+
from_=start_of_today,
|
|
61
|
+
until=end_of_today,
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Pharmacists and permissions
|
|
66
|
+
|
|
67
|
+
Every pharmacy-scoped call takes an apb number, the identifier of one pharmacy. An account only has roles at pharmacies it has been linked to through the app's self-onboarding, which the library exposes as well:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
linked = await client.async_link_pharmacy("343602")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Calls against a pharmacy the account has no role at raise `FarmadAuthorizationError`.
|
|
74
|
+
|
|
75
|
+
## Reading data
|
|
76
|
+
|
|
77
|
+
| Method | Returns |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `async_get_account()` | The account with its pharmacy memberships |
|
|
80
|
+
| `async_get_patient()` | The patient record per pharmacy |
|
|
81
|
+
| `async_get_organization(apb)` | Public pharmacy information |
|
|
82
|
+
| `async_get_pharmacy_preferences(apb)` | What the pharmacy allows for orders |
|
|
83
|
+
| `async_get_medication_day_scheme(apb, from_=..., until=...)` | Every intake per day in the window |
|
|
84
|
+
| `async_get_medication_nondaily_products(apb, day=...)` | Medications taken outside the daily scheme |
|
|
85
|
+
| `async_get_conversations(apb)` | Conversations with the pharmacy |
|
|
86
|
+
| `async_get_conversation_messages(apb, customer_account_id)` | Messages in one conversation |
|
|
87
|
+
| `async_get_baskets(apb)` | Submitted orders |
|
|
88
|
+
| `async_get_draft_basket(apb)` | The current draft basket, or `None` |
|
|
89
|
+
|
|
90
|
+
## Ordering
|
|
91
|
+
|
|
92
|
+
Write calls build a draft, submit it, and can cancel a submitted order before the pharmacy processes it:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from aiofarmad import DraftProduct, FarmadClient
|
|
96
|
+
|
|
97
|
+
products = (DraftProduct(product_cnk="1234567", quantity=1),)
|
|
98
|
+
|
|
99
|
+
async with FarmadClient(access_token=..., refresh_token=...) as client:
|
|
100
|
+
draft_id = await client.async_save_draft_basket("343602", products=products)
|
|
101
|
+
await client.async_submit_basket(
|
|
102
|
+
"343602",
|
|
103
|
+
draft_id,
|
|
104
|
+
products=products,
|
|
105
|
+
unit_prices=(("1234567", 4.95),),
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Orders are paid at pickup by default. Most pharmacies do not allow online payments, and asking for one there answers 400. Cancelling a submitted order is the pharmacy's decision: customer accounts regularly get `FarmadAuthorizationError` from `async_cancel_basket`.
|
|
110
|
+
|
|
111
|
+
## Prescriptions
|
|
112
|
+
|
|
113
|
+
Prescriptions sit behind the Belgian eHealth platform, and Farmad gates the eHealth session to the browser that completed the itsme consent. Tested against the platform: replaying a live consent session from any non-browser client, with the exact cookies, the exact tokens, and a browser TLS fingerprint, answers 401 every time. No cookie transfer, token pairing, or fingerprint trick carries the session out of the browser.
|
|
114
|
+
|
|
115
|
+
The library models this honestly:
|
|
116
|
+
|
|
117
|
+
- `async_get_prescriptions` and `async_get_prescription` carry the verified wire paths.
|
|
118
|
+
- Every eHealth call from a non-browser client raises `FarmadEhealthAuthorizationRequiredError`, without burning a refresh token cycle.
|
|
119
|
+
- `ehealth_cookie` exists to pass a consent session through, so the library works the day Farmad relaxes the gate. It currently does not open the prescriptions.
|
|
120
|
+
|
|
121
|
+
Until Farmad changes the platform, prescriptions work in the web app only.
|
|
122
|
+
|
|
123
|
+
## Errors
|
|
124
|
+
|
|
125
|
+
| Exception | Meaning |
|
|
126
|
+
| --- | --- |
|
|
127
|
+
| `FarmadAuthenticationError` | Credentials or tokens were rejected |
|
|
128
|
+
| `FarmadMfaRequiredError` | The account needs multi-factor authentication |
|
|
129
|
+
| `FarmadAuthorizationError` | The account has no role at this pharmacy |
|
|
130
|
+
| `FarmadEhealthAuthorizationRequiredError` | The eHealth consent is missing |
|
|
131
|
+
| `FarmadCommunicationError` | The API is unreachable or answered with a failure |
|
|
132
|
+
| `FarmadTimeoutError` | A request exceeded the configured timeout |
|
|
133
|
+
| `FarmadInvalidResponseError` | A response payload was unusable |
|
|
134
|
+
| `FarmadNotFoundError` | The requested object does not exist |
|
|
135
|
+
| `FarmadClientClosedError` | The client was closed |
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
uv sync
|
|
141
|
+
uv run python -m scripts.check
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.12.21,<0.13"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aiofarmad"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Unofficial asynchronous Python library to interact with the Mijn Farmad Apotheek API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.14"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Framework :: AsyncIO",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3.14",
|
|
18
|
+
"Topic :: Home Automation",
|
|
19
|
+
"Typing :: Typed",
|
|
20
|
+
]
|
|
21
|
+
keywords = [
|
|
22
|
+
"farmad",
|
|
23
|
+
"apotheek",
|
|
24
|
+
"pharmacy",
|
|
25
|
+
"belgium",
|
|
26
|
+
"api",
|
|
27
|
+
"asyncio",
|
|
28
|
+
"aiohttp",
|
|
29
|
+
"home-assistant",
|
|
30
|
+
]
|
|
31
|
+
dependencies = ["aiohttp>=3.14.3"]
|
|
32
|
+
|
|
33
|
+
[[project.authors]]
|
|
34
|
+
name = "Daan Vervacke"
|
|
35
|
+
email = "daan.vervacke@proton.me"
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/DaanVervacke/aiofarmad"
|
|
39
|
+
Repository = "https://github.com/DaanVervacke/aiofarmad"
|
|
40
|
+
Issues = "https://github.com/DaanVervacke/aiofarmad/issues"
|
|
41
|
+
Changelog = "https://github.com/DaanVervacke/aiofarmad/blob/main/CHANGELOG.md"
|
|
42
|
+
|
|
43
|
+
[dependency-groups]
|
|
44
|
+
dev = [
|
|
45
|
+
"pytest>=9.0.3",
|
|
46
|
+
"pytest-asyncio>=1.3.0",
|
|
47
|
+
"aioresponses>=0.7.9",
|
|
48
|
+
"mypy>=1.13",
|
|
49
|
+
"ruff>=0.16",
|
|
50
|
+
"coverage[toml]>=7.15.2",
|
|
51
|
+
"pyyaml>=6.0.2",
|
|
52
|
+
"types-pyyaml>=6.0.12",
|
|
53
|
+
]
|
|
54
|
+
docs = [
|
|
55
|
+
"furo>=2025.12.19",
|
|
56
|
+
"sphinx>=9.1.0",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[tool.uv]
|
|
60
|
+
required-version = ">=0.12.21,<0.13"
|
|
61
|
+
|
|
62
|
+
[tool.uv.build-backend]
|
|
63
|
+
source-include = ["LICENSE"]
|
|
64
|
+
|
|
65
|
+
[tool.ruff]
|
|
66
|
+
target-version = "py314"
|
|
67
|
+
line-length = 100
|
|
68
|
+
src = [
|
|
69
|
+
"src",
|
|
70
|
+
"tests",
|
|
71
|
+
]
|
|
72
|
+
extend-exclude = [
|
|
73
|
+
".claude",
|
|
74
|
+
".agents",
|
|
75
|
+
".codex",
|
|
76
|
+
".continue",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
[tool.ruff.format]
|
|
80
|
+
docstring-code-format = true
|
|
81
|
+
|
|
82
|
+
[tool.ruff.lint]
|
|
83
|
+
select = ["ALL"]
|
|
84
|
+
ignore = [
|
|
85
|
+
"D",
|
|
86
|
+
"COM812",
|
|
87
|
+
"ISC001",
|
|
88
|
+
"ANN401",
|
|
89
|
+
"PLR0913",
|
|
90
|
+
"PLR0917",
|
|
91
|
+
"TC001",
|
|
92
|
+
"TC002",
|
|
93
|
+
"TC003",
|
|
94
|
+
"CPY001",
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
[tool.ruff.lint.per-file-ignores]
|
|
98
|
+
"scripts/check.py" = [
|
|
99
|
+
"T201",
|
|
100
|
+
"S603",
|
|
101
|
+
]
|
|
102
|
+
"scripts/check_bruno_drift.py" = ["T201"]
|
|
103
|
+
"scripts/probe_farmad.py" = [
|
|
104
|
+
"T201",
|
|
105
|
+
"S105",
|
|
106
|
+
]
|
|
107
|
+
"scripts/capture_ehealth.py" = [
|
|
108
|
+
"T201",
|
|
109
|
+
"S105",
|
|
110
|
+
]
|
|
111
|
+
"scripts/capture_data.py" = [
|
|
112
|
+
"T201",
|
|
113
|
+
"S105",
|
|
114
|
+
]
|
|
115
|
+
"scripts/place_test_order.py" = ["T201"]
|
|
116
|
+
"scripts/_redact.py" = ["T201"]
|
|
117
|
+
"docs/conf.py" = ["INP001"]
|
|
118
|
+
"tests/**" = [
|
|
119
|
+
"S101",
|
|
120
|
+
"S105",
|
|
121
|
+
"S106",
|
|
122
|
+
"SLF001",
|
|
123
|
+
"PLR2004",
|
|
124
|
+
"RUF059",
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
[tool.pytest.ini_options]
|
|
128
|
+
minversion = "9.0"
|
|
129
|
+
testpaths = ["tests"]
|
|
130
|
+
addopts = [
|
|
131
|
+
"-ra",
|
|
132
|
+
"--strict-markers",
|
|
133
|
+
"--strict-config",
|
|
134
|
+
]
|
|
135
|
+
filterwarnings = [
|
|
136
|
+
"error",
|
|
137
|
+
"ignore:Unclosed client session:ResourceWarning",
|
|
138
|
+
"ignore:Unclosed connector:ResourceWarning",
|
|
139
|
+
]
|
|
140
|
+
asyncio_mode = "auto"
|
|
141
|
+
|
|
142
|
+
[tool.coverage.run]
|
|
143
|
+
branch = true
|
|
144
|
+
source = ["src"]
|
|
145
|
+
|
|
146
|
+
[tool.coverage.report]
|
|
147
|
+
show_missing = true
|
|
148
|
+
skip_covered = true
|
|
149
|
+
fail_under = 98
|
|
150
|
+
|
|
151
|
+
[tool.mypy]
|
|
152
|
+
python_version = "3.14"
|
|
153
|
+
strict = true
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.12.21,<0.13"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aiofarmad"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Unofficial asynchronous Python library to interact with the Mijn Farmad Apotheek API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.14"
|
|
13
|
+
authors = [{ name = "Daan Vervacke", email = "daan.vervacke@proton.me" }]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Framework :: AsyncIO",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3.14",
|
|
19
|
+
"Topic :: Home Automation",
|
|
20
|
+
"Typing :: Typed",
|
|
21
|
+
]
|
|
22
|
+
keywords = [
|
|
23
|
+
"farmad",
|
|
24
|
+
"apotheek",
|
|
25
|
+
"pharmacy",
|
|
26
|
+
"belgium",
|
|
27
|
+
"api",
|
|
28
|
+
"asyncio",
|
|
29
|
+
"aiohttp",
|
|
30
|
+
"home-assistant",
|
|
31
|
+
]
|
|
32
|
+
dependencies = ["aiohttp>=3.14.3"]
|
|
33
|
+
|
|
34
|
+
[dependency-groups]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=9.0.3",
|
|
37
|
+
"pytest-asyncio>=1.3.0",
|
|
38
|
+
"aioresponses>=0.7.9",
|
|
39
|
+
"mypy>=1.13",
|
|
40
|
+
"ruff>=0.16",
|
|
41
|
+
"coverage[toml]>=7.15.2",
|
|
42
|
+
"pyyaml>=6.0.2",
|
|
43
|
+
"types-pyyaml>=6.0.12",
|
|
44
|
+
]
|
|
45
|
+
docs = [
|
|
46
|
+
"furo>=2025.12.19",
|
|
47
|
+
"sphinx>=9.1.0",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[project.urls]
|
|
51
|
+
Homepage = "https://github.com/DaanVervacke/aiofarmad"
|
|
52
|
+
Repository = "https://github.com/DaanVervacke/aiofarmad"
|
|
53
|
+
Issues = "https://github.com/DaanVervacke/aiofarmad/issues"
|
|
54
|
+
Changelog = "https://github.com/DaanVervacke/aiofarmad/blob/main/CHANGELOG.md"
|
|
55
|
+
|
|
56
|
+
[tool.uv]
|
|
57
|
+
required-version = ">=0.12.21,<0.13"
|
|
58
|
+
|
|
59
|
+
[tool.uv.build-backend]
|
|
60
|
+
source-include = ["LICENSE"]
|
|
61
|
+
|
|
62
|
+
[tool.ruff]
|
|
63
|
+
target-version = "py314"
|
|
64
|
+
line-length = 100
|
|
65
|
+
src = ["src", "tests"]
|
|
66
|
+
extend-exclude = [".claude", ".agents", ".codex", ".continue"]
|
|
67
|
+
|
|
68
|
+
[tool.ruff.format]
|
|
69
|
+
docstring-code-format = true
|
|
70
|
+
|
|
71
|
+
[tool.ruff.lint]
|
|
72
|
+
select = ["ALL"]
|
|
73
|
+
ignore = [
|
|
74
|
+
"D",
|
|
75
|
+
"COM812",
|
|
76
|
+
"ISC001",
|
|
77
|
+
"ANN401",
|
|
78
|
+
"PLR0913",
|
|
79
|
+
"PLR0917",
|
|
80
|
+
"TC001",
|
|
81
|
+
"TC002",
|
|
82
|
+
"TC003",
|
|
83
|
+
"CPY001",
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
[tool.ruff.lint.per-file-ignores]
|
|
87
|
+
"scripts/check.py" = ["T201", "S603"]
|
|
88
|
+
"scripts/check_bruno_drift.py" = ["T201"]
|
|
89
|
+
"scripts/probe_farmad.py" = ["T201", "S105"]
|
|
90
|
+
"scripts/capture_ehealth.py" = ["T201", "S105"]
|
|
91
|
+
"scripts/capture_data.py" = ["T201", "S105"]
|
|
92
|
+
"scripts/place_test_order.py" = ["T201"]
|
|
93
|
+
"scripts/_redact.py" = ["T201"]
|
|
94
|
+
"docs/conf.py" = ["INP001"]
|
|
95
|
+
"tests/**" = [
|
|
96
|
+
"S101",
|
|
97
|
+
"S105",
|
|
98
|
+
"S106",
|
|
99
|
+
"SLF001",
|
|
100
|
+
"PLR2004",
|
|
101
|
+
"RUF059",
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
[tool.pytest.ini_options]
|
|
105
|
+
minversion = "9.0"
|
|
106
|
+
testpaths = ["tests"]
|
|
107
|
+
addopts = ["-ra", "--strict-markers", "--strict-config"]
|
|
108
|
+
filterwarnings = [
|
|
109
|
+
"error",
|
|
110
|
+
"ignore:Unclosed client session:ResourceWarning",
|
|
111
|
+
"ignore:Unclosed connector:ResourceWarning",
|
|
112
|
+
]
|
|
113
|
+
asyncio_mode = "auto"
|
|
114
|
+
|
|
115
|
+
[tool.coverage.run]
|
|
116
|
+
branch = true
|
|
117
|
+
source = ["src"]
|
|
118
|
+
|
|
119
|
+
[tool.coverage.report]
|
|
120
|
+
show_missing = true
|
|
121
|
+
skip_covered = true
|
|
122
|
+
fail_under = 98
|
|
123
|
+
|
|
124
|
+
[tool.mypy]
|
|
125
|
+
python_version = "3.14"
|
|
126
|
+
strict = true
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Public API for aiofarmad."""
|
|
2
|
+
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
|
|
5
|
+
from .client import FarmadClient, TokenRefreshCallback
|
|
6
|
+
from .exceptions import (
|
|
7
|
+
FarmadAuthenticationError,
|
|
8
|
+
FarmadAuthorizationError,
|
|
9
|
+
FarmadClientClosedError,
|
|
10
|
+
FarmadCommunicationError,
|
|
11
|
+
FarmadEhealthAuthorizationRequiredError,
|
|
12
|
+
FarmadError,
|
|
13
|
+
FarmadInvalidResponseError,
|
|
14
|
+
FarmadMfaRequiredError,
|
|
15
|
+
FarmadNotFoundError,
|
|
16
|
+
FarmadTimeoutError,
|
|
17
|
+
)
|
|
18
|
+
from .models import (
|
|
19
|
+
AccountMembership,
|
|
20
|
+
BasketItem,
|
|
21
|
+
BasketLine,
|
|
22
|
+
ConversationSummary,
|
|
23
|
+
CustomerBasket,
|
|
24
|
+
DraftBasket,
|
|
25
|
+
DraftProduct,
|
|
26
|
+
FarmadAccount,
|
|
27
|
+
FarmadMessage,
|
|
28
|
+
FarmadPatient,
|
|
29
|
+
FarmadTokens,
|
|
30
|
+
MedicationDayScheme,
|
|
31
|
+
MedicationIntakeMoment,
|
|
32
|
+
MedicationNondailyProduct,
|
|
33
|
+
MedicationSchemeProduct,
|
|
34
|
+
MedicationTemporality,
|
|
35
|
+
PatientInPharmacy,
|
|
36
|
+
Pharmacy,
|
|
37
|
+
PharmacyLinkResult,
|
|
38
|
+
PharmacyPreferences,
|
|
39
|
+
Prescription,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
__version__ = importlib.metadata.version("aiofarmad")
|
|
44
|
+
except importlib.metadata.PackageNotFoundError:
|
|
45
|
+
__version__ = "0.0.0"
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"AccountMembership",
|
|
49
|
+
"BasketItem",
|
|
50
|
+
"BasketLine",
|
|
51
|
+
"ConversationSummary",
|
|
52
|
+
"CustomerBasket",
|
|
53
|
+
"DraftBasket",
|
|
54
|
+
"DraftProduct",
|
|
55
|
+
"FarmadAccount",
|
|
56
|
+
"FarmadAuthenticationError",
|
|
57
|
+
"FarmadAuthorizationError",
|
|
58
|
+
"FarmadClient",
|
|
59
|
+
"FarmadClientClosedError",
|
|
60
|
+
"FarmadCommunicationError",
|
|
61
|
+
"FarmadEhealthAuthorizationRequiredError",
|
|
62
|
+
"FarmadError",
|
|
63
|
+
"FarmadInvalidResponseError",
|
|
64
|
+
"FarmadMessage",
|
|
65
|
+
"FarmadMfaRequiredError",
|
|
66
|
+
"FarmadNotFoundError",
|
|
67
|
+
"FarmadPatient",
|
|
68
|
+
"FarmadTimeoutError",
|
|
69
|
+
"FarmadTokens",
|
|
70
|
+
"MedicationDayScheme",
|
|
71
|
+
"MedicationIntakeMoment",
|
|
72
|
+
"MedicationNondailyProduct",
|
|
73
|
+
"MedicationSchemeProduct",
|
|
74
|
+
"MedicationTemporality",
|
|
75
|
+
"PatientInPharmacy",
|
|
76
|
+
"Pharmacy",
|
|
77
|
+
"PharmacyLinkResult",
|
|
78
|
+
"PharmacyPreferences",
|
|
79
|
+
"Prescription",
|
|
80
|
+
"TokenRefreshCallback",
|
|
81
|
+
"__version__",
|
|
82
|
+
]
|