15.2.106. camcops_server.cc_modules.cc_db

camcops_server/cc_modules/cc_db.py


Copyright (C) 2012, University of Cambridge, Department of Psychiatry. Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

This file is part of CamCOPS.

CamCOPS is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

CamCOPS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.


Common database code, e.g. mixins for tables that are uploaded from the client.

class camcops_server.cc_modules.cc_db.GenericTabletRecordMixin[source]

Mixin for all tables that are uploaded from the client, representing the fields that the server adds at the point of upload.

From the server’s perspective, _pk is the unique primary key.

However, records are defined also in their tablet context, for which an individual tablet (defined by the combination of _device_id and _era) sees its own PK, id.

create_fresh(req: CamcopsRequest, device_id: int, era: str, group_id: int) None[source]

Used to create a record from scratch.

created_on_server(req: CamcopsRequest) bool[source]

Was this record created on the server?

delete_with_dependants(req: CamcopsRequest) None[source]

Deletes (completely from the database) this record and any dependant records.

property device_id: Optional[int]

Returns the client device ID of this record.

property era: Optional[str]

Returns the era of this record (a text representation of the date/time of the point of record finalization, or NOW if the record is still present on the client device).

gen_ancillary_instances() Generator[camcops_server.cc_modules.cc_db.GenericTabletRecordMixin, None, None][source]

Generates all _current ancillary objects of this object.

gen_ancillary_instances_even_noncurrent() Generator[camcops_server.cc_modules.cc_db.GenericTabletRecordMixin, None, None][source]

Generates all ancillary objects of this object, even non-current ones.

gen_attrname_ancillary_pairs() Generator[Tuple[str, camcops_server.cc_modules.cc_db.GenericTabletRecordMixin], None, None][source]

Iterates through and yields all _current “ancillary” objects (typically: records of subtables).

Yields tuples of (attrname, related_record).

gen_blobs() Generator[Blob, None, None][source]

Generate all _current BLOBs owned by this object.

gen_blobs_even_noncurrent() Generator[Blob, None, None][source]

Generates all BLOBs owned by this object, even non-current ones.

get_lineage() List[camcops_server.cc_modules.cc_db.GenericTabletRecordMixin][source]

Returns all records that are part of the same “lineage”, that is:

  • of the same class;

  • matching on id/device_id/era;

  • including both current and any historical non-current versions.

Will include the “self” object.

classmethod get_linked(client_id: Optional[int], other: camcops_server.cc_modules.cc_db.GenericTabletRecordMixin) Optional[camcops_server.cc_modules.cc_db.GenericTabletRecordMixin][source]

Returns a specific linked record, of the class of self, whose client-side ID is client_id, and which matches other in terms of device/era.

get_summaries(req: CamcopsRequest) List[SummaryElement][source]

Return a list of SummaryElement objects, for this database object (not any dependent classes/tables).

Note that this is implemented on GenericTabletRecordMixin, not camcops_server.cc_modules.cc_task.Task, so that ancillary objects can also provide summaries.

get_summary_names(req: CamcopsRequest) List[str][source]

Returns a list of summary field names.

property group_id: Optional[int]

Returns the group ID of this record.

is_finalized() bool[source]

Is the record finalized (no longer available to be edited on the client device), and therefore (if required) editable on the server?

is_live_on_tablet() bool[source]

Is the record live on a tablet (not finalized)?

manually_erase_with_dependants(req: CamcopsRequest) None[source]

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.

mark_as_deleted(req: CamcopsRequest) None[source]

Ends the history chain and marks this record as non-current.

property pk: Optional[int]

Returns the (server) primary key of this record.

save_with_next_available_id(req: CamcopsRequest, device_id: int, era: str = 'NOW') None[source]

Save a record with the next available client pk in sequence. This is of use when creating patients and ID numbers on the server to ensure uniqueness, or when fixing up a missing ID number for a patient created on a device.

set_predecessor(req: CamcopsRequest, predecessor: GenericTabletRecordMixin) None[source]

Used for some unusual server-side manipulations (e.g. editing patient details).

Amends this object so the “self” object replaces the predecessor, so:

  • “self” becomes current and refers back to “predecessor”;

  • “predecessor” becomes non-current and refers forward to “self”.

class camcops_server.cc_modules.cc_db.TaskDescendant[source]

Information mixin for sub-tables that can be traced back to a class. Used to denormalize the database for export in some circumstances.

Not used for the Blob class, which has no reasonable way of tracing itself back to a given task if it is used by a task’s ancillary tables rather than a primary task row.

