notamify-sdk 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.
- notamify_sdk-0.1.0/LICENSE +21 -0
- notamify_sdk-0.1.0/MANIFEST.in +1 -0
- notamify_sdk-0.1.0/PKG-INFO +203 -0
- notamify_sdk-0.1.0/README.md +180 -0
- notamify_sdk-0.1.0/examples/README.md +128 -0
- notamify_sdk-0.1.0/examples/local_service_run.py +201 -0
- notamify_sdk-0.1.0/examples/notams_fetch.py +105 -0
- notamify_sdk-0.1.0/examples/service_receiver.py +99 -0
- notamify_sdk-0.1.0/notamify_sdk/__init__.py +109 -0
- notamify_sdk-0.1.0/notamify_sdk/client.py +504 -0
- notamify_sdk-0.1.0/notamify_sdk/cloudflared.py +90 -0
- notamify_sdk-0.1.0/notamify_sdk/config.py +80 -0
- notamify_sdk-0.1.0/notamify_sdk/models.py +498 -0
- notamify_sdk-0.1.0/notamify_sdk/receiver.py +183 -0
- notamify_sdk-0.1.0/notamify_sdk/signature.py +61 -0
- notamify_sdk-0.1.0/notamify_sdk.egg-info/PKG-INFO +203 -0
- notamify_sdk-0.1.0/notamify_sdk.egg-info/SOURCES.txt +34 -0
- notamify_sdk-0.1.0/notamify_sdk.egg-info/dependency_links.txt +1 -0
- notamify_sdk-0.1.0/notamify_sdk.egg-info/requires.txt +1 -0
- notamify_sdk-0.1.0/notamify_sdk.egg-info/top_level.txt +2 -0
- notamify_sdk-0.1.0/notamify_watcher_sdk/__init__.py +25 -0
- notamify_sdk-0.1.0/notamify_watcher_sdk/client.py +78 -0
- notamify_sdk-0.1.0/notamify_watcher_sdk/cloudflared.py +8 -0
- notamify_sdk-0.1.0/notamify_watcher_sdk/config.py +59 -0
- notamify_sdk-0.1.0/notamify_watcher_sdk/models.py +1 -0
- notamify_sdk-0.1.0/notamify_watcher_sdk/receiver.py +7 -0
- notamify_sdk-0.1.0/notamify_watcher_sdk/signature.py +13 -0
- notamify_sdk-0.1.0/pyproject.toml +45 -0
- notamify_sdk-0.1.0/setup.cfg +4 -0
- notamify_sdk-0.1.0/tests/test_client.py +531 -0
- notamify_sdk-0.1.0/tests/test_cloudflared.py +22 -0
- notamify_sdk-0.1.0/tests/test_compat.py +120 -0
- notamify_sdk-0.1.0/tests/test_config.py +72 -0
- notamify_sdk-0.1.0/tests/test_models.py +175 -0
- notamify_sdk-0.1.0/tests/test_receiver.py +141 -0
- notamify_sdk-0.1.0/tests/test_signature.py +34 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Skymerse Inc. (Notamify)
|
|
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 @@
|
|
|
1
|
+
recursive-include examples *.md *.py
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: notamify-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Notamify APIs
|
|
5
|
+
Author: Damian Szumski
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/skymerse/notamify-sdk-python
|
|
8
|
+
Project-URL: Repository, https://github.com/skymerse/notamify-sdk-python
|
|
9
|
+
Project-URL: Issues, https://github.com/skymerse/notamify-sdk-python/issues
|
|
10
|
+
Keywords: notam,aviation,webhook,sdk
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: pydantic<3,>=2
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# notamify-sdk (Python)
|
|
25
|
+
|
|
26
|
+
Python SDK for Notamify public APIs:
|
|
27
|
+
|
|
28
|
+
- Notamify API v2 (`https://api.notamify.com/api/v2`)
|
|
29
|
+
- Watcher API (`https://watcher.notamify.com`)
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Typed API client with one auth token
|
|
34
|
+
- Pydantic response/request models
|
|
35
|
+
- Pager-style NOTAM listing via `client.notams.*` with item iteration and `pager.pages`
|
|
36
|
+
- Supported NOTAM endpoints:
|
|
37
|
+
- `GET /notams`
|
|
38
|
+
- `GET /notams/raw`
|
|
39
|
+
- `GET /notams/nearby`
|
|
40
|
+
- `GET /notams/archive`
|
|
41
|
+
- `POST /notams/briefing`
|
|
42
|
+
- `GET /notams/briefing/{uuid}`
|
|
43
|
+
- `POST /notams/prioritisation`
|
|
44
|
+
- Watcher listener management + webhook logs/secrets
|
|
45
|
+
- Listener `mode` support (`prod` default, `sandbox` for test-only listeners)
|
|
46
|
+
- Listener lifecycle support via `lifecycle.enabled` and `lifecycle.types`
|
|
47
|
+
- Sandbox test delivery endpoint (`POST /listeners/{id}/sandbox:send`)
|
|
48
|
+
- Webhook signature verification (`X-Notamify-Signature`)
|
|
49
|
+
- Typed webhook event models for `interpretation` and `lifecycle` payloads
|
|
50
|
+
- Embedded receiver + cloudflared helper for local webhook testing
|
|
51
|
+
- Strict receiver mode fails fast when webhook secret is missing
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install notamify-sdk
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Or with `uv`:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv add notamify-sdk
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For contributors working from a source checkout:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv sync
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
Environment variables (highest priority):
|
|
74
|
+
|
|
75
|
+
- `NOTAMIFY_TOKEN`
|
|
76
|
+
- `NOTAMIFY_API_BASE_URL`
|
|
77
|
+
- `NOTAMIFY_WATCHER_BASE_URL`
|
|
78
|
+
- `NOTAMIFY_WEBHOOK_SECRET`
|
|
79
|
+
- `NOTAMIFY_CONFIG_FILE`
|
|
80
|
+
|
|
81
|
+
Default config file: `~/.config/notamify/config.json`
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from notamify_sdk import NotamifyClient
|
|
87
|
+
|
|
88
|
+
client = NotamifyClient(token="YOUR_TOKEN")
|
|
89
|
+
|
|
90
|
+
# The API accepts at most 30 items per page.
|
|
91
|
+
active_notams = list(client.notams.active({
|
|
92
|
+
"location": ["KJFK", "KLAX"],
|
|
93
|
+
"per_page": 30,
|
|
94
|
+
}))
|
|
95
|
+
print(len(active_notams))
|
|
96
|
+
|
|
97
|
+
first_page = next(iter(client.notams.active({
|
|
98
|
+
"location": ["KJFK", "KLAX"],
|
|
99
|
+
"per_page": 30,
|
|
100
|
+
}).pages))
|
|
101
|
+
print(first_page.total_count)
|
|
102
|
+
|
|
103
|
+
job = client.create_async_briefing({
|
|
104
|
+
"locations": [{
|
|
105
|
+
"location": "KJFK",
|
|
106
|
+
"type": "origin",
|
|
107
|
+
"starts_at": "2026-02-25T10:00:00Z",
|
|
108
|
+
"ends_at": "2026-02-25T12:00:00Z",
|
|
109
|
+
}],
|
|
110
|
+
})
|
|
111
|
+
print(job.uuid)
|
|
112
|
+
|
|
113
|
+
# Watcher sandbox flow
|
|
114
|
+
listener = client.create_listener(
|
|
115
|
+
"https://example.trycloudflare.com/webhooks/notamify",
|
|
116
|
+
mode="sandbox",
|
|
117
|
+
lifecycle={"enabled": False},
|
|
118
|
+
)
|
|
119
|
+
print(listener.webhook_secret)
|
|
120
|
+
sandbox_result = client.send_sandbox_message(listener.id, "SANDBOX-NOTAM-1")
|
|
121
|
+
print(sandbox_result.notam_id)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Pagination
|
|
125
|
+
|
|
126
|
+
The SDK exposes two NOTAM access styles:
|
|
127
|
+
|
|
128
|
+
- `client.get_active_notams(...)`, `client.get_raw_notams(...)`, `client.get_nearby_notams(...)`, and `client.get_historical_notams(...)` return a single `NotamListResult` page.
|
|
129
|
+
- `client.notams.active(...)`, `client.notams.raw(...)`, `client.notams.nearby(...)`, and `client.notams.historical(...)` return a pager that fetches all pages lazily as you iterate.
|
|
130
|
+
|
|
131
|
+
Use the pager when you want all NOTAMs across pages:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
pager = client.notams.active(
|
|
135
|
+
{"location": ["KJFK", "KLAX"]},
|
|
136
|
+
per_page=30,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
for notam in pager:
|
|
140
|
+
print(notam.id)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
If you want everything in memory at once, materialize the pager with `list(...)`:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
all_notams = list(
|
|
147
|
+
client.notams.active(
|
|
148
|
+
{"location": ["KJFK", "KLAX"]},
|
|
149
|
+
per_page=30,
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
If you need page metadata such as `page`, `per_page`, or `total_count`, iterate over `pager.pages`:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
pager = client.notams.active(
|
|
158
|
+
{"location": ["KJFK", "KLAX"]},
|
|
159
|
+
per_page=30,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
for page in pager.pages:
|
|
163
|
+
print(page.page, page.total_count, len(page.notams))
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The API allows at most `30` items per page. The SDK validates this with Pydantic and rejects larger `per_page` values.
|
|
167
|
+
|
|
168
|
+
## Local Webhook Testing
|
|
169
|
+
|
|
170
|
+
The example scripts live in the repository and source distribution under `examples/`.
|
|
171
|
+
They are intended to be run from a source checkout or source distribution, not from an installed wheel alone.
|
|
172
|
+
|
|
173
|
+
- Production: implement the webhook endpoint in your own application code.
|
|
174
|
+
- Verify `X-Notamify-Signature` using `NOTAMIFY_WEBHOOK_SECRET`.
|
|
175
|
+
- Production watcher deliveries include `kind`, `event_id`, and `notam`.
|
|
176
|
+
- Lifecycle watcher deliveries also include `change.changed_notam_id` for the original NOTAM that was cancelled or replaced.
|
|
177
|
+
- Local development: expose your local app endpoint with `cloudflared`, then set watcher `webhook_url` to that tunnel URL.
|
|
178
|
+
- Sandbox test sends use the same webhook payload DTO as production sends, with mock NOTAM content.
|
|
179
|
+
|
|
180
|
+
Prerequisite: `cloudflared` must be installed and available in `PATH`.
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
export NOTAMIFY_TOKEN="your_notamify_token"
|
|
184
|
+
export LOCAL_APP_URL="http://127.0.0.1:8080/webhooks/notamify"
|
|
185
|
+
uv run python ./examples/local_service_run.py
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Examples
|
|
189
|
+
|
|
190
|
+
- `examples/service_receiver.py`: minimal production-style webhook receiver
|
|
191
|
+
- `examples/local_service_run.py`: cloudflared tunnel + sandbox delivery flow
|
|
192
|
+
- `examples/notams_fetch.py`: straightforward NOTAM query examples
|
|
193
|
+
- `examples/README.md`: setup notes and sample payloads
|
|
194
|
+
|
|
195
|
+
## Development
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
uv run python -m unittest discover -s tests -p 'test_*.py'
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# notamify-sdk (Python)
|
|
2
|
+
|
|
3
|
+
Python SDK for Notamify public APIs:
|
|
4
|
+
|
|
5
|
+
- Notamify API v2 (`https://api.notamify.com/api/v2`)
|
|
6
|
+
- Watcher API (`https://watcher.notamify.com`)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- Typed API client with one auth token
|
|
11
|
+
- Pydantic response/request models
|
|
12
|
+
- Pager-style NOTAM listing via `client.notams.*` with item iteration and `pager.pages`
|
|
13
|
+
- Supported NOTAM endpoints:
|
|
14
|
+
- `GET /notams`
|
|
15
|
+
- `GET /notams/raw`
|
|
16
|
+
- `GET /notams/nearby`
|
|
17
|
+
- `GET /notams/archive`
|
|
18
|
+
- `POST /notams/briefing`
|
|
19
|
+
- `GET /notams/briefing/{uuid}`
|
|
20
|
+
- `POST /notams/prioritisation`
|
|
21
|
+
- Watcher listener management + webhook logs/secrets
|
|
22
|
+
- Listener `mode` support (`prod` default, `sandbox` for test-only listeners)
|
|
23
|
+
- Listener lifecycle support via `lifecycle.enabled` and `lifecycle.types`
|
|
24
|
+
- Sandbox test delivery endpoint (`POST /listeners/{id}/sandbox:send`)
|
|
25
|
+
- Webhook signature verification (`X-Notamify-Signature`)
|
|
26
|
+
- Typed webhook event models for `interpretation` and `lifecycle` payloads
|
|
27
|
+
- Embedded receiver + cloudflared helper for local webhook testing
|
|
28
|
+
- Strict receiver mode fails fast when webhook secret is missing
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install notamify-sdk
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or with `uv`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv add notamify-sdk
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For contributors working from a source checkout:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv sync
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
Environment variables (highest priority):
|
|
51
|
+
|
|
52
|
+
- `NOTAMIFY_TOKEN`
|
|
53
|
+
- `NOTAMIFY_API_BASE_URL`
|
|
54
|
+
- `NOTAMIFY_WATCHER_BASE_URL`
|
|
55
|
+
- `NOTAMIFY_WEBHOOK_SECRET`
|
|
56
|
+
- `NOTAMIFY_CONFIG_FILE`
|
|
57
|
+
|
|
58
|
+
Default config file: `~/.config/notamify/config.json`
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from notamify_sdk import NotamifyClient
|
|
64
|
+
|
|
65
|
+
client = NotamifyClient(token="YOUR_TOKEN")
|
|
66
|
+
|
|
67
|
+
# The API accepts at most 30 items per page.
|
|
68
|
+
active_notams = list(client.notams.active({
|
|
69
|
+
"location": ["KJFK", "KLAX"],
|
|
70
|
+
"per_page": 30,
|
|
71
|
+
}))
|
|
72
|
+
print(len(active_notams))
|
|
73
|
+
|
|
74
|
+
first_page = next(iter(client.notams.active({
|
|
75
|
+
"location": ["KJFK", "KLAX"],
|
|
76
|
+
"per_page": 30,
|
|
77
|
+
}).pages))
|
|
78
|
+
print(first_page.total_count)
|
|
79
|
+
|
|
80
|
+
job = client.create_async_briefing({
|
|
81
|
+
"locations": [{
|
|
82
|
+
"location": "KJFK",
|
|
83
|
+
"type": "origin",
|
|
84
|
+
"starts_at": "2026-02-25T10:00:00Z",
|
|
85
|
+
"ends_at": "2026-02-25T12:00:00Z",
|
|
86
|
+
}],
|
|
87
|
+
})
|
|
88
|
+
print(job.uuid)
|
|
89
|
+
|
|
90
|
+
# Watcher sandbox flow
|
|
91
|
+
listener = client.create_listener(
|
|
92
|
+
"https://example.trycloudflare.com/webhooks/notamify",
|
|
93
|
+
mode="sandbox",
|
|
94
|
+
lifecycle={"enabled": False},
|
|
95
|
+
)
|
|
96
|
+
print(listener.webhook_secret)
|
|
97
|
+
sandbox_result = client.send_sandbox_message(listener.id, "SANDBOX-NOTAM-1")
|
|
98
|
+
print(sandbox_result.notam_id)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Pagination
|
|
102
|
+
|
|
103
|
+
The SDK exposes two NOTAM access styles:
|
|
104
|
+
|
|
105
|
+
- `client.get_active_notams(...)`, `client.get_raw_notams(...)`, `client.get_nearby_notams(...)`, and `client.get_historical_notams(...)` return a single `NotamListResult` page.
|
|
106
|
+
- `client.notams.active(...)`, `client.notams.raw(...)`, `client.notams.nearby(...)`, and `client.notams.historical(...)` return a pager that fetches all pages lazily as you iterate.
|
|
107
|
+
|
|
108
|
+
Use the pager when you want all NOTAMs across pages:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
pager = client.notams.active(
|
|
112
|
+
{"location": ["KJFK", "KLAX"]},
|
|
113
|
+
per_page=30,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
for notam in pager:
|
|
117
|
+
print(notam.id)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
If you want everything in memory at once, materialize the pager with `list(...)`:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
all_notams = list(
|
|
124
|
+
client.notams.active(
|
|
125
|
+
{"location": ["KJFK", "KLAX"]},
|
|
126
|
+
per_page=30,
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
If you need page metadata such as `page`, `per_page`, or `total_count`, iterate over `pager.pages`:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
pager = client.notams.active(
|
|
135
|
+
{"location": ["KJFK", "KLAX"]},
|
|
136
|
+
per_page=30,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
for page in pager.pages:
|
|
140
|
+
print(page.page, page.total_count, len(page.notams))
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The API allows at most `30` items per page. The SDK validates this with Pydantic and rejects larger `per_page` values.
|
|
144
|
+
|
|
145
|
+
## Local Webhook Testing
|
|
146
|
+
|
|
147
|
+
The example scripts live in the repository and source distribution under `examples/`.
|
|
148
|
+
They are intended to be run from a source checkout or source distribution, not from an installed wheel alone.
|
|
149
|
+
|
|
150
|
+
- Production: implement the webhook endpoint in your own application code.
|
|
151
|
+
- Verify `X-Notamify-Signature` using `NOTAMIFY_WEBHOOK_SECRET`.
|
|
152
|
+
- Production watcher deliveries include `kind`, `event_id`, and `notam`.
|
|
153
|
+
- Lifecycle watcher deliveries also include `change.changed_notam_id` for the original NOTAM that was cancelled or replaced.
|
|
154
|
+
- Local development: expose your local app endpoint with `cloudflared`, then set watcher `webhook_url` to that tunnel URL.
|
|
155
|
+
- Sandbox test sends use the same webhook payload DTO as production sends, with mock NOTAM content.
|
|
156
|
+
|
|
157
|
+
Prerequisite: `cloudflared` must be installed and available in `PATH`.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
export NOTAMIFY_TOKEN="your_notamify_token"
|
|
161
|
+
export LOCAL_APP_URL="http://127.0.0.1:8080/webhooks/notamify"
|
|
162
|
+
uv run python ./examples/local_service_run.py
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Examples
|
|
166
|
+
|
|
167
|
+
- `examples/service_receiver.py`: minimal production-style webhook receiver
|
|
168
|
+
- `examples/local_service_run.py`: cloudflared tunnel + sandbox delivery flow
|
|
169
|
+
- `examples/notams_fetch.py`: straightforward NOTAM query examples
|
|
170
|
+
- `examples/README.md`: setup notes and sample payloads
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
uv run python -m unittest discover -s tests -p 'test_*.py'
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Notamify Examples (Python)
|
|
2
|
+
|
|
3
|
+
These examples cover watcher webhook integration and NOTAM data fetching.
|
|
4
|
+
Run them from a source checkout or source distribution after installing dependencies with `uv sync`.
|
|
5
|
+
|
|
6
|
+
## 1) Copy-paste receiver into your service
|
|
7
|
+
|
|
8
|
+
Use this file as your base:
|
|
9
|
+
|
|
10
|
+
- `examples/service_receiver.py`
|
|
11
|
+
|
|
12
|
+
Run it locally:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
export NOTAMIFY_WEBHOOK_SECRET="your_webhook_secret"
|
|
16
|
+
export NOTAMIFY_WEBHOOK_PATH="/webhooks/notamify"
|
|
17
|
+
uv run python ./examples/service_receiver.py
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Replace `handle_notamify_event(event)` with your business logic.
|
|
21
|
+
|
|
22
|
+
## 2) End-to-end local test against your service
|
|
23
|
+
|
|
24
|
+
This script:
|
|
25
|
+
|
|
26
|
+
- starts cloudflared tunnel to your local app origin
|
|
27
|
+
- derives watcher `webhook_url` from tunnel host + your local path
|
|
28
|
+
- creates/updates one listener in test mode (`mode=sandbox`)
|
|
29
|
+
- calls `POST /listeners/{id}/sandbox:send` to trigger a webhook
|
|
30
|
+
- retries transient DNS propagation errors
|
|
31
|
+
|
|
32
|
+
Prerequisite: `cloudflared` must be installed and available in `PATH`.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
export NOTAMIFY_TOKEN="your_notamify_token"
|
|
36
|
+
export LOCAL_APP_URL="http://127.0.0.1:8080/webhooks/notamify"
|
|
37
|
+
uv run python ./examples/local_service_run.py
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Optional:
|
|
41
|
+
|
|
42
|
+
- `NOTAMIFY_LISTENER_ID`
|
|
43
|
+
- `NOTAMIFY_LISTENER_NAME` (default `notamify-sdk-sandbox-example`)
|
|
44
|
+
|
|
45
|
+
Create a token in the [Notamify API Manager](https://notamify.com/api-manager).
|
|
46
|
+
|
|
47
|
+
## 3) Fetch NOTAMs with the SDK pager
|
|
48
|
+
|
|
49
|
+
This script keeps the NOTAM examples intentionally simple. It makes a few direct SDK calls with the NOTAM pager resource:
|
|
50
|
+
|
|
51
|
+
- `client.notams.active(...)`
|
|
52
|
+
- `client.notams.raw(...)`
|
|
53
|
+
- `client.notams.nearby(...)`
|
|
54
|
+
- `client.notams.historical(...)`
|
|
55
|
+
|
|
56
|
+
It also shows how to access the first page metadata via `pager.pages`.
|
|
57
|
+
|
|
58
|
+
Use `per_page` values up to `30`; larger values are rejected by the SDK query models.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
export NOTAMIFY_TOKEN="your_notamify_token"
|
|
62
|
+
uv run python ./examples/notams_fetch.py
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Edit the ICAO codes, coordinates, or page sizes in `examples/notams_fetch.py` to match your own use case.
|
|
66
|
+
|
|
67
|
+
## Webhook payload DTO
|
|
68
|
+
|
|
69
|
+
Sandbox test sends and production sends use the same webhook payload DTO. Watcher can send standard `interpretation` messages and optional `lifecycle` messages for later NOTAMC/NOTAMR changes when the listener has `lifecycle.enabled = true`.
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"listener_id": "listener-123",
|
|
74
|
+
"kind": "interpretation",
|
|
75
|
+
"event_id": "event-123",
|
|
76
|
+
"notam": {
|
|
77
|
+
"id": "A1234/26",
|
|
78
|
+
"notam_number": "A1234/26",
|
|
79
|
+
"notam_type": "N",
|
|
80
|
+
"location": "EPWA",
|
|
81
|
+
"icao_code": "EPWA",
|
|
82
|
+
"qcode": "QMRLC",
|
|
83
|
+
"classification": "INTL",
|
|
84
|
+
"starts_at": "2026-02-25T12:00:00Z",
|
|
85
|
+
"ends_at": "2026-02-25T13:00:00Z",
|
|
86
|
+
"issued_at": "2026-02-25T11:55:00Z",
|
|
87
|
+
"is_estimated": false,
|
|
88
|
+
"is_permanent": false,
|
|
89
|
+
"message": "NOTAM text",
|
|
90
|
+
"icao_message": "NOTAM text",
|
|
91
|
+
"interpretation": {
|
|
92
|
+
"description": "Human summary",
|
|
93
|
+
"excerpt": "Short summary",
|
|
94
|
+
"category": "AERODROME",
|
|
95
|
+
"subcategory": "RUNWAY_OPERATIONS",
|
|
96
|
+
"map_elements": [],
|
|
97
|
+
"affected_elements": [],
|
|
98
|
+
"schedules": []
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"context": {
|
|
102
|
+
"location": {
|
|
103
|
+
"ident": "EPWA",
|
|
104
|
+
"icao": "EPWA",
|
|
105
|
+
"name": "Warsaw Chopin Airport",
|
|
106
|
+
"iso_country": "PL",
|
|
107
|
+
"iso_country_name": "Poland",
|
|
108
|
+
"coordinates": {
|
|
109
|
+
"lat": 52.1657,
|
|
110
|
+
"lon": 20.9671
|
|
111
|
+
},
|
|
112
|
+
"fir_icaos": ["EPWW"]
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"sent_at": "2026-02-25T13:09:51Z"
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Lifecycle payloads use the same top-level structure, but set `kind` to `"lifecycle"` and add:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"change": {
|
|
124
|
+
"changed_notam_id": "old-notam-uuid",
|
|
125
|
+
"notam_type": "R"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|