Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@
"coverage-gutters.xmlname": "coverage.xml",
"python.analysis.extraPaths": ["${workspaceFolder}/src"],
"python.defaultInterpreterPath": ".venv/bin/python",
"python.formatting.provider": "ruff",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.testing.cwd": "${workspaceFolder}",
"python.testing.pytestArgs": ["--cov-report=xml"],
"python.testing.pytestEnabled": true,
Expand Down
22 changes: 16 additions & 6 deletions .github/SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
# Security Policy

We take the security of this project seriously. We appreciate your efforts to responsibly disclose your findings and will make every effort to acknowledge your contributions.
We take the security of this project seriously. We appreciate your efforts to
responsibly disclose your findings and will make every effort to acknowledge
your contributions.

## Reporting a Vulnerability

**Please do not report security vulnerabilities through public GitHub issues.**

If you discover a security vulnerability, please report it via GitHub's private vulnerability reporting:
If you discover a security vulnerability, please report it privately using
GitHub's private vulnerability reporting at
<https://github.com/frenck/python-twentemilieu/security/advisories/new>.

1. Navigate to the Security tab of this repository
2. Click "Report a vulnerability"
3. Provide a description of the vulnerability and steps to reproduce
Alternatively, you can email [opensource@frenck.dev](mailto:opensource@frenck.dev)
directly.

After the initial report, we will keep you informed of the progress towards a fix and may ask for additional information or guidance.
When reporting, please include:

1. A description of the vulnerability and its potential impact.
2. Steps to reproduce the issue or a proof of concept.
3. Any known mitigations or workarounds.

After the initial report, we will keep you informed of the progress towards a
fix and may ask for additional information or guidance.

