def _copy_object_to_dump(self, src_obj: object) -> None: """ Copy the SQLAlchemy ORM object to the dump. """ # noinspection PyUnresolvedReferences src_table = src_obj.__table__ # type: Table # 1. Insert row for this object, potentially adding and removing # columns. tablename = src_table.name dst_table = self.dst_tables[tablename] assert dst_table.name == tablename row = {} # type: Dict[str, Any] # Copy columns, skipping any we don't want for attrname, column in gen_columns(src_obj): if self._dump_skip_column(tablename, column.name): continue row[column.name] = getattr(src_obj, attrname) # Any other columns to add for this table? if isinstance(src_obj, GenericTabletRecordMixin): for summary_element in src_obj.get_summaries(self.req): row[summary_element.name] = summary_element.value self.dst_session.execute(dst_table.insert(row)) # 2. If required, add extra tables/rows that this task wants to # offer (usually tables whose rows don't have a 1:1 correspondence # to the task or its ancillary objects). if isinstance(src_obj, Task): estables = src_obj.get_all_summary_tables(self.req) # ... includes SNOMED for est in estables: dst_summary_table = self._get_or_insert_summary_table(est) for row in est.rows: self.dst_session.execute(dst_summary_table.insert(row))
def make_xml_branches_from_columns(obj, skip_fields: List[str] = None ) -> List[XmlElement]: """ Returns a list of XML branches, each an :class:`camcops_server.cc_modules.cc_xml.XmlElement`, from an SQLAlchemy ORM object, using the list of SQLAlchemy Column objects that define/describe its fields. Args: obj: the SQLAlchemy ORM object skip_fields: database column names to skip """ skip_fields = skip_fields or [] # type: List[str] branches = [] # type: List[XmlElement] for attrname, column in gen_columns(obj): # log.debug("make_xml_branches_from_columns: {!r}", attrname) colname = column.name if colname in skip_fields: continue branches.append( XmlElement( name=colname, value=getattr(obj, attrname), datatype=get_xml_datatype_from_sqla_column(column), comment=column.comment, )) return branches
def get_attrnames(self) -> List[str]: """ Returns all relevant attribute names. """ attrnames = set([attrname for attrname, _ in gen_columns(self)]) attrnames.update(key for key in self.__dict__ if not key.startswith('_')) # noqa return sorted(attrnames)
def _get_core_tsv_page(self, req: "CamcopsRequest", heading_prefix: str = "") -> TsvPage: """ Returns a single-row :class:`camcops_server.cc_modules.cc_tsv.TsvPage`, like an Excel "sheet", representing this record. (It may be combined with others later to produce a multi-row spreadsheet.) """ row = OrderedDict() for attrname, column in gen_columns(self): row[heading_prefix + attrname] = getattr(self, attrname) for s in self.get_summaries(req): row[heading_prefix + s.name] = s.value return TsvPage(name=self.__tablename__, rows=[row])
def _get_core_spreadsheet_schema( self, table_name: str = "", column_name_prefix: str = "") -> Set[SummarySchemaInfo]: """ Returns schema information compatible with :func:`_get_core_spreadsheet_page`. """ return set( SummarySchemaInfo.from_column( column, table_name=table_name, column_name_prefix=column_name_prefix, ) for _, column in gen_columns(self))
def manually_erase_with_dependants(self, req: "CamcopsRequest") -> None: """ Manually erases a standard record and marks it so erased. Iterates through any dependants and does likewise to them. The object remains ``_current`` (if it was), as a placeholder, but its contents are wiped. WRITES TO THE DATABASE. """ if self._manually_erased or self._pk is None or self._era == ERA_NOW: # ... _manually_erased: don't do it twice # ... _pk: basic sanity check # ... _era: don't erase things that are current on the tablet return # 1. "Erase my dependants" for ancillary in self.gen_ancillary_instances_even_noncurrent(): ancillary.manually_erase_with_dependants(req) for blob in self.gen_blobs_even_noncurrent(): blob.manually_erase_with_dependants(req) # 2. "Erase me" erasure_attrs = [] # type: List[str] for attrname, column in gen_columns(self): if attrname.startswith("_"): # system field continue if not column.nullable: # this should cover FKs continue if column.foreign_keys: # ... but to be sure... continue erasure_attrs.append(attrname) for attrname in erasure_attrs: setattr(self, attrname, None) self._current = False self._manually_erased = True self._manually_erased_at = req.now self._manually_erasing_user_id = req.user_id
def __repr__(self) -> str: attrnames = sorted(attrname for attrname, _ in gen_columns(self)) return simple_repr(self, attrnames)
def _copy_object_to_dump(self, src_obj: object) -> None: """ Copy the SQLAlchemy ORM object to the dump. """ # noinspection PyUnresolvedReferences src_table = src_obj.__table__ # type: Table adding_extra_ids = False patient = None # type: Optional[Patient] if self.export_options.db_patient_id_in_each_row: adding_extra_ids, patient = self._merits_extra_id_num_columns( src_obj) # 1. Insert row for this object, potentially adding and removing # columns. tablename = src_table.name dst_table = self.dst_tables[tablename] assert dst_table.name == tablename row = {} # type: Dict[str, Any] # Copy columns, skipping any we don't want for attrname, column in gen_columns(src_obj): if self._dump_skip_column(tablename, column.name): continue row[column.name] = getattr(src_obj, attrname) # Any other columns to add for this table? if isinstance(src_obj, GenericTabletRecordMixin): if self.export_options.db_include_summaries: for summary_element in src_obj.get_summaries(self.req): row[summary_element.name] = summary_element.value if adding_extra_ids: if patient: patient.add_extra_idnum_info_to_row(row) if isinstance(src_obj, TaskDescendant): src_obj.add_extra_task_xref_info_to_row(row) try: self.dst_session.execute(dst_table.insert(row)) except CompileError: log.critical("\ndst_table:\n{}\nrow:\n{}", dst_table, row) raise # 2. If required, add extra tables/rows that this task wants to # offer (usually tables whose rows don't have a 1:1 correspondence # to the task or its ancillary objects). if isinstance(src_obj, Task): estables = src_obj.get_all_summary_tables(self.req) # ... includes SNOMED for est in estables: dst_summary_table = self._get_or_insert_summary_table( est, add_extra_id_cols=adding_extra_ids) for row in est.rows: if patient: patient.add_extra_idnum_info_to_row(row) if adding_extra_ids: est.add_extra_task_xref_info_to_row(row) try: self.dst_session.execute(dst_summary_table.insert(row)) except CompileError: log.critical( "\ndst_summary_table:\n{}\nrow:\n{}", dst_table, row, ) raise