diff --git a/docs/source/biodata_models/external.md b/docs/source/biodata_models/external.md index 64ce05d2..ba1a3603 100644 --- a/docs/source/biodata_models/external.md +++ b/docs/source/biodata_models/external.md @@ -4,15 +4,23 @@ External registries act as an application programming interfaces (API). They all ## Model definitions -### MouseAnatomyModel +### AnatomyModel + +Shared Pydantic model for species-specific anatomy terms. Fields typed as `AnatomyModel` can use a lookup subclass such as `MouseAnatomyLookup` or `HumanAnatomyLookup`, which provides ontology-specific lookup behavior. + +| Name | Registry | Registry Identifier | +|------|-------|--------| +| `heart` | `Registry.EMAPA` | `EMAPA:16105` | + +### MouseAnatomyLookup [EMAPA](https://www.ebi.ac.uk/ols4/ontologies/emapa) -Base model for mouse anatomy. Some examples: +Lookup model for mouse anatomy terms. Use `search_by_name` to find terms and `get_by_name` for an exact label match. | Name | Registry | Registry Identifier | |------|-------|--------| -| `heart` | `Registry.EMAPA` | `16105` | +| `heart` | `Registry.EMAPA` | `EMAPA:16105` | ### Gene diff --git a/docs/source/biodata_models/process_names.md b/docs/source/biodata_models/process_names.md deleted file mode 100644 index 1bafd183..00000000 --- a/docs/source/biodata_models/process_names.md +++ /dev/null @@ -1,59 +0,0 @@ -# Process_names - -## Model definitions - -### ProcessName - -Process names - -| Name | Value | -|------|-------| -| `ANALYSIS` | `Analysis` | -| `COMPRESSION` | `Compression` | -| `DENOISING` | `Denoising` | -| `EPHYS_CURATION` | `Ephys curation` | -| `EPHYS_POSTPROCESSING` | `Ephys postprocessing` | -| `EPHYS_PREPROCESSING` | `Ephys preprocessing` | -| `EPHYS_VISUALIZATION` | `Ephys visualization` | -| `FIDUCIAL_SEGMENTATION` | `Fiducial segmentation` | -| `FILE_FORMAT_CONVERSION` | `File format conversion` | -| `FIX_COLOR_RANGE` | `Fix color range` | -| `FLUORESCENCE_EVENT_DETECTION` | `Fluorescence event detection` | -| `IMAGE_ATLAS_ALIGNMENT` | `Image atlas alignment` | -| `IMAGE_BACKGROUND_SUBTRACTION` | `Image background subtraction` | -| `IMAGE_CELL_CLASSIFICATION` | `Image cell classification` | -| `IMAGE_CELL_QUANTIFICATION` | `Image cell quantification` | -| `IMAGE_CELL_SEGMENTATION` | `Image cell segmentation` | -| `IMAGE_CROSS_IMAGE_ALIGNMENT` | `Image cross-image alignment` | -| `IMAGE_DESTRIPING` | `Image destriping` | -| `IMAGE_FLAT_FIELD_CORRECTION` | `Image flat-field correction` | -| `IMAGE_IMPORTING` | `Image importing` | -| `IMAGE_MIP_VISUALIZATION` | `Image mip visualization` | -| `IMAGE_MULTISCALING` | `Image multiscaling` | -| `IMAGE_RADIAL_CORRECTION` | `Image radial correction` | -| `IMAGE_SPOT_DETECTION` | `Image spot detection` | -| `IMAGE_SPOT_SPECTRAL_UNMIXING` | `Image spot spectral unmixing` | -| `IMAGE_THRESHOLDING` | `Image thresholding` | -| `IMAGE_TILE_ALIGNMENT` | `Image tile alignment` | -| `IMAGE_TILE_FUSING` | `Image tile fusing` | -| `IMAGE_TILE_PROJECTION` | `Image tile projection` | -| `MANUAL_CURATION` | `Manual curation` | -| `MODEL_EVALUATION` | `Model evaluation` | -| `MODEL_TRAINING` | `Model training` | -| `NEURON_SKELETON_PROCESSING` | `Neuron skeleton processing` | -| `NEUROPIL_SUBTRACTION` | `Neuropil subtraction` | -| `OTHER` | `Other` | -| `PIPELINE` | `Pipeline` | -| `SIMULATION` | `Simulation` | -| `SKULL_STRIPPING` | `Skull stripping` | -| `SPATIAL_TIMESERIES_DEMIXING` | `Spatial timeseries demixing` | -| `SPIKE_SORTING` | `Spike sorting` | -| `VIDEO_ROI_CLASSIFICATION` | `Video ROI classification` | -| `VIDEO_ROI_CROSS_SESSION_MATCHING` | `Video ROI cross session matching` | -| `VIDEO_ROI_SEGMENTATION` | `Video ROI segmentation` | -| `VIDEO_ROI_TIMESERIES_EXTRACTION` | `Video ROI timeseries extraction` | -| `VIDEO_MOTION_CORRECTION` | `Video motion correction` | -| `VIDEO_PLANE_DECROSSTALK` | `Video plane decrosstalk` | -| `DF_F_ESTIMATION` | `dF/F estimation` | - - diff --git a/docs/source/components/configs.md b/docs/source/components/configs.md index 61d15983..cb2c7771 100644 --- a/docs/source/components/configs.md +++ b/docs/source/components/configs.md @@ -24,7 +24,7 @@ Configuration of a catheter | Field | Type | Title (Description) | |-------|------|-------------| -| `targeted_structure` | [MouseAnatomyModel](../biodata_models/external.md#mouseanatomymodel) | Targeted blood vessel (Use options from MouseBloodVessels) | +| `targeted_structure` | [AnatomyModel](../biodata_models/external.md#anatomymodel) | Targeted blood vessel (For common mouse blood-vessel targets, use MouseBloodVessels with MouseAnatomyLookup.get_by_name.) | | `device_name` | `str` | Device name (Must match a device defined in the instrument.json) | diff --git a/docs/source/components/coordinates.md b/docs/source/components/coordinates.md index 6652976c..9940de7b 100644 --- a/docs/source/components/coordinates.md +++ b/docs/source/components/coordinates.md @@ -23,7 +23,7 @@ Definition an atlas | `size_unit` | [SizeUnit](../biodata_models/units.md#sizeunit) | Size unit | | `resolution` | `List[float]` | Resolution | | `resolution_unit` | [SizeUnit](../biodata_models/units.md#sizeunit) | Resolution unit | -| `origin` | [MouseAnatomyModel](../biodata_models/external.md#mouseanatomymodel) | Origin (Defines the position of (0,0,0) in the coordinate system) | +| `origin` | ONE OF: [Origin](../biodata_models/coordinates.md#origin), [AnatomyModel](../biodata_models/external.md#anatomymodel) | Origin (Defines the position of (0,0,0) in the coordinate system) | | `axes` | List[[Axis](#axis)] | Axis names (Axis names and directions) | | `axis_unit` | [SizeUnit](../biodata_models/units.md#sizeunit) | Size unit | | `handedness` | Optional[[Handedness](#handedness)] | Handedness (Whether the coordinate system is right-handed or left-handed) | @@ -57,7 +57,7 @@ Definition of a coordinate system | Field | Type | Title (Description) | |-------|------|-------------| | `name` | `str` | Name (Convention is to use _ etc) | -| `origin` | [MouseAnatomyModel](../biodata_models/external.md#mouseanatomymodel) | Origin (Defines the position of (0,0,0) in the coordinate system) | +| `origin` | ONE OF: [Origin](../biodata_models/coordinates.md#origin), [AnatomyModel](../biodata_models/external.md#anatomymodel) | Origin (Defines the position of (0,0,0) in the coordinate system) | | `axes` | List[[Axis](#axis)] | Axis names (Axis names and directions) | | `axis_unit` | [SizeUnit](../biodata_models/units.md#sizeunit) | Size unit | | `handedness` | Optional[[Handedness](#handedness)] | Handedness (Whether the coordinate system is right-handed or left-handed) | diff --git a/docs/source/components/devices.md b/docs/source/components/devices.md index 1e78ff30..78012d3a 100644 --- a/docs/source/components/devices.md +++ b/docs/source/components/devices.md @@ -676,9 +676,9 @@ Description of a contact on a myomatrix thread | Field | Type | Title (Description) | |-------|------|-------------| -| `body_part` | [MouseAnatomyModel](../biodata_models/external.md#mouseanatomymodel) | Body part of contact insertion (Use MouseBodyParts) | +| `body_part` | [AnatomyModel](../biodata_models/external.md#anatomymodel) | Body part of contact insertion (For common mouse body parts, use MouseBodyParts with MouseAnatomyLookup.get_by_name.) | | `relative_position` | [AnatomicalRelative](../biodata_models/coordinates.md#anatomicalrelative) | Relative position (Position relative to procedures coordinate system) | -| `muscle` | [MouseAnatomyModel](../biodata_models/external.md#mouseanatomymodel) | Muscle of contact insertion (Use MouseEmgMuscles) | +| `muscle` | [AnatomyModel](../biodata_models/external.md#anatomymodel) | Muscle of contact insertion (For common mouse EMG muscles, use MouseEmgMuscles with MouseAnatomyLookup.get_by_name.) | | `in_muscle` | `bool` | In muscle | @@ -688,7 +688,7 @@ Description of a thread of a myomatrix array | Field | Type | Title (Description) | |-------|------|-------------| -| `ground_electrode_location` | [MouseAnatomyModel](../biodata_models/external.md#mouseanatomymodel) | Location of ground electrode (Use GroundWireLocations) | +| `ground_electrode_location` | [AnatomyModel](../biodata_models/external.md#anatomymodel) | Location of ground electrode (For common mouse ground-wire locations, use MouseGroundWireLocations with MouseAnatomyLookup.get_by_name.) | | `contacts` | List[[MyomatrixContact](#myomatrixcontact)] | Contacts | diff --git a/docs/source/components/injection_procedures.md b/docs/source/components/injection_procedures.md index d4603ef4..90664a1c 100644 --- a/docs/source/components/injection_procedures.md +++ b/docs/source/components/injection_procedures.md @@ -4,12 +4,15 @@ ### Injection -Description of an injection procedure +Description of an injection procedure. + +For common mouse injection targets, use MouseInjectionTargets with +MouseAnatomyLookup.get_by_name(...). | Field | Type | Title (Description) | |-------|------|-------------| | `injection_materials` | List[[ViralMaterial](#viralmaterial) or [NonViralMaterial](#nonviralmaterial)] | Injection material | -| `targeted_structure` | Optional[[MouseAnatomyModel](../biodata_models/external.md#mouseanatomymodel)] | Injection target (Use InjectionTargets) | +| `targeted_structure` | Optional[[AnatomyModel](../biodata_models/external.md#anatomymodel)] | Injection target (Species-specific anatomy term; use the appropriate lookup (e.g., MouseAnatomyLookup or HumanAnatomyLookup).) | | `relative_position` | Optional[List[[AnatomicalRelative](../biodata_models/coordinates.md#anatomicalrelative)]] | Relative position | | `dynamics` | List[[InjectionDynamics](#injectiondynamics)] | Injection dynamics (List of injection events, one per location/depth) | | `protocol_id` | `Optional[str]` | Protocol ID (DOI for protocols.io) | diff --git a/docs/source/components/surgery_procedures.md b/docs/source/components/surgery_procedures.md index 329e7c5e..1d73a1ed 100644 --- a/docs/source/components/surgery_procedures.md +++ b/docs/source/components/surgery_procedures.md @@ -100,7 +100,7 @@ Ground wire implant procedure | Field | Type | Title (Description) | |-------|------|-------------| -| `ground_electrode_location` | [MouseAnatomyModel](../biodata_models/external.md#mouseanatomymodel) | Location of ground electrode | +| `ground_electrode_location` | [AnatomyModel](../biodata_models/external.md#anatomymodel) | Location of ground electrode (For common mouse ground-wire locations, use MouseGroundWireLocations with MouseAnatomyLookup.get_by_name.) | | `ground_wire_hole` | `Optional[int]` | Ground wire hole (For SHIELD implants, the hole number for the ground wire) | | `ground_wire_material` | Optional[[GroundWireMaterial](#groundwirematerial)] | Ground wire material | | `ground_wire_diameter` | `Optional[float]` | Ground wire diameter | diff --git a/docs/source/inheritance.md b/docs/source/inheritance.md index 89bcaf66..4414c404 100644 --- a/docs/source/inheritance.md +++ b/docs/source/inheritance.md @@ -33,7 +33,7 @@ For assets created during analysis that aggregate across multiple acquisitions o from datetime import datetime, timezone from biodata_schema.core.metadata import Metadata -from biodata_schema.core.processing import DataProcess, Processing, ProcessName, ProcessStage +from biodata_schema.core.processing import DataProcess, Processing, ProcessStage from biodata_schema.core.quality_control import QCMetric, QCStatus, QualityControl, Stage, Status from biodata_schema.components.identifiers import Code from biodata_models.modalities import Modality @@ -45,7 +45,7 @@ source = Metadata.model_validate_json(open("metadata.nd.json").read()) new_processing = Processing.create_with_sequential_process_graph( data_processes=[ DataProcess( - process_type=ProcessName.IMAGE_TILE_FUSING, + process_type="Image tile fusing", name="Tile fusing", experimenters=["Dr. Dan"], stage=ProcessStage.PROCESSING, diff --git a/docs/source/model.md b/docs/source/model.md index 7637f393..4fdf6bdd 100644 --- a/docs/source/model.md +++ b/docs/source/model.md @@ -35,7 +35,7 @@ Description of model evaluation | Field | Type | Title (Description) | |-------|------|-------------| -| `process_type` | [ProcessName](biodata_models/process_names.md#processname) | | +| `process_type` | `str` | | | `performance` | List[[PerformanceMetric](model.md#performancemetric)] | Evaluation performance | | `name` | `str` | Name (Unique name of the processing step. If not provided, the type will be used as the name.) | | `stage` | [ProcessStage](processing.md#processstage) | Processing stage | @@ -65,7 +65,7 @@ Description of model training | Field | Type | Title (Description) | |-------|------|-------------| -| `process_type` | [ProcessName](biodata_models/process_names.md#processname) | | +| `process_type` | `str` | | | `train_performance` | List[[PerformanceMetric](model.md#performancemetric)] | Training performance (Performance on training set) | | `test_performance` | Optional[List[[PerformanceMetric](model.md#performancemetric)]] | Test performance (Performance on test data, evaluated during training) | | `test_evaluation_method` | `Optional[str]` | Test evaluation method (Approach to cross-validation or Train/test splitting) | diff --git a/docs/source/processing.md b/docs/source/processing.md index 0901917f..ac9124af 100644 --- a/docs/source/processing.md +++ b/docs/source/processing.md @@ -35,7 +35,7 @@ Description of a single processing step | Field | Type | Title (Description) | |-------|------|-------------| -| `process_type` | [ProcessName](biodata_models/process_names.md#processname) | Process type | +| `process_type` | `str` | Process type | | `name` | `str` | Name (Unique name of the processing step. If not provided, the type will be used as the name.) | | `stage` | [ProcessStage](processing.md#processstage) | Processing stage | | `code` | [Code](components/identifiers.md#code) | Code (Code used for processing) | diff --git a/docs/source/registries.rst b/docs/source/registries.rst index 349bc24c..4b7a15de 100644 --- a/docs/source/registries.rst +++ b/docs/source/registries.rst @@ -19,7 +19,6 @@ Registries are models that are linked to an external definition, like the NCBI s biodata_models/modalities biodata_models/organizations biodata_models/pid_names - biodata_models/process_names biodata_models/reagent biodata_models/registries biodata_models/species diff --git a/examples/processing.py b/examples/processing.py index b480f9c2..a9997d5c 100644 --- a/examples/processing.py +++ b/examples/processing.py @@ -10,7 +10,6 @@ from biodata_schema.core.processing import ( DataProcess, Processing, - ProcessName, ProcessStage, ResourceTimestamped, ResourceUsage, @@ -62,7 +61,7 @@ ], data_processes=[ DataProcess( - process_type=ProcessName.IMAGE_TILE_FUSING, + process_type="Image tile fusing", experimenters=["Dr. Dan"], stage=ProcessStage.PROCESSING, start_date_time=t, @@ -90,7 +89,7 @@ ), ), DataProcess( - process_type=ProcessName.FILE_FORMAT_CONVERSION, + process_type="File format conversion", pipeline_name="Imaging processing pipeline", experimenters=["Dr. Dan"], stage=ProcessStage.PROCESSING, @@ -104,7 +103,7 @@ ), ), DataProcess( - process_type=ProcessName.IMAGE_DESTRIPING, + process_type="Image destriping", pipeline_name="Imaging processing pipeline", experimenters=["Dr. Dan"], stage=ProcessStage.PROCESSING, @@ -121,7 +120,7 @@ name="Analysis 1", stage=ProcessStage.ANALYSIS, experimenters=["Some Analyzer"], - process_type=ProcessName.ANALYSIS, + process_type="Analysis", start_date_time=t, end_date_time=t, output_path="path/to/outputs", @@ -135,7 +134,7 @@ name="Analysis 2", stage=ProcessStage.ANALYSIS, experimenters=["Some Analyzer"], - process_type=ProcessName.ANALYSIS, + process_type="Analysis", start_date_time=t, end_date_time=t, output_path="path/to/outputs", diff --git a/pyproject.toml b/pyproject.toml index 966b341f..5d045f24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ readme = "README.md" dynamic = ["version"] dependencies = [ - 'biodata-models>=1.0.1,<2', + 'biodata-models>=2.0.2,<3', 'pydantic>=2.7, <3', 'pydantic-extra-types', 'tzdata', @@ -30,7 +30,7 @@ dev = [ 'pytest>=8.3', 'pytest-cov>=6', 'ruff>=0.11', - 'semver', + 'semver>=3.0.0', ] docs = [ @@ -70,7 +70,6 @@ ignore-words-list = 'nd,DeviceC' testpaths = ["tests"] strict_config = true strict_markers = true -markers = ["online: tests requiring external services"] [tool.ruff] line-length = 120 diff --git a/schemas/metadata_schema.json b/schemas/metadata_schema.json index e6ff0551..63859aed 100644 --- a/schemas/metadata_schema.json +++ b/schemas/metadata_schema.json @@ -9102,8 +9102,8 @@ "type": "string" }, "process_type": { - "$ref": "#/$defs/ProcessName", - "title": "Process type" + "title": "Process type", + "type": "string" }, "name": { "default": "", @@ -26888,8 +26888,9 @@ "type": "string" }, "process_type": { - "$ref": "#/$defs/ProcessName", - "default": "Model evaluation" + "default": "Model evaluation", + "title": "Process Type", + "type": "string" }, "name": { "default": "", @@ -27047,8 +27048,9 @@ "type": "string" }, "process_type": { - "$ref": "#/$defs/ProcessName", - "default": "Model training" + "default": "Model training", + "title": "Process Type", + "type": "string" }, "name": { "default": "", @@ -37433,60 +37435,6 @@ "title": "Procedures", "type": "object" }, - "ProcessName": { - "description": "Process names", - "enum": [ - "Analysis", - "Compression", - "Denoising", - "Ephys curation", - "Ephys postprocessing", - "Ephys preprocessing", - "Ephys visualization", - "Fiducial segmentation", - "File format conversion", - "Fix color range", - "Fluorescence event detection", - "Image atlas alignment", - "Image background subtraction", - "Image cell classification", - "Image cell quantification", - "Image cell segmentation", - "Image cross-image alignment", - "Image destriping", - "Image flat-field correction", - "Image importing", - "Image mip visualization", - "Image multiscaling", - "Image radial correction", - "Image spot detection", - "Image spot spectral unmixing", - "Image thresholding", - "Image tile alignment", - "Image tile fusing", - "Image tile projection", - "Manual curation", - "Model evaluation", - "Model training", - "Neuron skeleton processing", - "Neuropil subtraction", - "Other", - "Pipeline", - "Simulation", - "Skull stripping", - "Spatial timeseries demixing", - "Spike sorting", - "Video ROI classification", - "Video ROI cross session matching", - "Video ROI segmentation", - "Video ROI timeseries extraction", - "Video motion correction", - "Video plane decrosstalk", - "dF/F estimation" - ], - "title": "ProcessName", - "type": "string" - }, "ProcessStage": { "description": "Stages of processing", "enum": [ diff --git a/schemas/model_schema.json b/schemas/model_schema.json index 1b9f3386..2f04b20b 100644 --- a/schemas/model_schema.json +++ b/schemas/model_schema.json @@ -362,8 +362,9 @@ "type": "string" }, "process_type": { - "$ref": "#/$defs/ProcessName", - "default": "Model evaluation" + "default": "Model evaluation", + "title": "Process Type", + "type": "string" }, "name": { "default": "", @@ -521,8 +522,9 @@ "type": "string" }, "process_type": { - "$ref": "#/$defs/ProcessName", - "default": "Model training" + "default": "Model training", + "title": "Process Type", + "type": "string" }, "name": { "default": "", @@ -702,60 +704,6 @@ "title": "PerformanceMetric", "type": "object" }, - "ProcessName": { - "description": "Process names", - "enum": [ - "Analysis", - "Compression", - "Denoising", - "Ephys curation", - "Ephys postprocessing", - "Ephys preprocessing", - "Ephys visualization", - "Fiducial segmentation", - "File format conversion", - "Fix color range", - "Fluorescence event detection", - "Image atlas alignment", - "Image background subtraction", - "Image cell classification", - "Image cell quantification", - "Image cell segmentation", - "Image cross-image alignment", - "Image destriping", - "Image flat-field correction", - "Image importing", - "Image mip visualization", - "Image multiscaling", - "Image radial correction", - "Image spot detection", - "Image spot spectral unmixing", - "Image thresholding", - "Image tile alignment", - "Image tile fusing", - "Image tile projection", - "Manual curation", - "Model evaluation", - "Model training", - "Neuron skeleton processing", - "Neuropil subtraction", - "Other", - "Pipeline", - "Simulation", - "Skull stripping", - "Spatial timeseries demixing", - "Spike sorting", - "Video ROI classification", - "Video ROI cross session matching", - "Video ROI segmentation", - "Video ROI timeseries extraction", - "Video motion correction", - "Video plane decrosstalk", - "dF/F estimation" - ], - "title": "ProcessName", - "type": "string" - }, "ProcessStage": { "description": "Stages of processing", "enum": [ diff --git a/schemas/processing_schema.json b/schemas/processing_schema.json index bdbb4da4..daea0ef1 100644 --- a/schemas/processing_schema.json +++ b/schemas/processing_schema.json @@ -324,8 +324,8 @@ "type": "string" }, "process_type": { - "$ref": "#/$defs/ProcessName", - "title": "Process type" + "title": "Process type", + "type": "string" }, "name": { "default": "", @@ -466,60 +466,6 @@ "title": "MemoryUnit", "type": "string" }, - "ProcessName": { - "description": "Process names", - "enum": [ - "Analysis", - "Compression", - "Denoising", - "Ephys curation", - "Ephys postprocessing", - "Ephys preprocessing", - "Ephys visualization", - "Fiducial segmentation", - "File format conversion", - "Fix color range", - "Fluorescence event detection", - "Image atlas alignment", - "Image background subtraction", - "Image cell classification", - "Image cell quantification", - "Image cell segmentation", - "Image cross-image alignment", - "Image destriping", - "Image flat-field correction", - "Image importing", - "Image mip visualization", - "Image multiscaling", - "Image radial correction", - "Image spot detection", - "Image spot spectral unmixing", - "Image thresholding", - "Image tile alignment", - "Image tile fusing", - "Image tile projection", - "Manual curation", - "Model evaluation", - "Model training", - "Neuron skeleton processing", - "Neuropil subtraction", - "Other", - "Pipeline", - "Simulation", - "Skull stripping", - "Spatial timeseries demixing", - "Spike sorting", - "Video ROI classification", - "Video ROI cross session matching", - "Video ROI segmentation", - "Video ROI timeseries extraction", - "Video motion correction", - "Video plane decrosstalk", - "dF/F estimation" - ], - "title": "ProcessName", - "type": "string" - }, "ProcessStage": { "description": "Stages of processing", "enum": [ diff --git a/src/biodata_schema/components/configs.py b/src/biodata_schema/components/configs.py index ef8b18c2..f91d65a2 100644 --- a/src/biodata_schema/components/configs.py +++ b/src/biodata_schema/components/configs.py @@ -4,10 +4,10 @@ from enum import Enum from typing import List, Optional +from biodata_models.anatomy import AnatomyModel from biodata_models.brain_atlas import BrainStructureModel from biodata_models.coordinates import AnatomicalRelative from biodata_models.devices import ImmersionMedium -from biodata_models.mouse_anatomy import MouseAnatomyModel from biodata_models.slap2_acquisition_type import Slap2AcquisitionType from biodata_models.units import ( AngleUnit, @@ -621,6 +621,8 @@ def validate_non_setup(self): class CatheterConfig(DeviceConfig): """Configuration of a catheter""" - targeted_structure: MouseAnatomyModel = Field( - ..., title="Targeted blood vessel", description="Use options from MouseBloodVessels" + targeted_structure: AnatomyModel = Field( + ..., + title="Targeted blood vessel", + description="For common mouse blood-vessel targets, use MouseBloodVessels with MouseAnatomyLookup.get_by_name.", ) diff --git a/src/biodata_schema/components/coordinates.py b/src/biodata_schema/components/coordinates.py index d4b2757d..67e6caea 100644 --- a/src/biodata_schema/components/coordinates.py +++ b/src/biodata_schema/components/coordinates.py @@ -3,9 +3,9 @@ from enum import Enum from typing import List, Optional +from biodata_models.anatomy import AnatomyModel from biodata_models.atlas import AtlasName from biodata_models.coordinates import AxisName, Direction, Origin -from biodata_models.mouse_anatomy import MouseAnatomyModel from biodata_models.units import AngleUnit, SizeUnit from pydantic import Field, field_validator @@ -134,7 +134,7 @@ class CoordinateSystem(DataModel): ..., title="Name", description="Convention is to use _ etc" ) - origin: Origin | MouseAnatomyModel = Field( + origin: Origin | AnatomyModel = Field( ..., title="Origin", description="Defines the position of (0,0,0) in the coordinate system" ) axes: List[Axis] = Field(..., title="Axis names", description="Axis names and directions") diff --git a/src/biodata_schema/components/devices.py b/src/biodata_schema/components/devices.py index 5debf02d..e826d10d 100644 --- a/src/biodata_schema/components/devices.py +++ b/src/biodata_schema/components/devices.py @@ -6,6 +6,7 @@ from enum import Enum from typing import List, Literal, Optional +from biodata_models.anatomy import AnatomyModel from biodata_models.coordinates import AnatomicalRelative from biodata_models.devices import ( BinMode, @@ -28,7 +29,6 @@ StageAxisDirection, ) from biodata_models.harp_types import HarpDeviceType -from biodata_models.mouse_anatomy import MouseAnatomyModel from biodata_models.organizations import Organization from biodata_models.units import ( FrequencyUnit, @@ -704,19 +704,29 @@ class Scanner(Device): class MyomatrixContact(DataModel): """Description of a contact on a myomatrix thread""" - body_part: MouseAnatomyModel = Field(..., title="Body part of contact insertion", description="Use MouseBodyParts") + body_part: AnatomyModel = Field( + ..., + title="Body part of contact insertion", + description="For common mouse body parts, use MouseBodyParts with MouseAnatomyLookup.get_by_name.", + ) relative_position: AnatomicalRelative = Field( ..., title="Relative position", description="Position relative to procedures coordinate system" ) - muscle: MouseAnatomyModel = Field(..., title="Muscle of contact insertion", description="Use MouseEmgMuscles") + muscle: AnatomyModel = Field( + ..., + title="Muscle of contact insertion", + description="For common mouse EMG muscles, use MouseEmgMuscles with MouseAnatomyLookup.get_by_name.", + ) in_muscle: bool = Field(..., title="In muscle") class MyomatrixThread(DataModel): """Description of a thread of a myomatrix array""" - ground_electrode_location: MouseAnatomyModel = Field( - ..., title="Location of ground electrode", description="Use GroundWireLocations" + ground_electrode_location: AnatomyModel = Field( + ..., + title="Location of ground electrode", + description="For common mouse ground-wire locations, use MouseGroundWireLocations with MouseAnatomyLookup.get_by_name.", ) contacts: List[MyomatrixContact] = Field(..., title="Contacts") diff --git a/src/biodata_schema/components/injection_procedures.py b/src/biodata_schema/components/injection_procedures.py index 083e14b0..11c82179 100644 --- a/src/biodata_schema/components/injection_procedures.py +++ b/src/biodata_schema/components/injection_procedures.py @@ -4,8 +4,8 @@ from enum import Enum from typing import List, Optional +from biodata_models.anatomy import AnatomyModel from biodata_models.coordinates import AnatomicalRelative -from biodata_models.mouse_anatomy import MouseAnatomyModel from biodata_models.pid_names import PIDName from biodata_models.units import CurrentUnit, TimeUnit, VolumeUnit from pydantic import Field, model_validator @@ -109,8 +109,10 @@ class Injection(ProtocolMixin, DataModel): injection_materials: DiscriminatedList[ViralMaterial | NonViralMaterial] = Field( ..., title="Injection material", min_length=1 ) - targeted_structure: Optional[MouseAnatomyModel] = Field( - default=None, title="Injection target", description="Use InjectionTargets" + targeted_structure: Optional[AnatomyModel] = Field( + default=None, + title="Injection target", + description="Species-specific anatomy term; use the appropriate lookup (e.g., MouseAnatomyLookup or HumanAnatomyLookup).", ) relative_position: Optional[List[AnatomicalRelative]] = Field(default=None, title="Relative position") diff --git a/src/biodata_schema/components/subjects.py b/src/biodata_schema/components/subjects.py index 749ca46a..6deac61a 100644 --- a/src/biodata_schema/components/subjects.py +++ b/src/biodata_schema/components/subjects.py @@ -5,6 +5,7 @@ from enum import Enum from typing import Annotated, List, Optional +from biodata_models.cell_line import CellLineModel from biodata_models.organizations import Organization from biodata_models.pid_names import PIDName from biodata_models.species import Species, Strain @@ -195,3 +196,18 @@ class CalibrationObject(DataModel): objects: Optional[list[Device]] = Field( default=None, title="Objects", description="For calibration objects that are built up from one or more devices." ) + + +class CellLine(DataModel): + """Description of a cultured cell line""" + + cell_line_name: str = Field(..., title="Cell line name") + cell_line_type: CellLineModel = Field( + ..., title="Cell line type", description="Use CellLineLookup.get_by_name/search_by_name to populate" + ) + species: Species.ONE_OF = Field(..., title="Species") + protein: PIDName = Field(..., title="Protein labeled", description="Protein uses UniProt registry") + gene: PIDName = Field(..., title="Gene targeted", description="Gene uses NCBI taxonomy") + cell_structure: str = Field(..., title="Cell structure protein found in") + fluorescent_protein: PIDName = Field(..., title="Fluorescent protein", description="Uses FPbase") + clone_number: Optional[int] = Field(default=None, title="Clone number") diff --git a/src/biodata_schema/components/surgery_procedures.py b/src/biodata_schema/components/surgery_procedures.py index 36d9fb0a..7f5d6666 100644 --- a/src/biodata_schema/components/surgery_procedures.py +++ b/src/biodata_schema/components/surgery_procedures.py @@ -3,9 +3,9 @@ from enum import Enum from typing import List, Optional, Union +from biodata_models.anatomy import AnatomyModel from biodata_models.brain_atlas import BrainStructureModel from biodata_models.coordinates import AnatomicalRelative -from biodata_models.mouse_anatomy import MouseAnatomyModel from biodata_models.organizations import Organization from biodata_models.units import SizeUnit, TimeUnit, VolumeUnit from pydantic import Field, field_validator, model_validator @@ -176,7 +176,15 @@ class Headframe(ProtocolMixin, DataModel): class GroundWireImplant(DataModel): """Ground wire implant procedure""" - ground_electrode_location: MouseAnatomyModel = Field(..., title="Location of ground electrode") + ground_electrode_location: AnatomyModel = Field( + ..., + title="Location of ground electrode", + description=( + "Species-specific anatomy term; use the appropriate lookup (e.g., MouseAnatomyLookup or " + "HumanAnatomyLookup). For common mouse ground-wire locations, use MouseGroundWireLocations and resolve " + "the target with MouseAnatomyLookup.get_by_name(...)." + ), + ) ground_wire_hole: Optional[int] = Field( default=None, title="Ground wire hole", description="For SHIELD implants, the hole number for the ground wire" ) diff --git a/src/biodata_schema/core/model.py b/src/biodata_schema/core/model.py index 3cbd6761..5c9ccea4 100644 --- a/src/biodata_schema/core/model.py +++ b/src/biodata_schema/core/model.py @@ -7,7 +7,7 @@ from biodata_schema.base import DataCoreModel, DataModel, DiscriminatedList, GenericModel from biodata_schema.components.identifiers import Code, Software -from biodata_schema.core.processing import DataProcess, ProcessName +from biodata_schema.core.processing import DataProcess class PerformanceMetric(DataModel): @@ -20,14 +20,14 @@ class PerformanceMetric(DataModel): class ModelEvaluation(DataProcess): """Description of model evaluation""" - process_type: ProcessName = ProcessName.MODEL_EVALUATION + process_type: str = "Model evaluation" performance: List[PerformanceMetric] = Field(..., title="Evaluation performance") class ModelTraining(DataProcess): """Description of model training""" - process_type: ProcessName = ProcessName.MODEL_TRAINING + process_type: str = "Model training" train_performance: List[PerformanceMetric] = Field( ..., title="Training performance", description="Performance on training set" ) diff --git a/src/biodata_schema/core/processing.py b/src/biodata_schema/core/processing.py index 337a03dd..55130e75 100644 --- a/src/biodata_schema/core/processing.py +++ b/src/biodata_schema/core/processing.py @@ -5,7 +5,6 @@ from enum import Enum from typing import Annotated, Dict, List, Literal, Optional -from biodata_models.process_names import ProcessName from biodata_models.units import MemoryUnit, UnitlessUnit from pydantic import Field, SkipValidation, model_validator @@ -52,7 +51,7 @@ class ResourceUsage(DataModel): class DataProcess(DataModel): """Description of a single processing step""" - process_type: ProcessName = Field(..., title="Process type") + process_type: str = Field(..., title="Process type") name: str = Field( default="", title="Name", @@ -75,16 +74,6 @@ class DataProcess(DataModel): notes: Optional[str] = Field(default=None, title="Notes", validate_default=True) resources: Optional[ResourceUsage] = Field(default=None, title="Process resource usage") - @model_validator(mode="after") - def validate_generic_processtype(self) -> "DataProcess": - """Validate to be sure OTHER types are pinned down in name or notes""" - - if self.process_type in [ProcessName.OTHER, ProcessName.ANALYSIS] and not (self.notes or self.name): - raise ValueError( - "If 'process_type' is Other or Analysis, either 'name' or 'notes' must specify process details." - ) - return self - @model_validator(mode="after") def fill_default_name(self) -> "DataProcess": """Fill in default name if not provided""" diff --git a/src/biodata_schema/core/subject.py b/src/biodata_schema/core/subject.py index 913ab53b..bba7927b 100644 --- a/src/biodata_schema/core/subject.py +++ b/src/biodata_schema/core/subject.py @@ -1,11 +1,17 @@ -"""schema for mostly mouse metadata""" +"""schema for subject metadata""" from typing import Annotated, Literal, Optional from pydantic import Field, SkipValidation from biodata_schema.base import DataCoreModel, Discriminated, DraftRequirement -from biodata_schema.components.subjects import CalibrationObject, HumanSubject, MouseSubject, NonHumanPrimateSubject +from biodata_schema.components.subjects import ( + CalibrationObject, + CellLine, + HumanSubject, + MouseSubject, + NonHumanPrimateSubject, +) class Subject(DataCoreModel): @@ -20,8 +26,8 @@ class Subject(DataCoreModel): title="Subject ID", ) - subject_details: Discriminated[MouseSubject | HumanSubject | NonHumanPrimateSubject | CalibrationObject] = Field( - ..., title="Subject Details" - ) + subject_details: Discriminated[ + MouseSubject | HumanSubject | NonHumanPrimateSubject | CellLine | CalibrationObject + ] = Field(..., title="Subject Details") notes: Optional[str] = Field(default=None, title="Notes") diff --git a/src/biodata_schema/utils/docs/model_generator.py b/src/biodata_schema/utils/docs/model_generator.py index 7f581023..68fbb2f2 100644 --- a/src/biodata_schema/utils/docs/model_generator.py +++ b/src/biodata_schema/utils/docs/model_generator.py @@ -24,8 +24,9 @@ "biodata_schema.core.quality_control.QCMetric": "{QCMetric} or {CurationMetric}", "biodata_schema.components.wrappers.AssetPath": "AssetPath", "biodata_schema.base._GenericModel": "dict", - "biodata_models.mouse_anatomy.MouseAnatomyModel": ( - "[MouseAnatomyModel](biodata_models/external.md#mouseanatomymodel)" + "biodata_models.anatomy.AnatomyModel": "[AnatomyModel](biodata_models/external.md#anatomymodel)", + "biodata_models.anatomy.MouseAnatomyLookup": ( + "[MouseAnatomyLookup](biodata_models/external.md#mouseanatomylookup)" ), "biodata_models.pid_names.PIDName": "{PIDName}", } diff --git a/src/biodata_schema/utils/docs/registries_generator.py b/src/biodata_schema/utils/docs/registries_generator.py index 2cde5745..8e58797a 100644 --- a/src/biodata_schema/utils/docs/registries_generator.py +++ b/src/biodata_schema/utils/docs/registries_generator.py @@ -45,9 +45,6 @@ from biodata_models.organizations import Organization from biodata_models.pid_names import PIDName -# Processing -from biodata_models.process_names import ProcessName - # Reagent models from biodata_models.reagent import FluorophoreType, StainType @@ -81,6 +78,7 @@ VolumeUnit, ) +# Processing from biodata_schema.utils.docs.utils import generate_enum_table, save_model_info, update_model_links # Special case classes that should be processed as model schemas even if they don't contain model instances @@ -104,8 +102,6 @@ AnatomicalRelative, # Organization models Organization, - # Process names - ProcessName, # Reagent StainType, FluorophoreType, diff --git a/src/biodata_schema/utils/schema_tree.py b/src/biodata_schema/utils/schema_tree.py index 0abe308e..86272fe9 100644 --- a/src/biodata_schema/utils/schema_tree.py +++ b/src/biodata_schema/utils/schema_tree.py @@ -250,6 +250,11 @@ def _doc_url(model_cls) -> str: everything else gets a page per module under its top-level package folder). """ module = model_cls.__module__ + if module == "biodata_models.anatomy" and model_cls.__name__ == "AnatomyModel": + return "biodata_models/external.html#anatomymodel" + if module == "biodata_models.anatomy" and model_cls.__name__ == "MouseAnatomyLookup": + return "biodata_models/external.html#mouseanatomylookup" + for prefix in ("biodata_schema.", "biodata_models."): prefix_len = len(prefix) if module.startswith(prefix): diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index c1807b75..00000000 --- a/tests/conftest.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Shared pytest configuration.""" - -import pytest - - -def pytest_addoption(parser): - """Register the opt-in flag for tests that require external services.""" - parser.addoption( - "--run-online", - action="store_true", - default=False, - help="run tests that require access to external services", - ) - - -def pytest_collection_modifyitems(config, items): - """Skip online tests unless explicitly requested.""" - if config.getoption("--run-online"): - return # pragma: no cover - - skip_online = pytest.mark.skip(reason="requires --run-online and external network access") - for item in items: - if "online" in item.keywords: - item.add_marker(skip_online) diff --git a/tests/test_composability_merge.py b/tests/test_composability_merge.py index c4dce40b..678b21cd 100644 --- a/tests/test_composability_merge.py +++ b/tests/test_composability_merge.py @@ -9,7 +9,7 @@ from biodata_schema.components.identifiers import Code from biodata_schema.core.acquisition import AcquisitionSubjectDetails from biodata_schema.core.procedures import Procedures -from biodata_schema.core.processing import DataProcess, Processing, ProcessName, ProcessStage +from biodata_schema.core.processing import DataProcess, Processing, ProcessStage from biodata_schema.core.quality_control import QCMetric, QCStatus, QualityControl, Stage, Status from examples.exaspim_acquisition import acq from examples.procedures import p, t, t2 @@ -263,7 +263,7 @@ def test_add_processing_objects(self): data_processes=[ DataProcess( experimenters=["Dr. Dan"], - process_type=ProcessName.DENOISING, + process_type="Denoising", stage=ProcessStage.PROCESSING, output_path="path/to/outputs1", start_date_time=t, @@ -281,7 +281,7 @@ def test_add_processing_objects(self): data_processes=[ DataProcess( experimenters=["Dr. Jane"], - process_type=ProcessName.COMPRESSION, + process_type="Compression", stage=ProcessStage.PROCESSING, output_path="path/to/outputs2", start_date_time=t, @@ -300,8 +300,8 @@ def test_add_processing_objects(self): # Check that the combined object has the correct data_processes and notes assert len(combined.data_processes) == 2 - assert combined.data_processes[0].name == ProcessName.DENOISING - assert combined.data_processes[1].name == ProcessName.COMPRESSION + assert combined.data_processes[0].name == "Denoising" + assert combined.data_processes[1].name == "Compression" assert "First processing object" in combined.notes assert "Second processing object" in combined.notes # check combined dependency graph @@ -342,7 +342,7 @@ def test_merge_dependency_graph(self): data_processes=[ DataProcess( experimenters=["Dr. Dan"], - process_type=ProcessName.DENOISING, + process_type="Denoising", stage=ProcessStage.PROCESSING, start_date_time=t, code=Code(url="https://example.com", version="1.0"), @@ -355,7 +355,7 @@ def test_merge_dependency_graph(self): data_processes=[ DataProcess( experimenters=["Dr. Jane"], - process_type=ProcessName.COMPRESSION, + process_type="Compression", stage=ProcessStage.PROCESSING, start_date_time=t, code=Code(url="https://example.com", version="1.0"), @@ -372,7 +372,7 @@ def test_merge_dependency_graph(self): data_processes=[ DataProcess( experimenters=["Dr. Dan"], - process_type=ProcessName.DENOISING, + process_type="Denoising", stage=ProcessStage.PROCESSING, start_date_time=t, code=Code(url="https://example.com", version="1.0"), @@ -384,7 +384,7 @@ def test_merge_dependency_graph(self): data_processes=[ DataProcess( experimenters=["Dr. Jane"], - process_type=ProcessName.COMPRESSION, + process_type="Compression", stage=ProcessStage.PROCESSING, start_date_time=t, code=Code(url="https://example.com", version="1.0"), diff --git a/tests/test_coordinates.py b/tests/test_coordinates.py index 79371eaf..e020e00f 100644 --- a/tests/test_coordinates.py +++ b/tests/test_coordinates.py @@ -142,17 +142,23 @@ def test_validate_atlas_valid(self): assert atlas is not None -class TestCoordinateSystemMouseAnatomyOrigin: - """Tests for CoordinateSystem with MouseAnatomyModel as origin""" - - @pytest.mark.online - def test_mouse_anatomy_origin(self): # pragma: no cover - """Test that CoordinateSystem accepts a MouseAnatomyModel as origin""" - from biodata_models.mouse_anatomy import MouseAnatomy +class TestCoordinateSystemAnatomyModelOrigin: + """Tests for CoordinateSystem with AnatomyModel as origin""" + + def test_anatomy_model_origin(self): + """Test that CoordinateSystem accepts an AnatomyModel as origin""" + from biodata_models.anatomy import AnatomyModel + from biodata_models.registries import Registry + + frontonasal_suture = AnatomyModel( + name="Frontonasal suture", + registry=Registry.EMAPA, + registry_identifier="EMAPA:TEST", + ) cs = CoordinateSystem( name="TEST_MOUSE_ANATOMY", - origin=MouseAnatomy.FRONTONASAL_SUTURE, + origin=frontonasal_suture, axis_unit=SizeUnit.MM, axes=[ Axis(name=AxisName.AP, direction=Direction.PA), @@ -160,7 +166,7 @@ def test_mouse_anatomy_origin(self): # pragma: no cover Axis(name=AxisName.SI, direction=Direction.SI), ], ) - assert cs.origin == MouseAnatomy.FRONTONASAL_SUTURE + assert cs.origin == frontonasal_suture cs_roundtrip = CoordinateSystem.model_validate(cs.model_dump()) - assert cs_roundtrip.origin == MouseAnatomy.FRONTONASAL_SUTURE + assert cs_roundtrip.origin == frontonasal_suture diff --git a/tests/test_device.py b/tests/test_device.py index 6075a318..46b5e3de 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -1,9 +1,19 @@ """test Device models""" +from unittest.mock import call, patch + import pytest +from biodata_models.anatomy import ( + AnatomyModel, + MouseAnatomyLookup, + MouseBodyParts, + MouseEmgMuscles, + MouseGroundWireLocations, +) from biodata_models.coordinates import AnatomicalRelative from biodata_models.harp_types import HarpDeviceType from biodata_models.organizations import Organization +from biodata_models.registries import Registry from biodata_models.units import UnitlessUnit from pydantic import ValidationError @@ -21,6 +31,8 @@ ImagingDeviceType, ImmersionMedium, Monitor, + MyomatrixContact, + MyomatrixThread, Objective, ) from tests.coordinate_systems import BREGMA_ARI @@ -29,6 +41,36 @@ class TestDevice: """tests device schemas""" + @patch("biodata_models.anatomy.MouseAnatomyLookup.get_by_name") + def test_myomatrix_anatomy_target_lookups(self, mock_get_by_name): + """Common mouse EMG, body-part, and ground-wire targets use the v2 enums.""" + mock_get_by_name.return_value = AnatomyModel( + name="test anatomy target", + registry=Registry.EMAPA, + registry_identifier="EMAPA:TEST", + ) + + contact = MyomatrixContact( + body_part=MouseAnatomyLookup.get_by_name(MouseBodyParts.FORELIMB), + relative_position=AnatomicalRelative.LEFT, + muscle=MouseAnatomyLookup.get_by_name(MouseEmgMuscles.DELTOID), + in_muscle=True, + ) + thread = MyomatrixThread( + ground_electrode_location=MouseAnatomyLookup.get_by_name(MouseGroundWireLocations.BRAIN), + contacts=[contact], + ) + + assert thread.contacts[0].muscle.name == "test anatomy target" + mock_get_by_name.assert_has_calls( + [ + call(MouseBodyParts.FORELIMB), + call(MouseEmgMuscles.DELTOID), + call(MouseGroundWireLocations.BRAIN), + ] + ) + assert mock_get_by_name.call_count == 3 + def test_other_validators(self): """tests validators which require notes when an instance of 'other' is used""" diff --git a/tests/test_imaging.py b/tests/test_imaging.py index b7043eea..8de4a2c9 100644 --- a/tests/test_imaging.py +++ b/tests/test_imaging.py @@ -13,7 +13,7 @@ from biodata_schema.components.identifiers import Code from biodata_schema.core.acquisition import Acquisition from biodata_schema.core.instrument import Instrument -from biodata_schema.core.processing import DataProcess, ProcessName, ProcessStage +from biodata_schema.core.processing import DataProcess, ProcessStage from examples.exaspim_acquisition import acq from tests.coordinate_systems import BREGMA_ARI @@ -109,7 +109,7 @@ def test_registration(self): ], } t = DataProcess( - process_type=ProcessName.IMAGE_TILE_ALIGNMENT, + process_type="Image tile alignment", stage=ProcessStage.PROCESSING, experimenters=["Dr. Dan"], start_date_time=datetime.now(tz=timezone.utc), diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py index 6cbc9795..d1792a36 100644 --- a/tests/test_inheritance.py +++ b/tests/test_inheritance.py @@ -11,7 +11,7 @@ from biodata_schema.components.identifiers import Code, Person from biodata_schema.core.data_description import DataDescription, Funding from biodata_schema.core.metadata import Metadata -from biodata_schema.core.processing import DataProcess, Processing, ProcessName, ProcessStage +from biodata_schema.core.processing import DataProcess, Processing, ProcessStage from biodata_schema.core.quality_control import QCMetric, QCStatus, QualityControl, Stage, Status from biodata_schema.core.subject import Subject from biodata_schema.utils.inheritance import ( @@ -89,7 +89,7 @@ def setup_method(self): self.new_processing = Processing.create_with_sequential_process_graph( data_processes=[ DataProcess( - process_type=ProcessName.ANALYSIS, + process_type="Analysis", name="Derived analysis", experimenters=["Dr. Test"], stage=ProcessStage.ANALYSIS, @@ -213,7 +213,7 @@ def test_different_acquisitions_does_not_accumulate_processing(self): new_proc = Processing.create_with_sequential_process_graph( data_processes=[ DataProcess( - process_type=ProcessName.ANALYSIS, + process_type="Analysis", name="New step", experimenters=["Dr. Test"], stage=ProcessStage.ANALYSIS, @@ -279,7 +279,7 @@ def test_different_subjects_drops_subject(self): new_proc = Processing.create_with_sequential_process_graph( data_processes=[ DataProcess( - process_type=ProcessName.ANALYSIS, + process_type="Analysis", name="New step", experimenters=["Dr. Test"], stage=ProcessStage.ANALYSIS, @@ -302,7 +302,7 @@ def test_different_subjects_drops_procedures(self): new_proc = Processing.create_with_sequential_process_graph( data_processes=[ DataProcess( - process_type=ProcessName.ANALYSIS, + process_type="Analysis", name="New step", experimenters=["Dr. Test"], stage=ProcessStage.ANALYSIS, diff --git a/tests/test_metadata.py b/tests/test_metadata.py index f25f05f6..85dc6d20 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -24,7 +24,7 @@ from biodata_schema.core.instrument import Instrument from biodata_schema.core.metadata import Metadata, create_metadata_json from biodata_schema.core.procedures import Procedures, Surgery -from biodata_schema.core.processing import DataProcess, Processing, ProcessName, ProcessStage +from biodata_schema.core.processing import DataProcess, Processing, ProcessStage from biodata_schema.core.subject import Subject from examples.aibs_smartspim_instrument import inst as spim_inst from examples.barseq_acquisition import acquisition as barseq_acquisition @@ -100,7 +100,7 @@ def setup_class(cls) -> None: DataProcess( experimenters=["Dr. Dan"], name="My Analysis", - process_type=ProcessName.ANALYSIS, + process_type="Analysis", stage=ProcessStage.ANALYSIS, output_path="path/to/outputs", start_date_time=t, @@ -853,7 +853,7 @@ def test_validate_time_constraints_processing(self): DataProcess( experimenters=["Dr. Dan"], name="My Analysis", - process_type=ProcessName.ANALYSIS, + process_type="Analysis", stage=ProcessStage.ANALYSIS, output_path="path/to/outputs", start_date_time=datetime(2023, 4, 3, 20, 0, 0, tzinfo=timezone.utc), # After acquisition @@ -881,7 +881,7 @@ def test_validate_time_constraints_processing(self): DataProcess( experimenters=["Dr. Dan"], name="My Analysis", - process_type=ProcessName.ANALYSIS, + process_type="Analysis", stage=ProcessStage.ANALYSIS, output_path="path/to/outputs", start_date_time=datetime(2023, 4, 3, 17, 0, 0, tzinfo=timezone.utc), # Before acquisition start diff --git a/tests/test_procedures.py b/tests/test_procedures.py index be232334..742aa030 100644 --- a/tests/test_procedures.py +++ b/tests/test_procedures.py @@ -1,13 +1,20 @@ """test Procedures""" from datetime import date -from unittest.mock import patch +from unittest.mock import call, patch import pytest +from biodata_models.anatomy import ( + AnatomyModel, + MouseAnatomyLookup, + MouseBloodVessels, + MouseGroundWireLocations, + MouseInjectionTargets, +) from biodata_models.brain_atlas import CCFv3 from biodata_models.coordinates import AnatomicalRelative -from biodata_models.mouse_anatomy import InjectionTargets, MouseBloodVessels from biodata_models.organizations import Organization +from biodata_models.registries import Registry from biodata_models.specimen_procedure_types import SpecimenProcedureType from biodata_models.units import ConcentrationUnit, CurrentUnit, SizeUnit, TimeUnit, VolumeUnit from pydantic import ValidationError @@ -32,12 +39,17 @@ SpecimenProcedure, ) from biodata_schema.components.subject_procedures import BrainInjection, Injection, Surgery -from biodata_schema.components.surgery_procedures import CatheterImplant, Craniotomy, CraniotomyType +from biodata_schema.components.surgery_procedures import CatheterImplant, Craniotomy, CraniotomyType, GroundWireImplant from biodata_schema.core.procedures import Procedures from biodata_schema.utils.exceptions import OneOfError from tests.coordinate_systems import BREGMA_ARI, BREGMA_RAS +def mouse_anatomy(name: str) -> AnatomyModel: + """Construct an offline anatomy model fixture.""" + return AnatomyModel(name=name, registry=Registry.EMAPA, registry_identifier="EMAPA:TEST") + + class TestProcedures: """test Procedures""" @@ -53,10 +65,8 @@ def test_required_field_validation_check(self): p = Procedures(subject_id="12345") assert "12345" == p.subject_id - @patch("biodata_models.mouse_anatomy.get_emapa_id") - def test_unwrapped_injection_rejected(self, mock_get_emapa_id): + def test_unwrapped_injection_rejected(self): """Unwrapped Injection in subject_procedures should raise""" - mock_get_emapa_id.return_value = "123456" with pytest.raises(ValidationError): Procedures( subject_id="12345", @@ -76,11 +86,10 @@ def test_unwrapped_injection_rejected(self, mock_get_emapa_id): ], ) - @patch("biodata_models.mouse_anatomy.get_emapa_id") - def test_injection_material_check(self, mock_get_emapa_id): + @patch("biodata_models.anatomy.MouseAnatomyLookup.get_by_name") + def test_injection_material_check(self, mock_get_by_name): """Check for validation error when injection_materials is empty""" - - mock_get_emapa_id.return_value = "123456" + mock_get_by_name.return_value = mouse_anatomy(MouseInjectionTargets.RETRO_ORBITAL.value) with pytest.raises(ValidationError) as e: Procedures( @@ -102,7 +111,7 @@ def test_injection_material_check(self, mock_get_emapa_id): profile=InjectionProfile.BOLUS, ) ], - targeted_structure=InjectionTargets.RETRO_ORBITAL, + targeted_structure=MouseAnatomyLookup.get_by_name(MouseInjectionTargets.RETRO_ORBITAL), relative_position=[AnatomicalRelative.LEFT], ), ], @@ -111,11 +120,12 @@ def test_injection_material_check(self, mock_get_emapa_id): ) assert "injection_materials" in repr(e.value) + mock_get_by_name.assert_called_once_with(MouseInjectionTargets.RETRO_ORBITAL) - @patch("biodata_models.mouse_anatomy.get_emapa_id") - def test_injection_material_none(self, mock_get_emapa_id): + @patch("biodata_models.anatomy.MouseAnatomyLookup.get_by_name") + def test_injection_material_none(self, mock_get_by_name): """Check for validation error when injection_materials is None""" - mock_get_emapa_id.return_value = "123456" + mock_get_by_name.return_value = mouse_anatomy(MouseInjectionTargets.RETRO_ORBITAL.value) with pytest.raises(ValidationError) as e: Procedures( subject_id="12345", @@ -136,7 +146,7 @@ def test_injection_material_none(self, mock_get_emapa_id): profile=InjectionProfile.BOLUS, ) ], - targeted_structure=InjectionTargets.RETRO_ORBITAL, + targeted_structure=MouseAnatomyLookup.get_by_name(MouseInjectionTargets.RETRO_ORBITAL), relative_position=[AnatomicalRelative.LEFT], ), ], @@ -145,11 +155,16 @@ def test_injection_material_none(self, mock_get_emapa_id): ) assert "injection_materials" in repr(e.value) + mock_get_by_name.assert_called_once_with(MouseInjectionTargets.RETRO_ORBITAL) - @patch("biodata_models.mouse_anatomy.get_emapa_id") - def test_injection_materials_list(self, mock_get_emapa_id): + @patch("biodata_models.anatomy.MouseAnatomyLookup.get_by_name") + def test_injection_materials_list(self, mock_get_by_name): """Valid injection_materials list""" - mock_get_emapa_id.return_value = "123456" + mock_get_by_name.side_effect = lambda target_name: AnatomyModel( + name=target_name.value, + registry=Registry.EMAPA, + registry_identifier="EMAPA:TEST", + ) p = Procedures( subject_id="12345", @@ -183,7 +198,7 @@ def test_injection_materials_list(self, mock_get_emapa_id): titer=2300000000, ) ], - targeted_structure=InjectionTargets.RETRO_ORBITAL, + targeted_structure=MouseAnatomyLookup.get_by_name(MouseInjectionTargets.RETRO_ORBITAL), relative_position=[AnatomicalRelative.LEFT], dynamics=[ InjectionDynamics( @@ -206,7 +221,7 @@ def test_injection_materials_list(self, mock_get_emapa_id): concentration_unit=ConcentrationUnit.UM, ) ], - targeted_structure=InjectionTargets.INTRAPERITONEAL, + targeted_structure=MouseAnatomyLookup.get_by_name(MouseInjectionTargets.INTRAPERITONEAL), dynamics=[ InjectionDynamics( volume=1, @@ -256,6 +271,11 @@ def test_injection_materials_list(self, mock_get_emapa_id): ], ) + mock_get_by_name.assert_has_calls( + [call(MouseInjectionTargets.RETRO_ORBITAL), call(MouseInjectionTargets.INTRAPERITONEAL)] + ) + assert mock_get_by_name.call_count == 2 + assert 1 == len(p.subject_procedures) assert p == Procedures.model_validate_json(p.model_dump_json()) @@ -723,9 +743,10 @@ def test_get_device_names_with_constructed_surgery_procedure(self): assert procedures.get_device_names() == ["Catheter"] - @pytest.mark.online - def test_get_device_names_with_surgery_procedures(self): # pragma: no cover + @patch("biodata_models.anatomy.MouseAnatomyLookup.get_by_name") + def test_get_device_names_with_surgery_procedures(self, mock_get_by_name): """Test get_device_names method with nested surgery procedures""" + mock_get_by_name.return_value = mouse_anatomy(MouseBloodVessels.CAROTID_ARTERY.value) device1 = Catheter( name="Catheter", @@ -736,7 +757,7 @@ def test_get_device_names_with_surgery_procedures(self): # pragma: no cover config = CatheterConfig( device_name="Catheter", - targeted_structure=MouseBloodVessels.CAROTID_ARTERY, + targeted_structure=MouseAnatomyLookup.get_by_name(MouseBloodVessels.CAROTID_ARTERY), ) # Test with surgery containing procedures with implanted devices @@ -759,6 +780,32 @@ def test_get_device_names_with_surgery_procedures(self): # pragma: no cover device_names = procedures.get_device_names() assert "Catheter" in device_names assert len(device_names) == 1 + mock_get_by_name.assert_called_once_with(MouseBloodVessels.CAROTID_ARTERY) + + @patch("biodata_models.anatomy.MouseAnatomyLookup.get_by_name") + def test_catheter_blood_vessel_target_lookup(self, mock_get_by_name): + """Common catheter blood-vessel targets can use the v2 lookup enum.""" + mock_get_by_name.return_value = mouse_anatomy(MouseBloodVessels.CAROTID_ARTERY.value) + + config = CatheterConfig( + device_name="Catheter", + targeted_structure=MouseAnatomyLookup.get_by_name(MouseBloodVessels.CAROTID_ARTERY), + ) + + assert config.targeted_structure.name == MouseBloodVessels.CAROTID_ARTERY.value + mock_get_by_name.assert_called_once_with(MouseBloodVessels.CAROTID_ARTERY) + + @patch("biodata_models.anatomy.MouseAnatomyLookup.get_by_name") + def test_ground_wire_location_lookup(self, mock_get_by_name): + """Common ground-wire locations can use the v2 lookup enum.""" + mock_get_by_name.return_value = mouse_anatomy(MouseGroundWireLocations.BRAIN.value) + + implant = GroundWireImplant( + ground_electrode_location=MouseAnatomyLookup.get_by_name(MouseGroundWireLocations.BRAIN), + ) + + assert implant.ground_electrode_location.name == MouseGroundWireLocations.BRAIN.value + mock_get_by_name.assert_called_once_with(MouseGroundWireLocations.BRAIN) def test_procedures_addition_coordinate_system_validation(self): """Test that Procedures addition raises error for different coordinate systems""" diff --git a/tests/test_processing.py b/tests/test_processing.py index fe12f9f4..411aca21 100644 --- a/tests/test_processing.py +++ b/tests/test_processing.py @@ -11,7 +11,6 @@ from biodata_schema.core.processing import ( DataProcess, Processing, - ProcessName, ProcessStage, ResourceTimestamped, ResourceUsage, @@ -42,7 +41,7 @@ def test_constructors(self): data_processes=[ DataProcess( experimenters=["Dr. Dan"], - process_type=ProcessName.DENOISING, + process_type="Denoising", stage=ProcessStage.PROCESSING, code=code, output_path="./path/to/outputs", @@ -53,7 +52,7 @@ def test_constructors(self): ) assert p is not None - assert p.data_processes[0].name == ProcessName.DENOISING + assert p.data_processes[0].name == "Denoising" def test_resource_usage(self): """Test the ResourceUsage class""" @@ -118,7 +117,7 @@ def test_unique_process_names(self): data_processes=[ DataProcess( experimenters=["Dr. Dan"], - process_type=ProcessName.DENOISING, + process_type="Denoising", stage=ProcessStage.PROCESSING, start_date_time=t, end_date_time=t, @@ -126,7 +125,7 @@ def test_unique_process_names(self): ), DataProcess( experimenters=["Dr. Dan"], - process_type=ProcessName.DENOISING, + process_type="Denoising", stage=ProcessStage.PROCESSING, start_date_time=t, end_date_time=t, @@ -145,7 +144,7 @@ def test_validate_data_processes(self): DataProcess( experimenters=["Dr. Dan"], name="My Analysis", - process_type=ProcessName.ANALYSIS, + process_type="Analysis", stage=ProcessStage.ANALYSIS, output_path="./path/to/outputs", start_date_time=t, @@ -164,7 +163,7 @@ def test_validate_data_processes(self): DataProcess( experimenters=["Dr. Dan"], name="My Analysis", - process_type=ProcessName.ANALYSIS, + process_type="Analysis", stage=ProcessStage.ANALYSIS, output_path="./path/to/outputs", start_date_time=t, @@ -181,7 +180,7 @@ def test_rename_process(self): process1 = DataProcess( name="process1", experimenters=["Dr. Dan"], - process_type=ProcessName.COMPRESSION, + process_type="Compression", stage=ProcessStage.PROCESSING, code=code, start_date_time=t, @@ -190,7 +189,7 @@ def test_rename_process(self): process2 = DataProcess( name="process2", experimenters=["Dr. Dan"], - process_type=ProcessName.ANALYSIS, + process_type="Analysis", stage=ProcessStage.ANALYSIS, code=code, start_date_time=t, @@ -199,7 +198,7 @@ def test_rename_process(self): process3 = DataProcess( name="process3", experimenters=["Dr. Dan"], - process_type=ProcessName.SPIKE_SORTING, + process_type="Spike sorting", stage=ProcessStage.PROCESSING, code=code, start_date_time=t, @@ -242,7 +241,7 @@ def test_validate_process_graph(self): process1 = DataProcess( name="process1", experimenters=["Dr. Dan"], - process_type=ProcessName.COMPRESSION, + process_type="Compression", stage=ProcessStage.PROCESSING, code=code, start_date_time=t, @@ -251,7 +250,7 @@ def test_validate_process_graph(self): process2 = DataProcess( name="process2", experimenters=["Dr. Dan"], - process_type=ProcessName.ANALYSIS, + process_type="Analysis", stage=ProcessStage.ANALYSIS, code=code, start_date_time=t, @@ -271,7 +270,7 @@ def test_validate_process_graph(self): process3 = DataProcess( name="process3", experimenters=["Dr. Dan"], - process_type=ProcessName.SPIKE_SORTING, + process_type="Spike sorting", stage=ProcessStage.PROCESSING, code=code, start_date_time=t, @@ -300,7 +299,7 @@ def test_dependency_graph_none(self): DataProcess( start_date_time=datetime(2024, 10, 10, 1, 2, 3), end_date_time=datetime(2024, 10, 11, 1, 2, 3), - process_type=ProcessName.COMPRESSION, + process_type="Compression", experimenters=["AIND Scientific Computing"], stage=ProcessStage.PROCESSING, code=Code( @@ -311,7 +310,7 @@ def test_dependency_graph_none(self): DataProcess( start_date_time=datetime(2024, 10, 10, 1, 2, 3), end_date_time=datetime(2024, 10, 11, 1, 2, 4), - process_type=ProcessName.OTHER, + process_type="Other", experimenters=["AIND Scientific Computing"], stage=ProcessStage.PROCESSING, code=Code(url="", version="0.0.1"), @@ -334,7 +333,7 @@ def test_validate_pipeline_names(self): process1 = DataProcess( name="process1", experimenters=["Dr. Dan"], - process_type=ProcessName.COMPRESSION, + process_type="Compression", stage=ProcessStage.PROCESSING, code=code, start_date_time=t, @@ -344,7 +343,7 @@ def test_validate_pipeline_names(self): process2 = DataProcess( name="process2", experimenters=["Dr. Dan"], - process_type=ProcessName.ANALYSIS, + process_type="Analysis", stage=ProcessStage.ANALYSIS, code=code, start_date_time=t, @@ -364,7 +363,7 @@ def test_validate_pipeline_names(self): process3 = DataProcess( name="process3", experimenters=["Dr. Dan"], - process_type=ProcessName.SPIKE_SORTING, + process_type="Spike sorting", stage=ProcessStage.PROCESSING, code=code, start_date_time=t, @@ -391,7 +390,7 @@ def test_order_processes(self): process1 = DataProcess( name="process1", experimenters=["Dr. Dan"], - process_type=ProcessName.COMPRESSION, + process_type="Compression", stage=ProcessStage.PROCESSING, code=code, start_date_time=t1, @@ -400,7 +399,7 @@ def test_order_processes(self): process2 = DataProcess( name="process2", experimenters=["Dr. Dan"], - process_type=ProcessName.ANALYSIS, + process_type="Analysis", stage=ProcessStage.ANALYSIS, code=code, start_date_time=t2, @@ -409,7 +408,7 @@ def test_order_processes(self): process3 = DataProcess( name="process3", experimenters=["Dr. Dan"], - process_type=ProcessName.SPIKE_SORTING, + process_type="Spike sorting", stage=ProcessStage.PROCESSING, code=code, start_date_time=t3, @@ -433,7 +432,7 @@ def test_order_processes(self): process4 = DataProcess( name="process4", experimenters=["Dr. Dan"], - process_type=ProcessName.COMPRESSION, + process_type="Compression", stage=ProcessStage.PROCESSING, code=code, start_date_time=t1, @@ -442,7 +441,7 @@ def test_order_processes(self): process5 = DataProcess( name="process5", experimenters=["Dr. Dan"], - process_type=ProcessName.ANALYSIS, + process_type="Analysis", stage=ProcessStage.ANALYSIS, code=code, start_date_time=t3, @@ -471,64 +470,22 @@ def test_order_processes(self): assert p4.notes is None -class TestDataProcessValidateOther: - """Tests for DataProcess.validate_other""" +class TestProcessingProcessTypeAcceptsArbitraryStrings: + """Tests for Processing types are open-ended strings, not a fixed enum.""" - def _make(self, process_type, **kwargs): - """Helper method to create a DataProcess with default values and override with kwargs""" - return DataProcess( - process_type=process_type, - stage=ProcessStage.PROCESSING, + def test_process_type_accepts_arbitrary_strings(self): + """Processing types are open-ended strings, not a fixed enum.""" + process = DataProcess( experimenters=["Dr. Dan"], + process_type="Cell-level tracking", + stage=ProcessStage.PROCESSING, code=code, start_date_time=t, - **kwargs, + end_date_time=t, ) + process_type_schema = DataProcess.model_json_schema()["properties"]["process_type"] - # --- ProcessName.OTHER --- - - def test_other_with_name_passes(self): - """OTHER is allowed when a custom name is provided""" - dp = self._make(ProcessName.OTHER, name="my custom step") - assert dp.process_type == ProcessName.OTHER - - def test_other_with_notes_passes(self): - """OTHER is allowed when notes describe the process""" - dp = self._make(ProcessName.OTHER, notes="some detail") - assert dp.process_type == ProcessName.OTHER - - def test_other_with_name_and_notes_passes(self): - """OTHER is allowed when both name and notes are provided""" - dp = self._make(ProcessName.OTHER, name="step", notes="detail") - assert dp.process_type == ProcessName.OTHER - - def test_other_without_name_or_notes_fails(self): - """OTHER without name or notes should raise a ValidationError""" - with pytest.raises(pydantic.ValidationError) as ctx: - self._make(ProcessName.OTHER) - assert "name' or 'notes' must specify process details" in str(ctx.value) - - # --- ProcessName.ANALYSIS --- - - def test_analysis_with_name_passes(self): - """ANALYSIS is allowed when a custom name is provided""" - dp = self._make(ProcessName.ANALYSIS, name="my analysis") - assert dp.process_type == ProcessName.ANALYSIS - - def test_analysis_with_notes_passes(self): - """ANALYSIS is allowed when notes are provided""" - dp = self._make(ProcessName.ANALYSIS, notes="analysis detail") - assert dp.process_type == ProcessName.ANALYSIS - - def test_analysis_without_name_or_notes_fails(self): - """ANALYSIS without name or notes should raise a ValidationError""" - with pytest.raises(pydantic.ValidationError) as ctx: - self._make(ProcessName.ANALYSIS) - assert "name' or 'notes' must specify process details" in str(ctx.value) - - # --- Other process types are not affected --- - - def test_compression_without_name_or_notes_passes(self): - """Non-OTHER/ANALYSIS types do not require name or notes""" - dp = self._make(ProcessName.COMPRESSION) - assert dp.process_type == ProcessName.COMPRESSION + assert process.process_type == "Cell-level tracking" + assert process.name == "Cell-level tracking" + assert process_type_schema["type"] == "string" + assert "enum" not in process_type_schema diff --git a/tests/test_subjects.py b/tests/test_subjects.py index d21db829..7be3a8b9 100644 --- a/tests/test_subjects.py +++ b/tests/test_subjects.py @@ -3,6 +3,7 @@ from datetime import datetime import pytest +from biodata_models.cell_line import CellLineModel from biodata_models.organizations import Organization from biodata_models.pid_names import PIDName from biodata_models.registries import Registry @@ -11,6 +12,7 @@ from biodata_schema.components.subjects import ( BreedingInfo, CalibrationObject, + CellLine, Housing, HumanSubject, LightCycle, @@ -230,3 +232,42 @@ def test_breeding_info(self): assert breeding_info.maternal_genotype == "wt/wt" assert breeding_info.paternal_id == "P001" assert breeding_info.paternal_genotype == "wt/wt" + + +class TestCellLine: + """Test the Cell Line model""" + + def test_cellline(self): + """Test of cell line""" + + subject = CellLine( + cell_line_name="AICS-0005", + cell_line_type=CellLineModel( + name="Human induced pluripotent stem cell line cell", + registry=Registry.CLO, + registry_identifier="CLO:0037308", + ), + species=Species.HUMAN, + protein=PIDName( + name="paxilin", + registry=Registry.UNIPROT, + registry_identifier="P49023", + ), + gene=PIDName( + name="paxilin", + abbreviation="PXN", + registry=Registry.NCBI, + registry_identifier="NM_0028593", + ), + cell_structure="cytoskeleton", + fluorescent_protein=PIDName( + name="enhanced GFP", + abbreviation="EGFP", + registry=Registry.FPbase, + registry_identifier="R9NL8", + ), + clone_number=1, + ) + + assert subject.cell_structure == "cytoskeleton" + assert subject.species == Species.HUMAN diff --git a/uv.lock b/uv.lock index 51522912..70678656 100644 --- a/uv.lock +++ b/uv.lock @@ -94,16 +94,16 @@ wheels = [ [[package]] name = "biodata-models" -version = "1.0.1" +version = "2.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-resources" }, { name = "pydantic" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/06/04cb073d3a7b3aace223c559e2f964e515ce644a074a09cf9ebb0f3b7b07/biodata_models-1.0.1.tar.gz", hash = "sha256:d057dc3d3c154c9330771ea7cc9e4a408f90fa45d2c32f648f4c43cf06b9199b", size = 386193, upload-time = "2026-09-11T17:53:41.683Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/4a/e31189ed13493fcaf98ce69980005637182a64e0d12c6735bb68367564a8/biodata_models-2.0.2.tar.gz", hash = "sha256:babdf2da0552e23ae702e08584ceec28b74b1396335ba53feb0df5112a41e8b6", size = 146594, upload-time = "2026-10-02T22:14:42.952Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/ee/5f8560cc41e7e584d3ffba2ff3fa7fa30470c61d80534fe7d6f0bb53a19a/biodata_models-1.0.1-py3-none-any.whl", hash = "sha256:7b0d25ca936f4ebece75d5ab8b14c2418aa6501448ff76c52b4b123c75f613b0", size = 359546, upload-time = "2026-09-11T17:53:40.129Z" }, + { url = "https://files.pythonhosted.org/packages/7f/18/ea6e82474610af0804df44f1c4971562f4fea595498d8c0c5761f487db40/biodata_models-2.0.2-py3-none-any.whl", hash = "sha256:d38fc817ed6e3b361f6279a65c8c5bcde136bd20b46848366f166fe2efba95ce", size = 113259, upload-time = "2026-10-02T22:14:41.262Z" }, ] [[package]] @@ -142,7 +142,7 @@ docs = [ [package.metadata] requires-dist = [ - { name = "biodata-models", specifier = ">=1.0.1,<2" }, + { name = "biodata-models", specifier = ">=2.0.2,<3" }, { name = "pydantic", specifier = ">=2.7,<3" }, { name = "pydantic-extra-types" }, { name = "tzdata" }, @@ -158,7 +158,7 @@ dev = [ { name = "pytest", specifier = ">=8.3" }, { name = "pytest-cov", specifier = ">=6" }, { name = "ruff", specifier = ">=0.11" }, - { name = "semver" }, + { name = "semver", specifier = ">=3.0.0" }, ] docs = [ { name = "autodoc-pydantic" },