def test_observation_cr_d(self, repository: Repository): """Test checks if Create, Retrieve and Delete operations work for observations.""" observation: Observation = { 'obs_id': ObservationId(0), 'aos': datetime.datetime(2020, 3, 21, 12, 00, 0), 'tca': datetime.datetime(2020, 3, 21, 12, 15, 0), 'los': datetime.datetime(2020, 3, 21, 12, 30, 0), 'sat_id': SatelliteId(28654), 'thumbnail': 'thumb-123.png', 'config': None, 'notes': None, 'station_id': StationId(1), 'tle': [ "1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927", "2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537" ], 'rating': None } obs_id = repository.insert_observation(observation) self.assertEqual(type(obs_id), int) observation["obs_id"] = obs_id db_observation = repository.read_observation(obs_id) self.assertIsNotNone(db_observation) self.assertDictEqual(observation, db_observation) # type: ignore observation_file: ObservationFile = { 'obs_file_id': ObservationFileId(0), 'filename': '123.png', 'media_type': 'image/png', 'obs_id': obs_id, 'rating': 0.66 } file_id = repository.insert_observation_file(observation_file) self.assertEqual(type(file_id), int) observation_files = repository.read_observation_files(obs_id) self.assertEqual(len(observation_files), 1) observation_file["obs_file_id"] = file_id db_observation_file = observation_files[0] self.assertDictEqual(observation_file, db_observation_file) # type: ignore db_observation = repository.read_observation(obs_id) self.assertAlmostEqual(db_observation["rating"], 0.66, places=2) repository.delete_observation(obs_id) observation_files = repository.read_observation_files(obs_id) self.assertEqual(len(observation_files), 0)
def receive(station_id: str, args: RequestArguments): ''' Receive observation from station. Station must be authenticated. Request must have attached at least one imagery file. We accept only files with MIME-type and extension listed in @ALLOWED_FILE_TYPES dictionary. From first imagery file we create a thumbnail. Files are sorted using HTTP request keys. Request data are stored in DB. Binary files are saved in filesystem with unique, conflict-safe filename. Satellite assigned to observation must exists in database. ''' files: Dict[str, WebFileLike] = request.files if len(files) == 0: abort(400, description="Missing file") # Filter files and create safe filenames uid = str(uuid.uuid4()) items = enumerate(sorted(files.items(), key=lambda e: e[0])) file_entries: List[Tuple[str, WebFileLike]] = [] for idx, (_, file_) in items: if not is_allowed_file(file_): app.logger.warning( f"File {file_.filename} is not allowed due to its type ({file_.mimetype}) or extension" ) continue org_filename = secure_filename(file_.filename) filename = "%s-%d-%s" % (uid, idx, org_filename) file_entries.append((filename, file_)) app.logger.info( f"Received file {file_.filename}, to be stored as {filename}") # Select thumbnail source file thumbnail_source_entry = first( lambda f: f[1].mimetype.startswith("image/"), file_entries) if thumbnail_source_entry is None: app.logger.info(f"No suitable images for thumbnail, using None") thumb_filename = None else: thumb_source_filename, _ = thumbnail_source_entry thumb_filename = "thumb-%s-%s" % (str( uuid.uuid4()), thumb_source_filename) # Save data in DB repository = Repository() with repository.transaction() as transaction: satellite = repository.read_satellite(args["sat"]) if satellite is None: abort(400, description="Unknown satellite") sat_id = SatelliteId(satellite["sat_id"]) tle = args.get('tle') if tle: # Remove trailing character tle = [line.strip() for line in tle] # Let's get metadata and try to run some sanity checks on it. Technically the whole thing # is not mandatory, so we only complain if important parameters are missing, but we # accept the observation anyway. metadata = args.get('config') or "{}" mandatory_tags = [ "protocol", "frequency", "antenna", "antenna-type", "receiver", "lna", "filter" ] missing = [] for t in mandatory_tags: if t not in metadata: missing.append(t) if len(missing): app.logger.warning( f"Received observation from station {station_id} with missing tags: {' '.join(missing)}" ) observation: Observation = { 'obs_id': ObservationId(0), 'aos': args['aos'], 'tca': args['tca'], 'los': args['los'], 'sat_id': sat_id, 'thumbnail': thumb_filename, 'config': metadata, 'station_id': StationId(station_id), 'tle': tle } app.logger.info( "Received observation: station_id=%s sat_id=%s config=%s rating=%s" % (station_id, sat_id, args.get('config'), args.get('rating'))) obs_id = repository.insert_observation(observation) observation["obs_id"] = obs_id for filename, file_ in file_entries: observation_file: ObservationFile = { "obs_file_id": ObservationFileId(0), "filename": filename, "media_type": file_.mimetype, "obs_id": obs_id, 'rating': args.get('rating') } repository.insert_observation_file(observation_file) transaction.commit() # Save files in filesystem root = app.config["storage"]['image_root'] for filename, file_ in file_entries: path = os.path.join(root, filename) try: file_.save(path) app.logger.info("File %s written to %s" % (filename, root)) except OSError as e: app.logger.error("Failed to write %s (image_root=%s): %s" % (path, root, e)) return abort( 503, "Unable to write file %s. Disk operation error." % filename) if thumb_filename != None: # Make thumbnail (but only if suitable images were submitted, for text we're out of luck) thumb_source_path = os.path.join(root, thumb_source_filename) thumb_path = os.path.join(root, "thumbs", thumb_filename) app.logger.debug(f"Generating thumbnail for {thumb_source_filename}.") make_thumbnail(thumb_source_path, thumb_path) # Make charts station = repository.read_station(observation["station_id"]) make_charts(observation, station, root) # Make sure to return the observation id to the station. This may be useful if the station wants # to update the observation in some way (send additional info or perhaps decide to delete it in the future). return 'Observation %d received.' % obs_id, 204