From 793097b901938af0d0a92ea4d1d7a051d47f44a5 Mon Sep 17 00:00:00 2001 From: Saskia de Vries Date: Thu, 17 Sep 2026 20:53:56 -0700 Subject: [PATCH 1/9] adding cell line to subjects --- src/biodata_schema/components/subjects.py | 13 +++++++++++++ src/biodata_schema/core/subject.py | 11 ++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/biodata_schema/components/subjects.py b/src/biodata_schema/components/subjects.py index 749ca46a..9fd3b4fe 100644 --- a/src/biodata_schema/components/subjects.py +++ b/src/biodata_schema/components/subjects.py @@ -185,6 +185,19 @@ def validate_date_year_consistency(self): return self +class CellLine(DataModel): + """Description of a cultured cell line""" + + cell_line_name: str = Field(..., title="Cell line name") + cell_line_type: PIDName = Field(..., title="Cell line type", description="Uses Cell Line Ontology") + 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") # TODO: ontology or enum in model? + fluorescent_protein: PIDName = Field(..., title="Fluorescent protein", desciption="Uses FPbase") + clone_number: Optional[int] = Field(default=None, title="Clone number") + + class CalibrationObject(DataModel): """Description of a calibration object""" diff --git a/src/biodata_schema/core/subject.py b/src/biodata_schema/core/subject.py index 028f36ce..10075a8e 100644 --- a/src/biodata_schema/core/subject.py +++ b/src/biodata_schema/core/subject.py @@ -1,11 +1,11 @@ -"""schema for mostly mouse metadata""" +"""schema for subject metadata""" from typing import Literal, Optional from pydantic import Field, SkipValidation from biodata_schema.base import DataCoreModel, Discriminated -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,7 +20,12 @@ class Subject(DataCoreModel): title="Subject ID", ) - subject_details: Discriminated[MouseSubject | HumanSubject | NonHumanPrimateSubject | CalibrationObject] = Field( + subject_details: Discriminated[MouseSubject | + HumanSubject | + NonHumanPrimateSubject | + CalibrationObject | + CellLine + ] = Field( ..., title="Subject Details" ) From 784270e7663e9c0eb2fea38882f7426cad2c43fa Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 03:55:29 +0000 Subject: [PATCH 2/9] update docs --- docs/source/biodata_models/organizations.md | 124 ++++++++++---------- docs/source/biodata_models/species.md | 34 +++--- docs/source/components/devices.md | 2 +- docs/source/components/subjects.md | 16 +++ docs/source/subject.md | 2 +- 5 files changed, 97 insertions(+), 81 deletions(-) diff --git a/docs/source/biodata_models/organizations.md b/docs/source/biodata_models/organizations.md index 6f1e1d1f..da8c6c2e 100644 --- a/docs/source/biodata_models/organizations.md +++ b/docs/source/biodata_models/organizations.md @@ -9,130 +9,130 @@ Organization | Name | name | abbreviation | registry | registry_identifier | |------|------|------|------|------| | `AA_OPTO_ELECTRONIC` | `AA Opto Electronic` | `None` | `None` | `None` | -| `ABCAM` | `Abcam` | `None` | `Registry.ROR` | `02e1wjw63` | -| `ADDGENE` | `Addgene` | `None` | `Registry.ROR` | `01nn1pw54` | -| `AI` | `Allen Institute` | `AI` | `Registry.ROR` | `03cpe7c52` | -| `AIBS` | `Allen Institute for Brain Science` | `AIBS` | `Registry.ROR` | `00dcv1019` | +| `ABCAM` | `Abcam` | `None` | `Research Organization Registry (ROR)` | `02e1wjw63` | +| `ADDGENE` | `Addgene` | `None` | `Research Organization Registry (ROR)` | `01nn1pw54` | +| `AI` | `Allen Institute` | `AI` | `Research Organization Registry (ROR)` | `03cpe7c52` | +| `AIBS` | `Allen Institute for Brain Science` | `AIBS` | `Research Organization Registry (ROR)` | `00dcv1019` | | `AILIPU` | `Ailipu Technology Co` | `Ailipu` | `None` | `None` | -| `AIND` | `Allen Institute for Neural Dynamics` | `AIND` | `Registry.ROR` | `04szwah67` | -| `AMS_OSRAM` | `ams OSRAM` | `None` | `Registry.ROR` | `045d0h266` | +| `AIND` | `Allen Institute for Neural Dynamics` | `AIND` | `Research Organization Registry (ROR)` | `04szwah67` | +| `AMS_OSRAM` | `ams OSRAM` | `None` | `Research Organization Registry (ROR)` | `045d0h266` | | `ANTIBODIES_INC` | `Antibodies Inc` | `None` | `None` | `None` | | `ARDUINO` | `Arduino` | `None` | `None` | `None` | | `ARECONT_VISION_COSTAR` | `Arecont Vision Costar` | `None` | `None` | `None` | | `ASI` | `Applied Scientific Instrumentation` | `ASI` | `None` | `None` | -| `ASUS` | `ASUS` | `None` | `Registry.ROR` | `00bxkz165` | +| `ASUS` | `ASUS` | `None` | `Research Organization Registry (ROR)` | `00bxkz165` | | `BASLER` | `Basler` | `None` | `None` | `None` | -| `BCM` | `Baylor College of Medicine` | `BCM` | `Registry.ROR` | `02pttbw34` | -| `BROADCOM` | `Broadcom` | `None` | `Registry.ROR` | `035gt5s03` | -| `BRUKER` | `Bruker` | `None` | `Registry.ROR` | `04r739x86` | -| `BU` | `Boston University` | `BU` | `Registry.ROR` | `05qwgg493` | -| `CAJAL` | `Cajal Neuroscience` | `Cajal` | `Registry.ROR` | `05pdc0q70` | +| `BCM` | `Baylor College of Medicine` | `BCM` | `Research Organization Registry (ROR)` | `02pttbw34` | +| `BROADCOM` | `Broadcom` | `None` | `Research Organization Registry (ROR)` | `035gt5s03` | +| `BRUKER` | `Bruker` | `None` | `Research Organization Registry (ROR)` | `04r739x86` | +| `BU` | `Boston University` | `BU` | `Research Organization Registry (ROR)` | `05qwgg493` | +| `CAJAL` | `Cajal Neuroscience` | `Cajal` | `Research Organization Registry (ROR)` | `05pdc0q70` | | `CAMBRIDGE_TECHNOLOGY` | `Cambridge Technology` | `None` | `None` | `None` | -| `CARL_ZEISS` | `Carl Zeiss` | `None` | `Registry.ROR` | `01xk5xs43` | -| `CHAMPALIMAUD` | `Champalimaud Foundation` | `Champalimaud` | `Registry.ROR` | `03g001n57` | +| `CARL_ZEISS` | `Carl Zeiss` | `None` | `Research Organization Registry (ROR)` | `01xk5xs43` | +| `CHAMPALIMAUD` | `Champalimaud Foundation` | `Champalimaud` | `Research Organization Registry (ROR)` | `03g001n57` | | `CHROMA` | `Chroma` | `None` | `None` | `None` | -| `COHERENT_SCIENTIFIC` | `Coherent Scientific` | `None` | `Registry.ROR` | `031tysd23` | -| `COLUMBIA` | `Columbia University` | `Columbia` | `Registry.ROR` | `00hj8s172` | +| `COHERENT_SCIENTIFIC` | `Coherent Scientific` | `None` | `Research Organization Registry (ROR)` | `031tysd23` | +| `COLUMBIA` | `Columbia University` | `Columbia` | `Research Organization Registry (ROR)` | `00hj8s172` | | `COMPUTAR` | `Computar` | `None` | `None` | `None` | | `CONOPTICS` | `Conoptics` | `None` | `None` | `None` | | `CRESTOPTICS` | `CrestOptics` | `None` | `None` | `None` | -| `CRL` | `Charles River Laboratories` | `CRL` | `Registry.ROR` | `03ndmsg87` | +| `CRL` | `Charles River Laboratories` | `CRL` | `Research Organization Registry (ROR)` | `03ndmsg87` | | `CUSTOM` | `Custom` | `None` | `None` | `None` | -| `CZI` | `Chan Zuckerberg Initiative` | `CZI` | `Registry.ROR` | `02qenvm24` | +| `CZI` | `Chan Zuckerberg Initiative` | `CZI` | `Research Organization Registry (ROR)` | `02qenvm24` | | `DIGIKEY` | `DigiKey` | `None` | `None` | `None` | | `DODOTRONIC` | `Dodotronic` | `None` | `None` | `None` | -| `DORIC` | `Doric` | `None` | `Registry.ROR` | `059n53q30` | +| `DORIC` | `Doric` | `None` | `Research Organization Registry (ROR)` | `059n53q30` | | `EALING` | `Ealing` | `None` | `None` | `None` | -| `EDMUND_OPTICS` | `Edmund Optics` | `None` | `Registry.ROR` | `01j1gwp17` | -| `EMORY` | `Emory University` | `Emory` | `Registry.ROR` | `03czfpz43` | +| `EDMUND_OPTICS` | `Edmund Optics` | `None` | `Research Organization Registry (ROR)` | `01j1gwp17` | +| `EMORY` | `Emory University` | `Emory` | `Research Organization Registry (ROR)` | `03czfpz43` | | `EURESYS` | `Euresys` | `None` | `None` | `None` | -| `EXCELITAS_TECHNOLOGIES` | `Excelitas Technologies` | `None` | `Registry.ROR` | `01tpbbf75` | -| `FLIR` | `Teledyne FLIR` | `FLIR` | `Registry.ROR` | `01j1gwp17` | +| `EXCELITAS_TECHNOLOGIES` | `Excelitas Technologies` | `None` | `Research Organization Registry (ROR)` | `01tpbbf75` | +| `FLIR` | `Teledyne FLIR` | `FLIR` | `Research Organization Registry (ROR)` | `01j1gwp17` | | `FUJINON` | `Fujinon` | `None` | `None` | `None` | -| `HAMAMATSU` | `Hamamatsu` | `None` | `Registry.ROR` | `03natb733` | +| `HAMAMATSU` | `Hamamatsu` | `None` | `Research Organization Registry (ROR)` | `03natb733` | | `HAMILTON` | `Hamilton` | `None` | `None` | `None` | -| `HUST` | `Huazhong University of Science and Technology` | `HUST` | `Registry.ROR` | `00p991c53` | -| `IDT` | `Integrated DNA Technologies` | `IDT` | `Registry.ROR` | `009jvpf03` | -| `IMEC` | `Interuniversity Microelectronics Center` | `IMEC` | `Registry.ROR` | `02kcbn207` | +| `HUST` | `Huazhong University of Science and Technology` | `HUST` | `Research Organization Registry (ROR)` | `00p991c53` | +| `IDT` | `Integrated DNA Technologies` | `IDT` | `Research Organization Registry (ROR)` | `009jvpf03` | +| `IMEC` | `Interuniversity Microelectronics Center` | `IMEC` | `Research Organization Registry (ROR)` | `02kcbn207` | | `INFINITY_PHOTO_OPTICAL` | `Infinity Photo-Optical` | `None` | `None` | `None` | -| `INVITROGEN` | `Invitrogen` | `None` | `Registry.ROR` | `03x1ewr52` | +| `INVITROGEN` | `Invitrogen` | `None` | `Research Organization Registry (ROR)` | `03x1ewr52` | | `IR_ROBOT_CO` | `IR Robot Co` | `None` | `None` | `None` | | `ISL` | `ISL Products International` | `ISL` | `None` | `None` | | `ITEM` | `Item` | `None` | `None` | `None` | -| `JANELIA` | `Janelia Research Campus` | `Janelia` | `Registry.ROR` | `013sk6x84` | -| `JAX` | `Jackson Laboratory` | `JAX` | `Registry.ROR` | `021sy4w91` | -| `JENOPTIK` | `Jenoptik` | `None` | `Registry.ROR` | `05g7t5c49` | -| `JHU` | `Johns Hopkins University` | `JHU` | `Registry.ROR` | `00za53h95` | +| `JANELIA` | `Janelia Research Campus` | `Janelia` | `Research Organization Registry (ROR)` | `013sk6x84` | +| `JAX` | `Jackson Laboratory` | `JAX` | `Research Organization Registry (ROR)` | `021sy4w91` | +| `JENOPTIK` | `Jenoptik` | `None` | `Research Organization Registry (ROR)` | `05g7t5c49` | +| `JHU` | `Johns Hopkins University` | `JHU` | `Research Organization Registry (ROR)` | `00za53h95` | | `JULABO` | `Julabo` | `None` | `None` | `None` | -| `KENT_SCIENTIFIC_CORPORATION` | `Kent Scientific Corporation` | `None` | `Registry.ROR` | `03xkj6a08` | -| `KOWA` | `Kowa` | `None` | `Registry.ROR` | `03zbwg482` | +| `KENT_SCIENTIFIC_CORPORATION` | `Kent Scientific Corporation` | `None` | `Research Organization Registry (ROR)` | `03xkj6a08` | +| `KOWA` | `Kowa` | `None` | `Research Organization Registry (ROR)` | `03zbwg482` | | `LASOS` | `LASOS Lasertechnik` | `LASOS` | `None` | `None` | | `LEICA` | `Leica` | `None` | `None` | `None` | -| `LG` | `LG` | `None` | `Registry.ROR` | `02b948n83` | +| `LG` | `LG` | `None` | `Research Organization Registry (ROR)` | `02b948n83` | | `LIFECANVAS` | `LifeCanvas` | `None` | `None` | `None` | | `LUMENCOR` | `Lumencor` | `None` | `None` | `None` | | `LUMEN_DYNAMICS` | `Lumen Dynamics` | `None` | `None` | `None` | -| `MBF` | `MBF Bioscience` | `MBF` | `Registry.ROR` | `02zynam48` | -| `MEADOWLARK_OPTICS` | `Meadowlark Optics` | `None` | `Registry.ROR` | `00n8qbq54` | -| `MIBR` | `McGovern Institute for Brain Research` | `MIBR` | `Registry.ROR` | `05ymca674` | +| `MBF` | `MBF Bioscience` | `MBF` | `Research Organization Registry (ROR)` | `02zynam48` | +| `MEADOWLARK_OPTICS` | `Meadowlark Optics` | `None` | `Research Organization Registry (ROR)` | `00n8qbq54` | +| `MIBR` | `McGovern Institute for Brain Research` | `MIBR` | `Research Organization Registry (ROR)` | `05ymca674` | | `MIDOPT` | `Midwest Optical Systems, Inc.` | `MidOpt` | `None` | `None` | -| `MIT` | `Massachusetts Institute of Technology` | `MIT` | `Registry.ROR` | `042nb2s44` | +| `MIT` | `Massachusetts Institute of Technology` | `MIT` | `Research Organization Registry (ROR)` | `042nb2s44` | | `MITUTUYO` | `Mitutuyo` | `None` | `None` | `None` | | `MIT_BCS` | `MIT Department of Brain and Cognitive Sciences` | `MIT-BCS` | `None` | `None` | -| `MJFF` | `Michael J. Fox Foundation for Parkinson's Research` | `MJFF` | `Registry.ROR` | `03arq3225` | -| `MKS_NEWPORT` | `MKS Newport` | `None` | `Registry.ROR` | `00k17f049` | +| `MJFF` | `Michael J. Fox Foundation for Parkinson's Research` | `MJFF` | `Research Organization Registry (ROR)` | `03arq3225` | +| `MKS_NEWPORT` | `MKS Newport` | `None` | `Research Organization Registry (ROR)` | `00k17f049` | | `MOLECULAR_INSTRUMENTS` | `Molecular Instruments` | `None` | `None` | `None` | | `MPI` | `MPI` | `MPI` | `None` | `None` | -| `NATIONAL_INSTRUMENTS` | `National Instruments` | `None` | `Registry.ROR` | `026exqw73` | +| `NATIONAL_INSTRUMENTS` | `National Instruments` | `None` | `Research Organization Registry (ROR)` | `026exqw73` | | `NAVITAR` | `Navitar` | `None` | `None` | `None` | -| `NCCIH` | `National Center for Complementary and Integrative Health` | `NCCIH` | `Registry.ROR` | `00190t495` | +| `NCCIH` | `National Center for Complementary and Integrative Health` | `NCCIH` | `Research Organization Registry (ROR)` | `00190t495` | | `NEURALYNX` | `NeuraLynx` | `None` | `None` | `None` | | `NEUROPHOTOMETRICS` | `Neurophotometrics` | `None` | `None` | `None` | | `NEW_SCALE_TECHNOLOGIES` | `New Scale Technologies` | `None` | `None` | `None` | -| `NIKON` | `Nikon` | `None` | `Registry.ROR` | `0280y9h11` | -| `NIMH` | `National Institute of Mental Health` | `NIMH` | `Registry.ROR` | `04xeg9z08` | -| `NINDS` | `National Institute of Neurological Disorders and Stroke` | `NINDS` | `Registry.ROR` | `01s5ya894` | +| `NIKON` | `Nikon` | `None` | `Research Organization Registry (ROR)` | `0280y9h11` | +| `NIMH` | `National Institute of Mental Health` | `NIMH` | `Research Organization Registry (ROR)` | `04xeg9z08` | +| `NINDS` | `National Institute of Neurological Disorders and Stroke` | `NINDS` | `Research Organization Registry (ROR)` | `01s5ya894` | | `NRESEARCH_INC` | `NResearch Inc` | `None` | `None` | `None` | -| `NYU` | `New York University` | `NYU` | `Registry.ROR` | `0190ak572` | -| `OEPS` | `Open Ephys Production Site` | `OEPS` | `Registry.ROR` | `007rkz355` | -| `OLYMPUS` | `Olympus` | `None` | `Registry.ROR` | `02vcdte90` | +| `NYU` | `New York University` | `NYU` | `Research Organization Registry (ROR)` | `0190ak572` | +| `OEPS` | `Open Ephys Production Site` | `OEPS` | `Research Organization Registry (ROR)` | `007rkz355` | +| `OLYMPUS` | `Olympus` | `None` | `Research Organization Registry (ROR)` | `02vcdte90` | | `OPTOTUNE` | `Optotune` | `None` | `None` | `None` | | `OTHER` | `Other` | `None` | `None` | `None` | -| `OXFORD_INSTRUMENTS` | `Oxford Instruments` | `None` | `Registry.ROR` | `01age2z78` | +| `OXFORD_INSTRUMENTS` | `Oxford Instruments` | `None` | `Research Organization Registry (ROR)` | `01age2z78` | | `OXXIUS` | `Oxxius` | `None` | `None` | `None` | | `PLACID_INDUSTRIES` | `Placid Industries` | `None` | `None` | `None` | | `PRIZMATIX` | `Prizmatix` | `None` | `None` | `None` | | `QUANTIFI` | `Quantifi` | `None` | `None` | `None` | | `RASPBERRY_PI` | `Raspberry Pi` | `None` | `None` | `None` | -| `ROCKLAND` | `Rockland Immunochemicals` | `ROCKLAND` | `Registry.ROR` | `02z2r2t61` | +| `ROCKLAND` | `Rockland Immunochemicals` | `ROCKLAND` | `Research Organization Registry (ROR)` | `02z2r2t61` | | `SAME_SKY` | `Same Sky` | `None` | `None` | `None` | | `SCHNEIDER_KREUZNACH` | `Schneider-Kreuznach` | `None` | `None` | `None` | -| `SCIENTIFICA` | `Scientifica` | `None` | `Registry.ROR` | `00z72df47` | +| `SCIENTIFICA` | `Scientifica` | `None` | `Research Organization Registry (ROR)` | `00z72df47` | | `SECOND_ORDER_EFFECTS` | `Second Order Effects` | `None` | `None` | `None` | | `SEMROCK` | `Semrock` | `None` | `None` | `None` | | `SICGEN` | `SICGEN` | `None` | `None` | `None` | | `SIGMA_ALDRICH` | `Sigma-Aldrich` | `None` | `None` | `None` | -| `SIMONS_FOUNDATION` | `Simons Foundation` | `None` | `Registry.ROR` | `01cmst727` | -| `SPECTRA_PHYSICS` | `Spectra-Physics` | `None` | `Registry.ROR` | `02ad9kp97` | +| `SIMONS_FOUNDATION` | `Simons Foundation` | `None` | `Research Organization Registry (ROR)` | `01cmst727` | +| `SPECTRA_PHYSICS` | `Spectra-Physics` | `None` | `Research Organization Registry (ROR)` | `02ad9kp97` | | `SPINNAKER` | `Spinnaker` | `None` | `None` | `None` | -| `STANFORD_UNIVERSITY` | `Stanford University` | `None` | `Registry.ROR` | `00f54p054` | +| `STANFORD_UNIVERSITY` | `Stanford University` | `None` | `Research Organization Registry (ROR)` | `00f54p054` | | `SYNAPTIC_SYSTEMS` | `Synaptic Systems` | `None` | `None` | `None` | | `TAMRON` | `Tamron` | `None` | `None` | `None` | | `TELEDYNE_VISION_SOLUTIONS` | `Teledyne Vision Solutions` | `None` | `None` | `None` | -| `TE_CONNECTIVITY` | `TE Connectivity` | `None` | `Registry.ROR` | `034frgp20` | -| `THERMO_FISHER_SCIENTIFIC` | `Thermo Fisher Scientific` | `None` | `Registry.ROR` | `03x1ewr52` | +| `TE_CONNECTIVITY` | `TE Connectivity` | `None` | `Research Organization Registry (ROR)` | `034frgp20` | +| `THERMO_FISHER_SCIENTIFIC` | `Thermo Fisher Scientific` | `None` | `Research Organization Registry (ROR)` | `03x1ewr52` | | `THE_IMAGING_SOURCE` | `The Imaging Source` | `None` | `None` | `None` | | `THE_LEE_COMPANY` | `The Lee Company` | `None` | `None` | `None` | -| `THORLABS` | `Thorlabs` | `None` | `Registry.ROR` | `04gsnvb07` | +| `THORLABS` | `Thorlabs` | `None` | `Research Organization Registry (ROR)` | `04gsnvb07` | | `TMC` | `Technical Manufacturing Corporation` | `TMC` | `None` | `None` | | `TRANSDUCER_TECHNIQUES` | `Transducer Techniques` | `None` | `None` | `None` | -| `TWCF` | `Templeton World Charity Foundation` | `TWCF` | `Registry.ROR` | `00x0z1472` | +| `TWCF` | `Templeton World Charity Foundation` | `TWCF` | `Research Organization Registry (ROR)` | `00x0z1472` | | `TYMPHANY` | `Tymphany` | `None` | `None` | `None` | -| `UCSD` | `University of California, San Diego` | `UCSD` | `Registry.ROR` | `0168r3w48` | +| `UCSD` | `University of California, San Diego` | `UCSD` | `Research Organization Registry (ROR)` | `0168r3w48` | | `UNKNOWN` | `Unknown` | `UNKNOWN` | `None` | `None` | -| `UPENN` | `University of Pennsylvania` | `UPENN` | `Registry.ROR` | `00b30xv10` | +| `UPENN` | `University of Pennsylvania` | `UPENN` | `Research Organization Registry (ROR)` | `00b30xv10` | | `VIEWORKS` | `Vieworks` | `None` | `None` | `None` | | `VORTRAN` | `Vortran` | `None` | `None` | `None` | -| `WPI` | `World Precision Intstruments` | `WPI` | `Registry.ROR` | `03st5df34` | +| `WPI` | `World Precision Intstruments` | `WPI` | `Research Organization Registry (ROR)` | `03st5df34` | diff --git a/docs/source/biodata_models/species.md b/docs/source/biodata_models/species.md index 4644d958..0ee0feb3 100644 --- a/docs/source/biodata_models/species.md +++ b/docs/source/biodata_models/species.md @@ -8,21 +8,21 @@ Species | Name | name | common_name | registry | registry_identifier | |------|------|------|------|------| -| `ALPACA` | `Vicuna pacos` | `Alpaca` | `Registry.NCBI` | `NCBI:txid30538` | -| `CHICKEN` | `Gallus gallus` | `Chicken` | `Registry.NCBI` | `NCBI:txid9031` | -| `COMMON_MARMOSET` | `Callithrix jacchus` | `Common marmoset` | `Registry.NCBI` | `NCBI:txid9483` | -| `COMMON_SQUIRREL_MONKEY` | `Saimiri sciureus` | `Common squirrel monkey` | `Registry.NCBI` | `NCBI:txid9521` | -| `CRAB_EATING_MACAQUE` | `Macaca fascicularis` | `Crab-eating macaque` | `Registry.NCBI` | `NCBI:txid9541` | -| `DONKEY` | `Equus asinus` | `Donkey` | `Registry.NCBI` | `NCBI:txid9793` | -| `EUROPEAN_RABBIT` | `Oryctolagus cuniculus` | `European rabbit` | `Registry.NCBI` | `NCBI:txid9986` | -| `GOAT` | `Carpa hircus` | `Goat` | `Registry.NCBI` | `NCBI:txid9925` | -| `GUINEA_PIG` | `Cavia porcellus` | `Guinea pig` | `Registry.NCBI` | `NCBI:txid10141` | -| `HOUSE_MOUSE` | `Mus musculus` | `House mouse` | `Registry.NCBI` | `NCBI:txid10090` | -| `HUMAN` | `Homo sapiens` | `Human` | `Registry.NCBI` | `NCBI:txid9606` | -| `LLAMA` | `Lama glama` | `Llama` | `Registry.NCBI` | `NCBI:txid9844` | -| `NORWAY_RAT` | `Rattus norvegicus` | `Norway rat` | `Registry.NCBI` | `NCBI:txid10116` | -| `PIG_TAILED_MACAQUE` | `Macaca nemestrina` | `Pig-tailed macaque` | `Registry.NCBI` | `NCBI:txid9545` | -| `RHESUS_MACAQUE` | `Macaca mulatta` | `Rhesus macaque` | `Registry.NCBI` | `NCBI:txid9544` | +| `ALPACA` | `Vicuna pacos` | `Alpaca` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid30538` | +| `CHICKEN` | `Gallus gallus` | `Chicken` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid9031` | +| `COMMON_MARMOSET` | `Callithrix jacchus` | `Common marmoset` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid9483` | +| `COMMON_SQUIRREL_MONKEY` | `Saimiri sciureus` | `Common squirrel monkey` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid9521` | +| `CRAB_EATING_MACAQUE` | `Macaca fascicularis` | `Crab-eating macaque` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid9541` | +| `DONKEY` | `Equus asinus` | `Donkey` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid9793` | +| `EUROPEAN_RABBIT` | `Oryctolagus cuniculus` | `European rabbit` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid9986` | +| `GOAT` | `Carpa hircus` | `Goat` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid9925` | +| `GUINEA_PIG` | `Cavia porcellus` | `Guinea pig` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid10141` | +| `HOUSE_MOUSE` | `Mus musculus` | `House mouse` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid10090` | +| `HUMAN` | `Homo sapiens` | `Human` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid9606` | +| `LLAMA` | `Lama glama` | `Llama` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid9844` | +| `NORWAY_RAT` | `Rattus norvegicus` | `Norway rat` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid10116` | +| `PIG_TAILED_MACAQUE` | `Macaca nemestrina` | `Pig-tailed macaque` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid9545` | +| `RHESUS_MACAQUE` | `Macaca mulatta` | `Rhesus macaque` | `National Center for Biotechnology Information (NCBI)` | `NCBI:txid9544` | ### Strain @@ -31,8 +31,8 @@ Strain | Name | name | species | registry | registry_identifier | |------|------|------|------|------| -| `BALB_C` | `BALB/c` | `Mus musculus` | `Registry.MGI` | `MGI:2159737` | -| `C57BL_6J` | `C57BL/6J` | `Mus musculus` | `Registry.MGI` | `MGI:3028467` | +| `BALB_C` | `BALB/c` | `Mus musculus` | `Mouse Genome Informatics (MGI)` | `MGI:2159737` | +| `C57BL_6J` | `C57BL/6J` | `Mus musculus` | `Mouse Genome Informatics (MGI)` | `MGI:3028467` | | `UNKNOWN` | `Unknown` | `Mus musculus` | `None` | `None` | diff --git a/docs/source/components/devices.md b/docs/source/components/devices.md index 1e78ff30..53f8972a 100644 --- a/docs/source/components/devices.md +++ b/docs/source/components/devices.md @@ -783,7 +783,7 @@ Multichannel electrophysiology DAQ | Field | Type | Title (Description) | |-------|------|-------------| | `ports` | List[[ProbePort](#probeport)] | Acquisition board ports | -| `data_interface` | `"DataInterface.USB"` | | +| `data_interface` | `"USB"` | | | `manufacturer` | [Organization](../biodata_models/organizations.md#organization) | | | `channels` | List[[DAQChannel](#daqchannel)] | DAQ channels | | `firmware_version` | `Optional[str]` | Firmware version | diff --git a/docs/source/components/subjects.md b/docs/source/components/subjects.md index 9329bd26..ef4a9fff 100644 --- a/docs/source/components/subjects.md +++ b/docs/source/components/subjects.md @@ -25,6 +25,22 @@ Description of a calibration object | `objects` | Optional[List[[Device](devices.md#device)]] | Objects (For calibration objects that are built up from one or more devices.) | +### CellLine + +Description of a cultured cell line + +| Field | Type | Title (Description) | +|-------|------|-------------| +| `cell_line_name` | `str` | Cell line name | +| `cell_line_type` | [PIDName](../biodata_models/pid_names.md#pidname) | Cell line type (Uses Cell Line Ontology) | +| `species` | [Species](../biodata_models/species.md#species) | Species | +| `protein` | [PIDName](../biodata_models/pid_names.md#pidname) | Protein labeled (Protein uses UniProt registry) | +| `gene` | [PIDName](../biodata_models/pid_names.md#pidname) | Gene targeted (Gene uses NCBI taxonomy) | +| `cell_structure` | `str` | Cell structure protein found in | +| `fluorescent_protein` | [PIDName](../biodata_models/pid_names.md#pidname) | Fluorescent protein | +| `clone_number` | `Optional[int]` | Clone number | + + ### HomeCageEnrichment Materials provided in animal home cage diff --git a/docs/source/subject.md b/docs/source/subject.md index 916ab632..ab5e2a5e 100644 --- a/docs/source/subject.md +++ b/docs/source/subject.md @@ -21,5 +21,5 @@ Description of a subject of data collection | Field | Type | Title (Description) | |-------|------|-------------| | `subject_id` | `str` | Subject ID (Unique identifier for the subject of data acquisition) | -| `subject_details` | [MouseSubject](components/subjects.md#mousesubject) or [HumanSubject](components/subjects.md#humansubject) or [NonHumanPrimateSubject](components/subjects.md#nonhumanprimatesubject) or [CalibrationObject](components/subjects.md#calibrationobject) | Subject Details | +| `subject_details` | [MouseSubject](components/subjects.md#mousesubject) or [HumanSubject](components/subjects.md#humansubject) or [NonHumanPrimateSubject](components/subjects.md#nonhumanprimatesubject) or [CalibrationObject](components/subjects.md#calibrationobject) or [CellLine](components/subjects.md#cellline) | Subject Details | | `notes` | `Optional[str]` | Notes | From 93e341691a6c217b3fe9889f331cfc3cc9ae0844 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 03:58:26 +0000 Subject: [PATCH 3/9] chore: update progress on lint fix Co-authored-by: saskiad <3857958+saskiad@users.noreply.github.com> --- src/biodata_schema/core/subject.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/biodata_schema/core/subject.py b/src/biodata_schema/core/subject.py index 10075a8e..b409703e 100644 --- a/src/biodata_schema/core/subject.py +++ b/src/biodata_schema/core/subject.py @@ -5,7 +5,13 @@ from pydantic import Field, SkipValidation from biodata_schema.base import DataCoreModel, Discriminated -from biodata_schema.components.subjects import CalibrationObject, CellLine, HumanSubject, MouseSubject, NonHumanPrimateSubject +from biodata_schema.components.subjects import ( + CalibrationObject, + CellLine, + HumanSubject, + MouseSubject, + NonHumanPrimateSubject, +) class Subject(DataCoreModel): From 805d4984f315339495c484c6183cedec6036a14a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 03:58:46 +0000 Subject: [PATCH 4/9] chore: fix remaining lint findings Co-authored-by: saskiad <3857958+saskiad@users.noreply.github.com> --- src/biodata_schema/components/subjects.py | 2 +- src/biodata_schema/core/subject.py | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/biodata_schema/components/subjects.py b/src/biodata_schema/components/subjects.py index 9fd3b4fe..cb7f25e2 100644 --- a/src/biodata_schema/components/subjects.py +++ b/src/biodata_schema/components/subjects.py @@ -193,7 +193,7 @@ class CellLine(DataModel): 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") # TODO: ontology or enum in model? + cell_structure: str = Field(..., title="Cell structure protein found in") # TODO: ontology or enum in model? fluorescent_protein: PIDName = Field(..., title="Fluorescent protein", desciption="Uses FPbase") clone_number: Optional[int] = Field(default=None, title="Clone number") diff --git a/src/biodata_schema/core/subject.py b/src/biodata_schema/core/subject.py index b409703e..4fa65e6d 100644 --- a/src/biodata_schema/core/subject.py +++ b/src/biodata_schema/core/subject.py @@ -26,13 +26,8 @@ class Subject(DataCoreModel): title="Subject ID", ) - subject_details: Discriminated[MouseSubject | - HumanSubject | - NonHumanPrimateSubject | - CalibrationObject | - CellLine - ] = Field( - ..., title="Subject Details" - ) + subject_details: Discriminated[ + MouseSubject | HumanSubject | NonHumanPrimateSubject | CalibrationObject | CellLine + ] = Field(..., title="Subject Details") notes: Optional[str] = Field(default=None, title="Notes") From a3774fd7f6bb2a02435b9c9620669a4bb3b057ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 03:59:05 +0000 Subject: [PATCH 5/9] fix: correct fluorescent protein description field Co-authored-by: saskiad <3857958+saskiad@users.noreply.github.com> --- src/biodata_schema/components/subjects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/biodata_schema/components/subjects.py b/src/biodata_schema/components/subjects.py index cb7f25e2..e806b75e 100644 --- a/src/biodata_schema/components/subjects.py +++ b/src/biodata_schema/components/subjects.py @@ -194,7 +194,7 @@ class CellLine(DataModel): 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") # TODO: ontology or enum in model? - fluorescent_protein: PIDName = Field(..., title="Fluorescent protein", desciption="Uses FPbase") + fluorescent_protein: PIDName = Field(..., title="Fluorescent protein", description="Uses FPbase") clone_number: Optional[int] = Field(default=None, title="Clone number") From 3dc9a05074acbd6fe037ff927d010f29f4037034 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 04:00:32 +0000 Subject: [PATCH 6/9] test: cover cell line subject details Co-authored-by: saskiad <3857958+saskiad@users.noreply.github.com> --- docs/source/components/subjects.md | 5 ++- src/biodata_schema/components/subjects.py | 8 +++-- tests/test_subject.py | 25 ++++++++++++++- tests/test_subjects.py | 39 +++++++++++++++++++++++ 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/docs/source/components/subjects.md b/docs/source/components/subjects.md index ef4a9fff..c6dd74ae 100644 --- a/docs/source/components/subjects.md +++ b/docs/source/components/subjects.md @@ -35,8 +35,8 @@ Description of a cultured cell line | `cell_line_type` | [PIDName](../biodata_models/pid_names.md#pidname) | Cell line type (Uses Cell Line Ontology) | | `species` | [Species](../biodata_models/species.md#species) | Species | | `protein` | [PIDName](../biodata_models/pid_names.md#pidname) | Protein labeled (Protein uses UniProt registry) | -| `gene` | [PIDName](../biodata_models/pid_names.md#pidname) | Gene targeted (Gene uses NCBI taxonomy) | -| `cell_structure` | `str` | Cell structure protein found in | +| `gene` | [PIDName](../biodata_models/pid_names.md#pidname) | Gene targeted (Gene uses NCBI registry) | +| `cell_structure` | `str` | Cell structure protein found in (Use controlled cellular structure terms, such as Gene Ontology cellular component names) | | `fluorescent_protein` | [PIDName](../biodata_models/pid_names.md#pidname) | Fluorescent protein | | `clone_number` | `Optional[int]` | Clone number | @@ -153,4 +153,3 @@ Wellness report on animal health | `date` | `datetime.date` | Date | | `report` | `str` | Report | - diff --git a/src/biodata_schema/components/subjects.py b/src/biodata_schema/components/subjects.py index e806b75e..e3386ebf 100644 --- a/src/biodata_schema/components/subjects.py +++ b/src/biodata_schema/components/subjects.py @@ -192,8 +192,12 @@ class CellLine(DataModel): cell_line_type: PIDName = Field(..., title="Cell line type", description="Uses Cell Line Ontology") 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") # TODO: ontology or enum in model? + gene: PIDName = Field(..., title="Gene targeted", description="Gene uses NCBI registry") + cell_structure: str = Field( + ..., + title="Cell structure protein found in", + description="Use controlled cellular structure terms, such as Gene Ontology cellular component names", + ) # TODO: ontology or enum in model? fluorescent_protein: PIDName = Field(..., title="Fluorescent protein", description="Uses FPbase") clone_number: Optional[int] = Field(default=None, title="Clone number") diff --git a/tests/test_subject.py b/tests/test_subject.py index 77a11679..2b06417b 100644 --- a/tests/test_subject.py +++ b/tests/test_subject.py @@ -9,7 +9,7 @@ from biodata_models.registries import Registry from biodata_models.species import Species, Strain -from biodata_schema.components.subjects import BreedingInfo, Housing, LightCycle, MouseSubject +from biodata_schema.components.subjects import BreedingInfo, CellLine, Housing, LightCycle, MouseSubject from biodata_schema.core.subject import Subject @@ -53,3 +53,26 @@ def test_constructors(self): Subject.model_validate_json(s.model_dump_json()) assert s is not None + + def test_cell_line_subject_constructor(self): + """try building Subjects with cell line details""" + + s = Subject( + subject_id="cell-line-123", + subject_details=CellLine( + cell_line_name="HEK293T", + cell_line_type=PIDName( + registry_identifier="CLO:0000001", name="immortalized cell line", registry=Registry.NCBI + ), + species=Species.HUMAN, + protein=PIDName(registry_identifier="P12345", name="GFAP", registry=Registry.UNIPROT), + gene=PIDName(registry_identifier="672", name="BRCA1", registry=Registry.NCBI), + cell_structure="nucleus", + fluorescent_protein=PIDName(registry_identifier="FPbase:123", name="EGFP", registry=Registry.UNIPROT), + ), + ) + + restored = Subject.model_validate_json(s.model_dump_json()) + + assert isinstance(restored.subject_details, CellLine) + assert restored.subject_details.cell_line_name == "HEK293T" diff --git a/tests/test_subjects.py b/tests/test_subjects.py index d21db829..f44d51cd 100644 --- a/tests/test_subjects.py +++ b/tests/test_subjects.py @@ -2,6 +2,7 @@ from datetime import datetime +import pydantic import pytest from biodata_models.organizations import Organization from biodata_models.pid_names import PIDName @@ -11,6 +12,7 @@ from biodata_schema.components.subjects import ( BreedingInfo, CalibrationObject, + CellLine, Housing, HumanSubject, LightCycle, @@ -230,3 +232,40 @@ 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_cell_line(self): + """Test creating CellLine""" + + cell_line = CellLine( + cell_line_name="HEK293T", + cell_line_type=PIDName( + registry_identifier="CLO:0000001", name="immortalized cell line", registry=Registry.NCBI + ), + species=Species.HUMAN, + protein=PIDName(registry_identifier="P12345", name="GFAP", registry=Registry.UNIPROT), + gene=PIDName(registry_identifier="672", name="BRCA1", registry=Registry.NCBI), + cell_structure="nucleus", + fluorescent_protein=PIDName(registry_identifier="FPbase:123", name="EGFP", registry=Registry.UNIPROT), + ) + + assert cell_line.cell_line_name == "HEK293T" + assert cell_line.cell_structure == "nucleus" + + def test_cell_line_requires_fluorescent_protein(self): + """Test CellLine required fields""" + + with pytest.raises(pydantic.ValidationError): + CellLine( + cell_line_name="HEK293T", + cell_line_type=PIDName( + registry_identifier="CLO:0000001", name="immortalized cell line", registry=Registry.NCBI + ), + species=Species.HUMAN, + protein=PIDName(registry_identifier="P12345", name="GFAP", registry=Registry.UNIPROT), + gene=PIDName(registry_identifier="672", name="BRCA1", registry=Registry.NCBI), + cell_structure="nucleus", + ) From 3a5fc2b173e635acfb21c0fed483bb16458c8ecd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 04:01:29 +0000 Subject: [PATCH 7/9] docs: clarify cell line field metadata Co-authored-by: saskiad <3857958+saskiad@users.noreply.github.com> --- docs/source/components/subjects.md | 3 +-- src/biodata_schema/components/subjects.py | 4 +++- tests/test_subjects.py | 6 +++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/source/components/subjects.md b/docs/source/components/subjects.md index c6dd74ae..c3083de3 100644 --- a/docs/source/components/subjects.md +++ b/docs/source/components/subjects.md @@ -37,7 +37,7 @@ Description of a cultured cell line | `protein` | [PIDName](../biodata_models/pid_names.md#pidname) | Protein labeled (Protein uses UniProt registry) | | `gene` | [PIDName](../biodata_models/pid_names.md#pidname) | Gene targeted (Gene uses NCBI registry) | | `cell_structure` | `str` | Cell structure protein found in (Use controlled cellular structure terms, such as Gene Ontology cellular component names) | -| `fluorescent_protein` | [PIDName](../biodata_models/pid_names.md#pidname) | Fluorescent protein | +| `fluorescent_protein` | [PIDName](../biodata_models/pid_names.md#pidname) | Fluorescent protein (Fluorescent protein uses FPbase registry) | | `clone_number` | `Optional[int]` | Clone number | @@ -152,4 +152,3 @@ Wellness report on animal health |-------|------|-------------| | `date` | `datetime.date` | Date | | `report` | `str` | Report | - diff --git a/src/biodata_schema/components/subjects.py b/src/biodata_schema/components/subjects.py index e3386ebf..9b89b092 100644 --- a/src/biodata_schema/components/subjects.py +++ b/src/biodata_schema/components/subjects.py @@ -198,7 +198,9 @@ class CellLine(DataModel): title="Cell structure protein found in", description="Use controlled cellular structure terms, such as Gene Ontology cellular component names", ) # TODO: ontology or enum in model? - fluorescent_protein: PIDName = Field(..., title="Fluorescent protein", description="Uses FPbase") + fluorescent_protein: PIDName = Field( + ..., title="Fluorescent protein", description="Fluorescent protein uses FPbase registry" + ) clone_number: Optional[int] = Field(default=None, title="Clone number") diff --git a/tests/test_subjects.py b/tests/test_subjects.py index f44d51cd..f1b9e72f 100644 --- a/tests/test_subjects.py +++ b/tests/test_subjects.py @@ -258,7 +258,7 @@ def test_cell_line(self): def test_cell_line_requires_fluorescent_protein(self): """Test CellLine required fields""" - with pytest.raises(pydantic.ValidationError): + with pytest.raises(pydantic.ValidationError) as exc_info: CellLine( cell_line_name="HEK293T", cell_line_type=PIDName( @@ -269,3 +269,7 @@ def test_cell_line_requires_fluorescent_protein(self): gene=PIDName(registry_identifier="672", name="BRCA1", registry=Registry.NCBI), cell_structure="nucleus", ) + + errors = exc_info.value.errors() + assert len(errors) == 1 + assert errors[0]["loc"] == ("fluorescent_protein",) From ca742cb4bc3fba839abfeec6be6ca74f73b11fc6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 04:02:57 +0000 Subject: [PATCH 8/9] fix: validate cell line pid registries Co-authored-by: saskiad <3857958+saskiad@users.noreply.github.com> --- src/biodata_schema/components/subjects.py | 47 +++++++++++++++++++ tests/test_subject.py | 5 ++- tests/test_subjects.py | 55 +++++++++++++++++++++-- 3 files changed, 102 insertions(+), 5 deletions(-) diff --git a/src/biodata_schema/components/subjects.py b/src/biodata_schema/components/subjects.py index 9b89b092..869ec8f1 100644 --- a/src/biodata_schema/components/subjects.py +++ b/src/biodata_schema/components/subjects.py @@ -7,6 +7,7 @@ from biodata_models.organizations import Organization from biodata_models.pid_names import PIDName +from biodata_models.registries import Registry from biodata_models.species import Species, Strain from pydantic import Field, field_validator, model_validator from pydantic_core.core_schema import ValidationInfo @@ -203,6 +204,52 @@ class CellLine(DataModel): ) clone_number: Optional[int] = Field(default=None, title="Clone number") + @staticmethod + def _registry_matches(actual: Registry | str | None, expected: Registry | str) -> bool: + """Check whether a PID registry matches the expected enum or string label""" + + actual_options = {actual} + if isinstance(actual, Registry): + actual_options.update({actual.name, actual.value}) + + expected_options = {expected} + if isinstance(expected, Registry): + expected_options.update({expected.name, expected.value}) + + return bool(actual_options & expected_options) + + @classmethod + def _validate_pid_registry(cls, value: PIDName, expected: Registry | str, field_name: str) -> PIDName: + """Ensure a PIDName field uses the expected registry""" + + if not cls._registry_matches(value.registry, expected): + raise ValueError(f"{field_name} must use the {expected} registry") + return value + + @field_validator("cell_line_type", mode="after") + def validate_cell_line_type_registry(cls, value: PIDName) -> PIDName: + """Ensure cell line type uses the Cell Line Ontology registry""" + + return cls._validate_pid_registry(value, "Cell Line Ontology", "cell_line_type") + + @field_validator("protein", mode="after") + def validate_protein_registry(cls, value: PIDName) -> PIDName: + """Ensure protein uses the UniProt registry""" + + return cls._validate_pid_registry(value, Registry.UNIPROT, "protein") + + @field_validator("gene", mode="after") + def validate_gene_registry(cls, value: PIDName) -> PIDName: + """Ensure gene uses the NCBI registry""" + + return cls._validate_pid_registry(value, Registry.NCBI, "gene") + + @field_validator("fluorescent_protein", mode="after") + def validate_fluorescent_protein_registry(cls, value: PIDName) -> PIDName: + """Ensure fluorescent protein uses the FPbase registry""" + + return cls._validate_pid_registry(value, "FPbase", "fluorescent_protein") + class CalibrationObject(DataModel): """Description of a calibration object""" diff --git a/tests/test_subject.py b/tests/test_subject.py index 2b06417b..1959bcda 100644 --- a/tests/test_subject.py +++ b/tests/test_subject.py @@ -62,13 +62,13 @@ def test_cell_line_subject_constructor(self): subject_details=CellLine( cell_line_name="HEK293T", cell_line_type=PIDName( - registry_identifier="CLO:0000001", name="immortalized cell line", registry=Registry.NCBI + registry_identifier="CLO:0000001", name="immortalized cell line", registry="Cell Line Ontology" ), species=Species.HUMAN, protein=PIDName(registry_identifier="P12345", name="GFAP", registry=Registry.UNIPROT), gene=PIDName(registry_identifier="672", name="BRCA1", registry=Registry.NCBI), cell_structure="nucleus", - fluorescent_protein=PIDName(registry_identifier="FPbase:123", name="EGFP", registry=Registry.UNIPROT), + fluorescent_protein=PIDName(registry_identifier="FPbase:123", name="EGFP", registry="FPbase"), ), ) @@ -76,3 +76,4 @@ def test_cell_line_subject_constructor(self): assert isinstance(restored.subject_details, CellLine) assert restored.subject_details.cell_line_name == "HEK293T" + assert restored.subject_details.gene.registry == Registry.NCBI diff --git a/tests/test_subjects.py b/tests/test_subjects.py index f1b9e72f..628feb08 100644 --- a/tests/test_subjects.py +++ b/tests/test_subjects.py @@ -243,13 +243,13 @@ def test_cell_line(self): cell_line = CellLine( cell_line_name="HEK293T", cell_line_type=PIDName( - registry_identifier="CLO:0000001", name="immortalized cell line", registry=Registry.NCBI + registry_identifier="CLO:0000001", name="immortalized cell line", registry="Cell Line Ontology" ), species=Species.HUMAN, protein=PIDName(registry_identifier="P12345", name="GFAP", registry=Registry.UNIPROT), gene=PIDName(registry_identifier="672", name="BRCA1", registry=Registry.NCBI), cell_structure="nucleus", - fluorescent_protein=PIDName(registry_identifier="FPbase:123", name="EGFP", registry=Registry.UNIPROT), + fluorescent_protein=PIDName(registry_identifier="FPbase:123", name="EGFP", registry="FPbase"), ) assert cell_line.cell_line_name == "HEK293T" @@ -262,7 +262,7 @@ def test_cell_line_requires_fluorescent_protein(self): CellLine( cell_line_name="HEK293T", cell_line_type=PIDName( - registry_identifier="CLO:0000001", name="immortalized cell line", registry=Registry.NCBI + registry_identifier="CLO:0000001", name="immortalized cell line", registry="Cell Line Ontology" ), species=Species.HUMAN, protein=PIDName(registry_identifier="P12345", name="GFAP", registry=Registry.UNIPROT), @@ -273,3 +273,52 @@ def test_cell_line_requires_fluorescent_protein(self): errors = exc_info.value.errors() assert len(errors) == 1 assert errors[0]["loc"] == ("fluorescent_protein",) + + @pytest.mark.parametrize( + ("field_name", "field_value", "expected_registry"), + [ + ( + "cell_line_type", + PIDName(registry_identifier="CLO:0000001", name="immortalized cell line", registry=Registry.NCBI), + "Cell Line Ontology", + ), + ( + "protein", + PIDName(registry_identifier="P12345", name="GFAP", registry=Registry.NCBI), + "Registry.UNIPROT", + ), + ( + "gene", + PIDName(registry_identifier="672", name="BRCA1", registry=Registry.UNIPROT), + "Registry.NCBI", + ), + ( + "fluorescent_protein", + PIDName(registry_identifier="FPbase:123", name="EGFP", registry=Registry.UNIPROT), + "FPbase", + ), + ], + ) + def test_cell_line_requires_expected_registry(self, field_name, field_value, expected_registry): + """Test CellLine PIDName registries are validated""" + + kwargs = { + "cell_line_name": "HEK293T", + "cell_line_type": PIDName( + registry_identifier="CLO:0000001", name="immortalized cell line", registry="Cell Line Ontology" + ), + "species": Species.HUMAN, + "protein": PIDName(registry_identifier="P12345", name="GFAP", registry=Registry.UNIPROT), + "gene": PIDName(registry_identifier="672", name="BRCA1", registry=Registry.NCBI), + "cell_structure": "nucleus", + "fluorescent_protein": PIDName(registry_identifier="FPbase:123", name="EGFP", registry="FPbase"), + } + kwargs[field_name] = field_value + + with pytest.raises(pydantic.ValidationError) as exc_info: + CellLine(**kwargs) + + errors = exc_info.value.errors() + assert len(errors) == 1 + assert errors[0]["loc"] == (field_name,) + assert expected_registry in errors[0]["msg"] From 7970c74b0337ea21ac0e9e2ff8325ce97ac39d24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 04:03:55 +0000 Subject: [PATCH 9/9] fix: clarify cell line registry errors Co-authored-by: saskiad <3857958+saskiad@users.noreply.github.com> --- src/biodata_schema/components/subjects.py | 3 ++- tests/test_subjects.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/biodata_schema/components/subjects.py b/src/biodata_schema/components/subjects.py index 869ec8f1..222980cd 100644 --- a/src/biodata_schema/components/subjects.py +++ b/src/biodata_schema/components/subjects.py @@ -222,8 +222,9 @@ def _registry_matches(actual: Registry | str | None, expected: Registry | str) - def _validate_pid_registry(cls, value: PIDName, expected: Registry | str, field_name: str) -> PIDName: """Ensure a PIDName field uses the expected registry""" + expected_label = expected.value if isinstance(expected, Registry) else expected if not cls._registry_matches(value.registry, expected): - raise ValueError(f"{field_name} must use the {expected} registry") + raise ValueError(f"{field_name} must use the {expected_label} registry") return value @field_validator("cell_line_type", mode="after") diff --git a/tests/test_subjects.py b/tests/test_subjects.py index 628feb08..15b8939d 100644 --- a/tests/test_subjects.py +++ b/tests/test_subjects.py @@ -285,12 +285,12 @@ def test_cell_line_requires_fluorescent_protein(self): ( "protein", PIDName(registry_identifier="P12345", name="GFAP", registry=Registry.NCBI), - "Registry.UNIPROT", + Registry.UNIPROT.value, ), ( "gene", PIDName(registry_identifier="672", name="BRCA1", registry=Registry.UNIPROT), - "Registry.NCBI", + Registry.NCBI.value, ), ( "fluorescent_protein",