|
9 | 9 | [](https://pypi.org/project/vgi-easter/) |
10 | 10 | [](LICENSE) |
11 | 11 |
|
12 | | -A minimal [VGI (Vector Gateway Interface)](https://github.com/Query-farm) worker |
13 | | -that computes the date of **Western (Gregorian) Easter Sunday** for a given year |
14 | | -and exposes it to DuckDB as a SQL scalar function. |
| 12 | +A tiny [VGI (Vector Gateway Interface)](https://github.com/Query-farm) worker |
| 13 | +that gives DuckDB one SQL function — `easter_date(year)` — returning the date of |
| 14 | +Western (Gregorian) Easter Sunday. It has no external data and almost no code, |
| 15 | +which makes it a clean, copyable example of a VGI scalar-function worker. |
| 16 | + |
| 17 | +## Quick start |
| 18 | + |
| 19 | +In DuckDB: |
15 | 20 |
|
16 | 21 | ```sql |
17 | 22 | ATTACH 'easter' (TYPE 'vgi', LOCATION 'uvx vgi-easter'); |
18 | 23 |
|
19 | | -SELECT easter.easter_date(2025); |
20 | | --- 2025-04-20 |
| 24 | +SELECT easter.easter_date(2025); -- 2025-04-20 |
21 | 25 |
|
22 | 26 | SELECT year, easter.easter_date(year) AS easter |
23 | | -FROM range(2020, 2031) t(year); |
24 | | --- 2020 2020-04-12 |
25 | | --- 2021 2021-04-04 |
26 | | --- ... |
| 27 | +FROM range(2020, 2025) t(year); |
27 | 28 | ``` |
28 | 29 |
|
29 | | -The function `easter_date(year)` takes a `BIGINT` year and returns a `DATE`, |
30 | | -computed with the Anonymous Gregorian algorithm (the Meeus/Jones/Butcher |
31 | | -*Computus*). It is pure standard-library arithmetic — no network calls, no |
32 | | -external data — which makes this repo a clean, self-contained example of a VGI |
33 | | -scalar-function worker. |
| 30 | +DuckDB launches the worker for you, and `easter_date` then behaves like a native |
| 31 | +function (a null year yields a null date). The `uvx vgi-easter` location fetches |
| 32 | +the worker on demand; to keep it around, `pip install vgi-easter`. |
34 | 33 |
|
35 | 34 | ## How it works |
36 | 35 |
|
37 | | -VGI lets a worker process publish catalogs, schemas, and functions that DuckDB |
38 | | -can `ATTACH` and query as if they were native. Data crosses the boundary as |
39 | | -Apache Arrow IPC, so values stay columnar end to end. |
| 36 | +A VGI worker publishes catalogs, schemas, and functions that DuckDB can `ATTACH` |
| 37 | +and query as if they were built in. Values cross the boundary as Apache Arrow, |
| 38 | +so they stay columnar end to end. |
40 | 39 |
|
41 | | -This worker publishes one catalog: |
| 40 | +This worker publishes a single function: |
42 | 41 |
|
43 | 42 | ``` |
44 | | -easter (catalog, data version 1.0.0) |
45 | | -└── main (schema) |
46 | | - └── easter_date(year BIGINT) -> DATE |
| 43 | +easter (catalog) |
| 44 | +└── main (schema) |
| 45 | + └── easter_date(year BIGINT) → DATE |
47 | 46 | ``` |
48 | 47 |
|
49 | | -`year` propagates nulls — a null year yields a null date. |
50 | | - |
51 | | -The entire implementation lives in [`easter_worker.py`](easter_worker.py): |
52 | | - |
53 | | -- `_easter_sunday(year)` — the Computus, returning a `datetime.date`. |
54 | | -- `EasterDateFunction` — a `ScalarFunction` mapping an `Int64Array` of years to |
55 | | - a `date32` array, with metadata and SQL examples for catalog introspection. |
56 | | -- `EasterCatalog` / `EasterWorker` — wire the function into a VGI catalog that |
57 | | - advertises a stable `data_version` (`1.0.0`) and a git-SHA |
58 | | - `implementation_version` (from `VGI_EASTER_GIT_COMMIT`). |
| 48 | +The whole implementation is ~160 lines in |
| 49 | +[`easter_worker.py`](easter_worker.py): the date calculation |
| 50 | +(`_easter_sunday`, the Anonymous Gregorian *Computus* — pure standard library), |
| 51 | +a `ScalarFunction` that maps an Arrow array of years to dates, and a few lines |
| 52 | +wiring it into a catalog. |
59 | 53 |
|
60 | | -## Requirements |
| 54 | +## Running it |
61 | 55 |
|
62 | | -- Python **3.13+** |
63 | | -- [`uv`](https://docs.astral.sh/uv/) (recommended) or `pip` |
64 | | - |
65 | | -The only dependency is [`vgi-python`](https://pypi.org/project/vgi-python/) (the |
66 | | -`http` extra adds the HTTP-server transport); it is published on PyPI, so no |
67 | | -sibling checkouts are needed. |
68 | | - |
69 | | -## Installing |
70 | | - |
71 | | -```bash |
72 | | -# Install from PyPI (provides the vgi-easter and vgi-easter-http commands) |
73 | | -pip install vgi-easter |
74 | | -# or run it ad hoc without installing |
75 | | -uvx vgi-easter |
76 | | -``` |
77 | | - |
78 | | -## Running |
79 | | - |
80 | | -The worker supports both VGI transports. Two console scripts are installed: |
81 | | -`vgi-easter` (stdio) and `vgi-easter-http` (HTTP server). |
82 | | - |
83 | | -### stdio (DuckDB spawns the worker) |
84 | | - |
85 | | -DuckDB runs the worker as a subprocess and talks to it over stdin/stdout. No |
86 | | -server to manage — point the LOCATION at the installed command (or `uvx |
87 | | -vgi-easter` to fetch it on demand): |
88 | | - |
89 | | -```sql |
90 | | -ATTACH 'easter' (TYPE 'vgi', LOCATION 'uvx vgi-easter'); |
91 | | -SELECT easter.easter_date(2025); |
92 | | -DETACH easter; |
93 | | -``` |
| 56 | +Once installed, the package gives you one command per VGI transport: |
94 | 57 |
|
95 | | -### HTTP |
| 58 | +| Command | Transport | Use it when | |
| 59 | +| ----------------- | --------- | -------------------------------------------------------- | |
| 60 | +| `vgi-easter` | stdio | DuckDB spawns the worker as a subprocess (the quickstart)| |
| 61 | +| `vgi-easter-http` | HTTP | you want a long-running server to attach to | |
96 | 62 |
|
97 | | -Start the worker as an HTTP server (`vgi-easter-http` calls |
98 | | -`EasterWorker.main_http()`): |
| 63 | +To run over HTTP, start the server and attach to its URL: |
99 | 64 |
|
100 | 65 | ```bash |
101 | 66 | VGI_SIGNING_KEY=dev vgi-easter-http --host 0.0.0.0 --port 8000 |
102 | 67 | ``` |
103 | 68 |
|
104 | | -Then attach over HTTP (the VGI extension auto-loads `httpfs`): |
105 | | - |
106 | 69 | ```sql |
107 | 70 | ATTACH 'easter' (TYPE 'vgi', LOCATION 'http://localhost:8000'); |
108 | | -SELECT easter.easter_date(2025); |
109 | 71 | ``` |
110 | 72 |
|
111 | | -### From a source checkout |
112 | | - |
113 | | -The two modules also carry inline [PEP 723](https://peps.python.org/pep-0723/) |
114 | | -metadata, so you can run them directly without installing: |
| 73 | +Working from a checkout instead? Both modules carry |
| 74 | +[PEP 723](https://peps.python.org/pep-0723/) metadata, so `uv run |
| 75 | +easter_worker.py` (stdio) and `uv run serve.py` (HTTP) run without installing |
| 76 | +anything. |
115 | 77 |
|
116 | | -```bash |
117 | | -uv run --python 3.13 easter_worker.py # stdio |
118 | | -VGI_SIGNING_KEY=dev uv run --python 3.13 serve.py --host 0.0.0.0 --port 8000 # HTTP |
119 | | -``` |
| 78 | +## Configuration |
120 | 79 |
|
121 | | -## Testing |
| 80 | +| Variable | Purpose | |
| 81 | +| --------------------------------- | ------------------------------------------------------------- | |
| 82 | +| `VGI_SIGNING_KEY` | Signing key for HTTP state tokens (required by the HTTP server).| |
| 83 | +| `VGI_HTTP_HOST` / `VGI_HTTP_PORT` | HTTP bind address (default: all interfaces / `8000`). | |
| 84 | +| `VGI_EASTER_GIT_COMMIT` | Reported as the catalog's `implementation_version`. | |
| 85 | +| `VGI_WORKER_DEBUG` | Set to `1` for debug logging. | |
122 | 86 |
|
123 | | -### Unit tests (pytest) |
| 87 | +## Development |
124 | 88 |
|
125 | | -The `tests/` suite checks the Computus against known Easter dates (including the |
126 | | -March 22 / April 25 extremes) and the Arrow `compute()` path including null |
127 | | -propagation: |
| 89 | +Requires Python 3.13+ and [`uv`](https://docs.astral.sh/uv/); the only |
| 90 | +dependency is [`vgi-python`](https://pypi.org/project/vgi-python/). |
128 | 91 |
|
129 | 92 | ```bash |
130 | | -uv run --python 3.13 \ |
131 | | - --with pytest --with vgi-python \ |
132 | | - pytest tests/ --rootdir=. -o "addopts=" -q |
| 93 | +uv run --frozen pytest tests/ -q |
133 | 94 | ``` |
134 | 95 |
|
135 | | -The `--rootdir=. -o "addopts="` flags keep pytest from picking up an upstream |
136 | | -`pyproject.toml` that injects `--mypy --ruff`. `conftest.py` puts the repo root |
137 | | -on `sys.path` so the tests can `import easter_worker`. |
138 | | - |
139 | | -### SQL integration tests (sqllogictest) |
140 | | - |
141 | | -`test/sql/` contains [sqllogictest](https://duckdb.org/dev/sqllogictest/intro) |
142 | | -files that run against the worker through the real DuckDB VGI extension: |
| 96 | +The `tests/` suite covers the Easter calculation (including the March 22 / |
| 97 | +April 25 extremes) and the Arrow compute path. A separate |
| 98 | +[sqllogictest](https://duckdb.org/dev/sqllogictest/intro) suite in `test/sql/` |
| 99 | +drives the worker through the **real** DuckDB `vgi` extension. CI runs both on |
| 100 | +Linux, macOS, and Windows — see [`ci/README.md`](ci/README.md). |
143 | 101 |
|
144 | | -- `easter_catalog.test` — catalog discovery, `data_version_spec`, `ATTACH` and |
145 | | - schema introspection. |
146 | | -- `easter_function.test` — scalar evaluation, the `DATE` result type, and null |
147 | | - propagation. |
| 102 | +## Releasing |
148 | 103 |
|
149 | | -Both are gated on `require-env VGI_EASTER_WORKER`, so point that at a worker |
150 | | -LOCATION (a stdio command or an HTTP URL) and run them with the DuckDB |
151 | | -`unittest` binary built with the VGI extension. |
152 | | - |
153 | | -## Environment variables |
154 | | - |
155 | | -| Variable | Purpose | |
156 | | -| ------------------------- | ------------------------------------------------------------------- | |
157 | | -| `VGI_SIGNING_KEY` | Stable key for state-token signing (required for the HTTP server). | |
158 | | -| `VGI_EASTER_GIT_COMMIT` | Git SHA reported as the catalog's `implementation_version`. | |
159 | | -| `VGI_HTTP_PORT` / `VGI_HTTP_HOST` | HTTP bind address (defaults: `8000` / all interfaces). | |
160 | | -| `VGI_WORKER_DEBUG` | Set to `1` for debug logging. | |
161 | | - |
162 | | -## Publishing |
163 | | - |
164 | | -This repo is a packaged distribution (`vgi-easter`) built with hatchling: |
165 | | - |
166 | | -```bash |
167 | | -uv build # writes dist/*.whl and dist/*.tar.gz |
168 | | -uv publish # upload to PyPI (needs a token) |
169 | | -``` |
| 104 | +`vgi-easter` is built with hatchling and published to PyPI by CI when a GitHub |
| 105 | +Release is created (it runs the test suites, then `uv build && uv publish`). To |
| 106 | +cut a release, bump `version` in `pyproject.toml` and publish a GitHub Release. |
170 | 107 |
|
171 | 108 | ## License |
172 | 109 |
|
|
0 commit comments