Pydantic switchover debugging.

This commit is contained in:
lwark
2025-03-07 10:19:12 -06:00
parent abcdbac5b8
commit 919376c1ce
7 changed files with 1327 additions and 9 deletions

View File

@@ -52,7 +52,12 @@ def import_irida(ctx: Settings):
sample = new_session.query(BasicSample).filter(BasicSample.submitter_id == instance.name).first() sample = new_session.query(BasicSample).filter(BasicSample.submitter_id == instance.name).first()
if sample: if sample:
instance.sample = sample instance.sample = sample
instance.submission = sample.submissions[0] try:
instance.submission = sample.submissions[0]
except IndexError:
logger.error(f"Could not get sample for {sample}")
instance.submission = None
# instance.submission = sample.submission[0]
new_session.add(instance) new_session.add(instance)
new_session.commit() new_session.commit()
new_session.close() new_session.close()

View File

@@ -397,8 +397,8 @@ class BaseClass(Base):
""" """
Custom dunder method to handle potential list relationship issues. Custom dunder method to handle potential list relationship issues.
""" """
if key != "_sa_instance_state": # if key != "_sa_instance_state":
logger.debug(f"Attempting to set {key} to {pformat(value)}") # logger.debug(f"Attempting to set {key} to {pformat(value)}")
try: try:
field_type = getattr(self.__class__, key) field_type = getattr(self.__class__, key)
except AttributeError: except AttributeError:
@@ -407,21 +407,25 @@ class BaseClass(Base):
logger.debug(f"{key} is an InstrumentedAttribute.") logger.debug(f"{key} is an InstrumentedAttribute.")
match field_type.property: match field_type.property:
case ColumnProperty(): case ColumnProperty():
logger.debug(f"Setting ColumnProperty to {value}") # logger.debug(f"Setting ColumnProperty to {value}")
return super().__setattr__(key, value) return super().__setattr__(key, value)
case _RelationshipDeclared(): case _RelationshipDeclared():
logger.debug(f"Setting _RelationshipDeclared to {value}") logger.debug(f"{self.__class__.__name__} Setting _RelationshipDeclared for {key} to {value}")
if field_type.property.uselist: if field_type.property.uselist:
logger.debug(f"Setting with uselist") logger.debug(f"Setting with uselist")
existing = self.__getattribute__(key) existing = self.__getattribute__(key)
# NOTE: This is causing problems with removal of items from lists. Have to overhaul it.
if existing is not None: if existing is not None:
logger.debug(f"{key} Existing: {existing}, incoming: {value}")
if isinstance(value, list): if isinstance(value, list):
value = existing + value # value = existing + value
value = value
else: else:
value = existing + [value] value = existing + [value]
else: else:
value = [value] value = [value]
value = list(set(value)) value = list(set(value))
logger.debug(f"Final value for {key}: {value}")
return super().__setattr__(key, value) return super().__setattr__(key, value)
else: else:
if isinstance(value, list): if isinstance(value, list):
@@ -429,6 +433,7 @@ class BaseClass(Base):
value = value[0] value = value[0]
else: else:
raise ValueError("Object is too long to parse a single value.") raise ValueError("Object is too long to parse a single value.")
# value = value
return super().__setattr__(key, value) return super().__setattr__(key, value)
case _: case _:
return super().__setattr__(key, value) return super().__setattr__(key, value)

View File

@@ -3,6 +3,7 @@ All kit and reagent related models
""" """
from __future__ import annotations from __future__ import annotations
import json, zipfile, yaml, logging, re import json, zipfile, yaml, logging, re
import sys
from pprint import pformat from pprint import pformat
from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey, Interval, Table, FLOAT, BLOB from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey, Interval, Table, FLOAT, BLOB
from sqlalchemy.orm import relationship, validates, Query from sqlalchemy.orm import relationship, validates, Query
@@ -231,6 +232,7 @@ class KitType(BaseClass):
@classmethod @classmethod
def query_or_create(cls, **kwargs) -> Tuple[KitType, bool]: def query_or_create(cls, **kwargs) -> Tuple[KitType, bool]:
from backend.validators.pydant import PydKitType from backend.validators.pydant import PydKitType
from backend.validators.omni_gui_objects import BaseOmni
new = False new = False
disallowed = ['expiry'] disallowed = ['expiry']
sanitized_kwargs = {k: v for k, v in kwargs.items() if k not in disallowed} sanitized_kwargs = {k: v for k, v in kwargs.items() if k not in disallowed}
@@ -1074,7 +1076,7 @@ class SubmissionType(BaseClass):
new = True new = True
for k, v in sanitized_kwargs.items(): for k, v in sanitized_kwargs.items():
setattr(instance, k, v) setattr(instance, k, v)
logger.info(f"Instance from query or create: {instance}") logger.info(f"Instance from submissiontype query or create: {instance}")
return instance, new return instance, new
@classmethod @classmethod
@@ -1298,7 +1300,7 @@ class SubmissionTypeKitTypeAssociation(BaseClass):
new = True new = True
for k, v in sanitized_kwargs.items(): for k, v in sanitized_kwargs.items():
setattr(instance, k, v) setattr(instance, k, v)
logger.info(f"Instance from query or create: {instance}") logger.info(f"Instance from SubmissionTypeKitTypeAssociation query or create: {instance}")
return instance, new return instance, new
@classmethod @classmethod
@@ -1428,6 +1430,22 @@ class KitTypeReagentRoleAssociation(BaseClass):
except AttributeError: except AttributeError:
return "Blank KitTypeReagentRole" return "Blank KitTypeReagentRole"
@hybrid_property
def submissiontype(self):
return self.submission_type
@submissiontype.setter
def submissiontype(self, value):
self.submission_type = value
@hybrid_property
def kittype(self):
return self.kit_type
@kittype.setter
def kittype(self, value):
self.kit_type = value
@validates('required') @validates('required')
def validate_required(self, key, value): def validate_required(self, key, value):
""" """
@@ -1478,8 +1496,31 @@ class KitTypeReagentRoleAssociation(BaseClass):
instance = cls() instance = cls()
new = True new = True
for k, v in sanitized_kwargs.items(): for k, v in sanitized_kwargs.items():
logger.debug(f"Key: {k} has value: {v}")
match k:
case "kittype" | "kit_type":
k = "kit_type"
if isinstance(v, str):
v = KitType.query(name=v)
else:
v = v.instance_object
case "submissiontype" | "submission_type":
k = "submission_type"
if isinstance(v, str):
v = SubmissionType.query(name=v)
else:
v = v.instance_object
case "reagentrole" | "reagent_role":
k = "reagent_role"
if isinstance(v, str):
v = ReagentRole.query(name=v)
else:
v = v.instance_object
case _:
pass
setattr(instance, k, v) setattr(instance, k, v)
logger.info(f"Instance from query or create: {instance}") logger.info(f"Instance from query or create: {instance.__dict__}")
# sys.exit()
return instance, new return instance, new
@classmethod @classmethod