We aim to address reported vulnerabilities within 90 days.

Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/linting.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ jobs:
node-version-file: ".nvmrc"
cache: "npm"
- name: 🏗 Install NPM dependencies
# Versions are locked via package-lock.json; Scorecard's
# Pinned-Dependencies check still scores this 9/10 because npm
# commands can't be hash-pinned the way GitHub Actions can.
# Accepted trade-off.
run: npm install
- name: 🚀 Run prettier
run: poetry run prek run prettier --all-files
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
name: release
url: https://pypi.org/p/twentemilieu
permissions:
attestations: write
contents: write
id-token: write
steps:
Expand Down Expand Up @@ -52,6 +53,28 @@ jobs:
poetry version --no-interaction "${version}"
- name: 🏗 Build package
run: poetry build --no-interaction
- name: 🧭 Locate built wheel
run: |
shopt -s failglob
wheels=(dist/twentemilieu-*-py3-none-any.whl)
if (( ${#wheels[@]} != 1 )); then
echo "::error::Expected exactly one wheel in dist/, found ${#wheels[@]}"
exit 1
fi
echo "WHEEL_PATH=${wheels[0]}" >> "${GITHUB_ENV}"
- name: 📝 Generate CycloneDX SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
file: ${{ env.WHEEL_PATH }}
format: cyclonedx-json
output-file: twentemilieu.cdx.json
upload-artifact: false
upload-release-assets: false
- name: 📝 Attest SBOM against built artifacts
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
with:
subject-path: "dist/*"
sbom-path: twentemilieu.cdx.json
- name: 🚀 Publish to PyPi
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
# policy, and support documentation.

name: Scorecard supply-chain security
"on":

# yamllint disable-line rule:truthy
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,4 @@ node_modules/

# Deepcode AI
.dccache
.claude/
4 changes: 3 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ repos:
name: ⮐ Fix End of Files
language: system
types: [text]
exclude: ^tests/(?:.*/)?__snapshots__/
entry: poetry run end-of-file-fixer
stages: [commit, push, manual]
- id: ty
Expand Down Expand Up @@ -118,12 +119,13 @@ repos:
name: 🧪 Running tests and test coverage with pytest
language: system
types: [python]
entry: poetry run pytest
entry: poetry run pytest --cov twentemilieu tests
pass_filenames: false
- id: trailing-whitespace
name: ✄ Trim Trailing Whitespace
language: system
types: [text]
exclude: ^tests/(?:.*/)?__snapshots__/
entry: poetry run trailing-whitespace-fixer
stages: [commit, push, manual]
- id: yamllint
Expand Down
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

[![Build Status][build-shield]][build]
[![Code Coverage][codecov-shield]][codecov]
[![OpenSSF Scorecard][scorecard-shield]][scorecard]
[![Open in Dev Containers][devcontainer-shield]][devcontainer]

[![Sponsor Frenck via GitHub Sponsors][github-sponsors-shield]][github-sponsors]
Expand Down Expand Up @@ -53,6 +54,73 @@ if __name__ == "__main__":
asyncio.run(main())
```

## Behavior & error handling

Each API call is a single HTTP POST — the client does **not** retry on
transient failures. If you need retries with backoff, wrap the calls in
your own retry loop (or use something like [`backoff`][backoff]).

Requests are bounded by a per-call timeout, which defaults to 10 seconds
and can be overridden via the `request_timeout` constructor argument:

```python
async with TwenteMilieu(
post_code="1234AB",
house_number=1,
request_timeout=5,
) as twente:
...
```

Cancellation is plain `asyncio`: cancelling the task awaiting
`unique_id()` or `update()` aborts the in-flight request, and the
context manager still cleans up the internal session on exit.

All exceptions inherit from `TwenteMilieuError`:

| Exception | Raised when |
| ----------------------------- | ------------------------------------------------------ |
| `TwenteMilieuConnectionError` | Request timed out or the network / API was unreachable |
| `TwenteMilieuAddressError` | The address could not be found in the service area |
| `TwenteMilieuError` | Any other unexpected response from the API |

## Command-line interface

This package ships with an optional CLI that is handy for quickly
inspecting the waste pickup schedule for an address. Install it with
the `cli` extra:

```bash
pip install "twentemilieu[cli]"
```

The CLI exposes two commands: `upcoming` (a chronologically sorted list
of the next pickups across all waste types) and `next` (the single next
pickup, optionally filtered by waste type). Both commands accept
`--post-code`, `--house-number`, and an optional `--house-letter`, and
both support a `--json` flag for machine-readable output.

```bash
# Show the next 5 pickups across all waste types
twentemilieu upcoming --post-code 7531AT --house-number 148

# Limit to the next 3 pickups and emit JSON
twentemilieu upcoming --post-code 7531AT --house-number 148 --limit 3 --json

# Show the very next pickup (any waste type)
twentemilieu next --post-code 7531AT --house-number 148

# Show the next organic pickup only
twentemilieu next --post-code 7531AT --house-number 148 --waste-type organic

# Emit as JSON for use in scripts
twentemilieu next --post-code 7531AT --house-number 148 --waste-type organic --json
```

Address options can also be supplied via the `TWENTEMILIEU_POST_CODE`,
`TWENTEMILIEU_HOUSE_NUMBER`, and `TWENTEMILIEU_HOUSE_LETTER` environment
variables. Run any command with `--help` for the full reference.

## Changelog & Releases

This repository keeps a change log using [GitHub's releases][releases]
Expand Down Expand Up @@ -144,6 +212,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

[backoff]: https://github.com/litl/backoff
[build-shield]: https://github.com/frenck/python-twentemilieu/actions/workflows/tests.yaml/badge.svg
[build]: https://github.com/frenck/python-twentemilieu/actions/workflows/tests.yaml
[codecov-shield]: https://codecov.io/gh/frenck/python-twentemilieu/branch/main/graph/badge.svg
Expand All @@ -167,4 +236,6 @@ SOFTWARE.
[pypi]: https://pypi.org/project/twentemilieu
[releases-shield]: https://img.shields.io/github/release/frenck/python-twentemilieu.svg
[releases]: https://github.com/frenck/python-twentemilieu/releases
[scorecard-shield]: https://api.scorecard.dev/projects/github.com/frenck/python-twentemilieu/badge
[scorecard]: https://scorecard.dev/viewer/?uri=github.com/frenck/python-twentemilieu
[semver]: http://semver.org/spec/v2.0.0.html
2 changes: 1 addition & 1 deletion examples/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

async def main() -> None:
"""Show example on stats from Twente Milieu."""
async with TwenteMilieu(post_code="7531LA", house_number=16) as twente:
async with TwenteMilieu(post_code="7531AT", house_number=148) as twente:
print(twente)
unique_id = await twente.unique_id()
print("Unique Address ID:", unique_id)
Expand Down
Loading
Loading