Skip to content

Commit 4b5e705

Browse files
committed
pre-release update
1 parent 0b63d02 commit 4b5e705

14 files changed

Lines changed: 1008 additions & 368 deletions

File tree

.github/workflows/ci.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
python-version: ["3.10", "3.11", "3.12", "3.13"]
16+
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
21+
- name: Set up Python
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: ${{ matrix.python-version }}
25+
26+
- name: Set up uv
27+
uses: astral-sh/setup-uv@v6
28+
with:
29+
enable-cache: true
30+
31+
- name: Sync environment
32+
run: uv sync --python ${{ matrix.python-version }}
33+
34+
- name: Run tests
35+
run: uv run python -m unittest discover -s tests -p 'test_*.py'
36+
37+
build:
38+
runs-on: ubuntu-latest
39+
needs: test
40+
41+
steps:
42+
- name: Checkout
43+
uses: actions/checkout@v4
44+
45+
- name: Set up Python
46+
uses: actions/setup-python@v5
47+
with:
48+
python-version: "3.13"
49+
50+
- name: Set up uv
51+
uses: astral-sh/setup-uv@v6
52+
with:
53+
enable-cache: true
54+
55+
- name: Sync environment
56+
run: uv sync --python 3.13
57+
58+
- name: Build distributions
59+
run: uv build
60+
61+
- name: Check distributions
62+
run: uvx --from twine twine check dist/*

.github/workflows/release.yml

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
concurrency:
9+
group: release-${{ github.ref }}
10+
cancel-in-progress: false
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
permissions:
16+
contents: read
17+
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: "3.13"
26+
27+
- name: Set up uv
28+
uses: astral-sh/setup-uv@v6
29+
with:
30+
enable-cache: true
31+
32+
- name: Ensure tag matches package version
33+
run: |
34+
TAG_VERSION="${GITHUB_REF_NAME#v}"
35+
PACKAGE_VERSION="$(grep -E '^version = ' pyproject.toml | head -n1 | sed -E 's/version = "([^"]+)"/\1/')"
36+
if [ "${TAG_VERSION}" != "${PACKAGE_VERSION}" ]; then
37+
echo "::error::Git tag ${TAG_VERSION} does not match pyproject version ${PACKAGE_VERSION}"
38+
exit 1
39+
fi
40+
41+
- name: Sync environment
42+
run: uv sync --python 3.13
43+
44+
- name: Run tests
45+
run: uv run python -m unittest discover -s tests -p 'test_*.py'
46+
47+
- name: Build distributions
48+
run: uv build
49+
50+
- name: Check distributions
51+
run: uvx --from twine twine check dist/*
52+
53+
- name: Upload distributions
54+
uses: actions/upload-artifact@v4
55+
with:
56+
name: release-dists
57+
path: dist/notamify_sdk-*
58+
if-no-files-found: error
59+
60+
publish-pypi:
61+
needs: build
62+
runs-on: ubuntu-latest
63+
permissions:
64+
id-token: write
65+
environment:
66+
name: pypi
67+
68+
steps:
69+
- name: Download distributions
70+
uses: actions/download-artifact@v4
71+
with:
72+
name: release-dists
73+
path: dist
74+
75+
- name: Publish to PyPI
76+
uses: pypa/gh-action-pypi-publish@release/v1
77+
with:
78+
packages-dir: dist
79+
80+
github-release:
81+
needs:
82+
- build
83+
- publish-pypi
84+
runs-on: ubuntu-latest
85+
permissions:
86+
contents: write
87+
88+
steps:
89+
- name: Download distributions
90+
uses: actions/download-artifact@v4
91+
with:
92+
name: release-dists
93+
path: dist
94+
95+
- name: Create GitHub release
96+
env:
97+
GH_TOKEN: ${{ github.token }}
98+
run: gh release create "${GITHUB_REF_NAME}" dist/* --generate-notes --verify-tag

README.md

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Python SDK for Notamify public APIs:
99

1010
- Typed API client with one auth token
1111
- Pydantic response/request models
12+
- Pager-style NOTAM listing via `client.notams.*` with item iteration and `pager.pages`
1213
- Supported NOTAM endpoints:
1314
- `GET /notams`
1415
- `GET /notams/raw`
@@ -19,7 +20,7 @@ Python SDK for Notamify public APIs:
1920
- `POST /notams/prioritisation`
2021
- Watcher listener management + webhook logs/secrets
2122
- Listener `mode` support (`prod` default, `sandbox` for test-only listeners)
22-
- Listener `lifecycle_enabled` support for NOTAMC/NOTAMR follow-up events
23+
- Listener lifecycle support via `lifecycle.enabled` and `lifecycle.types`
2324
- Sandbox test delivery endpoint (`POST /listeners/{id}/sandbox:send`)
2425
- Webhook signature verification (`X-Notamify-Signature`)
2526
- Typed webhook event models for `interpretation` and `lifecycle` payloads
@@ -63,12 +64,18 @@ from notamify_sdk import NotamifyClient
6364

6465
client = NotamifyClient(token="YOUR_TOKEN")
6566

66-
active = client.get_active_notams({
67+
# The API accepts at most 30 items per page.
68+
active_notams = list(client.notams.active({
6769
"location": ["KJFK", "KLAX"],
68-
"page": 1,
6970
"per_page": 30,
70-
})
71-
print(active.total_count)
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)
7279

7380
job = client.create_async_briefing({
7481
"locations": [{
@@ -84,13 +91,57 @@ print(job.uuid)
8491
listener = client.create_listener(
8592
"https://example.trycloudflare.com/webhooks/notamify",
8693
mode="sandbox",
87-
lifecycle_enabled=False,
94+
lifecycle={"enabled": False},
8895
)
8996
print(listener.webhook_secret)
9097
sandbox_result = client.send_sandbox_message(listener.id, "SANDBOX-NOTAM-1")
9198
print(sandbox_result.notam_id)
9299
```
93100

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+
94145
## Local Webhook Testing
95146

96147
The example scripts live in the repository and source distribution under `examples/`.
@@ -115,7 +166,7 @@ uv run python ./examples/local_service_run.py
115166

116167
- `examples/service_receiver.py`: minimal production-style webhook receiver
117168
- `examples/local_service_run.py`: cloudflared tunnel + sandbox delivery flow
118-
- `examples/notams_fetch.py`: paginated NOTAM fetch example
169+
- `examples/notams_fetch.py`: straightforward NOTAM query examples
119170
- `examples/README.md`: setup notes and sample payloads
120171

121172
## Development

examples/README.md

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,31 +44,29 @@ Optional:
4444

4545
Create a token in the [Notamify API Manager](https://notamify.com/api-manager).
4646

47-
## 3) Fetch NOTAMs across all pages
47+
## 3) Fetch NOTAMs with the SDK pager
4848

49-
This script paginates until all NOTAM pages are fetched for one endpoint.
49+
This script keeps the NOTAM examples intentionally simple. It makes a few direct SDK calls with the NOTAM pager resource:
5050

51-
- file: `examples/notams_fetch.py`
52-
- supported endpoints via `NOTAM_ENDPOINT`: `active`, `raw`, `nearby`, `historical`
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.
5359

5460
```bash
5561
export NOTAMIFY_TOKEN="your_notamify_token"
56-
export NOTAM_ENDPOINT="active"
57-
export NOTAM_LOCATION="KJFK,KLAX"
5862
uv run python ./examples/notams_fetch.py
5963
```
6064

61-
Optional variables:
62-
63-
- `NOTAM_PER_PAGE` (default `100`)
64-
- `NOTAM_MAX_PAGES` (default `200`)
65-
- `NOTAM_STARTS_AT`, `NOTAM_ENDS_AT` (ISO datetime)
66-
- `NOTAM_LAT`, `NOTAM_LON`, `NOTAM_RADIUS_NM` (for `nearby`)
67-
- `NOTAM_VALID_AT` (YYYY-MM-DD, for `historical`)
65+
Edit the ICAO codes, coordinates, or page sizes in `examples/notams_fetch.py` to match your own use case.
6866

6967
## Webhook payload DTO
7068

71-
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`.
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`.
7270

7371
```json
7472
{

examples/local_service_run.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def upsert_sandbox_listener(
8989
filters={},
9090
active=True,
9191
mode=ListenerMode.sandbox,
92-
lifecycle_enabled=False,
92+
lifecycle={"enabled": False},
9393
)
9494
print(f"created sandbox listener id={created.id}")
9595
return created
@@ -102,7 +102,7 @@ def upsert_sandbox_listener(
102102
name=current.name or preferred_listener_name,
103103
active=True,
104104
mode=ListenerMode.sandbox,
105-
lifecycle_enabled=current.lifecycle_enabled,
105+
lifecycle=current.lifecycle,
106106
)
107107
print(f"updated sandbox listener id={updated.id}")
108108
return updated

0 commit comments

Comments
 (0)