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.
- streetworks-0.1.0/.github/workflows/ci.yml +21 -0
- streetworks-0.1.0/.github/workflows/integration.yml +32 -0
- streetworks-0.1.0/.github/workflows/publish.yml +33 -0
- streetworks-0.1.0/.github/workflows/regenerate-models.yml +43 -0
- streetworks-0.1.0/.gitignore +14 -0
- streetworks-0.1.0/CHANGELOG.md +27 -0
- streetworks-0.1.0/CONTRIBUTING.md +27 -0
- streetworks-0.1.0/LICENSE +21 -0
- streetworks-0.1.0/PKG-INFO +361 -0
- streetworks-0.1.0/README.md +304 -0
- streetworks-0.1.0/docs/DTRO_SCHEMAS.md +56 -0
- streetworks-0.1.0/docs/INTEGRATION.md +103 -0
- streetworks-0.1.0/docs/RELEASING.md +32 -0
- streetworks-0.1.0/examples/datavia_queries.py +34 -0
- streetworks-0.1.0/examples/dtro_consume.py +21 -0
- streetworks-0.1.0/examples/opendata_fastapi.py +35 -0
- streetworks-0.1.0/examples/streetmanager_async_bulk.py +24 -0
- streetworks-0.1.0/examples/streetmanager_quickstart.py +15 -0
- streetworks-0.1.0/pyproject.toml +74 -0
- streetworks-0.1.0/scripts/generate_models.py +200 -0
- streetworks-0.1.0/scripts/smoke_test.py +271 -0
- streetworks-0.1.0/src/streetworks/__init__.py +40 -0
- streetworks-0.1.0/src/streetworks/_oauth.py +115 -0
- streetworks-0.1.0/src/streetworks/_transport.py +197 -0
- streetworks-0.1.0/src/streetworks/datavia/__init__.py +23 -0
- streetworks-0.1.0/src/streetworks/datavia/client.py +389 -0
- streetworks-0.1.0/src/streetworks/datavia/filters.py +141 -0
- streetworks-0.1.0/src/streetworks/dtro/__init__.py +5 -0
- streetworks-0.1.0/src/streetworks/dtro/client.py +334 -0
- streetworks-0.1.0/src/streetworks/exceptions.py +97 -0
- streetworks-0.1.0/src/streetworks/opendata/__init__.py +21 -0
- streetworks-0.1.0/src/streetworks/opendata/models.py +74 -0
- streetworks-0.1.0/src/streetworks/opendata/sns.py +197 -0
- streetworks-0.1.0/src/streetworks/streetmanager/__init__.py +12 -0
- streetworks-0.1.0/src/streetworks/streetmanager/auth.py +203 -0
- streetworks-0.1.0/src/streetworks/streetmanager/client.py +467 -0
- streetworks-0.1.0/src/streetworks/streetmanager/environments.py +58 -0
- streetworks-0.1.0/src/streetworks/streetmanager/models/__init__.py +17 -0
- streetworks-0.1.0/tests/fixtures/mini-swagger.json +45 -0
- streetworks-0.1.0/tests/integration/__init__.py +0 -0
- streetworks-0.1.0/tests/integration/test_connectivity.py +140 -0
- streetworks-0.1.0/tests/test_datavia.py +130 -0
- streetworks-0.1.0/tests/test_dtro.py +155 -0
- streetworks-0.1.0/tests/test_model_generation.py +56 -0
- streetworks-0.1.0/tests/test_opendata.py +94 -0
- streetworks-0.1.0/tests/test_streetmanager.py +152 -0
- 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,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
|
+
[](https://github.com/KFergusonUK/StreetWorks-SDK/actions/workflows/ci.yml)
|
|
61
|
+
[](https://pypi.org/project/streetworks/)
|
|
62
|
+
[](https://pypi.org/project/streetworks/)
|
|
63
|
+
[](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.
|