def validate_date_in_range(request_headers: Dict[str, str]) -> None: """ Validate the date header given to a VWS endpoint is in range. Args: request_headers: The headers sent with the request. Raises: RequestTimeTooSkewed: The date is out of range. """ date_from_header = datetime.datetime.strptime( request_headers['Date'], '%a, %d %b %Y %H:%M:%S GMT', ) gmt = ZoneInfo('GMT') now = datetime.datetime.now(tz=gmt) date_from_header = date_from_header.replace(tzinfo=gmt) time_difference = now - date_from_header maximum_time_difference = datetime.timedelta(minutes=5) if abs(time_difference) >= maximum_time_difference: raise RequestTimeTooSkewed
def handle(self, *args, **options): print("") try: cam = Camera.objects.get(name=options['camera']) print(cam) except: print("A camera with this name does not exist.") return dt = datetime.combine(date.today(), options['time'], tzinfo=ZoneInfo('Europe/Berlin')) if dt > timezone.now(): dt -= timedelta(days=1) print(dt) path = Path(options['filename']) print(path, path.name) with open(path, 'rb') as source_file: suf = SimpleUploadedFile(path.name, source_file.read(), content_type="image/jpeg") save_to_disk(cam, suf) # If a picture with the given filename already exists, the file on # disk is overwritten and the related `Picture` object is updated # rather than created (the `filename` must be unique). Picture.objects.update_or_create(filename=path.name, defaults={ 'camera': cam, 'timestamp': dt, })
def assert_valid_date_header(response: Response) -> None: """ Assert that a response includes a `Date` header which is within two minutes of "now". Args: response: The response returned by a request to a Vuforia service. Raises: AssertionError: The response does not include a `Date` header which is within one minute of "now". """ date_response = response.headers['Date'] date_from_response = email.utils.parsedate(date_response) assert date_from_response is not None year, month, day, hour, minute, second, _, _, _ = date_from_response gmt = ZoneInfo('GMT') datetime_from_response = datetime.datetime( year=year, month=month, day=day, hour=hour, minute=minute, second=second, tzinfo=gmt, ) current_date = datetime.datetime.now(tz=gmt) time_difference = abs(current_date - datetime_from_response) assert time_difference < datetime.timedelta(minutes=2)
def insert_title_sheet(self) -> None: about_sheet = self._workbook.create_sheet("About", 0) about_sheet.title = "About" about_sheet.column_dimensions["A"].width = 20 about_sheet.column_dimensions["B"].width = 40 now_au = datetime.datetime.now(tz=ZoneInfo("Australia/Sydney")) about_sheet.append({"A": "Created:", "B": now_au.ctime()}) about_sheet.append( { "A": "Connect Group Count:", "B": self._membership_manager.connect_groups_count, } ) about_sheet.append( { "A": "Connect Group Total Member Count:", "B": self._membership_manager.connect_groups_member_count, } ) # Show a list of ConnectGroups if self._membership_manager.connect_groups_member_count > 0: about_sheet.append( {"A": "Connect Group List:", "B": self._workbook.worksheets[1].title} ) # Ignore the zeroth worksheet (this about page), and the first worksheet # that we printed in the line about for ws in self._workbook.worksheets[2:]: about_sheet.append({"B": ws.title})
def test_date_in_range( self, time_multiplier: int, endpoint: Endpoint, ) -> None: """ If a date header is within five minutes before or after the request is sent, no error is returned. Because there is a small delay in sending requests and Vuforia isn't consistent, some leeway is given. """ url = str(endpoint.prepared_request.url) netloc = urlparse(url).netloc skew = { 'vws.vuforia.com': _VWS_MAX_TIME_SKEW, 'cloudreco.vuforia.com': _VWQ_MAX_TIME_SKEW, }[netloc] time_difference_from_now = skew - _LEEWAY time_difference_from_now *= time_multiplier gmt = ZoneInfo('GMT') with freeze_time(datetime.now(tz=gmt) + time_difference_from_now): date = rfc_1123_date() endpoint_headers = dict(endpoint.prepared_request.headers) content = endpoint.prepared_request.body or b'' assert isinstance(content, bytes) authorization_string = authorization_header( access_key=endpoint.access_key, secret_key=endpoint.secret_key, method=str(endpoint.prepared_request.method), content=content, content_type=endpoint.auth_header_content_type, date=date, request_path=endpoint.prepared_request.path_url, ) headers = { **endpoint_headers, 'Authorization': authorization_string, 'Date': date, } endpoint.prepared_request.headers = CaseInsensitiveDict(data=headers) session = requests.Session() response = session.send( # type: ignore request=endpoint.prepared_request, ) url = str(endpoint.prepared_request.url) netloc = urlparse(url).netloc if netloc == 'cloudreco.vuforia.com': assert_query_success(response=response) return assert_vws_response( response=response, status_code=endpoint.successful_headers_status_code, result_code=endpoint.successful_headers_result_code, )
def delete(self) -> None: """ Mark the target as deleted. """ gmt = ZoneInfo('GMT') now = datetime.datetime.now(tz=gmt) self.delete_date = now
def tracking_rating(self) -> int: """ Return the tracking rating of the target recognition image. In this implementation that is just a random integer between 0 and 5 if the target status is 'success'. The rating is 0 if the target status is 'failed'. The rating is -1 for a short time while the target is being processed. The real VWS seems to give -1 for a short time while processing, then the real rating, even while it is still processing. """ pre_rating_time = datetime.timedelta( # That this is half of the total processing time is unrealistic. # In VWS it is not a constant percentage. seconds=self._processing_time_seconds / 2, ) gmt = ZoneInfo('GMT') now = datetime.datetime.now(tz=gmt) time_since_upload = now - self.upload_date if time_since_upload <= pre_rating_time: return -1 if self._post_processing_status == TargetStatuses.SUCCESS: return self.processed_tracking_rating return 0
def test_incorrect_date_format( self, endpoint: Endpoint, ) -> None: """ A `BAD_REQUEST` response is returned when the date given in the date header is not in the expected format (RFC 1123) to VWS API. An `UNAUTHORIZED` response is returned to the VWQ API. """ gmt = ZoneInfo('GMT') with freeze_time(datetime.now(tz=gmt)): now = datetime.now() date_incorrect_format = now.strftime('%a %b %d %H:%M:%S') endpoint_headers = dict(endpoint.prepared_request.headers) content = endpoint.prepared_request.body or b'' assert isinstance(content, bytes) authorization_string = authorization_header( access_key=endpoint.access_key, secret_key=endpoint.secret_key, method=str(endpoint.prepared_request.method), content=content, content_type=endpoint.auth_header_content_type, date=date_incorrect_format, request_path=endpoint.prepared_request.path_url, ) headers = { **endpoint_headers, 'Authorization': authorization_string, 'Date': date_incorrect_format, } endpoint.prepared_request.headers = CaseInsensitiveDict(data=headers) session = requests.Session() response = session.send( # type: ignore request=endpoint.prepared_request, ) url = str(endpoint.prepared_request.url) netloc = urlparse(url).netloc if netloc == 'cloudreco.vuforia.com': assert response.text == 'Malformed date header.' assert_vwq_failure( response=response, status_code=HTTPStatus.UNAUTHORIZED, content_type='text/plain; charset=ISO-8859-1', ) return assert_vws_failure( response=response, status_code=HTTPStatus.BAD_REQUEST, result_code=ResultCodes.FAIL, )
def test_date_out_of_range( self, time_multiplier: int, endpoint: Endpoint, ) -> None: """ If the date header is more than five minutes (target API) or 65 minutes (query API) before or after the request is sent, a `FORBIDDEN` response is returned. Because there is a small delay in sending requests and Vuforia isn't consistent, some leeway is given. """ url = str(endpoint.prepared_request.url) netloc = urlparse(url).netloc skew = { 'vws.vuforia.com': _VWS_MAX_TIME_SKEW, 'cloudreco.vuforia.com': _VWQ_MAX_TIME_SKEW, }[netloc] time_difference_from_now = skew + _LEEWAY time_difference_from_now *= time_multiplier gmt = ZoneInfo('GMT') with freeze_time(datetime.now(tz=gmt) + time_difference_from_now): date = rfc_1123_date() endpoint_headers = dict(endpoint.prepared_request.headers) content = endpoint.prepared_request.body or b'' assert isinstance(content, bytes) authorization_string = authorization_header( access_key=endpoint.access_key, secret_key=endpoint.secret_key, method=str(endpoint.prepared_request.method), content=content, content_type=endpoint.auth_header_content_type, date=date, request_path=endpoint.prepared_request.path_url, ) headers = { **endpoint_headers, 'Authorization': authorization_string, 'Date': date, } endpoint.prepared_request.headers = CaseInsensitiveDict(data=headers) session = requests.Session() response = session.send( # type: ignore request=endpoint.prepared_request, ) # Even with the query endpoint, we get a JSON response. assert_vws_failure( response=response, status_code=HTTPStatus.FORBIDDEN, result_code=ResultCodes.REQUEST_TIME_TOO_SKEWED, )
def __init__( # pylint: disable=too-many-arguments self, name: str, active_flag: bool, width: float, image: io.BytesIO, processing_time_seconds: Union[int, float], application_metadata: str, ) -> None: """ Args: name: The name of the target. active_flag: Whether or not the target is active for query. width: The width of the image in scene unit. image: The image associated with the target. processing_time_seconds: The number of seconds to process each image for. In the real Vuforia Web Services, this is not deterministic. application_metadata: The base64 encoded application metadata associated with the target. Attributes: name (str): The name of the target. target_id (str): The unique ID of the target. active_flag (bool): Whether or not the target is active for query. width (float): The width of the image in scene unit. upload_date (datetime.datetime): The time that the target was created. last_modified_date (datetime.datetime): The time that the target was last modified. processed_tracking_rating (int): The tracking rating of the target once it has been processed. image (io.BytesIO): The image data associated with the target. reco_rating (str): An empty string ("for now" according to Vuforia's documentation). application_metadata (str): The base64 encoded application metadata associated with the target. delete_date (typing.Optional[datetime.datetime]): The time that the target was deleted. """ self.name = name self.target_id = uuid.uuid4().hex self.active_flag = active_flag self.width = width gmt = ZoneInfo('GMT') now = datetime.datetime.now(tz=gmt) self.upload_date: datetime.datetime = now self.last_modified_date = self.upload_date self.processed_tracking_rating = random.randint(0, 5) self.image = image self.reco_rating = '' self._processing_time_seconds = processing_time_seconds self.application_metadata = application_metadata self.delete_date: Optional[datetime.datetime] = None
def parse_event_time(time_playload, calendar_tz): """ Parse a Gcal event time and timezone and return a naive datetime in UTC. The payload has different fields for a full datetime or just a date. Datetimes are assumed to be in UTC already as that can be forced in the Gcal request. Dates are for all day events, so the day begins at 0000 in the calendar's timezone. """ if "date" in time_playload: midnight = datetime.strptime(time_playload["date"], "%Y-%m-%d") aware_time = midnight.replace(tzinfo=ZoneInfo(calendar_tz)) return aware_time.astimezone(timezone.utc).replace(tzinfo=None) return datetime.strptime(time_playload["dateTime"], "%Y-%m-%dT%H:%M:%SZ")
def run_action(self): for cam in Camera.objects.filter(pwd__startswith="pick up "): url = cam.pwd[8:] if not url.lower().endswith(".jpg"): print(f"Warning: URL {url} does not end with .jpg") # TODO: First use HTTP HEAD for checking the `last-modified` header, # only then load the contents with HTTP GET? # TODO: Use PIL to verify that the image is valid? try: r = requests.get(url, timeout=20.0) except requests.exceptions.Timeout as e: print(str(e)) return try: # About converting from GMT to local time, see: # https://stackoverflow.com/questions/4563272/how-to-convert-a-utc-datetime-to-a-local-datetime-using-only-standard-library lm = r.headers.get('last-modified', '') dt = parser.parse(lm).astimezone(ZoneInfo('localtime')) except parser.ParserError as e: print(str(e)) return # print(f"{self.latest_dt = }, {lm = }, {dt = }") if dt <= self.latest_dt: return self.latest_dt = dt path_name = get_pic_stem_from_data( dt, 6 if dt.minute <= 2 else 4) + ".jpg" suf = SimpleUploadedFile(path_name, r.content, content_type="image/jpeg") save_to_disk(cam, suf) # If a picture with the given filename already exists, the file on disk is # overwritten and the related `Picture` object is updated rather than created # (the `filename` must be unique). Picture.objects.update_or_create(filename=path_name, defaults={ 'camera': cam, 'timestamp': dt, })
def upload_view(request): # The basics of this view are explained at # https://docs.djangoproject.com/en/4.0/topics/http/file-uploads/ if request.method == 'POST': # The items in `request.FILES` are of type `UploadedFile`. For details, # see https://docs.djangoproject.com/en/4.0/ref/files/uploads/ form = UploadPictureForm(request.POST, request.FILES) if form.is_valid(): # The `ImageField` in the form normalizes to an `UploadedFile` object. # This is just the `UploadedFile` taken from request.FILES['pic_file'], # augmented with an additional `image` attribute as described at # https://docs.djangoproject.com/en/4.0/ref/forms/fields/#imagefield assert form.cleaned_data['pic_file'] == request.FILES['pic_file'] pic_path = save_to_disk(form.cleaned_data['camera'], form.cleaned_data['pic_file']) try: dt = datetime.strptime(pic_path.name[4:17], "%Y%m%d_%H%M") dt = dt.replace(tzinfo=ZoneInfo('Europe/Berlin')) except ValueError: dt = timezone.now() # If a picture with the given filename already exists, the file on # disk is overwritten and the related `Picture` object is updated # rather than created (the `filename` must be unique). Picture.objects.update_or_create(filename=pic_path.name, defaults={ 'camera': form.cleaned_data['camera'], 'timestamp': dt, }) return redirect('core:upload') else: form = UploadPictureForm() return render(request, 'Core/upload.html', { 'title': "Upload image 😜", 'form': form, })
def test_target_summary( self, vws_client: VWS, vuforia_database: VuforiaDatabase, image_file_failed_state: io.BytesIO, active_flag: bool, ) -> None: """ A target summary is returned. """ name = uuid.uuid4().hex gmt = ZoneInfo('GMT') date_before_add_target = datetime.datetime.now(tz=gmt).date() target_id = vws_client.add_target( name=name, width=1, image=image_file_failed_state, active_flag=active_flag, application_metadata=None, ) date_after_add_target = datetime.datetime.now(tz=gmt).date() report = vws_client.get_target_summary_report(target_id=target_id) assert report.status == TargetStatuses.PROCESSING assert report.database_name == vuforia_database.database_name assert report.target_name == name assert report.active_flag == active_flag # In case the date changes while adding a target # we allow the date before and after adding the target. assert report.upload_date in ( date_before_add_target, date_after_add_target, ) # While processing the tracking rating is -1. assert report.tracking_rating == -1 assert report.total_recos == 0 assert report.current_month_recos == 0 assert report.previous_month_recos == 0
class Migration(migrations.Migration): dependencies = [ ('Core', '0004_picture_timestamp'), ] operations = [ migrations.AlterField( model_name='picture', name='timestamp', field=models.DateTimeField( default=datetime.datetime(2022, 1, 1, 0, 0, tzinfo=ZoneInfo('Europe/Berlin')), help_text='The time at which the camera took the picture.'), preserve_default=False, ), ]
def status(self) -> str: """ Return the status of the target. For now this waits half a second (arbitrary) before changing the status from 'processing' to 'failed' or 'success'. The status depends on the standard deviation of the color bands. How VWS determines this is unknown, but it relates to how suitable the target is for detection. """ processing_time = datetime.timedelta( seconds=self._processing_time_seconds, ) gmt = ZoneInfo('GMT') now = datetime.datetime.now(tz=gmt) time_since_change = now - self.last_modified_date if time_since_change <= processing_time: return str(TargetStatuses.PROCESSING.value) return str(self._post_processing_status.value)
def get_commits(self): commit_stats_days: OrderedDict[str, int] = OrderedDict( { "Sunday": 0, "Monday": 0, "Tuesday": 0, "Wednesday": 0, "Thursday": 0, "Friday": 0, "Saturday": 0, } ) commit_stats_hours: Dict[str, int] = { "00": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "06": 0, "07": 0, "08": 0, "09": 0, "10": 0, "11": 0, "12": 0, "13": 0, "14": 0, "15": 0, "16": 0, "17": 0, "18": 0, "19": 0, "20": 0, "21": 0, "22": 0, "23": 0, } sum_commits: int = 0 my_names: List[str] = ["gpnn", "Gordon", "Gordon Pham-Nguyen", "gordonpn"] usernames: Set[str] = set() repos: Set[str] = set() for repo in self.g.get_user().get_repos(): repos.add(repo.name) if repo.name in self.not_my_repos: continue try: commits = repo.get_commits() for commit in commits: name: str = commit.commit.author.name this_date_unaware: datetime = commit.commit.author.date usernames.add(name) if name not in my_names: continue sum_commits += 1 this_date_aware = this_date_unaware.replace(tzinfo=ZoneInfo("UTC")) local_aware = this_date_aware.astimezone( ZoneInfo("America/Montreal") ) this_day: str = get_day_string(local_aware) commit_stats_days[this_day] = commit_stats_days.get(this_day, 0) + 1 hours_key = str(local_aware.hour) if len(hours_key) == 1: hours_key = f"0{hours_key}" commit_stats_hours[hours_key] = ( commit_stats_hours.get(hours_key, 0) + 1 ) except GithubException as e: logger.info(f"{repo.name} {str(e)}") logger.info(f"Usernames found in commits {usernames}") logger.info(f"Repositories found {repos}") logger.info(f"Total number of commits: {sum_commits}") logger.info(f"Returning {commit_stats_days=}") logger.info(f"Returning {commit_stats_hours=}") return commit_stats_days, commit_stats_hours
def to_timezone(date, from_timezone='UTC', to_timezone='UTC'): date = date.replace(tzinfo=ZoneInfo(from_timezone)).astimezone( tz=ZoneInfo(to_timezone)).replace(tzinfo=None) return date
def test_invalid_json( self, endpoint: Endpoint, date_skew_minutes: int, ) -> None: """ Giving invalid JSON to endpoints returns error responses. """ date_is_skewed = not date_skew_minutes == 0 content = b'a' gmt = ZoneInfo('GMT') now = datetime.now(tz=gmt) time_to_freeze = now + timedelta(minutes=date_skew_minutes) with freeze_time(time_to_freeze): date = rfc_1123_date() endpoint_headers = dict(endpoint.prepared_request.headers) authorization_string = authorization_header( access_key=endpoint.access_key, secret_key=endpoint.secret_key, method=str(endpoint.prepared_request.method), content=content, content_type=endpoint.auth_header_content_type, date=date, request_path=endpoint.prepared_request.path_url, ) headers = { **endpoint_headers, 'Authorization': authorization_string, 'Date': date, } endpoint.prepared_request.body = content endpoint.prepared_request.headers = CaseInsensitiveDict(data=headers) endpoint.prepared_request.prepare_content_length(body=content) session = requests.Session() response = session.send( # type: ignore request=endpoint.prepared_request, ) takes_json_data = ( endpoint.auth_header_content_type == 'application/json' ) assert_valid_date_header(response=response) if date_is_skewed and takes_json_data: # On the real implementation, we get `HTTPStatus.FORBIDDEN` and # `REQUEST_TIME_TOO_SKEWED`. # See https://github.com/VWS-Python/vws-python-mock/issues/4 for # implementing this on them mock. return if not date_is_skewed and takes_json_data: assert_vws_failure( response=response, status_code=HTTPStatus.BAD_REQUEST, result_code=ResultCodes.FAIL, ) return assert response.status_code == HTTPStatus.BAD_REQUEST url = str(endpoint.prepared_request.url) netloc = urlparse(url).netloc if netloc == 'cloudreco.vuforia.com': assert_vwq_failure( response=response, status_code=HTTPStatus.BAD_REQUEST, content_type='text/html;charset=UTF-8', ) expected_text = ( 'java.lang.RuntimeException: RESTEASY007500: ' 'Could find no Content-Disposition header within part' ) assert response.text == expected_text return assert response.text == '' assert 'Content-Type' not in response.headers
def get_query_match_response_text( request_headers: Dict[str, str], request_body: bytes, request_method: str, request_path: str, databases: Set[VuforiaDatabase], query_processes_deletion_seconds: Union[int, float], query_recognizes_deletion_seconds: Union[int, float], ) -> str: """ Args: request_path: The path of the request. request_headers: The headers sent with the request. request_body: The body of the request. request_method: The HTTP method of the request. databases: All Vuforia databases. query_recognizes_deletion_seconds: The number of seconds after a target has been deleted that the query endpoint will still recognize the target for. query_processes_deletion_seconds: The number of seconds after a target deletion is recognized that the query endpoint will return a 500 response on a match. Returns: The response text for a query endpoint request. Raises: MatchingTargetsWithProcessingStatus: There is at least one matching target which has the status 'processing'. ActiveMatchingTargetsDeleteProcessing: There is at least one active target which matches and was recently deleted. """ body_file = io.BytesIO(request_body) _, pdict = cgi.parse_header(request_headers['Content-Type']) parsed = cgi.parse_multipart( fp=body_file, pdict={ 'boundary': pdict['boundary'].encode(), }, ) [max_num_results] = parsed.get('max_num_results', ['1']) [include_target_data] = parsed.get('include_target_data', ['top']) include_target_data = include_target_data.lower() [image_bytes] = parsed['image'] assert isinstance(image_bytes, bytes) image = io.BytesIO(image_bytes) gmt = ZoneInfo('GMT') now = datetime.datetime.now(tz=gmt) processing_timedelta = datetime.timedelta( seconds=query_processes_deletion_seconds, ) recognition_timedelta = datetime.timedelta( seconds=query_recognizes_deletion_seconds, ) database = get_database_matching_client_keys( request_headers=request_headers, request_body=request_body, request_method=request_method, request_path=request_path, databases=databases, ) assert isinstance(database, VuforiaDatabase) matching_targets = [ target for target in database.targets if _images_match(image=target.image, another_image=image) ] not_deleted_matches = [ target for target in matching_targets if target.active_flag and not target.delete_date and target.status == TargetStatuses.SUCCESS.value ] deletion_not_recognized_matches = [ target for target in matching_targets if target.active_flag and target.delete_date and (now - target.delete_date) < recognition_timedelta ] matching_targets_with_processing_status = [ target for target in matching_targets if target.status == TargetStatuses.PROCESSING.value ] active_matching_targets_delete_processing = [ target for target in matching_targets if target.active_flag and target.delete_date and (now - target.delete_date) < (recognition_timedelta + processing_timedelta) and target not in deletion_not_recognized_matches ] if matching_targets_with_processing_status: raise MatchingTargetsWithProcessingStatus if active_matching_targets_delete_processing: raise ActiveMatchingTargetsDeleteProcessing matches = not_deleted_matches + deletion_not_recognized_matches results: List[Dict[str, Any]] = [] for target in matches: target_timestamp = target.last_modified_date.timestamp() if target.application_metadata is None: application_metadata = None else: application_metadata = base64.b64encode( decode_base64(encoded_data=target.application_metadata), ).decode('ascii') target_data = { 'target_timestamp': int(target_timestamp), 'name': target.name, 'application_metadata': application_metadata, } if include_target_data == 'all': result = { 'target_id': target.target_id, 'target_data': target_data, } elif include_target_data == 'top' and not results: result = { 'target_id': target.target_id, 'target_data': target_data, } else: result = { 'target_id': target.target_id, } results.append(result) results = results[: int(max_num_results)] body = { 'result_code': ResultCodes.SUCCESS.value, 'results': results, 'query_id': uuid.uuid4().hex, } value = json_dump(body) return value
def update_target( self, request: _RequestObjectProxy, context: _Context, ) -> str: """ Update a target. Fake implementation of https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Update-a-Target """ target = _get_target_from_request( request_path=request.path, databases=self.databases, ) body: Dict[str, str] = {} database = get_database_matching_server_keys( request_headers=request.headers, request_body=request.body, request_method=request.method, request_path=request.path, databases=self.databases, ) assert isinstance(database, VuforiaDatabase) if target.status != TargetStatuses.SUCCESS.value: context.status_code = HTTPStatus.FORBIDDEN body = { 'transaction_id': uuid.uuid4().hex, 'result_code': ResultCodes.TARGET_STATUS_NOT_SUCCESS.value, } return json_dump(body) if 'width' in request.json(): target.width = request.json()['width'] if 'active_flag' in request.json(): active_flag = request.json()['active_flag'] if active_flag is None: body = { 'transaction_id': uuid.uuid4().hex, 'result_code': ResultCodes.FAIL.value, } context.status_code = HTTPStatus.BAD_REQUEST return json_dump(body) target.active_flag = active_flag if 'application_metadata' in request.json(): if request.json()['application_metadata'] is None: body = { 'transaction_id': uuid.uuid4().hex, 'result_code': ResultCodes.FAIL.value, } context.status_code = HTTPStatus.BAD_REQUEST return json_dump(body) application_metadata = request.json()['application_metadata'] target.application_metadata = application_metadata if 'name' in request.json(): name = request.json()['name'] other_targets = set(database.targets) - set([target]) if any( other.name == name for other in other_targets if not other.delete_date ): context.status_code = HTTPStatus.FORBIDDEN body = { 'transaction_id': uuid.uuid4().hex, 'result_code': ResultCodes.TARGET_NAME_EXIST.value, } return json_dump(body) target.name = name if 'image' in request.json(): image = request.json()['image'] decoded = base64.b64decode(image) image_file = io.BytesIO(decoded) target.image = image_file # In the real implementation, the tracking rating can stay the same. # However, for demonstration purposes, the tracking rating changes but # when the target is updated. available_values = list(set(range(6)) - set([target.tracking_rating])) target.processed_tracking_rating = random.choice(available_values) gmt = ZoneInfo('GMT') now = datetime.datetime.now(tz=gmt) target.last_modified_date = now body = { 'result_code': ResultCodes.SUCCESS.value, 'transaction_id': uuid.uuid4().hex, } return json_dump(body)