gwdatafind-server 2.2.0__tar.gz → 2.3.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 (89) hide show
  1. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/.gitlab-ci.yml +1 -0
  2. gwdatafind_server-2.3.0/AGENTS.md +94 -0
  3. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/CHANGELOG.md +9 -0
  4. gwdatafind_server-2.3.0/CLAUDE.md +94 -0
  5. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/Dockerfile +9 -9
  6. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/PKG-INFO +1 -1
  7. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/config/gunicorn.conf.py +7 -9
  8. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/config/gwdatafind-server.toml +1 -0
  9. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docker-compose.yml +12 -5
  10. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docs/changelog.md +9 -0
  11. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docs/configuration.md +1 -3
  12. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/examples/auth/docker-compose.yml +9 -2
  13. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/examples/auth/gwdatafind-server.toml +4 -0
  14. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/examples/diskcache/docker-compose.yml +10 -1
  15. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/examples/diskcache/gwdatafind-server.toml +1 -0
  16. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/__init__.py +14 -8
  17. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/_version.py +3 -3
  18. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/app.py +28 -22
  19. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/source/diskcache.py +1 -2
  20. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/source/pelican.py +120 -80
  21. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server.egg-info/PKG-INFO +1 -1
  22. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server.egg-info/SOURCES.txt +4 -0
  23. gwdatafind_server-2.3.0/gwdatafind_server.egg-info/scm_file_list.json +82 -0
  24. gwdatafind_server-2.3.0/gwdatafind_server.egg-info/scm_version.json +8 -0
  25. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/pyproject.toml +8 -4
  26. gwdatafind_server-2.3.0/tests/test_app.py +122 -0
  27. gwdatafind_server-2.2.0/tests/test_app.py +0 -6
  28. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/.gitignore +0 -0
  29. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/.markdownlint.yml +0 -0
  30. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/LICENSE +0 -0
  31. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/MANIFEST.in +0 -0
  32. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/README.md +0 -0
  33. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/config/docker-compose.yml +0 -0
  34. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/config/gwdatafind-server.service +0 -0
  35. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/diskcache/Makefile +0 -0
  36. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/diskcache/create_testdata.py +0 -0
  37. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/diskcache/diskcache.rsc +0 -0
  38. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docs/api/index.md +0 -0
  39. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docs/api/ldr.md +0 -0
  40. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docs/api/v1.md +0 -0
  41. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docs/deploy/examples/auth.md +0 -0
  42. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docs/deploy/examples/diskcache.md +0 -0
  43. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docs/deploy/examples/gwosc.md +0 -0
  44. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docs/deploy/examples/index.md +0 -0
  45. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docs/deploy/index.md +0 -0
  46. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docs/index.md +0 -0
  47. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docs/overview.md +0 -0
  48. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/docs/requirements.txt +0 -0
  49. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/api/__init__.py +0 -0
  50. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/api/base.py +0 -0
  51. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/api/ldr.py +0 -0
  52. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/api/utils.py +0 -0
  53. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/api/v1.py +0 -0
  54. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/auth/__init__.py +0 -0
  55. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/auth/core.py +0 -0
  56. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/auth/scitoken.py +0 -0
  57. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/cache.py +0 -0
  58. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/config.py +0 -0
  59. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/credential/__init__.py +0 -0
  60. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/credential/credential.py +0 -0
  61. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/credential/scitoken.py +0 -0
  62. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/gwdatafind_server.wsgi +0 -0
  63. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/source/__init__.py +0 -0
  64. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/source/source.py +0 -0
  65. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server/urls.py +0 -0
  66. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server.egg-info/dependency_links.txt +0 -0
  67. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server.egg-info/requires.txt +0 -0
  68. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/gwdatafind_server.egg-info/top_level.txt +0 -0
  69. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/mkdocs.yml +0 -0
  70. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/setup.cfg +0 -0
  71. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/__init__.py +0 -0
  72. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/cache-0x00ff.dat +0 -0
  73. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/cache-0x0101.dat +0 -0
  74. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/conftest.py +0 -0
  75. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/grid-mapfile +0 -0
  76. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/gwdatafind-server.ini +0 -0
  77. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/gwdatafind-server.toml +0 -0
  78. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/test_api_v1.py +0 -0
  79. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/test_auth_override.py +0 -0
  80. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/test_auth_scitoken.py +0 -0
  81. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/test_cache.py +0 -0
  82. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/test_config.py +0 -0
  83. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/test_credential.py +0 -0
  84. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/test_credential_scitoken.py +0 -0
  85. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/test_source.py +0 -0
  86. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/test_source_diskcache.py +0 -0
  87. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/test_source_pelican.py +0 -0
  88. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/test_urls.py +0 -0
  89. {gwdatafind_server-2.2.0 → gwdatafind_server-2.3.0}/tests/utils.py +0 -0
