Skip to content

Commit ed0a22e

Browse files
authored
docs: README in remaining component bricks (final sweep) (#925)
## Summary Tier 1 README sweep, **batch 4 of 4** — closes out the goal of "every directory that has at least a file or two has a README". Adds 26 short READMEs across the remaining component bricks and a few subdirectories. ## Coverage - **Auth + middleware:** api_key_auth, auth - **Data plumbing:** composer, datatypes, exceptions, logging, mongodb_connection, string_utils - **Schema + config:** lif_schema_config, openapi_schema_parser, openapi_to_graphql, schema_state_manager - **Source-adapter framework:** data_source_adapters + two adapter subdirs (lif_to_lif_adapter, example_data_source_rest_api_to_lif_adapter) - **Identity mapper trio:** identity_mapper_service, identity_mapper_storage, identity_mapper_storage_sql - **Service-specific bricks:** example_data_source_service, graphql_client, langchain_agent (+ prompts/), semantic_search_service, translator - **Tenant routing pure helpers:** tenant_routing - **Subdirs:** langchain_agent/prompts (5 prompt templates), mdr_client/resources (bundled OpenAPI snapshot) ## Wrapping up the sweep | Batch | PR | Scope | |---|---|---| | 1 | #922 | 11 bases | | 2 | #923 | 5 MDR components | | 3 | #924 | 7 query/orchestration components | | 4 | **this PR** | 26 remaining components + subdirs | After this merges, the only missing README under `bases/lif/` is `learner_data_export_api/` — which will land with cbeach's open PR #920. ## Test plan - [x] `uv run pre-commit run cspell --files <new READMEs>` passes - [ ] Spot-check the "Used by" lists after merge - [ ] Render check on GitHub: tables, cross-links between READMEs 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent 2b69caa commit ed0a22e

27 files changed

Lines changed: 562 additions & 0 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# `api_key_auth` — Component
2+
3+
Simple API-key authentication middleware for FastAPI. Validates `X-API-Key` headers against a configurable map of `key:client-name` pairs, and exposes the matched client name on `request.state.principal`.
4+
5+
Used by services that want bare API-key auth (no Cognito, no HS256 JWT) — the GraphQL API in particular. For the richer MDR-side middleware that handles three principal types, see [`mdr_auth`](../mdr_auth/).
6+
7+
## Public surface
8+
9+
```python
10+
from lif.api_key_auth import ApiKeyAuthMiddleware, ApiKeyConfig
11+
12+
config = ApiKeyConfig.from_environment(prefix="GRAPHQL_AUTH")
13+
if config.is_enabled:
14+
app.add_middleware(ApiKeyAuthMiddleware, config=config)
15+
```
16+
17+
The middleware no-ops when `<PREFIX>__API_KEYS` is unset, making local dev simple.
18+
19+
## Used by
20+
- `bases/lif/api_graphql` — the GraphQL service's only auth path

components/lif/auth/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# `auth` — Component
2+
3+
Lightweight HS256 JWT auth used by the Advisor and Example Data Source bases. Not the same as [`mdr_auth`](../mdr_auth/) — this one is older and simpler, with no Cognito or middleware-managed tenant routing. Demo-grade.
4+
5+
## Public surface
6+
7+
```python
8+
from lif.auth.core import (
9+
create_access_token, create_refresh_token, decode_jwt,
10+
verify_token, get_current_user,
11+
)
12+
```
13+
14+
`create_access_token` / `create_refresh_token` mint short-lived access tokens and longer-lived refresh tokens. `verify_token` is a sync validator suitable for use as a FastAPI dependency. `get_current_user` is the FastAPI `Depends(...)` callable that extracts the authenticated username from a bearer token.
15+
16+
## Used by
17+
- `bases/lif/advisor_restapi` — login + per-endpoint user resolution
18+
- `bases/lif/example_data_source_rest_api``verify_token` behind `x-key` header auth
19+
20+
New services should default to `mdr_auth` instead unless they specifically don't want Cognito support.

components/lif/composer/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# `composer` — Component
2+
3+
Merges LIF fragments back into a `LIFRecord`. Used by the cache to assemble the final record returned to callers from many small fragment writes (one per data source, one per orchestration job).
4+
5+
## Public surface
6+
7+
```python
8+
from lif.composer import compose_with_single_fragment, compose_with_fragment_list
9+
```
10+
11+
| Function | Purpose |
12+
|---|---|
13+
| `compose_with_single_fragment` | Apply one `LIFFragment` to a base record |
14+
| `compose_with_fragment_list` | Apply many fragments at once; order matters when they overlap |
15+
16+
Composition is path-driven: a fragment with `fragment_path = "person.Name"` is merged at that location. Path syntax follows the PascalCase/camelCase rules documented in [`docs/specs/data-model-rules.md`](../../../docs/specs/data-model-rules.md).
17+
18+
## Used by
19+
- `components/lif/query_cache_service` — fragments arrive piecemeal; the composer stitches them into a complete record
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# `data_source_adapters` — Component
2+
3+
Adapter framework for pulling LIF data from heterogeneous source systems. The orchestrator picks an adapter by id, the adapter handles the source-specific API contract (auth scheme, pagination, response shape), and returns LIF fragments that the orchestrator merges into a record.
4+
5+
## Public surface
6+
7+
```python
8+
from lif.data_source_adapters import LIFDataSourceAdapter, ADAPTER_REGISTRY, register_adapter
9+
```
10+
11+
`LIFDataSourceAdapter` is the abstract base class. Subclasses live in sibling directories (one per adapter):
12+
13+
| Adapter id | Directory | Notes |
14+
|---|---|---|
15+
| `lif-to-lif` | [`lif_to_lif_adapter/`](lif_to_lif_adapter/) | Reads from another LIF GraphQL API |
16+
| `example-data-source-rest-api-to-lif` | [`example_data_source_rest_api_to_lif_adapter/`](example_data_source_rest_api_to_lif_adapter/) | Reference impl that pulls from the bundled example data source |
17+
18+
External adapters can be registered via `register_adapter(adapter_id, adapter_class)`.
19+
20+
## Adding a new adapter
21+
22+
See [`docs/operations/guides/creating-a-data-source-adapter.md`](../../../docs/operations/guides/creating-a-data-source-adapter.md) for the class contract and design guidelines, or [`docs/operations/guides/add-data-source.md`](../../../docs/operations/guides/add-data-source.md) for the end-to-end tutorial.
23+
24+
## Used by
25+
Pulled in by the orchestrator via configuration — the registry is consulted at adapter-dispatch time rather than by direct import from other components.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# `example_data_source_rest_api_to_lif_adapter` — Adapter
2+
3+
Reference adapter that pulls from the bundled [`example_data_source_rest_api`](../../../../bases/lif/example_data_source_rest_api/) base. Treat as a worked example for how to write a custom adapter against a real source API — copy this directory, rename, swap in your auth + URL conventions.
4+
5+
## Files
6+
7+
| File | What it does |
8+
|---|---|
9+
| `adapter.py` | `ExampleDataSourceRestAPIToLIFAdapter` — implements the `LIFDataSourceAdapter` contract |
10+
11+
## Registered as
12+
`example-data-source-rest-api-to-lif` (see [`../README.md`](../README.md) for the registry).
13+
14+
## See also
15+
[`docs/operations/guides/add-data-source.md`](../../../../docs/operations/guides/add-data-source.md) walks through cloning this adapter as the starting point for a custom data source.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# `lif_to_lif_adapter` — Adapter
2+
3+
Reads from another LIF GraphQL API. Used when one LIF deployment needs to pull learner data from a peer LIF deployment — typically in multi-org demos where org1's orchestrator fetches data hosted by org2 or org3.
4+
5+
## Files
6+
7+
| File | What it does |
8+
|---|---|
9+
| `adapter.py` | `LIFToLIFAdapter` — implements the `LIFDataSourceAdapter` contract |
10+
| `graphql_query_all_fields_org2.graphql` | Pre-baked query template for org2 |
11+
| `graphql_query_all_fields_org3.graphql` | Pre-baked query template for org3 |
12+
13+
The per-org `.graphql` files exist because schema field selection is hard to template dynamically against an evolving LIF data model; keeping the queries as text files makes them easy to inspect and edit. Generic adapters that target arbitrary LIF deployments will need to generate selections at runtime — that work isn't done here.
14+
15+
## Registered as
16+
`lif-to-lif` (see [`../README.md`](../README.md) for the registry).

components/lif/datatypes/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# `datatypes` — Component
2+
3+
Core Pydantic models that flow through the LIF data plane. Every service that handles LIF records, queries, or jobs imports from here. Single source of truth for the wire shapes — bases don't define their own.
4+
5+
## Layout
6+
7+
| File | Contents |
8+
|---|---|
9+
| `core.py` | `LIFRecord`, `LIFPerson`, `LIFFragment`, `LIFQuery`, `LIFQueryFilter`, `LIFUpdate`, `LIFQueryPlan*`, `LIFPersonIdentifier(s)`, `LIFQueryStatusResponse`, `LIFQueryPlanPartTranslation`, `HealthCheckResponse`, `TargetTransformationDataModel(s)DTO` |
10+
| `identity_mapping.py` | `IdentityMapping` |
11+
| `mdr_sql_model.py` | SQLModel-style classes used by MDR persistence |
12+
| `orchestration.py` | `OrchestratorJob`, `OrchestratorJobDefinition`, `OrchestratorJobRequest`, request/response wrappers |
13+
14+
## Naming convention
15+
16+
Models follow the PascalCase/camelCase split documented in [`docs/specs/data-model-rules.md`](../../../docs/specs/data-model-rules.md): entities (containers) are PascalCase, scalars are camelCase. Many models use `populate_by_name=True` with `alias="EntityName"` so they accept either case on input but normalize internally.
17+
18+
## Used by
19+
Practically everything: every REST base, the cache and planner services, the orchestrator, the translator, and the MDR services. Changes here ripple widely; treat additions as a stable-API extension.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# `example_data_source_service` — Component
2+
3+
Sample data + business logic backing the [`example_data_source_rest_api`](../../../bases/lif/example_data_source_rest_api/) base. Provides a small fake person/course dataset that adapters can exercise locally without standing up a real SIS or LMS.
4+
5+
## Public surface
6+
7+
```python
8+
from lif.example_data_source_service.core import (
9+
user_info, users_info, users_info_filtered, courses_info,
10+
)
11+
```
12+
13+
| Function | Returns |
14+
|---|---|
15+
| `user_info(user_id)` | One sample person |
16+
| `users_info()` | All sample persons |
17+
| `users_info_filtered(filter)` | Subset matching the filter |
18+
| `courses_info()` | Sample courses dataset |
19+
20+
## Used by
21+
- `bases/lif/example_data_source_rest_api` — only consumer; this component exists to keep that base small and stub-able.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# `exceptions` — Component
2+
3+
LIF-wide exception types. Services raise these instead of bare `Exception` so HTTP bases can centralize translation to status codes via `@app.exception_handler` registrations.
4+
5+
## Public surface
6+
7+
```python
8+
from lif.exceptions.core import (
9+
LIFException,
10+
ResourceNotFoundException,
11+
DataNotFoundException,
12+
# ...
13+
)
14+
```
15+
16+
`LIFException` is the catch-all base. Sub-types convey common semantics (not-found, data-not-found, validation failure, etc.) so handlers can map them to 404 / 422 / 500 without `isinstance` chains in business code.
17+
18+
## Convention
19+
20+
When you add a new exception type, also wire its handler in any base that should respond differently to it. The translator base is a good template — see its `@app.exception_handler` cascade in `bases/lif/translator_restapi/core.py`.
21+
22+
## Used by
23+
- Every REST base — handlers convert these to HTTP responses
24+
- Service components — raise these from business logic
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# `graphql_client` — Component
2+
3+
Authenticated HTTP client for calling the LIF GraphQL API. Wraps the boilerplate (auth header, error mapping, JSON shaping) into two functions so callers don't need to learn `httpx` semantics.
4+
5+
## Public surface
6+
7+
```python
8+
from lif.graphql_client import graphql_query, graphql_mutation, GraphQLClientException
9+
```
10+
11+
Both functions send `X-API-Key` from `LIF_GRAPHQL_API_KEY` (when set) as the auth header — see CLAUDE.md § "GraphQL API Key Authentication" for the server-side configuration.
12+
13+
| Function | Purpose |
14+
|---|---|
15+
| `graphql_query(...)` | Read-side query, returns parsed data |
16+
| `graphql_mutation(...)` | Write-side mutation, returns parsed data |
17+
| `GraphQLClientException` | Raised on transport or GraphQL-error response |
18+
19+
## Used by
20+
- `components/lif/semantic_search_service` — calls GraphQL to fulfill MCP queries

0 commit comments

Comments
 (0)