Skip to content

Release 0.13.0 - #281

Open
khoroshevskyi wants to merge 34 commits into
masterfrom
dev
Open

Release 0.13.0#281
khoroshevskyi wants to merge 34 commits into
masterfrom
dev

Conversation

@khoroshevskyi

Copy link
Copy Markdown
Member

Changes:

  • Refactored statistics caching: moved from a module-level cache in dependencies.py to using app.state.detailed_stats (a TTLCache), ensuring thread safety and correct cache lifecycle management. Added a fallback for empty datasets to avoid StatisticsError and return a well-formed, zeroed FileStats object.
  • Changed several API endpoints from async def to def to ensure blocking database queries run in a threadpool, preventing event loop stalls.
  • Added new endpoints /exports and /files to provide indexes of published bulk metadata exports and standalone analysis files, respectively. These endpoints rewrite file_path fields to the absolute HTTPS CDN URL using the new EXPORTS_URL_BASE constant.
  • Improved error handling for markdown rendering endpoints: now returns a 404 if a requested markdown file is missing, rather than a 500 error.
  • Added a Testing section to the README.md with instructions for running black-box and integration tests, as well as manual service control. Also clarified environment variable usage for ML model initialization in CI and production. (
  • Updated the configure function to respect the BEDHOST_INIT_ML environment variable, allowing deployments to skip expensive ML model loading when not needed.
  • Centralized logging configuration in main.py (now the application entry point), using logmuse for logger initialization and setting log levels for dependencies. Removed logger initialization from __init__.py to avoid duplicate configuration.
  • Improved usage tracking logic to handle cases where query parameters may be None, preventing potential crashes.
  • Ensured synchronous FastAPI endpoints are dispatched in a threadpool, preventing event loop blocking and type errors.

TODO:

  • Version of pepdbagent updated in __version__.py file
  • Changelog updated

nsheff and others added 30 commits April 20, 2026 18:45
New tests/api/ suite tests bedhost against any deployed instance via
--api_root flag. Covers service-info, stats, BED/BEDSET CRUD, search,
and OpenAPI docs endpoints. Removes old test_fastapi stubs and
test_compliance.py.
One-command, parallel-safe local stack (ephemeral Postgres + Qdrant) plus
a tests/integration/ tree that runs the 37 compliance tests via FastAPI
TestClient against the ephemeral DB. The existing black-box tests/api/
suite stays runnable against real deployments.

- tests/scripts/services.sh: start/stop/status for postgres:17 and
  qdrant/qdrant:latest containers. Unique names (PID-suffixed) and
  randomized ports for parallel safety. tmpfs mounts for speed. pg_isready
  and qdrant /readyz health-wait loops.
- tests/scripts/test-integration.sh: orchestrator. trap cleanup EXIT INT
  TERM, pre-computes ports so cleanup has them. Exports RUN_INTEGRATION_TESTS,
  TEST_DB_URL, TEST_QDRANT_URL, BEDHOST_INIT_ML=false, then runs pytest.
- tests/fixtures/bedbase_test_config.template.yaml: minimal bbconf YAML
  with {DB_PORT} / {QDRANT_PORT} placeholders.
- tests/integration/conftest.py: renders the template to tmp_path, sets
  BEDBASE_CONFIG, imports bedhost.main.app, wraps TestClient in a
  TestClientAdapter. Gates the whole directory with a
  pytest_collection_modifyitems skip when RUN_INTEGRATION_TESTS != "true".
- tests/client.py: RequestsClient and TestClientAdapter expose the same
  .get/.post surface so the 37 compliance tests run unchanged in both
  modes.
- tests/conftest.py: api_root fixture now returns a client object (not a
  URL string). In integration mode it delegates to integration_api_root;
  otherwise it builds a RequestsClient from --api_root.
- tests/api/test_compliance.py: mechanical rewrite of all 37 tests to use
  api_root.get("/v1/...") instead of requests.get(f"{api_root}/v1/...").
- tests/api/conftest.py: example_bed_id / example_bedset_id fixtures use
  the new adapter.
- README.md: "Testing" section documenting both modes.

Plain `pytest` still collects all 38 tests and skips cleanly when
RUN_INTEGRATION_TESTS is unset and no server is reachable.

No version pins added. No backwards compatibility (api_root return type
changed).
…egration mode

configure() in bedhost/helpers.py now reads BEDHOST_INIT_ML from the env
and passes init_ml= through to BedBaseAgent. Default remains True so
production behavior is unchanged. With BEDHOST_INIT_ML=false (set by
test-integration.sh), bbconf skips loading the dense/sparse encoders,
UMAP, and region2vec - which matters for the integration test stack
because the installed fastembed/torch versions are incompatible with
bbconf's current ML init path.

Also expand test-integration.sh to run both tests/integration/ and
tests/api/, so the 37 compliance tests execute against the in-process
TestClient (not just the smoke test).
The yacman/bbconf stack registers SIGINT/SIGTERM handlers in
YacAttMap.__enter__, which raises ValueError under FastAPI TestClient
since lifespan runs on an AnyIO worker thread. Monkey-patch
signal.signal to swallow that specific case so bbconf can finish
loading its config during test startup.

Also tighten example_{bed,bedset}_id fixture timeouts from 10s to 3s
to fail fast when the endpoint errors instead of blocking a test run.
- Move FastAPI's OpenAPI schema from /openapi.json to /v1/openapi.json
  so it lives under the same /v1 prefix as the rest of the API. The
  /v1 regression test and the compliance test now agree on the path.

- render_markdown() now raises HTTPException(404) when the markdown
  source isn't on disk instead of propagating FileNotFoundError as a
  500. docs/changelog.md was removed from the repo in 2024 but the
  /v1/docs/changelog route remained registered; this makes it
  behave gracefully when the file is absent without changing output
  when the file is present.
When bbconf is initialized with init_ml=False (BEDHOST_INIT_ML=false
for CI / smoke deploys), BedBaseConfig never sets dense_encoder at
all. Hybrid search deep in bbconf then raises AttributeError and
surfaces as a 500. Return 503 up-front when dense_encoder is absent,
matching the pattern already used by /v1/bed/analyze-genome for the
reference validator.
- count_requests(): when the wrapped endpoint has query=None (as
  /v1/bedset/list does when no search term is supplied), the
  decorator called None.strip() and crashed with AttributeError.
  Skip usage tracking in that case. This is the root cause of the
  500 seen on prod for GET /v1/bedset/list with no query param.

- fetch_detailed_stats(): bbconf.bbagent._bin_number_of_regions
  calls statistics.mean on a list of per-bed region counts, which
  raises StatisticsError on an empty database. Catch it and return
  a FileStats with empty bins/zeroed means so the endpoint stays a
  200 on a freshly-provisioned instance. Same failure was observed
  on prod.
- TestBedExampleEndpoint and TestBedsetExampleEndpoint now skip when
  the corresponding example_{bed,bedset}_id fixture is empty (no
  seeded data), matching the pattern already used by the
  metadata tests. Structural schema is still exercised against
  seeded instances.

- TestBedSearchEndpoints.test_text_search_* accept 503 (from the
  new ML-disabled guard) in addition to 200, and skip the
  shape-of-results check when 503 is returned.

- test_openapi_json_available now points at /v1/openapi.json,
  matching the new mount path.

- test_v1_changelog_returns_200 renamed to ..._returns_200_or_404
  since docs/changelog.md may or may not be present on disk.
SQLAlchemy queries plus numpy work, which would otherwise stall the event
loop (and every other request) for the full duration. FastAPI dispatches
plain def handlers to a thread pool instead.
Move logging setup out of __init__.py
Add bulk metadata exports: listing endpoint, DRS objects, downloads page
Copilot AI lite review requested due to automatic review settings August 18, 2026 19:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This release PR updates both the FastAPI service and the UI to improve runtime safety (threadpool dispatch for blocking routes, safer detailed-stats caching), expand download/index functionality (new /exports + /files indexes and corresponding UI pages), and overhaul testing with a new compliance+integration test harness.

Changes:

  • Added new API indexes for bulk exports and analysis files, plus new UI pages/routes to browse and download them.
  • Refactored detailed-stats caching to use app.state lifecycle-scoped cache with an empty-dataset fallback.
  • Replaced/modernized tests with a dual-mode compliance suite (black-box and ephemeral integration stack scripts/fixtures).

Reviewed changes

Copilot reviewed 44 out of 47 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
ui/src/queries/useStats.ts Extends React Query caching settings for stats queries.
ui/src/queries/useExports.ts Adds a typed React Query hook for the new exports index endpoint.
ui/src/queries/useAnalysisFiles.ts Adds a typed React Query hook for the new analysis-files index endpoint (with filters).
ui/src/pages/downloads.tsx Adds a “Bulk exports” downloads UI, grouped by snapshot time, with copyable checksum and DuckDB snippet.
ui/src/pages/bed-splash.tsx Updates bedset card/table mapping to use bedfile_count.
ui/src/pages/analysis-files.tsx Adds an “Analysis files” UI to browse standalone analysis artifacts.
ui/src/main.tsx Registers new frontend routes for /downloads and /files.
ui/src/components/search/text2bedset/text2bedset.tsx Updates pagination wiring to the new hasMore pagination model.
ui/src/components/search/text2bedset/t2bs-search-results-table.tsx Displays bedfile_count instead of deriving count from bed_ids.
ui/src/components/search/text2bedset/t2bs-search-results-cards.tsx Displays bedfile_count in the card view.
ui/src/components/search/text2bed/text2bed.tsx Updates pagination wiring to the new hasMore pagination model.
ui/src/components/search/search-bar.tsx Adds a max-width style for the genome select to prevent layout overflow.
ui/src/components/search/pagination-bar.tsx Refactors pagination UI to operate without a true total, using hasMore.
ui/src/components/search/bed2bed/bed2bed.tsx Updates pagination wiring to the new hasMore pagination model.
ui/src/components/nav/nav-mobile.tsx Adds mobile nav links for the new Downloads and Analysis files pages.
ui/src/components/nav/nav-desktop.tsx Moves secondary links into a “More” dropdown and adds links to new pages.
ui/bedbase-types.d.ts Extends generated types to include bedfile_count on bedset models.
tests/test_fastapi/test_config.yaml Removes legacy fastapi test config.
tests/test_fastapi/test_api.py Removes legacy (skipped) fastapi test module.
tests/test_fastapi/README.md Removes legacy test setup notes superseded by new scripts/docs.
tests/test_compliance.py Removes legacy compliance tests superseded by new suite.
tests/scripts/test-integration.sh Adds an integration-test runner that boots ephemeral services and runs pytest.
tests/scripts/services.sh Adds helper script to start/stop ephemeral Postgres + Qdrant for tests.
tests/integration/test_integration_smoke.py Adds a smoke test validating the ephemeral stack boots and serves /v1/service-info.
tests/integration/conftest.py Adds integration fixtures, config templating, and directory-level skip gating.
tests/integration/init.py Marks integration test package.
tests/fixtures/bedbase_test_config.template.yaml Adds bbconf config template filled with ephemeral service ports.
tests/conftest.py Adds top-level fixture dispatch between black-box and integration modes + service availability skips.
tests/client.py Adds client adapters so compliance tests can run against requests or TestClient.
tests/api/test_compliance.py Introduces a comprehensive compliance test suite for core endpoints and regressions.
tests/api/conftest.py Adds fixtures to discover example IDs when available.
tests/api/init.py Marks API tests package.
requirements/requirements-all.txt Bumps bbconf/logmuse minimum versions to support new behavior.
README.md Adds Testing section documenting black-box and integration test workflows.
pytest.ini Adds pytest configuration and marker registration.
bedhost/templates/page.html Updates footer asset to load from the app’s /static mount.
bedhost/routers/objects_api.py Adds DRS object endpoints for exports and analysis files.
bedhost/routers/bedset_api.py Converts several endpoints to sync def for threadpool execution of blocking work.
bedhost/routers/bed_api.py Converts several endpoints to sync def and adds an explicit 503 when ML is disabled for text search.
bedhost/routers/base_api.py Adds /v1/exports and /v1/files index endpoints; wires detailed-stats via request-scoped cache.
bedhost/main.py Centralizes logging init, mounts /static, tweaks OpenAPI URL, and improves markdown missing-file handling.
bedhost/helpers.py Adds BEDHOST_INIT_ML handling and ensures decorated sync endpoints run in a threadpool; hardens usage tracking.
bedhost/dependencies.py Moves detailed-stats caching to app.state and adds empty-dataset fallback for StatisticsError.
bedhost/const.py Adds EXPORTS_URL_BASE constant for CDN URL rewriting.
bedhost/_version.py Bumps bedhost version constant (currently to 0.12.8).
bedhost/init.py Removes logger init side effects; leaves a standard library logger handle.
Suppressed comments (2)

bedhost/routers/base_api.py:186

  • os.path.join(EXPORTS_URL_BASE, row.file_path) is not safe for constructing HTTPS URLs (it can drop the base if row.file_path starts with /, and it's not URL-aware). Prefer explicit URL concatenation or urllib.parse.urljoin.

Same pattern also exists in bedhost/routers/base_api.py:145 and bedhost/routers/objects_api.py:292,328.

    result.results = [
        row.model_copy(
            update={"file_path": os.path.join(EXPORTS_URL_BASE, row.file_path)}
        )

bedhost/routers/objects_api.py:329

  • os.path.join(EXPORTS_URL_BASE, row.file_path) is not safe for constructing HTTPS URLs (it can drop the base if row.file_path starts with /, and it's not URL-aware). Prefer explicit URL concatenation or urllib.parse.urljoin when building AccessURL.

Same issue also appears in bedhost/routers/objects_api.py:292 and bedhost/routers/base_api.py:145,185.

        AccessMethod(
            type="https",
            access_id="https",
            access_url=AccessURL(url=os.path.join(EXPORTS_URL_BASE, row.file_path)),
        )

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +42 to +61
<Dropdown.Item href='/downloads'>
<i className='bi bi-download me-2' />
Downloads
</Dropdown.Item>
<Dropdown.Item href='/files'>
<i className='bi bi-file-earmark-binary me-2' />
Analysis files
</Dropdown.Item>
<Dropdown.Item href='https://github.com/databio/bedhost' target='_blank'>
<i className='bi bi-github me-2' />
GitHub
</Dropdown.Item>
<Dropdown.Item href='https://docs.bedbase.org/bedbase/' target='_blank'>
<i className='bi bi-file-earmark-text me-2' />
Docs
</Dropdown.Item>
<Dropdown.Item href={`${API_BASE}`} target='_blank'>
<i className='bi bi-hdd-stack me-2' />
API
</Dropdown.Item>
Comment on lines +22 to +37
# yacman (pulled in transitively by bbconf/bedhost) registers SIGINT/SIGTERM
# handlers in YacAttMap.__enter__, which raises ``ValueError: signal only
# works in main thread of the main interpreter`` under FastAPI TestClient
# (lifespan runs on an AnyIO worker thread). Swallow those so bbconf can
# finish loading its config. Tests don't need graceful signal handling.
_real_signal = _signal_mod.signal


def _signal_main_thread_only(signum, handler):
try:
return _real_signal(signum, handler)
except ValueError:
return handler


_signal_mod.signal = _signal_main_thread_only
Comment on lines +97 to +103
cd "$PROJECT_ROOT"

python3 -m pytest tests/integration/ tests/api/ "$@"
TEST_EXIT_CODE=$?

echo "----------------------------------------------"
exit $TEST_EXIT_CODE
Comment thread bedhost/dependencies.py
Comment on lines +60 to +68
if concise not in request.app.state.detailed_stats:
_LOGGER.info("Stats are not cached, fetching...")
try:
request.app.state.detailed_stats[concise] = bbagent.get_detailed_stats(
concise=concise
)
except StatisticsError:
return _empty_file_stats()
return request.app.state.detailed_stats[concise]
Comment on lines +142 to +146
result = bbagent.snapshot.list(limit=limit, offset=offset)
result.results = [
row.model_copy(
update={"file_path": os.path.join(EXPORTS_URL_BASE, row.file_path)}
)
Comment on lines +289 to +293
AccessMethod(
type="https",
access_id="https",
access_url=AccessURL(url=os.path.join(EXPORTS_URL_BASE, row.file_path)),
)
Comment thread tests/scripts/services.sh
Comment on lines +84 to +88
docker run -d \
--name "$QDRANT_CONTAINER" \
-p "${QDRANT_PORT}:6333" \
--tmpfs /qdrant/storage \
qdrant/qdrant:latest
Comment thread bedhost/_version.py
@@ -1 +1 @@
__version__ = "0.12.7"
__version__ = "0.12.8"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants