diff --git a/wetlab/__init__.py b/wetlab/__init__.py index 79cfbaf..69f2af4 100644 --- a/wetlab/__init__.py +++ b/wetlab/__init__.py @@ -11,8 +11,8 @@ Create records: ->>> compound_perturbation = wl.CompoundPerturbation( -... name="Aspirin treatment day 1", +>>> biosample = wl.Biosample( +... name="Sample 1", ... ).save() Registries: @@ -30,7 +30,14 @@ EnvironmentalPerturbation GeneticPerturbation PerturbationTarget + +Types: + +.. autosummary:: + :toctree: . + GeneticPerturbationSystem + BiologicType """ @@ -62,7 +69,7 @@ def __getattr__(name): Techsample, Well, ) - from .types import GeneticPerturbationSystem + from .types import BiologicType, GeneticPerturbationSystem # backwards compatibility CombinationTreatment = CombinationPerturbation diff --git a/wetlab/migrations/0029_artifactbiologic_biologic_artifactbiologic_biologic.py b/wetlab/migrations/0029_artifactbiologic_biologic_artifactbiologic_biologic.py new file mode 100644 index 0000000..e2ecce9 --- /dev/null +++ b/wetlab/migrations/0029_artifactbiologic_biologic_artifactbiologic_biologic.py @@ -0,0 +1,207 @@ +# Generated by Django 5.1.3 on 2024-12-16 21:49 + +import django.db.models.deletion +import lnschema_core.fields +import lnschema_core.ids +import lnschema_core.models +import lnschema_core.users +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("bionty", "0041_squashed"), + ("lnschema_core", "0069_squashed"), + ("wetlab", "0028_remove_combinationperturbation_compounds_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="ArtifactBiologic", + fields=[ + ( + "created_at", + lnschema_core.fields.DateTimeField( + auto_now_add=True, db_index=True + ), + ), + ("id", models.BigAutoField(primary_key=True, serialize=False)), + ( + "label_ref_is_name", + lnschema_core.fields.BooleanField( + blank=True, default=None, null=True + ), + ), + ( + "feature_ref_is_name", + lnschema_core.fields.BooleanField( + blank=True, default=None, null=True + ), + ), + ( + "artifact", + lnschema_core.fields.ForeignKey( + blank=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="links_biologic", + to="lnschema_core.artifact", + ), + ), + ( + "created_by", + lnschema_core.fields.ForeignKey( + blank=True, + default=lnschema_core.users.current_user_id, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="lnschema_core.user", + ), + ), + ( + "feature", + lnschema_core.fields.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="links_artifactbiologic", + to="lnschema_core.feature", + ), + ), + ( + "run", + lnschema_core.fields.ForeignKey( + blank=True, + default=lnschema_core.models.current_run, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="lnschema_core.run", + ), + ), + ], + options={ + "abstract": False, + }, + bases=(lnschema_core.models.LinkORM, models.Model), + ), + migrations.CreateModel( + name="Biologic", + fields=[ + ( + "created_at", + lnschema_core.fields.DateTimeField( + auto_now_add=True, db_index=True + ), + ), + ( + "updated_at", + lnschema_core.fields.DateTimeField(auto_now=True, db_index=True), + ), + ("id", models.AutoField(primary_key=True, serialize=False)), + ( + "uid", + lnschema_core.fields.CharField( + blank=True, + default=lnschema_core.ids.base62_12, + max_length=12, + unique=True, + ), + ), + ( + "name", + lnschema_core.fields.CharField( + blank=True, + db_index=True, + default=None, + max_length=255, + unique=True, + ), + ), + ( + "type", + lnschema_core.fields.CharField( + blank=True, db_index=True, default=None, max_length=32 + ), + ), + ( + "abbr", + lnschema_core.fields.CharField( + blank=True, + db_index=True, + default=None, + max_length=32, + null=True, + unique=True, + ), + ), + ( + "synonyms", + lnschema_core.fields.TextField(blank=True, default=None, null=True), + ), + ( + "description", + lnschema_core.fields.TextField(blank=True, default=None, null=True), + ), + ( + "_previous_runs", + models.ManyToManyField(related_name="+", to="lnschema_core.run"), + ), + ( + "artifacts", + models.ManyToManyField( + related_name="biologics", + through="wetlab.ArtifactBiologic", + to="lnschema_core.artifact", + ), + ), + ( + "created_by", + lnschema_core.fields.ForeignKey( + blank=True, + default=lnschema_core.users.current_user_id, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="lnschema_core.user", + ), + ), + ( + "proteins", + models.ManyToManyField( + related_name="biologics", to="bionty.protein" + ), + ), + ( + "run", + lnschema_core.fields.ForeignKey( + blank=True, + default=lnschema_core.models.current_run, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="lnschema_core.run", + ), + ), + ( + "targets", + models.ManyToManyField( + related_name="biologic_targets", to="wetlab.perturbationtarget" + ), + ), + ], + options={ + "abstract": False, + }, + bases=(lnschema_core.models.CanCurate, models.Model), + ), + migrations.AddField( + model_name="artifactbiologic", + name="biologic", + field=lnschema_core.fields.ForeignKey( + blank=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="links_artifact", + to="wetlab.biologic", + ), + ), + ] diff --git a/wetlab/models.py b/wetlab/models.py index 35a3aa3..6f96903 100644 --- a/wetlab/models.py +++ b/wetlab/models.py @@ -41,7 +41,7 @@ TracksUpdates, ) -from .types import GeneticPerturbationSystem # noqa +from .types import BiologicType, GeneticPerturbationSystem # noqa # def _get_related_repr(instance, related_name: str) -> str: # try: @@ -389,6 +389,84 @@ class ArtifactGeneticPerturbation(Record, LinkORM, TracksRun): feature_ref_is_name: bool | None = BooleanField(null=True, default=None) +class Biologic(Record, CanCurate, TracksRun, TracksUpdates): + """Proteins, peptides, antibodies, enzymes, growth factors, etc. + + Examples: + >>> biologic = wl.Biologic( + ... name="IFNG", + ... type="cytokine", + ... ).save() + """ + + class Meta(BioRecord.Meta, TracksRun.Meta, TracksUpdates.Meta): + abstract = False + + _name_field: str = "name" + + id: int = models.AutoField(primary_key=True) + """Internal id, valid only in one DB instance.""" + uid: str = CharField(unique=True, max_length=12, default=ids.base62_12) + """A universal id (hash of selected field).""" + name: str = CharField(unique=True, db_index=True) + """Name of the compound.""" + type: BiologicType = CharField(max_length=32, db_index=True, default=None) + """The type.""" + abbr: str | None = CharField( + max_length=32, db_index=True, unique=True, null=True, default=None + ) + """A unique abbreviation.""" + synonyms: str | None = TextField(null=True, default=None) + """Bar-separated (|) synonyms that correspond to this compound.""" + description: str | None = TextField(null=True, default=None) + """Description of the compound.""" + proteins: Protein = models.ManyToManyField( + "bionty.Protein", related_name="biologics" + ) + """Proteins associated with this biologic.""" + targets: PerturbationTarget = models.ManyToManyField( + PerturbationTarget, related_name="biologic_targets" + ) + """Targets of the perturbation.""" + artifacts: Artifact = models.ManyToManyField( + Artifact, through="ArtifactBiologic", related_name="biologics" + ) + """Artifacts linked to the compound.""" + + @overload + def __init__( + self, + name: str, + abbr: str | None, + synonyms: str | None, + description: str | None, + ): ... + + @overload + def __init__( + self, + *db_args, + ): ... + + def __init__( + self, + *args, + **kwargs, + ): + super().__init__(*args, **kwargs) + + +class ArtifactBiologic(Record, LinkORM, TracksRun): + id: int = models.BigAutoField(primary_key=True) + artifact: Artifact = ForeignKey(Artifact, CASCADE, related_name="links_biologic") + biologic: Biologic = ForeignKey(Biologic, PROTECT, related_name="links_artifact") + feature: Feature = ForeignKey( + Feature, PROTECT, null=True, default=None, related_name="links_artifactbiologic" + ) + label_ref_is_name: bool | None = BooleanField(null=True, default=None) + feature_ref_is_name: bool | None = BooleanField(null=True, default=None) + + class CompoundPerturbation(Record, CanCurate, TracksRun, TracksUpdates): """Models compound perturbations such as drugs. diff --git a/wetlab/types.py b/wetlab/types.py index dfa0f0e..01c18a7 100644 --- a/wetlab/types.py +++ b/wetlab/types.py @@ -9,3 +9,15 @@ "transgene", "transient-transfection", ] + +BiologicType = Literal[ + "protein", + "peptide", + "antibody", + "enzyme", + "growth-factor", + "cytokine", + "hormone", + "vaccine", + "oligonucleotide", +]