19.1.56. camcops_server.cc_modules.client_api


Copyright (C) 2012-2018 Rudolf Cardinal (rudolf@pobox.com).

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 <http://www.gnu.org/licenses/>.


We use primarily SQLAlchemy Core here (in contrast to the ORM used elsewhere).

class camcops_server.cc_modules.client_api.ClientApiTests(methodName='runTest')[source]
camcops_server.cc_modules.client_api.audit(req: camcops_server.cc_modules.cc_request.CamcopsRequest, details: str, patient_server_pk: int = None, tablename: str = None, server_pk: int = None) → None[source]

Audit something.

camcops_server.cc_modules.client_api.check_device_registered(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → None[source]

Check that a device is registered, or raise UserErrorException.

camcops_server.cc_modules.client_api.check_upload_user_and_device(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → None[source]

Stub function for the operation to check that a user is valid.

camcops_server.cc_modules.client_api.clear_dirty_tables(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → None[source]

Clears the dirty-table list for a device.

camcops_server.cc_modules.client_api.client_api(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → pyramid.response.Response[source]

View for client API. All tablet interaction comes through here. Wraps main_client_api().

camcops_server.cc_modules.client_api.client_pks_that_exist(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, clientpk_name: str, clientpk_values: List[int]) → Dict[int, int][source]

Searches for client PK values (for this device, current, and ‘now’) matching the input list. Returns a dictionary of {clientpk: serverpk} values for those that do.

camcops_server.cc_modules.client_api.commit_all(req: camcops_server.cc_modules.cc_request.CamcopsRequest, batchtime: pendulum.datetime.DateTime, preserving: bool) → None[source]

Commits additions, removals, and preservations for all tables.

camcops_server.cc_modules.client_api.commit_table(req: camcops_server.cc_modules.cc_request.CamcopsRequest, batchtime: pendulum.datetime.DateTime, preserving: bool, table: sqlalchemy.sql.schema.Table, clear_dirty: bool = True) → Tuple[int, int, int][source]

Commits additions, removals, and preservations for one table.

camcops_server.cc_modules.client_api.delete_where_key_not(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → str[source]

Marks records for deletion, for a device/table, where the client PK is not in a specified list.

camcops_server.cc_modules.client_api.duplicate_record(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, serverpk: int) → int[source]

Duplicates the record defined by the table/serverpk combination. Will raise an exception if the insert fails. Otherwise… The old record then NEEDS MODIFICATION by flag_modified(). The new record NEEDS MODIFICATION by update_new_copy_of_record().

camcops_server.cc_modules.client_api.end_device_upload_batch(req: camcops_server.cc_modules.cc_request.CamcopsRequest, batchtime: pendulum.datetime.DateTime, preserving: bool) → None[source]

Ends an upload batch, committing all changes made thus far.

camcops_server.cc_modules.client_api.end_upload(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → None[source]

Ends an upload and commits changes.

camcops_server.cc_modules.client_api.ensure_valid_field_name(table: sqlalchemy.sql.schema.Table, fieldname: str) → None[source]

Ensures a field name contains only valid characters, and isn’t a reserved fieldname that the user isn’t allowed to access. Raises UserErrorException upon failure.

… 2017-10-08: shortcut: it’s OK if it’s a column name for a particular table.

camcops_server.cc_modules.client_api.ensure_valid_table_name(req: camcops_server.cc_modules.cc_request.CamcopsRequest, tablename: str) → None[source]

Ensures a table name doesn’t contain bad characters, isn’t a reserved table that the user is prohibited from accessing, and is a valid table name that’s in the database. Raises UserErrorException upon failure.

… 2017-10-08: shortcut to all that: it’s OK if it’s listed as a valid client table. … 2018-01-16 (v2.2.0): check also that client version is OK

camcops_server.cc_modules.client_api.flag_all_records_deleted(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table) → None[source]

Marks all records in a table as deleted (that are current and in the current era).

camcops_server.cc_modules.client_api.flag_deleted(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, pklist: Iterable[int]) → None[source]

Marks record(s) as deleted, specified by a list of server PKs.

camcops_server.cc_modules.client_api.flag_deleted_where_clientpk_not(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, clientpk_name: str, clientpk_values: Sequence[Any]) → None[source]

Marks for deletion all current/current-era records for a device, defined by a list of client-side PK values.

camcops_server.cc_modules.client_api.flag_modified(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, pk: int, successor_pk: int) → None[source]

Marks a record as old, storing its successor’s details.

camcops_server.cc_modules.client_api.flag_record_for_preservation(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, pk: int) → None[source]

Marks a record for preservation (moving off the tablet, changing its era details).

camcops_server.cc_modules.client_api.get_allowed_tables(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → Dict[str, str][source]

Returns the names of all possible tables on the server, each paired with the minimum client (tablet) version that will be accepted for each table. (Typically these are all the same as the minimum global tablet version.)

Uses the SELECT-like syntax.

camcops_server.cc_modules.client_api.get_batch_details_start_if_needed(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → Tuple[Union[pendulum.datetime.DateTime, NoneType], Union[bool, NoneType]][source]

Gets a (upload_batch_utc, currently_preserving) tuple.

upload_batch_utc: the batchtime; UTC date/time of the current upload batch. currently_preserving: Boolean; whether preservation (shifting to an older era) is currently taking place.

SIDE EFFECT: if the username is different from the username that started a previous upload batch for this device, we restart the upload batch (thus rolling back previous pending changes).

camcops_server.cc_modules.client_api.get_dirty_tables(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → List[sqlalchemy.sql.schema.Table][source]

Returns tables marked as dirty for this device.

camcops_server.cc_modules.client_api.get_extra_strings(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → Dict[str, str][source]

Fetch all local extra strings from the server.

camcops_server.cc_modules.client_api.get_fields_and_values(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, fields_var: str, values_var: str, mandatory: bool = True) → Dict[str, Any][source]

Gets fieldnames and matching values from two variables in a request.

camcops_server.cc_modules.client_api.get_fields_from_post_var(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, var: str, mandatory: bool = True) → List[str][source]

Get a comma-separated list of field names from a request and checks that all are acceptable. Returns a list of fieldnames.

camcops_server.cc_modules.client_api.get_id_info(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → Dict[source]

Fetch server ID information.

camcops_server.cc_modules.client_api.get_select_reply(fields: Sequence[str], rows: Sequence[Sequence[Any]]) → Dict[str, str][source]

Return format:

nfields:X
fields:X
nrecords:X
record0:VALUES_AS_CSV_LIST_OF_ENCODED_SQL_VALUES
    ...
record{n}:VALUES_AS_CSV_LIST_OF_ENCODED_SQL_VALUES
camcops_server.cc_modules.client_api.get_server_id_info(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → Dict[str, str][source]

Returns a reply for the tablet giving details of the server.

camcops_server.cc_modules.client_api.get_server_pks_of_active_records(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table) → List[int][source]

Gets server PKs of active records (_current and in the ‘NOW’ era) for the specified device/table.

camcops_server.cc_modules.client_api.get_server_pks_of_specified_records(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, wheredict: Dict) → List[int][source]

Retrieves server PKs for a table, for a given device, given a set of ‘where’ conditions specified in wheredict (as field/value combinations, joined with AND).

camcops_server.cc_modules.client_api.get_single_field_from_post_var(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, var: str, mandatory: bool = True) → str[source]

Retrieves a field name from a the request and checks it’s not a bad fieldname.

camcops_server.cc_modules.client_api.get_str_var(req: camcops_server.cc_modules.cc_request.CamcopsRequest, var: str, mandatory: bool = True) → Union[str, NoneType][source]

Retrieves a string variable from CamcopsRequest, raising an error that gets passed to the client device if it’s mandatory and missing.

Parameters:
  • req – CamcopsRequest
  • var – name of variable to retrieve
  • mandatory – if True, script aborts if variable missing
Returns:

value

camcops_server.cc_modules.client_api.get_table_from_req(req: camcops_server.cc_modules.cc_request.CamcopsRequest, var: str) → sqlalchemy.sql.schema.Table[source]

Retrieves a table name from a CGI form and checks it’s a valid client table.

camcops_server.cc_modules.client_api.get_tables_from_post_var(req: camcops_server.cc_modules.cc_request.CamcopsRequest, var: str, mandatory: bool = True) → List[sqlalchemy.sql.schema.Table][source]

Gets a list of tables from a CGI form variable, and ensures all are valid.

camcops_server.cc_modules.client_api.get_values_from_post_var(req: camcops_server.cc_modules.cc_request.CamcopsRequest, var: str, mandatory: bool = True) → List[Any][source]

Retrieves a list of values from a CSV-separated list of SQL values stored in a CGI form (including e.g. NULL, numbers, quoted strings, and special handling for base-64/hex-encoded BLOBs.)

camcops_server.cc_modules.client_api.insert_record(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, valuedict: Dict, predecessor_pk: Union[int, NoneType]) → int[source]

Inserts a record, or raises an exception if that fails.

camcops_server.cc_modules.client_api.main_client_api(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → Dict[str, str][source]

Main HTTP processor.

For success, returns a dictionary to send (will use status ‘200 OK’) For failure, raises an exception.

camcops_server.cc_modules.client_api.mark_table_dirty(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table) → None[source]

Marks a table as having been modified during the current upload.

camcops_server.cc_modules.client_api.record_exists(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, clientpk_name: str, clientpk_value: Any) → Tuple[bool, Union[int, NoneType]][source]

Checks if a record exists, using the device’s perspective of a table/client PK combination. Returns (exists, serverpk), where exists is Boolean. If exists is False, serverpk will be None.

camcops_server.cc_modules.client_api.record_identical_by_date(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, serverpk: int, client_date_value: pendulum.datetime.DateTime) → bool[source]

Shortcut to detecting a record being identical. Returns true if the record (defined by its table/server PK) has a CLIENT_DATE_FIELD field that matches that of the incoming record. As long as the tablet always updates the CLIENT_DATE_FIELD when it saves a record, and the clock on the device doesn’t go backwards by a certain exact millisecond-precision value, this is a valid method.

camcops_server.cc_modules.client_api.record_identical_full(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, serverpk: int, wheredict: Dict) → bool[source]

If a record with the specified server PK exists in the specified table having all its values matching the field/value combinations in wheredict (joined with AND), returns True. Otherwise, returns False. Used to detect if an incoming record matches the database record.

CURRENTLY UNUSED.

camcops_server.cc_modules.client_api.register(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → Dict[str, Any][source]

Register a device with the server.

camcops_server.cc_modules.client_api.rollback_all(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → None[source]

Rolls back all pending changes for a device.

camcops_server.cc_modules.client_api.rollback_table(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table) → None[source]

Rolls back changes for an individual table for a device.

camcops_server.cc_modules.client_api.start_device_upload_batch(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → None[source]

Starts an upload batch for a device.

camcops_server.cc_modules.client_api.start_preservation(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → str[source]

Marks this upload batch as one in which all records will be preserved (i.e. moved from NOW-era to an older era, so they can be deleted safely from the tablet).

Without this, individual records can still be marked for preservation if their MOVE_OFF_TABLET_FIELD field (_move_off_tablet) is set; see upload_record and its functions.

camcops_server.cc_modules.client_api.start_preserving(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → None[source]

Starts preservation (the process of moving records from the NOW era to an older era, so they can be removed safely from the tablet).

camcops_server.cc_modules.client_api.start_upload(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → None[source]

Begin an upload.

camcops_server.cc_modules.client_api.update_new_copy_of_record(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, serverpk: int, valuedict: Dict, predecessor_pk: int) → None[source]

Following duplicate_record(), use this to modify the new copy (defined by the table/serverpk combination).

camcops_server.cc_modules.client_api.upload_empty_tables(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → str[source]

The tablet supplies a list of tables that are empty at its end, and we will ‘wipe’ all appropriate tables; this reduces the number of HTTP requests.

camcops_server.cc_modules.client_api.upload_record(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → str[source]

Upload an individual record. (Typically used for BLOBs.) Incoming POST information includes a CSV list of fields and a CSV list of values.

camcops_server.cc_modules.client_api.upload_record_core(req: camcops_server.cc_modules.cc_request.CamcopsRequest, table: sqlalchemy.sql.schema.Table, clientpk_name: str, valuedict: Dict, recordnum: int) → Tuple[Union[int, NoneType], int][source]

Uploads a record. Deals with IDENTICAL, NEW, and MODIFIED records.

camcops_server.cc_modules.client_api.upload_table(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → str[source]

Upload a table.

Incoming information in the POST request includes a CSV list of fields, a count of the number of records being provided, and a set of variables named record0 … record{nrecords}, each containing a CSV list of SQL-encoded values.

Typically used for smaller tables, i.e. most except for BLOBs.

camcops_server.cc_modules.client_api.which_keys_to_send(req: camcops_server.cc_modules.cc_request.CamcopsRequest) → str[source]

Intended use: “For my device, and a specified table, here are my client- side PKs (as a CSV list), and the modification dates for each corresponding record (as a CSV list). Please tell me which records have mismatching dates on the server, i.e. those that I need to re-upload.”

Used particularly for BLOBs, to reduce traffic, i.e. so we don’t have to send a lot of BLOBs.