Bundled resources and test fixtures#
Non-Python data files fall into two categories in this repo, and they are
stored and loaded differently. Picking the wrong one is the bug behind
#158:
a data file the API needed at runtime lived outside the package and was reached
by walking up from __file__, so it vanished the moment qfa was installed
from a wheel.
Runtime resource |
Test-only fixture |
|
|---|---|---|
Lives in |
|
|
Loaded with |
|
|
Ships in the wheel |
Yes |
No |
Read by |
production code ( |
tests and |
Examples |
|
|
Runtime resources#
Anything production code reads while serving a request — or while building the
app at startup — belongs inside the qfa package as a package resource, and
must be addressed through importlib.resources:
import importlib.resources
resource = importlib.resources.files("qfa.resources").joinpath("model_prices.yaml")
data = resource.read_text(encoding="utf-8")
If you need a real filesystem path (to hand to a library that only takes
str/Path), wrap the read in importlib.resources.as_file and stay inside
the context manager:
with importlib.resources.as_file(resource) as path:
custom_prices = yaml.safe_load(path.read_text())
Never do this:
# WRONG — assumes a repo checkout; lands in site-packages after `pip install`.
root = Path(__file__).resolve().parents[3]
data = (root / "fixtures" / "coding_framework.json").read_text()
parents[...] arithmetic encodes the source layout, not the installed
layout. It works from a checkout and from an editable install, then silently
breaks in the container image — silently, because such loaders usually have an
if not path.is_file() fallback, so the failure surfaces as degraded output
rather than an error. qfa.api.schemas._assign_codes_request_examples did
exactly that: the OpenAPI schema quietly served a single placeholder
“no-framework” example instead of the real coding framework, so Swagger’s
“Try it out” was unusable in every deployed environment.
Current runtime resources, both in src/qfa/resources/:
model_prices.yaml— custom LiteLLM cost entries, registered byregister_custom_model_prices().coding_framework.json— the COVID-19 coding framework used to build thePOST /v1/assign-codesSwagger examples.
Packaging#
[tool.hatch.build] in pyproject.toml sets packages = ["src/qfa"], and
hatchling ships every tracked file under that tree — .py or not — so a
new file dropped into src/qfa/resources/ needs no package-data entry. Do
verify it after adding one, because a missing resource degrades quietly:
uv build --wheel
unzip -l dist/*.whl | grep qfa/resources
Both the wheel and the sdist are checked; fixtures/ appears in neither.
Test-only fixtures#
Benchmark corpora stay in the repo-root fixtures/ directory and are loaded by
path from the test that uses them:
FIXTURES = Path(__file__).resolve().parent.parent / "fixtures"
CORPUS_PATH = FIXTURES / "analyze_corpus.yaml"
This is fine — and preferred — for test data: tests always run from a checkout,
and analyze_corpus.yaml alone is 2.9 MB, which has no business in an image
shipped to production. The rule of thumb: if only tests and scripts/ read
it, keep it out of the package. The moment production code needs a fixture,
move it into src/qfa/resources/ and switch every reader —
tests and scripts included — to importlib.resources, so there is exactly one
copy of the file.
Where to go next#
Developer guide — environment setup, test tiers, coding conventions
Components — the composition root that loads
model_prices.yaml