h2hdb-opds 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.
@@ -0,0 +1,119 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - pyproject.toml
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ validate:
14
+ runs-on: ubuntu-latest
15
+ outputs:
16
+ bumped: ${{ steps.version.outputs.bumped }}
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 2
21
+
22
+ - uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.14"
25
+
26
+ - name: Install packaging tools
27
+ run: python -m pip install --upgrade packaging build
28
+
29
+ - name: Check version bump
30
+ id: version
31
+ run: |
32
+ python - <<'PY'
33
+ import os
34
+ from pathlib import Path
35
+ import subprocess
36
+ import tomllib
37
+
38
+ from packaging.version import Version
39
+
40
+
41
+ def project_version(document: str) -> Version:
42
+ return Version(str(tomllib.loads(document)["project"]["version"]))
43
+
44
+
45
+ current = project_version(
46
+ Path("pyproject.toml").read_text(encoding="utf-8")
47
+ )
48
+ previous_document = subprocess.run(
49
+ ["git", "show", "HEAD~1:pyproject.toml"],
50
+ check=False,
51
+ capture_output=True,
52
+ text=True,
53
+ )
54
+ previous = (
55
+ project_version(previous_document.stdout)
56
+ if previous_document.returncode == 0
57
+ else None
58
+ )
59
+ if previous is not None and current < previous:
60
+ raise SystemExit(
61
+ f"project.version decreased from {previous} to {current}"
62
+ )
63
+
64
+ bumped = previous is None or current > previous
65
+ output_path = Path(os.environ["GITHUB_OUTPUT"])
66
+ with output_path.open("a", encoding="utf-8") as output:
67
+ print(f"current={current}", file=output)
68
+ print(f"previous={previous or ''}", file=output)
69
+ print(f"bumped={'true' if bumped else 'false'}", file=output)
70
+ PY
71
+
72
+ - name: Install project and development dependencies
73
+ run: python -m pip install -e ".[dev]"
74
+
75
+ - name: Check formatting
76
+ run: python -m black --check .
77
+
78
+ - name: Lint
79
+ run: python -m ruff check .
80
+
81
+ - name: Type-check
82
+ run: python -m mypy src tests
83
+
84
+ - name: Test
85
+ run: python -m pytest
86
+
87
+ - name: Build distributions
88
+ run: python -m build
89
+
90
+ - name: Smoke-test wheel
91
+ run: |
92
+ WHEEL="$(find dist -maxdepth 1 -name '*.whl' -print -quit)"
93
+ test -n "$WHEEL"
94
+ python -m pip install --force-reinstall --no-deps "$WHEEL"
95
+ python -c "import h2hdb_opds"
96
+
97
+ - name: Store distributions
98
+ if: steps.version.outputs.bumped == 'true'
99
+ uses: actions/upload-artifact@v4
100
+ with:
101
+ name: distributions
102
+ path: dist/
103
+ if-no-files-found: error
104
+
105
+ publish:
106
+ needs: validate
107
+ if: needs.validate.outputs.bumped == 'true'
108
+ runs-on: ubuntu-latest
109
+ environment: pypi
110
+ permissions:
111
+ contents: read
112
+ id-token: write
113
+ steps:
114
+ - uses: actions/download-artifact@v4
115
+ with:
116
+ name: distributions
117
+ path: dist/
118
+
119
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,13 @@
1
+ .DS_Store
2
+ .coverage
3
+ .mypy_cache/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .venv/
7
+ __pycache__/
8
+ *.egg-info/
9
+ *.py[cod]
10
+ build/
11
+ dist/
12
+ htmlcov/
13
+ uv.lock
@@ -0,0 +1,98 @@
1
+ # Agent Instructions
2
+
3
+ ## Project
4
+
5
+ `h2hdb-opds` is the OPDS 2.0 HTTP service for H2HDB catalogs. Python must be
6
+ run through `uv run --no-sync`, using the repository-local virtual environment.
7
+ The supported Python version is defined by `requires-python` in
8
+ `pyproject.toml`.
9
+
10
+ ## Ownership Boundary
11
+
12
+ This repository owns:
13
+
14
+ - FastAPI and ASGI integration;
15
+ - OPDS 2.0 models and serialization;
16
+ - navigation, publications, search, and pagination;
17
+ - authentication;
18
+ - acquisition, Range, and conditional HTTP responses.
19
+
20
+ The `h2hdb` core package exclusively owns connectors, transactions, database
21
+ schema and migrations, durable queues, coordination fencing, and catalog
22
+ repositories. Depend only on core public interfaces. Do not import connector or
23
+ repository internals and do not create or migrate schema here. Database access
24
+ is always forced to read-only, and the service exposes only published catalog
25
+ revisions. Production startup uses `open_database()` for its compatibility
26
+ check. Injected readers are checked through their public `check_compatibility()`
27
+ method. No startup path may call `migrate()`.
28
+
29
+ ## Module Layout and HTTP Invariants
30
+
31
+ - `config.py` owns the frozen OPDS, server, authentication, and nested core
32
+ configuration models.
33
+ - `app.py` wires the FastAPI lifespan and routes to a compatible public
34
+ `CatalogReader`.
35
+ - `serialization.py` owns OPDS feed, publication, navigation, search template,
36
+ and pagination serialization.
37
+ - `auth.py` owns Basic authentication and the public OPDS authentication
38
+ document. Credential comparisons must use `secrets.compare_digest`.
39
+ - `acquisition.py` owns artifact file responses and HTTP validators/ranges.
40
+
41
+ Feed responses use `application/opds+json`; standalone publications use
42
+ `application/opds-publication+json`. Acquisition ETags are strong SHA-256
43
+ validators from the verified artifact. Apply the RFC precondition order from
44
+ `If-Match` through `If-Modified-Since`. Support only one byte range; invalid,
45
+ multiple, and unsatisfiable byte ranges return 416 with `Content-Range`, unknown
46
+ range units are ignored, and an `If-Range` mismatch returns the complete 200
47
+ representation. An `If-Range` date comparison is exact.
48
+
49
+ All acquisition paths must remain under the configured real artifact root and
50
+ must be opened without following symlinks. Verify size and SHA-256 before sending
51
+ a strong validator. Absolute links come only from the canonical public base URL,
52
+ never the request Host header. Basic authentication requires an effective HTTPS
53
+ request and either local TLS or an explicitly trusted TLS-terminating proxy.
54
+ Catalog listing and counts use core's pinned `require_artifact` filter so every
55
+ serialized OPDS Publication has an acquisition link.
56
+
57
+ All feed, publication, pagination, and acquisition links carry their selected
58
+ revision. Resolve an omitted revision to the current snapshot and an explicit
59
+ revision through core's public revision-history lookup. Map a missing revision
60
+ to an explicit 404, and pass the resolved `CatalogRevision` into every list,
61
+ publication, and artifact read. Never fall back from a missing requested
62
+ revision to current data or mix snapshots across pagination.
63
+
64
+ ## Environment and Commands
65
+
66
+ This is a standalone repository, not a uv workspace. `uv.lock` is ignored and
67
+ must not become a build, test, or runtime input.
68
+
69
+ ```bash
70
+ uv venv --python 3.14
71
+ uv pip install -e ".[dev]"
72
+ uv run --no-sync ruff check .
73
+ uv run --no-sync black --check .
74
+ uv run --no-sync mypy src tests
75
+ uv run --no-sync pytest
76
+ uv run --no-sync python -m build
77
+ ```
78
+
79
+ If the environment is damaged, run `scripts/rebuild-env.sh`.
80
+
81
+ ## Shared Finalization
82
+
83
+ The repository keeps provider-neutral hooks in `scripts/hooks/`.
84
+
85
+ - After changing Python, run `bash scripts/hooks/finalize-python.sh`.
86
+ - After changing Markdown, run `bash scripts/hooks/finalize-markdown.sh`.
87
+
88
+ Do not create agent-specific copies of these implementations.
89
+
90
+ ## Design and Compatibility
91
+
92
+ The project is pre-1.0. Prefer clear responsibility boundaries and the cleanest
93
+ end state over compatibility shims or deprecated aliases. Follow SOLID
94
+ principles. Keep authentication, file I/O, and response streaming separate from
95
+ catalog query construction and never perform schema mutation at service startup.
96
+
97
+ `CLAUDE.md` documents the same repository rules. Keep both files synchronized
98
+ when changing workflow, ownership, testing, or tooling conventions.
@@ -0,0 +1,95 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides repository guidance to Claude Code and other coding agents.
4
+
5
+ ## Communication
6
+
7
+ - Claude 必須以繁體中文回答所有對話內容;程式碼、指令、檔名與專有名詞維持原文。
8
+
9
+ ## Project
10
+
11
+ `h2hdb-opds` exposes an H2HDB catalog through an OPDS 2.0 HTTP service. It owns
12
+ FastAPI and ASGI integration, OPDS models and serialization, navigation,
13
+ publications, search, pagination, authentication, and acquisition responses
14
+ including Range and conditional HTTP behavior.
15
+
16
+ The `h2hdb` core package exclusively owns database connectors, transactions,
17
+ schema migrations, durable queues, coordination fencing, and catalog
18
+ repositories. Use only core public interfaces. Never import connector or
19
+ repository internals, create or migrate schema, or expose unpublished catalog
20
+ state. Database access is always forced to read-only. Production startup calls
21
+ the public `open_database()` compatibility path; injected readers call only
22
+ `check_compatibility()`. No startup path may call `migrate()`.
23
+
24
+ ## Module Layout and HTTP Invariants
25
+
26
+ - `config.py` owns the frozen OPDS, server, authentication, and nested core
27
+ configuration models.
28
+ - `app.py` wires the FastAPI lifespan and routes to a compatible public
29
+ `CatalogReader`.
30
+ - `serialization.py` owns OPDS feed, publication, navigation, search template,
31
+ and pagination serialization.
32
+ - `auth.py` owns Basic authentication and the public OPDS authentication
33
+ document. Credential comparisons must use `secrets.compare_digest`.
34
+ - `acquisition.py` owns artifact file responses and HTTP validators/ranges.
35
+
36
+ Feed responses use `application/opds+json`; standalone publications use
37
+ `application/opds-publication+json`. Acquisition ETags are strong SHA-256
38
+ validators from the verified artifact. Apply the RFC precondition order from
39
+ `If-Match` through `If-Modified-Since`. Support only one byte range; invalid,
40
+ multiple, and unsatisfiable byte ranges return 416 with `Content-Range`, unknown
41
+ range units are ignored, and an `If-Range` mismatch returns the complete 200
42
+ representation. An `If-Range` date comparison is exact.
43
+
44
+ All acquisition paths must remain under the configured real artifact root and
45
+ must be opened without following symlinks. Verify size and SHA-256 before sending
46
+ a strong validator. Absolute links come only from the canonical public base URL,
47
+ never the request Host header. Basic authentication requires an effective HTTPS
48
+ request and either local TLS or an explicitly trusted TLS-terminating proxy.
49
+ Catalog listing and counts use core's pinned `require_artifact` filter so every
50
+ serialized OPDS Publication has an acquisition link.
51
+
52
+ All feed, publication, pagination, and acquisition links carry their selected
53
+ revision. Resolve an omitted revision to the current snapshot and an explicit
54
+ revision through core's public revision-history lookup. Map a missing revision
55
+ to an explicit 404, and pass the resolved `CatalogRevision` into every list,
56
+ publication, and artifact read. Never fall back from a missing requested
57
+ revision to current data or mix snapshots across pagination.
58
+
59
+ ## Development
60
+
61
+ This is an independent repository and not part of a uv workspace. `uv.lock` is
62
+ ignored and must not be used by build, test, or runtime workflows. Always run
63
+ Python through `uv run --no-sync` after installing the editable environment.
64
+
65
+ ```bash
66
+ uv venv --python 3.14
67
+ uv pip install -e ".[dev]"
68
+ uv run --no-sync ruff check .
69
+ uv run --no-sync black --check .
70
+ uv run --no-sync mypy src tests
71
+ uv run --no-sync pytest
72
+ uv run --no-sync python -m build
73
+ ```
74
+
75
+ Use `scripts/rebuild-env.sh` to recreate a damaged environment.
76
+
77
+ ## Shared Finalization
78
+
79
+ Provider-neutral hooks live in `scripts/hooks/` and are shared by humans and
80
+ all coding agents.
81
+
82
+ - After Python changes, run `bash scripts/hooks/finalize-python.sh`.
83
+ - After Markdown changes, run `bash scripts/hooks/finalize-markdown.sh`.
84
+
85
+ Do not duplicate their implementation under an agent-specific directory.
86
+
87
+ ## Design
88
+
89
+ This project is pre-1.0. Prefer the cleanest architecture over compatibility
90
+ shims and deprecated aliases. Follow SOLID principles. Keep authentication,
91
+ file I/O, and response streaming separate from catalog query construction, and
92
+ never perform schema mutation during service startup.
93
+
94
+ Keep this document synchronized with `AGENTS.md` whenever ownership, workflow,
95
+ testing, or tooling conventions change.