REST API adapters
Construction of authenticated HTTP clients for the ACMAD API.
- acmad_uploader.adapters.api.auth.create_authenticated_client(*, api_url, realm_url, client_id, persist_tokens=True, open_browser=True, verify_tls=True, timeout=30.0, login_url_callback=None)
Create an HTTP client authenticated by OIDC device flow.
Credentials are entered at the identity provider. Only OIDC tokens may be persisted, using the operating system keyring when requested.
- Return type:
Client
Shared low-level HTTP/JSON plumbing for ACMAD REST API adapters.
The patient/diagnosis/gait-assessment adapters predate this and
deliberately keep their own copies of this plumbing (see CLAUDE.md) to
avoid touching already-tested code for DRY’s sake alone. New adapters
should use this shared client instead of duplicating it again.
- class acmad_uploader.adapters.api.client.JsonApiClient(client, error_type)
Bases:
objectTranslate HTTP/JSON mechanics into a caller-supplied error type.
- request(method, url, **kwargs)
Send a request and translate transport/API errors to the port error.
- Return type:
Response
- object(response, description)
Decode an API object response.
- Return type:
Mapping[str,Any]
- list_items(endpoint, *, params, description)
Return every item from the first page of a filtered list response.
- Return type:
list[Mapping[str,Any]]
- single_or_none(endpoint, *, params, description)
Return zero or one filtered object, rejecting ambiguous results.
- Return type:
Mapping[str,Any] |None
- single(endpoint, *, params, description)
Return exactly one filtered API object.
- Return type:
Mapping[str,Any]
- required_url(item, description)
Read a hyperlinked serializer URL.
- Return type:
str
- error(message)
Build an instance of the destination error type for a custom failure.
For callers that need custom client-side matching beyond what
single/single_or_nonesupport (e.g. filtering results by a field the API doesn’t expose as a server-side filter) and still want to raise the same caller-supplied error type on a bad match.- Return type:
Exception
- static pk_from_url(url)
Return the primary key segment of a hyperlinked serializer URL.
Auto-generated filterset fields for foreign keys expect the related object’s primary key, not its hyperlinked URL.
- Return type:
str
Shared payload building for PatientEvent metadata across event adapters.
- acmad_uploader.adapters.api.patient_event.patient_event_details_payload(details)
Build the writable
PatientEventfields common to every event type.- Return type:
dict[str,Any]
Shared read-only resolution of an existing gait assessment’s URL.
Every slice hanging off GaitAssessment (FunctionAssessment, MocapData,
PhysicalExamination, …) needs to resolve the same
patient -> patient event -> gait assessment chain by natural key. Only
AcmadGaitAssessmentRepository itself is responsible for creating that
chain; this helper only looks it up, raising the caller’s error type if any
link is missing (which means the gait assessment upload hasn’t run yet).
- class acmad_uploader.adapters.api.gait_assessment_lookup.GaitAssessmentLookup(api)
Bases:
objectResolve a gait assessment’s URL from
(site_patient_id, age_in_days).- resolve(*, site_patient_id, age_in_days)
Return the gait assessment URL, raising if any link is missing.
- Return type:
str
Shared read-only resolution of an existing mocap session’s URL.
Any slice hanging off MocapData (currently only BiomechanicsArtifact)
needs to resolve the same patient -> patient event -> gait assessment ->
mocap session chain by natural key. Only AcmadMocapDataRepository
itself is responsible for creating that chain; this helper only looks it
up, raising the caller’s error type if any link is missing (which means
the mocap session upload hasn’t run yet).
AcmadMocapDataRepository._find_existing already implements this same
client-side matching, deliberately kept separate/duplicated here rather
than refactored to share code, to avoid touching already-tested code for
DRY’s sake alone (see CLAUDE.md).
- class acmad_uploader.adapters.api.mocap_data_lookup.MocapDataLookup(api)
Bases:
objectResolve a mocap session’s URL from its full natural key.
MocapDatahas no database-level natural key at all (mocapdata/only filters ongait_assessment/session_condition/gait_aidserver-side) — the left/right condition-detail pair is matched client-side over that filtered list.- resolve(*, site_patient_id, age_in_days, session_condition, gait_aid, left_condition_detail, right_condition_detail)
Return the mocap session URL, raising if any link is missing.
- Return type:
str
Patient repository backed by the ACMAD REST API.
- class acmad_uploader.adapters.api.patient.AcmadPatientRepository(client)
Bases:
objectIdempotently create or update basic patient records over HTTP.
- upsert(patient)
Upsert a patient located by its globally unique site identifier.
- Return type:
Diagnosis repository backed by the ACMAD REST API.
- class acmad_uploader.adapters.api.diagnosis.AcmadDiagnosisRepository(client)
Bases:
objectIdempotently create or update diagnosis records over HTTP.
definitionandlatest_primary_diagnosisare never sent: the API resolves the matching diagnosis reference record from the submitted group/subgroup/detail itself, and recalculates which primary diagnosis is “latest” from the patient’s full diagnosis history after every write.siteis always resolved from the workbook’s ownsite_idrather than inferred from the patient: a patient merged across clinics via the de-identified hash can be associated with more than one site, but a diagnosis is owned by the specific clinic that produced it.- upsert(diagnosis)
Upsert a diagnosis located by patient, type and age at diagnosis.
- Return type:
Gait assessment repository backed by the ACMAD REST API.
- class acmad_uploader.adapters.api.gait_assessment.AcmadGaitAssessmentRepository(client)
Bases:
objectResolve or create a patient event and its gait assessment over HTTP.
patientevent/filters onpatient,event_typeandage_in_days;gaitassessment/filters onpatient_event(confirmed againstacmadapi.views.patientevent/gaitassessment), so both lookups are single filtered GETs rather than a listing narrowed client-side.A repeat run with an unchanged assessment performs no write once the event and gait assessment already exist: both are only looked up and reused, since neither resource has writable fields beyond its linkage.
siteis resolved from the workbook’s ownsite_idrather than inferred from the patient: a patient merged across clinics via the de-identified hash can be associated with more than one site, so the event needs its own explicit site, not a guess at the patient’s site.- upsert(assessment)
Resolve the patient, event and gait assessment, creating as needed.
- Return type:
Function assessment repository backed by the ACMAD REST API.
- class acmad_uploader.adapters.api.function_assessment.AcmadFunctionAssessmentRepository(client)
Bases:
objectIdempotently create or update function assessment records over HTTP.
Matched by its owning
gait_assessment, resolved from(site_patient_id, age_in_days)via the sharedGaitAssessmentLookup— the gait assessment itself must already have been uploaded.- upsert(assessment)
Upsert a function assessment located by its owning gait assessment.
- Return type:
Mocap session repository backed by the ACMAD REST API.
- class acmad_uploader.adapters.api.mocap_data.AcmadMocapDataRepository(client)
Bases:
objectIdempotently create or update mocap session records over HTTP.
mocapdata/only filters ongait_assessment/session_condition/gait_aidserver-side;MocapDatahas no database-level natural key at all, so the left/right condition-detail match is done client-side over that filtered list (confirmed with the user that the full combination is unique per day/assessment in practice).- upsert(session)
Upsert a mocap session located by its condition-detail combination.
- Return type:
Biomechanics artifact repository backed by the ACMAD REST API.
- class acmad_uploader.adapters.api.biomechanics_artifact.AcmadBiomechanicsArtifactRepository(client)
Bases:
objectIdempotently upload a biomechanics artifact file and its metadata.
Matched by
(mocap_data, original_path)— the destination’s own natural key (a realunique_together), so no client-side dedup is needed here, unlikeMocapData/SurgicalProcedure` before their own natural-key fixes. An unchanged file (samesha256already stored) is skipped entirely rather than re-uploaded.The three “derived” CSV roles (
normalised_kinematics_csv/normalised_kinetics_csv/spatiotemporal_csv) require anOpenSimProcessingRun— resolved (or created once) per mocap session and reused for all three, since the destination forbids changingprocessing_runon an existing derived artifact (BiomechanicsArtifactSerializer.validate). Every other role uploads with no processing run at all.- upsert(artifact)
Resolve the mocap session, then upload or skip the artifact file.
- Return type:
Physical examination repository backed by the ACMAD REST API.
- class acmad_uploader.adapters.api.physical_examination.AcmadPhysicalExaminationRepository(client)
Bases:
objectIdempotently create or update a physical examination and its detail rows.
Resolves the owning
GaitAssessmentvia the sharedGaitAssessmentLookup, then upsertsPhysicalExamination(matched bygait_assessment), followed byAnthropometrics(1:1, matched byphysical_examination), followed by the five side-keyed detail models (AnthropometricSide/RangeOfMotion/Strength/Tone/FootStructure, each matched by(physical_examination, side)) — the same “parent then children” idiom asSurgery/SurgicalProcedure, just with a fixed left+right pair per detail model instead of a variable-length collection.- upsert(examination)
Resolve the gait assessment, then upsert the examination and its details.
- Return type:
PROMs family repository backed by the ACMAD REST API.
- class acmad_uploader.adapters.api.proms.AcmadPromsRepository(client)
Bases:
objectIdempotently create or update a PROMs parent row and its questionnaires.
Resolves the owning
GaitAssessmentvia the sharedGaitAssessmentLookup, then upsertsPROMs(matched bygait_assessment), followed byFAQ(1:1, matched byproms) and each dual-respondent questionnaire (matched by(proms, completed_by)) — the same “parent then children” idiom asSurgery/PhysicalExamination.- upsert(proms)
Resolve the gait assessment, then upsert PROMs and its questionnaires.
- Return type:
Surgery/intervention episode repository backed by the ACMAD REST API.
- class acmad_uploader.adapters.api.surgery.AcmadSurgeryRepository(client)
Bases:
objectIdempotently create or update a surgery episode and its procedures.
Resolves patient -> patient event -> surgery directly (not via the shared
GaitAssessmentLookup, since this is a surgery-type event, not an assessment-type one).hospitalis resolved best-effort: unlikesite(the uploading clinic, always a participating institution), the physical hospital where surgery was performed may not correspond to any institution in the system at all (e.g. “other overseas site”), so a lookup miss just leaves the field unset rather than erroring.Each
SurgicalProcedureis matched by(surgery, procedure_index), the destination’s own natural key for it —procedure_indexis the numbered workbook slot the procedure was read from.- upsert(surgery)
Resolve the patient, event and surgery, then upsert its procedures.
- Return type: