Skip to content

Commit 72abfa1

Browse files
committed
Enhance development environment by adding new extensions for improved functionality and updating architecture rules documentation for clarity and structure
1 parent 2999cb9 commit 72abfa1

6 files changed

Lines changed: 249 additions & 4 deletions

File tree

.devcontainer/antigravity/devcontainer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@
101101
"github.copilot-chat",
102102
"charliermarsh.ruff",
103103
"tim-koehler.helm-intellisense",
104-
"vadzimnestsiarenka.helm-template-preview-and-more"
104+
"vadzimnestsiarenka.helm-template-preview-and-more",
105+
"jebbs.plantuml",
106+
"systemticks.c4-dsl-extension"
105107
]
106108
}
107109
},

.devcontainer/vscode/devcontainer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@
107107
"github.copilot-chat",
108108
"charliermarsh.ruff",
109109
"tim-koehler.helm-intellisense",
110-
"vadzimnestsiarenka.helm-template-preview-and-more"
110+
"vadzimnestsiarenka.helm-template-preview-and-more",
111+
"jebbs.plantuml",
112+
"systemticks.c4-dsl-extension"
111113
]
112114
}
113115
},

.vscode/extensions.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
"charliermarsh.ruff",
2222
"tim-koehler.helm-intellisense",
2323
"vadzimnestsiarenka.helm-template-preview-and-more",
24-
"ms-vscode-remote.remote-containers"
24+
"ms-vscode-remote.remote-containers",
25+
"jebbs.plantuml",
26+
"systemticks.c4-dsl-extension"
2527
],
2628
}