add_extra_task_xref_info_to_row(row: Dict[str, Any]) None[source]

For the DB_PATIENT_ID_PER_ROW export option. Adds additional cross-referencing info to a row.

Parameters

row – future database row, as a dictionary

classmethod extra_task_xref_columns() List[sqlalchemy.sql.schema.Column][source]

Returns extra columns used to cross-reference this TaskDescendant to its ancestor task, in certain export formats (DB_PATIENT_ID_PER_ROW).

task_ancestor() Optional[Task][source]

Returns the specific ancestor task of this object.

classmethod task_ancestor_class() Optional[Type[Task]][source]

Returns the class of the ancestral task.

If the descendant can descend from lots of types of task (rare; only applies to camcops_server.cc_modules.cc_blob.Blob and camcops_server.cc_modules.cc_summaryelement.ExtraSummaryTable), returns None.

classmethod task_ancestor_might_have_patient() bool[source]

Does this object have a single task ancestor, that is not anonymous?

task_ancestor_patient() Optional[Patient][source]

Returns the associated patient, if there is one.

task_ancestor_server_pk() Optional[int][source]

Returns the server PK of the ancestral task.

Note that this is an export-time calculation; the client may update its task rows without updating its descendant rows (so server PKs change whilst client IDs don’t).

camcops_server.cc_modules.cc_db.add_multiple_columns(cls: Type, prefix: str, start: int, end: int, coltype=<class 'sqlalchemy.sql.sqltypes.Integer'>, colkwargs: Optional[Dict[str, Any]] = None, comment_fmt: Optional[str] = None, comment_strings: Optional[List[str]] = None, minimum: Optional[Union[float, int]] = None, maximum: Optional[Union[float, int]] = None, pv: Optional[List[Any]] = None, suffix: str = '') None[source]

Add a sequence of SQLAlchemy columns to a class.

Called from a metaclass. Used to make task creation a bit easier.

Parameters
  • cls – class to which to add columns

  • prefix – Fieldname will be prefix + str(n) + suffix, where n is defined as below.

  • suffix – Optional. See prefix.

  • start – Start of range.

  • end – End of range. Thus: i will range from 0 to (end - start) inclusive; n will range from start to end inclusive.

  • coltype – SQLAlchemy column type, in either of these formats: (a) Integer (of general type Type[TypeEngine]?); (b) Integer() (of general type TypeEngine).

  • colkwargs – SQLAlchemy column arguments, as in Column(name, coltype, **colkwargs)

  • comment_fmt

    Format string defining field comments. Substitutable values are:

    • {n}: field number (from range).

    • {s}: comment_strings[i], where i is a zero-based index as defined as above, or “” if out of range.

  • comment_strings – see comment_fmt

  • minimum – minimum permitted value, or None

  • maximum – maximum permitted value, or None

  • pv – list of permitted values, or None

camcops_server.cc_modules.cc_db.ancillary_relationship(parent_class_name: str, ancillary_class_name: str, ancillary_fk_to_parent_attr_name: str, ancillary_order_by_attr_name: Optional[str] = None, read_only: bool = True) sqlalchemy.orm.relationships.RelationshipProperty[source]

Implements a one-to-many relationship, i.e. one parent to many ancillaries.

camcops_server.cc_modules.cc_db.mysqldb_crash_on_bad_conversion(o: Any, d: Dict[Any, Callable]) NoReturn[source]

Reports a bad conversion and crashes. For debugging only (obviously)!

Conversions by mysqlclient (MySQLdb)

As per the help docstring for MySQLdb/converters.py,

  • the Python-to-database conversion function has the signature f(o, d) where o is the thing to be converted (such as a datetime.datetime) and d is the conversion dictionary; it returns an SQL literal value.

  • The database-to-Python conversion function has the argument f(s) where s is a string; it returns a Python object.

Both types of functions are stored in MySQLdb.converters, which is a dict. The keys named FIELD_TYPE.* are the database-to-Python converters; the others are the Python-to-database converters.

Conversions by pymysql

Similar (for back compatibility), but not the same.

  • pymysql.converters.conversions is pymysql.converters.decoders and contains database-to-Python converters.

  • pymysql.converters.encoders contains Python-to-database converters.

Parameters
  • o – Python object

  • d – MySQLdb conversion dictionary

Returns

SQL literal

camcops_server.cc_modules.cc_db.pymysql_crash_on_bad_conversion(obj: Any, mapping: Dict[Type, Callable]) NoReturn[source]

See mysqldb_crash_on_bad_conversion().