streetworks 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 (47) hide show
  1. streetworks-0.1.0/.github/workflows/ci.yml +21 -0
  2. streetworks-0.1.0/.github/workflows/integration.yml +32 -0
  3. streetworks-0.1.0/.github/workflows/publish.yml +33 -0
  4. streetworks-0.1.0/.github/workflows/regenerate-models.yml +43 -0
  5. streetworks-0.1.0/.gitignore +14 -0
  6. streetworks-0.1.0/CHANGELOG.md +27 -0
  7. streetworks-0.1.0/CONTRIBUTING.md +27 -0
  8. streetworks-0.1.0/LICENSE +21 -0
  9. streetworks-0.1.0/PKG-INFO +361 -0
  10. streetworks-0.1.0/README.md +304 -0
  11. streetworks-0.1.0/docs/DTRO_SCHEMAS.md +56 -0
  12. streetworks-0.1.0/docs/INTEGRATION.md +103 -0
  13. streetworks-0.1.0/docs/RELEASING.md +32 -0
  14. streetworks-0.1.0/examples/datavia_queries.py +34 -0
  15. streetworks-0.1.0/examples/dtro_consume.py +21 -0
  16. streetworks-0.1.0/examples/opendata_fastapi.py +35 -0
  17. streetworks-0.1.0/examples/streetmanager_async_bulk.py +24 -0
  18. streetworks-0.1.0/examples/streetmanager_quickstart.py +15 -0
  19. streetworks-0.1.0/pyproject.toml +74 -0
  20. streetworks-0.1.0/scripts/generate_models.py +200 -0
  21. streetworks-0.1.0/scripts/smoke_test.py +271 -0
  22. streetworks-0.1.0/src/streetworks/__init__.py +40 -0
  23. streetworks-0.1.0/src/streetworks/_oauth.py +115 -0
  24. streetworks-0.1.0/src/streetworks/_transport.py +197 -0
  25. streetworks-0.1.0/src/streetworks/datavia/__init__.py +23 -0
  26. streetworks-0.1.0/src/streetworks/datavia/client.py +389 -0
  27. streetworks-0.1.0/src/streetworks/datavia/filters.py +141 -0
  28. streetworks-0.1.0/src/streetworks/dtro/__init__.py +5 -0
  29. streetworks-0.1.0/src/streetworks/dtro/client.py +334 -0
  30. streetworks-0.1.0/src/streetworks/exceptions.py +97 -0
  31. streetworks-0.1.0/src/streetworks/opendata/__init__.py +21 -0
  32. streetworks-0.1.0/src/streetworks/opendata/models.py +74 -0
  33. streetworks-0.1.0/src/streetworks/opendata/sns.py +197 -0
  34. streetworks-0.1.0/src/streetworks/streetmanager/__init__.py +12 -0
  35. streetworks-0.1.0/src/streetworks/streetmanager/auth.py +203 -0
  36. streetworks-0.1.0/src/streetworks/streetmanager/client.py +467 -0
  37. streetworks-0.1.0/src/streetworks/streetmanager/environments.py +58 -0
  38. streetworks-0.1.0/src/streetworks/streetmanager/models/__init__.py +17 -0
  39. streetworks-0.1.0/tests/fixtures/mini-swagger.json +45 -0
  40. streetworks-0.1.0/tests/integration/__init__.py +0 -0
  41. streetworks-0.1.0/tests/integration/test_connectivity.py +140 -0
  42. streetworks-0.1.0/tests/test_datavia.py +130 -0
  43. streetworks-0.1.0/tests/test_dtro.py +155 -0
  44. streetworks-0.1.0/tests/test_model_generation.py +56 -0
  45. streetworks-0.1.0/tests/test_opendata.py +94 -0
  46. streetworks-0.1.0/tests/test_streetmanager.py +152 -0
  47. streetworks-0.1.0/tests/test_transport.py +73 -0
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - run: pip install -e ".[dev]"
20
+ - run: ruff check .
21
+ - run: pytest -q
@@ -0,0 +1,32 @@
1
+ name: Integration tests (live systems)
2
+
3
+ # Manual only. Hits real test/sandbox systems using credentials stored as
4
+ # repository secrets. Never runs on push/PR, so it can't break normal CI.
5
+ on:
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ integration:
10
+ runs-on: ubuntu-latest
11
+ environment: integration # optional: gate behind a reviewer
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+ - run: pip install -e ".[dev]"
18
+ - run: pytest -m integration -v
19
+ env:
20
+ SM_EMAIL: ${{ secrets.SM_EMAIL }}
21
+ SM_PASSWORD: ${{ secrets.SM_PASSWORD }}
22
+ SM_ENV: sandbox
23
+ SM_VERSION: v6
24
+ DATAVIA_USER: ${{ secrets.DATAVIA_USER }}
25
+ DATAVIA_PASSWORD: ${{ secrets.DATAVIA_PASSWORD }}
26
+ DATAVIA_CLIENT_ID: ${{ secrets.DATAVIA_CLIENT_ID }}
27
+ DATAVIA_CLIENT_SECRET: ${{ secrets.DATAVIA_CLIENT_SECRET }}
28
+ DATAVIA_USRN: ${{ secrets.DATAVIA_USRN }}
29
+ DTRO_CLIENT_ID: ${{ secrets.DTRO_CLIENT_ID }}
30
+ DTRO_CLIENT_SECRET: ${{ secrets.DTRO_CLIENT_SECRET }}
31
+ DTRO_APP_ID: ${{ secrets.DTRO_APP_ID }}
32
+ DTRO_ENV: integration
@@ -0,0 +1,33 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: actions/setup-python@v5
13
+ with:
14
+ python-version: "3.12"
15
+ - run: pip install build
16
+ - run: python -m build
17
+ - uses: actions/upload-artifact@v4
18
+ with:
19
+ name: dist
20
+ path: dist/
21
+
22
+ publish:
23
+ needs: build
24
+ runs-on: ubuntu-latest
25
+ environment: pypi
26
+ permissions:
27
+ id-token: write # required for PyPI Trusted Publishing (no API token needed)
28
+ steps:
29
+ - uses: actions/download-artifact@v4
30
+ with:
31
+ name: dist
32
+ path: dist/
33
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,43 @@
1
+ name: Regenerate Street Manager models
2
+
3
+ # Run manually from the Actions tab. Downloads the official swagger specs
4
+ # and opens a PR if the generated Pydantic models changed.
5
+ on:
6
+ workflow_dispatch:
7
+ inputs:
8
+ version:
9
+ description: "Street Manager API version"
10
+ default: "v6"
11
+ required: true
12
+ url_template:
13
+ description: "Swagger spec URL ({api}/{version} placeholders)"
14
+ default: "https://api.sandbox.manage-roadworks.service.gov.uk/{version}/{api}/docs/swagger.json"
15
+ required: true
16
+
17
+ jobs:
18
+ regenerate:
19
+ runs-on: ubuntu-latest
20
+ permissions:
21
+ contents: write
22
+ pull-requests: write
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.12"
28
+ - run: pip install -e ".[gen]" httpx
29
+ - run: |
30
+ python scripts/generate_models.py \
31
+ --version "${{ inputs.version }}" \
32
+ --url-template "${{ inputs.url_template }}"
33
+ - run: pip install -e ".[dev]" && pytest -q
34
+ - uses: peter-evans/create-pull-request@v6
35
+ with:
36
+ branch: regenerate-models-${{ inputs.version }}
37
+ title: "Regenerate ${{ inputs.version }} Street Manager models"
38
+ commit-message: "Regenerate ${{ inputs.version }} models from official swagger specs"
39
+ body: |
40
+ Automated regeneration of `streetworks.streetmanager.models.${{ inputs.version }}`
41
+ from the official DfT swagger specifications.
42
+
43
+ Spec source: `${{ inputs.url_template }}`
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ venv/
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ .coverage
13
+ htmlcov/
14
+ .env
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (unreleased)
4
+
5
+ Initial release.
6
+
7
+ - `streetworks.streetmanager`: sync + async clients for all nine Street
8
+ Manager APIs (V6/V7, sandbox/production) with automatic auth, token
9
+ refresh, retries and rate-limit handling. Explicit `authenticate()` method
10
+ for fail-fast credential/connectivity checks.
11
+ - Connectivity smoke test (`scripts/smoke_test.py`) and skip-guarded
12
+ integration test suite (`pytest -m integration`) for verifying against the
13
+ real test/sandbox systems.
14
+ - `streetworks.opendata`: SNS receiver toolkit — parsing, signature
15
+ verification, subscription auto-confirmation, event extraction.
16
+ - `streetworks.datavia`: OGC WFS client for Geoplace DataVIA - Basic and
17
+ OAuth2 client-credentials auth, full NSG layer catalogue, composable
18
+ OGC filters (USRN, DWithin, Intersects, BBOX, attribute equality),
19
+ documentation-faithful POST GetFeature bodies, KVP GET, paging iterator,
20
+ and all documented output formats.
21
+ - `streetworks.dtro`: DfT Digital Traffic Regulation Orders client -
22
+ OAuth2 client credentials with token caching, integration/production
23
+ environments, publish (body/file/gzip), retrieve, delete, events search,
24
+ signed-URL full CSV export, provisions (create/update/delete, with the
25
+ distinct `App-Id` header), schemas, and search. Token metadata exposed via
26
+ `token_info`. Verified against the official OpenAPI spec and Postman
27
+ collection.
@@ -0,0 +1,27 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in improving `streetworks`!
4
+
5
+ ## Getting started
6
+
7
+ ```bash
8
+ git clone https://github.com/KFergusonUK/streetworks
9
+ cd streetworks
10
+ pip install -e ".[dev]"
11
+ pytest
12
+ ```
13
+
14
+ ## Ground rules
15
+
16
+ - All tests must run **without credentials** — mock HTTP with `respx`.
17
+ - New endpoints: add a typed method to the relevant API group, with the
18
+ documented path in the docstring, plus a mocked test.
19
+ - New providers: one module under `src/streetworks/`, built on
20
+ `streetworks._transport`, raising `streetworks.exceptions` types.
21
+ - Run `ruff check .` before opening a PR.
22
+
23
+ ## Verifying against real services
24
+
25
+ If you have sandbox credentials for Street Manager or a DataVIA account,
26
+ real-world verification of endpoint paths and payloads is hugely valuable —
27
+ please note in your PR what you verified and against which environment/version.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kevin Ferguson
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,361 @@
1
+ Metadata-Version: 2.4
2
+ Name: streetworks
3
+ Version: 0.1.0
4
+ Summary: An open SDK for UK street works APIs: DfT Street Manager, Street Manager Open Data (SNS), and Geoplace DataVIA.
5
+ Project-URL: Homepage, https://github.com/KFergusonUK/StreetWorks-SDK
6
+ Project-URL: Documentation, https://github.com/KFergusonUK/StreetWorks-SDK#readme
7
+ Project-URL: Issues, https://github.com/KFergusonUK/StreetWorks-SDK/issues
8
+ Author: Kevin Ferguson
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Kevin Ferguson
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: datavia,dft,geoplace,highways,nsg,permits,roadworks,street manager,street works,usrn
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Programming Language :: Python :: 3.13
40
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
41
+ Requires-Python: >=3.10
42
+ Requires-Dist: httpx>=0.27
43
+ Requires-Dist: pydantic>=2.7
44
+ Provides-Extra: dev
45
+ Requires-Dist: cryptography>=42; extra == 'dev'
46
+ Requires-Dist: datamodel-code-generator>=0.25; extra == 'dev'
47
+ Requires-Dist: mypy>=1.10; extra == 'dev'
48
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
49
+ Requires-Dist: pytest>=8; extra == 'dev'
50
+ Requires-Dist: respx>=0.21; extra == 'dev'
51
+ Requires-Dist: ruff>=0.5; extra == 'dev'
52
+ Provides-Extra: gen
53
+ Requires-Dist: datamodel-code-generator>=0.25; extra == 'gen'
54
+ Provides-Extra: sns
55
+ Requires-Dist: cryptography>=42; extra == 'sns'
56
+ Description-Content-Type: text/markdown
57
+
58
+ # streetworks
59
+
60
+ [![CI](https://github.com/KFergusonUK/StreetWorks-SDK/actions/workflows/ci.yml/badge.svg)](https://github.com/KFergusonUK/StreetWorks-SDK/actions/workflows/ci.yml)
61
+ [![PyPI](https://img.shields.io/pypi/v/streetworks)](https://pypi.org/project/streetworks/)
62
+ [![Python](https://img.shields.io/pypi/pyversions/streetworks)](https://pypi.org/project/streetworks/)
63
+ [![Licence: MIT](https://img.shields.io/badge/licence-MIT-green.svg)](LICENSE)
64
+
65
+ An open Python SDK for UK street works APIs — one consistent, typed,
66
+ well-tested client for the services the sector actually uses.
67
+
68
+ > We do this not because it is easy, but because it is hard.
69
+
70
+ ```python
71
+ from streetworks.streetmanager import StreetManagerClient, Environment
72
+
73
+ with StreetManagerClient("api-user@example.com", password, environment=Environment.SANDBOX) as sm:
74
+ sm.authenticate() # verify credentials
75
+ submitted = sm.reporting.permits(status="submitted")
76
+ ```
77
+
78
+ | Module | Service | Direction |
79
+ |---|---|---|
80
+ | `streetworks.streetmanager` | [DfT Street Manager](https://department-for-transport-streetmanager.github.io/street-manager-docs/api-documentation/) — all nine APIs (Work, Reporting, Street Lookup, GeoJSON, Party, Data Export, Event, Sampling, Worklist), V6 & V7, sandbox & production | read + write |
81
+ | `streetworks.opendata` | [Street Manager Open Data](https://department-for-transport-streetmanager.github.io/street-manager-docs/open-data/) — AWS SNS push notifications | receive |
82
+ | `streetworks.datavia` | [Geoplace DataVIA](https://datavia.geoplace.co.uk/documentation) — full NSG layer catalogue over OGC WFS (Basic + OAuth2) | read |
83
+ | `streetworks.dtro` | [DfT Digital Traffic Regulation Orders](https://d-tro.dft.gov.uk/api-documentation/) — integration & production | read + write |
84
+
85
+ Shared across all modules: automatic retries with exponential backoff and
86
+ jitter, `Retry-After`-aware 429 handling, a single exception hierarchy, and
87
+ both **sync and async** clients built on [httpx](https://www.python-httpx.org/).
88
+
89
+ ## What this is (and isn't)
90
+
91
+ **It is** a typed client library: it handles authentication, token lifecycles,
92
+ retries, rate limiting, pagination, and request/response plumbing for each of
93
+ these APIs, so you call Python methods instead of hand-rolling HTTP. Auth and
94
+ connectivity are verified against the real systems (see **Status** below).
95
+
96
+ **It isn't** a replacement for the APIs' own documentation. You still bring
97
+ your own credentials (issued by the service operators, not by this SDK) and
98
+ you still need each API's domain concepts — what a permit payload contains,
99
+ what makes a valid USRN filter, which DataVIA layer holds which data. The SDK
100
+ gets you connected and typed; the linked docs tell you what to send.
101
+
102
+ ## Status
103
+
104
+ Early alpha (`0.1.0`). **Authentication and read/consume access are verified
105
+ against the real systems for all four providers:** Street Manager (SANDBOX),
106
+ Geoplace DataVIA (live — including a real feature query), D-TRO (production
107
+ token + events search), and the Open Data SNS parsing/verification pipeline.
108
+
109
+ Not yet exercised against live systems — implemented to the published specs
110
+ and covered by mocked tests: the **write/publish** paths (Street Manager work
111
+ submission and assessment; D-TRO create/update and provisions). These are
112
+ publisher-scoped and deliberately excluded from the read-only smoke test.
113
+
114
+ Known reconciliation items: D-TRO payload models track spec `3.4.x` while
115
+ production is on `3.5.1` (with `4.0.0` due mid-2026); the
116
+ `streetworks.exceptions` API and client method surface may change before
117
+ `1.0`. See [docs/INTEGRATION.md](docs/INTEGRATION.md) for how to verify
118
+ against the real systems yourself. First-contact reports welcome.
119
+
120
+ ## Install
121
+
122
+ ```bash
123
+ pip install streetworks # core
124
+ pip install "streetworks[sns]" # + SNS signature verification (cryptography)
125
+ ```
126
+
127
+ Requires Python 3.10+.
128
+
129
+ ## Prerequisites: credentials
130
+
131
+ Credentials are issued by the service operators. You only need the ones for the
132
+ service(s) you'll use. Keep them in environment variables or a secret manager —
133
+ never in code.
134
+
135
+ | Service | How to get access | Environment variables |
136
+ |---|---|---|
137
+ | Street Manager | Your organisation's Street Manager admin issues API accounts; [start in sandbox](https://department-for-transport-streetmanager.github.io/street-manager-docs/articles/testing-with-street-manager-sandbox-environment.html) | `SM_EMAIL`, `SM_PASSWORD` |
138
+ | Street Manager Open Data | Register an HTTPS endpoint with DfT to receive the SNS subscription | *(none — you host the receiver)* |
139
+ | DataVIA | A [Geoplace DataVIA](https://datavia.geoplace.co.uk/) account (username/password) or issued OAuth2 client credentials | `DATAVIA_USER` + `DATAVIA_PASSWORD`, or `DATAVIA_CLIENT_ID` + `DATAVIA_CLIENT_SECRET` |
140
+ | D-TRO | Register an application via the [D-TRO service](https://d-tro.dft.gov.uk/) for an app id and OAuth2 client credentials (integration first, then production) | `DTRO_CLIENT_ID`, `DTRO_CLIENT_SECRET`, `DTRO_APP_ID` |
141
+
142
+ Credentials are **per-environment** — sandbox/integration credentials do not
143
+ work against production, and vice versa.
144
+
145
+ ## Verify your setup
146
+
147
+ Before writing any code, confirm your credentials and connectivity with the
148
+ included smoke test. It targets the **test** environments by default, is
149
+ read-only, and skips any service you haven't configured:
150
+
151
+ ```bash
152
+ SM_EMAIL='api-user@example.com' SM_PASSWORD='...' python scripts/smoke_test.py
153
+ ```
154
+
155
+ ```
156
+ ================================================================
157
+ streetworks connectivity smoke test
158
+ TARGET Street Manager: sandbox
159
+ All checks are READ-ONLY.
160
+ ================================================================
161
+
162
+ [PASS] Street Manager - authenticated (sandbox/v6), organisation 1355
163
+ ...
164
+ ```
165
+
166
+ A `FAIL` prints the exact exception, so a wrong credential or environment is
167
+ obvious immediately. See [docs/INTEGRATION.md](docs/INTEGRATION.md) for the
168
+ full variable list and how to (deliberately) target production.
169
+
170
+ ## Street Manager
171
+
172
+ Authentication, token caching, and refresh (via the Party API, with automatic
173
+ fall-back to re-authentication) are handled for you — following the DfT
174
+ integration guidance: one token, reused, never re-authenticating per call.
175
+
176
+ ```python
177
+ from streetworks.streetmanager import StreetManagerClient, Environment, ApiVersion
178
+
179
+ with StreetManagerClient(
180
+ "api-user@example.com",
181
+ "password", # store securely, e.g. environment variable
182
+ environment=Environment.SANDBOX, # or Environment.PRODUCTION
183
+ version=ApiVersion.V6, # or ApiVersion.V7 / ApiVersion.LATEST
184
+ ) as sm:
185
+ # Typed convenience methods for common workflows...
186
+ work = sm.work.get_work("TSR1591199404915")
187
+ submitted = sm.reporting.permits(status="submitted")
188
+ sm.work.assess_permit("TSR1591199404915", "TSR1591199404915-01",
189
+ {"assessment_status": "granted", ...})
190
+
191
+ # ...and a generic escape hatch for every endpoint we haven't wrapped yet:
192
+ s58 = sm.work.post("section-58s", json={...})
193
+ updates = sm.event.works_updates()
194
+ ```
195
+
196
+ Async is a mirror image:
197
+
198
+ ```python
199
+ from streetworks.streetmanager import AsyncStreetManagerClient
200
+
201
+ async with AsyncStreetManagerClient("api-user@example.com", "password") as sm:
202
+ permits = await sm.reporting.permits(status="submitted")
203
+ ```
204
+
205
+ > **Environments.** `Environment.SANDBOX` and `Environment.PRODUCTION` are
206
+ > isolated systems with separate credentials. Develop and test against
207
+ > SANDBOX; only point at PRODUCTION once your workflows are proven. The smoke
208
+ > test and integration suite refuse to touch production without an explicit
209
+ > opt-in, so a stray setting can't send you at live data by accident.
210
+
211
+ ### Typed models
212
+
213
+ Pydantic v2 models generated from the official DfT swagger specifications
214
+ live under `streetworks.streetmanager.models.<version>` and validate any
215
+ client payload:
216
+
217
+ ```python
218
+ from streetworks.streetmanager.models.v6.work import WorkResponse
219
+
220
+ work = WorkResponse.model_validate(sm.work.get_work("TSR1591199404915"))
221
+ ```
222
+
223
+ To regenerate after a DfT release, run the **Regenerate Street Manager
224
+ models** workflow from the Actions tab (it opens a PR), or locally:
225
+
226
+ ```bash
227
+ pip install -e ".[gen]"
228
+ python scripts/generate_models.py --version v6 --from-dir specs/streetmanager/v6
229
+ ```
230
+
231
+ ## Street Manager Open Data (SNS push)
232
+
233
+ Open Data is a *push* model: Street Manager POSTs event notifications to an
234
+ HTTPS endpoint you host. **The receiver needs no credentials** — messages are
235
+ authenticated with AWS's public signing certificate (fetched over HTTPS), not
236
+ a shared secret, so there's nothing to configure on the SDK side for parsing,
237
+ verifying, or confirming. `streetworks.opendata` handles all of that,
238
+ framework-agnostic:
239
+
240
+ ```python
241
+ from streetworks.opendata import handle
242
+
243
+ # inside your web handler, with the raw request body:
244
+ event = handle(request_body, expected_topic_arn="arn:aws:sns:eu-west-2:...:...")
245
+ if event is not None: # None => subscription handshake, auto-confirmed
246
+ print(event["event_type"], event["object_reference"])
247
+ ```
248
+
249
+ See [`examples/opendata_fastapi.py`](examples/opendata_fastapi.py) for a
250
+ complete FastAPI receiver.
251
+
252
+ > **Credentials nuance.** *Receiving* Open Data needs no credentials. But note
253
+ > there are two distinct feeds: the fully public **Open Data** feed (this
254
+ > module), and a separate per-organisation **API Notifications** feed whose
255
+ > *subscription* is set up by calling an authenticated Street Manager endpoint
256
+ > (`POST api-notifications/subscribe`) — that setup step needs Street Manager
257
+ > credentials, though the messages, once flowing, are received the same
258
+ > credential-free way. This module handles the receiving side of both.
259
+
260
+ ## Geoplace DataVIA
261
+
262
+ Basic auth or OAuth2 client credentials (server-to-server), the full NSG layer
263
+ catalogue (`Layer.STREET_LINES`, `ESU_STREETS`, `ESU_ONE_WAY_EXEMPTIONS`, and
264
+ the Interest / Construction / Special Designation layers in all three geometry
265
+ flavours), composable OGC filters, and transparent paging:
266
+
267
+ ```python
268
+ from streetworks.datavia import DataViaClient, Layer, filters
269
+
270
+ with DataViaClient(username="user", password="pass") as dv: # or client_id=/client_secret=
271
+ street = dv.street_by_usrn(4401245)
272
+ nearby = dv.streets_near_point(-0.138405, 50.825181, 100) # within 100m
273
+
274
+ sed = dv.get_features(
275
+ Layer.SPECIAL_DESIGNATION_LINES,
276
+ filter_fragment=filters.and_(
277
+ filters.intersects_polygon(ring),
278
+ filters.property_equals("special_designation_code", 3),
279
+ ),
280
+ )
281
+
282
+ for feature in dv.iter_features(Layer.ESU_STREETS, page_size=500):
283
+ ...
284
+ ```
285
+
286
+ POST `GetFeature` bodies match the shapes in the DataVIA documentation
287
+ (WFS 1.1.0 + `ogc:Filter`); GET KVP with `startIndex`/`count` is also
288
+ available via `get_features_kvp()`. Output formats: GeoJSON (default),
289
+ OGRGML, SHAPEZIP, CSV, SPATIALITEZIP.
290
+
291
+ ## DfT D-TRO
292
+
293
+ OAuth2 client credentials (30-minute tokens, cached and renewed
294
+ automatically), `x-app-id` and per-request `X-Correlation-ID` headers handled
295
+ for you:
296
+
297
+ ```python
298
+ from streetworks.dtro import DTROClient, Environment
299
+
300
+ with DTROClient(client_id, client_secret, app_id=app_id,
301
+ environment=Environment.INTEGRATION) as dtro:
302
+ events = dtro.search_events(since="2026-06-01T00:00:00", pageSize=50)
303
+ record = dtro.get_dtro(events["events"][0]["id"])
304
+
305
+ dtro.create_dtro(payload) # publisher scope
306
+ dtro.create_dtro_from_file(big_json, gzip=True) # large D-TROs
307
+ signed = dtro.get_all_dtros_url() # full CSV extract
308
+
309
+ dtro.schema_versions() # available schema versions
310
+ dtro.search({...}) # search published D-TROs
311
+ dtro.create_provisions([...], dtro_id="...") # provisions (App-Id header handled)
312
+ ```
313
+
314
+ ## Design principles
315
+
316
+ 1. **Never block the user.** Typed methods for confirmed, common endpoints;
317
+ generic `get/post/put/delete` on every API group for everything else.
318
+ 2. **Be a good API citizen.** Token reuse, refresh-then-reauth, exponential
319
+ backoff, honoured `Retry-After` — per the DfT integration guidance.
320
+ 3. **Test without credentials, verify with them.** The whole unit suite runs
321
+ against mocked transports (`respx`) so CI needs no secrets; a separate
322
+ smoke test and skip-guarded integration suite verify against the real
323
+ systems when you supply credentials.
324
+ 4. **Room to grow.** Each provider is a self-contained module over a shared
325
+ transport/exception core — adding a new API is additive.
326
+
327
+ ## Roadmap
328
+
329
+ - [x] Pydantic model generation pipeline for the Street Manager swagger specs
330
+ - [ ] Auto-pagination helpers for the Reporting API
331
+ - [ ] DataVIA WMS support
332
+ - [ ] D-TRO publish models generated from the DfT JSON schemas, version-namespaced
333
+ (`v3.5.1` to match production, `v4.0.0` to follow) — see [docs/DTRO_SCHEMAS.md](docs/DTRO_SCHEMAS.md)
334
+ - [ ] Scottish Road Works Register (SRWR)?
335
+ - [ ] Ordnance Survey NGD / Linked Identifiers?
336
+
337
+ Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
338
+
339
+ ## Development
340
+
341
+ ```bash
342
+ pip install -e ".[dev]"
343
+ pytest # 35 mocked unit tests - no credentials needed
344
+ ruff check .
345
+ ```
346
+
347
+ The unit tests mock the network so they run offline and without credentials.
348
+ To verify the SDK against the **real** test/sandbox systems with your own
349
+ credentials, use the smoke test or the integration suite — see
350
+ [docs/INTEGRATION.md](docs/INTEGRATION.md):
351
+
352
+ ```bash
353
+ python scripts/smoke_test.py # one read-only call per configured service
354
+ pytest -m integration -v # same checks, in the test suite
355
+ ```
356
+
357
+ ## Licence
358
+
359
+ MIT. Not affiliated with or endorsed by the Department for Transport or
360
+ Geoplace. Street Manager documentation is © Crown copyright, available under
361
+ the Open Government Licence v3.0.