app.services¶
Tenant-scoped CRUD + transaction + DAG construction. Routes call into here; never the other way around.
Project CRUD.
- async app.services.project_service.create_project(session, *, tenant_id, name, description=None)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
name (str)
description (str | None)
- Return type:
Project
- async app.services.project_service.get_project(session, *, tenant_id, project_id)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
project_id (str)
- Return type:
Project
- async app.services.project_service.list_projects(session, *, tenant_id, page_size=50, page_token=None)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
page_size (int)
page_token (str | None)
- Return type:
tuple[list[Project], str | None]
- async app.services.project_service.patch_project(session, *, tenant_id, project_id, updates)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
project_id (str)
updates (dict)
- Return type:
Project
- async app.services.project_service.delete_project(session, *, tenant_id, project_id)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
project_id (str)
- Return type:
None
Dataset CRUD + manifest_hash recompute + ImageSource creation.
- async app.services.dataset_service.create_image_source(session, *, tenant_id, source)[source]
Persist an ImageSource row from a request-side source spec.
- Parameters:
session (AsyncSession)
tenant_id (str)
source (Annotated[UploadSourceSpec | LocalSourceSpec | S3SourceSpec, FieldInfo(annotation=NoneType, required=True, discriminator='kind')])
- Return type:
ImageSource
- async app.services.dataset_service.create_dataset(session, *, tenant_id, project_id, source_id, name, camera_model='SIMPLE_RADIAL', intrinsics_mode='single_camera', is_spherical=False, rig_config=None, respect_exif_orientation=False)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
project_id (str)
source_id (str)
name (str)
camera_model (str)
intrinsics_mode (str)
is_spherical (bool)
rig_config (dict | None)
respect_exif_orientation (bool)
- Return type:
Dataset
- async app.services.dataset_service.get_dataset(session, *, tenant_id, dataset_id, project_id=None)[source]
Load a Dataset by id, scoped to
tenant_id.Pass
project_idto additionally enforce that the row belongs to that project — raisesValidationErroron mismatch. Routes nested under/projects/{pid}/datasets/{did}should pass it; top-level reads (/v1/datasets/{did}/...) leave itNone.- Parameters:
session (AsyncSession)
tenant_id (str)
dataset_id (str)
project_id (str | None)
- Return type:
Dataset
- async app.services.dataset_service.list_datasets(session, *, tenant_id, project_id, page_size=100, page_token=None)[source]
AIP-158 keyset pagination on
dataset_idascending.- Parameters:
session (AsyncSession)
tenant_id (str)
project_id (str)
page_size (int)
page_token (str | None)
- Return type:
tuple[list[Dataset], str | None]
- async app.services.dataset_service.delete_dataset(session, *, tenant_id, dataset_id)[source]
Cascade-delete a dataset and its images. Blob refcounts are decremented for upload-sourced images. Caller is responsible for workspace cleanup if any.
- Parameters:
session (AsyncSession)
tenant_id (str)
dataset_id (str)
- Return type:
None
- async app.services.dataset_service.patch_dataset(session, *, tenant_id, dataset_id, updates)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
dataset_id (str)
updates (dict)
- Return type:
Dataset
- async app.services.dataset_service.recompute_manifest_hash(session, *, dataset_id)[source]
- Parameters:
session (AsyncSession)
dataset_id (str)
- Return type:
str
Image CRUD inside a dataset.
- async app.services.image_service.add_image(session, *, tenant_id, dataset, name, content_sha, source_kind, rel_path=None, byte_size=None, width=None, height=None, exif=None)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
dataset (Dataset)
name (str)
content_sha (str)
source_kind (str)
rel_path (str | None)
byte_size (int | None)
width (int | None)
height (int | None)
exif (dict[str, Any] | None)
- Return type:
Image
- async app.services.image_service.list_images(session, *, tenant_id, dataset_id, page_size=100, page_token=None)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
dataset_id (str)
page_size (int)
page_token (str | None)
- Return type:
tuple[list[Image], str | None]
- async app.services.image_service.get_image(session, *, tenant_id, image_id)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
image_id (str)
- Return type:
Image
- async app.services.image_service.set_pose_prior(session, *, tenant_id, image_id, prior)[source]
Attach (or clear) a PosePrior on an image. Pass
Noneto clear.- Parameters:
session (AsyncSession)
tenant_id (str)
image_id (str)
prior (dict[str, Any] | None)
- Return type:
Image
- async app.services.image_service.list_pose_priors(session, *, tenant_id, dataset_id)[source]
Return
(image, prior_dict)for every image in the dataset that carries a non-nullpose_prior_json. Order: by image name.- Parameters:
session (AsyncSession)
tenant_id (str)
dataset_id (str)
- Return type:
list[tuple[Image, dict[str, Any]]]
- async app.services.image_service.delete_image(session, *, tenant_id, dataset_id, name)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
dataset_id (str)
name (str)
- Return type:
None
- async app.services.image_service.delete_image_by_id(session, *, tenant_id, image_id)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
image_id (str)
- Return type:
None
Chunked upload state management.
- async app.services.upload_service.init_upload(session, *, tenant_id, expected_size, content_type, expected_sha, idempotency_key, settings=None)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
expected_size (int)
content_type (str | None)
expected_sha (str | None)
idempotency_key (str | None)
settings (Settings | None)
- Return type:
Upload
- async app.services.upload_service.get_upload(session, *, tenant_id, upload_id)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
upload_id (str)
- Return type:
Upload
- async app.services.upload_service.append_chunk(session, *, tenant_id, upload_id, offset, data)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
upload_id (str)
offset (int)
data (bytes)
- Return type:
Upload
- async app.services.upload_service.finalize_upload(session, *, tenant_id, upload_id, client_sha=None)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
upload_id (str)
client_sha (str | None)
- Return type:
Upload
- async app.services.upload_service.gc_expired_uploads(session, *, now=None)[source]
- Parameters:
session (AsyncSession)
now (datetime | None)
- Return type:
int
Job CRUD + cache lookup + DAG persistence.
The orchestrator owns DAG building (in app.orchestrator.dag). This service persists Jobs and Tasks to the DB and is responsible for cache short-circuit logic.
- async app.services.job_service.create_job(session, *, tenant_id, project_id, recipe, spec)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
project_id (str)
recipe (str)
spec (dict[str, Any] | None)
- Return type:
Job
- async app.services.job_service.get_job(session, *, tenant_id, job_id)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
job_id (str)
- Return type:
Job
- async app.services.job_service.list_jobs(session, *, tenant_id, page_size=50, page_token=None, status=None)[source]
AIP-158 keyset pagination on
job_iddescending (most recent submissions first).statusfilters to a single lifecycle state when set — the canonical cheap-filter exposed in lieu of a full AIP-160 grammar.- Parameters:
session (AsyncSession)
tenant_id (str)
page_size (int)
page_token (str | None)
status (str | None)
- Return type:
tuple[list[Job], str | None]
- async app.services.job_service.lookup_cached_task(session, *, tenant_id, cache_key)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
cache_key (str)
- Return type:
Task | None
- async app.services.job_service.materialize_dag(session, *, tenant_id, job_id, runtime_version_id, nodes)[source]
Persist task nodes to the DB. Looks up cache hits per task; if a successful Task with the same cache_key exists, copies its outputs_ref_json and marks the new Task succeeded immediately.
- Parameters:
session (AsyncSession)
tenant_id (str)
job_id (str)
runtime_version_id (str)
nodes (Iterable[TaskNode])
- Return type:
list[Task]
- async app.services.job_service.cancel_job(session, *, tenant_id, job_id, force)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
job_id (str)
force (bool)
- Return type:
Job
Reconstruction + SubModel CRUD + Snapshot reads.
- async app.services.reconstruction_service.get_reconstruction(session, *, tenant_id, recon_id)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
recon_id (str)
- Return type:
Reconstruction
- async app.services.reconstruction_service.list_submodels(session, *, tenant_id, recon_id, page_size=100, page_token=None)[source]
AIP-158 keyset pagination on
submodel_idascending;idxdetermines display order butsubmodel_idis the cursor key (stable even when components are added / removed mid-iteration).- Parameters:
session (AsyncSession)
tenant_id (str)
recon_id (str)
page_size (int)
page_token (str | None)
- Return type:
tuple[list[SubModel], str | None]
- async app.services.reconstruction_service.get_submodel(session, *, tenant_id, submodel_id)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
submodel_id (str)
- Return type:
SubModel
- async app.services.reconstruction_service.mark_reconstruction_status(session, *, tenant_id, recon_id, status)[source]
Update a reconstruction lifecycle state if the row still exists.
- Parameters:
session (AsyncSession)
tenant_id (str)
recon_id (str)
status (str)
- Return type:
None
- async app.services.reconstruction_service.record_mapping_result(session, *, tenant_id, recon_id, models, snapshot_seq, snapshot_path)[source]
Replace SubModel rows with the components emitted by a map task.
Backends return one summary per disconnected mapping component. The snapshot writer seals those component files under the same sequence; this function makes the resource layer reflect that output.
- Parameters:
session (AsyncSession)
tenant_id (str)
recon_id (str)
models (list[dict[str, Any]])
snapshot_seq (int | None)
snapshot_path (str | None)
- Return type:
None
- app.services.reconstruction_service.list_snapshot_seqs(paths, tenant_id, project_id, recon_id)[source]
- Parameters:
paths (Paths)
tenant_id (str)
project_id (str)
recon_id (str)
- Return type:
list[int]
- app.services.reconstruction_service.snapshot_dir(paths, tenant_id, project_id, recon_id, seq)[source]
- Parameters:
paths (Paths)
tenant_id (str)
project_id (str)
recon_id (str)
seq (int)
- Return type:
Path
Build the Job -> Task DAG for SfM stage calls and named recipes.
The HTTP layer no longer needs to pass image_root / image_list / database_path — they are derived here from the dataset’s source and the persisted Image rows. This keeps the API surface clean (the client knows about its dataset, not about worker-side filesystem layout) and ensures the same materialization logic is used for every stage.
A recipe (incremental | global | hierarchical | spherical) is sugar over the per-stage builders: it strings extract → match → verify → map into one DAG so per-stage caching short-circuits as soon as any prefix is reused.
- async app.services.sfm_stage_service.ensure_reconstruction(session, *, tenant_id, dataset, spec)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
dataset (Dataset)
spec (dict[str, Any])
- Return type:
Reconstruction
- async app.services.sfm_stage_service.derive_materialization(session, *, tenant_id, dataset)[source]
Return a manifest the worker uses to realize the dataset on disk.
Shape:
{ "kind": "upload" | "local" | "s3", "image_list": [str], # ordered image names "image_root": str | None, # filesystem path (None for upload) "blob_shas": {name: sha}, # only for upload "bucket": str, "prefix": str, # only for s3 }
- Parameters:
session (AsyncSession)
tenant_id (str)
dataset (Dataset)
- Return type:
dict[str, Any]
- app.services.sfm_stage_service.reconstruction_database_path(tenant_id, project_id, recon_id)[source]
- Parameters:
tenant_id (str)
project_id (str)
recon_id (str)
- Return type:
str
- app.services.sfm_stage_service.validate_features_config(spec, *, project_id=None)[source]
- Parameters:
spec (dict[str, Any])
project_id (str | None)
- Return type:
None
- app.services.sfm_stage_service.validate_matches_config(spec, *, project_id=None)[source]
- Parameters:
spec (dict[str, Any])
project_id (str | None)
- Return type:
None
- app.services.sfm_stage_service.validate_verify_config(spec, *, project_id=None)[source]
- Parameters:
spec (dict[str, Any])
project_id (str | None)
- Return type:
None
- app.services.sfm_stage_service.validate_mapping_config(spec, *, project_id=None)[source]
- Parameters:
spec (dict[str, Any])
project_id (str | None)
- Return type:
None
- app.services.sfm_stage_service.validate_recipe_stage_configs(*, features_spec, matches_spec, verify_spec, pipeline_spec, project_id=None)[source]
- Parameters:
features_spec (dict[str, Any])
matches_spec (dict[str, Any])
verify_spec (dict[str, Any])
pipeline_spec (dict[str, Any])
project_id (str | None)
- Return type:
None
- async app.services.sfm_stage_service.submit_features(session, *, tenant_id, dataset_id, spec, input_artifacts=None, inline=False)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
dataset_id (str)
spec (dict[str, Any])
input_artifacts (dict[str, Any] | None)
inline (bool)
- Return type:
tuple[str, list[Any]]
- async app.services.sfm_stage_service.submit_matches(session, *, tenant_id, dataset_id, spec, input_artifacts=None, inline=False)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
dataset_id (str)
spec (dict[str, Any])
input_artifacts (dict[str, Any] | None)
inline (bool)
- Return type:
tuple[str, list[Any]]
- async app.services.sfm_stage_service.submit_render_cubemap(session, *, tenant_id, dataset_id, face_size=None, spec=None, inline=False)[source]
Render every spherical panorama into 6 cubemap faces.
Refuses if the dataset isn’t
is_spherical=true. The output is a directory under the dataset’s workspace; the user can register it as a newlocaldataset for downstream pinhole-only pipelines.- Parameters:
session (AsyncSession)
tenant_id (str)
dataset_id (str)
face_size (int | None)
spec (dict[str, Any] | None)
inline (bool)
- Return type:
tuple[str, list[Any]]
- async app.services.sfm_stage_service.submit_project_images(session, *, tenant_id, dataset_id, spec, recipe='project_images', inline=False)[source]
Submit a portable image projection job over a dataset.
- Parameters:
session (AsyncSession)
tenant_id (str)
dataset_id (str)
spec (dict[str, Any])
recipe (str)
inline (bool)
- Return type:
tuple[str, list[Any]]
- async app.services.sfm_stage_service.submit_video_frames(session, *, tenant_id, project_id, video_path, fps=2.0, max_frames=1000, inline=False)[source]
Extract keyframes from a worker-local video file.
- Parameters:
session (AsyncSession)
tenant_id (str)
project_id (str)
video_path (str)
fps (float)
max_frames (int)
inline (bool)
- Return type:
tuple[str, list[Any]]
- async app.services.sfm_stage_service.submit_kapture_import(session, *, tenant_id, project_id, archive_path, inline=False)[source]
Parse a Kapture archive (extracted directory) into
sensorsandrecordslists in the task result. The client follows up with aPOST /v1/projects/{pid}/datasetsof kind=``local`` pointing at the returnedimage_root.- Parameters:
session (AsyncSession)
tenant_id (str)
project_id (str)
archive_path (str)
inline (bool)
- Return type:
tuple[str, list[Any]]
- async app.services.sfm_stage_service.submit_merge_recons(session, *, tenant_id, target_recon_id, source_recon_ids, sim3_aligners=None, inline=False)[source]
Merge several reconstructions into
target_recon_id.All sources MUST belong to the same project as the target.
- Parameters:
session (AsyncSession)
tenant_id (str)
target_recon_id (str)
source_recon_ids (list[str])
sim3_aligners (list[dict[str, Any]] | None)
inline (bool)
- Return type:
tuple[str, list[Any]]
- async app.services.sfm_stage_service.submit_to_cubemap(session, *, tenant_id, recon_id, inline=False)[source]
Convert a spherical reconstruction to a cubemap rig (worker job).
Refuses if the reconstruction’s dataset isn’t marked
is_spherical— this avoids running the converter against a pinhole reconstruction (which produces nonsense).- Parameters:
session (AsyncSession)
tenant_id (str)
recon_id (str)
inline (bool)
- Return type:
tuple[str, list[Any]]
- async app.services.sfm_stage_service.submit_georegister(session, *, tenant_id, recon_id, sim3, inline=False)[source]
Apply a Sim(3) georegistration transform to a reconstruction.
- Parameters:
session (AsyncSession)
tenant_id (str)
recon_id (str)
sim3 (dict[str, Any])
inline (bool)
- Return type:
tuple[str, list[Any]]
- async app.services.sfm_stage_service.submit_localize(session, *, tenant_id, recon_id, blob_sha, spec=None, inline=False)[source]
Localize a single query image (blob_sha) against recon_id.
- Parameters:
session (AsyncSession)
tenant_id (str)
recon_id (str)
blob_sha (str)
spec (dict[str, Any] | None)
inline (bool)
- Return type:
tuple[str, list[Any]]
- async app.services.sfm_stage_service.submit_vlad_index(session, *, tenant_id, dataset_id, spec=None, inline=False)[source]
Build a VLAD descriptor index for the dataset (worker job).
- Parameters:
session (AsyncSession)
tenant_id (str)
dataset_id (str)
spec (dict[str, Any] | None)
inline (bool)
- Return type:
tuple[str, list[Any]]
- async app.services.sfm_stage_service.submit_verify(session, *, tenant_id, dataset_id, spec, input_artifacts=None, inline=False)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
dataset_id (str)
spec (dict[str, Any])
input_artifacts (dict[str, Any] | None)
inline (bool)
- Return type:
tuple[str, list[Any]]
- async app.services.sfm_stage_service.collect_pose_priors_by_name(session, *, tenant_id, dataset_id)[source]
Return
{image_name: PosePrior dict}for every image in the dataset that has a non-nullpose_prior_json. Keyed by name so the worker can correlate with pycolmap’s image-name primary key.- Parameters:
session (AsyncSession)
tenant_id (str)
dataset_id (str)
- Return type:
dict[str, dict[str, Any]]
- app.services.sfm_stage_service.build_recipe_dag(*, project_id, dataset_id, recon_id, materialization, database_path, features_spec, matches_spec, verify_spec, pipeline_spec, pose_priors=None, input_artifacts=None)[source]
Stitch extract → match → verify → map into one DAG. Each TaskNode is hashed with the same shape as a single-stage submission, so a recipe that re-uses an already-computed extract+match prefix short-circuits to the cached results.
pose_priorsare optional per-image priors (keyed by image name); when present they’re forwarded into the map task’s inputs so the worker can wire them into pycolmap’sMappingInput.- Parameters:
project_id (str)
dataset_id (str)
recon_id (str)
materialization (dict[str, Any])
database_path (str)
features_spec (dict[str, Any])
matches_spec (dict[str, Any])
verify_spec (dict[str, Any])
pipeline_spec (dict[str, Any])
pose_priors (dict[str, dict[str, Any]] | None)
input_artifacts (dict[str, dict[str, Any]] | None)
- Return type:
list[TaskNode]
API key issuance + tenant resolution.
- async app.services.api_key_service.issue_key(session, *, tenant_id, name=None)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
name (str | None)
- Return type:
tuple[str, ApiKey]
- async app.services.api_key_service.resolve_tenant(session, *, key)[source]
- Parameters:
session (AsyncSession)
key (str)
- Return type:
str
Quota enforcement hooks (NOOP unless auth_mode=api_key).
- async app.services.quota_service.get_or_create_quota(session, *, tenant_id)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
- Return type:
TenantQuota
- async app.services.quota_service.check_storage(session, *, tenant_id, additional)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
additional (int)
- Return type:
None
- async app.services.quota_service.check_gpu_seconds(session, *, tenant_id)[source]
- Parameters:
session (AsyncSession)
tenant_id (str)
- Return type:
None
runtime_version row management.
- async app.services.runtime_version_service.ensure_runtime_version(session, settings=None)[source]
- Parameters:
session (AsyncSession)
settings (Settings | None)
- Return type:
RuntimeVersion