@@ -53,6 +53,7 @@ include:
53
53
  --bind=0.0.0.0:8080
54
54
  --config=/etc/gwdatafind/gunicorn.conf.py
55
55
  --forwarded-allow-ips="*"
56
+ --workers=1
56
57
  "gwdatafind_server:create_app()"
57
58
  # deploy options
58
59
  deploy_tag: $CI_COMMIT_REF_NAME # use verbatim tag name
@@ -0,0 +1,94 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for AI coding agents (and humans) working in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ GWDataFind Server is a [Flask](https://flask.palletsprojects.com/) application that
8
+ implements the HTTP API used to discover URLs for gravitational-wave observatory data
9
+ files.
10
+ It is developed by the LIGO Scientific Collaboration / IGWN and used in production
11
+ to serve data discovery requests for LIGO/Virgo/KAGRA data.
12
+
13
+ The accompanying Python client library (`gwdatafind`, not in this repo) is what most
14
+ end users interact with; this repo is the server side of that API.
15
+
16
+ Canonical docs live in `docs/` (built with MkDocs, published at
17
+ <https://computing.docs.ligo.org/gwdatafind/server/>).
18
+ Read `docs/overview.md` first for architecture,
19
+ and `docs/configuration.md` for the full config schema —
20
+ both are kept up to date and should be treated as source of truth over any summary here.
21
+
22
+ ## Architecture (brief)
23
+
24
+ - The app (`gwdatafind_server/app.py`, `GWDataFindApp`) runs one or more **sources**
25
+ (`gwdatafind_server/source/`: `diskcache`, `pelican`) on independent async schedules
26
+ (via `flask-apscheduler`). Each source discovers fully-qualified URLs
27
+ (`file://`, `pelican://`, etc.) plus metadata (observatory, dataset, format, GPS
28
+ segment) and feeds it into an in-memory `Cache` (`gwdatafind_server/cache.py`).
29
+ - **URL transforms** (`gwdatafind_server/urls.py`, configured under `[url.transform]`)
30
+ rewrite URLs between schemes (e.g. `pelican://` → `https://`) at query time.
31
+ - **Auth** (`gwdatafind_server/auth/`) supports SciToken bearer-token authorization and
32
+ per-IP/subnet overrides.
33
+ - **Credentials** (`gwdatafind_server/credential/`) are refreshed on a schedule and
34
+ attached to sources that need them (e.g. SciTokens obtained via Kerberos keytab for
35
+ Pelican access).
36
+ - **API** (`gwdatafind_server/api/`): versioned HTTP endpoints (`ldr` legacy, `v1`
37
+ current) built on the shared cache; see `docs/api/`.
38
+ - Configuration is TOML (`gwdatafind_server/config.py`), one config file drives the
39
+ whole app. `python3 -m gwdatafind_server.config <file>` parses and dumps it as JSON
40
+ for validation.
41
+
42
+ ## Repo layout
43
+
44
+ - `gwdatafind_server/` — the application package (source of truth for behavior).
45
+ - `tests/` — pytest suite, one `test_*.py` per module roughly mirroring the package.
46
+ - `docs/` — MkDocs site (nav defined in `mkdocs.yml`); includes API reference and
47
+ deployment examples (`docs/deploy/examples/`).
48
+ - `examples/` — runnable docker-compose deployment examples (diskcache, auth/SciToken).
49
+ - `config/` — example/reference config files (TOML, gunicorn, systemd unit) referenced
50
+ by the docs via `include-markdown`; treat as documentation fixtures, not scratch space.
51
+ - `Dockerfile`, `docker-compose.yml` — container build/quick-start.
52
+ - `.gitlab-ci.yml` — CI pipeline (GitLab, not GitHub). Runs lint/type-check/tests across
53
+ Python 3.11–3.14, builds the Docker image, and builds the MkDocs site.
54
+
55
+ ## Development practices
56
+
57
+ - **Dependency management**: [`uv`](https://docs.astral.sh/uv/).
58
+ Use `uv run …` / `uv sync --group dev` rather than raw `pip`.
59
+ - **Tests**: `pytest` (`uv run pytest`).
60
+ Config in `pyproject.toml` (`[tool.pytest.ini_options]`);
61
+ warnings are errors (`filterwarnings = ["error", ...]`),
62
+ so new code must not raise unexpected warnings.
63
+ Coverage is measured against `gwdatafind_server`.
64
+ - **Linting**: `ruff`, with `select = ["ALL"]` and an explicit ignore list in
65
+ `pyproject.toml`.
66
+ Run `uv run ruff check` before considering work done.
67
+ Per-directory ignores exist for `tests/`, `config/`, `docs/`.
68
+ - **Type checking**: [`ty`](https://github.com/astral-sh/ty) (not mypy) —
69
+ see `[tool.ty.src]` in `pyproject.toml`.
70
+ Note the codebase mostly ignores annotation lint rules (`ANN` is ruff-ignored)
71
+ but does use `from __future__ import annotations` and `TYPE_CHECKING` blocks.
72
+ - **Docs**: MkDocs + `mkdocs-material`-based theme, built via a shared CI component.
73
+ Markdown is linted with `markdownlint` (`.markdownlint.yml`); config docs pull live
74
+ examples from `config/` via the `include-markdown` plugin, so keep those files valid.
75
+ - **Commit style**: Conventional-commit-like scoped messages are the norm, e.g.
76
+ `feat(config): add support for source update jitter`,
77
+ `fix(pelican): don't skip recursion using cache`,
78
+ `build(docker-compose): add volume for pelican caches`.
79
+ Follow this pattern.
80
+ - **Workflow**: Development happens on GitLab (`git.ligo.org/computing/gwdatafind/server`)
81
+ via merge requests into `main`;
82
+ releases are tagged and generate `CHANGELOG.md` entries per version.
83
+ Licensed GPL-3.0-or-later — keep the copyright/license header convention
84
+ (`# Copyright (c) 2019-2025 Cardiff University` /
85
+ `# Licensed under GPLv3+ - see LICENSE`) on new source files.
86
+
87
+ ## Sensitive areas
88
+
89
+ - **Auth/credential code** (`gwdatafind_server/auth/`, `gwdatafind_server/credential/`)
90
+ gates access to (sometimes proprietary/embargoed) LIGO/Virgo/KAGRA data —
91
+ change carefully and preserve existing test coverage.
92
+ - **URL transform logic** (`gwdatafind_server/urls.py`) is order-sensitive and affects
93
+ which URLs get served for which scheme; see the "Transform behavior" warning in
94
+ `docs/configuration.md` before changing matching/precedence semantics.
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.0 - 2025-07-06 { #2.3.0 }
4
+
5
+ - Load source cache upfront during app startup, use concurrent gunicorn workers
6
+ ([!133](https://git.ligo.org/computing/gwdatafind/server/-/merge_requests/133))
7
+ - Improve Pelican sqlite db transaction locking
8
+ ([!134](https://git.ligo.org/computing/gwdatafind/server/-/merge_requests/134))
9
+
10
+ [Full details](https://git.ligo.org/computing/gwdatafind/server/-/releases/2.3.0)
11
+
3
12
  ## 2.2.0 - 2025-04-09 { #2.2.0 }
4
13
 
5
14
  - Fix directory recursion with Pelican memory cache
@@ -0,0 +1,94 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for AI coding agents (and humans) working in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ GWDataFind Server is a [Flask](https://flask.palletsprojects.com/) application that
8
+ implements the HTTP API used to discover URLs for gravitational-wave observatory data
9
+ files.
10
+ It is developed by the LIGO Scientific Collaboration / IGWN and used in production
11
+ to serve data discovery requests for LIGO/Virgo/KAGRA data.
12
+
13
+ The accompanying Python client library (`gwdatafind`, not in this repo) is what most
14
+ end users interact with; this repo is the server side of that API.
15
+
16
+ Canonical docs live in `docs/` (built with MkDocs, published at
17
+ <https://computing.docs.ligo.org/gwdatafind/server/>).
18
+ Read `docs/overview.md` first for architecture,
19
+ and `docs/configuration.md` for the full config schema —
20
+ both are kept up to date and should be treated as source of truth over any summary here.
21
+
22
+ ## Architecture (brief)
23
+
24
+ - The app (`gwdatafind_server/app.py`, `GWDataFindApp`) runs one or more **sources**
25
+ (`gwdatafind_server/source/`: `diskcache`, `pelican`) on independent async schedules
26
+ (via `flask-apscheduler`). Each source discovers fully-qualified URLs
27
+ (`file://`, `pelican://`, etc.) plus metadata (observatory, dataset, format, GPS
28
+ segment) and feeds it into an in-memory `Cache` (`gwdatafind_server/cache.py`).
29
+ - **URL transforms** (`gwdatafind_server/urls.py`, configured under `[url.transform]`)
30
+ rewrite URLs between schemes (e.g. `pelican://` → `https://`) at query time.
31
+ - **Auth** (`gwdatafind_server/auth/`) supports SciToken bearer-token authorization and
32
+ per-IP/subnet overrides.
33
+ - **Credentials** (`gwdatafind_server/credential/`) are refreshed on a schedule and
34
+ attached to sources that need them (e.g. SciTokens obtained via Kerberos keytab for
35
+ Pelican access).
36
+ - **API** (`gwdatafind_server/api/`): versioned HTTP endpoints (`ldr` legacy, `v1`
37
+ current) built on the shared cache; see `docs/api/`.
38
+ - Configuration is TOML (`gwdatafind_server/config.py`), one config file drives the
39
+ whole app. `python3 -m gwdatafind_server.config <file>` parses and dumps it as JSON
40
+ for validation.
41
+
42
+ ## Repo layout
43
+
44
+ - `gwdatafind_server/` — the application package (source of truth for behavior).
45
+ - `tests/` — pytest suite, one `test_*.py` per module roughly mirroring the package.
46
+ - `docs/` — MkDocs site (nav defined in `mkdocs.yml`); includes API reference and
47
+ deployment examples (`docs/deploy/examples/`).
48
+ - `examples/` — runnable docker-compose deployment examples (diskcache, auth/SciToken).
49
+ - `config/` — example/reference config files (TOML, gunicorn, systemd unit) referenced
50
+ by the docs via `include-markdown`; treat as documentation fixtures, not scratch space.
51
+ - `Dockerfile`, `docker-compose.yml` — container build/quick-start.
52
+ - `.gitlab-ci.yml` — CI pipeline (GitLab, not GitHub). Runs lint/type-check/tests across
53
+ Python 3.11–3.14, builds the Docker image, and builds the MkDocs site.
54
+
55
+ ## Development practices
56
+
57
+ - **Dependency management**: [`uv`](https://docs.astral.sh/uv/).
58
+ Use `uv run …` / `uv sync --group dev` rather than raw `pip`.
59
+ - **Tests**: `pytest` (`uv run pytest`).
60
+ Config in `pyproject.toml` (`[tool.pytest.ini_options]`);
61
+ warnings are errors (`filterwarnings = ["error", ...]`),
62
+ so new code must not raise unexpected warnings.
63
+ Coverage is measured against `gwdatafind_server`.
64
+ - **Linting**: `ruff`, with `select = ["ALL"]` and an explicit ignore list in
65
+ `pyproject.toml`.
66
+ Run `uv run ruff check` before considering work done.
67
+ Per-directory ignores exist for `tests/`, `config/`, `docs/`.
68
+ - **Type checking**: [`ty`](https://github.com/astral-sh/ty) (not mypy) —
69
+ see `[tool.ty.src]` in `pyproject.toml`.
70
+ Note the codebase mostly ignores annotation lint rules (`ANN` is ruff-ignored)
71
+ but does use `from __future__ import annotations` and `TYPE_CHECKING` blocks.
72
+ - **Docs**: MkDocs + `mkdocs-material`-based theme, built via a shared CI component.
73
+ Markdown is linted with `markdownlint` (`.markdownlint.yml`); config docs pull live
74
+ examples from `config/` via the `include-markdown` plugin, so keep those files valid.
75
+ - **Commit style**: Conventional-commit-like scoped messages are the norm, e.g.
76
+ `feat(config): add support for source update jitter`,
77
+ `fix(pelican): don't skip recursion using cache`,
78
+ `build(docker-compose): add volume for pelican caches`.
79
+ Follow this pattern.
80
+ - **Workflow**: Development happens on GitLab (`git.ligo.org/computing/gwdatafind/server`)
81
+ via merge requests into `main`;
82
+ releases are tagged and generate `CHANGELOG.md` entries per version.
83
+ Licensed GPL-3.0-or-later — keep the copyright/license header convention
84
+ (`# Copyright (c) 2019-2025 Cardiff University` /
85
+ `# Licensed under GPLv3+ - see LICENSE`) on new source files.
86
+
87
+ ## Sensitive areas
88
+
89
+ - **Auth/credential code** (`gwdatafind_server/auth/`, `gwdatafind_server/credential/`)
90
+ gates access to (sometimes proprietary/embargoed) LIGO/Virgo/KAGRA data —
91
+ change carefully and preserve existing test coverage.
92
+ - **URL transform logic** (`gwdatafind_server/urls.py`) is order-sensitive and affects
93
+ which URLs get served for which scheme; see the "Transform behavior" warning in
94
+ `docs/configuration.md` before changing matching/precedence semantics.
@@ -2,18 +2,14 @@
2
2
 
3
3
  FROM ghcr.io/astral-sh/uv:python3.14-bookworm AS build
4
4
 
5
- ARG SETUPTOOLS_SCM_PRETEND_VERSION
6
-
7
5
  WORKDIR /code
8
6
 
9
- COPY .git .git
10
7
  COPY . ./
11
8
 
12
9
  # Generate a wheel
13
- RUN if [ -n "$SETUPTOOLS_SCM_PRETEND_VERSION" ]; then \
14
- export SETUPTOOLS_SCM_PRETEND_VERSION="$SETUPTOOLS_SCM_PRETEND_VERSION"; \
15
- fi \
16
- && uv build --wheel
10
+ RUN git config --global safe.directory $(pwd) \
11
+ && rm -rf docker_dist/*.whl \
12
+ && uv build --wheel --out-dir docker_dist/
17
13
 
18
14
  # -- install
19
15
 
@@ -28,10 +24,14 @@ RUN uv venv ${VIRTUAL_ENV}
28
24
 
29
25
  # Install the requirements
30
26
  COPY pyproject.toml .
31
- RUN uv pip install -r pyproject.toml
27
+ RUN uv pip install \
28
+ -r pyproject.toml \
29
+ --extra credentials \
30
+ --extra gunicorn \
31
+ --extra pelican
32
32
 
33
33
  # Install the app
34
- COPY --from=build /code/dist/*.whl /code
34
+ COPY --from=build /code/docker_dist/*.whl /code
35
35
  RUN WHEEL=$(ls /code/*.whl) \
36
36
  && uv pip install $WHEEL[credentials,gunicorn,pelican]
37
37
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gwdatafind-server
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: The GWDataFind data discover server application
5
5
  Author-email: Duncan Macleod <duncan.macleod@ligo.org>, Duncan Meacher <duncan.meacher@ligo.org>
6
6
  License: GPL-3.0-or-later
@@ -11,20 +11,17 @@ bind = "0.0.0.0:8080"
11
11
  backlog = 2048
12
12
 
13
13
  # Worker processes
14
- workers = 1
14
+ workers = 2
15
15
  worker_class = "gthread"
16
16
  worker_connections = 1000
17
- threads = 8
17
+ threads = 4
18
18
  timeout = 30
19
19
  keepalive = 2
20
20
 
21
- # Restart workers after this many requests, with up to this much jitter
22
- max_requests = 1000
23
- max_requests_jitter = 100
24
-
25
- # Worker recycling
26
- max_worker_memory = 134217728 # 128MB in bytes
27
- worker_tmp_dir = "/dev/shm"
21
+ # Restart workers after this many requests, with up to this much jitter.
22
+ # Keep this high enough to avoid frequent cache rebuilds.
23
+ max_requests = 10000
24
+ max_requests_jitter = 1000
28
25
 
29
26
  # Security
30
27
  limit_request_line = 4094
@@ -104,6 +101,7 @@ if os.environ.get("GWDATAFIND_SERVER_DEBUG"):
104
101
  loglevel = "debug"
105
102
  reload = True
106
103
  workers = 1
104
+ threads = 4
107
105
 
108
106
  if gwdfworkers := os.environ.get("GWDATAFIND_WORKERS"):
109
107
  workers = int(gwdfworkers)
@@ -36,6 +36,7 @@ type = "pelican"
36
36
  federation_url = "osg-htc.org"
37
37
  path = "/gwdata"
38
38
  interval = 10800
39
+ jitter = 600
39
40
  cachefile = "/var/lib/gwdatafind/pelican-gwosc.db"
40
41
 
41
42
  # -- credentials ------------
@@ -10,7 +10,7 @@
10
10
  services:
11
11
  gwdatafind:
12
12
  container_name: gwdatafind-server
13
- image: gwdatafind-server:latest
13
+ image: ${GWDATAFIND_SERVER_IMAGE-containers.ligo.org/computing/gwdatafind/server:latest}
14
14
  ports:
15
15
  - "127.0.0.1:8080:8080" # Bind only to localhost
16
16
  restart: unless-stopped
@@ -26,13 +26,16 @@ services:
26
26
  # Volumes
27
27
  volumes:
28
28
  - type: bind
29
- source: config/gwdatafind-server.toml
29
+ source: ${GWDATAFIND_SERVER_CONFIG-config/gwdatafind-server.toml}
30
30
  target: /etc/gwdatafind/gwdatafind-server.toml
31
31
  read_only: true
32
32
  - type: tmpfs
33
33
  target: /tmp
34
34
  tmpfs:
35
35
  size: 100m
36
+ - type: volume
37
+ source: gwdatafind-data
38
+ target: /var/lib/gwdatafind
36
39
 
37
40
  # Environment
38
41
  environment:
@@ -41,11 +44,11 @@ services:
41
44
 
42
45
  # Health check
43
46
  healthcheck:
44
- test: ["CMD", "curl", "-f", "http://localhost:8080/api/version", "--max-time", "5"]
47
+ test: ["CMD-SHELL", "pgrep -f 'gunicorn.*gwdatafind_server:create_app\\(\\)' >/dev/null"]
45
48
  interval: 30s
46
- timeout: 10s
49
+ timeout: 5s
47
50
  retries: 3
48
- start_period: 40s
51
+ start_period: 30s
49
52
 
50
53
  # Logging
51
54
  logging:
@@ -58,3 +61,7 @@ services:
58
61
  labels:
59
62
  - "com.example.service=gwdatafind-server"
60
63
  - "com.example.version=latest"
64
+
65
+ volumes:
66
+ gwdatafind-data:
67
+ driver: local
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.0 - 2025-07-06 { #2.3.0 }
4
+
5
+ - Load source cache upfront during app startup, use concurrent gunicorn workers
6
+ ([!133](https://git.ligo.org/computing/gwdatafind/server/-/merge_requests/133))
7
+ - Improve Pelican sqlite db transaction locking
8
+ ([!134](https://git.ligo.org/computing/gwdatafind/server/-/merge_requests/134))
9
+
10
+ [Full details](https://git.ligo.org/computing/gwdatafind/server/-/releases/2.3.0)
11
+
3
12
  ## 2.2.0 - 2025-04-09 { #2.2.0 }
4
13
 
5
14
  - Fix directory recursion with Pelican memory cache
@@ -257,6 +257,7 @@ All source types accept the following options:
257
257
  | `name` | [string](https://toml.io/en/v1.0.0#string) | [optional] A unique name for this source (defaults to "{type}_source_{index}") |
258
258
  | `interval` | [integer](https://toml.io/en/v1.0.0#integer) | [optional] How often (in seconds) to query for new data |
259
259
  | `initial_delay` | [integer](https://toml.io/en/v1.0.0#integer) | [optional] Delay (in seconds) before the first data collection |
260
+ | `jitter` | [integer](https://toml.io/en/v1.0.0#integer) | [optional] Maximum random jitter (in seconds) to add to the interval between data collections (default: 0) |
260
261
  | `credential` | [string](https://toml.io/en/v1.0.0#string) | [optional] Name of the credential to use for authentication |
261
262
  | `split_sites` | [boolean](https://toml.io/en/v1.0.0#boolean) | [optional] If `true`, multi-site files will be indexed for individual sites, as well as the combination (default: `false`) |
262
263
 
@@ -270,7 +271,6 @@ other keys:
270
271
  | `host` | [string](https://toml.io/en/v1.0.0#string) | The address of the diskcache instance |
271
272
  | `port` | [integer](https://toml.io/en/v1.0.0#integer) | The port to talk to on the diskcache host |
272
273
  | `path` | [string](https://toml.io/en/v1.0.0#string) | Alternatively, the path of a Diskcache ASCII output file to read |
273
- | `interval` | [integer](https://toml.io/en/v1.0.0#integer) | How often (in seconds) to request information from the diskcache host, or to reread the diskcache file |
274
274
 
275
275
  !!! example "Diskcache input configuration"
276
276
 
@@ -306,7 +306,6 @@ other keys:
306
306
  | `token` | [boolean](https://toml.io/en/v1.0.0#boolean) | If `true`, a token will be included in requests to the Pelican host (default `false`) |
307
307
  | `credential` | [string](https://toml.io/en/v1.0.0#string) | [optional] Name of the credential to use for authentication (alternative to `token`) |
308
308
  | `cachefile` | [string](https://toml.io/en/v1.0.0#string) | [optional] Path to SQLite cache file for persistent storage of directory listings (improves startup performance) |
309
- | `interval` | [integer](https://toml.io/en/v1.0.0#integer) | How often (in seconds) to query for new data from the Pelican host |
310
309
 
311
310
  !!! note "Pelican directory listings are cached"
312
311
 
@@ -353,7 +352,6 @@ other keys:
353
352
  | `principal` | [string](https://toml.io/en/v1.0.0#string) | [optional] The Kerberos principal to authenticate as |
354
353
  | `audience` | [string](https://toml.io/en/v1.0.0#string) | The intended audience for the SciToken |
355
354
  | `scopes` | [array](https://toml.io/en/v1.0.0#array) of [string](https://toml.io/en/v1.0.0#string) | [optional] List of scopes to request for the SciToken |
356
- | `interval` | [integer](https://toml.io/en/v1.0.0#integer) | [optional] How often (in seconds) to refresh the credential (default: 3600 = 1 hour) |
357
355
 
358
356
  !!! example "SciToken credential configuration"
359
357
 
@@ -10,7 +10,7 @@ services:
10
10
  # Rebuild image with Kerberos keytab embedded securely
11
11
  context: .
12
12
  dockerfile_inline: |
13
- FROM containers.ligo.org/computing/gwdatafind/server:latest
13
+ FROM ${GWDATAFIND_SERVER_IMAGE-containers.ligo.org/computing/gwdatafind/server:latest}
14
14
  COPY --chown=gwdatafind:gwdatafind --chmod=400 pelican.keytab /pelican.keytab
15
15
  restart: unless-stopped
16
16
  ports:
@@ -26,9 +26,12 @@ services:
26
26
  # Volumes
27
27
  volumes:
28
28
  - type: bind
29
- source: ./gwdatafind-server.toml
29
+ source: ${GWDATAFIND_SERVER_CONFIG-./gwdatafind-server.toml}
30
30
  target: /etc/gwdatafind/gwdatafind-server.toml
31
31
  read_only: true
32
+ - type: volume
33
+ source: gwdatafind-data
34
+ target: /var/lib/gwdatafind
32
35
  read_only: true # Read-only root filesystem
33
36
  tmpfs:
34
37
  - /tmp:noexec,nosuid,size=100m
@@ -64,3 +67,7 @@ services:
64
67
  options:
65
68
  max-size: "10m"
66
69
  max-file: "3"
70
+
71
+ volumes:
72
+ gwdatafind-data:
73
+ driver: local
@@ -38,6 +38,7 @@ type = "pelican"
38
38
  federation_url = "osg-htc.org"
39
39
  path = "/gwdata"
40
40
  interval = 10800
41
+ jitter = 600
41
42
  cachefile = "/var/lib/gwdatafind/pelican-gwosc.db"
42
43
 
43
44
  # Private LIGO data in Pelican
@@ -47,6 +48,7 @@ type = "pelican"
47
48
  federation_url = "osg-htc.org"
48
49
  path = "/igwn/ligo"
49
50
  interval = 600
51
+ jitter = 60
50
52
  credential = "pelican_token"
51
53
  cachefile = "/var/lib/gwdatafind/pelican-ligo.db"
52
54
 
@@ -57,6 +59,7 @@ type = "pelican"
57
59
  federation_url = "osg-htc.org"
58
60
  path = "/igwn/virgo"
59
61
  interval = 600
62
+ jitter = 60
60
63
  credential = "pelican_token"
61
64
  cachefile = "/var/lib/gwdatafind/pelican-virgo.db"
62
65
 
@@ -67,6 +70,7 @@ type = "pelican"
67
70
  federation_url = "osg-htc.org"
68
71
  path = "/igwn/kagra"
69
72
  interval = 600
73
+ jitter = 60
70
74
  credential = "pelican_token"
71
75
  cachefile = "/var/lib/gwdatafind/pelican-kagra.db"
72
76
 
@@ -30,6 +30,7 @@ services:
30
30
  gwdatafind:
31
31
  container_name: gwdatafind-server
32
32
  image: containers.ligo.org/computing/gwdatafind/server:latest
33
+ image: ${GWDATAFIND_SERVER_IMAGE-containers.ligo.org/computing/gwdatafind/server:latest}
33
34
  ports:
34
35
  - "8080:8080"
35
36
  restart: unless-stopped
@@ -45,9 +46,12 @@ services:
45
46
  # Volumes
46
47
  volumes:
47
48
  - type: bind
48
- source: gwdatafind-server.toml
49
+ source: ${GWDATAFIND_SERVER_CONFIG-./gwdatafind-server.toml}
49
50
  target: /etc/gwdatafind/gwdatafind-server.toml
50
51
  read_only: true
52
+ - type: volume
53
+ source: gwdatafind-data
54
+ target: /var/lib/gwdatafind
51
55
  tmpfs:
52
56
  - /tmp:noexec,nosuid,size=100m
53
57
  - /var/tmp:noexec,nosuid,size=10m
@@ -69,3 +73,8 @@ services:
69
73
  networks:
70
74
  gwdatafind_internal:
71
75
  driver: bridge
76
+
77
+ # Volumes
78
+ volumes:
79
+ gwdatafind-data:
80
+ driver: local
@@ -37,6 +37,7 @@ type = "pelican"
37
37
  federation_url = "osg-htc.org"
38
38
  path = "/gwdata"
39
39
  interval = 10800
40
+ jitter = 600
40
41
  cachefile = "/var/lib/gwdatafind/pelican-gwosc.db"
41
42
 
42
43
  # Local diskcache for file system scanning
@@ -1,13 +1,11 @@
1
1
  # Copyright (c) 2019-2025 Cardiff University
2
2
  # Licensed under GPLv3+ - see LICENSE
3
3
 
4
- """The GWDataFind Service provides a metadata-based
5
- data discover mechanism for data files.
4
+ """A metadata-based discovery mechanism for GW data files."""
6
5
 
7
- Users provide a few key details as part of an HTTP(S)
8
- query and receive URLs that indicate the locations
9
- of the relevant data.
10
- """
6
+ from __future__ import annotations
7
+
8
+ from typing import TYPE_CHECKING
11
9
 
12
10
  try:
13
11
  from ._version import version as __version__
@@ -17,9 +15,17 @@ except ModuleNotFoundError: # development mode
17
15
  from .app import GWDataFindApp
18
16
  from .config import get_config_path
19
17
 
18
+ if TYPE_CHECKING:
19
+ from pathlib import Path
20
+
20
21
 
21
- def create_app(config=None):
22
+ def create_app(
23
+ name: str = __name__,
24
+ config: Path | None = None,
25
+ app_class: type[GWDataFindApp] = GWDataFindApp,
26
+ **app_kwargs,
27
+ ):
22
28
  """Create an instance of the application."""
23
29
  if config is None:
24
30
  config = get_config_path()
25
- return GWDataFindApp(__name__, config)
31
+ return app_class(name, config, **app_kwargs)
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.2.0'
22
- __version_tuple__ = version_tuple = (2, 2, 0)
21
+ __version__ = version = '2.3.0'
22
+ __version_tuple__ = version_tuple = (2, 3, 0)
23
23
 
24
- __commit_id__ = commit_id = 'gc287e7f56'
24
+ __commit_id__ = commit_id = 'gffb5a6c3c'
@@ -63,13 +63,13 @@ class GWDataFindApp(Flask):
63
63
  """Initialize the GWDataFind server application."""
64
64
  super().__init__(import_name, *args, **kwargs)
65
65
 
66
- # load our configuration
66
+ # Load our configuration
67
67
  self._init_config(config)
68
68
 
69
- # finalise logging config
69
+ # Finalise logging config
70
70
  self._init_logger()
71
71
 
72
- # initialise auth manager
72
+ # Initialise auth manager
73
73
  self.auth = self._init_auth()
74
74
 
75
75
  # Initialize cache system
@@ -82,15 +82,23 @@ class GWDataFindApp(Flask):
82
82
  self._scheduler = APScheduler()
83
83
  self._init_scheduler()
84
84
 
85
- # start credential refresh polling
85
+ # Start credential refresh polling
86
+ # NOTE: includes initial credential get
86
87
  self._init_credentials()
87
88
 
88
- # start data source polling
89
+ # Start data source polling
90
+ # NOTE: this performs an initial cache update for each source
91
+ # before the scheduler starts accepting requests,
92
+ # so it may take some time to start up if there are many
93
+ # sources or they have long update times.
89
94
  self._init_sources()
90
95
 
91
- # register all API endpoints
96
+ # Register all API endpoints
92
97
  self._register_blueprints()
93
98
 
99
+ # Start the scheduler only after the initial cache warmup completes.
100
+ self._scheduler.start()
101
+
94
102
  # announce the app is ready
95
103
  self.logger.debug(
96
104
  "Initialised GWDataFind server with config: %s",
@@ -166,11 +174,8 @@ class GWDataFindApp(Flask):
166
174
  # Initialize scheduler with app
167
175
  self._scheduler.init_app(self)
168
176
 
169
- # Start scheduler
170
- self._scheduler.start()
171
-
172
177
  def _init_sources(self):
173
- """Initialize data sources with scheduled updates."""
178
+ """Initialize data sources with scheduled updates and warm caches."""
174
179
  # get list of sources
175
180
  sources: list[SourceConfig] = get_config_value(
176
181
  self.config,
@@ -182,14 +187,23 @@ class GWDataFindApp(Flask):
182
187
  if not sources:
183
188
  self.logger.warning("No sources configured")
184
189
 
190
+ initial_sources: list[Source] = []
185
191
  for i, source in enumerate(sources):
186
192
  source.setdefault("name", f"{source['type']}_source_{i}")
187
- self._init_source(source)
193
+ initial_sources.append(self._init_source(source))
194
+
195
+ # Populate the cache before the scheduler starts accepting requests
196
+ for source in initial_sources:
197
+ self.logger.debug(
198
+ "Running initial cache update for source: %s",
199
+ source.name,
200
+ )
201
+ self._update_source_job(source)
188
202
 
189
203
  def _init_source(
190
204
  self,
191
205
  config: SourceConfig,
192
- ):
206
+ ) -> Source:
193
207
  """Add a scheduled job to collect data from a source and update the cache."""
194
208
  # Look up the specific credential for this source
195
209
  credential = None
@@ -223,18 +237,10 @@ class GWDataFindApp(Flask):
223
237
  args=[source],
224
238
  trigger="interval",
225
239
  seconds=config["interval"],
240
+ jitter=int(config.get("jitter", 0)),
226
241
  name=f"Update {source.name} cache",
227
242
  )
228
-
229
- # Do immediate initial update
230
- self.logger.debug("Scheduling initial update for source: %s", source.name)
231
- self._scheduler.add_job(
232
- id=f"initial_{source.name}",
233
- func=self._update_source_job,
234
- args=[source],
235
- trigger="date",
236
- name=f"Initial update for {source.name}",
237
- )
243
+ return source
238
244
 
239
245
  def _update_source_job(
240
246
  self,