예제 #1
0
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
예제 #2
0
    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,
                                         })
예제 #3
0
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)
예제 #4
0
 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})
예제 #5
0
    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,
        )
예제 #6
0
 def delete(self) -> None:
     """
     Mark the target as deleted.
     """
     gmt = ZoneInfo('GMT')
     now = datetime.datetime.now(tz=gmt)
     self.delete_date = now
예제 #7
0
    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
예제 #8
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,
        )
예제 #9
0
    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,
        )
예제 #10
0
    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
예제 #11
0
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")
예제 #12
0
    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,
                                             })
예제 #13
0
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,
    })
예제 #14
0
    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,
        ),
    ]
예제 #16
0
    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)
예제 #17
0
    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
예제 #18
0
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
예제 #19
0
    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
예제 #20
0
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
예제 #21
0
    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)