def update_task_index_for_upload( cls, session: SqlASession, tablechanges: UploadTableChanges, indexed_at_utc: Pendulum, ) -> None: """ Updates the index for a device's upload. - Deletes index entries for records that are on the way out. - Creates index entries for records that are on the way in. - Deletes/recreates index entries for records being preserved. Args: session: an SQLAlchemy Session tablechanges: a :class:`camcops_server.cc_modules.cc_client_api_core.UploadTableChanges` object describing the changes to a table indexed_at_utc: current time in UTC """ # noqa tasktablename = tablechanges.tablename d = tablename_to_task_class_dict() try: taskclass = d[tasktablename] # may raise KeyError except KeyError: fail_user_error(f"Bug: no such task table: {tasktablename!r}") # noinspection PyUnresolvedReferences idxtable = cls.__table__ # type: Table idxcols = idxtable.columns # Delete the old. delete_index_pks = tablechanges.task_delete_index_pks if delete_index_pks: log.debug( "Deleting old task indexes: {}, server PKs {}", tasktablename, delete_index_pks, ) # noinspection PyProtectedMember session.execute( idxtable.delete() .where(idxcols.task_table_name == tasktablename) .where(idxcols.task_pk.in_(delete_index_pks)) ) # Create the new. reindex_pks = tablechanges.task_reindex_pks if reindex_pks: log.debug( "Recreating task indexes: {}, server PKs {}", tasktablename, reindex_pks, ) # noinspection PyUnboundLocalVariable,PyProtectedMember q = session.query(taskclass).filter(taskclass._pk.in_(reindex_pks)) for task in q: cls.index_task(task, session, indexed_at_utc=indexed_at_utc)
def ensure_device_registered(self) -> None: """ Ensure the device is registered. Raises :exc:`UserErrorException` on failure. """ if not self.is_device_registered(): fail_user_error("Unregistered device")
def __init__(self, req: "CamcopsRequest") -> None: # Check the basics if req.method != RequestMethod.POST: raise HTTPBadRequest("Must use POST method") # ... this is for humans to view, so it has a pretty error # Read key things self.req = req self.operation = req.get_str_param(TabletParam.OPERATION) self.device_name = req.get_str_param(TabletParam.DEVICE) self.username = req.get_str_param(TabletParam.USER) self.password = req.get_str_param(TabletParam.PASSWORD) self.session_id = req.get_int_param(TabletParam.SESSION_ID) self.session_token = req.get_str_param(TabletParam.SESSION_TOKEN) self.tablet_version_str = req.get_str_param(TabletParam.CAMCOPS_VERSION) # noqa try: self.tablet_version_ver = make_version(self.tablet_version_str) except ValueError: fail_user_error("CamCOPS tablet version nonsensical: {!r}".format( self.tablet_version_str)) # Basic security check: no pretending to be the server if self.device_name == DEVICE_NAME_FOR_SERVER: fail_user_error("Tablets cannot use the device name {!r}".format( DEVICE_NAME_FOR_SERVER)) if self.username == USER_NAME_FOR_SYSTEM: fail_user_error("Tablets cannot use the username {!r}".format( USER_NAME_FOR_SYSTEM)) self._device_obj = None # type: Device # Ensure table version is OK if self.tablet_version_ver < MINIMUM_TABLET_VERSION: fail_user_error( "Tablet CamCOPS version too old: is {v}, need {r}".format( v=self.tablet_version_str, r=MINIMUM_TABLET_VERSION)) # Other version things are done via properties # Upload efficiency self._dirty_table_names = set() # type: Set[str] # Report log.info("Incoming client API connection from IP={i}, port={p}, " "device_name={dn!r}, " # "device_id={di}, " "camcops_version={v}, " "username={u}, operation={o}", i=req.remote_addr, p=req.remote_port, dn=self.device_name, # di=self.device_id, v=self.tablet_version_str, u=self.username, o=self.operation)
def ensure_valid_user_for_device_registration(self) -> None: """ Ensure the username/password combination is valid for device registration. Raises :exc:`UserErrorException` on failure. """ user = self.req.user if not user: fail_user_error(INVALID_USERNAME_PASSWORD) if user.upload_group_id is None: fail_user_error(NO_UPLOAD_GROUP_SET + user.username) if not user.may_register_devices: fail_user_error("User not authorized to register devices for " "selected group")
def ensure_valid_device_and_user_for_uploading(self) -> None: """ Ensure the device/username/password combination is valid for uploading. Raises :exc:`UserErrorException` on failure. """ user = self.req.user if not user: fail_user_error(INVALID_USERNAME_PASSWORD) if user.upload_group_id is None: fail_user_error(NO_UPLOAD_GROUP_SET + user.username) if not user.may_upload: fail_user_error("User not authorized to upload to selected group") # Username/password combination found and is valid. Now check device. self.ensure_device_registered()
def test_client_api_basics(self) -> None: self.announce("test_client_api_basics") with self.assertRaises(UserErrorException): fail_user_error("testmsg") with self.assertRaises(ServerErrorException): fail_server_error("testmsg") with self.assertRaises(UserErrorException): fail_unsupported_operation("duffop") # Encoding/decoding tests # data = bytearray("hello") data = b"hello" enc_b64data = base64_64format_encode(data) enc_hexdata = hex_xformat_encode(data) not_enc_1 = "X'012345'" not_enc_2 = "64'aGVsbG8='" teststring = """one, two, 3, 4.5, NULL, 'hello "hi with linebreak"', 'NULL', 'quote''s here', {b}, {h}, {s1}, {s2}""" sql_csv_testdict = { teststring.format( b=enc_b64data, h=enc_hexdata, s1=sql_quote_string(not_enc_1), s2=sql_quote_string(not_enc_2), ): [ "one", "two", 3, 4.5, None, 'hello "hi\n with linebreak"', "NULL", "quote's here", data, data, not_enc_1, not_enc_2, ], "": [], } for k, v in sql_csv_testdict.items(): r = decode_values(k) self.assertEqual( r, v, "Mismatch! Result: {r!s}\n" "Should have been: {v!s}\n" "Key was: {k!s}".format(r=r, v=v, k=k), ) # Newline encoding/decodine ts2 = ( "slash \\ newline \n ctrl_r \r special \\n other special \\r " "quote ' doublequote \" " ) self.assertEqual( unescape_newlines(escape_newlines(ts2)), ts2, "Bug in escape_newlines() or unescape_newlines()", )