"""Contains the Animal, Worm, and Fly classes for CeDNe core."""
__author__ = "Sahil Moza"
__date__ = "2025-04-06"
__license__ = "MIT"
import pickle
from pathlib import Path
from .io import generate_random_string, load_pickle
from .source import (
CEDNE_SOFTWARE_CITATION,
CEDNE_SOFTWARE_CITATION_KEY,
Citable,
)
[docs]
class Animal(Citable):
"""This is a full animal class"""
def __init__(self, species="", name="", stage="", sex="", genotype="", **kwargs):
"""Initializes an Organism class"""
Citable.__init__(self) # provides self.citations = {}
# Every CeDNe Animal carries a self-citation for the CeDNe software so
# that downstream consumers (notebooks, web UI references panel,
# exported provenance manifests) automatically attribute CeDNe.
if CEDNE_SOFTWARE_CITATION_KEY not in self.citations:
self.citations[CEDNE_SOFTWARE_CITATION_KEY] = CEDNE_SOFTWARE_CITATION
self.species = species
self.name = name
self.stage = stage
self.sex = sex
self.genotype = genotype
self.networks = {}
self.contexts = {} # Dict[str, Context]
self.active_context = None
self.active_network = None
# Descriptive event log; see cedne.core.history. Older pickles will
# not have this attribute — readers should defensively initialize.
self.history = []
for key, value in kwargs.items():
self.set_property(key, value)
[docs]
def save(self, file_path, file_format="cedne"):
"""
Saves the Organism object to a pickle file at the specified file path.
Args:
file_path (str): The path to the pickle file.
"""
if file_format == "cedne":
output_path = Path(file_path)
if not output_path.suffix:
output_path = output_path.with_suffix(".cedne")
with open(output_path, "wb") as pickle_file:
pickle.dump(self, pickle_file, protocol=pickle.HIGHEST_PROTOCOL)
elif file_format == "full":
pass
else:
raise NotImplementedError("Only pickle format is supported.")
[docs]
def set_property(self, key, value):
"""
Set a property of the organism.
Args:
key (str): The name of the property.
value: The value of the property.
"""
setattr(self, key, value)
[docs]
def add_context(self, name, data=None):
"""
Adds a context to the organism.
Args:
name (str): The name of the context.
data: Optional data to associate with the context.
"""
self.contexts[name] = data
[docs]
def remove_context(self, name):
"""
Removes a context from the organism and clears active_context if it was active.
Args:
name (str): The name of the context to remove.
"""
if name not in self.contexts:
raise ValueError(f"Context '{name}' not found.")
# Clear active_context if it's the one being removed
if self.active_context == name:
self.active_context = None
del self.contexts[name]
[docs]
def get_context(self, name):
"""
Get a context by name.
Args:
name (str): The name of the context.
Returns:
The context data, or None if not found.
"""
return self.contexts.get(name)
[docs]
def set_active_context(self, name):
"""
Set the active context by name.
Args:
name (str): The name of the context to set as active.
"""
if name not in self.contexts:
raise ValueError(f"Context '{name}' not found.")
self.active_context = name
[docs]
def clear_active_context(self):
"""Clear the active context."""
self.active_context = None
[docs]
class Worm(Animal):
"""This is an explicit Worm class, a container for network(s)."""
def __init__(
self, name="", stage="Day-1 Adult", sex="Hermaphrodite", genotype="N2", **kwargs
) -> None:
"""
Initializes a Worm object.
Parameters:
name (str): The name of the worm. If empty,
a random alphanumeric string will be generated.
stage (str): The stage of the worm. Default is 'Day-1 Adult'.
Other options can be L1, L2, L3, L4, Day-2 Adult, etc.
sex (str): The sex of the worm. Default is 'Hermaphrodite'.
Other options can be 'Male', 'Feminized male', etc.
genotype (str): The genotype of the worm. Default is 'N2'.
Other options can be mutant names or other wild types, etc.
Returns:
None
"""
if not name:
name = "Worm-" + generate_random_string()
super().__init__(
species="Caenorhabditis elegans",
name=name,
stage=stage,
sex=sex,
genotype=genotype,
**kwargs,
)
[docs]
class Fly(Animal):
"""This is an explicit Fly class, a container for network(s)."""
def __init__(
self,
name="",
stage="Day-7 Adult",
sex="Female",
genotype="w1118 x Canton-S G1",
**kwargs,
) -> None:
"""
Initializes a Fly object.
Parameters:
name (str): The name of the Fly. If empty,
a random alphanumeric string will be generated.
stage (str): The stage of the fly. Default is 'Day-7 Adult'.
Other options can be E, L1, L2, P1, Day-2 Adult, etc.
sex (str): The sex of the fly. Default is 'Female'.
Other options can be 'Male', 'Feminized Male', etc.
genotype (str): The genotype of the worm. Default is 'w1118 x Canton-S G1'.
Other options can be mutant names or other wild-types.
Returns:
None
"""
if not name:
name = "Fly-" + generate_random_string()
super().__init__(
species="Drosophila melanogaster",
name=name,
stage=stage,
sex=sex,
genotype=genotype,
)
def load_worm(file_path):
"""
Load a Worm object from a pickle file.
Args:
file_path (str): The path to the pickle file.
Returns:
Worm: The loaded Worm object.
"""
try:
with open(file_path, "rb") as pickle_file:
# return pickle.load(pickle_file)
return load_pickle(pickle_file)
except Exception as exc:
raise RuntimeError(f"Failed to load {file_path}.") from exc