AGENTS.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,22 @@ docker compose logs -f
7676
docker compose down
7777
```
7878

79-
## 📝 Key Implementation Details
79+
## � Architecture Rules
80+
81+
Before generating or modifying code, read **[docs/ARCHITECTURE_RULES.md](docs/ARCHITECTURE_RULES.md)**.
82+
83+
It defines binding constraints that MUST be followed:
84+
85+
- **Module Dependency Graph**: Which module may import from which (no circular imports).
86+
- **Extension Points**: How to add new DB entities, mapper functions, or config values.
87+
- **Concurrency Rules**: IPC contract for worker processes, Semaphore scope.
88+
- **Error Handling**: Per-investigation failure isolation, stats update pattern.
89+
- **Config**: NEVER use `os.environ` directly — always extend `Config` in `config.py`.
90+
- **Database Access**: All SQL goes through `Database`; always use server-side cursors and bulk fetches.
91+
92+
---
93+
94+
## �📝 Key Implementation Details
8095

8196
### External Dependencies
8297

docs/ARCHITECTURE_RULES.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Architecture Rules: SQL-to-ARC Middleware
2+
3+
This document defines **binding rules** for the `middleware/sql_to_arc` package.
4+
These constraints exist to preserve correctness, prevent circular imports, and enforce design patterns.
5+
An AI assistant or developer modifying this codebase MUST follow these rules.
6+
7+
---
8+
9+
## 1. Module Dependency Graph
10+
11+
`sql_to_arc` is a single, self-contained component. There is no complex inter-package dependency policy to enforce within it.
12+
13+
The one cross-package rule is:
14+
15+
> `middleware.shared` and `middleware.api_client` are **read-only dependencies** of `sql_to_arc`.
16+
> They must NEVER import from `middleware.sql_to_arc`. This is naturally enforced since both packages live in a separate repository.
17+
18+
If intra-package layering rules become necessary in the future (e.g., forbidden imports between specific modules), document them here.
19+
20+
---
21+
22+
## 2. Extension Points
23+
24+
### 2.1 Adding a New Database Entity
25+
26+
When adding a new entity type (e.g. `SampleRow`), ALL of the following steps are mandatory:
27+
28+
1. **Define the model** in `models.py` by subclassing `BaseRow`:
29+
30+
```python
31+
class SampleRow(BaseRow):
32+
__view_name__: ClassVar[str] = "vSample"
33+
identifier: str = spec_field(required=True)
34+
...
35+
```
36+
37+
- Use `spec_field()` (not `Field()` directly) for all ARC-spec-relevant fields.
38+
- Set `__view_name__` to the exact database view name.
39+
40+
2. **Add a streaming method** to `Database` in `database.py`:
41+
42+
```python
43+
async def stream_samples(self, investigation_ids: list[str]) -> AsyncGenerator[SampleRow, None]:
44+
async for r in self._stream_by_investigation(SampleRow, investigation_ids, "sample"):
45+
yield r
46+
```
47+
48+
3. **Register for schema validation** in `Database.validate_schema()`:
49+
50+
```python
51+
models = [..., SampleRow]
52+
```
53+
54+
4. **Add a mapper function** in `mapper.py`.
55+
56+
5. **Link into the data bundle** `ArcBuildData` in `context.py` and populate it in `_fetch_and_group_related_data()` in `processor.py` using `group_stream()`.
57+
58+
6. **Call the mapper** inside `builder.py` in `build_single_arc_task()`.
59+
60+
### 2.2 Adding New Mapper Functions
61+
62+
- Mapper functions live exclusively in `mapper.py`.
63+
- They accept a single `*Row` Pydantic model as input and return an `arctrl` type.
64+
- They MUST NOT perform I/O, logging, or access the database.
65+
66+
### 2.3 Adding New Configuration Values
67+
68+
- All configuration values MUST be added as typed, annotated fields in the `Config` class in `config.py` or in other config classes that are referenced by `Config`.
69+
- MUST use `Annotated[..., Field(description="...")]` with a meaningful description.
70+
- Provide a sensible default whenever possible.
71+
- **NEVER** access `os.environ` directly in any module. The `Config` object is the single source of truth for all settings.
72+
- **NEVER** introduce new environment variables outside of `Config`.
73+
74+
---
75+
76+
## 3. Concurrency & IPC Rules
77+
78+
### 3.1 Process Pool Entry Point
79+
80+
- `build_single_arc_task()` in `builder.py` is the **only function** executed inside worker processes.
81+
- It MUST be a plain, top-level function (not a method or lambda) because it is pickled for IPC.
82+
- Its argument MUST be the frozen dataclass `ArcBuildData` (picklable, no locks, no sockets).
83+
- Its return value MUST be a `str` (JSON-LD string) or `None`. Returning complex objects (e.g., `ARC`) is forbidden — they are not reliably picklable across process boundaries and waste IPC bandwidth.
84+
85+
### 3.2 Memory Management in Workers
86+
87+
- After serializing the ARC to JSON, `del arc` MUST be called, followed by `gc.collect()`.
88+
- This prevents worker processes from accumulating memory across repeated calls.
89+
90+
### 3.3 Semaphore Usage
91+
92+
- The `asyncio.Semaphore` (from `config.max_concurrent_tasks`) limits the **full lifecycle** of each investigation: data bundling → CPU build → API upload.
93+
- It is acquired inside `_build_and_upload_single_arc()`.
94+
- NEVER acquire the semaphore in a different scope (e.g. before spawning a task).
95+
- Do NOT use `asyncio.Semaphore` as a substitute for the process pool limit. Both controls serve different purposes: the semaphore manages memory/IO, the `ProcessPoolExecutor` manages CPU.
96+
97+
---
98+
99+
## 4. Error Handling Rules
100+
101+
- A failure for one investigation MUST NOT abort the entire run.
102+
- Catch expected errors at the point closest to the failure (`_upload_and_update_stats`, `_build_and_upload_single_arc`).
103+
- On failure: increment `stats.failed_datasets` and append the identifier to `stats.failed_ids`.
104+
- Re-raise unexpected errors (i.e., programming errors) so they are visible immediately.
105+
- NEVER use bare `except Exception` as the final catch — only use it in `process_investigations`'s batch loop where it is immediately re-raised after logging.
106+
107+
---
108+
109+
## 5. Configuration & Secrets
110+
111+
- Configuration is loaded once in `main.py` via `ConfigWrapper` from `middleware.shared`.
112+
- The resulting `Config` object is passed explicitly to functions that need it (dependency injection).
113+
- Secrets (e.g. `connection_string`, API keys) use `pydantic.SecretStr`. Never log them with `str()` directly; use `.get_secret_value()` only at the point of use (e.g., engine creation).
114+
115+
---
116+
117+
## 6. Logging Conventions
118+
119+
- Every module defines: `logger = logging.getLogger(__name__)`.
120+
- Do NOT use `print()` for any diagnostic output.
121+
- Log messages that occur inside concurrent tasks MUST include a traceability prefix. Use the pattern `"%s: message", inv_info` (see `inv_info` in `processor.py`) so parallel log lines are distinguishable.
122+
- Log levels:
123+
- `DEBUG`: internal state, loop iterations.
124+
- `INFO`: successful milestones (fetch, build, upload).
125+
- `WARNING`: recoverable issues (missing optional column, assay without study).
126+
- `ERROR`: per-item failures (build failed, upload failed). Do not use for fatal errors.
127+
128+
---
129+
130+
## 7. Database Access Rules
131+
132+
- All database reads go through the `Database` class in `database.py`. No other module is allowed to instantiate `AsyncEngine` or execute SQL directly.
133+
- Use `stream_results=True` on all large queries to enable server-side cursors and avoid loading full tables into RAM.
134+
- Use `literal_column("*")` with `select()` rather than ORM field mappings to generate clean `SELECT *` SQL.
135+
- Related data (studies, assays, etc.) is ALWAYS fetched in bulk per batch using `WHERE investigation_ref IN (...)`. Never fetch related data row-by-row in a loop.

docs/workspace.dsl

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
workspace "SQL-to-ARC Middleware" "Middleware component to convert SQL views into Annotated Research Context (ARC) objects." {
2+
3+
model {
4+
user = person "RDI Data Manager" "Responsible for managing and providing metadata from a Research Data Infrastructure."
5+
6+
group "FAIRagro Ecosystem" {
7+
fairAgroApi = softwareSystem "FAIRagro Middleware API" "Receives RO-Crate JSON-LD payloads." "External"
8+
}
9+
10+
sqlToArc = softwareSystem "SQL-to-ARC Converter" "Central component that maps relational data to ARC objects and sends them to the API." {
11+
database = container "RDI SQL Database" "PostgreSQL database serving standardized metadata views." "PostgreSQL" "Database"
12+
13+
converter = container "Converter Service" "Core logic for database extraction, mapping, and API transmission." "Python" {
14+
main = component "Main Entry Point" "CLI interface and orchestrator." "Python"
15+
16+
group "Async IO Loop (Controller)" {
17+
orchestrator = component "Workflow Orchestrator" "Coordinates the data flow, manages concurrent tasks via Semaphores." "Python/Asyncio"
18+
stats = component "Processing Stats" "Collects success/failure metrics and generates final reports." "Python"
19+
}
20+
21+
group "Process Pool Executor (Worker)" {
22+
mapper = component "ARC Mapper" "Transforms relational rows into ARC structures using arctrl. Runs in separate OS processes to bypass GIL." "Python/arctrl"
23+
serializer = component "JSON-LD Serializer" "Converts ARC objects to JSON strings directly in the worker process." "Python"
24+
}
25+
26+
group "Streaming Generator (Data Layer)" {
27+
db_client = component "Database Client" "Implements lazy-loading and relational batching via SQLAlchemy streaming cursors." "Python/SQLAlchemy"
28+
}
29+
30+
api_client = component "API Client" "Handles mTLS secured async HTTP uploads to the Middleware API." "Python/httpx"
31+
}
32+
33+
demo_api = container "Mock API" "Simulates the FAIRagro API for local testing and CI." "FastAPI" "Development"
34+
}
35+
36+
# Relationships
37+
user -> main "Configures and starts"
38+
39+
main -> orchestrator "Orchestrates through"
40+
orchestrator -> db_client "Streams investigations from"
41+
orchestrator -> mapper "Submits tasks to Process Pool"
42+
orchestrator -> api_client "Enqueues uploads to"
43+
orchestrator -> stats "Updates metrics in"
44+
45+
mapper -> serializer "Serializes to JSON-LD via"
46+
db_client -> database "Queries views (vInvestigation, vStudy, etc.)"
47+
api_client -> fairAgroApi "Sends RO-Crate JSON-LD (mTLS)"
48+
api_client -> demo_api "Sends data during local demo"
49+
}
50+
51+
views {
52+
systemContext sqlToArc "SystemContext" {
53+
include *
54+
autoLayout
55+
}
56+
57+
container sqlToArc "Containers" {
58+
include *
59+
autoLayout
60+
}
61+
62+
component converter "Components" {
63+
include *
64+
autoLayout
65+
}
66+
67+
styles {
68+
element "Software System" {
69+
background #1168bd
70+
color #ffffff
71+
}
72+
element "Container" {
73+
background #438dd5
74+
color #ffffff
75+
}
76+
element "Database" {
77+
shape Cylinder
78+
}
79+
element "External" {
80+
background #999999
81+
color #ffffff
82+
}
83+
element "Group" {
84+
color #666666
85+
border Dotted
86+
}
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)