View File

@@ -460,6 +460,7 @@ class BasicSubmission(BaseClass, LogMixin):
""" """
rows = range(1, plate_rows + 1) rows = range(1, plate_rows + 1)
columns = range(1, plate_columns + 1) columns = range(1, plate_columns + 1)
logger.debug(f"sample list for plate map: {pformat(sample_list)}")
# NOTE: An overly complicated list comprehension create a list of sample locations # NOTE: An overly complicated list comprehension create a list of sample locations
# NOTE: next will return a blank cell if no value found for row/column # NOTE: next will return a blank cell if no value found for row/column
output_samples = [next((item for item in sample_list if item['row'] == row and item['column'] == column), output_samples = [next((item for item in sample_list if item['row'] == row and item['column'] == column),
@@ -1536,6 +1537,7 @@ class Wastewater(BasicSubmission):
continue continue
thing['tooltip'] = f"Sample Name: {thing['name']}\nWell: {thing['sample_location']}" thing['tooltip'] = f"Sample Name: {thing['name']}\nWell: {thing['sample_location']}"
dummy_samples.append(thing) dummy_samples.append(thing)
logger.debug(f"Dummy samples for 24 well: {pformat(dummy_samples)}")
output['origin_plate'] = self.__class__.make_plate_map(sample_list=dummy_samples, plate_rows=4, output['origin_plate'] = self.__class__.make_plate_map(sample_list=dummy_samples, plate_rows=4,
plate_columns=6) plate_columns=6)
# logger.debug(f"PCR info: {output['pcr_info']}") # logger.debug(f"PCR info: {output['pcr_info']}")

View File

@@ -0,0 +1,579 @@
from __future__ import annotations
import logging
import sys
from pydantic import BaseModel, field_validator, Field
from typing import List, ClassVar
from backend.db.models import *
from sqlalchemy.orm.properties import ColumnProperty
from sqlalchemy.orm.relationships import _RelationshipDeclared
logger = logging.getLogger(f"submissions.{__name__}")
class BaseOmni(BaseModel):
instance_object: Any | None = Field(default=None)
def __repr__(self):
try:
return f"<{self.__class__.__name__}({self.name})>"
except AttributeError:
return f"<{self.__class__.__name__}(NO NAME)>"
@classproperty
def aliases(cls):
return cls.class_object.aliases
def check_all_attributes(self, attributes: dict) -> bool:
"""
Checks this instance against a dictionary of attributes to determine if they are a match.
Args:
attributes (dict): A dictionary of attributes to be check for equivalence
Returns:
bool: If a single unequivocal value is found will be false, else true.
"""
logger.debug(f"Incoming attributes: {attributes}")
for key, value in attributes.items():
# print(getattr(self.__class__, key).property)
if value.lower() == "none":
value = None
logger.debug(f"Attempting to grab attribute: {key}")
self_value = getattr(self, key)
class_attr = getattr(self.class_object, key)
# logger.debug(f"Self value: {self_value}, class attr: {class_attr} of type: {type(class_attr)}")
if isinstance(class_attr, property):
filter = "property"
else:
filter = class_attr.property
match filter:
case ColumnProperty():
match class_attr.type:
case INTEGER():
if value.lower() == "true":
value = 1
elif value.lower() == "false":
value = 0
else:
value = int(value)
case FLOAT():
value = float(value)
case "property":
pass
case _RelationshipDeclared():
logger.debug(f"Checking {self_value}")
try:
self_value = self_value.name
except AttributeError:
pass
if class_attr.property.uselist:
self_value = self_value.__str__()
try:
logger.debug(f"Check if {self_value.__class__} is subclass of {self.__class__}")
check = issubclass(self_value.__class__, self.__class__)
except TypeError as e:
logger.error(f"Couldn't check if {self_value.__class__} is subclass of {self.__class__} due to {e}")
check = False
if check:
logger.debug(f"Checking for subclass name.")
self_value = self_value.name
logger.debug(
f"Checking self_value {self_value} of type {type(self_value)} against attribute {value} of type {type(value)}")
if self_value != value:
output = False
logger.debug(f"Value {key} is False, returning.")
return output
return True
def __setattr__(self, key, value):
try:
class_value = getattr(self.class_object, key)
except AttributeError:
return super().__setattr__(key, value)
try:
new_key = class_value.impl.key
except AttributeError:
new_key = None
logger.debug(f"Class value before new key: {class_value.property}")
if new_key and new_key != key:
class_value = getattr(self.class_object, new_key)
logger.debug(f"Class value after new key: {class_value.property}")
if isinstance(class_value, InstrumentedAttribute):
logger.debug(f"{key} is an InstrumentedAttribute with class_value.property: {class_value.property}.")
match class_value.property:
case ColumnProperty():
logger.debug(f"Setting ColumnProperty to {value}")
return super().__setattr__(key, value)
case _RelationshipDeclared():
logger.debug(f" {self.__class__.__name__} Setting _RelationshipDeclared for {key} to {value}")
if class_value.property.uselist:
logger.debug(f"Setting {key} with uselist")
existing = self.__getattribute__(key)
if existing is not None:
# NOTE: Getting some really weird duplicates for OmniSubmissionTypeKitTypeAssociation here.
logger.debug(f"Existing: {existing}, incoming: {value}")
if isinstance(value, list):
if value != existing:
value = existing + value
else:
value = existing
else:
if issubclass(value.__class__, self.__class__):
value = value.to_sql()
value = existing + [value]
else:
if issubclass(value.__class__, self.__class__):
value = value.to_sql()
value = [value]
# value = list(set(value))
logger.debug(f"Final value for {key}: {value}")
return super().__setattr__(key, value)
else:
if isinstance(value, list):
if len(value) == 1:
value = value[0]
else:
raise ValueError("Object is too long to parse a single value.")
return super().__setattr__(key, value)
case _:
return super().__setattr__(key, value)
else:
return super().__setattr__(key, value)
class OmniSubmissionType(BaseOmni):
class_object: ClassVar[Any] = SubmissionType
name: str = Field(default="", description="property")
info_map: dict = Field(default={}, description="property")
defaults: dict = Field(default={}, description="property")
template_file: bytes = Field(default=bytes(), description="property")
sample_map: dict = Field(default={}, description="property")
@field_validator("name", mode="before")
@classmethod
def rescue_name_none(cls, value):
if not value:
return ""
return value
@field_validator("sample_map", mode="before")
@classmethod
def rescue_sample_map_none(cls, value):
if not value:
return {}
return value
@field_validator("defaults", mode="before")
@classmethod
def rescue_defaults_none(cls, value):
if not value:
return {}
return value
@field_validator("info_map", mode="before")
@classmethod
def rescue_info_map_none(cls, value):
if not value:
return {}
return value
def __init__(self, instance_object: Any, **data):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
return dict(
name=self.name
)
def to_sql(self):
instance, new = self.class_object.query_or_create(name=self.name)
instance.info_map = self.info_map
instance.defaults = self.defaults
instance.sample_map = self.sample_map
if self.template_file:
instance.template_file = self.template_file
return instance
class OmniReagentRole(BaseOmni):
class_object: ClassVar[Any] = ReagentRole
name: str = Field(default="", description="property")
eol_ext: timedelta = Field(default=timedelta(days=0), description="property")
@field_validator("name", mode="before")
@classmethod
def rescue_name_none(cls, value):
if not value:
return ""
return value
@field_validator("eol_ext", mode="before")
@classmethod
def rescue_eol_ext(cls, value):
if not value:
value = timedelta(days=0)
return value
def __init__(self, instance_object: Any, **data):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
return dict(
name=self.name
)
def to_sql(self):
instance, new = self.class_object.query_or_create(name=self.name)
if new:
instance.eol_ext = self.eol_ext
return instance
class OmniSubmissionTypeKitTypeAssociation(BaseOmni):
class_object: ClassVar[Any] = SubmissionTypeKitTypeAssociation
submissiontype: str | OmniSubmissionType = Field(default="", description="relationship", title="SubmissionType")
kittype: str | OmniKitType = Field(default="", description="relationship", title="KitType")
mutable_cost_column: float = Field(default=0.0, description="property")
mutable_cost_sample: float = Field(default=0.0, description="property")
constant_cost: float = Field(default=0.0, description="property")
# processes: List[OmniProcess] | List[str] = Field(default=[], description="relationship", title="Process")
def __repr__(self):
if isinstance(self.submissiontype, str):
submissiontype = self.submissiontype
else:
submissiontype = self.submissiontype.name
if isinstance(self.kittype, str):
kittype = self.kittype
else:
kittype = self.kittype.name
try:
return f"<{self.__class__.__name__}({submissiontype}&{kittype})>"
except AttributeError:
return f"<{self.__class__.__name__}(NO NAME)>"
@field_validator("submissiontype", mode="before")
@classmethod
def rescue_submissiontype_none(cls, value):
if not value:
return ""
return value
@field_validator("kittype", mode="before")
@classmethod
def rescue_kittype_none(cls, value):
if not value:
return ""
return value
@field_validator("kittype")
@classmethod
def no_list_please(cls, value):
if isinstance(value, list):
raise ValueError("List is not allowed for kittype.")
return value
def __init__(self, instance_object: Any, **data):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
if isinstance(self.submissiontype, OmniSubmissionType):
submissiontype = self.submissiontype.name
else:
submissiontype = self.submissiontype
if isinstance(self.kittype, OmniKitType):
kittype = self.kittype.name
else:
kittype = self.kittype
return dict(
submissiontype=submissiontype,
kittype=kittype,
mutable_cost_column=self.mutable_cost_column,
mutable_cost_sample=self.mutable_cost_sample,
constant_cost=self.constant_cost
)
def to_sql(self):
logger.debug(f"Self kittype: {self.submissiontype}")
if issubclass(self.submissiontype.__class__, BaseOmni):
submissiontype = SubmissionType.query(name=self.submissiontype.name)
else:
submissiontype = SubmissionType.query(name=self.submissiontype)
if issubclass(self.kittype.__class__, BaseOmni):
kittype = KitType.query(name=self.kittype.name)
else:
kittype = KitType.query(name=self.kittype)
# logger.debug(f"Self kittype: {self.kittype}")
# kittype = KitType.query(name=self.kittype)
logger.debug(f"Query or create with {kittype}, {submissiontype}")
instance, is_new = self.class_object.query_or_create(kittype=kittype, submissiontype=submissiontype)
instance.mutable_cost_column = self.mutable_cost_column
instance.mutable_cost_sample = self.mutable_cost_sample
instance.constant_cost = self.constant_cost
return instance
class OmniKitTypeReagentRoleAssociation(BaseOmni):
class_object: ClassVar[Any] = KitTypeReagentRoleAssociation
reagent_role: str | OmniReagentRole = Field(default="", description="relationship", title="ReagentRole")
uses: dict = Field(default={}, description="property")
required: bool = Field(default=True, description="property")
submission_type: str | OmniSubmissionType = Field(default="", description="relationship", title="SubmissionType")
kit_type: str | OmniKitType = Field(default="", description="relationship", title="KitType")
@field_validator("uses", mode="before")
@classmethod
def rescue_uses_none(cls, value):
if not value:
return {}
return value
def __init__(self, instance_object: Any, **data):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
if isinstance(self.submission_type, OmniSubmissionType):
submission_type = self.submission_type.name
else:
submission_type = self.submission_type
if isinstance(self.kit_type, OmniKitType):
kit_type = self.kit_type.name
else:
kit_type = self.kit_type
# name = f"{kit_type} -> {self.reagent_role}"
# logger.debug(f"Using name: {name}")
if isinstance(self.reagent_role, OmniReagentRole):
reagent_role = self.reagent_role.name
else:
reagent_role = self.reagent_role
return dict(
reagent_role=reagent_role,
# name=self.reagent_role.name,
submission_type=submission_type,
kit_type=kit_type
)
def to_sql(self):
if isinstance(self.reagent_role, OmniReagentRole):
reagent_role = self.reagent_role.name
else:
reagent_role = self.reagent_role
instance, new = self.class_object.query_or_create(
reagentrole=reagent_role,
kittype=self.kit_type,
submissiontype=self.submission_type
)
if new:
reagent_role = self.reagent_role.to_sql()
instance.reagent_role = reagent_role
logger.debug(f"KTRRAssoc uses: {self.uses}")
instance.uses = self.uses
logger.debug(f"KitTypeReagentRoleAssociation: {pformat(instance.__dict__)}")
return instance
class OmniEquipmentRole(BaseOmni):
class_object: ClassVar[Any] = EquipmentRole
name: str = Field(default="", description="property")
@field_validator("name", mode="before")
@classmethod
def rescue_name_none(cls, value):
if not value:
return ""
return value
def __init__(self, instance_object: Any, **data):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
return dict(
name=self.name
)
def to_sql(self):
instance, new = self.class_object.query_or_create(name=self.name)
return instance
class OmniTips(BaseOmni):
class_object: ClassVar[Any] = Tips
name: str = Field(default="", description="property")
@field_validator("name", mode="before")
@classmethod
def rescue_name_none(cls, value):
if not value:
return ""
return value
def __init__(self, instance_object: Any, **data):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
return dict(
name=self.name
)
def to_sql(self):
instance, new = self.class_object.query_or_create(name=self.name)
return instance
class OmniTipRole(BaseOmni):
class_object: ClassVar[Any] = TipRole
name: str = Field(default="", description="property")
tips: List[OmniTips] = Field(default=[], description="relationship", title="Tips")
@field_validator("name", mode="before")
@classmethod
def rescue_name_none(cls, value):
if not value:
return ""
return value
def __init__(self, instance_object: Any, **data):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
return dict(
name=self.name,
tips=[item.name for item in self.tips]
)
def to_sql(self):
instance, new = self.class_object.query_or_create(name=self.name)
for tips in self.tips:
tips.to_sql()
# if new_assoc not in instance.instances:
# instance.instances.append(new_assoc)
return instance
class OmniProcess(BaseOmni):
class_object: ClassVar[Any] = Process
# NOTE: How am I going to figure out relatioinships without getting into recursion issues?
name: str = Field(default="", description="property") #: Process name
submission_types: List[OmniSubmissionType] | List[str] = Field(default=[], description="relationship",
title="SubmissionType")
equipment_roles: List[OmniEquipmentRole] | List[str] = Field(default=[], description="relationship",
title="EquipmentRole")
tip_roles: List[OmniTipRole] | List[str] = Field(default=[], description="relationship", title="TipRole")
def __init__(self, instance_object: Any, **data):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
submissiontypes = [item.name for item in self.submission_types]
logger.debug(f"Submission Types: {submissiontypes}")
equipmentroles = [item.name for item in self.equipment_roles]
logger.debug(f"Equipment Roles: {equipmentroles}")
return dict(
name=self.name,
submission_types=submissiontypes,
equipment_roles=equipmentroles
)
@field_validator("name", mode="before")
@classmethod
def rescue_name_none(cls, value):
if not value:
return ""
return value
def to_sql(self):
instance, new = self.class_object.query_or_create(name=self.name)
for st in self.submission_types:
new_assoc = st.to_sql()
if new_assoc not in instance.submission_types:
instance.submission_types.append(new_assoc)
for er in self.equipment_roles:
new_assoc = er.to_sql()
if new_assoc not in instance.equipment_roles:
instance.equipment_roles.append(new_assoc)
for tr in self.tip_roles:
new_assoc = tr.to_sql()
if new_assoc not in instance.tip_roles:
instance.tip_roles.append(new_assoc)
return instance
class OmniKitType(BaseOmni):
class_object: ClassVar[Any] = KitType
name: str = Field(default="", description="property")
kit_submissiontype_associations: List[OmniSubmissionTypeKitTypeAssociation] | List[str] = Field(default=[],
description="relationship",
title="SubmissionTypeKitTypeAssociation")
kit_reagentrole_associations: List[OmniKitTypeReagentRoleAssociation] | List[str] = Field(default=[],
description="relationship",
title="KitTypeReagentRoleAssociation")
processes: List[OmniProcess] | List[str] = Field(default=[], description="relationship", title="Process")
@field_validator("name", mode="before")
@classmethod
def rescue_name_none(cls, value):
if not value:
return ""
return value
def __init__(self, instance_object: Any, **data):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
return dict(
name=self.name
)
def to_sql(self) -> KitType:
kit, is_new = KitType.query_or_create(name=self.name)
if is_new:
logger.debug(f"New kit made: {kit}")
else:
logger.debug(f"Kit retrieved: {kit}")
new_rr = []
for rr_assoc in self.kit_reagentrole_associations:
new_assoc = rr_assoc.to_sql()
if new_assoc not in new_rr:
logger.debug(f"Adding {new_assoc} to kit_reagentrole_associations")
new_rr.append(new_assoc)
logger.debug(f"Setting kit_reagentrole_associations to {new_rr}")
kit.kit_reagentrole_associations = new_rr
new_st = []
for st_assoc in self.kit_submissiontype_associations:
new_assoc = st_assoc.to_sql()
if new_assoc not in new_st:
new_st.append(new_assoc)
kit.kit_submissiontype_associations = new_st
new_processes = []
for process in self.processes:
new_process = process.to_sql()
if new_process not in new_processes:
new_processes.append(new_process)
kit.processes = new_processes
logger.debug(f"Kit: {pformat(kit.__dict__)}")
for item in kit.kit_reagentrole_associations:
logger.debug(f"KTRRassoc: {item.__dict__}")
return kit

View File

@@ -242,6 +242,7 @@ class App(QMainWindow):
from frontend.widgets.omni_manager_pydant import ManagerWindow as ManagerWindowPyd from frontend.widgets.omni_manager_pydant import ManagerWindow as ManagerWindowPyd
dlg = ManagerWindowPyd(parent=self, object_type=KitType, extras=[], add_edit='edit', managers=set()) dlg = ManagerWindowPyd(parent=self, object_type=KitType, extras=[], add_edit='edit', managers=set())
if dlg.exec(): if dlg.exec():
logger.debug("\n\nBeginning parsing\n\n")
output = dlg.parse_form() output = dlg.parse_form()
# assert isinstance(output, KitType) # assert isinstance(output, KitType)
# output.save() # output.save()
@@ -249,6 +250,11 @@ class App(QMainWindow):
# output.to_sql() # output.to_sql()
with open(f"{output.name}.obj", "wb") as f: with open(f"{output.name}.obj", "wb") as f:
pickle.dump(output, f) pickle.dump(output, f)
logger.debug("\n\nBeginning transformation\n\n")
sql = output.to_sql()
with open(f"{output.name}.sql", "wb") as f:
pickle.dump(sql, f)
sql.save()
class AddSubForm(QWidget): class AddSubForm(QWidget):

View File

@@ -0,0 +1,680 @@
"""
Provides a screen for managing all attributes of a database object.
"""
import json, logging
import sys
from json.decoder import JSONDecodeError
from datetime import datetime, timedelta
from pprint import pformat
from typing import Any, List, Literal
from PyQt6.QtCore import QSortFilterProxyModel, Qt
from PyQt6.QtGui import QAction, QCursor
from PyQt6.QtWidgets import (
QLabel, QDialog,
QTableView, QWidget, QLineEdit, QGridLayout, QComboBox, QPushButton, QDialogButtonBox, QDateEdit, QMenu,
QDoubleSpinBox, QSpinBox, QCheckBox, QTextEdit, QVBoxLayout, QHBoxLayout
)
from pandas import DataFrame
from backend import db
from tools import check_object_in_manager
from .omni_search import SearchBox
from frontend.widgets.submission_table import pandasModel
logger = logging.getLogger(f"submissions.{__name__}")
class ManagerWindow(QDialog):
"""
Initially this is a window to manage Organization Contacts, but hope to abstract it more later.
"""
def __init__(self, parent,
extras: List[str],
instance: Any | None = None,
object_type: Any | None = None,
manager: Any | None = None,
add_edit: Literal['add', 'edit'] = 'edit',
**kwargs):
super().__init__(parent)
# NOTE: Should I pass in an instance?
self.instance = instance
logger.debug(f"Setting instance: {self.instance}")
if not self.instance:
self.class_object = self.original_type = object_type
else:
self.class_object = self.original_type = self.instance.__class__
self.add_edit = add_edit
if manager is None:
try:
self.manager = self.parent().omni_object
except AttributeError:
self.manager = None
else:
self.manager = manager
# logger.debug(f"Manager: {manager}")
self.extras = extras
self.context = kwargs
self.layout = QGridLayout(self)
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.setMinimumSize(600, 600)
sub_classes = ["Any"] + [cls.__name__ for cls in self.class_object.__subclasses__()]
if len(sub_classes) > 1:
self.sub_class = QComboBox(self)
self.sub_class.setObjectName("sub_class")
self.sub_class.addItems(sub_classes)
self.sub_class.currentTextChanged.connect(self.update_options)
self.sub_class.setEditable(False)
self.sub_class.setMinimumWidth(self.minimumWidth())
self.layout.addWidget(self.sub_class, 0, 0)
else:
self.sub_class = None
self.update_instance(initial=True)
if self.add_edit == "edit":
self.options = QComboBox(self)
self.options.setObjectName("options")
self.update_options()
else:
self.update_data()
self.setLayout(self.layout)
self.setWindowTitle(f"Manage {self.class_object.__name__} - Manager: {self.manager}")
def update_options(self) -> None:
"""
Changes form inputs based on sample type
"""
# logger.debug(f"Instance: {self.instance}")
if self.sub_class:
self.class_object = getattr(db, self.sub_class.currentText())
# logger.debug(f"From update options, managers: {self.managers}")
try:
query_kwargs = {self.parent().instance.query_alias: self.parent().instance}
except AttributeError as e:
# logger.debug(f"Couldn't set query kwargs due to: {e}")
query_kwargs = {}
# logger.debug(f"Query kwargs: {query_kwargs}")
# logger.debug(f"self.class_object: {self.class_object}")
options = [item.name for item in self.class_object.query(**query_kwargs)]
if self.instance:
try:
inserter = options.pop(options.index(self.instance.name))
except ValueError:
inserter = self.instance.name
options.insert(0, inserter)
self.options.clear()
self.options.addItems(options)
self.options.setEditable(False)
self.options.setMinimumWidth(self.minimumWidth())
self.layout.addWidget(self.options, 1, 0, 1, 1)
# if len(options) > 0:
self.add_button = QPushButton("Add New")
self.layout.addWidget(self.add_button, 1, 1, 1, 1)
self.add_button.clicked.connect(self.add_new)
self.options.currentTextChanged.connect(self.update_instance)
# logger.debug(f"Instance: {self.instance}")
self.update_data()
def update_instance(self, initial: bool = False):
if self.add_edit == "edit" or initial:
try:
# logger.debug(f"Querying with {self.options.currentText()}")
self.instance = self.class_object.query(name=self.options.currentText(), limit=1)
except AttributeError:
# self.instance = None
pass
# logger.debug(f"Instance: {self.instance}")
if not self.instance:
logger.warning(f"Instance not found, creating blank instance.")
self.instance = self.class_object()
# logger.debug(f"self.instance: {self.instance}")
if issubclass(self.instance.__class__, db.BaseClass):
self.omni_object = self.instance.to_omni(expand=True)
else:
self.omni_object = self.instance
logger.debug(f"Created omni_object: {self.omni_object.__dict__}")
self.update_data()
def update_data(self) -> None:
"""
Performs updating of widgets on first run and after options change.
Returns:
None
"""
# NOTE: Remove all old widgets.
deletes = [item for item in self.findChildren(EditProperty)] + \
[item for item in self.findChildren(EditRelationship)] + \
[item for item in self.findChildren(QDialogButtonBox)]
for item in deletes:
item.setParent(None)
fields = self.omni_object.__class__.model_fields
for key, info in fields.items():
# logger.debug(f"Attempting to set {key}, {info} widget")
try:
value = getattr(self.omni_object, key)
except AttributeError:
value = None
match info.description:
# NOTE: ColumnProperties will be directly edited.
case "property":
# NOTE: field.property.expression.type gives db column type eg. STRING or TIMESTAMP
# logger.debug(f"Creating property widget with value: {value}")
widget = EditProperty(self, key=key, column_type=info, value=value)
# NOTE: RelationshipDeclareds will be given a list of existing related objects.
case "relationship":
# NOTE: field.comparator.class_object.class_ gives the relationship class
# logger.debug(f"Creating relationship widget with value: {value}")
widget = EditRelationship(self, key=key, class_object=info.title, value=value)
case _:
continue
if widget:
self.layout.addWidget(widget, self.layout.rowCount(), 0, 1, 2)
# NOTE: Add OK|Cancel to bottom of dialog.
self.layout.addWidget(self.buttonBox, self.layout.rowCount(), 0, 1, 2)
def parse_form(self) -> Any:
"""
Returns the instance associated with this window.
Returns:
Any: The instance with updated fields.
"""
# TODO: Need Relationship property here too?
results = [item.parse_form() for item in self.findChildren(EditProperty)]
for result in results:
logger.debug(f"Incoming property result: {result}")
setattr(self.omni_object, result['field'], result['value'])
# NOTE: Getting 'None' back here.
logger.debug(f"Set result: {getattr(self.instance, result['field'])}")
results = [item.parse_form() for item in self.findChildren(EditRelationship)]
for result in results:
logger.debug(f"Incoming relationship result: {result}")
# if not getattr(self.omni_object, result['field']):
setattr(self.omni_object, result['field'], result['value'])
# logger.debug(f"Set result: {getattr(self.omni_object, result['field'])}")
# logger.debug(f"Instance coming from parsed form: {self.omni_object.__dict__}")
return self.omni_object
def add_new(self):
new_instance = self.class_object()
self.instance = new_instance
self.update_options()
class EditProperty(QWidget):
def __init__(self, parent: ManagerWindow, key: str, column_type: Any, value):
super().__init__(parent)
self.label = QLabel(key.title().replace("_", " "))
self.layout = QGridLayout()
self.layout.addWidget(self.label, 0, 0, 1, 1)
self.setObjectName(key)
# logger.debug(f"Column type for {key}: {type(column_type.default)}")
match column_type.default:
case str():
self.widget = QLineEdit(self)
self.widget.setText(value)
case bool():
if isinstance(column_type.default, bool):
self.widget = QCheckBox()
self.widget.setChecked(value)
else:
if value is None:
value = 0
self.widget = QSpinBox()
self.widget.setMaximum(1)
self.widget.setValue(value)
case float():
if not value:
value = 0.0
self.widget = QDoubleSpinBox()
self.widget.setMaximum(999.99)
self.widget.setValue(value)
case datetime():
self.widget = QDateEdit(self)
self.widget.setDate(value)
case timedelta():
self.widget = QSpinBox()
self.widget.setMaximum(9999)
self.widget.setToolTip("This time interval is measured in days.")
self.widget.setValue(value.days)
case dict():
self.widget = JsonEditButton(parent=self, key=key, value=value)
# self.widget.viewButton.clicked.connect(lambda: self.parent().toggle_textedit(self.widget))
# self.widget.addButton.clicked.connect(lambda: self.parent().add_to_json(self.widget))
case bytes():
self.widget = QLabel("BLOB Under construction")
case _:
self.widget = None
self.layout.addWidget(self.widget, 0, 1, 1, 3)
self.setLayout(self.layout)
def parse_form(self):
# logger.debug(f"Parsing widget {self.objectName()}: {type(self.widget)}")
match self.widget:
case QLineEdit():
value = self.widget.text()
case QDateEdit():
value = self.widget.date()
case QSpinBox() | QDoubleSpinBox():
value = self.widget.value()
case QCheckBox():
value = self.widget.isChecked()
case JsonEditButton():
value = self.widget.data
case _:
value = None
return dict(field=self.objectName(), value=value)
class EditRelationship(QWidget):
def __init__(self, parent, key: str, class_object: Any, value):
from backend.db import models
super().__init__(parent)
self.class_object = getattr(models, class_object)
logger.debug(f"Class object: {self.class_object}")
self.setParent(parent)
# logger.debug(f"Edit relationship class_object: {self.class_object}")
self.label = QLabel(key.title().replace("_", " "))
self.setObjectName(key) #: key is the name of the relationship this represents
logger.debug(f"Checking relationship for {self.parent().class_object}: {key}")
# try:
# self.relationship = getattr(self.parent().instance.__class__, key)
self.relationship = getattr(self.parent().class_object, key)
#: relationship object for type differentiation
# except AttributeError:
# logger.warning(f"Could not get relationship for: {key}.")
# self.relationship = None
self.widget = QTableView()
self.add_button = QPushButton("Add New")
self.add_button.clicked.connect(self.add_new)
self.existing_button = QPushButton("Add Existing")
self.existing_button.clicked.connect(self.add_existing)
# self.existing_button.setEnabled(self.class_object.level == 1)
if not isinstance(value, list):
if value not in [None, ""]:
value = [value]
else:
value = []
self.data = value
logger.debug(f"Set data: {self.data}")
# self.update_buttons()
logger.debug(f"Parent manager: {self.parent().manager}")
checked_manager, is_primary = check_object_in_manager(self.parent().manager, self.objectName())
if checked_manager:
logger.debug(f"Checked manager for {self.objectName()}: {checked_manager}")
# logger.debug(f"Omni will inherit: {self.class_object.omni_inheritable} from {self.parent().class_object}")
# if checked_manager is not None:# and not self.data:# and self.objectName() in self.parent().class_object.omni_inheritable:
# # logger.debug(f"Setting {checked_manager} in self.data")
# # if isinstance(checked_manager, InstrumentedList):
# # self.data = [item.to_omni() for item in checked_manager]
# # else:
# # self.data = [checked_manager.to_omni()]
# self.data = [checked_manager]
if not self.data:
self.data = [checked_manager]
try:
logger.debug(f"Relationship {key} uses list: {self.relationship.property.uselist}")
check = not self.relationship.property.uselist and len(self.data) >= 1
except AttributeError:
check = True
if check:
self.add_button.setEnabled(False)
self.existing_button.setEnabled(False)
if is_primary:
self.widget.setEnabled(False)
else:
self.add_button.setEnabled(True)
self.existing_button.setEnabled(True)
if is_primary:
self.widget.setEnabled(True)
self.layout = QGridLayout()
self.layout.addWidget(self.label, 0, 0, 1, 5)
self.layout.addWidget(self.widget, 1, 0, 1, 8)
self.layout.addWidget(self.add_button, 0, 6, 1, 1, alignment=Qt.AlignmentFlag.AlignRight)
self.layout.addWidget(self.existing_button, 0, 7, 1, 1, alignment=Qt.AlignmentFlag.AlignRight)
self.setLayout(self.layout)
self.set_data()
def update_buttons(self):
if not self.relationship.property.uselist and len(self.data) >= 1:
# logger.debug(f"Property {self.relationship} doesn't use list and data is of length: {len(self.data)}")
self.add_button.setEnabled(False)
self.existing_button.setEnabled(False)
else:
self.add_button.setEnabled(True)
self.existing_button.setEnabled(True)
def parse_row(self, x):
context = {item: x.sibling(x.row(), self.df.columns.get_loc(item)).data() for item in self.df.columns}
try:
object = self.class_object.query(**context)
except KeyError:
object = None
self.widget.doubleClicked.disconnect()
self.add_edit(instance=object)
def add_new(self, instance: Any = None, add_edit: Literal["add", "edit"] = "add"):
if add_edit == "edit":
logger.debug(f"Editing instance: {instance.__dict__}")
# NOTE: if an existing instance is not being edited, create a new instance
if not instance:
# logger.debug(f"Creating new instance of {self.class_object}")
instance = self.class_object()
# logger.debug(f"Creating manager window for {instance}")
manager = self.parent().manager
# logger.debug(f"Managers going into add new: {managers}")
# dlg = ManagerWindow(self.parent(), object_type=instance.__class__, extras=[], manager=manager, add_edit="add")
dlg = ManagerWindow(self.parent(), instance=instance, extras=[], manager=manager, add_edit=add_edit)
if dlg.exec():
new_instance = dlg.parse_form()
logger.debug(f"New instance: {pformat(new_instance.__dict__)}")
# NOTE: Somewhere between this and the next logger, I'm losing the uses data.
if add_edit == "add":
self.parent().omni_object.__setattr__(self.objectName(), new_instance)
else:
instance.__dict__.update(new_instance.__dict__)
logger.debug(f"Final instance: {pformat(instance.__dict__)}")
# self.parent().instance.save()
self.parent().update_data()
def add_existing(self):
dlg = SearchBox(self, object_type=self.class_object, returnable=True, extras=[])
if dlg.exec():
rows = dlg.return_selected_rows()
for row in rows:
# logger.debug(f"Querying with {row}")
instance = self.class_object.query(**row)
# NOTE: My custom __setattr__ should take care of any list problems.
if isinstance(instance, list):
instance = instance[0]
self.parent().omni_object.__setattr__(self.objectName(), instance.to_omni())
# self.parent().instance.save()
self.parent().update_data()
def set_data(self) -> None:
"""
sets data in model
"""
# logger.debug(f"Self.data: {self.data}")
try:
# records = [{k: v['instance_attr'] for k, v in item.omnigui_instance_dict.items()} for item in self.data]
records = [item.to_dataframe_dict() for item in self.data]
except AttributeError:
records = []
# logger.debug(f"Records: {records}")
self.df = DataFrame.from_records(records)
try:
self.columns_of_interest = [dict(name=item, column=self.df.columns.get_loc(item)) for item in self.extras]
except (KeyError, AttributeError):
self.columns_of_interest = []
try:
self.df['id'] = self.df['id'].apply(str)
self.df['id'] = self.df['id'].str.zfill(4)
except KeyError as e:
logger.error(f"Could not alter id to string due to KeyError: {e}")
proxy_model = QSortFilterProxyModel()
proxy_model.setSourceModel(pandasModel(self.df))
self.widget.setModel(proxy_model)
self.widget.resizeColumnsToContents()
self.widget.resizeRowsToContents()
self.widget.setSortingEnabled(True)
self.widget.doubleClicked.connect(self.parse_row)
# self.update_buttons()
def contextMenuEvent(self, event):
"""
Creates actions for right click menu events.
Args:
event (_type_): the item of interest
"""
if not self.widget.isEnabled():
logger.warning(f"{self.objectName()} is disabled.")
return
id = self.widget.selectionModel().currentIndex()
logger.debug(f"Row id: {id.row()}")
# NOTE: the overly complicated {column_name: row_value} dictionary construction
row_data = {self.df.columns[column]: self.widget.model().index(id.row(), column).data() for column in
range(self.widget.model().columnCount())}
# logger.debug(f"Row data: {row_data}")
logger.debug(f"Attempting to grab {self.objectName()} from {self.parent().omni_object}")
object = getattr(self.parent().omni_object, self.objectName())
logger.debug(f"Initial object: {object}")
if isinstance(object, list):
try:
object = next((item for item in object if item.check_all_attributes(attributes=row_data)))
except StopIteration:
logger.warning(f"Failed to find all attributes equal, getting row {id.row()}")
object = object[id.row()]
object.instance_object = object.to_sql()
logger.debug(f"Object of interest: {pformat(object.__dict__)}")
self.menu = QMenu(self)
try:
remove_action = QAction(f"Remove {object.name}", self)
except AttributeError:
remove_action = QAction(f"Remove object", self)
remove_action.triggered.connect(lambda: self.remove_item(object=object))
self.menu.addAction(remove_action)
try:
edit_action = QAction(f"Edit {object.name}", self)
except AttributeError:
edit_action = QAction(f"Edit object", self)
edit_action.triggered.connect(lambda: self.add_new(instance=object.instance_object, add_edit="edit"))
self.menu.addAction(edit_action)
self.menu.popup(QCursor.pos())
def remove_item(self, object):
logger.debug(f"Attempting to remove {object} from {self.parent().instance.__dict__}")
# editor = getattr(self.parent().instance, self.objectName().lower())
editor = getattr(self.parent().omni_object, self.objectName().lower())
logger.debug(f"Editor: {editor}")
# if object == self.parent().manager:
# logger.error(f"Can't remove manager object.")
# return
# logger.debug(f"Object: {object}")
# try:
# self.data.remove(object)
# except (AttributeError, ValueError) as e:
# logger.error(f"Couldn't remove object from self.data due to: {e}")
# self.data = []
try:
# logger.debug(f"Using remove technique")
editor.remove(object)
except AttributeError as e:
logger.error(f"Remove failed using set to None for {self.objectName().lower()}.")
setattr(self.parent().omni_object, self.objectName().lower(), None)
except ValueError as e:
logger.error(f"Remove failed for {self.objectName().lower()} due to {e}.")
logger.debug(f"Setting {self.objectName()} to {editor}")
setattr(self.parent().omni_object, self.objectName().lower(), editor)
logger.debug(f"After set: {getattr(self.parent().omni_object, self.objectName().lower())}")
# self.parent().instance.save()
# self.parent().update_data()
self.set_data()
self.update_buttons()
def parse_form(self):
# logger.debug(f"Returning parsed form data from {self.objectName()}: {self.data}")
try:
check = self.relationship.property.uselist
except AttributeError:
check = False
if check and isinstance(self.data, list):
output_data = self.data[0]
else:
output_data = self.data
return dict(field=self.objectName(), value=output_data)
class JsonEditButton(QWidget):
def __init__(self, parent, key: str, value: str = ""):
super().__init__(parent)
logger.debug(f"Setting jsonedit data to: {value}")
self.data = value
self.setParent(parent)
self.setObjectName(key)
self.addButton = QPushButton("Add Entry", parent=self)
self.addButton.clicked.connect(self.add_to_json)
self.viewButton = QPushButton("View >>>", parent=self)
self.viewButton.clicked.connect(self.toggle_textedit)
self.layout = QGridLayout()
self.layout.addWidget(self.addButton, 0, 0)
self.layout.addWidget(self.viewButton, 0, 1)
self.setLayout(self.layout)
self.edit_box = LargeTextEdit(parent=self, key=key)
self.parent().parent().layout.addWidget(self.edit_box, 1, self.parent().parent().layout.columnCount(),
self.parent().parent().layout.rowCount() - 1, 1)
self.edit_box.setVisible(False)
self.edit_box.widget.textChanged.connect(self.set_json_to_text)
def set_json_to_text(self):
logger.debug(self.edit_box.widget.toPlainText())
text = self.edit_box.widget.toPlainText()
try:
jsoner = json.loads(text)
except JSONDecodeError:
jsoner = None
if jsoner:
self.data = jsoner
def add_to_json(self):
jsonedit = JsonEditScreen(parent=self, parameter=self.objectName())
if jsonedit.exec():
data = jsonedit.parse_form()
# logger.debug(f"Data: {pformat(data)}")
self.data = data
def toggle_textedit(self):
self.edit_box.setVisible(not self.edit_box.isVisible())
# data = getattr(self.omni_object, name)
# logger.debug(f"Data: {data}")
data = json.dumps(self.data, indent=4)
self.edit_box.widget.setText(data)
class JsonEditScreen(QDialog):
def __init__(self, parent, parameter: str):
super().__init__(parent)
self.class_obj = parent.parent().parent().class_object
self.layout = QGridLayout()
# logger.debug(f"Parameter: {parameter}")
self.setWindowTitle(parameter)
try:
self.json_field = getattr(self.class_obj, f"{parameter}_json_edit_fields")
except AttributeError:
self.json_field = self.class_obj.json_edit_fields
match self.json_field:
case dict():
for key, value in self.json_field.items():
# logger.debug(f"Key: {key}, Value: {value}")
row = self.layout.rowCount()
self.layout.addWidget(QLabel(key), row, 0)
match value:
case "int":
self.widget = QSpinBox()
case "str":
self.widget = QLineEdit()
case dict():
self.widget = DictionaryJsonSubEdit(parent=self, key=key, dic=value)
case _:
continue
self.widget.setObjectName(key)
self.layout.addWidget(self.widget, row, 1)
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout.addWidget(self.buttonBox, self.layout.rowCount(), 0, 1, 2)
self.setLayout(self.layout)
def parse_form(self):
widgets = [item for item in self.findChildren(QWidget) if item.objectName() in self.json_field.keys()]
# logger.debug(f"Widgets: {widgets}")
# logger.debug(type(self.json_field))
if isinstance(self.json_field, dict):
output = {}
elif isinstance(self.json_field, list):
output = []
else:
raise ValueError(f"Inappropriate data type: {type(self.json_field)}")
for widget in widgets:
# logger.debug(f"JsonEditScreen Widget: {widget}")
key = widget.objectName()
match widget:
case QSpinBox():
value = widget.value()
case QLineEdit():
value = widget.text()
case DictionaryJsonSubEdit():
value = widget.parse_form()
case _:
continue
if isinstance(self.json_field, dict):
output[key] = value
elif isinstance(self.json_field, list):
if isinstance(value, list):
output += value
else:
output.append(value)
else:
raise ValueError(f"Inappropriate data type: {type(self.json_field)}")
# output[key] = value
return output
class DictionaryJsonSubEdit(QWidget):
def __init__(self, parent, key, dic: dict):
super().__init__(parent)
self.layout = QHBoxLayout()
self.setObjectName(key)
self.data = dic
for key, value in self.data.items():
self.layout.addWidget(QLabel(key))
match value:
case "int":
self.widget = QSpinBox()
case "str":
self.widget = QLineEdit()
case dict():
self.widget = DictionaryJsonSubEdit(parent, key=key, dic=value)
self.widget.setObjectName(key)
self.layout.addWidget(self.widget)
self.setLayout(self.layout)
def parse_form(self):
widgets = [item for item in self.findChildren(QWidget) if item.objectName() in self.data.keys()]
# logger.debug(f"Widgets: {widgets}")
output = {}
for widget in widgets:
# logger.debug(f"DictionaryJsonSubEdit Widget: {widget}")
key = widget.objectName()
match widget:
case QSpinBox():
value = widget.value()
case QLineEdit():
value = widget.text()
case DictionaryJsonSubEdit():
value = widget.parse_form()
case _:
continue
output[key] = value
return output
class LargeTextEdit(QWidget):
def __init__(self, parent, key: str):
super().__init__(parent)
self.setParent(parent)
self.setObjectName(key)
self.widget = QTextEdit()
self.layout = QVBoxLayout()
self.layout.addWidget(self.widget)
self.setLayout(self.layout)