From e4e85fa39d84f79288f47be3ab14fd34ba4faa7c Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 3 Jan 2020 22:38:10 +0000
Subject: [PATCH 0001/3455] Progress towards making nicer fixture for server
stuff
---
Dockerfile | 0
src/_mock_vws_server/__init__.py | 0
tests/conftest.py | 10 +++++++++-
3 files changed, 9 insertions(+), 1 deletion(-)
create mode 100644 Dockerfile
create mode 100644 src/_mock_vws_server/__init__.py
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/_mock_vws_server/__init__.py b/src/_mock_vws_server/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/conftest.py b/tests/conftest.py
index 82b0f2dad..4f88f07f0 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -120,8 +120,16 @@ def target_id(
new_target_id: str = response_json['target_id']
return new_target_id
+class VuforiaBackend(Enum):
-@pytest.fixture(params=[True, False], ids=['Real Vuforia', 'Mock Vuforia'])
+ REAL_VUFORIA = 'Real Vuforia'
+ MOCK_IN_MEMORY = 'Mock in memory'
+
+@pytest.fixture(
+ params=[
+ pytest.param(item, item.value) for item in list(VuforiaBackend),
+ ],
+)
def verify_mock_vuforia(
request: SubRequest,
vuforia_database: VuforiaDatabase,
From a69fa3ae94118bd252374ddd4685e4b71f89db04 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 5 Jan 2020 11:09:01 +0000
Subject: [PATCH 0002/3455] Add stub for documentation
---
docs/source/docker.rst | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 docs/source/docker.rst
diff --git a/docs/source/docker.rst b/docs/source/docker.rst
new file mode 100644
index 000000000..a0c7e331f
--- /dev/null
+++ b/docs/source/docker.rst
@@ -0,0 +1,2 @@
+Running a server with Docker
+============================
From c05eb4db6855e415feec2c0c0606446d14b6943a Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 5 Jan 2020 11:20:07 +0000
Subject: [PATCH 0003/3455] Progress towards docs
---
docs/source/docker.rst | 10 ++++++++++
docs/source/index.rst | 1 +
2 files changed, 11 insertions(+)
diff --git a/docs/source/docker.rst b/docs/source/docker.rst
index a0c7e331f..3adb696eb 100644
--- a/docs/source/docker.rst
+++ b/docs/source/docker.rst
@@ -1,2 +1,12 @@
Running a server with Docker
============================
+
+Running the mock
+----------------
+
+.. code:: sh
+
+ docker run adamtheturtle/mock-vws
+
+Customising the ports
+---------------------
diff --git a/docs/source/index.rst b/docs/source/index.rst
index aef0cfe16..0f3bbfdf1 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -26,6 +26,7 @@ Reference
api-reference
installation
+ docker
differences-to-vws
versioning-and-api-stability
contributing
From bd8272b7b25565166e1948fb83b415b7d8f309d3 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 5 Jan 2020 11:33:47 +0000
Subject: [PATCH 0004/3455] Progress towards docs
---
docs/source/docker.rst | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/docs/source/docker.rst b/docs/source/docker.rst
index 3adb696eb..53648756f 100644
--- a/docs/source/docker.rst
+++ b/docs/source/docker.rst
@@ -8,5 +8,11 @@ Running the mock
docker run adamtheturtle/mock-vws
-Customising the ports
----------------------
+Configuration
+-------------
+
+Ports
+~~~~~
+
+Using ``docker-compose``
+------------------------
From a3756a511fb1281412ba0c245c9cc9af26d8e1e2 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 5 Jan 2020 12:33:52 +0000
Subject: [PATCH 0005/3455] Add sample config
---
docs/source/docker.rst | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/docs/source/docker.rst b/docs/source/docker.rst
index 53648756f..3c0745d4e 100644
--- a/docs/source/docker.rst
+++ b/docs/source/docker.rst
@@ -6,11 +6,32 @@ Running the mock
.. code:: sh
- docker run adamtheturtle/mock-vws
+ docker run adamtheturtle/mock-vws -e VWS_MOCK_DATABASES=$(cat vws-mock-config.json)
Configuration
-------------
+The ``VWS_MOCK_DATABASES`` environment variable must be set to a JSON configuration which looks like:
+
+.. code-block:: json
+
+ [
+ {
+ "state": "working",
+ "server_access_key": "my_server_access_key",
+ "server_secret_key": "my_server_secret_key",
+ "client_access_key": "my_client_access_key",
+ "client_secret_key": "my_client_secret_key"
+ },
+ {
+ "state": "inactive",
+ "server_access_key": "my_server_access_key2",
+ "server_secret_key": "my_server_secret_key2",
+ "client_access_key": "my_client_access_key2",
+ "client_secret_key": "my_client_secret_key2"
+ }
+ ]
+
Ports
~~~~~
From bc7597d03af42f2d43813a1705429f2ca47b9f75 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 6 Jan 2020 14:14:25 +0000
Subject: [PATCH 0006/3455] Add a TODO
---
docs/source/docker.rst | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/source/docker.rst b/docs/source/docker.rst
index 3c0745d4e..ea4ecc33d 100644
--- a/docs/source/docker.rst
+++ b/docs/source/docker.rst
@@ -32,6 +32,8 @@ The ``VWS_MOCK_DATABASES`` environment variable must be set to a JSON configurat
}
]
+TODO: Also processing time etc.
+
Ports
~~~~~
From 0d59c24a596cacf4c95483f2ae37cbefdb7e1b4a Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 12 Jan 2020 11:56:28 +0000
Subject: [PATCH 0007/3455] Initial dockerfile
---
Dockerfile | 0
src/_mock_vws_server/Dockerfile | 4 ++++
2 files changed, 4 insertions(+)
delete mode 100644 Dockerfile
create mode 100644 src/_mock_vws_server/Dockerfile
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/_mock_vws_server/Dockerfile b/src/_mock_vws_server/Dockerfile
new file mode 100644
index 000000000..393ffa77a
--- /dev/null
+++ b/src/_mock_vws_server/Dockerfile
@@ -0,0 +1,4 @@
+FROM python:3.7-slim-buster
+COPY . /app
+WORKDIR /app
+RUN pip install .
From a38bc636bde4dde06293b59ba97c19ad2c460076 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 14 Jan 2020 15:06:16 +0000
Subject: [PATCH 0008/3455] Progress towards having tests run on new backends
---
tests/mock_vws/fixtures/vuforia_backends.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index e130cf3fe..6465638a5 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -107,6 +107,13 @@ def _enable_use_mock_vuforia(
yield
+def _enable_use_docker_in_memory(
+ working_database: VuforiaDatabase,
+ inactive_database: VuforiaDatabase,
+) -> Generator:
+ pass
+
+
class VuforiaBackend(Enum):
"""
Backends for tests.
@@ -114,6 +121,7 @@ class VuforiaBackend(Enum):
REAL = 'Real Vuforia'
MOCK = 'In Memory Mock Vuforia'
+ DOCKER_IN_MEMORY = 'In Memory version of Docker application'
@pytest.fixture(
@@ -139,6 +147,7 @@ def verify_mock_vuforia(
enable_function = {
VuforiaBackend.REAL: _enable_use_real_vuforia,
VuforiaBackend.MOCK: _enable_use_mock_vuforia,
+ VuforiaBackend.DOCKER_IN_MEMORY: _enable_use_docker_in_memory,
}[backend]
yield from enable_function(
From f4a189f61d7bbab32ce3f14d67dfc5be3e847bf6 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 20 Jan 2020 22:15:11 +0000
Subject: [PATCH 0009/3455] Progress towards using flask app as a mock
---
dev-requirements.txt | 1 +
tests/mock_vws/fixtures/vuforia_backends.py | 16 +++++++++++++++-
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 86caeacca..195286a79 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -25,6 +25,7 @@ pyroma==2.6 # Packaging best practices checker
pytest-cov==2.8.1 # Measure code coverage
pytest-envfiles==0.1.0 # Use files for environment variables for tests
pytest==5.3.2 # Test runners
+requests-mock-flask==2020.1.20.2
sphinx-autodoc-typehints==1.10.3
sphinx_paramlinks==0.3.7
sphinxcontrib-spelling==4.3.0
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index 6465638a5..5aed2109f 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -8,8 +8,10 @@
from typing import Generator
import pytest
+import requests_mock
from _pytest.fixtures import SubRequest
from requests import codes
+from requests_mock_flask import add_flask_app_to_mock
from mock_vws import MockVWS
from mock_vws._constants import ResultCodes
@@ -111,7 +113,19 @@ def _enable_use_docker_in_memory(
working_database: VuforiaDatabase,
inactive_database: VuforiaDatabase,
) -> Generator:
- pass
+ with requests_mock.Mocker(real_http=False) as mock:
+ add_flask_app_to_mock(
+ mock_obj=mock,
+ flask_app=VWS_FLASK_APP,
+ base_url='https://vws.vuforia.com',
+ )
+
+ add_flask_app_to_mock(
+ mock_obj=mock,
+ flask_app=CLOUDRECO_FLASK_APP,
+ base_url='https://cloudreco.vuforia.com',
+ )
+
class VuforiaBackend(Enum):
From aa09ddd2dc42c39849bdfc5a84d259432d20f601 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 20 Jan 2020 22:27:07 +0000
Subject: [PATCH 0010/3455] Progress towards using flask app as a mock
---
src/_mock_vws_server/vwq/__init__.py | 3 +++
src/_mock_vws_server/vws/__init__.py | 3 +++
tests/mock_vws/fixtures/vuforia_backends.py | 4 ++++
3 files changed, 10 insertions(+)
create mode 100644 src/_mock_vws_server/vwq/__init__.py
create mode 100644 src/_mock_vws_server/vws/__init__.py
diff --git a/src/_mock_vws_server/vwq/__init__.py b/src/_mock_vws_server/vwq/__init__.py
new file mode 100644
index 000000000..3a074733c
--- /dev/null
+++ b/src/_mock_vws_server/vwq/__init__.py
@@ -0,0 +1,3 @@
+from flask import Flask
+
+CLOUDRECO_FLASK_APP = Flask(__name__)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
new file mode 100644
index 000000000..46b768b41
--- /dev/null
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -0,0 +1,3 @@
+from flask import Flask
+
+VWS_FLASK_APP = Flask(__name__)
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index 5aed2109f..12407c8f0 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -17,6 +17,8 @@
from mock_vws._constants import ResultCodes
from mock_vws.database import VuforiaDatabase
from mock_vws.states import States
+from _mock_vws_server.vws import VWS_FLASK_APP
+from _mock_vws_server.vwq import CLOUDRECO_FLASK_APP
from tests.mock_vws.utils import (
delete_target,
list_targets,
@@ -126,6 +128,8 @@ def _enable_use_docker_in_memory(
base_url='https://cloudreco.vuforia.com',
)
+ yield
+
class VuforiaBackend(Enum):
From 8a741c8e1396fff621f0823eed05db1b881245c1 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 21 Jan 2020 09:24:07 +0000
Subject: [PATCH 0011/3455] Progress towards using flask app as a mock
---
src/_mock_vws_server/vws/__init__.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 46b768b41..544473059 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -1,3 +1,7 @@
from flask import Flask
VWS_FLASK_APP = Flask(__name__)
+
+@VWS_FLASK_APP.route('/targets', methods=['POST'])
+def _():
+ return ''
From 21b934aa2341e8c3b3afb92bcd631f9b483a490f Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 21 Jan 2020 09:36:16 +0000
Subject: [PATCH 0012/3455] Progress towards using flask app as a mock
---
src/_mock_vws_server/vws/__init__.py | 62 ++++++++++++++++++++-
tests/mock_vws/fixtures/vuforia_backends.py | 5 +-
2 files changed, 62 insertions(+), 5 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 544473059..00cddacf7 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -1,7 +1,65 @@
-from flask import Flask
+import base64
+import io
+import uuid
+
+from flask import Flask, request
+from requests import codes
+
+from mock_vws._constants import ResultCodes
+from mock_vws._mock_common import json_dump
+from mock_vws.target import Target
VWS_FLASK_APP = Flask(__name__)
+
@VWS_FLASK_APP.route('/targets', methods=['POST'])
def _():
- return ''
+ """
+ Add a target.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Add-a-Target
+ """
+ request.json['name']
+ # database = get_database_matching_server_keys(
+ # request=request,
+ # databases=self.databases,
+ # )
+ #
+ # assert isinstance(database, VuforiaDatabase)
+ #
+ # (target for target in database.targets if not target.delete_date)
+ # if any(target.name == name for target in targets):
+ # context.status_code = codes.FORBIDDEN
+ # body = {
+ # 'transaction_id': uuid.uuid4().hex,
+ # 'result_code': ResultCodes.TARGET_NAME_EXIST.value,
+ # }
+ # return json_dump(body)
+
+ active_flag = request.json.get('active_flag')
+ if active_flag is None:
+ active_flag = True
+
+ image = request.json['image']
+ decoded = base64.b64decode(image)
+ image_file = io.BytesIO(decoded)
+
+ new_target = Target(
+ name=request.json['name'],
+ width=request.json['width'],
+ image=image_file,
+ active_flag=active_flag,
+ processing_time_seconds=0.2,
+ # processing_time_seconds=self._processing_time_seconds,
+ application_metadata=request.json.get('application_metadata'),
+ )
+ # database.targets.append(new_target)
+
+ # context.status_code = codes.CREATED
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.TARGET_CREATED.value,
+ 'target_id': new_target.target_id,
+ }
+ return json_dump(body), codes.CREATED
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index 12407c8f0..15b21246d 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -13,12 +13,12 @@
from requests import codes
from requests_mock_flask import add_flask_app_to_mock
+from _mock_vws_server.vwq import CLOUDRECO_FLASK_APP
+from _mock_vws_server.vws import VWS_FLASK_APP
from mock_vws import MockVWS
from mock_vws._constants import ResultCodes
from mock_vws.database import VuforiaDatabase
from mock_vws.states import States
-from _mock_vws_server.vws import VWS_FLASK_APP
-from _mock_vws_server.vwq import CLOUDRECO_FLASK_APP
from tests.mock_vws.utils import (
delete_target,
list_targets,
@@ -131,7 +131,6 @@ def _enable_use_docker_in_memory(
yield
-
class VuforiaBackend(Enum):
"""
Backends for tests.
From a1b53ec4701b1068e3c9c6886bdab2756b1b9813 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 21 Jan 2020 10:07:21 +0000
Subject: [PATCH 0013/3455] Progress towards using flask app as a mock
---
src/_mock_vws_server/vws/__init__.py | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 00cddacf7..3d87d843f 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -11,6 +11,17 @@
VWS_FLASK_APP = Flask(__name__)
+# TODO Instead of decorator, use
+# @app.before_request
+#
+# @app.before_request
+
+@VWS_FLASK_APP.after_request
+def set_headers(response):
+ response.headers['Connection'] = 'keep-alive'
+ response.headers['Content-Type'] = 'application/json'
+ response.headers['Server'] = 'nginx'
+ return response
@VWS_FLASK_APP.route('/targets', methods=['POST'])
def _():
From 61963d1bd7424ece50b28847a9d9346e3cd848f9 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 21 Jan 2020 10:51:51 +0000
Subject: [PATCH 0014/3455] Progress towards using flask app as a mock
---
src/_mock_vws_server/vws/__init__.py | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 3d87d843f..b9f667b34 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -1,4 +1,5 @@
import base64
+import email.utils
import io
import uuid
@@ -11,27 +12,31 @@
VWS_FLASK_APP = Flask(__name__)
-# TODO Instead of decorator, use
-# @app.before_request
-#
-# @app.before_request
+# @VWS_FLASK_APP.before_request
+# def validate_request():
+# pass
+
@VWS_FLASK_APP.after_request
def set_headers(response):
response.headers['Connection'] = 'keep-alive'
response.headers['Content-Type'] = 'application/json'
response.headers['Server'] = 'nginx'
+ content_length = len(response.data)
+ response.headers['Content-Length'] = str(content_length)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ response.headers['Date'] = date
return response
@VWS_FLASK_APP.route('/targets', methods=['POST'])
-def _():
+def add_target():
"""
Add a target.
Fake implementation of
https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Add-a-Target
"""
- request.json['name']
+ name = request.json['name']
# database = get_database_matching_server_keys(
# request=request,
# databases=self.databases,
From 73f1b89b9b64e81006a06597bc275c2947ebf831 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 21 Jan 2020 14:54:49 +0000
Subject: [PATCH 0015/3455] Passing test for bad content type
---
src/_mock_vws_server/vws/__init__.py | 22 +++++++++++++---------
1 file changed, 13 insertions(+), 9 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index b9f667b34..abce11e87 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -1,4 +1,5 @@
import base64
+import json
import email.utils
import io
import uuid
@@ -12,9 +13,9 @@
VWS_FLASK_APP = Flask(__name__)
-# @VWS_FLASK_APP.before_request
-# def validate_request():
-# pass
+@VWS_FLASK_APP.before_request
+def validate_request():
+ pass
@VWS_FLASK_APP.after_request
@@ -36,7 +37,10 @@ def add_target():
Fake implementation of
https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Add-a-Target
"""
- name = request.json['name']
+ # We do not use ``request.json`` because this only works when the content
+ # type is given as ``application/json``.
+ request_json = json.loads(request.data)
+ name = request_json['name']
# database = get_database_matching_server_keys(
# request=request,
# databases=self.databases,
@@ -53,22 +57,22 @@ def add_target():
# }
# return json_dump(body)
- active_flag = request.json.get('active_flag')
+ active_flag = request_json.get('active_flag')
if active_flag is None:
active_flag = True
- image = request.json['image']
+ image = request_json['image']
decoded = base64.b64decode(image)
image_file = io.BytesIO(decoded)
new_target = Target(
- name=request.json['name'],
- width=request.json['width'],
+ name=request_json['name'],
+ width=request_json['width'],
image=image_file,
active_flag=active_flag,
processing_time_seconds=0.2,
# processing_time_seconds=self._processing_time_seconds,
- application_metadata=request.json.get('application_metadata'),
+ application_metadata=request_json.get('application_metadata'),
)
# database.targets.append(new_target)
From 1f2cdead979974622b47ab941cc1079a6b95bee8 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 21 Jan 2020 15:00:15 +0000
Subject: [PATCH 0016/3455] Remove commented out code
---
src/_mock_vws_server/vws/__init__.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index abce11e87..c008a0e02 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -76,7 +76,6 @@ def add_target():
)
# database.targets.append(new_target)
- # context.status_code = codes.CREATED
body = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.TARGET_CREATED.value,
From 2f6378b44be7a838baf4035ecc03fdc76c1cd385 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 21 Jan 2020 15:04:03 +0000
Subject: [PATCH 0017/3455] Validate that the content type header is given
---
src/_mock_vws_server/vws/__init__.py | 46 ++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index c008a0e02..02ea56e65 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -10,10 +10,56 @@
from mock_vws._constants import ResultCodes
from mock_vws._mock_common import json_dump
from mock_vws.target import Target
+import wrapt
+import uuid
+from typing import Any, Callable, Dict, Tuple
+
+import wrapt
+from requests import codes
+from requests_mock import POST, PUT
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+from mock_vws._constants import ResultCodes
+from mock_vws._mock_common import json_dump
VWS_FLASK_APP = Flask(__name__)
+@wrapt.decorator
+def validate_content_type_header_given(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that there is a non-empty content type header given if required.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNAUTHORIZED` response if there is no "Content-Type" header or the
+ given header is empty.
+ """
+ request_needs_content_type = bool(request.method in (POST, PUT))
+ if request.headers.get('Content-Type') or not request_needs_content_type:
+ return wrapped(*args, **kwargs)
+
+ # context.status_code = codes.UNAUTHORIZED
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
+ }
+ return json_dump(body), codes.UNAUTHORIZED
+
@VWS_FLASK_APP.before_request
+@validate_content_type_header_given
def validate_request():
pass
From c86a57595463e90c77d14be0a041900bbb73a539 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 22 Jan 2020 11:36:58 +0000
Subject: [PATCH 0018/3455] A bunch of passing tests
---
src/_mock_vws_server/vws/__init__.py | 126 +++++++++++++++++----------
1 file changed, 80 insertions(+), 46 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 02ea56e65..a6e4d11bb 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -1,7 +1,7 @@
import base64
-import json
import email.utils
import io
+import json
import uuid
from flask import Flask, request
@@ -10,58 +10,91 @@
from mock_vws._constants import ResultCodes
from mock_vws._mock_common import json_dump
from mock_vws.target import Target
-import wrapt
-import uuid
-from typing import Any, Callable, Dict, Tuple
-import wrapt
-from requests import codes
-from requests_mock import POST, PUT
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-from mock_vws._constants import ResultCodes
-from mock_vws._mock_common import json_dump
+from ._services_validators import (
+ validate_active_flag,
+ validate_metadata_encoding,
+ validate_metadata_size,
+ validate_metadata_type,
+ validate_name_characters_in_range,
+ validate_name_length,
+ validate_name_type,
+ validate_not_invalid_json,
+ validate_width,
+)
+from ._services_validators.auth_validators import (
+ validate_access_key_exists,
+ validate_auth_header_exists,
+ validate_auth_header_has_signature,
+)
+from ._services_validators.content_length_validators import (
+ validate_content_length_header_is_int,
+ validate_content_length_header_not_too_large,
+ validate_content_length_header_not_too_small,
+)
+from ._services_validators.content_type_validators import (
+ validate_content_type_header_given,
+)
+from ._services_validators.date_validators import (
+ validate_date_format,
+ validate_date_header_given,
+ validate_date_in_range,
+)
+from ._services_validators.image_validators import (
+ validate_image_color_space,
+ validate_image_data_type,
+ validate_image_encoding,
+ validate_image_format,
+ validate_image_is_image,
+ validate_image_size,
+)
VWS_FLASK_APP = Flask(__name__)
-@wrapt.decorator
-def validate_content_type_header_given(
- wrapped: Callable[..., str],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> str:
- """
- Validate that there is a non-empty content type header given if required.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNAUTHORIZED` response if there is no "Content-Type" header or the
- given header is empty.
- """
- request_needs_content_type = bool(request.method in (POST, PUT))
- if request.headers.get('Content-Type') or not request_needs_content_type:
- return wrapped(*args, **kwargs)
-
- # context.status_code = codes.UNAUTHORIZED
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
- }
- return json_dump(body), codes.UNAUTHORIZED
@VWS_FLASK_APP.before_request
+# @validate_project_state
+# @validate_authorization
+@validate_metadata_size
+@validate_metadata_encoding
+@validate_metadata_type
+@validate_active_flag
+@validate_image_size
+@validate_image_color_space
+@validate_image_format
+@validate_image_is_image
+@validate_image_encoding
+@validate_image_data_type
+@validate_name_characters_in_range
+@validate_name_length
+@validate_name_type
+@validate_width
@validate_content_type_header_given
+@validate_date_in_range
+@validate_date_format
+@validate_date_header_given
+@validate_not_invalid_json
+# @validate_access_key_exists
+@validate_auth_header_has_signature
+@validate_auth_header_exists
+@validate_content_length_header_not_too_small
+@validate_content_length_header_not_too_large
+@validate_content_length_header_is_int
def validate_request():
pass
+ # # TODO put this back somehow
+ # # key_validator = validate_keys(
+ # # optional_keys=optional_keys or set([]),
+ # # mandatory_keys=mandatory_keys or set([]),
+ # # )
+ #
+ # decorators = [
+ # # parse_target_id,
+ # # key_validator,
+ # # set_date_header,
+ # # set_content_length_header,
+ # # update_request_count,
+ # ]
@VWS_FLASK_APP.after_request
@@ -75,6 +108,7 @@ def set_headers(response):
response.headers['Date'] = date
return response
+
@VWS_FLASK_APP.route('/targets', methods=['POST'])
def add_target():
"""
@@ -83,10 +117,10 @@ def add_target():
Fake implementation of
https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Add-a-Target
"""
- # We do not use ``request.json`` because this only works when the content
+ # We do not use ``request.get_json(force=True)`` because this only works when the content
# type is given as ``application/json``.
request_json = json.loads(request.data)
- name = request_json['name']
+ request_json['name']
# database = get_database_matching_server_keys(
# request=request,
# databases=self.databases,
From 44505ecdf8c3139bcbf631ea61d11e7e207934dc Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 22 Jan 2020 11:39:17 +0000
Subject: [PATCH 0019/3455] A bunch of passing tests
---
src/_mock_vws_server/vws/_constants.py | 49 ++
src/_mock_vws_server/vws/_mock_common.py | 132 +++++
.../vws/_services_validators/__init__.py | 507 ++++++++++++++++++
.../_services_validators/auth_validators.py | 152 ++++++
.../content_length_validators.py | 119 ++++
.../content_type_validators.py | 49 ++
.../_services_validators/date_validators.py | 128 +++++
.../_services_validators/image_validators.py | 276 ++++++++++
8 files changed, 1412 insertions(+)
create mode 100644 src/_mock_vws_server/vws/_constants.py
create mode 100644 src/_mock_vws_server/vws/_mock_common.py
create mode 100644 src/_mock_vws_server/vws/_services_validators/__init__.py
create mode 100644 src/_mock_vws_server/vws/_services_validators/auth_validators.py
create mode 100644 src/_mock_vws_server/vws/_services_validators/content_length_validators.py
create mode 100644 src/_mock_vws_server/vws/_services_validators/content_type_validators.py
create mode 100644 src/_mock_vws_server/vws/_services_validators/date_validators.py
create mode 100644 src/_mock_vws_server/vws/_services_validators/image_validators.py
diff --git a/src/_mock_vws_server/vws/_constants.py b/src/_mock_vws_server/vws/_constants.py
new file mode 100644
index 000000000..cbba98ffa
--- /dev/null
+++ b/src/_mock_vws_server/vws/_constants.py
@@ -0,0 +1,49 @@
+"""
+Constants used to make the VWS mock.
+"""
+
+from enum import Enum
+
+
+class ResultCodes(Enum):
+ """
+ Constants representing various VWS result codes.
+
+ See
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Interperete-VWS-API-Result-Codes
+
+ Some codes here are not documented in the above link.
+ """
+
+ SUCCESS = 'Success'
+ TARGET_CREATED = 'TargetCreated'
+ AUTHENTICATION_FAILURE = 'AuthenticationFailure'
+ REQUEST_TIME_TOO_SKEWED = 'RequestTimeTooSkewed'
+ TARGET_NAME_EXIST = 'TargetNameExist'
+ UNKNOWN_TARGET = 'UnknownTarget'
+ BAD_IMAGE = 'BadImage'
+ IMAGE_TOO_LARGE = 'ImageTooLarge'
+ METADATA_TOO_LARGE = 'MetadataTooLarge'
+ # The documentation says "Start date is after the end date" but, at the
+ # time of writing, I do not know how to trigger that, therefore this is not
+ # tested.
+ DATE_RANGE_ERROR = 'DateRangeError'
+ FAIL = 'Fail'
+ TARGET_STATUS_PROCESSING = 'TargetStatusProcessing'
+ REQUEST_QUOTA_REACHED = 'RequestQuotaReached'
+ TARGET_STATUS_NOT_SUCCESS = 'TargetStatusNotSuccess'
+ PROJECT_INACTIVE = 'ProjectInactive'
+ INACTIVE_PROJECT = 'InactiveProject'
+
+
+class TargetStatuses(Enum):
+ """
+ Constants representing VWS target statuses.
+
+ See the 'status' field in
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Retrieve-a-Target-Record
+ """
+
+ PROCESSING = 'processing'
+ SUCCESS = 'success'
+ FAILED = 'failed'
diff --git a/src/_mock_vws_server/vws/_mock_common.py b/src/_mock_vws_server/vws/_mock_common.py
new file mode 100644
index 000000000..4a13a3cb9
--- /dev/null
+++ b/src/_mock_vws_server/vws/_mock_common.py
@@ -0,0 +1,132 @@
+"""
+Common utilities for creating mock routes.
+"""
+
+import cgi
+import email.utils
+import io
+import json
+from typing import Any, Callable, Dict, List, Mapping, Tuple, Union
+
+import wrapt
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+
+class Route:
+ """
+ A container for the route details which `requests_mock` needs.
+
+ We register routes with names, and when we have an instance to work with
+ later.
+ """
+
+ route_name: str
+ path_pattern: str
+ http_methods: List[str]
+
+ def __init__(
+ self,
+ route_name: str,
+ path_pattern: str,
+ http_methods: List[str],
+ ) -> None:
+ """
+ Args:
+ route_name: The name of the method.
+ path_pattern: The end part of a URL pattern. E.g. `/targets` or
+ `/targets/.+`.
+ http_methods: HTTP methods that map to the route function.
+
+ Attributes:
+ route_name: The name of the method.
+ path_pattern: The end part of a URL pattern. E.g. `/targets` or
+ `/targets/.+`.
+ http_methods: HTTP methods that map to the route function.
+ endpoint: The method `requests_mock` should call when the endpoint
+ is requested.
+ """
+ self.route_name = route_name
+ self.path_pattern = path_pattern
+ self.http_methods = http_methods
+
+
+def json_dump(body: Dict[str, Any]) -> str:
+ """
+ Returns:
+ JSON dump of data in the same way that Vuforia dumps data.
+ """
+ return json.dumps(obj=body, separators=(',', ':'))
+
+
+@wrapt.decorator
+def set_content_length_header(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Set the `Content-Length` header.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ """
+ _, context = args
+
+ result = wrapped(*args, **kwargs)
+ context.headers['Content-Length'] = str(len(result))
+ return result
+
+
+@wrapt.decorator
+def set_date_header(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Set the `Date` header.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ """
+ _, context = args
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+
+ result = wrapped(*args, **kwargs)
+ context.headers['Date'] = date
+ return result
+
+
+def parse_multipart( # pylint: disable=invalid-name
+ fp: io.BytesIO,
+ pdict: Mapping[str, bytes],
+) -> Dict[str, List[Union[str, bytes]]]:
+ """
+ Return parsed ``pdict``.
+
+ Wrapper for ``_parse_multipart`` to work around
+ https://bugs.python.org/issue34226.
+
+ See https://docs.python.org/3.8/library/cgi.html#_parse_multipart.
+ """
+ pdict = {
+ 'CONTENT-LENGTH': str(len(fp.getvalue())).encode(),
+ **pdict,
+ }
+
+ return cgi.parse_multipart(fp=fp, pdict=pdict)
diff --git a/src/_mock_vws_server/vws/_services_validators/__init__.py b/src/_mock_vws_server/vws/_services_validators/__init__.py
new file mode 100644
index 000000000..070e0175b
--- /dev/null
+++ b/src/_mock_vws_server/vws/_services_validators/__init__.py
@@ -0,0 +1,507 @@
+"""
+Input validators to use in the mock.
+"""
+
+import binascii
+import numbers
+import uuid
+from json.decoder import JSONDecodeError
+from pathlib import Path
+from typing import Any, Callable, Dict, Set, Tuple
+
+import wrapt
+from flask import request
+from requests import codes
+from requests_mock import POST, PUT
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+from mock_vws._base64_decoding import decode_base64
+from mock_vws._constants import ResultCodes
+from mock_vws._database_matchers import get_database_matching_server_keys
+from mock_vws._mock_common import json_dump
+from mock_vws.database import VuforiaDatabase
+from mock_vws.states import States
+
+
+@wrapt.decorator
+def validate_active_flag(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the active flag data given to the endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `BAD_REQUEST` response with a FAIL result code if there is
+ active flag data given to the endpoint which is not either a Boolean or
+ NULL.
+ """
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ if 'active_flag' not in request.get_json(force=True):
+ return wrapped(*args, **kwargs)
+
+ active_flag = request.get_json(force=True).get('active_flag')
+
+ if active_flag is None or isinstance(active_flag, bool):
+ return wrapped(*args, **kwargs)
+
+ body: Dict[str, str] = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+
+
+@wrapt.decorator
+def validate_project_state(
+ wrapped: Callable[..., str],
+ instance: Any,
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the state of the project.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `FORBIDDEN` response with a PROJECT_INACTIVE result code if the
+ project is inactive.
+ """
+ database = get_database_matching_server_keys(
+ request=request,
+ databases=instance.databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+ if database.state != States.PROJECT_INACTIVE:
+ return wrapped(*args, **kwargs)
+
+ if request.method == 'GET' and 'duplicates' not in request.path:
+ return wrapped(*args, **kwargs)
+
+ body: Dict[str, str] = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.PROJECT_INACTIVE.value,
+ }
+ return json_dump(body), codes.FORBIDDEN
+
+
+@wrapt.decorator
+def validate_not_invalid_json(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that there is either no JSON given or the JSON given is valid.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `BAD_REQUEST` response with a FAIL result code if there is invalid
+ JSON given to a POST or PUT request.
+ A `BAD_REQUEST` with empty text if there is data given to another
+ request type.
+ """
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ if request.method not in (POST, PUT):
+ # TODO this is commented out but not but should maybe be moved to an
+ # after_request decorator
+ # context.headers.pop('Content-Type')
+ return ''
+
+ try:
+ request.get_json(force=True)
+ except JSONDecodeError:
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+
+ return wrapped(*args, **kwargs)
+
+
+@wrapt.decorator
+def validate_width(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the width argument given to a VWS endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `BAD_REQUEST` response if the width is given and is not a positive
+ number.
+ """
+
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ if 'width' not in request.get_json(force=True):
+ return wrapped(*args, **kwargs)
+
+ width = request.get_json(force=True).get('width')
+
+ width_is_number = isinstance(width, numbers.Number)
+ width_positive = width_is_number and width > 0
+
+ if not width_positive:
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+
+ return wrapped(*args, **kwargs)
+
+
+@wrapt.decorator
+def validate_name_type(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the type of the name argument given to a VWS endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `BAD_REQUEST` response if the name is given and not a string.
+ is not between 1 and
+ 64 characters in length.
+ """
+
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ if 'name' not in request.get_json(force=True):
+ return wrapped(*args, **kwargs)
+
+ name = request.get_json(force=True)['name']
+
+ if isinstance(name, str):
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+
+
+@wrapt.decorator
+def validate_name_length(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the length of the name argument given to a VWS endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `BAD_REQUEST` response if the name is given is not between 1 and 64
+ characters in length.
+ """
+
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ if 'name' not in request.get_json(force=True):
+ return wrapped(*args, **kwargs)
+
+ name = request.get_json(force=True)['name']
+
+ if name and len(name) < 65:
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+
+
+@wrapt.decorator
+def validate_name_characters_in_range(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the characters in the name argument given to a VWS endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A ``FORBIDDEN`` response if the name is given includes characters
+ outside of the accepted range.
+ """
+
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ if 'name' not in request.get_json(force=True):
+ return wrapped(*args, **kwargs)
+
+ name = request.get_json(force=True)['name']
+
+ if all(ord(character) <= 65535 for character in name):
+ return wrapped(*args, **kwargs)
+
+ if (request.method, request.path) == ('POST', '/targets'):
+ resources_dir = Path(__file__).parent.parent / 'resources'
+ filename = 'oops_error_occurred_response.html'
+ oops_resp_file = resources_dir / filename
+ # TODO construct a Response
+ # context.headers['Content-Type'] = 'text/html; charset=UTF-8'
+ text = oops_resp_file.read_text()
+ return text, codes.INTERNAL_SERVER_ERROR
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.TARGET_NAME_EXIST.value,
+ }
+ return json_dump(body), codes.FORBIDDEN
+
+
+def validate_keys(
+ mandatory_keys: Set[str],
+ optional_keys: Set[str],
+) -> Callable:
+ """
+ Args:
+ mandatory_keys: Keys required by the endpoint.
+ optional_keys: Keys which are not required by the endpoint but which
+ are allowed.
+
+ Returns:
+ A wrapper function to validate that the keys given to the endpoint are
+ all allowed and that the mandatory keys are given.
+ """
+
+ # Args here to work around https://github.com/PyCQA/pydocstyle/issues/370.
+ #
+ # Args:
+ # wrapped: An endpoint function for `requests_mock`.
+ # instance: The class that the endpoint function is in.
+ # args: The arguments given to the endpoint function.
+ # kwargs: The keyword arguments given to the endpoint function.
+ @wrapt.decorator
+ def wrapper(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+ ) -> str:
+ """
+ Validate the request keys given to a VWS endpoint.
+
+ Returns:
+ The result of calling the endpoint.
+ A `BAD_REQUEST` error if any keys are not allowed, or if any
+ required keys are missing.
+ """
+
+ allowed_keys = mandatory_keys.union(optional_keys)
+
+ if request.text is None and not allowed_keys:
+ return wrapped(*args, **kwargs)
+
+ given_keys = set(request.get_json(force=True).keys())
+ all_given_keys_allowed = given_keys.issubset(allowed_keys)
+ all_mandatory_keys_given = mandatory_keys.issubset(given_keys)
+
+ if all_given_keys_allowed and all_mandatory_keys_given:
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+
+ wrapper_func: Callable[..., Any] = wrapper
+ return wrapper_func
+
+
+@wrapt.decorator
+def validate_metadata_encoding(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that the given application metadata can be base64 decoded.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNPROCESSABLE_ENTITY` response if application metadata is given and
+ it cannot be base64 decoded.
+ """
+
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ if 'application_metadata' not in request.get_json(force=True):
+ return wrapped(*args, **kwargs)
+
+ application_metadata = request.get_json(force=True).get('application_metadata')
+
+ if application_metadata is None:
+ return wrapped(*args, **kwargs)
+
+ try:
+ decode_base64(encoded_data=application_metadata)
+ except binascii.Error:
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.UNPROCESSABLE_ENTITY
+
+ return wrapped(*args, **kwargs)
+
+
+@wrapt.decorator
+def validate_metadata_type(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that the given application metadata is a string or NULL.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `BAD_REQUEST` response if application metadata is given and it is
+ not a string or NULL.
+ """
+
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ if 'application_metadata' not in request.get_json(force=True):
+ return wrapped(*args, **kwargs)
+
+ application_metadata = request.get_json(force=True).get('application_metadata')
+
+ if application_metadata is None or isinstance(application_metadata, str):
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+
+
+@wrapt.decorator
+def validate_metadata_size(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that the given application metadata is a string or 1024 * 1024
+ bytes or fewer.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNPROCESSABLE_ENTITY` response if application metadata is given and
+ it is too large.
+ """
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ application_metadata = request.get_json(force=True).get('application_metadata')
+ if application_metadata is None:
+ return wrapped(*args, **kwargs)
+ decoded = decode_base64(encoded_data=application_metadata)
+
+ max_metadata_bytes = 1024 * 1024 - 1
+ if len(decoded) <= max_metadata_bytes:
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.METADATA_TOO_LARGE.value,
+ }
+ return json_dump(body), codes.UNPROCESSABLE_ENTITY
diff --git a/src/_mock_vws_server/vws/_services_validators/auth_validators.py b/src/_mock_vws_server/vws/_services_validators/auth_validators.py
new file mode 100644
index 000000000..3b516f542
--- /dev/null
+++ b/src/_mock_vws_server/vws/_services_validators/auth_validators.py
@@ -0,0 +1,152 @@
+"""
+Authorization header validators to use in the mock.
+"""
+
+import uuid
+from typing import Any, Callable, Dict, Tuple
+
+import wrapt
+from flask import request
+from requests import codes
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+from mock_vws._constants import ResultCodes
+from mock_vws._database_matchers import get_database_matching_server_keys
+from mock_vws._mock_common import json_dump
+
+
+@wrapt.decorator
+def validate_auth_header_exists(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that there is an authorization header given to a VWS endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNAUTHORIZED` response if there is no "Authorization" header.
+ """
+
+ if 'Authorization' in request.headers:
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
+ }
+ return json_dump(body), codes.UNAUTHORIZED
+
+
+@wrapt.decorator
+def validate_access_key_exists(
+ wrapped: Callable[..., str],
+ instance: Any,
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the authorization header includes an access key for a database.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An ``UNAUTHORIZED`` response if the access key is unknown.
+ """
+
+ header = request.headers['Authorization']
+ first_part, _ = header.split(':')
+ _, access_key = first_part.split(' ')
+ for database in instance.databases:
+ if access_key == database.server_access_key:
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+
+
+@wrapt.decorator
+def validate_auth_header_has_signature(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the authorization header includes a signature.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An ``UNAUTHORIZED`` response if the "Authorization" header is not as
+ expected.
+ """
+
+ header = request.headers['Authorization']
+ if header.count(':') == 1 and header.split(':')[1]:
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+
+
+@wrapt.decorator
+def validate_authorization(
+ wrapped: Callable[..., str],
+ instance: Any,
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the authorization header given to a VWS endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `BAD_REQUEST` response if the "Authorization" header is not as
+ expected.
+ """
+
+ database = get_database_matching_server_keys(
+ request=request,
+ databases=instance.databases,
+ )
+
+ if database is not None:
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
+ }
+ return json_dump(body), codes.UNAUTHORIZED
diff --git a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
new file mode 100644
index 000000000..bc2712df2
--- /dev/null
+++ b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
@@ -0,0 +1,119 @@
+"""
+Content-Length header validators to use in the mock.
+"""
+
+import uuid
+from typing import Any, Callable, Dict, Tuple
+
+import wrapt
+from requests import codes
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+from .._constants import ResultCodes
+from .._mock_common import json_dump
+from flask import request
+
+
+@wrapt.decorator
+def validate_content_length_header_is_int(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the ``Content-Length`` header is an integer.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A ``BAD_REQUEST`` response if the content length header is not an
+ integer.
+ """
+
+ body_length = len(request.data if request.data else '')
+ given_content_length = request.headers.get('Content-Length', body_length)
+
+ try:
+ int(given_content_length)
+ except ValueError:
+ # TODO construct response
+ # context.headers = {'Connection': 'Close'}
+ return '', codes.BAD_REQUEST
+
+ return wrapped(*args, **kwargs)
+
+
+@wrapt.decorator
+def validate_content_length_header_not_too_large(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the ``Content-Length`` header is not too large.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A ``GATEWAY_TIMEOUT`` response if the given content length header says
+ that the content length is greater than the body length.
+ """
+
+ body_length = len(request.data if request.data else '')
+ given_content_length = request.headers.get('Content-Length', body_length)
+ given_content_length_value = int(given_content_length)
+ if given_content_length_value > body_length:
+ # TODO construct a response object
+ # context.headers = {'Connection': 'keep-alive'}
+ return '', codes.GATEWAY_TIMEOUT
+
+ return wrapped(*args, **kwargs)
+
+
+@wrapt.decorator
+def validate_content_length_header_not_too_small(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the ``Content-Length`` header is not too small.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An ``UNAUTHORIZED`` response if the given content length header says
+ that the content length is smaller than the body length.
+ """
+
+ body_length = len(request.data if request.data else '')
+ given_content_length = request.headers.get('Content-Length', body_length)
+ given_content_length_value = int(given_content_length)
+
+ if given_content_length_value < body_length:
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
+ }
+ return json_dump(body), codes.UNAUTHORIZED
+
+ return wrapped(*args, **kwargs)
diff --git a/src/_mock_vws_server/vws/_services_validators/content_type_validators.py b/src/_mock_vws_server/vws/_services_validators/content_type_validators.py
new file mode 100644
index 000000000..22403359d
--- /dev/null
+++ b/src/_mock_vws_server/vws/_services_validators/content_type_validators.py
@@ -0,0 +1,49 @@
+"""
+Content-Type header validators to use in the mock.
+"""
+
+import uuid
+from typing import Any, Callable, Dict, Tuple
+
+import wrapt
+from flask import request
+from requests import codes
+from requests_mock import POST, PUT
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+from mock_vws._constants import ResultCodes
+from mock_vws._mock_common import json_dump
+
+
+@wrapt.decorator
+def validate_content_type_header_given(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that there is a non-empty content type header given if required.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNAUTHORIZED` response if there is no "Content-Type" header or the
+ given header is empty.
+ """
+
+ request_needs_content_type = bool(request.method in (POST, PUT))
+ if request.headers.get('Content-Type') or not request_needs_content_type:
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
+ }
+ return json_dump(body), codes.UNAUTHORIZED
diff --git a/src/_mock_vws_server/vws/_services_validators/date_validators.py b/src/_mock_vws_server/vws/_services_validators/date_validators.py
new file mode 100644
index 000000000..8698696ca
--- /dev/null
+++ b/src/_mock_vws_server/vws/_services_validators/date_validators.py
@@ -0,0 +1,128 @@
+"""
+Validators of the date header to use in the mock services API.
+"""
+
+import datetime
+import uuid
+from typing import Any, Callable, Dict, Tuple
+
+import pytz
+import wrapt
+from flask import request
+from requests import codes
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+from mock_vws._constants import ResultCodes
+from mock_vws._mock_common import json_dump
+
+
+@wrapt.decorator
+def validate_date_header_given(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the date header is given to a VWS endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `BAD_REQUEST` response if the date is not given.
+ """
+
+ if 'Date' in request.headers:
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+
+
+@wrapt.decorator
+def validate_date_format(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the format of the date header given to a VWS endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `BAD_REQUEST` response if the date is in the wrong format.
+ A `FORBIDDEN` response if the date is out of range.
+ """
+
+ date_header = request.headers['Date']
+ date_format = '%a, %d %b %Y %H:%M:%S GMT'
+ try:
+ datetime.datetime.strptime(date_header, date_format)
+ except ValueError:
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+
+ return wrapped(*args, **kwargs)
+
+
+@wrapt.decorator
+def validate_date_in_range(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the date header given to a VWS endpoint is in range.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `FORBIDDEN` response if the date is out of range.
+ """
+
+ date_from_header = datetime.datetime.strptime(
+ request.headers['Date'],
+ '%a, %d %b %Y %H:%M:%S GMT',
+ )
+
+ gmt = pytz.timezone('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:
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.REQUEST_TIME_TOO_SKEWED.value,
+ }
+ return json_dump(body), codes.FORBIDDEN
+
+ return wrapped(*args, **kwargs)
diff --git a/src/_mock_vws_server/vws/_services_validators/image_validators.py b/src/_mock_vws_server/vws/_services_validators/image_validators.py
new file mode 100644
index 000000000..d2cd87b24
--- /dev/null
+++ b/src/_mock_vws_server/vws/_services_validators/image_validators.py
@@ -0,0 +1,276 @@
+"""
+Image validators to use in the mock.
+"""
+
+import binascii
+import io
+import uuid
+from typing import Any, Callable, Dict, Tuple
+
+import wrapt
+from flask import request
+from PIL import Image
+from requests import codes
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+from mock_vws._base64_decoding import decode_base64
+from mock_vws._constants import ResultCodes
+from mock_vws._mock_common import json_dump
+
+
+@wrapt.decorator
+def validate_image_format(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the format of the image given to a VWS endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNPROCESSABLE_ENTITY` response if the image is given and is not
+ either a PNG or a JPEG.
+ """
+
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ image = request.get_json(force=True).get('image')
+
+ if image is None:
+ return wrapped(*args, **kwargs)
+
+ decoded = decode_base64(encoded_data=image)
+ image_file = io.BytesIO(decoded)
+ pil_image = Image.open(image_file)
+
+ if pil_image.format in ('PNG', 'JPEG'):
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.BAD_IMAGE.value,
+ }
+ return json_dump(body), codes.UNPROCESSABLE_ENTITY
+
+
+@wrapt.decorator
+def validate_image_color_space(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the color space of the image given to a VWS endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNPROCESSABLE_ENTITY` response if the image is given and is not
+ in either the RGB or greyscale color space.
+ """
+
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ image = request.get_json(force=True).get('image')
+
+ if image is None:
+ return wrapped(*args, **kwargs)
+
+ decoded = decode_base64(encoded_data=image)
+ image_file = io.BytesIO(decoded)
+ pil_image = Image.open(image_file)
+
+ if pil_image.mode in ('L', 'RGB'):
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.BAD_IMAGE.value,
+ }
+ return json_dump(body), codes.UNPROCESSABLE_ENTITY
+
+
+@wrapt.decorator
+def validate_image_size(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the file size of the image given to a VWS endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNPROCESSABLE_ENTITY` response if the image is given and is not
+ under a certain file size threshold.
+ """
+
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ image = request.get_json(force=True).get('image')
+
+ if image is None:
+ return wrapped(*args, **kwargs)
+
+ decoded = decode_base64(encoded_data=image)
+
+ if len(decoded) <= 2359293:
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.IMAGE_TOO_LARGE.value,
+ }
+ return json_dump(body), codes.UNPROCESSABLE_ENTITY
+
+
+@wrapt.decorator
+def validate_image_is_image(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that the given image data is actually an image file.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNPROCESSABLE_ENTITY` response if image data is given and it is not
+ an image file.
+ """
+
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ image = request.get_json(force=True).get('image')
+
+ if image is None:
+ return wrapped(*args, **kwargs)
+
+ decoded = decode_base64(encoded_data=image)
+ image_file = io.BytesIO(decoded)
+
+ try:
+ Image.open(image_file)
+ except OSError:
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.BAD_IMAGE.value,
+ }
+ return json_dump(body), codes.UNPROCESSABLE_ENTITY
+
+ return wrapped(*args, **kwargs)
+
+
+@wrapt.decorator
+def validate_image_encoding(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that the given image data can be base64 decoded.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNPROCESSABLE_ENTITY` response if image data is given and it cannot
+ be base64 decoded.
+ """
+
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ if 'image' not in request.get_json(force=True):
+ return wrapped(*args, **kwargs)
+
+ image = request.get_json(force=True).get('image')
+
+ try:
+ decode_base64(encoded_data=image)
+ except binascii.Error:
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.UNPROCESSABLE_ENTITY
+
+ return wrapped(*args, **kwargs)
+
+
+@wrapt.decorator
+def validate_image_data_type(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that the given image data is a string.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `BAD_REQUEST` response if image data is given and it is not a
+ string.
+ """
+
+ if not request.data:
+ return wrapped(*args, **kwargs)
+
+ if 'image' not in request.get_json(force=True):
+ return wrapped(*args, **kwargs)
+
+ image = request.get_json(force=True).get('image')
+
+ if isinstance(image, str):
+ return wrapped(*args, **kwargs)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.BAD_REQUEST
From 3c28c0cb791a6afb5df183f11793ed5a10be1a93 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 22 Jan 2020 11:56:23 +0000
Subject: [PATCH 0020/3455] Add a bunch of key validation
---
src/_mock_vws_server/vws/__init__.py | 26 +++++++++++++++----
.../_services_validators/date_validators.py | 1 -
2 files changed, 21 insertions(+), 6 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index a6e4d11bb..4f6c270e7 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -6,6 +6,7 @@
from flask import Flask, request
from requests import codes
+from flask_json_schema import JsonSchema, JsonValidationError
from mock_vws._constants import ResultCodes
from mock_vws._mock_common import json_dump
@@ -50,6 +51,11 @@
)
VWS_FLASK_APP = Flask(__name__)
+JSON_SCHEMA = JsonSchema(VWS_FLASK_APP)
+
+ADD_TARGET_SCHEMA = {
+ 'required': ['name', 'image', 'width'],
+}
@VWS_FLASK_APP.before_request
@@ -82,11 +88,10 @@
@validate_content_length_header_is_int
def validate_request():
pass
- # # TODO put this back somehow
- # # key_validator = validate_keys(
- # # optional_keys=optional_keys or set([]),
- # # mandatory_keys=mandatory_keys or set([]),
- # # )
+ # key_validator = validate_keys(
+ # optional_keys=optional_keys or set([]),
+ # mandatory_keys=mandatory_keys or set([]),
+ # )
#
# decorators = [
# # parse_target_id,
@@ -96,6 +101,15 @@ def validate_request():
# # update_request_count,
# ]
+@VWS_FLASK_APP.errorhandler(JsonValidationError)
+def validation_error(e):
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.FAIL.value,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+
+
@VWS_FLASK_APP.after_request
def set_headers(response):
@@ -109,7 +123,9 @@ def set_headers(response):
return response
+
@VWS_FLASK_APP.route('/targets', methods=['POST'])
+@JSON_SCHEMA.validate(ADD_TARGET_SCHEMA)
def add_target():
"""
Add a target.
diff --git a/src/_mock_vws_server/vws/_services_validators/date_validators.py b/src/_mock_vws_server/vws/_services_validators/date_validators.py
index 8698696ca..611a5bc95 100644
--- a/src/_mock_vws_server/vws/_services_validators/date_validators.py
+++ b/src/_mock_vws_server/vws/_services_validators/date_validators.py
@@ -37,7 +37,6 @@ def validate_date_header_given(
The result of calling the endpoint.
A `BAD_REQUEST` response if the date is not given.
"""
-
if 'Date' in request.headers:
return wrapped(*args, **kwargs)
From 4a68664ba5d65e2adc748b39344d410c496ef490 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 22 Jan 2020 11:56:49 +0000
Subject: [PATCH 0021/3455] Add some error resources
---
.../vws/resources/match_processing_response | 78 +++++++++++++++++++
.../oops_error_occurred_response.html | 41 ++++++++++
2 files changed, 119 insertions(+)
create mode 100644 src/_mock_vws_server/vws/resources/match_processing_response
create mode 100644 src/_mock_vws_server/vws/resources/oops_error_occurred_response.html
diff --git a/src/_mock_vws_server/vws/resources/match_processing_response b/src/_mock_vws_server/vws/resources/match_processing_response
new file mode 100644
index 000000000..33d48f115
--- /dev/null
+++ b/src/_mock_vws_server/vws/resources/match_processing_response
@@ -0,0 +1,78 @@
+'\n\n\nError 500 Server Error
+title>\n\nHTTP ERROR 500
\nProblem accessing /v1/query. Reason:\n
Server Error
+p>Caused by:
org.jboss.resteasy.spi.UnhandledException: com.fasterxml.jackson.databind.exc.MismatchedInpu
+tException: No content to map due to end-of-input\n at [Source: (byte[])""; line: 1, column: 0]\n\tat org.jboss.restea
+sy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:76)\n\tat org.jboss.resteasy.core.ExceptionH
+andler.handleException(ExceptionHandler.java:212)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.writeException(S
+ynchronousDispatcher.java:168)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:4
+11)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:202)\n\tat org.jboss.resteas
+y.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:221)\n\tat org.jboss.reste
+asy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)\n\tat org.jboss.resteasy.plugi
+ns.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)\n\tat javax.servlet.http.HttpServlet.se
+rvice(HttpServlet.java:790)\n\tat org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:808)\n\tat org.ecl
+ipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669)\n\tat com.kooaba.queryservice.auth.KW
+SAuthFilter.doFilter(KWSAuthFilter.java:171)\n\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(Servl
+etHandler.java:1652)\n\tat org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)\n\tat org.eclips
+e.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)\n\tat org.eclipse.jetty.security.SecurityHandler.h
+andle(SecurityHandler.java:577)\n\tat org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223
+)\n\tat org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)\n\tat org.eclipse.jetty.ser
+vlet.ServletHandler.doScope(ServletHandler.java:515)\n\tat org.eclipse.jetty.server.session.SessionHandler.doScope(Ses
+sionHandler.java:185)\n\tat org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)\n\tat or
+g.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)\n\tat org.eclipse.jetty.server.handler.Con
+textHandlerCollection.handle(ContextHandlerCollection.java:215)\n\tat org.eclipse.jetty.server.handler.HandlerCollecti
+on.handle(HandlerCollection.java:110)\n\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java
+:97)\n\tat org.eclipse.jetty.server.Server.handle(Server.java:497)\n\tat org.eclipse.jetty.server.HttpChannel.handle(H
+ttpChannel.java:310)\n\tat org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)\n\tat org.eclip
+se.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool
+.runJob(QueuedThreadPool.java:635)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:55
+5)\n\tat java.lang.Thread.run(Thread.java:748)\nCaused by: com.fasterxml.jackson.databind.exc.MismatchedInputException
+: No content to map due to end-of-input\n at [Source: (byte[])""; line: 1, column: 0]\n\tat com.fasterxml.jackson.data
+bind.exc.MismatchedInputException.from(MismatchedInputException.java:59)\n\tat com.fasterxml.jackson.databind.ObjectMa
+pper._initForReading(ObjectMapper.java:4133)\n\tat com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(Object
+Mapper.java:3988)\n\tat com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3094)\n\tat com.kooaba
+.queryservice.domain.WebResult.setTargetData(WebResult.java:44)\n\tat com.kooaba.queryservice.domain.WebQueryResultPro
+cessor.formatResult(WebQueryResultProcessor.java:81)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.query
+Common(QueryResourceVuforia.java:230)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.limitedConcurrencyQu
+ery(QueryResourceVuforia.java:77)\n\tat com.kooaba.queryservice.services.QueryResourceCloudRecoWebAPI.query(QueryResou
+rceCloudRecoWebAPI.java:55)\n\tat sun.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)\n\tat sun.reflect.Deleg
+atingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java
+:606)\n\tat org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:139)\n\tat org.jboss.resteasy.co
+re.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:295)\n\tat org.jboss.resteasy.core.ResourceMethodIn
+voker.invoke(ResourceMethodInvoker.java:249)\n\tat org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethod
+Invoker.java:236)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:395)\n\t... 28
+ more\n
\nCaused by:
com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map
+due to end-of-input\n at [Source: (byte[])""; line: 1, column: 0]\n\tat com.fasterxml.jackson.databind.exc.MismatchedI
+nputException.from(MismatchedInputException.java:59)\n\tat com.fasterxml.jackson.databind.ObjectMapper._initForReading
+(ObjectMapper.java:4133)\n\tat com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3988)\n\
+tat com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3094)\n\tat com.kooaba.queryservice.domain
+.WebResult.setTargetData(WebResult.java:44)\n\tat com.kooaba.queryservice.domain.WebQueryResultProcessor.formatResult(
+WebQueryResultProcessor.java:81)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.queryCommon(QueryResource
+Vuforia.java:230)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.limitedConcurrencyQuery(QueryResourceVuf
+oria.java:77)\n\tat com.kooaba.queryservice.services.QueryResourceCloudRecoWebAPI.query(QueryResourceCloudRecoWebAPI.j
+ava:55)\n\tat sun.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorI
+mpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:606)\n\tat org.jbos
+s.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:139)\n\tat org.jboss.resteasy.core.ResourceMethodInv
+oker.invokeOnTarget(ResourceMethodInvoker.java:295)\n\tat org.jboss.resteasy.core.ResourceMethodInvoker.invoke(Resourc
+eMethodInvoker.java:249)\n\tat org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:236)\n\
+tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:395)\n\tat org.jboss.resteasy.core
+.SynchronousDispatcher.invoke(SynchronousDispatcher.java:202)\n\tat org.jboss.resteasy.plugins.server.servlet.ServletC
+ontainerDispatcher.service(ServletContainerDispatcher.java:221)\n\tat org.jboss.resteasy.plugins.server.servlet.HttpSe
+rvletDispatcher.service(HttpServletDispatcher.java:56)\n\tat org.jboss.resteasy.plugins.server.servlet.HttpServletDisp
+atcher.service(HttpServletDispatcher.java:51)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\tat
+ org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:808)\n\tat org.eclipse.jetty.servlet.ServletHandler
+$CachedChain.doFilter(ServletHandler.java:1669)\n\tat com.kooaba.queryservice.auth.KWSAuthFilter.doFilter(KWSAuthFilte
+r.java:171)\n\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)\n\tat org.ec
+lipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)\n\tat org.eclipse.jetty.server.handler.ScopedHand
+ler.handle(ScopedHandler.java:143)\n\tat org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)\n
+\tat org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)\n\tat org.eclipse.jetty.server.
+handler.ContextHandler.doHandle(ContextHandler.java:1127)\n\tat org.eclipse.jetty.servlet.ServletHandler.doScope(Servl
+etHandler.java:515)\n\tat org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)\n\tat org.e
+clipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)\n\tat org.eclipse.jetty.server.handler.Sc
+opedHandler.handle(ScopedHandler.java:141)\n\tat org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(Cont
+extHandlerCollection.java:215)\n\tat org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:
+110)\n\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)\n\tat org.eclipse.jetty.serv
+er.Server.handle(Server.java:497)\n\tat org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)\n\tat org.ec
+lipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)\n\tat org.eclipse.jetty.io.AbstractConnection$2.
+run(AbstractConnection.java:540)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635
+)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)\n\tat java.lang.Thread.run(Thr
+ead.java:748)\n
\n
Powered by Jetty://
\n\n\n\n'
diff --git a/src/_mock_vws_server/vws/resources/oops_error_occurred_response.html b/src/_mock_vws_server/vws/resources/oops_error_occurred_response.html
new file mode 100644
index 000000000..e72b8fc60
--- /dev/null
+++ b/src/_mock_vws_server/vws/resources/oops_error_occurred_response.html
@@ -0,0 +1,41 @@
+
+
+
+ Error
+
+
+
+ Oops, an error occurred
+
+
+ This exception has been logged with id 7db293le3.
+
+
+
+
From bcefe7bd05cab03a265e2906525590dfe2b59e14 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 22 Jan 2020 12:06:30 +0000
Subject: [PATCH 0022/3455] Progress towards using flask app as a mock
---
src/_mock_vws_server/vws/__init__.py | 4 ++++
src/_mock_vws_server/vws/_services_validators/__init__.py | 5 +++--
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 4f6c270e7..3b6dbd9b4 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -55,6 +55,10 @@
ADD_TARGET_SCHEMA = {
'required': ['name', 'image', 'width'],
+ # TODO are the properties useful for fixing tests?
+ 'properties': {
+ 'name': { 'type': 'string' },
+ }
}
diff --git a/src/_mock_vws_server/vws/_services_validators/__init__.py b/src/_mock_vws_server/vws/_services_validators/__init__.py
index 070e0175b..851976245 100644
--- a/src/_mock_vws_server/vws/_services_validators/__init__.py
+++ b/src/_mock_vws_server/vws/_services_validators/__init__.py
@@ -262,7 +262,7 @@ def validate_name_length(
name = request.get_json(force=True)['name']
- if name and len(name) < 65:
+ if name and len(str(name)) < 65:
return wrapped(*args, **kwargs)
body = {
@@ -302,7 +302,8 @@ def validate_name_characters_in_range(
name = request.get_json(force=True)['name']
- if all(ord(character) <= 65535 for character in name):
+ # import pdb; pdb.set_trace()
+ if all(ord(character) <= 65535 for character in str(name)):
return wrapped(*args, **kwargs)
if (request.method, request.path) == ('POST', '/targets'):
From 8516d7d243c039583023d88ce9f2e9dcb919c7c3 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 22 Jan 2020 12:22:24 +0000
Subject: [PATCH 0023/3455] A bunch more passing tests
---
src/_mock_vws_server/vws/__init__.py | 55 ++++++++++---------
.../vws/_services_validators/__init__.py | 6 +-
2 files changed, 32 insertions(+), 29 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 3b6dbd9b4..fc3a8575e 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -63,33 +63,33 @@
@VWS_FLASK_APP.before_request
-# @validate_project_state
-# @validate_authorization
-@validate_metadata_size
-@validate_metadata_encoding
-@validate_metadata_type
-@validate_active_flag
-@validate_image_size
-@validate_image_color_space
-@validate_image_format
-@validate_image_is_image
-@validate_image_encoding
-@validate_image_data_type
-@validate_name_characters_in_range
-@validate_name_length
-@validate_name_type
-@validate_width
-@validate_content_type_header_given
-@validate_date_in_range
-@validate_date_format
-@validate_date_header_given
-@validate_not_invalid_json
-# @validate_access_key_exists
-@validate_auth_header_has_signature
-@validate_auth_header_exists
-@validate_content_length_header_not_too_small
-@validate_content_length_header_not_too_large
@validate_content_length_header_is_int
+@validate_content_length_header_not_too_large
+@validate_content_length_header_not_too_small
+@validate_auth_header_exists
+@validate_auth_header_has_signature
+# @validate_access_key_exists
+@validate_not_invalid_json
+@validate_date_header_given
+@validate_date_format
+@validate_date_in_range
+@validate_content_type_header_given
+@validate_width
+@validate_name_type
+@validate_name_length
+@validate_name_characters_in_range
+@validate_image_data_type
+@validate_image_encoding
+@validate_image_is_image
+@validate_image_format
+@validate_image_color_space
+@validate_image_size
+@validate_active_flag
+@validate_metadata_type
+@validate_metadata_encoding
+@validate_metadata_size
+# @validate_authorization
+# @validate_project_state
def validate_request():
pass
# key_validator = validate_keys(
@@ -118,7 +118,8 @@ def validation_error(e):
@VWS_FLASK_APP.after_request
def set_headers(response):
response.headers['Connection'] = 'keep-alive'
- response.headers['Content-Type'] = 'application/json'
+ if response.status_code != codes.INTERNAL_SERVER_ERROR:
+ response.headers['Content-Type'] = 'application/json'
response.headers['Server'] = 'nginx'
content_length = len(response.data)
response.headers['Content-Length'] = str(content_length)
diff --git a/src/_mock_vws_server/vws/_services_validators/__init__.py b/src/_mock_vws_server/vws/_services_validators/__init__.py
index 851976245..46862236f 100644
--- a/src/_mock_vws_server/vws/_services_validators/__init__.py
+++ b/src/_mock_vws_server/vws/_services_validators/__init__.py
@@ -10,7 +10,7 @@
from typing import Any, Callable, Dict, Set, Tuple
import wrapt
-from flask import request
+from flask import request, make_response
from requests import codes
from requests_mock import POST, PUT
from requests_mock.request import _RequestObjectProxy
@@ -313,7 +313,9 @@ def validate_name_characters_in_range(
# TODO construct a Response
# context.headers['Content-Type'] = 'text/html; charset=UTF-8'
text = oops_resp_file.read_text()
- return text, codes.INTERNAL_SERVER_ERROR
+ oops_response = make_response(text)
+ oops_response.headers['Content-Type'] = 'text/html; charset=UTF-8'
+ return oops_response, codes.INTERNAL_SERVER_ERROR
body = {
'transaction_id': uuid.uuid4().hex,
From 3e0fbb21aa60b7e4cd7b204a852d6166e467bac3 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 22 Jan 2020 17:00:40 +0000
Subject: [PATCH 0024/3455] Do not allow extra properties
---
src/_mock_vws_server/vws/__init__.py | 7 ++++++-
src/_mock_vws_server/vws/_services_validators/__init__.py | 2 --
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index fc3a8575e..3aa4b2e1c 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -58,7 +58,12 @@
# TODO are the properties useful for fixing tests?
'properties': {
'name': { 'type': 'string' },
- }
+ 'image': {},
+ 'width': {},
+ 'active_flag': {},
+ 'application_metadata': {},
+ },
+ 'additionalProperties': False,
}
diff --git a/src/_mock_vws_server/vws/_services_validators/__init__.py b/src/_mock_vws_server/vws/_services_validators/__init__.py
index 46862236f..d1fa1ea66 100644
--- a/src/_mock_vws_server/vws/_services_validators/__init__.py
+++ b/src/_mock_vws_server/vws/_services_validators/__init__.py
@@ -310,8 +310,6 @@ def validate_name_characters_in_range(
resources_dir = Path(__file__).parent.parent / 'resources'
filename = 'oops_error_occurred_response.html'
oops_resp_file = resources_dir / filename
- # TODO construct a Response
- # context.headers['Content-Type'] = 'text/html; charset=UTF-8'
text = oops_resp_file.read_text()
oops_response = make_response(text)
oops_response.headers['Content-Type'] = 'text/html; charset=UTF-8'
From 35b86bba7a5baae32d8307e81380556c110d5e4a Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 22 Jan 2020 17:01:21 +0000
Subject: [PATCH 0025/3455] Remove some junk code
---
src/_mock_vws_server/vws/__init__.py | 9 ---------
1 file changed, 9 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 3aa4b2e1c..b6ba19a5c 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -96,17 +96,8 @@
# @validate_authorization
# @validate_project_state
def validate_request():
- pass
- # key_validator = validate_keys(
- # optional_keys=optional_keys or set([]),
- # mandatory_keys=mandatory_keys or set([]),
- # )
- #
# decorators = [
# # parse_target_id,
- # # key_validator,
- # # set_date_header,
- # # set_content_length_header,
# # update_request_count,
# ]
From abee69236170ebaf641454e501edb6286b199cb6 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 22 Jan 2020 17:02:43 +0000
Subject: [PATCH 0026/3455] Fix syntax issue
---
src/_mock_vws_server/vws/__init__.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index b6ba19a5c..178288348 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -96,6 +96,7 @@
# @validate_authorization
# @validate_project_state
def validate_request():
+ pass
# decorators = [
# # parse_target_id,
# # update_request_count,
From 4507225e53c3e023fa992833d74f2bafb5daa343 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 22 Jan 2020 17:44:52 +0000
Subject: [PATCH 0027/3455] Add a few TODOs
---
docs/source/docker.rst | 3 +++
src/_mock_vws_server/vws/__init__.py | 3 ++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/docs/source/docker.rst b/docs/source/docker.rst
index ea4ecc33d..897ff15f9 100644
--- a/docs/source/docker.rst
+++ b/docs/source/docker.rst
@@ -4,9 +4,12 @@ Running a server with Docker
Running the mock
----------------
+# TODO this won't work - we need some kind of storage backend thing
+
.. code:: sh
docker run adamtheturtle/mock-vws -e VWS_MOCK_DATABASES=$(cat vws-mock-config.json)
+ docker run adamtheturtle/mock-vwq -e VWS_MOCK_DATABASES=$(cat vws-mock-config.json)
Configuration
-------------
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 178288348..088a23a2f 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -4,7 +4,7 @@
import json
import uuid
-from flask import Flask, request
+from flask import Flask, request, session
from requests import codes
from flask_json_schema import JsonSchema, JsonValidationError
@@ -169,6 +169,7 @@ def add_target():
image=image_file,
active_flag=active_flag,
processing_time_seconds=0.2,
+ # TODO add this back:
# processing_time_seconds=self._processing_time_seconds,
application_metadata=request_json.get('application_metadata'),
)
From 0bc765436835f57c3c395eb2e2b7f55eac7c494b Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 24 Jan 2020 09:49:32 +0000
Subject: [PATCH 0028/3455] Add a TODO
---
src/_mock_vws_server/vws/__init__.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 088a23a2f..e67b9b741 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -80,6 +80,7 @@
@validate_date_in_range
@validate_content_type_header_given
@validate_width
+# TODO is validating the name type needed given JSON schema?
@validate_name_type
@validate_name_length
@validate_name_characters_in_range
From 1b90e18a94cbdbb1efdcbb5a8cfcf093ffeb7fec Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 24 Jan 2020 09:50:21 +0000
Subject: [PATCH 0029/3455] Add a TODO
---
src/_mock_vws_server/vws/__init__.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index e67b9b741..6ba7c569f 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -57,6 +57,7 @@
'required': ['name', 'image', 'width'],
# TODO are the properties useful for fixing tests?
'properties': {
+ # TODO maybe use more limits on types here and use a max length for string?
'name': { 'type': 'string' },
'image': {},
'width': {},
From 1e0468a5bc08ccf6b9ff5b0338c0ff589fcd92d1 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 24 Jan 2020 09:51:23 +0000
Subject: [PATCH 0030/3455] Add a TODO
---
src/_mock_vws_server/vws/__init__.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 6ba7c569f..07bf80e1b 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -58,6 +58,7 @@
# TODO are the properties useful for fixing tests?
'properties': {
# TODO maybe use more limits on types here and use a max length for string?
+ # TODO though actually - if authentication is wrong, surely that's the first issue and then maybe we need to re-think this and not have schema checks - or maybe not until later?
'name': { 'type': 'string' },
'image': {},
'width': {},
From bd67631a986414d2a4cc3804fbfde71102986d56 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 1 Feb 2020 23:28:19 +0000
Subject: [PATCH 0031/3455] Progress towards storage container
---
docs/source/docker.rst | 1 +
tests/mock_vws/fixtures/vuforia_backends.py | 9 ++++++++-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/docs/source/docker.rst b/docs/source/docker.rst
index 897ff15f9..661b8ec2a 100644
--- a/docs/source/docker.rst
+++ b/docs/source/docker.rst
@@ -8,6 +8,7 @@ Running the mock
.. code:: sh
+ docker run adamtheturtle/mock-vuforia-storage-backend -e VWS_MOCK_DATABASES=$(cat vws-mock-config.json)
docker run adamtheturtle/mock-vws -e VWS_MOCK_DATABASES=$(cat vws-mock-config.json)
docker run adamtheturtle/mock-vwq -e VWS_MOCK_DATABASES=$(cat vws-mock-config.json)
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index 15b21246d..d34366b5d 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -14,7 +14,8 @@
from requests_mock_flask import add_flask_app_to_mock
from _mock_vws_server.vwq import CLOUDRECO_FLASK_APP
-from _mock_vws_server.vws import VWS_FLASK_APP
+from _mock_vws_server.vws import VWS_FLASK_APP, STORAGE_BASE_URL
+from _mock_vws_server.storage import STORAGE_FLASK_APP
from mock_vws import MockVWS
from mock_vws._constants import ResultCodes
from mock_vws.database import VuforiaDatabase
@@ -128,6 +129,12 @@ def _enable_use_docker_in_memory(
base_url='https://cloudreco.vuforia.com',
)
+ add_flask_app_to_mock(
+ mock_obj=mock,
+ flask_app=STORAGE_FLASK_APP,
+ base_url=STORAGE_BASE_URL,
+ )
+
yield
From cf2ef8cc49960dc2ad014194534dfb32ed1b6f76 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 17 Feb 2020 22:03:42 +0000
Subject: [PATCH 0032/3455] Add stub for storage base URL
---
src/_mock_vws_server/storage/__init__.py | 3 +++
src/_mock_vws_server/vws/__init__.py | 2 ++
2 files changed, 5 insertions(+)
create mode 100644 src/_mock_vws_server/storage/__init__.py
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
new file mode 100644
index 000000000..b16000685
--- /dev/null
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -0,0 +1,3 @@
+from flask import Flask
+
+STORAGE_FLASK_APP = Flask(__name__)
\ No newline at end of file
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 07bf80e1b..1297b6bef 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -52,6 +52,8 @@
VWS_FLASK_APP = Flask(__name__)
JSON_SCHEMA = JsonSchema(VWS_FLASK_APP)
+# TODO this
+STORAGE_BASE_URL = 'TODO'
ADD_TARGET_SCHEMA = {
'required': ['name', 'image', 'width'],
From 6571d35d04cc65b97bf4e998d2533d1c076c2394 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 19 Feb 2020 23:04:42 +0000
Subject: [PATCH 0033/3455] Add stub for getting databases from storage
---
src/_mock_vws_server/vws/__init__.py | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 1297b6bef..a7581142f 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -11,6 +11,7 @@
from mock_vws._constants import ResultCodes
from mock_vws._mock_common import json_dump
from mock_vws.target import Target
+from mock_vws._database_matchers import get_database_matching_server_keys
from ._services_validators import (
validate_active_flag,
@@ -130,6 +131,9 @@ def set_headers(response):
return response
+def get_all_databases() -> Set[VuforiaDatabase]:
+ # TODO use the storage URL to get details then cast to VuforiaDatabase
+ pass
@VWS_FLASK_APP.route('/targets', methods=['POST'])
@JSON_SCHEMA.validate(ADD_TARGET_SCHEMA)
@@ -144,10 +148,11 @@ def add_target():
# type is given as ``application/json``.
request_json = json.loads(request.data)
request_json['name']
- # database = get_database_matching_server_keys(
- # request=request,
- # databases=self.databases,
- # )
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request=request,
+ databases=databases,
+ )
#
# assert isinstance(database, VuforiaDatabase)
#
From b4f1eae466cacfa6a39ee12223c9d489a043f287 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 21 Feb 2020 12:51:06 +0000
Subject: [PATCH 0034/3455] Progress towards getting databases from storage
---
src/_mock_vws_server/vws/__init__.py | 43 ++++++++++++++++++++++++++--
1 file changed, 40 insertions(+), 3 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index a7581142f..19888f782 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -3,14 +3,17 @@
import io
import json
import uuid
+from typing import Tuple, Dict, Set
from flask import Flask, request, session
from requests import codes
from flask_json_schema import JsonSchema, JsonValidationError
+import requests
from mock_vws._constants import ResultCodes
from mock_vws._mock_common import json_dump
from mock_vws.target import Target
+from mock_vws.database import VuforiaDatabase
from mock_vws._database_matchers import get_database_matching_server_keys
from ._services_validators import (
@@ -101,7 +104,7 @@
@validate_metadata_size
# @validate_authorization
# @validate_project_state
-def validate_request():
+def validate_request() -> None:
pass
# decorators = [
# # parse_target_id,
@@ -109,7 +112,7 @@ def validate_request():
# ]
@VWS_FLASK_APP.errorhandler(JsonValidationError)
-def validation_error(e):
+def validation_error(e) -> Tuple[Dict, int]:
body = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.FAIL.value,
@@ -133,7 +136,41 @@ def set_headers(response):
def get_all_databases() -> Set[VuforiaDatabase]:
# TODO use the storage URL to get details then cast to VuforiaDatabase
- pass
+ response = requests.get(url=STORAGE_BASE_URL + '/databases')
+ response_json = response.json()
+ databases = set()
+ for database_dict in response_json:
+ database_name = database_dict['database_name']
+ server_access_key = database_dict['server_access_key']
+ server_secret_key = database_dict['server_secret_key']
+ client_access_key = database_dict['client_access_key']
+ client_secret_key = database_dict['client_secret_key']
+ # TODO state
+
+ new_database = VuforiaDatabase(
+ database_name=database_name,
+ server_access_key=server_access_key,
+ server_secret_key=server_secret_key,
+ client_access_key=client_access_key,
+ client_secret_key=client_secret_key,
+ state=state,
+ )
+
+ for target_dict in database_dict['targets']:
+ # TODO fill this in
+ target = Target(
+ name=name,
+ active_flag=active_flag,
+ width=width,
+ image=image,
+ processing_time_seconds=processing_time_seconds,
+ application_metadata=application_metadata,
+ )
+ new_database.targets.append(target)
+
+ databases.add(new_database)
+
+ return databases
@VWS_FLASK_APP.route('/targets', methods=['POST'])
@JSON_SCHEMA.validate(ADD_TARGET_SCHEMA)
From c398a19b2a6733f86e5b940f586e79ab7e5d9295 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 21 Feb 2020 12:56:57 +0000
Subject: [PATCH 0035/3455] Progress towards passing mypy
---
src/_mock_vws_server/vws/__init__.py | 2 +-
.../vws/_services_validators/__init__.py | 44 +++++++++----------
.../_services_validators/auth_validators.py | 16 +++----
.../content_length_validators.py | 12 ++---
.../content_type_validators.py | 4 +-
.../_services_validators/date_validators.py | 12 ++---
.../_services_validators/image_validators.py | 24 +++++-----
7 files changed, 57 insertions(+), 57 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 19888f782..4b19592e6 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -112,7 +112,7 @@ def validate_request() -> None:
# ]
@VWS_FLASK_APP.errorhandler(JsonValidationError)
-def validation_error(e) -> Tuple[Dict, int]:
+def validation_error(e) -> Tuple[str, int]:
body = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.FAIL.value,
diff --git a/src/_mock_vws_server/vws/_services_validators/__init__.py b/src/_mock_vws_server/vws/_services_validators/__init__.py
index d1fa1ea66..617377411 100644
--- a/src/_mock_vws_server/vws/_services_validators/__init__.py
+++ b/src/_mock_vws_server/vws/_services_validators/__init__.py
@@ -26,11 +26,11 @@
@wrapt.decorator
def validate_active_flag(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the active flag data given to the endpoint.
@@ -66,11 +66,11 @@ def validate_active_flag(
@wrapt.decorator
def validate_project_state(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any,
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the state of the project.
@@ -106,11 +106,11 @@ def validate_project_state(
@wrapt.decorator
def validate_not_invalid_json(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate that there is either no JSON given or the JSON given is valid.
@@ -150,11 +150,11 @@ def validate_not_invalid_json(
@wrapt.decorator
def validate_width(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the width argument given to a VWS endpoint.
@@ -193,11 +193,11 @@ def validate_width(
@wrapt.decorator
def validate_name_type(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the type of the name argument given to a VWS endpoint.
@@ -234,11 +234,11 @@ def validate_name_type(
@wrapt.decorator
def validate_name_length(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the length of the name argument given to a VWS endpoint.
@@ -274,11 +274,11 @@ def validate_name_length(
@wrapt.decorator
def validate_name_characters_in_range(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the characters in the name argument given to a VWS endpoint.
@@ -346,11 +346,11 @@ def validate_keys(
# kwargs: The keyword arguments given to the endpoint function.
@wrapt.decorator
def wrapper(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
- ) -> str:
+ ) -> Tuple[str, int]:
"""
Validate the request keys given to a VWS endpoint.
@@ -384,11 +384,11 @@ def wrapper(
@wrapt.decorator
def validate_metadata_encoding(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate that the given application metadata can be base64 decoded.
@@ -429,11 +429,11 @@ def validate_metadata_encoding(
@wrapt.decorator
def validate_metadata_type(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate that the given application metadata is a string or NULL.
@@ -469,11 +469,11 @@ def validate_metadata_type(
@wrapt.decorator
def validate_metadata_size(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate that the given application metadata is a string or 1024 * 1024
bytes or fewer.
diff --git a/src/_mock_vws_server/vws/_services_validators/auth_validators.py b/src/_mock_vws_server/vws/_services_validators/auth_validators.py
index 3b516f542..b619c3fbd 100644
--- a/src/_mock_vws_server/vws/_services_validators/auth_validators.py
+++ b/src/_mock_vws_server/vws/_services_validators/auth_validators.py
@@ -18,11 +18,11 @@
@wrapt.decorator
def validate_auth_header_exists(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate that there is an authorization header given to a VWS endpoint.
@@ -49,11 +49,11 @@ def validate_auth_header_exists(
@wrapt.decorator
def validate_access_key_exists(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any,
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the authorization header includes an access key for a database.
@@ -84,11 +84,11 @@ def validate_access_key_exists(
@wrapt.decorator
def validate_auth_header_has_signature(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the authorization header includes a signature.
@@ -117,11 +117,11 @@ def validate_auth_header_has_signature(
@wrapt.decorator
def validate_authorization(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any,
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the authorization header given to a VWS endpoint.
diff --git a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
index bc2712df2..47c1e9505 100644
--- a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
+++ b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
@@ -17,11 +17,11 @@
@wrapt.decorator
def validate_content_length_header_is_int(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the ``Content-Length`` header is an integer.
@@ -52,11 +52,11 @@ def validate_content_length_header_is_int(
@wrapt.decorator
def validate_content_length_header_not_too_large(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the ``Content-Length`` header is not too large.
@@ -85,11 +85,11 @@ def validate_content_length_header_not_too_large(
@wrapt.decorator
def validate_content_length_header_not_too_small(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the ``Content-Length`` header is not too small.
diff --git a/src/_mock_vws_server/vws/_services_validators/content_type_validators.py b/src/_mock_vws_server/vws/_services_validators/content_type_validators.py
index 22403359d..0a59d9ebf 100644
--- a/src/_mock_vws_server/vws/_services_validators/content_type_validators.py
+++ b/src/_mock_vws_server/vws/_services_validators/content_type_validators.py
@@ -18,11 +18,11 @@
@wrapt.decorator
def validate_content_type_header_given(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate that there is a non-empty content type header given if required.
diff --git a/src/_mock_vws_server/vws/_services_validators/date_validators.py b/src/_mock_vws_server/vws/_services_validators/date_validators.py
index 611a5bc95..dfb4fcb16 100644
--- a/src/_mock_vws_server/vws/_services_validators/date_validators.py
+++ b/src/_mock_vws_server/vws/_services_validators/date_validators.py
@@ -19,11 +19,11 @@
@wrapt.decorator
def validate_date_header_given(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the date header is given to a VWS endpoint.
@@ -49,11 +49,11 @@ def validate_date_header_given(
@wrapt.decorator
def validate_date_format(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the format of the date header given to a VWS endpoint.
@@ -85,11 +85,11 @@ def validate_date_format(
@wrapt.decorator
def validate_date_in_range(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the date header given to a VWS endpoint is in range.
diff --git a/src/_mock_vws_server/vws/_services_validators/image_validators.py b/src/_mock_vws_server/vws/_services_validators/image_validators.py
index d2cd87b24..e1f613e62 100644
--- a/src/_mock_vws_server/vws/_services_validators/image_validators.py
+++ b/src/_mock_vws_server/vws/_services_validators/image_validators.py
@@ -21,11 +21,11 @@
@wrapt.decorator
def validate_image_format(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the format of the image given to a VWS endpoint.
@@ -65,11 +65,11 @@ def validate_image_format(
@wrapt.decorator
def validate_image_color_space(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the color space of the image given to a VWS endpoint.
@@ -109,11 +109,11 @@ def validate_image_color_space(
@wrapt.decorator
def validate_image_size(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the file size of the image given to a VWS endpoint.
@@ -151,11 +151,11 @@ def validate_image_size(
@wrapt.decorator
def validate_image_is_image(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate that the given image data is actually an image file.
@@ -196,11 +196,11 @@ def validate_image_is_image(
@wrapt.decorator
def validate_image_encoding(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate that the given image data can be base64 decoded.
@@ -238,11 +238,11 @@ def validate_image_encoding(
@wrapt.decorator
def validate_image_data_type(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate that the given image data is a string.
From 0ac781bc1301bb6caab76d888143bdd98a83c93a Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 21 Feb 2020 12:59:54 +0000
Subject: [PATCH 0036/3455] Progress towards passing mypy
---
src/_mock_vws_server/vws/_services_validators/__init__.py | 2 +-
.../vws/_services_validators/content_length_validators.py | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/_mock_vws_server/vws/_services_validators/__init__.py b/src/_mock_vws_server/vws/_services_validators/__init__.py
index 617377411..15cd01956 100644
--- a/src/_mock_vws_server/vws/_services_validators/__init__.py
+++ b/src/_mock_vws_server/vws/_services_validators/__init__.py
@@ -134,7 +134,7 @@ def validate_not_invalid_json(
# TODO this is commented out but not but should maybe be moved to an
# after_request decorator
# context.headers.pop('Content-Type')
- return ''
+ return '', codes.OK
try:
request.get_json(force=True)
diff --git a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
index 47c1e9505..2a5f54212 100644
--- a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
+++ b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
@@ -37,7 +37,7 @@ def validate_content_length_header_is_int(
integer.
"""
- body_length = len(request.data if request.data else '')
+ body_length = len(str(request.data) if request.data else '')
given_content_length = request.headers.get('Content-Length', body_length)
try:
@@ -72,7 +72,7 @@ def validate_content_length_header_not_too_large(
that the content length is greater than the body length.
"""
- body_length = len(request.data if request.data else '')
+ body_length = len(str(request.data) if request.data else '')
given_content_length = request.headers.get('Content-Length', body_length)
given_content_length_value = int(given_content_length)
if given_content_length_value > body_length:
@@ -105,7 +105,7 @@ def validate_content_length_header_not_too_small(
that the content length is smaller than the body length.
"""
- body_length = len(request.data if request.data else '')
+ body_length = len(str(request.data) if request.data else '')
given_content_length = request.headers.get('Content-Length', body_length)
given_content_length_value = int(given_content_length)
From 0dfe212faf8e6830a71394290e854170e926d3ad Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 21 Feb 2020 13:02:04 +0000
Subject: [PATCH 0037/3455] Progress towards passing mypy
---
src/_mock_vws_server/vws/__init__.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 4b19592e6..c46bf399e 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -5,7 +5,7 @@
import uuid
from typing import Tuple, Dict, Set
-from flask import Flask, request, session
+from flask import Flask, request, session, Response
from requests import codes
from flask_json_schema import JsonSchema, JsonValidationError
import requests
@@ -112,7 +112,7 @@ def validate_request() -> None:
# ]
@VWS_FLASK_APP.errorhandler(JsonValidationError)
-def validation_error(e) -> Tuple[str, int]:
+def validation_error(e: JsonValidationError) -> Tuple[str, int]:
body = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.FAIL.value,
@@ -122,7 +122,7 @@ def validation_error(e) -> Tuple[str, int]:
@VWS_FLASK_APP.after_request
-def set_headers(response):
+def set_headers(response: Response):
response.headers['Connection'] = 'keep-alive'
if response.status_code != codes.INTERNAL_SERVER_ERROR:
response.headers['Content-Type'] = 'application/json'
From 296710b21caf6da27a4bb71a28146852caae9166 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 21 Feb 2020 13:02:54 +0000
Subject: [PATCH 0038/3455] Progress towards passing mypy
---
src/_mock_vws_server/vws/__init__.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index c46bf399e..5ef6360bd 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -122,7 +122,7 @@ def validation_error(e: JsonValidationError) -> Tuple[str, int]:
@VWS_FLASK_APP.after_request
-def set_headers(response: Response):
+def set_headers(response: Response) -> Response:
response.headers['Connection'] = 'keep-alive'
if response.status_code != codes.INTERNAL_SERVER_ERROR:
response.headers['Content-Type'] = 'application/json'
@@ -174,7 +174,7 @@ def get_all_databases() -> Set[VuforiaDatabase]:
@VWS_FLASK_APP.route('/targets', methods=['POST'])
@JSON_SCHEMA.validate(ADD_TARGET_SCHEMA)
-def add_target():
+def add_target() -> Tuple[str, int]:
"""
Add a target.
From 72175eed39fa51620a3e48be8dbf849489e4f283 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 21 Feb 2020 13:07:50 +0000
Subject: [PATCH 0039/3455] Fix a bunch of tests
---
src/_mock_vws_server/vws/__init__.py | 10 +++++-----
.../vws/_services_validators/__init__.py | 1 -
.../_services_validators/content_length_validators.py | 6 +++---
3 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 5ef6360bd..c7336e30f 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -185,11 +185,11 @@ def add_target() -> Tuple[str, int]:
# type is given as ``application/json``.
request_json = json.loads(request.data)
request_json['name']
- databases = get_all_databases()
- database = get_database_matching_server_keys(
- request=request,
- databases=databases,
- )
+ # databases = get_all_databases()
+ # database = get_database_matching_server_keys(
+ # request=request,
+ # databases=databases,
+ # )
#
# assert isinstance(database, VuforiaDatabase)
#
diff --git a/src/_mock_vws_server/vws/_services_validators/__init__.py b/src/_mock_vws_server/vws/_services_validators/__init__.py
index 15cd01956..d7ed40e52 100644
--- a/src/_mock_vws_server/vws/_services_validators/__init__.py
+++ b/src/_mock_vws_server/vws/_services_validators/__init__.py
@@ -302,7 +302,6 @@ def validate_name_characters_in_range(
name = request.get_json(force=True)['name']
- # import pdb; pdb.set_trace()
if all(ord(character) <= 65535 for character in str(name)):
return wrapped(*args, **kwargs)
diff --git a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
index 2a5f54212..8f53d2615 100644
--- a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
+++ b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
@@ -37,7 +37,7 @@ def validate_content_length_header_is_int(
integer.
"""
- body_length = len(str(request.data) if request.data else '')
+ body_length = len(bytearray(request.data) if request.data else '')
given_content_length = request.headers.get('Content-Length', body_length)
try:
@@ -72,7 +72,7 @@ def validate_content_length_header_not_too_large(
that the content length is greater than the body length.
"""
- body_length = len(str(request.data) if request.data else '')
+ body_length = len(bytearray(request.data) if request.data else '')
given_content_length = request.headers.get('Content-Length', body_length)
given_content_length_value = int(given_content_length)
if given_content_length_value > body_length:
@@ -105,7 +105,7 @@ def validate_content_length_header_not_too_small(
that the content length is smaller than the body length.
"""
- body_length = len(str(request.data) if request.data else '')
+ body_length = len(bytearray(request.data) if request.data else '')
given_content_length = request.headers.get('Content-Length', body_length)
given_content_length_value = int(given_content_length)
From c89a2084d9c45f5a21a8585e45eb3b394aed60b5 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 21 Feb 2020 13:12:12 +0000
Subject: [PATCH 0040/3455] Progress towards working mock backend
---
src/_mock_vws_server/storage/__init__.py | 10 ++++++++--
src/_mock_vws_server/vws/__init__.py | 12 ++++++------
2 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index b16000685..5e402e8ad 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -1,3 +1,9 @@
-from flask import Flask
+from flask import Flask, jsonify
-STORAGE_FLASK_APP = Flask(__name__)
\ No newline at end of file
+STORAGE_FLASK_APP = Flask(__name__)
+
+
+@STORAGE_FLASK_APP.route('/databases', methods=['GET'])
+def get_databases() -> str:
+ databases = []
+ return jsonify(databases)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index c7336e30f..683b4f6c0 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -57,7 +57,7 @@
VWS_FLASK_APP = Flask(__name__)
JSON_SCHEMA = JsonSchema(VWS_FLASK_APP)
# TODO this
-STORAGE_BASE_URL = 'TODO'
+STORAGE_BASE_URL = 'http://todo.com'
ADD_TARGET_SCHEMA = {
'required': ['name', 'image', 'width'],
@@ -185,11 +185,11 @@ def add_target() -> Tuple[str, int]:
# type is given as ``application/json``.
request_json = json.loads(request.data)
request_json['name']
- # databases = get_all_databases()
- # database = get_database_matching_server_keys(
- # request=request,
- # databases=databases,
- # )
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request=request,
+ databases=databases,
+ )
#
# assert isinstance(database, VuforiaDatabase)
#
From 6522ba5fa1db49555a415536ae06967331482992 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 21 Feb 2020 13:13:27 +0000
Subject: [PATCH 0041/3455] Add TODO
---
tests/mock_vws/fixtures/vuforia_backends.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index d34366b5d..96f8e7e81 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -135,6 +135,8 @@ def _enable_use_docker_in_memory(
base_url=STORAGE_BASE_URL,
)
+ # TODO add database to storage
+
yield
From 84f030099e9a8ce27d7031285594eb2843fab049 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 21 Feb 2020 13:14:13 +0000
Subject: [PATCH 0042/3455] Fix some lint issues
---
src/_mock_vws_server/vws/__init__.py | 20 ++++++++++---------
.../vws/_services_validators/__init__.py | 11 ++++++----
.../content_length_validators.py | 2 +-
tests/mock_vws/fixtures/vuforia_backends.py | 4 ++--
4 files changed, 21 insertions(+), 16 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 683b4f6c0..04d54a866 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -3,18 +3,18 @@
import io
import json
import uuid
-from typing import Tuple, Dict, Set
+from typing import Set, Tuple
-from flask import Flask, request, session, Response
-from requests import codes
-from flask_json_schema import JsonSchema, JsonValidationError
import requests
+from flask import Flask, Response, request
+from flask_json_schema import JsonSchema, JsonValidationError
+from requests import codes
from mock_vws._constants import ResultCodes
+from mock_vws._database_matchers import get_database_matching_server_keys
from mock_vws._mock_common import json_dump
-from mock_vws.target import Target
from mock_vws.database import VuforiaDatabase
-from mock_vws._database_matchers import get_database_matching_server_keys
+from mock_vws.target import Target
from ._services_validators import (
validate_active_flag,
@@ -28,7 +28,6 @@
validate_width,
)
from ._services_validators.auth_validators import (
- validate_access_key_exists,
validate_auth_header_exists,
validate_auth_header_has_signature,
)
@@ -65,7 +64,9 @@
'properties': {
# TODO maybe use more limits on types here and use a max length for string?
# TODO though actually - if authentication is wrong, surely that's the first issue and then maybe we need to re-think this and not have schema checks - or maybe not until later?
- 'name': { 'type': 'string' },
+ 'name': {
+ 'type': 'string'
+ },
'image': {},
'width': {},
'active_flag': {},
@@ -111,6 +112,7 @@ def validate_request() -> None:
# # update_request_count,
# ]
+
@VWS_FLASK_APP.errorhandler(JsonValidationError)
def validation_error(e: JsonValidationError) -> Tuple[str, int]:
body = {
@@ -120,7 +122,6 @@ def validation_error(e: JsonValidationError) -> Tuple[str, int]:
return json_dump(body), codes.BAD_REQUEST
-
@VWS_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
response.headers['Connection'] = 'keep-alive'
@@ -172,6 +173,7 @@ def get_all_databases() -> Set[VuforiaDatabase]:
return databases
+
@VWS_FLASK_APP.route('/targets', methods=['POST'])
@JSON_SCHEMA.validate(ADD_TARGET_SCHEMA)
def add_target() -> Tuple[str, int]:
diff --git a/src/_mock_vws_server/vws/_services_validators/__init__.py b/src/_mock_vws_server/vws/_services_validators/__init__.py
index d7ed40e52..801ee42ba 100644
--- a/src/_mock_vws_server/vws/_services_validators/__init__.py
+++ b/src/_mock_vws_server/vws/_services_validators/__init__.py
@@ -10,7 +10,7 @@
from typing import Any, Callable, Dict, Set, Tuple
import wrapt
-from flask import request, make_response
+from flask import make_response, request
from requests import codes
from requests_mock import POST, PUT
from requests_mock.request import _RequestObjectProxy
@@ -409,7 +409,8 @@ def validate_metadata_encoding(
if 'application_metadata' not in request.get_json(force=True):
return wrapped(*args, **kwargs)
- application_metadata = request.get_json(force=True).get('application_metadata')
+ application_metadata = request.get_json(force=True
+ ).get('application_metadata')
if application_metadata is None:
return wrapped(*args, **kwargs)
@@ -454,7 +455,8 @@ def validate_metadata_type(
if 'application_metadata' not in request.get_json(force=True):
return wrapped(*args, **kwargs)
- application_metadata = request.get_json(force=True).get('application_metadata')
+ application_metadata = request.get_json(force=True
+ ).get('application_metadata')
if application_metadata is None or isinstance(application_metadata, str):
return wrapped(*args, **kwargs)
@@ -491,7 +493,8 @@ def validate_metadata_size(
if not request.data:
return wrapped(*args, **kwargs)
- application_metadata = request.get_json(force=True).get('application_metadata')
+ application_metadata = request.get_json(force=True
+ ).get('application_metadata')
if application_metadata is None:
return wrapped(*args, **kwargs)
decoded = decode_base64(encoded_data=application_metadata)
diff --git a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
index 8f53d2615..94c7e5d2f 100644
--- a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
+++ b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
@@ -6,13 +6,13 @@
from typing import Any, Callable, Dict, Tuple
import wrapt
+from flask import request
from requests import codes
from requests_mock.request import _RequestObjectProxy
from requests_mock.response import _Context
from .._constants import ResultCodes
from .._mock_common import json_dump
-from flask import request
@wrapt.decorator
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index 96f8e7e81..276fe05b2 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -13,9 +13,9 @@
from requests import codes
from requests_mock_flask import add_flask_app_to_mock
-from _mock_vws_server.vwq import CLOUDRECO_FLASK_APP
-from _mock_vws_server.vws import VWS_FLASK_APP, STORAGE_BASE_URL
from _mock_vws_server.storage import STORAGE_FLASK_APP
+from _mock_vws_server.vwq import CLOUDRECO_FLASK_APP
+from _mock_vws_server.vws import STORAGE_BASE_URL, VWS_FLASK_APP
from mock_vws import MockVWS
from mock_vws._constants import ResultCodes
from mock_vws.database import VuforiaDatabase
From 1df416e7fe0145f1bf02945c72b6cfef3fcd1c35 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 21 Feb 2020 14:29:16 +0000
Subject: [PATCH 0043/3455] Progress towards working mock backend
---
src/_mock_vws_server/storage/__init__.py | 12 ++++++++++--
tests/mock_vws/fixtures/vuforia_backends.py | 14 +++++++++++++-
2 files changed, 23 insertions(+), 3 deletions(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index 5e402e8ad..52dedbbf5 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -1,9 +1,17 @@
from flask import Flask, jsonify
+from requests import codes
+from typing import Tuple
STORAGE_FLASK_APP = Flask(__name__)
@STORAGE_FLASK_APP.route('/databases', methods=['GET'])
-def get_databases() -> str:
+def get_databases() -> Tuple[str, int]:
databases = []
- return jsonify(databases)
+ return jsonify(databases), codes.OK
+
+
+@STORAGE_FLASK_APP.route('/databases', methods=['POST'])
+def create_database() -> Tuple[str, int]:
+ database = {}
+ return jsonify(database), codes.CREATED
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index 276fe05b2..a05aab5cb 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -8,6 +8,7 @@
from typing import Generator
import pytest
+import requests
import requests_mock
from _pytest.fixtures import SubRequest
from requests import codes
@@ -135,7 +136,18 @@ def _enable_use_docker_in_memory(
base_url=STORAGE_BASE_URL,
)
- # TODO add database to storage
+ working_database_dict = {}
+ inactive_database_dict = {}
+
+ requests.post(
+ url=STORAGE_BASE_URL + '/databases',
+ data=working_database_dict,
+ )
+
+ requests.post(
+ url=STORAGE_BASE_URL + '/databases',
+ data=inactive_database_dict,
+ )
yield
From dbeea68833c45c98c7fb6eacb46bb31a22479ade Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 21 Feb 2020 14:32:14 +0000
Subject: [PATCH 0044/3455] Progress towards working mock backend
---
src/_mock_vws_server/storage/__init__.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index 52dedbbf5..26451b979 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -4,10 +4,11 @@
STORAGE_FLASK_APP = Flask(__name__)
+VUFORIA_DATABASES = []
@STORAGE_FLASK_APP.route('/databases', methods=['GET'])
def get_databases() -> Tuple[str, int]:
- databases = []
+ databases = [database.to_dict() for database in VUFORIA_DATABASES]
return jsonify(databases), codes.OK
From 380aa0b90e67636346badcfc709685cb994d6e51 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 21 Feb 2020 15:20:35 +0000
Subject: [PATCH 0045/3455] Add dataclass note
---
src/mock_vws/database.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 5234aa6a5..36f5edfa4 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -9,6 +9,9 @@
from .target import Target
+# This would be simpler as a dataclass, but
+# https://github.com/agronholm/sphinx-autodoc-typehints/issues/123 blocks us
+# doing that.
class VuforiaDatabase:
"""
Credentials for VWS APIs.
From e213f2a793c7e45c02fe535e27d5194fcee67e85 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 21 Feb 2020 15:24:46 +0000
Subject: [PATCH 0046/3455] Start of dict casting from database
---
src/mock_vws/database.py | 12 +++++++++++-
tests/mock_vws/fixtures/vuforia_backends.py | 4 ++--
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 36f5edfa4..4d298b644 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -3,7 +3,7 @@
"""
import uuid
-from typing import List, Optional
+from typing import Dict, List, Optional
from .states import States
from .target import Target
@@ -76,3 +76,13 @@ def __init__(
self.database_name = database_name
self.targets: List[Target] = []
self.state = state
+
+ def to_dict(self) -> Dict[str, str]:
+ return {
+ 'database_name': self.database_name,
+ 'server_access_key': self.server_access_key,
+ 'server_secret_key': self.server_secret_key,
+ 'client_access_key': self.client_access_key,
+ 'client_secret_key': self.client_secret_key,
+ # TODO target, state
+ }
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index a05aab5cb..dd9e55cc2 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -136,8 +136,8 @@ def _enable_use_docker_in_memory(
base_url=STORAGE_BASE_URL,
)
- working_database_dict = {}
- inactive_database_dict = {}
+ working_database_dict = working_database.to_dict()
+ inactive_database_dict = inactive_database.to_dict()
requests.post(
url=STORAGE_BASE_URL + '/databases',
From 038b40720936a46c3d8455bf3c3066f6ffc5b7f6 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 24 Feb 2020 01:44:36 +0000
Subject: [PATCH 0047/3455] Progress towards inline code substitution
---
src/_mock_vws_server/storage/__init__.py | 25 +++++++++++++++----
src/_mock_vws_server/vws/__init__.py | 1 +
.../content_length_validators.py | 6 ++---
src/mock_vws/_database_matchers.py | 3 ++-
src/mock_vws/database.py | 4 ++-
tests/mock_vws/fixtures/vuforia_backends.py | 4 +--
6 files changed, 31 insertions(+), 12 deletions(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index 26451b979..bf6b64051 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -1,10 +1,11 @@
-from flask import Flask, jsonify
+from flask import Flask, jsonify, request
from requests import codes
-from typing import Tuple
+from typing import Tuple, List
+from mock_vws.database import VuforiaDatabase
STORAGE_FLASK_APP = Flask(__name__)
-VUFORIA_DATABASES = []
+VUFORIA_DATABASES: List[VuforiaDatabase] = []
@STORAGE_FLASK_APP.route('/databases', methods=['GET'])
def get_databases() -> Tuple[str, int]:
@@ -14,5 +15,19 @@ def get_databases() -> Tuple[str, int]:
@STORAGE_FLASK_APP.route('/databases', methods=['POST'])
def create_database() -> Tuple[str, int]:
- database = {}
- return jsonify(database), codes.CREATED
+ server_access_key = request.json['server_access_key']
+ server_secret_key = request.json['server_secret_key']
+ client_access_key = request.json['client_access_key']
+ client_secret_key = request.json['client_secret_key']
+ database_name = request.json['database_name']
+ # TODO this will have to be converted by enum
+ state = request.json['state']
+
+ database = VuforiaDatabase(
+ server_access_key=server_access_key,
+ client_access_key=client_access_key,
+ database_name=database_name,
+ state=state,
+ )
+ VUFORIA_DATABASES.append(database)
+ return jsonify(database.to_dict()), codes.CREATED
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 04d54a866..3a8324b45 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -146,6 +146,7 @@ def get_all_databases() -> Set[VuforiaDatabase]:
server_secret_key = database_dict['server_secret_key']
client_access_key = database_dict['client_access_key']
client_secret_key = database_dict['client_secret_key']
+ state = database_dict['state']
# TODO state
new_database = VuforiaDatabase(
diff --git a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
index 94c7e5d2f..8abab8e0e 100644
--- a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
+++ b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
@@ -37,7 +37,7 @@ def validate_content_length_header_is_int(
integer.
"""
- body_length = len(bytearray(request.data) if request.data else '')
+ body_length = len(request.data.decode() if request.data else '')
given_content_length = request.headers.get('Content-Length', body_length)
try:
@@ -72,7 +72,7 @@ def validate_content_length_header_not_too_large(
that the content length is greater than the body length.
"""
- body_length = len(bytearray(request.data) if request.data else '')
+ body_length = len(request.data.decode() if request.data else '')
given_content_length = request.headers.get('Content-Length', body_length)
given_content_length_value = int(given_content_length)
if given_content_length_value > body_length:
@@ -105,7 +105,7 @@ def validate_content_length_header_not_too_small(
that the content length is smaller than the body length.
"""
- body_length = len(bytearray(request.data) if request.data else '')
+ body_length = len((request.data) if request.data else '')
given_content_length = request.headers.get('Content-Length', body_length)
given_content_length_value = int(given_content_length)
diff --git a/src/mock_vws/_database_matchers.py b/src/mock_vws/_database_matchers.py
index ed93a0554..f65fdf585 100644
--- a/src/mock_vws/_database_matchers.py
+++ b/src/mock_vws/_database_matchers.py
@@ -126,12 +126,13 @@ def get_database_matching_server_keys(
content_type = request.headers.get('Content-Type', '').split(';')[0]
auth_header = request.headers.get('Authorization')
+ import pdb; pdb.set_trace()
for database in databases:
expected_authorization_header = _authorization_header(
access_key=database.server_access_key,
secret_key=database.server_secret_key,
method=request.method,
- content=request.body or b'',
+ content=str(request.json()) or b'',
content_type=content_type,
date=request.headers.get('Date', ''),
request_path=request.path,
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 4d298b644..84e5f531d 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -78,11 +78,13 @@ def __init__(
self.state = state
def to_dict(self) -> Dict[str, str]:
+ targets = []
return {
'database_name': self.database_name,
'server_access_key': self.server_access_key,
'server_secret_key': self.server_secret_key,
'client_access_key': self.client_access_key,
'client_secret_key': self.client_secret_key,
- # TODO target, state
+ 'state': str(self.state),
+ 'targets': targets,
}
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index dd9e55cc2..5ec598af2 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -141,12 +141,12 @@ def _enable_use_docker_in_memory(
requests.post(
url=STORAGE_BASE_URL + '/databases',
- data=working_database_dict,
+ json=working_database_dict,
)
requests.post(
url=STORAGE_BASE_URL + '/databases',
- data=inactive_database_dict,
+ json=inactive_database_dict,
)
yield
From f0ce8a07819e1db1f844e4dd333e66507a4db57e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 24 Feb 2020 02:36:48 +0000
Subject: [PATCH 0048/3455] Progress towards inline dumping target to JSON
---
src/_mock_vws_server/vws/_services_validators/__init__.py | 5 ++++-
.../vws/_services_validators/auth_validators.py | 5 ++++-
.../vws/_services_validators/content_length_validators.py | 2 +-
src/mock_vws/database.py | 8 ++++----
src/mock_vws/target.py | 6 +++++-
5 files changed, 18 insertions(+), 8 deletions(-)
diff --git a/src/_mock_vws_server/vws/_services_validators/__init__.py b/src/_mock_vws_server/vws/_services_validators/__init__.py
index 801ee42ba..fef41097f 100644
--- a/src/_mock_vws_server/vws/_services_validators/__init__.py
+++ b/src/_mock_vws_server/vws/_services_validators/__init__.py
@@ -86,7 +86,10 @@ def validate_project_state(
project is inactive.
"""
database = get_database_matching_server_keys(
- request=request,
+ request_headers=dict(request.headers),
+ request_body=request.body,
+ request_method=request.method,
+ request_path=request.path,
databases=instance.databases,
)
diff --git a/src/_mock_vws_server/vws/_services_validators/auth_validators.py b/src/_mock_vws_server/vws/_services_validators/auth_validators.py
index b619c3fbd..ff6cfe2b0 100644
--- a/src/_mock_vws_server/vws/_services_validators/auth_validators.py
+++ b/src/_mock_vws_server/vws/_services_validators/auth_validators.py
@@ -138,7 +138,10 @@ def validate_authorization(
"""
database = get_database_matching_server_keys(
- request=request,
+ request_headers=dict(request.headers),
+ request_body=request.body,
+ request_method=request.method,
+ request_path=request.path,
databases=instance.databases,
)
diff --git a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
index 8abab8e0e..882f93593 100644
--- a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
+++ b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
@@ -105,7 +105,7 @@ def validate_content_length_header_not_too_small(
that the content length is smaller than the body length.
"""
- body_length = len((request.data) if request.data else '')
+ body_length = len(request.data.decode() if request.data else '')
given_content_length = request.headers.get('Content-Length', body_length)
given_content_length_value = int(given_content_length)
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 84e5f531d..806c642cc 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -3,7 +3,7 @@
"""
import uuid
-from typing import Dict, List, Optional
+from typing import Dict, List, Optional, Union
from .states import States
from .target import Target
@@ -77,8 +77,8 @@ def __init__(
self.targets: List[Target] = []
self.state = state
- def to_dict(self) -> Dict[str, str]:
- targets = []
+ def to_dict(self) -> Dict[str, Union[str, Dict[str, Dict[str, str]]]]:
+ target_dict = {target.name: target.to_dict() for target in self.targets}
return {
'database_name': self.database_name,
'server_access_key': self.server_access_key,
@@ -86,5 +86,5 @@ def to_dict(self) -> Dict[str, str]:
'client_access_key': self.client_access_key,
'client_secret_key': self.client_secret_key,
'state': str(self.state),
- 'targets': targets,
+ 'targets': target_dict,
}
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index dbde6d334..09551c9f2 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -7,7 +7,7 @@
import random
import statistics
import uuid
-from typing import Optional, Union
+from typing import Dict, Optional, Union
import pytz
from PIL import Image, ImageStat
@@ -162,3 +162,7 @@ def tracking_rating(self) -> int:
return self.processed_tracking_rating
return 0
+
+ def to_dict(self) -> Dict[str, str]:
+ # TODO
+ return {}
From 73469102ea73980817aad809357bf71c376e6706 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 24 Feb 2020 02:42:57 +0000
Subject: [PATCH 0049/3455] Progress towards inline dumping target to JSON
---
src/_mock_vws_server/vws/__init__.py | 36 ++++++++++++++++++----------
1 file changed, 23 insertions(+), 13 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 3a8324b45..8af23dcb0 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -160,6 +160,13 @@ def get_all_databases() -> Set[VuforiaDatabase]:
for target_dict in database_dict['targets']:
# TODO fill this in
+ name = target_dict['name']
+ active_flag = target_dict['active_flag']
+ width= target_dict['width']
+ image = target_dict['image']
+ processing_time_seconds = target_dict['processing_time_seconds']
+ application_metadata = target_dict['application_metadata']
+
target = Target(
name=name,
active_flag=active_flag,
@@ -187,23 +194,26 @@ def add_target() -> Tuple[str, int]:
# We do not use ``request.get_json(force=True)`` because this only works when the content
# type is given as ``application/json``.
request_json = json.loads(request.data)
- request_json['name']
+ name = request_json['name']
databases = get_all_databases()
+ # import pdb; pdb.set_trace()
database = get_database_matching_server_keys(
- request=request,
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
databases=databases,
)
- #
- # assert isinstance(database, VuforiaDatabase)
- #
- # (target for target in database.targets if not target.delete_date)
- # if any(target.name == name for target in targets):
- # context.status_code = codes.FORBIDDEN
- # body = {
- # 'transaction_id': uuid.uuid4().hex,
- # 'result_code': ResultCodes.TARGET_NAME_EXIST.value,
- # }
- # return json_dump(body)
+
+ assert isinstance(database, VuforiaDatabase)
+
+ targets = (target for target in database.targets if not target.delete_date)
+ if any(target.name == name for target in targets):
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.TARGET_NAME_EXIST.value,
+ }
+ return json_dump(body), codes.FORBIDDEN
active_flag = request_json.get('active_flag')
if active_flag is None:
From ced8cd16ee97745ac2468c843fb699a6eb918b3d Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 24 Feb 2020 03:05:41 +0000
Subject: [PATCH 0050/3455] Progress towards inline dumping target to JSON
---
src/_mock_vws_server/storage/__init__.py | 2 ++
src/_mock_vws_server/vws/__init__.py | 1 -
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index bf6b64051..c07a10fe7 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -25,7 +25,9 @@ def create_database() -> Tuple[str, int]:
database = VuforiaDatabase(
server_access_key=server_access_key,
+ server_secret_key=server_secret_key,
client_access_key=client_access_key,
+ client_secret_key=client_secret_key,
database_name=database_name,
state=state,
)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 8af23dcb0..0bdf184fe 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -196,7 +196,6 @@ def add_target() -> Tuple[str, int]:
request_json = json.loads(request.data)
name = request_json['name']
databases = get_all_databases()
- # import pdb; pdb.set_trace()
database = get_database_matching_server_keys(
request_headers=dict(request.headers),
request_body=request.data,
From 66b488c4ac6bdb157db6f22656076e5a4f309121 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 24 Feb 2020 03:08:48 +0000
Subject: [PATCH 0051/3455] Progress towards inline dumping target to JSON
---
src/_mock_vws_server/vws/__init__.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 0bdf184fe..f2de6d35f 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -232,7 +232,13 @@ def add_target() -> Tuple[str, int]:
# processing_time_seconds=self._processing_time_seconds,
application_metadata=request_json.get('application_metadata'),
)
+ # TODO make this work
# database.targets.append(new_target)
+ # --->
+ requests.post(
+ url=STORAGE_BASE_URL + f'/databases/{database_name}/targets',
+ json=new_target.to_dict(),
+ )
body = {
'transaction_id': uuid.uuid4().hex,
From 8abbb5462a260b7b2ba7dbb47d9e992d7d4af93f Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 25 Feb 2020 12:21:10 +0000
Subject: [PATCH 0052/3455] Progress towards inline dumping target to JSON
---
src/_mock_vws_server/vws/__init__.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index f2de6d35f..1983f0d6c 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -55,7 +55,8 @@
VWS_FLASK_APP = Flask(__name__)
JSON_SCHEMA = JsonSchema(VWS_FLASK_APP)
-# TODO this
+# TODO choose something for this - it should actually work in a docker-compose
+# scenario.
STORAGE_BASE_URL = 'http://todo.com'
ADD_TARGET_SCHEMA = {
@@ -236,7 +237,7 @@ def add_target() -> Tuple[str, int]:
# database.targets.append(new_target)
# --->
requests.post(
- url=STORAGE_BASE_URL + f'/databases/{database_name}/targets',
+ url=f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets',
json=new_target.to_dict(),
)
From 98c8fbd0a8cfdc400522c0b0753080f0a7fcd3b9 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 25 Feb 2020 14:52:15 +0000
Subject: [PATCH 0053/3455] Clear databases before each test
---
src/_mock_vws_server/storage/__init__.py | 38 +++++++++++++++++++++
tests/mock_vws/fixtures/vuforia_backends.py | 3 ++
2 files changed, 41 insertions(+)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index c07a10fe7..8d0ee30a3 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -7,6 +7,13 @@
VUFORIA_DATABASES: List[VuforiaDatabase] = []
+@STORAGE_FLASK_APP.route('/reset', methods=['POST'])
+def reset():
+ # import pdb; pdb.set_trace()
+
+ VUFORIA_DATABASES.clear()
+ return ''
+
@STORAGE_FLASK_APP.route('/databases', methods=['GET'])
def get_databases() -> Tuple[str, int]:
databases = [database.to_dict() for database in VUFORIA_DATABASES]
@@ -33,3 +40,34 @@ def create_database() -> Tuple[str, int]:
)
VUFORIA_DATABASES.append(database)
return jsonify(database.to_dict()), codes.CREATED
+
+
+@STORAGE_FLASK_APP.route(
+ '/databases//targets',
+ methods=['POST'],
+)
+def create_target(database_name: str) -> Tuple[str, int]:
+ [database] = [database for database in VUFORIA_DATABASES if database.database_name == database_name]
+ state = request.json['state']
+ target = Target(
+ name=request.json['name'],
+ width=request.json['width'],
+ image=request.json['image'],
+ active_flag=request.json['active_flag'],
+ processing_time_seconds=request.json['processing_time_seconds'],
+ application_metadata=request.json['application_metadata'],
+ )
+ database.targets.append(target)
+
+ database = VuforiaDatabase(
+ server_access_key=server_access_key,
+ server_secret_key=server_secret_key,
+ client_access_key=client_access_key,
+ client_secret_key=client_secret_key,
+ database_name=database_name,
+ state=state,
+ )
+ VUFORIA_DATABASES.append(database)
+ return jsonify(database.to_dict()), codes.CREATED
+
+
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index 5ec598af2..46d6d2b80 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -117,6 +117,7 @@ def _enable_use_docker_in_memory(
working_database: VuforiaDatabase,
inactive_database: VuforiaDatabase,
) -> Generator:
+ # import pdb; pdb.set_trace()
with requests_mock.Mocker(real_http=False) as mock:
add_flask_app_to_mock(
mock_obj=mock,
@@ -136,6 +137,8 @@ def _enable_use_docker_in_memory(
base_url=STORAGE_BASE_URL,
)
+ requests.post(url=STORAGE_BASE_URL + '/reset')
+
working_database_dict = working_database.to_dict()
inactive_database_dict = inactive_database.to_dict()
From 24d306ad9d27ceb5564841c546ff2f6b781b4e2f Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 25 Feb 2020 15:22:28 +0000
Subject: [PATCH 0054/3455] Progress towards dumping target as dict
---
src/_mock_vws_server/storage/__init__.py | 25 +++++++++++-------------
src/mock_vws/target.py | 15 ++++++++++++--
2 files changed, 24 insertions(+), 16 deletions(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index 8d0ee30a3..53502b84b 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -2,17 +2,18 @@
from requests import codes
from typing import Tuple, List
from mock_vws.database import VuforiaDatabase
+from mock_vws.target import Target
STORAGE_FLASK_APP = Flask(__name__)
VUFORIA_DATABASES: List[VuforiaDatabase] = []
@STORAGE_FLASK_APP.route('/reset', methods=['POST'])
-def reset():
+def reset() -> Tuple[str, int]:
# import pdb; pdb.set_trace()
VUFORIA_DATABASES.clear()
- return ''
+ return '', codes.OK
@STORAGE_FLASK_APP.route('/databases', methods=['GET'])
def get_databases() -> Tuple[str, int]:
@@ -48,26 +49,22 @@ def create_database() -> Tuple[str, int]:
)
def create_target(database_name: str) -> Tuple[str, int]:
[database] = [database for database in VUFORIA_DATABASES if database.database_name == database_name]
- state = request.json['state']
+ import io
+ import base64
+ image_base64 = request.json['image_base64']
+ # import pdb; pdb.set_trace()
+ image_bytes = base64.b64decode(image_base64)
+ image = io.BytesIO(image_bytes)
target = Target(
name=request.json['name'],
width=request.json['width'],
- image=request.json['image'],
+ image=image,
active_flag=request.json['active_flag'],
processing_time_seconds=request.json['processing_time_seconds'],
application_metadata=request.json['application_metadata'],
)
database.targets.append(target)
- database = VuforiaDatabase(
- server_access_key=server_access_key,
- server_secret_key=server_secret_key,
- client_access_key=client_access_key,
- client_secret_key=client_secret_key,
- database_name=database_name,
- state=state,
- )
- VUFORIA_DATABASES.append(database)
- return jsonify(database.to_dict()), codes.CREATED
+ return jsonify(target.to_dict()), codes.CREATED
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 09551c9f2..a7e8d88f5 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -163,6 +163,17 @@ def tracking_rating(self) -> int:
return 0
- def to_dict(self) -> Dict[str, str]:
+ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
# TODO
- return {}
+ import base64
+ # import pdb; pdb.set_trace()
+ return {
+ 'name': self.name,
+ 'width': self.width,
+ 'image_base64': base64.encodestring(self.image.getvalue()).decode(),
+ 'active_flag': self.active_flag,
+ 'processing_time_seconds': self._processing_time_seconds,
+ 'application_metadata': self.application_metadata,
+
+
+ }
From 4c1aa6275735d1e3b0337f0f004f99aec7dd6cf3 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 25 Feb 2020 15:25:04 +0000
Subject: [PATCH 0055/3455] Progress towards dumping target as dict
---
src/_mock_vws_server/vws/__init__.py | 4 +++-
src/mock_vws/database.py | 4 ++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 1983f0d6c..529b649af 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -164,7 +164,9 @@ def get_all_databases() -> Set[VuforiaDatabase]:
name = target_dict['name']
active_flag = target_dict['active_flag']
width= target_dict['width']
- image = target_dict['image']
+ image_base64 = target_dict['image_base64']
+ image_bytes = base64.b64decode(image_base64)
+ image = io.BytesIO(image_bytes)
processing_time_seconds = target_dict['processing_time_seconds']
application_metadata = target_dict['application_metadata']
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 806c642cc..c78c52e43 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -78,7 +78,7 @@ def __init__(
self.state = state
def to_dict(self) -> Dict[str, Union[str, Dict[str, Dict[str, str]]]]:
- target_dict = {target.name: target.to_dict() for target in self.targets}
+ targets = [target.to_dict() for target in self.targets]
return {
'database_name': self.database_name,
'server_access_key': self.server_access_key,
@@ -86,5 +86,5 @@ def to_dict(self) -> Dict[str, Union[str, Dict[str, Dict[str, str]]]]:
'client_access_key': self.client_access_key,
'client_secret_key': self.client_secret_key,
'state': str(self.state),
- 'targets': target_dict,
+ 'targets': targets,
}
From 7faa2c03dbfe34d85e9701ffd0455e98b13d6d91 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 25 Feb 2020 23:11:53 +0000
Subject: [PATCH 0056/3455] progress towards get_target
---
src/_mock_vws_server/vws/__init__.py | 54 ++++++++++++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 529b649af..c9ec30191 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -249,3 +249,57 @@ def add_target() -> Tuple[str, int]:
'target_id': new_target.target_id,
}
return json_dump(body), codes.CREATED
+
+@VWS_FLASK_APP.route('/targets/', methods=['GET'])
+# @JSON_SCHEMA.validate(ADD_TARGET_SCHEMA)
+def get_target(target_id: str) -> Tuple[str, int]:
+ """
+ Get details of a target.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Retrieve-a-Target-Record
+ """
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ name = target_dict['name']
+ active_flag = target_dict['active_flag']
+ width= target_dict['width']
+ image_base64 = target_dict['image_base64']
+ image_bytes = base64.b64decode(image_base64)
+ image = io.BytesIO(image_bytes)
+ processing_time_seconds = target_dict['processing_time_seconds']
+ application_metadata = target_dict['application_metadata']
+
+ target = Target(
+ name=name,
+ active_flag=active_flag,
+ width=width,
+ image=image,
+ processing_time_seconds=processing_time_seconds,
+ application_metadata=application_metadata,
+ )
+
+ target_record = {
+ 'target_id': target.target_id,
+ 'active_flag': target.active_flag,
+ 'name': target.name,
+ 'width': target.width,
+ 'tracking_rating': target.tracking_rating,
+ 'reco_rating': target.reco_rating,
+ }
+
+ body = {
+ 'result_code': ResultCodes.SUCCESS.value,
+ 'transaction_id': uuid.uuid4().hex,
+ 'target_record': target_record,
+ 'status': target.status,
+ }
+
+ return json_dump(body), codes.OK
From 5e7138eac6b3bf7d9f5d1f55ea253e0137114ce0 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 25 Feb 2020 23:17:09 +0000
Subject: [PATCH 0057/3455] progress towards get_target
---
src/_mock_vws_server/vws/__init__.py | 22 +++++-----------------
1 file changed, 5 insertions(+), 17 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index c9ec30191..3c43855f5 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -268,23 +268,11 @@ def get_target(target_id: str) -> Tuple[str, int]:
databases=databases,
)
- name = target_dict['name']
- active_flag = target_dict['active_flag']
- width= target_dict['width']
- image_base64 = target_dict['image_base64']
- image_bytes = base64.b64decode(image_base64)
- image = io.BytesIO(image_bytes)
- processing_time_seconds = target_dict['processing_time_seconds']
- application_metadata = target_dict['application_metadata']
-
- target = Target(
- name=name,
- active_flag=active_flag,
- width=width,
- image=image,
- processing_time_seconds=processing_time_seconds,
- application_metadata=application_metadata,
- )
+ try:
+ [target] = [target for target in database.targets if target.target_id == target_id]
+ except:
+ import pdb; pdb.set_trace()
+ pass
target_record = {
'target_id': target.target_id,
From 580ee19ad0f5a1e50a3e635d325cc5fefbc21daf Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 25 Feb 2020 23:35:17 +0000
Subject: [PATCH 0058/3455] progress towards get_target
---
src/_mock_vws_server/storage/__init__.py | 3 +++
src/_mock_vws_server/vws/__init__.py | 9 ++++-----
src/mock_vws/target.py | 19 +++++++++++++++++--
3 files changed, 24 insertions(+), 7 deletions(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index 53502b84b..f0e8a50e9 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -63,7 +63,10 @@ def create_target(database_name: str) -> Tuple[str, int]:
processing_time_seconds=request.json['processing_time_seconds'],
application_metadata=request.json['application_metadata'],
)
+ # import pdb; pdb.set_trace()
+ target.target_id = request.json['target_id']
database.targets.append(target)
+ # import pdb; pdb.set_trace(
return jsonify(target.to_dict()), codes.CREATED
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 3c43855f5..d4b334c8b 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -178,6 +178,7 @@ def get_all_databases() -> Set[VuforiaDatabase]:
processing_time_seconds=processing_time_seconds,
application_metadata=application_metadata,
)
+ target.target_id = target_dict['target_id']
new_database.targets.append(target)
databases.add(new_database)
@@ -235,6 +236,7 @@ def add_target() -> Tuple[str, int]:
# processing_time_seconds=self._processing_time_seconds,
application_metadata=request_json.get('application_metadata'),
)
+
# TODO make this work
# database.targets.append(new_target)
# --->
@@ -243,6 +245,7 @@ def add_target() -> Tuple[str, int]:
json=new_target.to_dict(),
)
+ # import pdb; pdb.set_trace()
body = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.TARGET_CREATED.value,
@@ -268,11 +271,7 @@ def get_target(target_id: str) -> Tuple[str, int]:
databases=databases,
)
- try:
- [target] = [target for target in database.targets if target.target_id == target_id]
- except:
- import pdb; pdb.set_trace()
- pass
+ [target] = [target for target in database.targets if target.target_id == target_id]
target_record = {
'target_id': target.target_id,
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index a7e8d88f5..ecbfe0a4e 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -21,6 +21,9 @@ class Target: # pylint: disable=too-many-instance-attributes
https://developer.vuforia.com/target-manager.
"""
+ # TODO remove
+ NUM = 1
+
name: str
target_id: str
active_flag: bool
@@ -74,6 +77,7 @@ def __init__( # pylint: disable=too-many-arguments
target was deleted.
"""
self.name = name
+ # TODO UNDO
self.target_id = uuid.uuid4().hex
self.active_flag = active_flag
self.width = width
@@ -88,6 +92,13 @@ def __init__( # pylint: disable=too-many-arguments
self.application_metadata = application_metadata
self.delete_date: Optional[datetime.datetime] = None
+ def __repr__(self) -> str:
+ """
+ XXX
+ """
+ class_name = self.__class__.__name__
+ return f'<{class_name}: {self.target_id}>'
+
@property
def _post_processing_status(self) -> TargetStatuses:
"""
@@ -112,7 +123,6 @@ def _post_processing_status(self) -> TargetStatuses:
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'.
@@ -164,7 +174,11 @@ def tracking_rating(self) -> int:
return 0
def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
- # TODO
+ # TODO e.g. processed tracking rating can surely change if
+ # target is dumped then recreated.
+ #
+ # as can e.g. processing time... maybe use dataclass but then
+ # https://github.com/agronholm/sphinx-autodoc-typehints/issues/123
import base64
# import pdb; pdb.set_trace()
return {
@@ -174,6 +188,7 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
'active_flag': self.active_flag,
'processing_time_seconds': self._processing_time_seconds,
'application_metadata': self.application_metadata,
+ 'target_id': self.target_id,
}
From 524a43439f174ae21b052c23e8c51bba895266dc Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 25 Feb 2020 23:46:02 +0000
Subject: [PATCH 0059/3455] progress towards get_target
---
src/_mock_vws_server/vws/__init__.py | 6 ++++++
src/mock_vws/target.py | 3 +--
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index d4b334c8b..4dfb56907 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -1,8 +1,10 @@
import base64
+import datetime
import email.utils
import io
import json
import uuid
+import pytz
from typing import Set, Tuple
import requests
@@ -179,6 +181,10 @@ def get_all_databases() -> Set[VuforiaDatabase]:
application_metadata=application_metadata,
)
target.target_id = target_dict['target_id']
+ gmt = pytz.timezone('GMT')
+ # import pdb; pdb.set_trace()
+ target.last_modified_date = datetime.datetime.fromordinal(target_dict['last_modified_date_ordinal'])
+ target.last_modified_date = target.last_modified_date.replace(tzinfo=gmt)
new_database.targets.append(target)
databases.add(new_database)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index ecbfe0a4e..952e38879 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -189,6 +189,5 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
'processing_time_seconds': self._processing_time_seconds,
'application_metadata': self.application_metadata,
'target_id': self.target_id,
-
-
+ 'last_modified_date_ordinal': self.last_modified_date.toordinal(),
}
From 2f54a211ed6cbc2cc4715038c42e000eb0ad46a4 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 25 Feb 2020 23:55:50 +0000
Subject: [PATCH 0060/3455] progress towards get_target
---
src/_mock_vws_server/storage/__init__.py | 19 ++++++++--
src/_mock_vws_server/vws/__init__.py | 46 ++++++++++++++++++++++--
src/mock_vws/database.py | 2 +-
3 files changed, 62 insertions(+), 5 deletions(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index f0e8a50e9..7e2c9ae2e 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -1,8 +1,12 @@
from flask import Flask, jsonify, request
+import datetime
from requests import codes
+import pytz
from typing import Tuple, List
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
+import io
+import base64
STORAGE_FLASK_APP = Flask(__name__)
@@ -49,8 +53,6 @@ def create_database() -> Tuple[str, int]:
)
def create_target(database_name: str) -> Tuple[str, int]:
[database] = [database for database in VUFORIA_DATABASES if database.database_name == database_name]
- import io
- import base64
image_base64 = request.json['image_base64']
# import pdb; pdb.set_trace()
image_bytes = base64.b64decode(image_base64)
@@ -71,3 +73,16 @@ def create_target(database_name: str) -> Tuple[str, int]:
return jsonify(target.to_dict()), codes.CREATED
+@STORAGE_FLASK_APP.route(
+ '/databases//targets/',
+ methods=['DELETE'],
+)
+def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
+ [database] = [database for database in VUFORIA_DATABASES if database.database_name == database_name]
+ [target] = [target for target in database.targets if target.target_id == target_id]
+ gmt = pytz.timezone('GMT')
+ now = datetime.datetime.now(tz=gmt)
+ target.delete_date = now
+ return jsonify(target.to_dict()), codes.OK
+
+
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 4dfb56907..92b3278e3 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -5,14 +5,14 @@
import json
import uuid
import pytz
-from typing import Set, Tuple
+from typing import Set, Tuple, Dict
import requests
from flask import Flask, Response, request
from flask_json_schema import JsonSchema, JsonValidationError
from requests import codes
-from mock_vws._constants import ResultCodes
+from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_server_keys
from mock_vws._mock_common import json_dump
from mock_vws.database import VuforiaDatabase
@@ -277,6 +277,7 @@ def get_target(target_id: str) -> Tuple[str, int]:
databases=databases,
)
+ assert isinstance(database, VuforiaDatabase)
[target] = [target for target in database.targets if target.target_id == target_id]
target_record = {
@@ -296,3 +297,44 @@ def get_target(target_id: str) -> Tuple[str, int]:
}
return json_dump(body), codes.OK
+
+@VWS_FLASK_APP.route('/targets/', methods=['DELETE'])
+def delete_target(target_id: str) -> Tuple[str, int]:
+ """
+ Delete a target.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Delete-a-Target
+ """
+ body: Dict[str, str] = {}
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+ [target] = [target for target in database.targets if target.target_id == target_id]
+
+ if target.status == TargetStatuses.PROCESSING.value:
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.TARGET_STATUS_PROCESSING.value,
+ }
+ return json_dump(body), codes.FORBIDDEN
+
+ # gmt = pytz.timezone('GMT')
+ # now = datetime.datetime.now(tz=gmt)
+ # target.delete_date = now
+ requests.delete(
+ url=f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/{target_id}',
+ )
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.SUCCESS.value,
+ }
+ return json_dump(body), codes.OK
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index c78c52e43..d6e21cf42 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -77,7 +77,7 @@ def __init__(
self.targets: List[Target] = []
self.state = state
- def to_dict(self) -> Dict[str, Union[str, Dict[str, Dict[str, str]]]]:
+ def to_dict(self) -> Dict[str, Union[str, List[Dict[str, Optional[Union[str, int, bool, float]]]]]]:
targets = [target.to_dict() for target in self.targets]
return {
'database_name': self.database_name,
From 3db1dfbb5e903226487504d6fed1db0a129d5d23 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 00:01:01 +0000
Subject: [PATCH 0061/3455] A bunch of passing tests
---
src/_mock_vws_server/vws/__init__.py | 4 ++++
src/mock_vws/target.py | 5 +++++
2 files changed, 9 insertions(+)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 92b3278e3..cd111083c 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -185,6 +185,10 @@ def get_all_databases() -> Set[VuforiaDatabase]:
# import pdb; pdb.set_trace()
target.last_modified_date = datetime.datetime.fromordinal(target_dict['last_modified_date_ordinal'])
target.last_modified_date = target.last_modified_date.replace(tzinfo=gmt)
+ delete_date_optional_ordinal = target_dict['delete_date_optional_ordinal']
+ if delete_date_optional_ordinal:
+ target.delete_date = datetime.datetime.fromordinal(delete_date_optional_ordinal)
+ target.delete_date = target.delete_date.replace(tzinfo=gmt)
new_database.targets.append(target)
databases.add(new_database)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 952e38879..f9515ea4d 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -181,6 +181,10 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
# https://github.com/agronholm/sphinx-autodoc-typehints/issues/123
import base64
# import pdb; pdb.set_trace()
+ if self.delete_date:
+ delete_date = datetime.datetime.toordinal(self.delete_date)
+ else:
+ delete_date = None
return {
'name': self.name,
'width': self.width,
@@ -190,4 +194,5 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
'application_metadata': self.application_metadata,
'target_id': self.target_id,
'last_modified_date_ordinal': self.last_modified_date.toordinal(),
+ 'delete_date_optional_ordinal': delete_date,
}
From a2af561686dac1a283986cfcc2f4a8b4c5bb609e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 00:32:06 +0000
Subject: [PATCH 0062/3455] A bunch of passing tests
---
src/_mock_vws_server/storage/__init__.py | 4 +-
src/_mock_vws_server/vws/__init__.py | 65 ++-------------
src/_mock_vws_server/vws/_constants.py | 4 +
src/_mock_vws_server/vws/_databases.py | 79 +++++++++++++++++++
.../vws/_services_validators/__init__.py | 6 +-
src/mock_vws/database.py | 2 +-
src/mock_vws/target.py | 2 +-
7 files changed, 96 insertions(+), 66 deletions(-)
create mode 100644 src/_mock_vws_server/vws/_databases.py
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index 7e2c9ae2e..2c01fb0bb 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -7,6 +7,7 @@
from mock_vws.target import Target
import io
import base64
+from mock_vws.states import States
STORAGE_FLASK_APP = Flask(__name__)
@@ -32,8 +33,7 @@ def create_database() -> Tuple[str, int]:
client_access_key = request.json['client_access_key']
client_secret_key = request.json['client_secret_key']
database_name = request.json['database_name']
- # TODO this will have to be converted by enum
- state = request.json['state']
+ state = States(request.json['state_value'])
database = VuforiaDatabase(
server_access_key=server_access_key,
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index cd111083c..f774edff6 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -28,6 +28,7 @@
validate_name_type,
validate_not_invalid_json,
validate_width,
+ validate_project_state,
)
from ._services_validators.auth_validators import (
validate_auth_header_exists,
@@ -55,11 +56,11 @@
validate_image_size,
)
+from ._databases import get_all_databases
+from ._constants import STORAGE_BASE_URL
+
VWS_FLASK_APP = Flask(__name__)
JSON_SCHEMA = JsonSchema(VWS_FLASK_APP)
-# TODO choose something for this - it should actually work in a docker-compose
-# scenario.
-STORAGE_BASE_URL = 'http://todo.com'
ADD_TARGET_SCHEMA = {
'required': ['name', 'image', 'width'],
@@ -107,7 +108,7 @@
@validate_metadata_encoding
@validate_metadata_size
# @validate_authorization
-# @validate_project_state
+@validate_project_state
def validate_request() -> None:
pass
# decorators = [
@@ -138,62 +139,6 @@ def set_headers(response: Response) -> Response:
return response
-def get_all_databases() -> Set[VuforiaDatabase]:
- # TODO use the storage URL to get details then cast to VuforiaDatabase
- response = requests.get(url=STORAGE_BASE_URL + '/databases')
- response_json = response.json()
- databases = set()
- for database_dict in response_json:
- database_name = database_dict['database_name']
- server_access_key = database_dict['server_access_key']
- server_secret_key = database_dict['server_secret_key']
- client_access_key = database_dict['client_access_key']
- client_secret_key = database_dict['client_secret_key']
- state = database_dict['state']
- # TODO state
-
- new_database = VuforiaDatabase(
- database_name=database_name,
- server_access_key=server_access_key,
- server_secret_key=server_secret_key,
- client_access_key=client_access_key,
- client_secret_key=client_secret_key,
- state=state,
- )
-
- for target_dict in database_dict['targets']:
- # TODO fill this in
- name = target_dict['name']
- active_flag = target_dict['active_flag']
- width= target_dict['width']
- image_base64 = target_dict['image_base64']
- image_bytes = base64.b64decode(image_base64)
- image = io.BytesIO(image_bytes)
- processing_time_seconds = target_dict['processing_time_seconds']
- application_metadata = target_dict['application_metadata']
-
- target = Target(
- name=name,
- active_flag=active_flag,
- width=width,
- image=image,
- processing_time_seconds=processing_time_seconds,
- application_metadata=application_metadata,
- )
- target.target_id = target_dict['target_id']
- gmt = pytz.timezone('GMT')
- # import pdb; pdb.set_trace()
- target.last_modified_date = datetime.datetime.fromordinal(target_dict['last_modified_date_ordinal'])
- target.last_modified_date = target.last_modified_date.replace(tzinfo=gmt)
- delete_date_optional_ordinal = target_dict['delete_date_optional_ordinal']
- if delete_date_optional_ordinal:
- target.delete_date = datetime.datetime.fromordinal(delete_date_optional_ordinal)
- target.delete_date = target.delete_date.replace(tzinfo=gmt)
- new_database.targets.append(target)
-
- databases.add(new_database)
-
- return databases
@VWS_FLASK_APP.route('/targets', methods=['POST'])
diff --git a/src/_mock_vws_server/vws/_constants.py b/src/_mock_vws_server/vws/_constants.py
index cbba98ffa..17477fb84 100644
--- a/src/_mock_vws_server/vws/_constants.py
+++ b/src/_mock_vws_server/vws/_constants.py
@@ -47,3 +47,7 @@ class TargetStatuses(Enum):
PROCESSING = 'processing'
SUCCESS = 'success'
FAILED = 'failed'
+
+# TODO choose something for this - it should actually work in a docker-compose
+# scenario.
+STORAGE_BASE_URL = 'http://todo.com'
diff --git a/src/_mock_vws_server/vws/_databases.py b/src/_mock_vws_server/vws/_databases.py
new file mode 100644
index 000000000..7282d4957
--- /dev/null
+++ b/src/_mock_vws_server/vws/_databases.py
@@ -0,0 +1,79 @@
+
+import base64
+import datetime
+import email.utils
+import io
+import json
+import uuid
+import pytz
+from typing import Set, Tuple, Dict
+
+import requests
+from flask import Flask, Response, request
+from flask_json_schema import JsonSchema, JsonValidationError
+from requests import codes
+from mock_vws._constants import ResultCodes, TargetStatuses
+from mock_vws._database_matchers import get_database_matching_server_keys
+from mock_vws._mock_common import json_dump
+from mock_vws.database import VuforiaDatabase
+from mock_vws.target import Target
+from mock_vws.states import States
+
+from ._constants import STORAGE_BASE_URL
+
+def get_all_databases() -> Set[VuforiaDatabase]:
+ # TODO use the storage URL to get details then cast to VuforiaDatabase
+ response = requests.get(url=STORAGE_BASE_URL + '/databases')
+ response_json = response.json()
+ databases = set()
+ for database_dict in response_json:
+ database_name = database_dict['database_name']
+ server_access_key = database_dict['server_access_key']
+ server_secret_key = database_dict['server_secret_key']
+ client_access_key = database_dict['client_access_key']
+ client_secret_key = database_dict['client_secret_key']
+ state = States(database_dict['state_value'])
+ # TODO state
+
+ new_database = VuforiaDatabase(
+ database_name=database_name,
+ server_access_key=server_access_key,
+ server_secret_key=server_secret_key,
+ client_access_key=client_access_key,
+ client_secret_key=client_secret_key,
+ state=state,
+ )
+
+ for target_dict in database_dict['targets']:
+ # TODO fill this in
+ name = target_dict['name']
+ active_flag = target_dict['active_flag']
+ width= target_dict['width']
+ image_base64 = target_dict['image_base64']
+ image_bytes = base64.b64decode(image_base64)
+ image = io.BytesIO(image_bytes)
+ processing_time_seconds = target_dict['processing_time_seconds']
+ application_metadata = target_dict['application_metadata']
+
+ target = Target(
+ name=name,
+ active_flag=active_flag,
+ width=width,
+ image=image,
+ processing_time_seconds=processing_time_seconds,
+ application_metadata=application_metadata,
+ )
+ target.target_id = target_dict['target_id']
+ gmt = pytz.timezone('GMT')
+ # import pdb; pdb.set_trace()
+ target.last_modified_date = datetime.datetime.fromordinal(target_dict['last_modified_date_ordinal'])
+ target.last_modified_date = target.last_modified_date.replace(tzinfo=gmt)
+ delete_date_optional_ordinal = target_dict['delete_date_optional_ordinal']
+ if delete_date_optional_ordinal:
+ target.delete_date = datetime.datetime.fromordinal(delete_date_optional_ordinal)
+ target.delete_date = target.delete_date.replace(tzinfo=gmt)
+ new_database.targets.append(target)
+
+ databases.add(new_database)
+
+ return databases
diff --git a/src/_mock_vws_server/vws/_services_validators/__init__.py b/src/_mock_vws_server/vws/_services_validators/__init__.py
index fef41097f..d1158688e 100644
--- a/src/_mock_vws_server/vws/_services_validators/__init__.py
+++ b/src/_mock_vws_server/vws/_services_validators/__init__.py
@@ -22,6 +22,7 @@
from mock_vws._mock_common import json_dump
from mock_vws.database import VuforiaDatabase
from mock_vws.states import States
+from .._databases import get_all_databases
@wrapt.decorator
@@ -85,12 +86,13 @@ def validate_project_state(
A `FORBIDDEN` response with a PROJECT_INACTIVE result code if the
project is inactive.
"""
+ databases = get_all_databases()
database = get_database_matching_server_keys(
request_headers=dict(request.headers),
- request_body=request.body,
+ request_body=request.data,
request_method=request.method,
request_path=request.path,
- databases=instance.databases,
+ databases=databases,
)
assert isinstance(database, VuforiaDatabase)
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index d6e21cf42..22a89de5c 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -85,6 +85,6 @@ def to_dict(self) -> Dict[str, Union[str, List[Dict[str, Optional[Union[str, int
'server_secret_key': self.server_secret_key,
'client_access_key': self.client_access_key,
'client_secret_key': self.client_secret_key,
- 'state': str(self.state),
+ 'state_value': self.state.value,
'targets': targets,
}
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index f9515ea4d..7f842c65e 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -182,7 +182,7 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
import base64
# import pdb; pdb.set_trace()
if self.delete_date:
- delete_date = datetime.datetime.toordinal(self.delete_date)
+ delete_date: Optional[int] = datetime.datetime.toordinal(self.delete_date)
else:
delete_date = None
return {
From e30f58443ba53be58deec0dff63196aec8f214cf Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 00:33:21 +0000
Subject: [PATCH 0063/3455] Update a bunch of lint fixes
---
src/_mock_vws_server/storage/__init__.py | 32 +++++++++++------
src/_mock_vws_server/vws/__init__.py | 26 +++++++-------
src/_mock_vws_server/vws/_constants.py | 1 +
src/_mock_vws_server/vws/_databases.py | 35 +++++++++----------
.../vws/_services_validators/__init__.py | 1 +
src/mock_vws/database.py | 5 ++-
src/mock_vws/target.py | 7 ++--
7 files changed, 63 insertions(+), 44 deletions(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index 2c01fb0bb..6a8b9bdc1 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -1,18 +1,21 @@
-from flask import Flask, jsonify, request
+import base64
import datetime
-from requests import codes
+import io
+from typing import List, Tuple
+
import pytz
-from typing import Tuple, List
+from flask import Flask, jsonify, request
+from requests import codes
+
from mock_vws.database import VuforiaDatabase
-from mock_vws.target import Target
-import io
-import base64
from mock_vws.states import States
+from mock_vws.target import Target
STORAGE_FLASK_APP = Flask(__name__)
VUFORIA_DATABASES: List[VuforiaDatabase] = []
+
@STORAGE_FLASK_APP.route('/reset', methods=['POST'])
def reset() -> Tuple[str, int]:
# import pdb; pdb.set_trace()
@@ -20,6 +23,7 @@ def reset() -> Tuple[str, int]:
VUFORIA_DATABASES.clear()
return '', codes.OK
+
@STORAGE_FLASK_APP.route('/databases', methods=['GET'])
def get_databases() -> Tuple[str, int]:
databases = [database.to_dict() for database in VUFORIA_DATABASES]
@@ -52,7 +56,10 @@ def create_database() -> Tuple[str, int]:
methods=['POST'],
)
def create_target(database_name: str) -> Tuple[str, int]:
- [database] = [database for database in VUFORIA_DATABASES if database.database_name == database_name]
+ [database] = [
+ database for database in VUFORIA_DATABASES
+ if database.database_name == database_name
+ ]
image_base64 = request.json['image_base64']
# import pdb; pdb.set_trace()
image_bytes = base64.b64decode(image_base64)
@@ -78,11 +85,14 @@ def create_target(database_name: str) -> Tuple[str, int]:
methods=['DELETE'],
)
def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
- [database] = [database for database in VUFORIA_DATABASES if database.database_name == database_name]
- [target] = [target for target in database.targets if target.target_id == target_id]
+ [database] = [
+ database for database in VUFORIA_DATABASES
+ if database.database_name == database_name
+ ]
+ [target] = [
+ target for target in database.targets if target.target_id == target_id
+ ]
gmt = pytz.timezone('GMT')
now = datetime.datetime.now(tz=gmt)
target.delete_date = now
return jsonify(target.to_dict()), codes.OK
-
-
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index f774edff6..57b37bad9 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -1,11 +1,9 @@
import base64
-import datetime
import email.utils
import io
import json
import uuid
-import pytz
-from typing import Set, Tuple, Dict
+from typing import Dict, Tuple
import requests
from flask import Flask, Response, request
@@ -18,6 +16,8 @@
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
+from ._constants import STORAGE_BASE_URL
+from ._databases import get_all_databases
from ._services_validators import (
validate_active_flag,
validate_metadata_encoding,
@@ -27,8 +27,8 @@
validate_name_length,
validate_name_type,
validate_not_invalid_json,
- validate_width,
validate_project_state,
+ validate_width,
)
from ._services_validators.auth_validators import (
validate_auth_header_exists,
@@ -56,9 +56,6 @@
validate_image_size,
)
-from ._databases import get_all_databases
-from ._constants import STORAGE_BASE_URL
-
VWS_FLASK_APP = Flask(__name__)
JSON_SCHEMA = JsonSchema(VWS_FLASK_APP)
@@ -139,8 +136,6 @@ def set_headers(response: Response) -> Response:
return response
-
-
@VWS_FLASK_APP.route('/targets', methods=['POST'])
@JSON_SCHEMA.validate(ADD_TARGET_SCHEMA)
def add_target() -> Tuple[str, int]:
@@ -208,6 +203,7 @@ def add_target() -> Tuple[str, int]:
}
return json_dump(body), codes.CREATED
+
@VWS_FLASK_APP.route('/targets/', methods=['GET'])
# @JSON_SCHEMA.validate(ADD_TARGET_SCHEMA)
def get_target(target_id: str) -> Tuple[str, int]:
@@ -227,7 +223,9 @@ def get_target(target_id: str) -> Tuple[str, int]:
)
assert isinstance(database, VuforiaDatabase)
- [target] = [target for target in database.targets if target.target_id == target_id]
+ [target] = [
+ target for target in database.targets if target.target_id == target_id
+ ]
target_record = {
'target_id': target.target_id,
@@ -247,6 +245,7 @@ def get_target(target_id: str) -> Tuple[str, int]:
return json_dump(body), codes.OK
+
@VWS_FLASK_APP.route('/targets/', methods=['DELETE'])
def delete_target(target_id: str) -> Tuple[str, int]:
"""
@@ -266,7 +265,9 @@ def delete_target(target_id: str) -> Tuple[str, int]:
)
assert isinstance(database, VuforiaDatabase)
- [target] = [target for target in database.targets if target.target_id == target_id]
+ [target] = [
+ target for target in database.targets if target.target_id == target_id
+ ]
if target.status == TargetStatuses.PROCESSING.value:
body = {
@@ -279,7 +280,8 @@ def delete_target(target_id: str) -> Tuple[str, int]:
# now = datetime.datetime.now(tz=gmt)
# target.delete_date = now
requests.delete(
- url=f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/{target_id}',
+ url=
+ f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/{target_id}',
)
body = {
diff --git a/src/_mock_vws_server/vws/_constants.py b/src/_mock_vws_server/vws/_constants.py
index 17477fb84..8050841a3 100644
--- a/src/_mock_vws_server/vws/_constants.py
+++ b/src/_mock_vws_server/vws/_constants.py
@@ -48,6 +48,7 @@ class TargetStatuses(Enum):
SUCCESS = 'success'
FAILED = 'failed'
+
# TODO choose something for this - it should actually work in a docker-compose
# scenario.
STORAGE_BASE_URL = 'http://todo.com'
diff --git a/src/_mock_vws_server/vws/_databases.py b/src/_mock_vws_server/vws/_databases.py
index 7282d4957..b07eb8089 100644
--- a/src/_mock_vws_server/vws/_databases.py
+++ b/src/_mock_vws_server/vws/_databases.py
@@ -1,26 +1,18 @@
-
import base64
import datetime
-import email.utils
import io
-import json
-import uuid
-import pytz
-from typing import Set, Tuple, Dict
+from typing import Set
+import pytz
import requests
-from flask import Flask, Response, request
-from flask_json_schema import JsonSchema, JsonValidationError
-from requests import codes
-from mock_vws._constants import ResultCodes, TargetStatuses
-from mock_vws._database_matchers import get_database_matching_server_keys
-from mock_vws._mock_common import json_dump
+
from mock_vws.database import VuforiaDatabase
-from mock_vws.target import Target
from mock_vws.states import States
+from mock_vws.target import Target
from ._constants import STORAGE_BASE_URL
+
def get_all_databases() -> Set[VuforiaDatabase]:
# TODO use the storage URL to get details then cast to VuforiaDatabase
response = requests.get(url=STORAGE_BASE_URL + '/databases')
@@ -48,7 +40,7 @@ def get_all_databases() -> Set[VuforiaDatabase]:
# TODO fill this in
name = target_dict['name']
active_flag = target_dict['active_flag']
- width= target_dict['width']
+ width = target_dict['width']
image_base64 = target_dict['image_base64']
image_bytes = base64.b64decode(image_base64)
image = io.BytesIO(image_bytes)
@@ -66,11 +58,18 @@ def get_all_databases() -> Set[VuforiaDatabase]:
target.target_id = target_dict['target_id']
gmt = pytz.timezone('GMT')
# import pdb; pdb.set_trace()
- target.last_modified_date = datetime.datetime.fromordinal(target_dict['last_modified_date_ordinal'])
- target.last_modified_date = target.last_modified_date.replace(tzinfo=gmt)
- delete_date_optional_ordinal = target_dict['delete_date_optional_ordinal']
+ target.last_modified_date = datetime.datetime.fromordinal(
+ target_dict['last_modified_date_ordinal']
+ )
+ target.last_modified_date = target.last_modified_date.replace(
+ tzinfo=gmt
+ )
+ delete_date_optional_ordinal = target_dict[
+ 'delete_date_optional_ordinal']
if delete_date_optional_ordinal:
- target.delete_date = datetime.datetime.fromordinal(delete_date_optional_ordinal)
+ target.delete_date = datetime.datetime.fromordinal(
+ delete_date_optional_ordinal
+ )
target.delete_date = target.delete_date.replace(tzinfo=gmt)
new_database.targets.append(target)
diff --git a/src/_mock_vws_server/vws/_services_validators/__init__.py b/src/_mock_vws_server/vws/_services_validators/__init__.py
index d1158688e..6e7986d18 100644
--- a/src/_mock_vws_server/vws/_services_validators/__init__.py
+++ b/src/_mock_vws_server/vws/_services_validators/__init__.py
@@ -22,6 +22,7 @@
from mock_vws._mock_common import json_dump
from mock_vws.database import VuforiaDatabase
from mock_vws.states import States
+
from .._databases import get_all_databases
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 22a89de5c..271ebec21 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -77,7 +77,10 @@ def __init__(
self.targets: List[Target] = []
self.state = state
- def to_dict(self) -> Dict[str, Union[str, List[Dict[str, Optional[Union[str, int, bool, float]]]]]]:
+ def to_dict(
+ self
+ ) -> Dict[str, Union[str, List[Dict[str, Optional[Union[str, int, bool,
+ float]]]]]]:
targets = [target.to_dict() for target in self.targets]
return {
'database_name': self.database_name,
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 7f842c65e..8de079ddd 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -182,13 +182,16 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
import base64
# import pdb; pdb.set_trace()
if self.delete_date:
- delete_date: Optional[int] = datetime.datetime.toordinal(self.delete_date)
+ delete_date: Optional[int] = datetime.datetime.toordinal(
+ self.delete_date
+ )
else:
delete_date = None
return {
'name': self.name,
'width': self.width,
- 'image_base64': base64.encodestring(self.image.getvalue()).decode(),
+ 'image_base64':
+ base64.encodestring(self.image.getvalue()).decode(),
'active_flag': self.active_flag,
'processing_time_seconds': self._processing_time_seconds,
'application_metadata': self.application_metadata,
From 2d8a9cfcf093cecdab3673a9f1175ed68229da7d Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 00:40:12 +0000
Subject: [PATCH 0064/3455] Update a bunch of lint fixes
---
src/_mock_vws_server/storage/__init__.py | 2 +
src/_mock_vws_server/vws/__init__.py | 139 +++++++++++++++++++++++
2 files changed, 141 insertions(+)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index 6a8b9bdc1..d875e88b2 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -96,3 +96,5 @@ def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
now = datetime.datetime.now(tz=gmt)
target.delete_date = now
return jsonify(target.to_dict()), codes.OK
+
+
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 57b37bad9..369fe4bfc 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -289,3 +289,142 @@ def delete_target(target_id: str) -> Tuple[str, int]:
'result_code': ResultCodes.SUCCESS.value,
}
return json_dump(body), codes.OK
+
+
+@VWS_FLASK_APP.route('/summary', methods=['GET'])
+def database_summary() -> Tuple[str, int]:
+ """
+ Get a database summary report.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Get-a-Database-Summary-Report
+ """
+ body: Dict[str, Union[str, int]] = {}
+
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+ active_images = len(
+ [
+ target for target in database.targets
+ if target.status == TargetStatuses.SUCCESS.value
+ and target.active_flag and not target.delete_date
+ ],
+ )
+
+ failed_images = len(
+ [
+ target for target in database.targets
+ if target.status == TargetStatuses.FAILED.value
+ and not target.delete_date
+ ],
+ )
+
+ inactive_images = len(
+ [
+ target for target in database.targets
+ if target.status == TargetStatuses.SUCCESS.value
+ and not target.active_flag and not target.delete_date
+ ],
+ )
+
+ processing_images = len(
+ [
+ target for target in database.targets
+ if target.status == TargetStatuses.PROCESSING.value
+ and not target.delete_date
+ ],
+ )
+
+ body = {
+ 'result_code': ResultCodes.SUCCESS.value,
+ 'transaction_id': uuid.uuid4().hex,
+ 'name': database.database_name,
+ 'active_images': active_images,
+ 'inactive_images': inactive_images,
+ 'failed_images': failed_images,
+ 'target_quota': 1000,
+ 'total_recos': 0,
+ 'current_month_recos': 0,
+ 'previous_month_recos': 0,
+ 'processing_images': processing_images,
+ 'reco_threshold': 1000,
+ 'request_quota': 100000,
+ # We have ``self.request_count`` but Vuforia always shows 0.
+ # This was not always the case.
+ 'request_usage': 0,
+ }
+ return json_dump(body), codes.OK
+
+@VWS_FLASK_APP.route('/duplicates/', methods=['GET'])
+def get_duplicates(target_id: str) -> Tuple[str, int]:
+ """
+ Get targets which may be considered duplicates of a given target.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Check-for-Duplicate-Targets
+ """
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+ other_targets = set(database.targets) - set([target])
+
+ similar_targets: List[str] = [
+ other.target_id for other in other_targets
+ if Image.open(other.image) == Image.open(target.image) and
+ TargetStatuses.FAILED.value not in (target.status, other.status)
+ and TargetStatuses.PROCESSING.value != other.status
+ and other.active_flag
+ ]
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.SUCCESS.value,
+ 'similar_targets': similar_targets,
+ }
+
+ return json_dump(body), codes.OK
+
+@VWS_FLASK_APP.route('/targets', methods=['GET'])
+def target_list(
+) -> str:
+ """
+ Get a list of all targets.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Get-a-Target-List-for-a-Cloud-Database
+ """
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+ assert isinstance(database, VuforiaDatabase)
+ results = [
+ target.target_id for target in database.targets
+ if not target.delete_date
+ ]
+
+ body: Dict[str, Union[str, List[str]]] = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.SUCCESS.value,
+ 'results': results,
+ }
+ return json_dump(body)
From 4ee76c2d3ed02f07d7a91c4cf383458c8a70839e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 00:43:31 +0000
Subject: [PATCH 0065/3455] Fix a few things
---
src/_mock_vws_server/vws/__init__.py | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 369fe4bfc..0e6fa9602 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -3,7 +3,8 @@
import io
import json
import uuid
-from typing import Dict, Tuple
+from typing import Dict, Tuple, Union, List
+from PIL import Image
import requests
from flask import Flask, Response, request
@@ -381,6 +382,9 @@ def get_duplicates(target_id: str) -> Tuple[str, int]:
)
assert isinstance(database, VuforiaDatabase)
+ [target] = [
+ target for target in database.targets if target.target_id == target_id
+ ]
other_targets = set(database.targets) - set([target])
similar_targets: List[str] = [
@@ -401,7 +405,7 @@ def get_duplicates(target_id: str) -> Tuple[str, int]:
@VWS_FLASK_APP.route('/targets', methods=['GET'])
def target_list(
-) -> str:
+) -> Tuple[str, int]:
"""
Get a list of all targets.
@@ -427,4 +431,4 @@ def target_list(
'result_code': ResultCodes.SUCCESS.value,
'results': results,
}
- return json_dump(body)
+ return json_dump(body), codes.OK
From 4fd776a26846c8e6704603217f4f311f5b190659 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 01:50:45 +0000
Subject: [PATCH 0066/3455] Fix a few things
---
src/_mock_vws_server/vws/__init__.py | 109 +++++++++++++++++++++++++
src/_mock_vws_server/vws/_databases.py | 6 ++
src/mock_vws/target.py | 1 +
3 files changed, 116 insertions(+)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 0e6fa9602..c8a4a6124 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -1,3 +1,6 @@
+import random
+import datetime
+import pytz
import base64
import email.utils
import io
@@ -432,3 +435,109 @@ def target_list(
'results': results,
}
return json_dump(body), codes.OK
+
+
+# @route(
+# path_pattern=f'/targets/{_TARGET_ID_PATTERN}',
+# http_methods=[PUT],
+# optional_keys={
+# 'active_flag',
+# 'application_metadata',
+# 'image',
+# 'name',
+# 'width',
+# },
+# )
+@VWS_FLASK_APP.route('/targets/', methods=['PUT'])
+# TODO
+# @JSON_SCHEMA.validate(UPDATE_TARGET_SCHEMA)
+def update_target(target_id: str) -> Tuple[str, int]:
+ """
+ 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
+ """
+ # We do not use ``request.get_json(force=True)`` because this only works when the content
+ # type is given as ``application/json``.
+ request_json = json.loads(request.data)
+ body: Dict[str, str] = {}
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+ [target] = [
+ target for target in database.targets if target.target_id == target_id
+ ]
+
+ if target.status != TargetStatuses.SUCCESS.value:
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.TARGET_STATUS_NOT_SUCCESS.value,
+ }
+ return json_dump(body), codes.FORBIDDEN
+
+ 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,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+ 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,
+ }
+ return json_dump(body), codes.BAD_REQUEST
+ 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
+ ):
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.TARGET_NAME_EXIST.value,
+ }
+ return json_dump(body), codes.FORBIDDEN
+ 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 = pytz.timezone('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), codes.OK
diff --git a/src/_mock_vws_server/vws/_databases.py b/src/_mock_vws_server/vws/_databases.py
index b07eb8089..eecda30c1 100644
--- a/src/_mock_vws_server/vws/_databases.py
+++ b/src/_mock_vws_server/vws/_databases.py
@@ -64,6 +64,12 @@ def get_all_databases() -> Set[VuforiaDatabase]:
target.last_modified_date = target.last_modified_date.replace(
tzinfo=gmt
)
+ target.upload_date = datetime.datetime.fromordinal(
+ target_dict['upload_date_ordinal']
+ )
+ target.upload_date = target.upload_date.replace(
+ tzinfo=gmt
+ )
delete_date_optional_ordinal = target_dict[
'delete_date_optional_ordinal']
if delete_date_optional_ordinal:
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 8de079ddd..8cc839e10 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -198,4 +198,5 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
'target_id': self.target_id,
'last_modified_date_ordinal': self.last_modified_date.toordinal(),
'delete_date_optional_ordinal': delete_date,
+ 'upload_date_ordinal': self.upload_date.toordinal(),
}
From 803629dad580d6894ccf3c1ae143452b50e7e1c5 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 01:52:26 +0000
Subject: [PATCH 0067/3455] Fix a few things
---
src/_mock_vws_server/vws/__init__.py | 1 -
src/_mock_vws_server/vws/_databases.py | 1 -
src/mock_vws/target.py | 3 +--
3 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index c8a4a6124..90a7ac6b0 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -199,7 +199,6 @@ def add_target() -> Tuple[str, int]:
json=new_target.to_dict(),
)
- # import pdb; pdb.set_trace()
body = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.TARGET_CREATED.value,
diff --git a/src/_mock_vws_server/vws/_databases.py b/src/_mock_vws_server/vws/_databases.py
index eecda30c1..90eecee07 100644
--- a/src/_mock_vws_server/vws/_databases.py
+++ b/src/_mock_vws_server/vws/_databases.py
@@ -57,7 +57,6 @@ def get_all_databases() -> Set[VuforiaDatabase]:
)
target.target_id = target_dict['target_id']
gmt = pytz.timezone('GMT')
- # import pdb; pdb.set_trace()
target.last_modified_date = datetime.datetime.fromordinal(
target_dict['last_modified_date_ordinal']
)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 8cc839e10..0904b86a4 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -11,6 +11,7 @@
import pytz
from PIL import Image, ImageStat
+import base64
from mock_vws._constants import TargetStatuses
@@ -179,8 +180,6 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
#
# as can e.g. processing time... maybe use dataclass but then
# https://github.com/agronholm/sphinx-autodoc-typehints/issues/123
- import base64
- # import pdb; pdb.set_trace()
if self.delete_date:
delete_date: Optional[int] = datetime.datetime.toordinal(
self.delete_date
From ded38f950224f184c1ee671a8bbc999a6ac5a3fd Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 01:53:09 +0000
Subject: [PATCH 0068/3455] Fix a few things
---
src/_mock_vws_server/storage/__init__.py | 2 --
src/_mock_vws_server/vws/__init__.py | 20 ++++++++++----------
src/_mock_vws_server/vws/_databases.py | 4 +---
src/mock_vws/target.py | 2 +-
4 files changed, 12 insertions(+), 16 deletions(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index d875e88b2..6a8b9bdc1 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -96,5 +96,3 @@ def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
now = datetime.datetime.now(tz=gmt)
target.delete_date = now
return jsonify(target.to_dict()), codes.OK
-
-
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 90a7ac6b0..527e830f1 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -1,17 +1,17 @@
-import random
-import datetime
-import pytz
import base64
+import datetime
import email.utils
import io
import json
+import random
import uuid
-from typing import Dict, Tuple, Union, List
-from PIL import Image
+from typing import Dict, List, Tuple, Union
+import pytz
import requests
from flask import Flask, Response, request
from flask_json_schema import JsonSchema, JsonValidationError
+from PIL import Image
from requests import codes
from mock_vws._constants import ResultCodes, TargetStatuses
@@ -366,6 +366,7 @@ def database_summary() -> Tuple[str, int]:
}
return json_dump(body), codes.OK
+
@VWS_FLASK_APP.route('/duplicates/', methods=['GET'])
def get_duplicates(target_id: str) -> Tuple[str, int]:
"""
@@ -392,9 +393,8 @@ def get_duplicates(target_id: str) -> Tuple[str, int]:
similar_targets: List[str] = [
other.target_id for other in other_targets
if Image.open(other.image) == Image.open(target.image) and
- TargetStatuses.FAILED.value not in (target.status, other.status)
- and TargetStatuses.PROCESSING.value != other.status
- and other.active_flag
+ TargetStatuses.FAILED.value not in (target.status, other.status) and
+ TargetStatuses.PROCESSING.value != other.status and other.active_flag
]
body = {
@@ -405,9 +405,9 @@ def get_duplicates(target_id: str) -> Tuple[str, int]:
return json_dump(body), codes.OK
+
@VWS_FLASK_APP.route('/targets', methods=['GET'])
-def target_list(
-) -> Tuple[str, int]:
+def target_list() -> Tuple[str, int]:
"""
Get a list of all targets.
diff --git a/src/_mock_vws_server/vws/_databases.py b/src/_mock_vws_server/vws/_databases.py
index 90eecee07..487cc41b3 100644
--- a/src/_mock_vws_server/vws/_databases.py
+++ b/src/_mock_vws_server/vws/_databases.py
@@ -66,9 +66,7 @@ def get_all_databases() -> Set[VuforiaDatabase]:
target.upload_date = datetime.datetime.fromordinal(
target_dict['upload_date_ordinal']
)
- target.upload_date = target.upload_date.replace(
- tzinfo=gmt
- )
+ target.upload_date = target.upload_date.replace(tzinfo=gmt)
delete_date_optional_ordinal = target_dict[
'delete_date_optional_ordinal']
if delete_date_optional_ordinal:
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 0904b86a4..e103df3ae 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -2,6 +2,7 @@
A fake implementation of a target for the Vuforia Web Services API.
"""
+import base64
import datetime
import io
import random
@@ -11,7 +12,6 @@
import pytz
from PIL import Image, ImageStat
-import base64
from mock_vws._constants import TargetStatuses
From 600dd467e18cd4e52168e569abea30367e39682b Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 02:06:18 +0000
Subject: [PATCH 0069/3455] Progress towards update
---
src/_mock_vws_server/storage/__init__.py | 43 ++++++++
src/_mock_vws_server/vws/__init__.py | 119 ++++++++++-------------
2 files changed, 96 insertions(+), 66 deletions(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index 6a8b9bdc1..a63fe25ab 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -96,3 +96,46 @@ def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
now = datetime.datetime.now(tz=gmt)
target.delete_date = now
return jsonify(target.to_dict()), codes.OK
+
+
+@STORAGE_FLASK_APP.route(
+ '/databases//targets//',
+ methods=['PUT'],
+)
+def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
+ [database] = [
+ database for database in VUFORIA_DATABASES
+ if database.database_name == database_name
+ ]
+ [target] = [
+ target for target in database.targets if target.target_id == target_id
+ ]
+
+ if 'name' in request.json():
+ target.name = request.json()['name']
+
+ if 'active_flag' in request.json():
+ target.active_flag = bool(request.json()['active_flag'])
+
+ if 'width' in request.json():
+ target.width = float(request.json()['width'])
+
+ if 'application_metadata' in request.json():
+ target.application_metadata = request.json()['application_metadata']
+
+ if 'image' in request.json():
+ decoded = base64.b64decode(request.json()['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 = pytz.timezone('GMT')
+ now = datetime.datetime.now(tz=gmt)
+ target.last_modified_date = now
+
+ return jsonify(target.to_dict()), codes.OK
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 527e830f1..a2439079a 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -5,16 +5,23 @@
import json
import random
import uuid
-from typing import Dict, List, Tuple, Union
+from typing import Dict
+from typing import List
+from typing import Tuple
+from typing import Union
import pytz
import requests
-from flask import Flask, Response, request
-from flask_json_schema import JsonSchema, JsonValidationError
+from flask import Flask
+from flask import Response
+from flask import request
+from flask_json_schema import JsonSchema
+from flask_json_schema import JsonValidationError
from PIL import Image
from requests import codes
-from mock_vws._constants import ResultCodes, TargetStatuses
+from mock_vws._constants import ResultCodes
+from mock_vws._constants import TargetStatuses
from mock_vws._database_matchers import get_database_matching_server_keys
from mock_vws._mock_common import json_dump
from mock_vws.database import VuforiaDatabase
@@ -22,43 +29,36 @@
from ._constants import STORAGE_BASE_URL
from ._databases import get_all_databases
-from ._services_validators import (
- validate_active_flag,
- validate_metadata_encoding,
- validate_metadata_size,
- validate_metadata_type,
- validate_name_characters_in_range,
- validate_name_length,
- validate_name_type,
- validate_not_invalid_json,
- validate_project_state,
- validate_width,
-)
-from ._services_validators.auth_validators import (
- validate_auth_header_exists,
- validate_auth_header_has_signature,
-)
-from ._services_validators.content_length_validators import (
- validate_content_length_header_is_int,
- validate_content_length_header_not_too_large,
- validate_content_length_header_not_too_small,
-)
-from ._services_validators.content_type_validators import (
- validate_content_type_header_given,
-)
-from ._services_validators.date_validators import (
- validate_date_format,
- validate_date_header_given,
- validate_date_in_range,
-)
-from ._services_validators.image_validators import (
- validate_image_color_space,
- validate_image_data_type,
- validate_image_encoding,
- validate_image_format,
- validate_image_is_image,
- validate_image_size,
-)
+from ._services_validators import validate_active_flag
+from ._services_validators import validate_metadata_encoding
+from ._services_validators import validate_metadata_size
+from ._services_validators import validate_metadata_type
+from ._services_validators import validate_name_characters_in_range
+from ._services_validators import validate_name_length
+from ._services_validators import validate_name_type
+from ._services_validators import validate_not_invalid_json
+from ._services_validators import validate_project_state
+from ._services_validators import validate_width
+from ._services_validators.auth_validators import validate_auth_header_exists
+from ._services_validators.auth_validators import \
+ validate_auth_header_has_signature
+from ._services_validators.content_length_validators import \
+ validate_content_length_header_is_int
+from ._services_validators.content_length_validators import \
+ validate_content_length_header_not_too_large
+from ._services_validators.content_length_validators import \
+ validate_content_length_header_not_too_small
+from ._services_validators.content_type_validators import \
+ validate_content_type_header_given
+from ._services_validators.date_validators import validate_date_format
+from ._services_validators.date_validators import validate_date_header_given
+from ._services_validators.date_validators import validate_date_in_range
+from ._services_validators.image_validators import validate_image_color_space
+from ._services_validators.image_validators import validate_image_data_type
+from ._services_validators.image_validators import validate_image_encoding
+from ._services_validators.image_validators import validate_image_format
+from ._services_validators.image_validators import validate_image_is_image
+from ._services_validators.image_validators import validate_image_size
VWS_FLASK_APP = Flask(__name__)
JSON_SCHEMA = JsonSchema(VWS_FLASK_APP)
@@ -191,9 +191,6 @@ def add_target() -> Tuple[str, int]:
application_metadata=request_json.get('application_metadata'),
)
- # TODO make this work
- # database.targets.append(new_target)
- # --->
requests.post(
url=f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets',
json=new_target.to_dict(),
@@ -366,7 +363,6 @@ def database_summary() -> Tuple[str, int]:
}
return json_dump(body), codes.OK
-
@VWS_FLASK_APP.route('/duplicates/', methods=['GET'])
def get_duplicates(target_id: str) -> Tuple[str, int]:
"""
@@ -393,8 +389,9 @@ def get_duplicates(target_id: str) -> Tuple[str, int]:
similar_targets: List[str] = [
other.target_id for other in other_targets
if Image.open(other.image) == Image.open(target.image) and
- TargetStatuses.FAILED.value not in (target.status, other.status) and
- TargetStatuses.PROCESSING.value != other.status and other.active_flag
+ TargetStatuses.FAILED.value not in (target.status, other.status)
+ and TargetStatuses.PROCESSING.value != other.status
+ and other.active_flag
]
body = {
@@ -405,9 +402,9 @@ def get_duplicates(target_id: str) -> Tuple[str, int]:
return json_dump(body), codes.OK
-
@VWS_FLASK_APP.route('/targets', methods=['GET'])
-def target_list() -> Tuple[str, int]:
+def target_list(
+) -> Tuple[str, int]:
"""
Get a list of all targets.
@@ -482,8 +479,9 @@ def update_target(target_id: str) -> Tuple[str, int]:
}
return json_dump(body), codes.FORBIDDEN
+ update_values = {}
if 'width' in request_json:
- target.width = request_json['width']
+ update_values['width'] = request_json['width']
if 'active_flag' in request_json:
active_flag = request_json['active_flag']
@@ -493,7 +491,7 @@ def update_target(target_id: str) -> Tuple[str, int]:
'result_code': ResultCodes.FAIL.value,
}
return json_dump(body), codes.BAD_REQUEST
- target.active_flag = active_flag
+ update_values['active_flag'] = active_flag
if 'application_metadata' in request_json:
if request_json['application_metadata'] is None:
@@ -503,7 +501,7 @@ def update_target(target_id: str) -> Tuple[str, int]:
}
return json_dump(body), codes.BAD_REQUEST
application_metadata = request_json['application_metadata']
- target.application_metadata = application_metadata
+ update_values['application_metadata'] = application_metadata
if 'name' in request_json:
name = request_json['name']
@@ -517,23 +515,12 @@ def update_target(target_id: str) -> Tuple[str, int]:
'result_code': ResultCodes.TARGET_NAME_EXIST.value,
}
return json_dump(body), codes.FORBIDDEN
- target.name = name
+ update_values['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 = pytz.timezone('GMT')
- now = datetime.datetime.now(tz=gmt)
- target.last_modified_date = now
+ update_values['image'] = image
+
body = {
'result_code': ResultCodes.SUCCESS.value,
From 6b3ef8cacd6d142289ca40beba973191fe7e39a9 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 02:10:54 +0000
Subject: [PATCH 0070/3455] Progress towards update
---
src/_mock_vws_server/storage/__init__.py | 23 ++++++++++++-----------
src/_mock_vws_server/vws/__init__.py | 5 +++++
2 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index a63fe25ab..94befb6ff 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -2,6 +2,7 @@
import datetime
import io
from typing import List, Tuple
+import random
import pytz
from flask import Flask, jsonify, request
@@ -99,7 +100,7 @@ def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
@STORAGE_FLASK_APP.route(
- '/databases//targets//',
+ '/databases//targets/',
methods=['PUT'],
)
def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
@@ -111,20 +112,20 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
target for target in database.targets if target.target_id == target_id
]
- if 'name' in request.json():
- target.name = request.json()['name']
+ if 'name' in request.json:
+ target.name = request.json['name']
- if 'active_flag' in request.json():
- target.active_flag = bool(request.json()['active_flag'])
+ if 'active_flag' in request.json:
+ target.active_flag = bool(request.json['active_flag'])
- if 'width' in request.json():
- target.width = float(request.json()['width'])
+ if 'width' in request.json:
+ target.width = float(request.json['width'])
- if 'application_metadata' in request.json():
- target.application_metadata = request.json()['application_metadata']
+ if 'application_metadata' in request.json:
+ target.application_metadata = request.json['application_metadata']
- if 'image' in request.json():
- decoded = base64.b64decode(request.json()['image'])
+ if 'image' in request.json:
+ decoded = base64.b64decode(request.json['image'])
image_file = io.BytesIO(decoded)
target.image = image_file
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index a2439079a..2e352045e 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -521,6 +521,11 @@ def update_target(target_id: str) -> Tuple[str, int]:
image = request_json['image']
update_values['image'] = image
+ requests.put(
+ url=f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/{target_id}',
+ json=update_values,
+ )
+
body = {
'result_code': ResultCodes.SUCCESS.value,
From fd61b52c33147f4ad1fd73d4d2829f4fa337d4a7 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 17:51:20 +0000
Subject: [PATCH 0071/3455] Progress towards update
---
src/mock_vws/target.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index e103df3ae..defb3c203 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -78,7 +78,6 @@ def __init__( # pylint: disable=too-many-arguments
target was deleted.
"""
self.name = name
- # TODO UNDO
self.target_id = uuid.uuid4().hex
self.active_flag = active_flag
self.width = width
From 7e838d53a464b9cfab6f214cd300b1dcba0da6b2 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 17:54:09 +0000
Subject: [PATCH 0072/3455] Progress towards query
---
src/_mock_vws_server/vwq/__init__.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/_mock_vws_server/vwq/__init__.py b/src/_mock_vws_server/vwq/__init__.py
index 3a074733c..eb6791399 100644
--- a/src/_mock_vws_server/vwq/__init__.py
+++ b/src/_mock_vws_server/vwq/__init__.py
@@ -1,3 +1,9 @@
from flask import Flask
+from requests import codes
+from typing import Tuple
CLOUDRECO_FLASK_APP = Flask(__name__)
+
+@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
+def query() -> Tuple[str, int]:
+ return '', codes.OK
From b475a803695d5f08b8a80f99a317468eff4e1f97 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 17:57:31 +0000
Subject: [PATCH 0073/3455] Remove some pdbs
---
spelling_private_dict.txt | 1 +
src/_mock_vws_server/storage/__init__.py | 5 -----
tests/mock_vws/fixtures/vuforia_backends.py | 1 -
3 files changed, 1 insertion(+), 6 deletions(-)
diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt
index 3db3bbbdf..35f00cffb 100644
--- a/spelling_private_dict.txt
+++ b/spelling_private_dict.txt
@@ -17,6 +17,7 @@ chunked
cmyk
connectionerror
customizable
+dataclass
datetime
decodable
dev
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index 94befb6ff..ba531c5ba 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -19,8 +19,6 @@
@STORAGE_FLASK_APP.route('/reset', methods=['POST'])
def reset() -> Tuple[str, int]:
- # import pdb; pdb.set_trace()
-
VUFORIA_DATABASES.clear()
return '', codes.OK
@@ -62,7 +60,6 @@ def create_target(database_name: str) -> Tuple[str, int]:
if database.database_name == database_name
]
image_base64 = request.json['image_base64']
- # import pdb; pdb.set_trace()
image_bytes = base64.b64decode(image_base64)
image = io.BytesIO(image_bytes)
target = Target(
@@ -73,10 +70,8 @@ def create_target(database_name: str) -> Tuple[str, int]:
processing_time_seconds=request.json['processing_time_seconds'],
application_metadata=request.json['application_metadata'],
)
- # import pdb; pdb.set_trace()
target.target_id = request.json['target_id']
database.targets.append(target)
- # import pdb; pdb.set_trace(
return jsonify(target.to_dict()), codes.CREATED
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index 46d6d2b80..8c46feece 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -117,7 +117,6 @@ def _enable_use_docker_in_memory(
working_database: VuforiaDatabase,
inactive_database: VuforiaDatabase,
) -> Generator:
- # import pdb; pdb.set_trace()
with requests_mock.Mocker(real_http=False) as mock:
add_flask_app_to_mock(
mock_obj=mock,
From ab748eed05df8a1bb077aba82cc9cdd70cc26ad5 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 26 Feb 2020 18:28:27 +0000
Subject: [PATCH 0074/3455] Fix some lint issues
---
src/_mock_vws_server/storage/__init__.py | 2 +-
src/_mock_vws_server/vwq/__init__.py | 4 +-
src/_mock_vws_server/vws/__init__.py | 99 ++++++++++++------------
3 files changed, 52 insertions(+), 53 deletions(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index ba531c5ba..019fd3e8a 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -1,8 +1,8 @@
import base64
import datetime
import io
-from typing import List, Tuple
import random
+from typing import List, Tuple
import pytz
from flask import Flask, jsonify, request
diff --git a/src/_mock_vws_server/vwq/__init__.py b/src/_mock_vws_server/vwq/__init__.py
index eb6791399..184a64db2 100644
--- a/src/_mock_vws_server/vwq/__init__.py
+++ b/src/_mock_vws_server/vwq/__init__.py
@@ -1,9 +1,11 @@
+from typing import Tuple
+
from flask import Flask
from requests import codes
-from typing import Tuple
CLOUDRECO_FLASK_APP = Flask(__name__)
+
@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
def query() -> Tuple[str, int]:
return '', codes.OK
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 2e352045e..8efb727d5 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -1,27 +1,17 @@
import base64
-import datetime
import email.utils
import io
import json
-import random
import uuid
-from typing import Dict
-from typing import List
-from typing import Tuple
-from typing import Union
+from typing import Dict, List, Tuple, Union
-import pytz
import requests
-from flask import Flask
-from flask import Response
-from flask import request
-from flask_json_schema import JsonSchema
-from flask_json_schema import JsonValidationError
+from flask import Flask, Response, request
+from flask_json_schema import JsonSchema, JsonValidationError
from PIL import Image
from requests import codes
-from mock_vws._constants import ResultCodes
-from mock_vws._constants import TargetStatuses
+from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_server_keys
from mock_vws._mock_common import json_dump
from mock_vws.database import VuforiaDatabase
@@ -29,36 +19,43 @@
from ._constants import STORAGE_BASE_URL
from ._databases import get_all_databases
-from ._services_validators import validate_active_flag
-from ._services_validators import validate_metadata_encoding
-from ._services_validators import validate_metadata_size
-from ._services_validators import validate_metadata_type
-from ._services_validators import validate_name_characters_in_range
-from ._services_validators import validate_name_length
-from ._services_validators import validate_name_type
-from ._services_validators import validate_not_invalid_json
-from ._services_validators import validate_project_state
-from ._services_validators import validate_width
-from ._services_validators.auth_validators import validate_auth_header_exists
-from ._services_validators.auth_validators import \
- validate_auth_header_has_signature
-from ._services_validators.content_length_validators import \
- validate_content_length_header_is_int
-from ._services_validators.content_length_validators import \
- validate_content_length_header_not_too_large
-from ._services_validators.content_length_validators import \
- validate_content_length_header_not_too_small
-from ._services_validators.content_type_validators import \
- validate_content_type_header_given
-from ._services_validators.date_validators import validate_date_format
-from ._services_validators.date_validators import validate_date_header_given
-from ._services_validators.date_validators import validate_date_in_range
-from ._services_validators.image_validators import validate_image_color_space
-from ._services_validators.image_validators import validate_image_data_type
-from ._services_validators.image_validators import validate_image_encoding
-from ._services_validators.image_validators import validate_image_format
-from ._services_validators.image_validators import validate_image_is_image
-from ._services_validators.image_validators import validate_image_size
+from ._services_validators import (
+ validate_active_flag,
+ validate_metadata_encoding,
+ validate_metadata_size,
+ validate_metadata_type,
+ validate_name_characters_in_range,
+ validate_name_length,
+ validate_name_type,
+ validate_not_invalid_json,
+ validate_project_state,
+ validate_width,
+)
+from ._services_validators.auth_validators import (
+ validate_auth_header_exists,
+ validate_auth_header_has_signature,
+)
+from ._services_validators.content_length_validators import (
+ validate_content_length_header_is_int,
+ validate_content_length_header_not_too_large,
+ validate_content_length_header_not_too_small,
+)
+from ._services_validators.content_type_validators import (
+ validate_content_type_header_given,
+)
+from ._services_validators.date_validators import (
+ validate_date_format,
+ validate_date_header_given,
+ validate_date_in_range,
+)
+from ._services_validators.image_validators import (
+ validate_image_color_space,
+ validate_image_data_type,
+ validate_image_encoding,
+ validate_image_format,
+ validate_image_is_image,
+ validate_image_size,
+)
VWS_FLASK_APP = Flask(__name__)
JSON_SCHEMA = JsonSchema(VWS_FLASK_APP)
@@ -363,6 +360,7 @@ def database_summary() -> Tuple[str, int]:
}
return json_dump(body), codes.OK
+
@VWS_FLASK_APP.route('/duplicates/', methods=['GET'])
def get_duplicates(target_id: str) -> Tuple[str, int]:
"""
@@ -389,9 +387,8 @@ def get_duplicates(target_id: str) -> Tuple[str, int]:
similar_targets: List[str] = [
other.target_id for other in other_targets
if Image.open(other.image) == Image.open(target.image) and
- TargetStatuses.FAILED.value not in (target.status, other.status)
- and TargetStatuses.PROCESSING.value != other.status
- and other.active_flag
+ TargetStatuses.FAILED.value not in (target.status, other.status) and
+ TargetStatuses.PROCESSING.value != other.status and other.active_flag
]
body = {
@@ -402,9 +399,9 @@ def get_duplicates(target_id: str) -> Tuple[str, int]:
return json_dump(body), codes.OK
+
@VWS_FLASK_APP.route('/targets', methods=['GET'])
-def target_list(
-) -> Tuple[str, int]:
+def target_list() -> Tuple[str, int]:
"""
Get a list of all targets.
@@ -522,11 +519,11 @@ def update_target(target_id: str) -> Tuple[str, int]:
update_values['image'] = image
requests.put(
- url=f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/{target_id}',
+ url=
+ f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/{target_id}',
json=update_values,
)
-
body = {
'result_code': ResultCodes.SUCCESS.value,
'transaction_id': uuid.uuid4().hex,
From c91b71fcc2ca48c53bcd1fcf4cbd27d2aed84d53 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 29 Feb 2020 14:17:39 +0000
Subject: [PATCH 0075/3455] Progress towards working query
---
src/_mock_vws_server/vwq/__init__.py | 201 +++++++++++-
src/_mock_vws_server/vwq/_constants.py | 49 +++
.../vwq/_database_matchers.py | 156 +++++++++
src/_mock_vws_server/vwq/_mock_common.py | 132 ++++++++
.../vwq/_query_validators/__init__.py | 302 ++++++++++++++++++
.../vwq/_query_validators/auth_validators.py | 210 ++++++++++++
.../content_length_validators.py | 121 +++++++
.../vwq/_query_validators/date_validators.py | 157 +++++++++
.../vwq/_query_validators/image_validators.py | 270 ++++++++++++++++
.../resources/query_out_of_bounds_response | 34 ++
10 files changed, 1630 insertions(+), 2 deletions(-)
create mode 100644 src/_mock_vws_server/vwq/_constants.py
create mode 100644 src/_mock_vws_server/vwq/_database_matchers.py
create mode 100644 src/_mock_vws_server/vwq/_mock_common.py
create mode 100644 src/_mock_vws_server/vwq/_query_validators/__init__.py
create mode 100644 src/_mock_vws_server/vwq/_query_validators/auth_validators.py
create mode 100644 src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
create mode 100644 src/_mock_vws_server/vwq/_query_validators/date_validators.py
create mode 100644 src/_mock_vws_server/vwq/_query_validators/image_validators.py
create mode 100644 src/_mock_vws_server/vwq/_query_validators/resources/query_out_of_bounds_response
diff --git a/src/_mock_vws_server/vwq/__init__.py b/src/_mock_vws_server/vwq/__init__.py
index 184a64db2..9e2c95969 100644
--- a/src/_mock_vws_server/vwq/__init__.py
+++ b/src/_mock_vws_server/vwq/__init__.py
@@ -1,11 +1,208 @@
from typing import Tuple
+import base64
+import cgi
+import datetime
+import io
+import uuid
+from pathlib import Path
+from typing import Any, Callable, Dict, List, Set, Union
+from flask import request
+# TODO move this
+from ..vws._databases import get_all_databases
-from flask import Flask
+import pytz
from requests import codes
+from requests_mock import POST
+
+from flask import Flask, Response
+from requests import codes
+from mock_vws._base64_decoding import decode_base64
+from mock_vws._constants import ResultCodes, TargetStatuses
+from mock_vws._database_matchers import get_database_matching_client_keys
+from mock_vws._mock_common import (
+ Route,
+ json_dump,
+ parse_multipart,
+ set_content_length_header,
+ set_date_header,
+)
+from mock_vws.database import VuforiaDatabase
+
+from ._query_validators import (
+ validate_accept_header,
+ validate_content_type_header,
+ validate_extra_fields,
+ validate_include_target_data,
+ validate_max_num_results,
+ validate_project_state,
+)
+from ._query_validators.auth_validators import (
+ validate_auth_header_exists,
+ validate_auth_header_has_signature,
+ validate_auth_header_number_of_parts,
+ validate_authorization,
+ validate_client_key_exists,
+)
+from ._query_validators.content_length_validators import (
+ validate_content_length_header_is_int,
+ validate_content_length_header_not_too_large,
+ validate_content_length_header_not_too_small,
+)
+from ._query_validators.date_validators import (
+ validate_date_format,
+ validate_date_header_given,
+ validate_date_in_range,
+)
+from ._query_validators.image_validators import (
+ validate_image_dimensions,
+ validate_image_field_given,
+ validate_image_file_size,
+ validate_image_format,
+ validate_image_is_image,
+)
CLOUDRECO_FLASK_APP = Flask(__name__)
@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
def query() -> Tuple[str, int]:
- return '', codes.OK
+ body_file = io.BytesIO(request.data)
+
+ _, pdict = cgi.parse_header(request.headers['Content-Type'])
+ parsed = 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] = parsed['image']
+ gmt = pytz.timezone('GMT')
+ now = datetime.datetime.now(tz=gmt)
+
+ processing_timedelta = datetime.timedelta(
+ # TODO add this back
+ # seconds=self._query_processes_deletion_seconds,
+ seconds=0.2,
+ )
+
+ recognition_timedelta = datetime.timedelta(
+ # TODO add this back
+ # seconds=self._query_recognizes_deletion_seconds,
+ seconds=0.2,
+ )
+
+ databases = get_all_databases()
+
+ database = get_database_matching_client_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+
+ matching_targets = [
+ target for target in database.targets
+ if target.image.getvalue() == 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
+ or active_matching_targets_delete_processing
+ ):
+ # We return an example 500 response.
+ # Each response given by Vuforia is different.
+ #
+ # Sometimes Vuforia will ignore matching targets with the
+ # processing status, but we choose to:
+ # * Do the most unexpected thing.
+ # * Be consistent with every response.
+ resources_dir = Path(__file__).parent / 'resources'
+ filename = 'match_processing_response'
+ match_processing_resp_file = resources_dir / filename
+ cache_control = 'must-revalidate,no-cache,no-store'
+ # TODO remove legacy
+ # context.headers['Cache-Control'] = cache_control
+ content_type = 'text/html; charset=ISO-8859-1'
+ # TODO remove legacy
+ # context.headers['Content-Type'] = content_type
+ return (
+ Path(match_processing_resp_file).read_text(),
+ codes.INTERNAL_SERVER_ERROR,
+ {'Cache-Control': cache_control, 'Content-Type': content_type},
+ )
+
+ 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)
+
+ body = {
+ 'result_code': ResultCodes.SUCCESS.value,
+ 'results': results[:int(max_num_results)],
+ 'query_id': uuid.uuid4().hex,
+ }
+
+ value = json_dump(body)
+ return value, codes.OK
diff --git a/src/_mock_vws_server/vwq/_constants.py b/src/_mock_vws_server/vwq/_constants.py
new file mode 100644
index 000000000..cbba98ffa
--- /dev/null
+++ b/src/_mock_vws_server/vwq/_constants.py
@@ -0,0 +1,49 @@
+"""
+Constants used to make the VWS mock.
+"""
+
+from enum import Enum
+
+
+class ResultCodes(Enum):
+ """
+ Constants representing various VWS result codes.
+
+ See
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Interperete-VWS-API-Result-Codes
+
+ Some codes here are not documented in the above link.
+ """
+
+ SUCCESS = 'Success'
+ TARGET_CREATED = 'TargetCreated'
+ AUTHENTICATION_FAILURE = 'AuthenticationFailure'
+ REQUEST_TIME_TOO_SKEWED = 'RequestTimeTooSkewed'
+ TARGET_NAME_EXIST = 'TargetNameExist'
+ UNKNOWN_TARGET = 'UnknownTarget'
+ BAD_IMAGE = 'BadImage'
+ IMAGE_TOO_LARGE = 'ImageTooLarge'
+ METADATA_TOO_LARGE = 'MetadataTooLarge'
+ # The documentation says "Start date is after the end date" but, at the
+ # time of writing, I do not know how to trigger that, therefore this is not
+ # tested.
+ DATE_RANGE_ERROR = 'DateRangeError'
+ FAIL = 'Fail'
+ TARGET_STATUS_PROCESSING = 'TargetStatusProcessing'
+ REQUEST_QUOTA_REACHED = 'RequestQuotaReached'
+ TARGET_STATUS_NOT_SUCCESS = 'TargetStatusNotSuccess'
+ PROJECT_INACTIVE = 'ProjectInactive'
+ INACTIVE_PROJECT = 'InactiveProject'
+
+
+class TargetStatuses(Enum):
+ """
+ Constants representing VWS target statuses.
+
+ See the 'status' field in
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Retrieve-a-Target-Record
+ """
+
+ PROCESSING = 'processing'
+ SUCCESS = 'success'
+ FAILED = 'failed'
diff --git a/src/_mock_vws_server/vwq/_database_matchers.py b/src/_mock_vws_server/vwq/_database_matchers.py
new file mode 100644
index 000000000..0edb5890e
--- /dev/null
+++ b/src/_mock_vws_server/vwq/_database_matchers.py
@@ -0,0 +1,156 @@
+"""
+Helpers for getting databases which match keys given in requests.
+"""
+
+import base64
+import hashlib
+import hmac
+from typing import Dict, Iterable, Optional
+
+from mock_vws.database import VuforiaDatabase
+
+
+def _compute_hmac_base64(key: bytes, data: bytes) -> bytes:
+ """
+ Return the Base64 encoded HMAC-SHA1 hash of the given `data` using the
+ provided `key`.
+ """
+ hashed = hmac.new(key=key, msg=None, digestmod=hashlib.sha1)
+ hashed.update(msg=data)
+ return base64.b64encode(s=hashed.digest())
+
+
+def _authorization_header( # pylint: disable=too-many-arguments
+ access_key: str,
+ secret_key: str,
+ method: str,
+ content: bytes,
+ content_type: str,
+ date: str,
+ request_path: str,
+) -> str:
+ """
+ Return an `Authorization` header which can be used for a request made to
+ the VWS API with the given attributes.
+
+ Args:
+ access_key: A VWS server or client access key.
+ secret_key: A VWS server or client secret key.
+ method: The HTTP method which will be used in the request.
+ content: The request body which will be used in the request.
+ content_type: The `Content-Type` header which will be used in the
+ request.
+ date: The current date which must exactly match the date sent in the
+ `Date` header.
+ request_path: The path to the endpoint which will be used in the
+ request.
+
+ Returns:
+ An `Authorization` header which can be used for a request made to the
+ VWS API with the given attributes.
+ """
+ hashed = hashlib.md5()
+ hashed.update(content)
+ content_md5_hex = hashed.hexdigest()
+
+ components_to_sign = [
+ method,
+ content_md5_hex,
+ content_type,
+ date,
+ request_path,
+ ]
+ string_to_sign = '\n'.join(components_to_sign)
+ signature = _compute_hmac_base64(
+ key=secret_key.encode(),
+ data=bytes(
+ string_to_sign,
+ encoding='utf-8',
+ ),
+ )
+ auth_header = f'VWS {access_key}:{signature.decode()}'
+ return auth_header
+
+
+def get_database_matching_client_keys(
+ request_headers: Dict[str, str],
+ request_body: Optional[bytes],
+ request_method: str,
+ request_path: str,
+ databases: Iterable[VuforiaDatabase],
+) -> Optional[VuforiaDatabase]:
+ """
+ Return which, if any, of the given databases is being accessed by the given
+ client request.
+
+ Args:
+ request_headers: The headers sent with the request.
+ request_body: The request body.
+ request_method: The HTTP method of the request.
+ request_path: The path of the request.
+ databases: The databases to check for matches.
+
+ Returns:
+ The database which is being accessed by the given client request.
+ """
+ content_type = request_headers.get('Content-Type', '').split(';')[0]
+ auth_header = request_headers.get('Authorization')
+ content = request_body or b''
+ date = request_headers.get('Date', '')
+
+ for database in databases:
+ expected_authorization_header = _authorization_header(
+ access_key=database.client_access_key,
+ secret_key=database.client_secret_key,
+ method=request_method,
+ content=content,
+ content_type=content_type,
+ date=date,
+ request_path=request_path,
+ )
+
+ if auth_header == expected_authorization_header:
+ return database
+ return None
+
+
+def get_database_matching_server_keys(
+ request_headers: Dict[str, str],
+ request_body: Optional[bytes],
+ request_method: str,
+ request_path: str,
+ databases: Iterable[VuforiaDatabase],
+) -> Optional[VuforiaDatabase]:
+ """
+ Return which, if any, of the given databases is being accessed by the given
+ server request.
+
+ Args:
+ request_headers: The headers sent with the request.
+ request_body: The request body.
+ request_method: The HTTP method of the request.
+ request_path: The path of the request.
+ databases: The databases to check for matches.
+
+ Returns:
+ The database being accessed by the given server request.
+ """
+ content_type = request_headers.get('Content-Type', '').split(';')[0]
+ auth_header = request_headers.get('Authorization')
+ content = request_body or b''
+ date = request_headers.get('Date', '')
+
+ for database in databases:
+ expected_authorization_header = _authorization_header(
+ access_key=database.server_access_key,
+ secret_key=database.server_secret_key,
+ method=request_method,
+ content=content,
+ content_type=content_type,
+ date=date,
+ request_path=request_path,
+ )
+
+ if auth_header == expected_authorization_header:
+ return database
+ return None
diff --git a/src/_mock_vws_server/vwq/_mock_common.py b/src/_mock_vws_server/vwq/_mock_common.py
new file mode 100644
index 000000000..4a13a3cb9
--- /dev/null
+++ b/src/_mock_vws_server/vwq/_mock_common.py
@@ -0,0 +1,132 @@
+"""
+Common utilities for creating mock routes.
+"""
+
+import cgi
+import email.utils
+import io
+import json
+from typing import Any, Callable, Dict, List, Mapping, Tuple, Union
+
+import wrapt
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+
+class Route:
+ """
+ A container for the route details which `requests_mock` needs.
+
+ We register routes with names, and when we have an instance to work with
+ later.
+ """
+
+ route_name: str
+ path_pattern: str
+ http_methods: List[str]
+
+ def __init__(
+ self,
+ route_name: str,
+ path_pattern: str,
+ http_methods: List[str],
+ ) -> None:
+ """
+ Args:
+ route_name: The name of the method.
+ path_pattern: The end part of a URL pattern. E.g. `/targets` or
+ `/targets/.+`.
+ http_methods: HTTP methods that map to the route function.
+
+ Attributes:
+ route_name: The name of the method.
+ path_pattern: The end part of a URL pattern. E.g. `/targets` or
+ `/targets/.+`.
+ http_methods: HTTP methods that map to the route function.
+ endpoint: The method `requests_mock` should call when the endpoint
+ is requested.
+ """
+ self.route_name = route_name
+ self.path_pattern = path_pattern
+ self.http_methods = http_methods
+
+
+def json_dump(body: Dict[str, Any]) -> str:
+ """
+ Returns:
+ JSON dump of data in the same way that Vuforia dumps data.
+ """
+ return json.dumps(obj=body, separators=(',', ':'))
+
+
+@wrapt.decorator
+def set_content_length_header(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Set the `Content-Length` header.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ """
+ _, context = args
+
+ result = wrapped(*args, **kwargs)
+ context.headers['Content-Length'] = str(len(result))
+ return result
+
+
+@wrapt.decorator
+def set_date_header(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Set the `Date` header.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ """
+ _, context = args
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+
+ result = wrapped(*args, **kwargs)
+ context.headers['Date'] = date
+ return result
+
+
+def parse_multipart( # pylint: disable=invalid-name
+ fp: io.BytesIO,
+ pdict: Mapping[str, bytes],
+) -> Dict[str, List[Union[str, bytes]]]:
+ """
+ Return parsed ``pdict``.
+
+ Wrapper for ``_parse_multipart`` to work around
+ https://bugs.python.org/issue34226.
+
+ See https://docs.python.org/3.8/library/cgi.html#_parse_multipart.
+ """
+ pdict = {
+ 'CONTENT-LENGTH': str(len(fp.getvalue())).encode(),
+ **pdict,
+ }
+
+ return cgi.parse_multipart(fp=fp, pdict=pdict)
diff --git a/src/_mock_vws_server/vwq/_query_validators/__init__.py b/src/_mock_vws_server/vwq/_query_validators/__init__.py
new file mode 100644
index 000000000..71e7884cd
--- /dev/null
+++ b/src/_mock_vws_server/vwq/_query_validators/__init__.py
@@ -0,0 +1,302 @@
+"""
+Input validators to use in the mock query API.
+"""
+
+import cgi
+import io
+import uuid
+from typing import Any, Callable, Dict, Tuple
+
+import wrapt
+from requests import codes
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+from mock_vws.database import VuforiaDatabase
+from mock_vws.states import States
+
+from .._constants import ResultCodes
+from .._database_matchers import get_database_matching_client_keys
+from .._mock_common import parse_multipart
+
+
+@wrapt.decorator
+def validate_project_state(
+ wrapped: Callable[..., str],
+ instance: Any,
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the state of the project.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `FORBIDDEN` response with an InactiveProject result code if the
+ project is inactive.
+ """
+ request, context = args
+
+ database = get_database_matching_client_keys(
+ request_headers=request.headers,
+ request_body=request.body,
+ request_method=request.method,
+ request_path=request.path,
+ databases=instance.databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+ if database.state != States.PROJECT_INACTIVE:
+ return wrapped(*args, **kwargs)
+
+ context.status_code = codes.FORBIDDEN
+ transaction_id = uuid.uuid4().hex
+ result_code = ResultCodes.INACTIVE_PROJECT.value
+
+ # The response has an unusual format of separators, so we construct it
+ # manually.
+ return (
+ '{"transaction_id": '
+ f'"{transaction_id}",'
+ f'"result_code":"{result_code}"'
+ '}'
+ )
+
+
+@wrapt.decorator
+def validate_max_num_results(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the ``max_num_results`` field is either an integer within range or
+ not given.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `BAD_REQUEST` response if the ``max_num_results`` field is either not
+ an integer, or an integer out of range.
+ """
+ request, context = args
+ body_file = io.BytesIO(request.body)
+
+ _, pdict = cgi.parse_header(request.headers['Content-Type'])
+ parsed = parse_multipart(
+ fp=body_file,
+ pdict={
+ 'boundary': pdict['boundary'].encode(),
+ },
+ )
+ [max_num_results] = parsed.get('max_num_results', ['1'])
+ assert isinstance(max_num_results, str)
+ invalid_type_error = (
+ f"Invalid value '{max_num_results}' in form data part "
+ "'max_result'. "
+ 'Expecting integer value in range from 1 to 50 (inclusive).'
+ )
+
+ try:
+ max_num_results_int = int(max_num_results)
+ except ValueError:
+ context.status_code = codes.BAD_REQUEST
+ return invalid_type_error
+
+ java_max_int = 2147483647
+ if max_num_results_int > java_max_int:
+ context.status_code = codes.BAD_REQUEST
+ return invalid_type_error
+
+ if max_num_results_int < 1 or max_num_results_int > 50:
+ context.status_code = codes.BAD_REQUEST
+ out_of_range_error = (
+ f'Integer out of range ({max_num_results_int}) in form data part '
+ "'max_result'. Accepted range is from 1 to 50 (inclusive)."
+ )
+ return out_of_range_error
+
+ return wrapped(*args, **kwargs)
+
+
+@wrapt.decorator
+def validate_include_target_data(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the ``include_target_data`` field is either an accepted value or
+ not given.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `BAD_REQUEST` response if the ``include_target_data`` field is not an
+ accepted value.
+ """
+ request, context = args
+ body_file = io.BytesIO(request.body)
+
+ _, pdict = cgi.parse_header(request.headers['Content-Type'])
+ parsed = parse_multipart(
+ fp=body_file,
+ pdict={
+ 'boundary': pdict['boundary'].encode(),
+ },
+ )
+
+ [include_target_data] = parsed.get('include_target_data', ['top'])
+ lower_include_target_data = include_target_data.lower()
+ allowed_included_target_data = {'top', 'all', 'none'}
+ if lower_include_target_data in allowed_included_target_data:
+ return wrapped(*args, **kwargs)
+
+ assert isinstance(include_target_data, str)
+ unexpected_target_data_message = (
+ f"Invalid value '{include_target_data}' in form data part "
+ "'include_target_data'. "
+ "Expecting one of the (unquoted) string values 'all', 'none' or 'top'."
+ )
+ context.status_code = codes.BAD_REQUEST
+ return unexpected_target_data_message
+
+
+@wrapt.decorator
+def validate_content_type_header(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the ``Content-Type`` header.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An ``UNSUPPORTED_MEDIA_TYPE`` response if the ``Content-Type`` header
+ main part is not 'multipart/form-data'.
+ A ``BAD_REQUEST`` response if the ``Content-Type`` header does not
+ contain a boundary which is in the request body.
+ """
+ request, context = args
+
+ main_value, pdict = cgi.parse_header(request.headers['Content-Type'])
+ if main_value != 'multipart/form-data':
+ context.status_code = codes.UNSUPPORTED_MEDIA_TYPE
+ context.headers.pop('Content-Type')
+ return ''
+
+ if 'boundary' not in pdict:
+ context.status_code = codes.BAD_REQUEST
+ context.headers['Content-Type'] = 'text/html;charset=UTF-8'
+ return (
+ 'java.io.IOException: RESTEASY007550: '
+ 'Unable to get boundary for multipart'
+ )
+
+ if pdict['boundary'].encode() not in request.body:
+ context.status_code = codes.BAD_REQUEST
+ context.headers['Content-Type'] = 'text/html;charset=UTF-8'
+ return (
+ 'java.lang.RuntimeException: RESTEASY007500: '
+ 'Could find no Content-Disposition header within part'
+ )
+
+ return wrapped(*args, **kwargs)
+
+
+@wrapt.decorator
+def validate_accept_header(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the accept header.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `NOT_ACCEPTABLE` response if the Accept header is given and is not
+ 'application/json' or '*/*'.
+ """
+ request, context = args
+
+ accept = request.headers.get('Accept')
+ if accept in ('application/json', '*/*', None):
+ return wrapped(*args, **kwargs)
+
+ context.headers.pop('Content-Type')
+ context.status_code = codes.NOT_ACCEPTABLE
+ return ''
+
+
+@wrapt.decorator
+def validate_extra_fields(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that the no unknown fields are given.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A ``BAD_REQUEST`` response if extra fields are given.
+ """
+ request, context = args
+ body_file = io.BytesIO(request.body)
+
+ _, pdict = cgi.parse_header(request.headers['Content-Type'])
+ parsed = parse_multipart(
+ fp=body_file,
+ pdict={
+ 'boundary': pdict['boundary'].encode(),
+ },
+ )
+
+ known_parameters = {'image', 'max_num_results', 'include_target_data'}
+
+ if not parsed.keys() - known_parameters:
+ return wrapped(*args, **kwargs)
+
+ context.status_code = codes.BAD_REQUEST
+ return 'Unknown parameters in the request.'
diff --git a/src/_mock_vws_server/vwq/_query_validators/auth_validators.py b/src/_mock_vws_server/vwq/_query_validators/auth_validators.py
new file mode 100644
index 000000000..c2f789d7b
--- /dev/null
+++ b/src/_mock_vws_server/vwq/_query_validators/auth_validators.py
@@ -0,0 +1,210 @@
+"""
+Authorization validators to use in the mock query API.
+"""
+
+import uuid
+from pathlib import Path
+from typing import Any, Callable, Dict, Tuple
+
+import wrapt
+from requests import codes
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+from .._constants import ResultCodes
+from .._database_matchers import get_database_matching_client_keys
+
+
+@wrapt.decorator
+def validate_auth_header_exists(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that there is an authorization header given to the query endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNAUTHORIZED` response if there is no "Authorization" header.
+ """
+ request, context = args
+ if 'Authorization' in request.headers:
+ return wrapped(*args, **kwargs)
+
+ context.status_code = codes.UNAUTHORIZED
+ text = 'Authorization header missing.'
+ content_type = 'text/plain; charset=ISO-8859-1'
+ context.headers['Content-Type'] = content_type
+ context.headers['WWW-Authenticate'] = 'VWS'
+ return text
+
+
+@wrapt.decorator
+def validate_auth_header_number_of_parts(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the authorization header includes text either side of a space.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An ``UNAUTHORIZED`` response if the "Authorization" header is not as
+ expected.
+ """
+ request, context = args
+
+ header = request.headers['Authorization']
+ parts = header.split(' ')
+ if len(parts) == 2 and parts[1]:
+ return wrapped(*args, **kwargs)
+
+ context.status_code = codes.UNAUTHORIZED
+ text = 'Malformed authorization header.'
+ content_type = 'text/plain; charset=ISO-8859-1'
+ context.headers['Content-Type'] = content_type
+ context.headers['WWW-Authenticate'] = 'VWS'
+ return text
+
+
+@wrapt.decorator
+def validate_client_key_exists(
+ wrapped: Callable[..., str],
+ instance: Any,
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the authorization header includes a client key for a database.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An ``UNAUTHORIZED`` response if the client key is unknown.
+ """
+ request, context = args
+
+ header = request.headers['Authorization']
+ first_part, _ = header.split(':')
+ _, access_key = first_part.split(' ')
+ for database in instance.databases:
+ if access_key == database.client_access_key:
+ return wrapped(*args, **kwargs)
+
+ context.status_code = codes.UNAUTHORIZED
+ context.headers['WWW-Authenticate'] = 'VWS'
+ transaction_id = uuid.uuid4().hex
+ result_code = ResultCodes.AUTHENTICATION_FAILURE.value
+ text = (
+ '{"transaction_id":'
+ f'"{transaction_id}",'
+ f'"result_code":"{result_code}"'
+ '}'
+ )
+ return text
+
+
+@wrapt.decorator
+def validate_auth_header_has_signature(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the authorization header includes a signature.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An ``UNAUTHORIZED`` response if the "Authorization" header is not as
+ expected.
+ """
+ request, context = args
+
+ header = request.headers['Authorization']
+ if header.count(':') == 1 and header.split(':')[1]:
+ return wrapped(*args, **kwargs)
+
+ context.status_code = codes.INTERNAL_SERVER_ERROR
+ current_parent = Path(__file__).parent
+ resources = current_parent / 'resources'
+ known_response = resources / 'query_out_of_bounds_response'
+ content_type = 'text/html; charset=ISO-8859-1'
+ context.headers['Content-Type'] = content_type
+ cache_control = 'must-revalidate,no-cache,no-store'
+ context.headers['Cache-Control'] = cache_control
+ return known_response.read_text()
+
+
+@wrapt.decorator
+def validate_authorization(
+ wrapped: Callable[..., str],
+ instance: Any,
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the authorization header given to the query endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `BAD_REQUEST` response if the "Authorization" header is not as
+ expected.
+ """
+ request, context = args
+
+ database = get_database_matching_client_keys(
+ request_headers=request.headers,
+ request_body=request.body,
+ request_method=request.method,
+ request_path=request.path,
+ databases=instance.databases,
+ )
+
+ if database is not None:
+ return wrapped(*args, **kwargs)
+
+ context.status_code = codes.UNAUTHORIZED
+ context.headers['WWW-Authenticate'] = 'VWS'
+ transaction_id = uuid.uuid4().hex
+ result_code = ResultCodes.AUTHENTICATION_FAILURE.value
+ text = (
+ '{"transaction_id":'
+ f'"{transaction_id}",'
+ f'"result_code":"{result_code}"'
+ '}'
+ )
+ return text
diff --git a/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py b/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
new file mode 100644
index 000000000..f81108432
--- /dev/null
+++ b/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
@@ -0,0 +1,121 @@
+"""
+Content-Length header validators to use in the mock.
+"""
+
+import uuid
+from typing import Any, Callable, Dict, Tuple
+
+import wrapt
+from requests import codes
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+from .._constants import ResultCodes
+from .._mock_common import json_dump
+
+
+@wrapt.decorator
+def validate_content_length_header_is_int(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the ``Content-Length`` header is an integer.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A ``BAD_REQUEST`` response if the content length header is not an
+ integer.
+ """
+ request, context = args
+ given_content_length = request.headers['Content-Length']
+
+ try:
+ int(given_content_length)
+ except ValueError:
+ context.status_code = codes.BAD_REQUEST
+ context.headers = {'Connection': 'Close'}
+ return ''
+
+ return wrapped(*args, **kwargs)
+
+
+@wrapt.decorator
+def validate_content_length_header_not_too_large(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the ``Content-Length`` header is not too large.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A ``GATEWAY_TIMEOUT`` response if the given content length header says
+ that the content length is greater than the body length.
+ """
+ request, context = args
+ given_content_length = request.headers['Content-Length']
+
+ body_length = len(request.body if request.body else '')
+ given_content_length_value = int(given_content_length)
+ if given_content_length_value > body_length:
+ context.status_code = codes.GATEWAY_TIMEOUT
+ context.headers = {'Connection': 'keep-alive'}
+ return ''
+
+ return wrapped(*args, **kwargs)
+
+
+@wrapt.decorator
+def validate_content_length_header_not_too_small(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the ``Content-Length`` header is not too small.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An ``UNAUTHORIZED`` response if the given content length header says
+ that the content length is smaller than the body length.
+ """
+ request, context = args
+ given_content_length = request.headers['Content-Length']
+
+ body_length = len(request.body if request.body else '')
+ given_content_length_value = int(given_content_length)
+
+ if given_content_length_value < body_length:
+ context.status_code = codes.UNAUTHORIZED
+ context.headers['WWW-Authenticate'] = 'VWS'
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
+ }
+ return json_dump(body)
+
+ return wrapped(*args, **kwargs)
diff --git a/src/_mock_vws_server/vwq/_query_validators/date_validators.py b/src/_mock_vws_server/vwq/_query_validators/date_validators.py
new file mode 100644
index 000000000..9e8cedf36
--- /dev/null
+++ b/src/_mock_vws_server/vwq/_query_validators/date_validators.py
@@ -0,0 +1,157 @@
+"""
+Validators of the date header to use in the mock query API.
+"""
+
+import datetime
+import uuid
+from typing import Any, Callable, Dict, Set, Tuple
+
+import pytz
+import wrapt
+from requests import codes
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+from .._constants import ResultCodes
+from .._mock_common import json_dump
+
+
+@wrapt.decorator
+def validate_date_header_given(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the date header is given to the query endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `BAD_REQUEST` response if the date is not given.
+ """
+ request, context = args
+
+ if 'Date' in request.headers:
+ return wrapped(*args, **kwargs)
+
+ context.status_code = codes.BAD_REQUEST
+ content_type = 'text/plain; charset=ISO-8859-1'
+ context.headers['Content-Type'] = content_type
+ return 'Date header required.'
+
+
+def _accepted_date_formats() -> Set[str]:
+ """
+ Return all known accepted date formats.
+
+ We expect that more formats than this will be accepted.
+ These are the accepted ones we know of at the time of writing.
+ """
+ known_accepted_formats = {
+ '%a, %b %d %H:%M:%S %Y',
+ '%a %b %d %H:%M:%S %Y',
+ '%a, %d %b %Y %H:%M:%S',
+ '%a %d %b %Y %H:%M:%S',
+ }
+
+ known_accepted_formats = known_accepted_formats.union(
+ set(date_format + ' GMT' for date_format in known_accepted_formats),
+ )
+
+ return known_accepted_formats
+
+
+@wrapt.decorator
+def validate_date_format(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the format of the date header given to the query endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNAUTHORIZED` response if the date is in the wrong format.
+ """
+ request, context = args
+ date_header = request.headers['Date']
+
+ for date_format in _accepted_date_formats():
+ try:
+ datetime.datetime.strptime(date_header, date_format)
+ except ValueError:
+ pass
+ else:
+ return wrapped(*args, **kwargs)
+
+ context.status_code = codes.UNAUTHORIZED
+ context.headers['WWW-Authenticate'] = 'VWS'
+ text = 'Malformed date header.'
+ content_type = 'text/plain; charset=ISO-8859-1'
+ context.headers['Content-Type'] = content_type
+ return text
+
+
+@wrapt.decorator
+def validate_date_in_range(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate date in the date header given to the query endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A `FORBIDDEN` response if the date is out of range.
+ """
+ request, context = args
+ date_header = request.headers['Date']
+
+ for date_format in _accepted_date_formats():
+ try:
+ date = datetime.datetime.strptime(date_header, date_format)
+ # We could break here but that would give a coverage report that is
+ # not 100%.
+ except ValueError:
+ pass
+
+ gmt = pytz.timezone('GMT')
+ now = datetime.datetime.now(tz=gmt)
+ date_from_header = date.replace(tzinfo=gmt)
+ time_difference = now - date_from_header
+
+ maximum_time_difference = datetime.timedelta(minutes=65)
+
+ if abs(time_difference) < maximum_time_difference:
+ return wrapped(*args, **kwargs)
+
+ context.status_code = codes.FORBIDDEN
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.REQUEST_TIME_TOO_SKEWED.value,
+ }
+ return json_dump(body)
diff --git a/src/_mock_vws_server/vwq/_query_validators/image_validators.py b/src/_mock_vws_server/vwq/_query_validators/image_validators.py
new file mode 100644
index 000000000..6fb8ad520
--- /dev/null
+++ b/src/_mock_vws_server/vwq/_query_validators/image_validators.py
@@ -0,0 +1,270 @@
+"""
+Input validators for the image field use in the mock query API.
+"""
+
+import cgi
+import io
+import uuid
+from typing import Any, Callable, Dict, Tuple
+
+import requests
+import wrapt
+from PIL import Image
+from requests import codes
+from requests_mock.request import _RequestObjectProxy
+from requests_mock.response import _Context
+
+from .._constants import ResultCodes
+from .._mock_common import parse_multipart
+
+
+@wrapt.decorator
+def validate_image_field_given(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that the image field is given.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ A ``BAD_REQUEST`` response if the image field is not given.
+ """
+ request, context = args
+ body_file = io.BytesIO(request.body)
+
+ _, pdict = cgi.parse_header(request.headers['Content-Type'])
+ parsed = parse_multipart(
+ fp=body_file,
+ pdict={
+ 'boundary': pdict['boundary'].encode(),
+ },
+ )
+
+ if 'image' in parsed.keys():
+ return wrapped(*args, **kwargs)
+
+ context.status_code = codes.BAD_REQUEST
+ return 'No image.'
+
+
+@wrapt.decorator
+def validate_image_file_size(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the file size of the image given to the query endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+
+ Raises:
+ requests.exceptions.ConnectionError: The image file size is too large.
+ """
+ request, _ = args
+ body_file = io.BytesIO(request.body)
+
+ _, pdict = cgi.parse_header(request.headers['Content-Type'])
+ parsed = parse_multipart(
+ fp=body_file,
+ pdict={
+ 'boundary': pdict['boundary'].encode(),
+ },
+ )
+
+ [image] = parsed['image']
+
+ # This is the documented maximum size of a PNG as per.
+ # https://library.vuforia.com/articles/Solution/How-To-Perform-an-Image-Recognition-Query.
+ # However, the tests show that this maximum size also applies to JPEG
+ # files.
+ max_bytes = 2 * 1024 * 1024
+ if len(image) > max_bytes:
+ raise requests.exceptions.ConnectionError
+ return wrapped(*args, **kwargs)
+
+
+@wrapt.decorator
+def validate_image_dimensions(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the dimensions the image given to the query endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+
+ Raises:
+ The result of calling the endpoint.
+ An ``UNPROCESSABLE_ENTITY`` response if the image is given and is not
+ within the maximum width and height limits.
+ """
+ request, context = args
+ body_file = io.BytesIO(request.body)
+
+ _, pdict = cgi.parse_header(request.headers['Content-Type'])
+ parsed = parse_multipart(
+ fp=body_file,
+ pdict={
+ 'boundary': pdict['boundary'].encode(),
+ },
+ )
+
+ [image] = parsed['image']
+ assert isinstance(image, bytes)
+ image_file = io.BytesIO(image)
+ pil_image = Image.open(image_file)
+ max_width = 30000
+ max_height = 30000
+ if pil_image.height <= max_height and pil_image.width <= max_width:
+ return wrapped(*args, **kwargs)
+
+ context.status_code = codes.UNPROCESSABLE_ENTITY
+ transaction_id = uuid.uuid4().hex
+ result_code = ResultCodes.BAD_IMAGE.value
+
+ # The response has an unusual format of separators, so we construct it
+ # manually.
+ return (
+ '{"transaction_id": '
+ f'"{transaction_id}",'
+ f'"result_code":"{result_code}"'
+ '}'
+ )
+
+
+@wrapt.decorator
+def validate_image_format(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate the format of the image given to the query endpoint.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNPROCESSABLE_ENTITY` response if the image is given and is not
+ either a PNG or a JPEG.
+ """
+ request, context = args
+ body_file = io.BytesIO(request.body)
+
+ _, pdict = cgi.parse_header(request.headers['Content-Type'])
+ parsed = parse_multipart(
+ fp=body_file,
+ pdict={
+ 'boundary': pdict['boundary'].encode(),
+ },
+ )
+
+ [image] = parsed['image']
+
+ assert isinstance(image, bytes)
+ image_file = io.BytesIO(image)
+ pil_image = Image.open(image_file)
+
+ if pil_image.format in ('PNG', 'JPEG'):
+ return wrapped(*args, **kwargs)
+
+ context.status_code = codes.UNPROCESSABLE_ENTITY
+ transaction_id = uuid.uuid4().hex
+ result_code = ResultCodes.BAD_IMAGE.value
+
+ # The response has an unusual format of separators, so we construct it
+ # manually.
+ return (
+ '{"transaction_id": '
+ f'"{transaction_id}",'
+ f'"result_code":"{result_code}"'
+ '}'
+ )
+
+
+@wrapt.decorator
+def validate_image_is_image(
+ wrapped: Callable[..., str],
+ instance: Any, # pylint: disable=unused-argument
+ args: Tuple[_RequestObjectProxy, _Context],
+ kwargs: Dict,
+) -> str:
+ """
+ Validate that the given image data is actually an image file.
+
+ Args:
+ wrapped: An endpoint function for `requests_mock`.
+ instance: The class that the endpoint function is in.
+ args: The arguments given to the endpoint function.
+ kwargs: The keyword arguments given to the endpoint function.
+
+ Returns:
+ The result of calling the endpoint.
+ An `UNPROCESSABLE_ENTITY` response if image data is given and it is not
+ an image file.
+ """
+ request, context = args
+ body_file = io.BytesIO(request.body)
+
+ _, pdict = cgi.parse_header(request.headers['Content-Type'])
+ parsed = parse_multipart(
+ fp=body_file,
+ pdict={
+ 'boundary': pdict['boundary'].encode(),
+ },
+ )
+
+ [image] = parsed['image']
+
+ assert isinstance(image, bytes)
+ image_file = io.BytesIO(image)
+
+ try:
+ Image.open(image_file)
+ except OSError:
+ context.status_code = codes.UNPROCESSABLE_ENTITY
+ transaction_id = uuid.uuid4().hex
+ result_code = ResultCodes.BAD_IMAGE.value
+
+ # The response has an unusual format of separators, so we construct it
+ # manually.
+ return (
+ '{"transaction_id": '
+ f'"{transaction_id}",'
+ f'"result_code":"{result_code}"'
+ '}'
+ )
+
+ return wrapped(*args, **kwargs)
diff --git a/src/_mock_vws_server/vwq/_query_validators/resources/query_out_of_bounds_response b/src/_mock_vws_server/vwq/_query_validators/resources/query_out_of_bounds_response
new file mode 100644
index 000000000..7a97a1674
--- /dev/null
+++ b/src/_mock_vws_server/vwq/_query_validators/resources/query_out_of_bounds_response
@@ -0,0 +1,34 @@
+
+
+
+Error 500 Server Error
+
+HTTP ERROR 500
+Problem accessing /v1/query. Reason:
+
Server Error
Caused by:
java.lang.ArrayIndexOutOfBoundsException: 1
+ at com.kooaba.queryservice.auth.KWSAuthFilter.doFilter(KWSAuthFilter.java:81)
+ at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
+ at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
+ at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
+ at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
+ at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
+ at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
+ at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
+ at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
+ at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
+ at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
+ at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
+ at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:110)
+ at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
+ at org.eclipse.jetty.server.Server.handle(Server.java:497)
+ at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
+ at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
+ at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
+ at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
+ at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
+ at java.lang.Thread.run(Thread.java:748)
+
+
Powered by Jetty://
+
+
+
From 0e3d2507f3a52845dd8fb35bac1e7f8e74748e6e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 29 Feb 2020 14:36:25 +0000
Subject: [PATCH 0076/3455] Progress towards working query
---
src/_mock_vws_server/vwq/__init__.py | 41 ++++++++++++++++
.../vwq/_query_validators/__init__.py | 47 ++++++++++---------
.../vwq/_query_validators/date_validators.py | 39 +++++++--------
.../vwq/_query_validators/image_validators.py | 41 ++++++++--------
4 files changed, 104 insertions(+), 64 deletions(-)
diff --git a/src/_mock_vws_server/vwq/__init__.py b/src/_mock_vws_server/vwq/__init__.py
index 9e2c95969..b520c0ee0 100644
--- a/src/_mock_vws_server/vwq/__init__.py
+++ b/src/_mock_vws_server/vwq/__init__.py
@@ -1,5 +1,6 @@
from typing import Tuple
import base64
+import email.utils
import cgi
import datetime
import io
@@ -63,12 +64,52 @@
CLOUDRECO_FLASK_APP = Flask(__name__)
+@CLOUDRECO_FLASK_APP.before_request
+@validate_date_in_range
+@validate_date_format
+@validate_date_header_given
+@validate_include_target_data
+@validate_max_num_results
+@validate_image_file_size
+@validate_image_dimensions
+@validate_image_format
+@validate_image_is_image
+@validate_image_field_given
+@validate_extra_fields
+@validate_content_type_header
+@validate_accept_header
+@validate_project_state
+@validate_authorization
+@validate_client_key_exists
+@validate_auth_header_has_signature
+@validate_auth_header_number_of_parts
+@validate_auth_header_exists
+@validate_content_length_header_not_too_small
+@set_date_header
+@validate_content_length_header_not_too_large
+@validate_content_length_header_is_int
+def validate_request() -> None:
+ pass
+
+
+@CLOUDRECO_FLASK_APP.after_request
+def set_headers(response: Response) -> Response:
+ response.headers['Connection'] = 'keep-alive'
+ if response.status_code != codes.INTERNAL_SERVER_ERROR:
+ response.headers['Content-Type'] = 'application/json'
+ response.headers['Server'] = 'nginx'
+ content_length = len(response.data)
+ response.headers['Content-Length'] = str(content_length)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ response.headers['Date'] = date
+ return response
@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
def query() -> Tuple[str, int]:
body_file = io.BytesIO(request.data)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
+ import pdb; pdb.set_trace()
parsed = parse_multipart(
fp=body_file,
pdict={
diff --git a/src/_mock_vws_server/vwq/_query_validators/__init__.py b/src/_mock_vws_server/vwq/_query_validators/__init__.py
index 71e7884cd..fdf54d902 100644
--- a/src/_mock_vws_server/vwq/_query_validators/__init__.py
+++ b/src/_mock_vws_server/vwq/_query_validators/__init__.py
@@ -11,6 +11,7 @@
from requests import codes
from requests_mock.request import _RequestObjectProxy
from requests_mock.response import _Context
+from flask import request
from mock_vws.database import VuforiaDatabase
from mock_vws.states import States
@@ -22,11 +23,11 @@
@wrapt.decorator
def validate_project_state(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any,
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the state of the project.
@@ -41,11 +42,11 @@ def validate_project_state(
A `FORBIDDEN` response with an InactiveProject result code if the
project is inactive.
"""
- request, context = args
+
database = get_database_matching_client_keys(
request_headers=request.headers,
- request_body=request.body,
+ request_body=request.data,
request_method=request.method,
request_path=request.path,
databases=instance.databases,
@@ -71,11 +72,11 @@ def validate_project_state(
@wrapt.decorator
def validate_max_num_results(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the ``max_num_results`` field is either an integer within range or
not given.
@@ -91,8 +92,8 @@ def validate_max_num_results(
A `BAD_REQUEST` response if the ``max_num_results`` field is either not
an integer, or an integer out of range.
"""
- request, context = args
- body_file = io.BytesIO(request.body)
+
+ body_file = io.BytesIO(request.data)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
parsed = parse_multipart(
@@ -133,11 +134,11 @@ def validate_max_num_results(
@wrapt.decorator
def validate_include_target_data(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the ``include_target_data`` field is either an accepted value or
not given.
@@ -153,8 +154,8 @@ def validate_include_target_data(
A `BAD_REQUEST` response if the ``include_target_data`` field is not an
accepted value.
"""
- request, context = args
- body_file = io.BytesIO(request.body)
+
+ body_file = io.BytesIO(request.data)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
parsed = parse_multipart(
@@ -182,11 +183,11 @@ def validate_include_target_data(
@wrapt.decorator
def validate_content_type_header(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the ``Content-Type`` header.
@@ -203,7 +204,7 @@ def validate_content_type_header(
A ``BAD_REQUEST`` response if the ``Content-Type`` header does not
contain a boundary which is in the request body.
"""
- request, context = args
+
main_value, pdict = cgi.parse_header(request.headers['Content-Type'])
if main_value != 'multipart/form-data':
@@ -219,7 +220,7 @@ def validate_content_type_header(
'Unable to get boundary for multipart'
)
- if pdict['boundary'].encode() not in request.body:
+ if pdict['boundary'].encode() not in request.data:
context.status_code = codes.BAD_REQUEST
context.headers['Content-Type'] = 'text/html;charset=UTF-8'
return (
@@ -232,11 +233,11 @@ def validate_content_type_header(
@wrapt.decorator
def validate_accept_header(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the accept header.
@@ -251,7 +252,7 @@ def validate_accept_header(
A `NOT_ACCEPTABLE` response if the Accept header is given and is not
'application/json' or '*/*'.
"""
- request, context = args
+
accept = request.headers.get('Accept')
if accept in ('application/json', '*/*', None):
@@ -264,11 +265,11 @@ def validate_accept_header(
@wrapt.decorator
def validate_extra_fields(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate that the no unknown fields are given.
@@ -282,8 +283,8 @@ def validate_extra_fields(
The result of calling the endpoint.
A ``BAD_REQUEST`` response if extra fields are given.
"""
- request, context = args
- body_file = io.BytesIO(request.body)
+
+ body_file = io.BytesIO(request.data)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
parsed = parse_multipart(
diff --git a/src/_mock_vws_server/vwq/_query_validators/date_validators.py b/src/_mock_vws_server/vwq/_query_validators/date_validators.py
index 9e8cedf36..661ec16ae 100644
--- a/src/_mock_vws_server/vwq/_query_validators/date_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/date_validators.py
@@ -14,15 +14,16 @@
from .._constants import ResultCodes
from .._mock_common import json_dump
+from flask import request
@wrapt.decorator
def validate_date_header_given(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the date header is given to the query endpoint.
@@ -36,15 +37,15 @@ def validate_date_header_given(
The result of calling the endpoint.
A `BAD_REQUEST` response if the date is not given.
"""
- request, context = args
+
if 'Date' in request.headers:
return wrapped(*args, **kwargs)
- context.status_code = codes.BAD_REQUEST
content_type = 'text/plain; charset=ISO-8859-1'
- context.headers['Content-Type'] = content_type
- return 'Date header required.'
+ # TODO remove legacy
+ # context.headers['Content-Type'] = content_type
+ return 'Date header required.', codes.BAD_REQUEST, {'Content-Type': content_type}
def _accepted_date_formats() -> Set[str]:
@@ -70,11 +71,11 @@ def _accepted_date_formats() -> Set[str]:
@wrapt.decorator
def validate_date_format(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the format of the date header given to the query endpoint.
@@ -88,7 +89,7 @@ def validate_date_format(
The result of calling the endpoint.
An `UNAUTHORIZED` response if the date is in the wrong format.
"""
- request, context = args
+
date_header = request.headers['Date']
for date_format in _accepted_date_formats():
@@ -99,21 +100,23 @@ def validate_date_format(
else:
return wrapped(*args, **kwargs)
- context.status_code = codes.UNAUTHORIZED
- context.headers['WWW-Authenticate'] = 'VWS'
+ # context.status_code = codes.UNAUTHORIZED
+ # TODO remove this legacy
+ # context.headers['WWW-Authenticate'] = 'VWS'
text = 'Malformed date header.'
content_type = 'text/plain; charset=ISO-8859-1'
- context.headers['Content-Type'] = content_type
- return text
+ # TODO remove this legacy
+ # context.headers['Content-Type'] = content_type
+ return text, codes.UNAUTHORIZED, {'Content-Type': content_type, 'WWW-Authenticate': 'VWS'}
@wrapt.decorator
def validate_date_in_range(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate date in the date header given to the query endpoint.
@@ -127,7 +130,7 @@ def validate_date_in_range(
The result of calling the endpoint.
A `FORBIDDEN` response if the date is out of range.
"""
- request, context = args
+
date_header = request.headers['Date']
for date_format in _accepted_date_formats():
@@ -148,10 +151,8 @@ def validate_date_in_range(
if abs(time_difference) < maximum_time_difference:
return wrapped(*args, **kwargs)
- context.status_code = codes.FORBIDDEN
-
body = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.REQUEST_TIME_TOO_SKEWED.value,
}
- return json_dump(body)
+ return json_dump(body), codes.FORBIDDEN
diff --git a/src/_mock_vws_server/vwq/_query_validators/image_validators.py b/src/_mock_vws_server/vwq/_query_validators/image_validators.py
index 6fb8ad520..bcd5069b2 100644
--- a/src/_mock_vws_server/vwq/_query_validators/image_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/image_validators.py
@@ -6,6 +6,7 @@
import io
import uuid
from typing import Any, Callable, Dict, Tuple
+from flask import request
import requests
import wrapt
@@ -20,11 +21,11 @@
@wrapt.decorator
def validate_image_field_given(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate that the image field is given.
@@ -38,7 +39,7 @@ def validate_image_field_given(
The result of calling the endpoint.
A ``BAD_REQUEST`` response if the image field is not given.
"""
- request, context = args
+
body_file = io.BytesIO(request.body)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
@@ -52,17 +53,16 @@ def validate_image_field_given(
if 'image' in parsed.keys():
return wrapped(*args, **kwargs)
- context.status_code = codes.BAD_REQUEST
- return 'No image.'
+ return 'No image.', codes.BAD_REQUEST
@wrapt.decorator
def validate_image_file_size(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the file size of the image given to the query endpoint.
@@ -103,11 +103,11 @@ def validate_image_file_size(
@wrapt.decorator
def validate_image_dimensions(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the dimensions the image given to the query endpoint.
@@ -125,7 +125,7 @@ def validate_image_dimensions(
An ``UNPROCESSABLE_ENTITY`` response if the image is given and is not
within the maximum width and height limits.
"""
- request, context = args
+
body_file = io.BytesIO(request.body)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
@@ -145,7 +145,6 @@ def validate_image_dimensions(
if pil_image.height <= max_height and pil_image.width <= max_width:
return wrapped(*args, **kwargs)
- context.status_code = codes.UNPROCESSABLE_ENTITY
transaction_id = uuid.uuid4().hex
result_code = ResultCodes.BAD_IMAGE.value
@@ -156,16 +155,16 @@ def validate_image_dimensions(
f'"{transaction_id}",'
f'"result_code":"{result_code}"'
'}'
- )
+ ), codes.UNPROCESSABLE_ENTITY
@wrapt.decorator
def validate_image_format(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the format of the image given to the query endpoint.
@@ -180,7 +179,7 @@ def validate_image_format(
An `UNPROCESSABLE_ENTITY` response if the image is given and is not
either a PNG or a JPEG.
"""
- request, context = args
+
body_file = io.BytesIO(request.body)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
@@ -200,7 +199,6 @@ def validate_image_format(
if pil_image.format in ('PNG', 'JPEG'):
return wrapped(*args, **kwargs)
- context.status_code = codes.UNPROCESSABLE_ENTITY
transaction_id = uuid.uuid4().hex
result_code = ResultCodes.BAD_IMAGE.value
@@ -211,16 +209,16 @@ def validate_image_format(
f'"{transaction_id}",'
f'"result_code":"{result_code}"'
'}'
- )
+ ), codes.UNPROCESSABLE_ENTITY
@wrapt.decorator
def validate_image_is_image(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate that the given image data is actually an image file.
@@ -235,7 +233,7 @@ def validate_image_is_image(
An `UNPROCESSABLE_ENTITY` response if image data is given and it is not
an image file.
"""
- request, context = args
+
body_file = io.BytesIO(request.body)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
@@ -254,7 +252,6 @@ def validate_image_is_image(
try:
Image.open(image_file)
except OSError:
- context.status_code = codes.UNPROCESSABLE_ENTITY
transaction_id = uuid.uuid4().hex
result_code = ResultCodes.BAD_IMAGE.value
@@ -265,6 +262,6 @@ def validate_image_is_image(
f'"{transaction_id}",'
f'"result_code":"{result_code}"'
'}'
- )
+ ), codes.UNPROCESSABLE_ENTITY
return wrapped(*args, **kwargs)
From b9154146b3549335b1712e13fd3106622ec6d53f Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 29 Feb 2020 14:38:38 +0000
Subject: [PATCH 0077/3455] Progress towards working query
---
src/_mock_vws_server/vwq/__init__.py | 39 +++++++++----------
.../vwq/_query_validators/__init__.py | 11 ++----
.../vwq/_query_validators/date_validators.py | 16 +++++---
.../vwq/_query_validators/image_validators.py | 10 ++---
4 files changed, 37 insertions(+), 39 deletions(-)
diff --git a/src/_mock_vws_server/vwq/__init__.py b/src/_mock_vws_server/vwq/__init__.py
index b520c0ee0..b47ea8b07 100644
--- a/src/_mock_vws_server/vwq/__init__.py
+++ b/src/_mock_vws_server/vwq/__init__.py
@@ -1,34 +1,24 @@
-from typing import Tuple
import base64
-import email.utils
import cgi
import datetime
+import email.utils
import io
import uuid
from pathlib import Path
-from typing import Any, Callable, Dict, List, Set, Union
-from flask import request
-# TODO move this
-from ..vws._databases import get_all_databases
+from typing import Any, Dict, List, Tuple
import pytz
+from flask import Flask, Response, request
from requests import codes
-from requests_mock import POST
-from flask import Flask, Response
-from requests import codes
from mock_vws._base64_decoding import decode_base64
from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_client_keys
-from mock_vws._mock_common import (
- Route,
- json_dump,
- parse_multipart,
- set_content_length_header,
- set_date_header,
-)
+from mock_vws._mock_common import json_dump, parse_multipart, set_date_header
from mock_vws.database import VuforiaDatabase
+# TODO move this
+from ..vws._databases import get_all_databases
from ._query_validators import (
validate_accept_header,
validate_content_type_header,
@@ -64,6 +54,7 @@
CLOUDRECO_FLASK_APP = Flask(__name__)
+
@CLOUDRECO_FLASK_APP.before_request
@validate_date_in_range
@validate_date_format
@@ -104,12 +95,14 @@ def set_headers(response: Response) -> Response:
response.headers['Date'] = date
return response
+
@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
def query() -> Tuple[str, int]:
body_file = io.BytesIO(request.data)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
- import pdb; pdb.set_trace()
+ import pdb
+ pdb.set_trace()
parsed = parse_multipart(
fp=body_file,
pdict={
@@ -173,9 +166,10 @@ def query() -> Tuple[str, int]:
]
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)
+ 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
]
@@ -202,7 +196,10 @@ def query() -> Tuple[str, int]:
return (
Path(match_processing_resp_file).read_text(),
codes.INTERNAL_SERVER_ERROR,
- {'Cache-Control': cache_control, 'Content-Type': content_type},
+ {
+ 'Cache-Control': cache_control,
+ 'Content-Type': content_type
+ },
)
matches = not_deleted_matches + deletion_not_recognized_matches
diff --git a/src/_mock_vws_server/vwq/_query_validators/__init__.py b/src/_mock_vws_server/vwq/_query_validators/__init__.py
index fdf54d902..93c7c3376 100644
--- a/src/_mock_vws_server/vwq/_query_validators/__init__.py
+++ b/src/_mock_vws_server/vwq/_query_validators/__init__.py
@@ -8,10 +8,10 @@
from typing import Any, Callable, Dict, Tuple
import wrapt
+from flask import request
from requests import codes
from requests_mock.request import _RequestObjectProxy
from requests_mock.response import _Context
-from flask import request
from mock_vws.database import VuforiaDatabase
from mock_vws.states import States
@@ -42,7 +42,6 @@ def validate_project_state(
A `FORBIDDEN` response with an InactiveProject result code if the
project is inactive.
"""
-
database = get_database_matching_client_keys(
request_headers=request.headers,
@@ -92,7 +91,7 @@ def validate_max_num_results(
A `BAD_REQUEST` response if the ``max_num_results`` field is either not
an integer, or an integer out of range.
"""
-
+
body_file = io.BytesIO(request.data)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
@@ -154,7 +153,7 @@ def validate_include_target_data(
A `BAD_REQUEST` response if the ``include_target_data`` field is not an
accepted value.
"""
-
+
body_file = io.BytesIO(request.data)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
@@ -204,7 +203,6 @@ def validate_content_type_header(
A ``BAD_REQUEST`` response if the ``Content-Type`` header does not
contain a boundary which is in the request body.
"""
-
main_value, pdict = cgi.parse_header(request.headers['Content-Type'])
if main_value != 'multipart/form-data':
@@ -252,7 +250,6 @@ def validate_accept_header(
A `NOT_ACCEPTABLE` response if the Accept header is given and is not
'application/json' or '*/*'.
"""
-
accept = request.headers.get('Accept')
if accept in ('application/json', '*/*', None):
@@ -283,7 +280,7 @@ def validate_extra_fields(
The result of calling the endpoint.
A ``BAD_REQUEST`` response if extra fields are given.
"""
-
+
body_file = io.BytesIO(request.data)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
diff --git a/src/_mock_vws_server/vwq/_query_validators/date_validators.py b/src/_mock_vws_server/vwq/_query_validators/date_validators.py
index 661ec16ae..d20a3a365 100644
--- a/src/_mock_vws_server/vwq/_query_validators/date_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/date_validators.py
@@ -8,13 +8,13 @@
import pytz
import wrapt
+from flask import request
from requests import codes
from requests_mock.request import _RequestObjectProxy
from requests_mock.response import _Context
from .._constants import ResultCodes
from .._mock_common import json_dump
-from flask import request
@wrapt.decorator
@@ -37,7 +37,6 @@ def validate_date_header_given(
The result of calling the endpoint.
A `BAD_REQUEST` response if the date is not given.
"""
-
if 'Date' in request.headers:
return wrapped(*args, **kwargs)
@@ -45,7 +44,9 @@ def validate_date_header_given(
content_type = 'text/plain; charset=ISO-8859-1'
# TODO remove legacy
# context.headers['Content-Type'] = content_type
- return 'Date header required.', codes.BAD_REQUEST, {'Content-Type': content_type}
+ return 'Date header required.', codes.BAD_REQUEST, {
+ 'Content-Type': content_type
+ }
def _accepted_date_formats() -> Set[str]:
@@ -89,7 +90,7 @@ def validate_date_format(
The result of calling the endpoint.
An `UNAUTHORIZED` response if the date is in the wrong format.
"""
-
+
date_header = request.headers['Date']
for date_format in _accepted_date_formats():
@@ -107,7 +108,10 @@ def validate_date_format(
content_type = 'text/plain; charset=ISO-8859-1'
# TODO remove this legacy
# context.headers['Content-Type'] = content_type
- return text, codes.UNAUTHORIZED, {'Content-Type': content_type, 'WWW-Authenticate': 'VWS'}
+ return text, codes.UNAUTHORIZED, {
+ 'Content-Type': content_type,
+ 'WWW-Authenticate': 'VWS'
+ }
@wrapt.decorator
@@ -130,7 +134,7 @@ def validate_date_in_range(
The result of calling the endpoint.
A `FORBIDDEN` response if the date is out of range.
"""
-
+
date_header = request.headers['Date']
for date_format in _accepted_date_formats():
diff --git a/src/_mock_vws_server/vwq/_query_validators/image_validators.py b/src/_mock_vws_server/vwq/_query_validators/image_validators.py
index bcd5069b2..abe9c289e 100644
--- a/src/_mock_vws_server/vwq/_query_validators/image_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/image_validators.py
@@ -6,10 +6,10 @@
import io
import uuid
from typing import Any, Callable, Dict, Tuple
-from flask import request
import requests
import wrapt
+from flask import request
from PIL import Image
from requests import codes
from requests_mock.request import _RequestObjectProxy
@@ -39,7 +39,7 @@ def validate_image_field_given(
The result of calling the endpoint.
A ``BAD_REQUEST`` response if the image field is not given.
"""
-
+
body_file = io.BytesIO(request.body)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
@@ -125,7 +125,7 @@ def validate_image_dimensions(
An ``UNPROCESSABLE_ENTITY`` response if the image is given and is not
within the maximum width and height limits.
"""
-
+
body_file = io.BytesIO(request.body)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
@@ -179,7 +179,7 @@ def validate_image_format(
An `UNPROCESSABLE_ENTITY` response if the image is given and is not
either a PNG or a JPEG.
"""
-
+
body_file = io.BytesIO(request.body)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
@@ -233,7 +233,7 @@ def validate_image_is_image(
An `UNPROCESSABLE_ENTITY` response if image data is given and it is not
an image file.
"""
-
+
body_file = io.BytesIO(request.body)
_, pdict = cgi.parse_header(request.headers['Content-Type'])
From d8af7030fb23f021b220e917445fea693baef577 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 29 Feb 2020 14:43:54 +0000
Subject: [PATCH 0078/3455] Progress towards working query
---
src/_mock_vws_server/vwq/__init__.py | 44 +++++++++----------
.../content_length_validators.py | 23 +++++-----
2 files changed, 34 insertions(+), 33 deletions(-)
diff --git a/src/_mock_vws_server/vwq/__init__.py b/src/_mock_vws_server/vwq/__init__.py
index b47ea8b07..b5141db5d 100644
--- a/src/_mock_vws_server/vwq/__init__.py
+++ b/src/_mock_vws_server/vwq/__init__.py
@@ -56,29 +56,29 @@
@CLOUDRECO_FLASK_APP.before_request
-@validate_date_in_range
-@validate_date_format
-@validate_date_header_given
-@validate_include_target_data
-@validate_max_num_results
-@validate_image_file_size
-@validate_image_dimensions
-@validate_image_format
-@validate_image_is_image
-@validate_image_field_given
-@validate_extra_fields
-@validate_content_type_header
-@validate_accept_header
-@validate_project_state
-@validate_authorization
-@validate_client_key_exists
-@validate_auth_header_has_signature
-@validate_auth_header_number_of_parts
-@validate_auth_header_exists
-@validate_content_length_header_not_too_small
-@set_date_header
-@validate_content_length_header_not_too_large
@validate_content_length_header_is_int
+@validate_content_length_header_not_too_large
+@set_date_header
+@validate_content_length_header_not_too_small
+@validate_auth_header_exists
+@validate_auth_header_number_of_parts
+@validate_auth_header_has_signature
+@validate_client_key_exists
+@validate_authorization
+@validate_project_state
+@validate_accept_header
+@validate_content_type_header
+@validate_extra_fields
+@validate_image_field_given
+@validate_image_is_image
+@validate_image_format
+@validate_image_dimensions
+@validate_image_file_size
+@validate_max_num_results
+@validate_include_target_data
+@validate_date_header_given
+@validate_date_format
+@validate_date_in_range
def validate_request() -> None:
pass
diff --git a/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py b/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
index f81108432..39e6043ed 100644
--- a/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
@@ -7,6 +7,7 @@
import wrapt
from requests import codes
+from flask import request
from requests_mock.request import _RequestObjectProxy
from requests_mock.response import _Context
@@ -16,11 +17,11 @@
@wrapt.decorator
def validate_content_length_header_is_int(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the ``Content-Length`` header is an integer.
@@ -35,7 +36,7 @@ def validate_content_length_header_is_int(
A ``BAD_REQUEST`` response if the content length header is not an
integer.
"""
- request, context = args
+
given_content_length = request.headers['Content-Length']
try:
@@ -50,11 +51,11 @@ def validate_content_length_header_is_int(
@wrapt.decorator
def validate_content_length_header_not_too_large(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the ``Content-Length`` header is not too large.
@@ -69,10 +70,10 @@ def validate_content_length_header_not_too_large(
A ``GATEWAY_TIMEOUT`` response if the given content length header says
that the content length is greater than the body length.
"""
- request, context = args
+
given_content_length = request.headers['Content-Length']
- body_length = len(request.body if request.body else '')
+ body_length = len(request.data if request.data else '')
given_content_length_value = int(given_content_length)
if given_content_length_value > body_length:
context.status_code = codes.GATEWAY_TIMEOUT
@@ -84,11 +85,11 @@ def validate_content_length_header_not_too_large(
@wrapt.decorator
def validate_content_length_header_not_too_small(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the ``Content-Length`` header is not too small.
@@ -103,10 +104,10 @@ def validate_content_length_header_not_too_small(
An ``UNAUTHORIZED`` response if the given content length header says
that the content length is smaller than the body length.
"""
- request, context = args
+
given_content_length = request.headers['Content-Length']
- body_length = len(request.body if request.body else '')
+ body_length = len(request.data if request.data else '')
given_content_length_value = int(given_content_length)
if given_content_length_value < body_length:
From 80d6bf236e625d2332b5722583e369006ba60fc5 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 29 Feb 2020 15:31:40 +0000
Subject: [PATCH 0079/3455] Progress towards working query
---
src/_mock_vws_server/vwq/__init__.py | 5 +-
.../vwq/_query_validators/__init__.py | 29 +++++----
.../vwq/_query_validators/auth_validators.py | 60 +++++++++++--------
.../content_length_validators.py | 14 +++--
4 files changed, 64 insertions(+), 44 deletions(-)
diff --git a/src/_mock_vws_server/vwq/__init__.py b/src/_mock_vws_server/vwq/__init__.py
index b5141db5d..907439a5a 100644
--- a/src/_mock_vws_server/vwq/__init__.py
+++ b/src/_mock_vws_server/vwq/__init__.py
@@ -58,7 +58,6 @@
@CLOUDRECO_FLASK_APP.before_request
@validate_content_length_header_is_int
@validate_content_length_header_not_too_large
-@set_date_header
@validate_content_length_header_not_too_small
@validate_auth_header_exists
@validate_auth_header_number_of_parts
@@ -88,6 +87,10 @@ def set_headers(response: Response) -> Response:
response.headers['Connection'] = 'keep-alive'
if response.status_code != codes.INTERNAL_SERVER_ERROR:
response.headers['Content-Type'] = 'application/json'
+ if response.status_code == codes.UNSUPPORTED_MEDIA_TYPE:
+ # response.headers.pop('Content-Type')
+ # TODO we need to remove this somehow but I don't know how
+ response.headers['Content-Type'] = ''
response.headers['Server'] = 'nginx'
content_length = len(response.data)
response.headers['Content-Length'] = str(content_length)
diff --git a/src/_mock_vws_server/vwq/_query_validators/__init__.py b/src/_mock_vws_server/vwq/_query_validators/__init__.py
index 93c7c3376..1438e3222 100644
--- a/src/_mock_vws_server/vwq/_query_validators/__init__.py
+++ b/src/_mock_vws_server/vwq/_query_validators/__init__.py
@@ -19,6 +19,7 @@
from .._constants import ResultCodes
from .._database_matchers import get_database_matching_client_keys
from .._mock_common import parse_multipart
+from ...vws._databases import get_all_databases
@wrapt.decorator
@@ -43,12 +44,13 @@ def validate_project_state(
project is inactive.
"""
+ databases = get_all_databases()
database = get_database_matching_client_keys(
request_headers=request.headers,
request_body=request.data,
request_method=request.method,
request_path=request.path,
- databases=instance.databases,
+ databases=databases,
)
assert isinstance(database, VuforiaDatabase)
@@ -176,8 +178,7 @@ def validate_include_target_data(
"'include_target_data'. "
"Expecting one of the (unquoted) string values 'all', 'none' or 'top'."
)
- context.status_code = codes.BAD_REQUEST
- return unexpected_target_data_message
+ return unexpected_target_data_message, codes.BAD_REQUEST
@wrapt.decorator
@@ -206,25 +207,29 @@ def validate_content_type_header(
main_value, pdict = cgi.parse_header(request.headers['Content-Type'])
if main_value != 'multipart/form-data':
- context.status_code = codes.UNSUPPORTED_MEDIA_TYPE
- context.headers.pop('Content-Type')
- return ''
+ # context.status_code = codes.UNSUPPORTED_MEDIA_TYPE
+ # TODO Do this somehow
+ # context.headers.pop('Content-Type')
+ return '', codes.UNSUPPORTED_MEDIA_TYPE
if 'boundary' not in pdict:
- context.status_code = codes.BAD_REQUEST
- context.headers['Content-Type'] = 'text/html;charset=UTF-8'
+ # context.status_code = codes.BAD_REQUEST
+ # context.headers['Content-Type'] = 'text/html;charset=UTF-8'
+ content_type = 'text/html; charset=UTF-8'
return (
'java.io.IOException: RESTEASY007550: '
'Unable to get boundary for multipart'
- )
+ ), codes.BAD_REQUEST, {'Content-Type': content_type}
if pdict['boundary'].encode() not in request.data:
- context.status_code = codes.BAD_REQUEST
- context.headers['Content-Type'] = 'text/html;charset=UTF-8'
+ # TODO
+ # context.status_code = codes.BAD_REQUEST
+ content_type = 'text/html; charset=UTF-8'
+ # context.headers['Content-Type'] = content_type
return (
'java.lang.RuntimeException: RESTEASY007500: '
'Could find no Content-Disposition header within part'
- )
+ ), codes.BAD_REQUEST, {'Content-Type': content_type}
return wrapped(*args, **kwargs)
diff --git a/src/_mock_vws_server/vwq/_query_validators/auth_validators.py b/src/_mock_vws_server/vwq/_query_validators/auth_validators.py
index c2f789d7b..33964388e 100644
--- a/src/_mock_vws_server/vwq/_query_validators/auth_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/auth_validators.py
@@ -10,6 +10,8 @@
from requests import codes
from requests_mock.request import _RequestObjectProxy
from requests_mock.response import _Context
+from flask import request
+from ...vws._databases import get_all_databases
from .._constants import ResultCodes
from .._database_matchers import get_database_matching_client_keys
@@ -17,11 +19,11 @@
@wrapt.decorator
def validate_auth_header_exists(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate that there is an authorization header given to the query endpoint.
@@ -35,7 +37,7 @@ def validate_auth_header_exists(
The result of calling the endpoint.
An `UNAUTHORIZED` response if there is no "Authorization" header.
"""
- request, context = args
+
if 'Authorization' in request.headers:
return wrapped(*args, **kwargs)
@@ -49,11 +51,11 @@ def validate_auth_header_exists(
@wrapt.decorator
def validate_auth_header_number_of_parts(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the authorization header includes text either side of a space.
@@ -68,7 +70,7 @@ def validate_auth_header_number_of_parts(
An ``UNAUTHORIZED`` response if the "Authorization" header is not as
expected.
"""
- request, context = args
+
header = request.headers['Authorization']
parts = header.split(' ')
@@ -85,11 +87,11 @@ def validate_auth_header_number_of_parts(
@wrapt.decorator
def validate_client_key_exists(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any,
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the authorization header includes a client key for a database.
@@ -103,12 +105,13 @@ def validate_client_key_exists(
The result of calling the endpoint.
An ``UNAUTHORIZED`` response if the client key is unknown.
"""
- request, context = args
+
header = request.headers['Authorization']
first_part, _ = header.split(':')
_, access_key = first_part.split(' ')
- for database in instance.databases:
+ databases = get_all_databases()
+ for database in databases:
if access_key == database.client_access_key:
return wrapped(*args, **kwargs)
@@ -127,11 +130,11 @@ def validate_client_key_exists(
@wrapt.decorator
def validate_auth_header_has_signature(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any, # pylint: disable=unused-argument
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the authorization header includes a signature.
@@ -146,30 +149,34 @@ def validate_auth_header_has_signature(
An ``UNAUTHORIZED`` response if the "Authorization" header is not as
expected.
"""
- request, context = args
+
header = request.headers['Authorization']
if header.count(':') == 1 and header.split(':')[1]:
return wrapped(*args, **kwargs)
- context.status_code = codes.INTERNAL_SERVER_ERROR
+ # context.status_code = codes.INTERNAL_SERVER_ERROR
current_parent = Path(__file__).parent
resources = current_parent / 'resources'
known_response = resources / 'query_out_of_bounds_response'
content_type = 'text/html; charset=ISO-8859-1'
- context.headers['Content-Type'] = content_type
+ # TODO
+ # context.headers['Content-Type'] = content_type
cache_control = 'must-revalidate,no-cache,no-store'
- context.headers['Cache-Control'] = cache_control
- return known_response.read_text()
+ # context.headers['Cache-Control'] = cache_control
+ return known_response.read_text(), codes.INTERNAL_SERVER_ERROR, {
+ 'Content-Type': content_type,
+ 'Cache-Control': cache_control,
+ }
@wrapt.decorator
def validate_authorization(
- wrapped: Callable[..., str],
+ wrapped: Callable[..., Tuple[str, int]],
instance: Any,
args: Tuple[_RequestObjectProxy, _Context],
kwargs: Dict,
-) -> str:
+) -> Tuple[str, int]:
"""
Validate the authorization header given to the query endpoint.
@@ -184,21 +191,24 @@ def validate_authorization(
A `BAD_REQUEST` response if the "Authorization" header is not as
expected.
"""
- request, context = args
+
+ databases = get_all_databases()
database = get_database_matching_client_keys(
request_headers=request.headers,
- request_body=request.body,
+ request_body=request.data,
request_method=request.method,
request_path=request.path,
- databases=instance.databases,
+ databases=databases,
)
if database is not None:
return wrapped(*args, **kwargs)
- context.status_code = codes.UNAUTHORIZED
- context.headers['WWW-Authenticate'] = 'VWS'
+ # TODO
+ # context.status_code = codes.UNAUTHORIZED
+ # TODO
+ # context.headers['WWW-Authenticate'] = 'VWS'
transaction_id = uuid.uuid4().hex
result_code = ResultCodes.AUTHENTICATION_FAILURE.value
text = (
@@ -207,4 +217,4 @@ def validate_authorization(
f'"result_code":"{result_code}"'
'}'
)
- return text
+ return text, codes.UNAUTHORIZED, {'WWW-Authenticate': 'VWS'}
diff --git a/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py b/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
index 39e6043ed..9661d5e0a 100644
--- a/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
@@ -42,9 +42,10 @@ def validate_content_length_header_is_int(
try:
int(given_content_length)
except ValueError:
- context.status_code = codes.BAD_REQUEST
- context.headers = {'Connection': 'Close'}
- return ''
+ # TODO remove legacy
+ # context.status_code = codes.BAD_REQUEST
+ # context.headers = {'Connection': 'Close'}
+ return '', codes.BAD_REQUEST, {'Connection': 'Close'}
return wrapped(*args, **kwargs)
@@ -76,9 +77,10 @@ def validate_content_length_header_not_too_large(
body_length = len(request.data if request.data else '')
given_content_length_value = int(given_content_length)
if given_content_length_value > body_length:
- context.status_code = codes.GATEWAY_TIMEOUT
- context.headers = {'Connection': 'keep-alive'}
- return ''
+ # TODO Remove legacy
+ # context.status_code = codes.GATEWAY_TIMEOUT
+ # context.headers = {'Connection': 'keep-alive'}
+ return '', codes.GATEWAY_TIMEOUT, {'Connection': 'keep-alive'}
return wrapped(*args, **kwargs)
From 6db81d50f060271307018fc609a32d9e73a082e8 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 1 Mar 2020 04:33:25 +0000
Subject: [PATCH 0080/3455] Progress towards working query
---
src/_mock_vws_server/vwq/__init__.py | 6 ++----
.../vwq/_query_validators/__init__.py | 7 +++++--
.../vwq/_query_validators/auth_validators.py | 2 +-
.../_query_validators/content_length_validators.py | 5 +++--
.../vwq/_query_validators/image_validators.py | 12 ++++++------
.../_query_validators/content_length_validators.py | 1 +
6 files changed, 18 insertions(+), 15 deletions(-)
diff --git a/src/_mock_vws_server/vwq/__init__.py b/src/_mock_vws_server/vwq/__init__.py
index 907439a5a..c14130ea9 100644
--- a/src/_mock_vws_server/vwq/__init__.py
+++ b/src/_mock_vws_server/vwq/__init__.py
@@ -101,11 +101,9 @@ def set_headers(response: Response) -> Response:
@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
def query() -> Tuple[str, int]:
- body_file = io.BytesIO(request.data)
+ body_file = io.BytesIO(request.input_stream.getvalue())
_, pdict = cgi.parse_header(request.headers['Content-Type'])
- import pdb
- pdb.set_trace()
parsed = parse_multipart(
fp=body_file,
pdict={
@@ -138,7 +136,7 @@ def query() -> Tuple[str, int]:
database = get_database_matching_client_keys(
request_headers=dict(request.headers),
- request_body=request.data,
+ request_body=request.input_stream.getvalue(),
request_method=request.method,
request_path=request.path,
databases=databases,
diff --git a/src/_mock_vws_server/vwq/_query_validators/__init__.py b/src/_mock_vws_server/vwq/_query_validators/__init__.py
index 1438e3222..ca2a50f99 100644
--- a/src/_mock_vws_server/vwq/_query_validators/__init__.py
+++ b/src/_mock_vws_server/vwq/_query_validators/__init__.py
@@ -47,7 +47,7 @@ def validate_project_state(
databases = get_all_databases()
database = get_database_matching_client_keys(
request_headers=request.headers,
- request_body=request.data,
+ request_body=request.input_stream.getvalue(),
request_method=request.method,
request_path=request.path,
databases=databases,
@@ -114,15 +114,18 @@ def validate_max_num_results(
try:
max_num_results_int = int(max_num_results)
except ValueError:
+ import pdb; pdb.set_trace()
context.status_code = codes.BAD_REQUEST
return invalid_type_error
java_max_int = 2147483647
if max_num_results_int > java_max_int:
+ import pdb; pdb.set_trace()
context.status_code = codes.BAD_REQUEST
return invalid_type_error
if max_num_results_int < 1 or max_num_results_int > 50:
+ import pdb; pdb.set_trace()
context.status_code = codes.BAD_REQUEST
out_of_range_error = (
f'Integer out of range ({max_num_results_int}) in form data part '
@@ -221,7 +224,7 @@ def validate_content_type_header(
'Unable to get boundary for multipart'
), codes.BAD_REQUEST, {'Content-Type': content_type}
- if pdict['boundary'].encode() not in request.data:
+ if pdict['boundary'].encode() not in request.input_stream.getvalue():
# TODO
# context.status_code = codes.BAD_REQUEST
content_type = 'text/html; charset=UTF-8'
diff --git a/src/_mock_vws_server/vwq/_query_validators/auth_validators.py b/src/_mock_vws_server/vwq/_query_validators/auth_validators.py
index 33964388e..b05d29fcc 100644
--- a/src/_mock_vws_server/vwq/_query_validators/auth_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/auth_validators.py
@@ -196,7 +196,7 @@ def validate_authorization(
databases = get_all_databases()
database = get_database_matching_client_keys(
request_headers=request.headers,
- request_body=request.data,
+ request_body=request.input_stream.getvalue(),
request_method=request.method,
request_path=request.path,
databases=databases,
diff --git a/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py b/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
index 9661d5e0a..d6d8480f7 100644
--- a/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
@@ -74,12 +74,13 @@ def validate_content_length_header_not_too_large(
given_content_length = request.headers['Content-Length']
- body_length = len(request.data if request.data else '')
+ body_length = len(request.input_stream.getvalue())
given_content_length_value = int(given_content_length)
if given_content_length_value > body_length:
# TODO Remove legacy
# context.status_code = codes.GATEWAY_TIMEOUT
# context.headers = {'Connection': 'keep-alive'}
+ import pdb; pdb.set_trace()
return '', codes.GATEWAY_TIMEOUT, {'Connection': 'keep-alive'}
return wrapped(*args, **kwargs)
@@ -109,7 +110,7 @@ def validate_content_length_header_not_too_small(
given_content_length = request.headers['Content-Length']
- body_length = len(request.data if request.data else '')
+ body_length = len(request.input_stream.getvalue())
given_content_length_value = int(given_content_length)
if given_content_length_value < body_length:
diff --git a/src/_mock_vws_server/vwq/_query_validators/image_validators.py b/src/_mock_vws_server/vwq/_query_validators/image_validators.py
index abe9c289e..6954fcedc 100644
--- a/src/_mock_vws_server/vwq/_query_validators/image_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/image_validators.py
@@ -40,7 +40,7 @@ def validate_image_field_given(
A ``BAD_REQUEST`` response if the image field is not given.
"""
- body_file = io.BytesIO(request.body)
+ body_file = io.BytesIO(request.input_stream.getvalue())
_, pdict = cgi.parse_header(request.headers['Content-Type'])
parsed = parse_multipart(
@@ -78,8 +78,8 @@ def validate_image_file_size(
Raises:
requests.exceptions.ConnectionError: The image file size is too large.
"""
- request, _ = args
- body_file = io.BytesIO(request.body)
+
+ body_file = io.BytesIO(request.input_stream.getvalue())
_, pdict = cgi.parse_header(request.headers['Content-Type'])
parsed = parse_multipart(
@@ -126,7 +126,7 @@ def validate_image_dimensions(
within the maximum width and height limits.
"""
- body_file = io.BytesIO(request.body)
+ body_file = io.BytesIO(request.input_stream.getvalue())
_, pdict = cgi.parse_header(request.headers['Content-Type'])
parsed = parse_multipart(
@@ -180,7 +180,7 @@ def validate_image_format(
either a PNG or a JPEG.
"""
- body_file = io.BytesIO(request.body)
+ body_file = io.BytesIO(request.input_stream.getvalue())
_, pdict = cgi.parse_header(request.headers['Content-Type'])
parsed = parse_multipart(
@@ -234,7 +234,7 @@ def validate_image_is_image(
an image file.
"""
- body_file = io.BytesIO(request.body)
+ body_file = io.BytesIO(request.input_stream.getvalue())
_, pdict = cgi.parse_header(request.headers['Content-Type'])
parsed = parse_multipart(
diff --git a/src/mock_vws/_query_validators/content_length_validators.py b/src/mock_vws/_query_validators/content_length_validators.py
index f81108432..bfe218828 100644
--- a/src/mock_vws/_query_validators/content_length_validators.py
+++ b/src/mock_vws/_query_validators/content_length_validators.py
@@ -74,6 +74,7 @@ def validate_content_length_header_not_too_large(
body_length = len(request.body if request.body else '')
given_content_length_value = int(given_content_length)
+ import pdb; pdb.set_trace()
if given_content_length_value > body_length:
context.status_code = codes.GATEWAY_TIMEOUT
context.headers = {'Connection': 'keep-alive'}
From 6c2837be2648abc16fa0632d7e158c4eec982cf5 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 1 Mar 2020 04:50:16 +0000
Subject: [PATCH 0081/3455] Passing query on success
---
src/_mock_vws_server/storage/__init__.py | 1 +
src/_mock_vws_server/vws/_databases.py | 18 +++++++++---------
src/mock_vws/target.py | 8 ++++----
3 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index 019fd3e8a..38bde6767 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -73,6 +73,7 @@ def create_target(database_name: str) -> Tuple[str, int]:
target.target_id = request.json['target_id']
database.targets.append(target)
+ import pdb; pdb.set_trace()
return jsonify(target.to_dict()), codes.CREATED
diff --git a/src/_mock_vws_server/vws/_databases.py b/src/_mock_vws_server/vws/_databases.py
index 487cc41b3..277c22047 100644
--- a/src/_mock_vws_server/vws/_databases.py
+++ b/src/_mock_vws_server/vws/_databases.py
@@ -57,21 +57,21 @@ def get_all_databases() -> Set[VuforiaDatabase]:
)
target.target_id = target_dict['target_id']
gmt = pytz.timezone('GMT')
- target.last_modified_date = datetime.datetime.fromordinal(
- target_dict['last_modified_date_ordinal']
+ target.last_modified_date = datetime.datetime.fromisoformat(
+ target_dict['last_modified_date']
)
target.last_modified_date = target.last_modified_date.replace(
tzinfo=gmt
)
- target.upload_date = datetime.datetime.fromordinal(
- target_dict['upload_date_ordinal']
+ target.upload_date = datetime.datetime.fromisoformat(
+ target_dict['upload_date']
)
target.upload_date = target.upload_date.replace(tzinfo=gmt)
- delete_date_optional_ordinal = target_dict[
- 'delete_date_optional_ordinal']
- if delete_date_optional_ordinal:
- target.delete_date = datetime.datetime.fromordinal(
- delete_date_optional_ordinal
+ delete_date_optional = target_dict[
+ 'delete_date_optional']
+ if delete_date_optional:
+ target.delete_date = datetime.datetime.fromisoformat(
+ delete_date_optional
)
target.delete_date = target.delete_date.replace(tzinfo=gmt)
new_database.targets.append(target)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index defb3c203..51243fb72 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -180,7 +180,7 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
# as can e.g. processing time... maybe use dataclass but then
# https://github.com/agronholm/sphinx-autodoc-typehints/issues/123
if self.delete_date:
- delete_date: Optional[int] = datetime.datetime.toordinal(
+ delete_date: Optional[int] = datetime.datetime.isoformat(
self.delete_date
)
else:
@@ -194,7 +194,7 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
'processing_time_seconds': self._processing_time_seconds,
'application_metadata': self.application_metadata,
'target_id': self.target_id,
- 'last_modified_date_ordinal': self.last_modified_date.toordinal(),
- 'delete_date_optional_ordinal': delete_date,
- 'upload_date_ordinal': self.upload_date.toordinal(),
+ 'last_modified_date': self.last_modified_date.isoformat(),
+ 'delete_date_optional': delete_date,
+ 'upload_date': self.upload_date.isoformat(),
}
From b2afc0c6e78d219f3bcf0a73e57678c76d6bc206 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 1 Mar 2020 04:51:40 +0000
Subject: [PATCH 0082/3455] Remove some pdbs
---
src/_mock_vws_server/storage/__init__.py | 1 -
src/_mock_vws_server/vwq/_query_validators/__init__.py | 3 ---
.../vwq/_query_validators/content_length_validators.py | 1 -
src/mock_vws/_query_validators/content_length_validators.py | 1 -
4 files changed, 6 deletions(-)
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/_mock_vws_server/storage/__init__.py
index 38bde6767..019fd3e8a 100644
--- a/src/_mock_vws_server/storage/__init__.py
+++ b/src/_mock_vws_server/storage/__init__.py
@@ -73,7 +73,6 @@ def create_target(database_name: str) -> Tuple[str, int]:
target.target_id = request.json['target_id']
database.targets.append(target)
- import pdb; pdb.set_trace()
return jsonify(target.to_dict()), codes.CREATED
diff --git a/src/_mock_vws_server/vwq/_query_validators/__init__.py b/src/_mock_vws_server/vwq/_query_validators/__init__.py
index ca2a50f99..0b6d8518c 100644
--- a/src/_mock_vws_server/vwq/_query_validators/__init__.py
+++ b/src/_mock_vws_server/vwq/_query_validators/__init__.py
@@ -114,18 +114,15 @@ def validate_max_num_results(
try:
max_num_results_int = int(max_num_results)
except ValueError:
- import pdb; pdb.set_trace()
context.status_code = codes.BAD_REQUEST
return invalid_type_error
java_max_int = 2147483647
if max_num_results_int > java_max_int:
- import pdb; pdb.set_trace()
context.status_code = codes.BAD_REQUEST
return invalid_type_error
if max_num_results_int < 1 or max_num_results_int > 50:
- import pdb; pdb.set_trace()
context.status_code = codes.BAD_REQUEST
out_of_range_error = (
f'Integer out of range ({max_num_results_int}) in form data part '
diff --git a/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py b/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
index d6d8480f7..c8e16b8f4 100644
--- a/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
@@ -80,7 +80,6 @@ def validate_content_length_header_not_too_large(
# TODO Remove legacy
# context.status_code = codes.GATEWAY_TIMEOUT
# context.headers = {'Connection': 'keep-alive'}
- import pdb; pdb.set_trace()
return '', codes.GATEWAY_TIMEOUT, {'Connection': 'keep-alive'}
return wrapped(*args, **kwargs)
diff --git a/src/mock_vws/_query_validators/content_length_validators.py b/src/mock_vws/_query_validators/content_length_validators.py
index bfe218828..f81108432 100644
--- a/src/mock_vws/_query_validators/content_length_validators.py
+++ b/src/mock_vws/_query_validators/content_length_validators.py
@@ -74,7 +74,6 @@ def validate_content_length_header_not_too_large(
body_length = len(request.body if request.body else '')
given_content_length_value = int(given_content_length)
- import pdb; pdb.set_trace()
if given_content_length_value > body_length:
context.status_code = codes.GATEWAY_TIMEOUT
context.headers = {'Connection': 'keep-alive'}
From a0706bf2ccb12b8d72d59e440cd906601eb65912 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 21 Mar 2020 18:51:33 +0000
Subject: [PATCH 0083/3455] Fix 3 tests (82 now failing) by copying an expected
test response
---
.../vwq/resources/match_processing_response | 78 +++++++++++++++++++
1 file changed, 78 insertions(+)
create mode 100644 src/_mock_vws_server/vwq/resources/match_processing_response
diff --git a/src/_mock_vws_server/vwq/resources/match_processing_response b/src/_mock_vws_server/vwq/resources/match_processing_response
new file mode 100644
index 000000000..33d48f115
--- /dev/null
+++ b/src/_mock_vws_server/vwq/resources/match_processing_response
@@ -0,0 +1,78 @@
+'\n\n\nError 500 Server Error
+title>\n\nHTTP ERROR 500
\nProblem accessing /v1/query. Reason:\n
Server Error
+p>Caused by:
org.jboss.resteasy.spi.UnhandledException: com.fasterxml.jackson.databind.exc.MismatchedInpu
+tException: No content to map due to end-of-input\n at [Source: (byte[])""; line: 1, column: 0]\n\tat org.jboss.restea
+sy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:76)\n\tat org.jboss.resteasy.core.ExceptionH
+andler.handleException(ExceptionHandler.java:212)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.writeException(S
+ynchronousDispatcher.java:168)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:4
+11)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:202)\n\tat org.jboss.resteas
+y.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:221)\n\tat org.jboss.reste
+asy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)\n\tat org.jboss.resteasy.plugi
+ns.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)\n\tat javax.servlet.http.HttpServlet.se
+rvice(HttpServlet.java:790)\n\tat org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:808)\n\tat org.ecl
+ipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669)\n\tat com.kooaba.queryservice.auth.KW
+SAuthFilter.doFilter(KWSAuthFilter.java:171)\n\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(Servl
+etHandler.java:1652)\n\tat org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)\n\tat org.eclips
+e.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)\n\tat org.eclipse.jetty.security.SecurityHandler.h
+andle(SecurityHandler.java:577)\n\tat org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223
+)\n\tat org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)\n\tat org.eclipse.jetty.ser
+vlet.ServletHandler.doScope(ServletHandler.java:515)\n\tat org.eclipse.jetty.server.session.SessionHandler.doScope(Ses
+sionHandler.java:185)\n\tat org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)\n\tat or
+g.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)\n\tat org.eclipse.jetty.server.handler.Con
+textHandlerCollection.handle(ContextHandlerCollection.java:215)\n\tat org.eclipse.jetty.server.handler.HandlerCollecti
+on.handle(HandlerCollection.java:110)\n\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java
+:97)\n\tat org.eclipse.jetty.server.Server.handle(Server.java:497)\n\tat org.eclipse.jetty.server.HttpChannel.handle(H
+ttpChannel.java:310)\n\tat org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)\n\tat org.eclip
+se.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool
+.runJob(QueuedThreadPool.java:635)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:55
+5)\n\tat java.lang.Thread.run(Thread.java:748)\nCaused by: com.fasterxml.jackson.databind.exc.MismatchedInputException
+: No content to map due to end-of-input\n at [Source: (byte[])""; line: 1, column: 0]\n\tat com.fasterxml.jackson.data
+bind.exc.MismatchedInputException.from(MismatchedInputException.java:59)\n\tat com.fasterxml.jackson.databind.ObjectMa
+pper._initForReading(ObjectMapper.java:4133)\n\tat com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(Object
+Mapper.java:3988)\n\tat com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3094)\n\tat com.kooaba
+.queryservice.domain.WebResult.setTargetData(WebResult.java:44)\n\tat com.kooaba.queryservice.domain.WebQueryResultPro
+cessor.formatResult(WebQueryResultProcessor.java:81)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.query
+Common(QueryResourceVuforia.java:230)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.limitedConcurrencyQu
+ery(QueryResourceVuforia.java:77)\n\tat com.kooaba.queryservice.services.QueryResourceCloudRecoWebAPI.query(QueryResou
+rceCloudRecoWebAPI.java:55)\n\tat sun.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)\n\tat sun.reflect.Deleg
+atingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java
+:606)\n\tat org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:139)\n\tat org.jboss.resteasy.co
+re.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:295)\n\tat org.jboss.resteasy.core.ResourceMethodIn
+voker.invoke(ResourceMethodInvoker.java:249)\n\tat org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethod
+Invoker.java:236)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:395)\n\t... 28
+ more\n
\nCaused by:
com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map
+due to end-of-input\n at [Source: (byte[])""; line: 1, column: 0]\n\tat com.fasterxml.jackson.databind.exc.MismatchedI
+nputException.from(MismatchedInputException.java:59)\n\tat com.fasterxml.jackson.databind.ObjectMapper._initForReading
+(ObjectMapper.java:4133)\n\tat com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3988)\n\
+tat com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3094)\n\tat com.kooaba.queryservice.domain
+.WebResult.setTargetData(WebResult.java:44)\n\tat com.kooaba.queryservice.domain.WebQueryResultProcessor.formatResult(
+WebQueryResultProcessor.java:81)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.queryCommon(QueryResource
+Vuforia.java:230)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.limitedConcurrencyQuery(QueryResourceVuf
+oria.java:77)\n\tat com.kooaba.queryservice.services.QueryResourceCloudRecoWebAPI.query(QueryResourceCloudRecoWebAPI.j
+ava:55)\n\tat sun.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorI
+mpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:606)\n\tat org.jbos
+s.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:139)\n\tat org.jboss.resteasy.core.ResourceMethodInv
+oker.invokeOnTarget(ResourceMethodInvoker.java:295)\n\tat org.jboss.resteasy.core.ResourceMethodInvoker.invoke(Resourc
+eMethodInvoker.java:249)\n\tat org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:236)\n\
+tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:395)\n\tat org.jboss.resteasy.core
+.SynchronousDispatcher.invoke(SynchronousDispatcher.java:202)\n\tat org.jboss.resteasy.plugins.server.servlet.ServletC
+ontainerDispatcher.service(ServletContainerDispatcher.java:221)\n\tat org.jboss.resteasy.plugins.server.servlet.HttpSe
+rvletDispatcher.service(HttpServletDispatcher.java:56)\n\tat org.jboss.resteasy.plugins.server.servlet.HttpServletDisp
+atcher.service(HttpServletDispatcher.java:51)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\tat
+ org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:808)\n\tat org.eclipse.jetty.servlet.ServletHandler
+$CachedChain.doFilter(ServletHandler.java:1669)\n\tat com.kooaba.queryservice.auth.KWSAuthFilter.doFilter(KWSAuthFilte
+r.java:171)\n\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)\n\tat org.ec
+lipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)\n\tat org.eclipse.jetty.server.handler.ScopedHand
+ler.handle(ScopedHandler.java:143)\n\tat org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)\n
+\tat org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)\n\tat org.eclipse.jetty.server.
+handler.ContextHandler.doHandle(ContextHandler.java:1127)\n\tat org.eclipse.jetty.servlet.ServletHandler.doScope(Servl
+etHandler.java:515)\n\tat org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)\n\tat org.e
+clipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)\n\tat org.eclipse.jetty.server.handler.Sc
+opedHandler.handle(ScopedHandler.java:141)\n\tat org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(Cont
+extHandlerCollection.java:215)\n\tat org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:
+110)\n\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)\n\tat org.eclipse.jetty.serv
+er.Server.handle(Server.java:497)\n\tat org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)\n\tat org.ec
+lipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)\n\tat org.eclipse.jetty.io.AbstractConnection$2.
+run(AbstractConnection.java:540)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635
+)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)\n\tat java.lang.Thread.run(Thr
+ead.java:748)\n
\n
Powered by Jetty://
\n\n\n\n'
From 158d47dcd1e83d488c943607641ac6519424e88e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 21 Mar 2020 18:58:51 +0000
Subject: [PATCH 0084/3455] Fix a few lint issues
---
src/_mock_vws_server/vwq/__init__.py | 4 +--
.../vwq/_query_validators/__init__.py | 10 ++++--
.../vwq/_query_validators/auth_validators.py | 10 ++----
.../content_length_validators.py | 8 ++---
.../vwq/_query_validators/date_validators.py | 4 +--
.../vwq/_query_validators/image_validators.py | 2 +-
src/_mock_vws_server/vws/__init__.py | 32 +++++++++++--------
src/_mock_vws_server/vws/_databases.py | 11 +++----
.../vws/_services_validators/__init__.py | 6 ++--
src/mock_vws/database.py | 11 +++++--
src/mock_vws/target.py | 2 +-
11 files changed, 54 insertions(+), 46 deletions(-)
diff --git a/src/_mock_vws_server/vwq/__init__.py b/src/_mock_vws_server/vwq/__init__.py
index c14130ea9..a8a67bc24 100644
--- a/src/_mock_vws_server/vwq/__init__.py
+++ b/src/_mock_vws_server/vwq/__init__.py
@@ -14,7 +14,7 @@
from mock_vws._base64_decoding import decode_base64
from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_client_keys
-from mock_vws._mock_common import json_dump, parse_multipart, set_date_header
+from mock_vws._mock_common import json_dump, parse_multipart
from mock_vws.database import VuforiaDatabase
# TODO move this
@@ -199,7 +199,7 @@ def query() -> Tuple[str, int]:
codes.INTERNAL_SERVER_ERROR,
{
'Cache-Control': cache_control,
- 'Content-Type': content_type
+ 'Content-Type': content_type,
},
)
diff --git a/src/_mock_vws_server/vwq/_query_validators/__init__.py b/src/_mock_vws_server/vwq/_query_validators/__init__.py
index 0b6d8518c..d2d5f172b 100644
--- a/src/_mock_vws_server/vwq/_query_validators/__init__.py
+++ b/src/_mock_vws_server/vwq/_query_validators/__init__.py
@@ -16,10 +16,10 @@
from mock_vws.database import VuforiaDatabase
from mock_vws.states import States
+from ...vws._databases import get_all_databases
from .._constants import ResultCodes
from .._database_matchers import get_database_matching_client_keys
from .._mock_common import parse_multipart
-from ...vws._databases import get_all_databases
@wrapt.decorator
@@ -219,7 +219,9 @@ def validate_content_type_header(
return (
'java.io.IOException: RESTEASY007550: '
'Unable to get boundary for multipart'
- ), codes.BAD_REQUEST, {'Content-Type': content_type}
+ ), codes.BAD_REQUEST, {
+ 'Content-Type': content_type,
+ }
if pdict['boundary'].encode() not in request.input_stream.getvalue():
# TODO
@@ -229,7 +231,9 @@ def validate_content_type_header(
return (
'java.lang.RuntimeException: RESTEASY007500: '
'Could find no Content-Disposition header within part'
- ), codes.BAD_REQUEST, {'Content-Type': content_type}
+ ), codes.BAD_REQUEST, {
+ 'Content-Type': content_type,
+ }
return wrapped(*args, **kwargs)
diff --git a/src/_mock_vws_server/vwq/_query_validators/auth_validators.py b/src/_mock_vws_server/vwq/_query_validators/auth_validators.py
index b05d29fcc..73517af68 100644
--- a/src/_mock_vws_server/vwq/_query_validators/auth_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/auth_validators.py
@@ -7,12 +7,12 @@
from typing import Any, Callable, Dict, Tuple
import wrapt
+from flask import request
from requests import codes
from requests_mock.request import _RequestObjectProxy
from requests_mock.response import _Context
-from flask import request
-from ...vws._databases import get_all_databases
+from ...vws._databases import get_all_databases
from .._constants import ResultCodes
from .._database_matchers import get_database_matching_client_keys
@@ -37,7 +37,7 @@ def validate_auth_header_exists(
The result of calling the endpoint.
An `UNAUTHORIZED` response if there is no "Authorization" header.
"""
-
+
if 'Authorization' in request.headers:
return wrapped(*args, **kwargs)
@@ -70,7 +70,6 @@ def validate_auth_header_number_of_parts(
An ``UNAUTHORIZED`` response if the "Authorization" header is not as
expected.
"""
-
header = request.headers['Authorization']
parts = header.split(' ')
@@ -105,7 +104,6 @@ def validate_client_key_exists(
The result of calling the endpoint.
An ``UNAUTHORIZED`` response if the client key is unknown.
"""
-
header = request.headers['Authorization']
first_part, _ = header.split(':')
@@ -149,7 +147,6 @@ def validate_auth_header_has_signature(
An ``UNAUTHORIZED`` response if the "Authorization" header is not as
expected.
"""
-
header = request.headers['Authorization']
if header.count(':') == 1 and header.split(':')[1]:
@@ -191,7 +188,6 @@ def validate_authorization(
A `BAD_REQUEST` response if the "Authorization" header is not as
expected.
"""
-
databases = get_all_databases()
database = get_database_matching_client_keys(
diff --git a/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py b/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
index c8e16b8f4..4cc32b5e2 100644
--- a/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
@@ -6,8 +6,8 @@
from typing import Any, Callable, Dict, Tuple
import wrapt
-from requests import codes
from flask import request
+from requests import codes
from requests_mock.request import _RequestObjectProxy
from requests_mock.response import _Context
@@ -36,7 +36,7 @@ def validate_content_length_header_is_int(
A ``BAD_REQUEST`` response if the content length header is not an
integer.
"""
-
+
given_content_length = request.headers['Content-Length']
try:
@@ -71,7 +71,7 @@ def validate_content_length_header_not_too_large(
A ``GATEWAY_TIMEOUT`` response if the given content length header says
that the content length is greater than the body length.
"""
-
+
given_content_length = request.headers['Content-Length']
body_length = len(request.input_stream.getvalue())
@@ -106,7 +106,7 @@ def validate_content_length_header_not_too_small(
An ``UNAUTHORIZED`` response if the given content length header says
that the content length is smaller than the body length.
"""
-
+
given_content_length = request.headers['Content-Length']
body_length = len(request.input_stream.getvalue())
diff --git a/src/_mock_vws_server/vwq/_query_validators/date_validators.py b/src/_mock_vws_server/vwq/_query_validators/date_validators.py
index d20a3a365..9e0de47f8 100644
--- a/src/_mock_vws_server/vwq/_query_validators/date_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/date_validators.py
@@ -45,7 +45,7 @@ def validate_date_header_given(
# TODO remove legacy
# context.headers['Content-Type'] = content_type
return 'Date header required.', codes.BAD_REQUEST, {
- 'Content-Type': content_type
+ 'Content-Type': content_type,
}
@@ -110,7 +110,7 @@ def validate_date_format(
# context.headers['Content-Type'] = content_type
return text, codes.UNAUTHORIZED, {
'Content-Type': content_type,
- 'WWW-Authenticate': 'VWS'
+ 'WWW-Authenticate': 'VWS',
}
diff --git a/src/_mock_vws_server/vwq/_query_validators/image_validators.py b/src/_mock_vws_server/vwq/_query_validators/image_validators.py
index 6954fcedc..5da0ff2b0 100644
--- a/src/_mock_vws_server/vwq/_query_validators/image_validators.py
+++ b/src/_mock_vws_server/vwq/_query_validators/image_validators.py
@@ -78,7 +78,7 @@ def validate_image_file_size(
Raises:
requests.exceptions.ConnectionError: The image file size is too large.
"""
-
+
body_file = io.BytesIO(request.input_stream.getvalue())
_, pdict = cgi.parse_header(request.headers['Content-Type'])
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 8efb727d5..ddde3123f 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -64,10 +64,13 @@
'required': ['name', 'image', 'width'],
# TODO are the properties useful for fixing tests?
'properties': {
- # TODO maybe use more limits on types here and use a max length for string?
- # TODO though actually - if authentication is wrong, surely that's the first issue and then maybe we need to re-think this and not have schema checks - or maybe not until later?
+ # TODO maybe use more limits on types here and use a max length for
+ # string?
+ # TODO though actually - if authentication is wrong, surely that's the
+ # first issue and then maybe we need to re-think this and not have
+ # schema checks - or maybe not until later?
'name': {
- 'type': 'string'
+ 'type': 'string',
},
'image': {},
'width': {},
@@ -146,8 +149,8 @@ def add_target() -> Tuple[str, int]:
Fake implementation of
https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Add-a-Target
"""
- # We do not use ``request.get_json(force=True)`` because this only works when the content
- # type is given as ``application/json``.
+ # We do not use ``request.get_json(force=True)`` because this only works
+ # when the content type is given as ``application/json``.
request_json = json.loads(request.data)
name = request_json['name']
databases = get_all_databases()
@@ -276,10 +279,11 @@ def delete_target(target_id: str) -> Tuple[str, int]:
# gmt = pytz.timezone('GMT')
# now = datetime.datetime.now(tz=gmt)
# target.delete_date = now
- requests.delete(
- url=
- f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/{target_id}',
+ delete_url = (
+ f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/'
+ f'{target_id}'
)
+ requests.delete(url=delete_url)
body = {
'transaction_id': uuid.uuid4().hex,
@@ -451,8 +455,8 @@ def update_target(target_id: str) -> Tuple[str, int]:
Fake implementation of
https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Update-a-Target
"""
- # We do not use ``request.get_json(force=True)`` because this only works when the content
- # type is given as ``application/json``.
+ # We do not use ``request.get_json(force=True)`` because this only works
+ # when the content type is given as ``application/json``.
request_json = json.loads(request.data)
body: Dict[str, str] = {}
databases = get_all_databases()
@@ -518,11 +522,11 @@ def update_target(target_id: str) -> Tuple[str, int]:
image = request_json['image']
update_values['image'] = image
- requests.put(
- url=
- f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/{target_id}',
- json=update_values,
+ put_url = (
+ f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/'
+ f'{target_id}'
)
+ requests.put(url=put_url, json=update_values)
body = {
'result_code': ResultCodes.SUCCESS.value,
diff --git a/src/_mock_vws_server/vws/_databases.py b/src/_mock_vws_server/vws/_databases.py
index 277c22047..8a701fc1c 100644
--- a/src/_mock_vws_server/vws/_databases.py
+++ b/src/_mock_vws_server/vws/_databases.py
@@ -58,20 +58,19 @@ def get_all_databases() -> Set[VuforiaDatabase]:
target.target_id = target_dict['target_id']
gmt = pytz.timezone('GMT')
target.last_modified_date = datetime.datetime.fromisoformat(
- target_dict['last_modified_date']
+ target_dict['last_modified_date'],
)
target.last_modified_date = target.last_modified_date.replace(
- tzinfo=gmt
+ tzinfo=gmt,
)
target.upload_date = datetime.datetime.fromisoformat(
- target_dict['upload_date']
+ target_dict['upload_date'],
)
target.upload_date = target.upload_date.replace(tzinfo=gmt)
- delete_date_optional = target_dict[
- 'delete_date_optional']
+ delete_date_optional = target_dict['delete_date_optional']
if delete_date_optional:
target.delete_date = datetime.datetime.fromisoformat(
- delete_date_optional
+ delete_date_optional,
)
target.delete_date = target.delete_date.replace(tzinfo=gmt)
new_database.targets.append(target)
diff --git a/src/_mock_vws_server/vws/_services_validators/__init__.py b/src/_mock_vws_server/vws/_services_validators/__init__.py
index 6e7986d18..42afee7a1 100644
--- a/src/_mock_vws_server/vws/_services_validators/__init__.py
+++ b/src/_mock_vws_server/vws/_services_validators/__init__.py
@@ -415,7 +415,7 @@ def validate_metadata_encoding(
if 'application_metadata' not in request.get_json(force=True):
return wrapped(*args, **kwargs)
- application_metadata = request.get_json(force=True
+ application_metadata = request.get_json(force=True,
).get('application_metadata')
if application_metadata is None:
@@ -461,7 +461,7 @@ def validate_metadata_type(
if 'application_metadata' not in request.get_json(force=True):
return wrapped(*args, **kwargs)
- application_metadata = request.get_json(force=True
+ application_metadata = request.get_json(force=True,
).get('application_metadata')
if application_metadata is None or isinstance(application_metadata, str):
@@ -499,7 +499,7 @@ def validate_metadata_size(
if not request.data:
return wrapped(*args, **kwargs)
- application_metadata = request.get_json(force=True
+ application_metadata = request.get_json(force=True,
).get('application_metadata')
if application_metadata is None:
return wrapped(*args, **kwargs)
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 271ebec21..d3229063e 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -78,9 +78,14 @@ def __init__(
self.state = state
def to_dict(
- self
- ) -> Dict[str, Union[str, List[Dict[str, Optional[Union[str, int, bool,
- float]]]]]]:
+ self,
+ ) -> Dict[
+ str,
+ Union[
+ str,
+ List[Dict[str, Optional[Union[str, int, bool, float]]]],
+ ],
+ ]:
targets = [target.to_dict() for target in self.targets]
return {
'database_name': self.database_name,
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 51243fb72..83f00a927 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -181,7 +181,7 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
# https://github.com/agronholm/sphinx-autodoc-typehints/issues/123
if self.delete_date:
delete_date: Optional[int] = datetime.datetime.isoformat(
- self.delete_date
+ self.delete_date,
)
else:
delete_date = None
From aae79505e7a218326c2207dc85502637b9f639b6 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 25 Mar 2020 21:14:37 +0000
Subject: [PATCH 0085/3455] Remove some validators
---
.../vws/_services_validators/__init__.py | 516 ------------------
.../_services_validators/auth_validators.py | 155 ------
.../content_length_validators.py | 119 ----
.../content_type_validators.py | 49 --
.../_services_validators/date_validators.py | 127 -----
.../_services_validators/image_validators.py | 276 ----------
6 files changed, 1242 deletions(-)
delete mode 100644 src/_mock_vws_server/vws/_services_validators/__init__.py
delete mode 100644 src/_mock_vws_server/vws/_services_validators/auth_validators.py
delete mode 100644 src/_mock_vws_server/vws/_services_validators/content_length_validators.py
delete mode 100644 src/_mock_vws_server/vws/_services_validators/content_type_validators.py
delete mode 100644 src/_mock_vws_server/vws/_services_validators/date_validators.py
delete mode 100644 src/_mock_vws_server/vws/_services_validators/image_validators.py
diff --git a/src/_mock_vws_server/vws/_services_validators/__init__.py b/src/_mock_vws_server/vws/_services_validators/__init__.py
deleted file mode 100644
index 42afee7a1..000000000
--- a/src/_mock_vws_server/vws/_services_validators/__init__.py
+++ /dev/null
@@ -1,516 +0,0 @@
-"""
-Input validators to use in the mock.
-"""
-
-import binascii
-import numbers
-import uuid
-from json.decoder import JSONDecodeError
-from pathlib import Path
-from typing import Any, Callable, Dict, Set, Tuple
-
-import wrapt
-from flask import make_response, request
-from requests import codes
-from requests_mock import POST, PUT
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-from mock_vws._base64_decoding import decode_base64
-from mock_vws._constants import ResultCodes
-from mock_vws._database_matchers import get_database_matching_server_keys
-from mock_vws._mock_common import json_dump
-from mock_vws.database import VuforiaDatabase
-from mock_vws.states import States
-
-from .._databases import get_all_databases
-
-
-@wrapt.decorator
-def validate_active_flag(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the active flag data given to the endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `BAD_REQUEST` response with a FAIL result code if there is
- active flag data given to the endpoint which is not either a Boolean or
- NULL.
- """
- if not request.data:
- return wrapped(*args, **kwargs)
-
- if 'active_flag' not in request.get_json(force=True):
- return wrapped(*args, **kwargs)
-
- active_flag = request.get_json(force=True).get('active_flag')
-
- if active_flag is None or isinstance(active_flag, bool):
- return wrapped(*args, **kwargs)
-
- body: Dict[str, str] = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.BAD_REQUEST
-
-
-@wrapt.decorator
-def validate_project_state(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any,
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the state of the project.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `FORBIDDEN` response with a PROJECT_INACTIVE result code if the
- project is inactive.
- """
- databases = get_all_databases()
- database = get_database_matching_server_keys(
- request_headers=dict(request.headers),
- request_body=request.data,
- request_method=request.method,
- request_path=request.path,
- databases=databases,
- )
-
- assert isinstance(database, VuforiaDatabase)
- if database.state != States.PROJECT_INACTIVE:
- return wrapped(*args, **kwargs)
-
- if request.method == 'GET' and 'duplicates' not in request.path:
- return wrapped(*args, **kwargs)
-
- body: Dict[str, str] = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.PROJECT_INACTIVE.value,
- }
- return json_dump(body), codes.FORBIDDEN
-
-
-@wrapt.decorator
-def validate_not_invalid_json(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate that there is either no JSON given or the JSON given is valid.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `BAD_REQUEST` response with a FAIL result code if there is invalid
- JSON given to a POST or PUT request.
- A `BAD_REQUEST` with empty text if there is data given to another
- request type.
- """
- if not request.data:
- return wrapped(*args, **kwargs)
-
- if request.method not in (POST, PUT):
- # TODO this is commented out but not but should maybe be moved to an
- # after_request decorator
- # context.headers.pop('Content-Type')
- return '', codes.OK
-
- try:
- request.get_json(force=True)
- except JSONDecodeError:
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.BAD_REQUEST
-
- return wrapped(*args, **kwargs)
-
-
-@wrapt.decorator
-def validate_width(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the width argument given to a VWS endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `BAD_REQUEST` response if the width is given and is not a positive
- number.
- """
-
- if not request.data:
- return wrapped(*args, **kwargs)
-
- if 'width' not in request.get_json(force=True):
- return wrapped(*args, **kwargs)
-
- width = request.get_json(force=True).get('width')
-
- width_is_number = isinstance(width, numbers.Number)
- width_positive = width_is_number and width > 0
-
- if not width_positive:
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.BAD_REQUEST
-
- return wrapped(*args, **kwargs)
-
-
-@wrapt.decorator
-def validate_name_type(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the type of the name argument given to a VWS endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `BAD_REQUEST` response if the name is given and not a string.
- is not between 1 and
- 64 characters in length.
- """
-
- if not request.data:
- return wrapped(*args, **kwargs)
-
- if 'name' not in request.get_json(force=True):
- return wrapped(*args, **kwargs)
-
- name = request.get_json(force=True)['name']
-
- if isinstance(name, str):
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.BAD_REQUEST
-
-
-@wrapt.decorator
-def validate_name_length(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the length of the name argument given to a VWS endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `BAD_REQUEST` response if the name is given is not between 1 and 64
- characters in length.
- """
-
- if not request.data:
- return wrapped(*args, **kwargs)
-
- if 'name' not in request.get_json(force=True):
- return wrapped(*args, **kwargs)
-
- name = request.get_json(force=True)['name']
-
- if name and len(str(name)) < 65:
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.BAD_REQUEST
-
-
-@wrapt.decorator
-def validate_name_characters_in_range(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the characters in the name argument given to a VWS endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A ``FORBIDDEN`` response if the name is given includes characters
- outside of the accepted range.
- """
-
- if not request.data:
- return wrapped(*args, **kwargs)
-
- if 'name' not in request.get_json(force=True):
- return wrapped(*args, **kwargs)
-
- name = request.get_json(force=True)['name']
-
- if all(ord(character) <= 65535 for character in str(name)):
- return wrapped(*args, **kwargs)
-
- if (request.method, request.path) == ('POST', '/targets'):
- resources_dir = Path(__file__).parent.parent / 'resources'
- filename = 'oops_error_occurred_response.html'
- oops_resp_file = resources_dir / filename
- text = oops_resp_file.read_text()
- oops_response = make_response(text)
- oops_response.headers['Content-Type'] = 'text/html; charset=UTF-8'
- return oops_response, codes.INTERNAL_SERVER_ERROR
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.TARGET_NAME_EXIST.value,
- }
- return json_dump(body), codes.FORBIDDEN
-
-
-def validate_keys(
- mandatory_keys: Set[str],
- optional_keys: Set[str],
-) -> Callable:
- """
- Args:
- mandatory_keys: Keys required by the endpoint.
- optional_keys: Keys which are not required by the endpoint but which
- are allowed.
-
- Returns:
- A wrapper function to validate that the keys given to the endpoint are
- all allowed and that the mandatory keys are given.
- """
-
- # Args here to work around https://github.com/PyCQA/pydocstyle/issues/370.
- #
- # Args:
- # wrapped: An endpoint function for `requests_mock`.
- # instance: The class that the endpoint function is in.
- # args: The arguments given to the endpoint function.
- # kwargs: The keyword arguments given to the endpoint function.
- @wrapt.decorator
- def wrapper(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
- ) -> Tuple[str, int]:
- """
- Validate the request keys given to a VWS endpoint.
-
- Returns:
- The result of calling the endpoint.
- A `BAD_REQUEST` error if any keys are not allowed, or if any
- required keys are missing.
- """
-
- allowed_keys = mandatory_keys.union(optional_keys)
-
- if request.text is None and not allowed_keys:
- return wrapped(*args, **kwargs)
-
- given_keys = set(request.get_json(force=True).keys())
- all_given_keys_allowed = given_keys.issubset(allowed_keys)
- all_mandatory_keys_given = mandatory_keys.issubset(given_keys)
-
- if all_given_keys_allowed and all_mandatory_keys_given:
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.BAD_REQUEST
-
- wrapper_func: Callable[..., Any] = wrapper
- return wrapper_func
-
-
-@wrapt.decorator
-def validate_metadata_encoding(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate that the given application metadata can be base64 decoded.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNPROCESSABLE_ENTITY` response if application metadata is given and
- it cannot be base64 decoded.
- """
-
- if not request.data:
- return wrapped(*args, **kwargs)
-
- if 'application_metadata' not in request.get_json(force=True):
- return wrapped(*args, **kwargs)
-
- application_metadata = request.get_json(force=True,
- ).get('application_metadata')
-
- if application_metadata is None:
- return wrapped(*args, **kwargs)
-
- try:
- decode_base64(encoded_data=application_metadata)
- except binascii.Error:
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.UNPROCESSABLE_ENTITY
-
- return wrapped(*args, **kwargs)
-
-
-@wrapt.decorator
-def validate_metadata_type(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate that the given application metadata is a string or NULL.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `BAD_REQUEST` response if application metadata is given and it is
- not a string or NULL.
- """
-
- if not request.data:
- return wrapped(*args, **kwargs)
-
- if 'application_metadata' not in request.get_json(force=True):
- return wrapped(*args, **kwargs)
-
- application_metadata = request.get_json(force=True,
- ).get('application_metadata')
-
- if application_metadata is None or isinstance(application_metadata, str):
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.BAD_REQUEST
-
-
-@wrapt.decorator
-def validate_metadata_size(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate that the given application metadata is a string or 1024 * 1024
- bytes or fewer.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNPROCESSABLE_ENTITY` response if application metadata is given and
- it is too large.
- """
- if not request.data:
- return wrapped(*args, **kwargs)
-
- application_metadata = request.get_json(force=True,
- ).get('application_metadata')
- if application_metadata is None:
- return wrapped(*args, **kwargs)
- decoded = decode_base64(encoded_data=application_metadata)
-
- max_metadata_bytes = 1024 * 1024 - 1
- if len(decoded) <= max_metadata_bytes:
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.METADATA_TOO_LARGE.value,
- }
- return json_dump(body), codes.UNPROCESSABLE_ENTITY
diff --git a/src/_mock_vws_server/vws/_services_validators/auth_validators.py b/src/_mock_vws_server/vws/_services_validators/auth_validators.py
deleted file mode 100644
index ff6cfe2b0..000000000
--- a/src/_mock_vws_server/vws/_services_validators/auth_validators.py
+++ /dev/null
@@ -1,155 +0,0 @@
-"""
-Authorization header validators to use in the mock.
-"""
-
-import uuid
-from typing import Any, Callable, Dict, Tuple
-
-import wrapt
-from flask import request
-from requests import codes
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-from mock_vws._constants import ResultCodes
-from mock_vws._database_matchers import get_database_matching_server_keys
-from mock_vws._mock_common import json_dump
-
-
-@wrapt.decorator
-def validate_auth_header_exists(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate that there is an authorization header given to a VWS endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNAUTHORIZED` response if there is no "Authorization" header.
- """
-
- if 'Authorization' in request.headers:
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
- }
- return json_dump(body), codes.UNAUTHORIZED
-
-
-@wrapt.decorator
-def validate_access_key_exists(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any,
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the authorization header includes an access key for a database.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An ``UNAUTHORIZED`` response if the access key is unknown.
- """
-
- header = request.headers['Authorization']
- first_part, _ = header.split(':')
- _, access_key = first_part.split(' ')
- for database in instance.databases:
- if access_key == database.server_access_key:
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.BAD_REQUEST
-
-
-@wrapt.decorator
-def validate_auth_header_has_signature(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the authorization header includes a signature.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An ``UNAUTHORIZED`` response if the "Authorization" header is not as
- expected.
- """
-
- header = request.headers['Authorization']
- if header.count(':') == 1 and header.split(':')[1]:
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.BAD_REQUEST
-
-
-@wrapt.decorator
-def validate_authorization(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any,
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the authorization header given to a VWS endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `BAD_REQUEST` response if the "Authorization" header is not as
- expected.
- """
-
- database = get_database_matching_server_keys(
- request_headers=dict(request.headers),
- request_body=request.body,
- request_method=request.method,
- request_path=request.path,
- databases=instance.databases,
- )
-
- if database is not None:
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
- }
- return json_dump(body), codes.UNAUTHORIZED
diff --git a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py b/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
deleted file mode 100644
index 882f93593..000000000
--- a/src/_mock_vws_server/vws/_services_validators/content_length_validators.py
+++ /dev/null
@@ -1,119 +0,0 @@
-"""
-Content-Length header validators to use in the mock.
-"""
-
-import uuid
-from typing import Any, Callable, Dict, Tuple
-
-import wrapt
-from flask import request
-from requests import codes
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-from .._constants import ResultCodes
-from .._mock_common import json_dump
-
-
-@wrapt.decorator
-def validate_content_length_header_is_int(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the ``Content-Length`` header is an integer.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A ``BAD_REQUEST`` response if the content length header is not an
- integer.
- """
-
- body_length = len(request.data.decode() if request.data else '')
- given_content_length = request.headers.get('Content-Length', body_length)
-
- try:
- int(given_content_length)
- except ValueError:
- # TODO construct response
- # context.headers = {'Connection': 'Close'}
- return '', codes.BAD_REQUEST
-
- return wrapped(*args, **kwargs)
-
-
-@wrapt.decorator
-def validate_content_length_header_not_too_large(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the ``Content-Length`` header is not too large.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A ``GATEWAY_TIMEOUT`` response if the given content length header says
- that the content length is greater than the body length.
- """
-
- body_length = len(request.data.decode() if request.data else '')
- given_content_length = request.headers.get('Content-Length', body_length)
- given_content_length_value = int(given_content_length)
- if given_content_length_value > body_length:
- # TODO construct a response object
- # context.headers = {'Connection': 'keep-alive'}
- return '', codes.GATEWAY_TIMEOUT
-
- return wrapped(*args, **kwargs)
-
-
-@wrapt.decorator
-def validate_content_length_header_not_too_small(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the ``Content-Length`` header is not too small.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An ``UNAUTHORIZED`` response if the given content length header says
- that the content length is smaller than the body length.
- """
-
- body_length = len(request.data.decode() if request.data else '')
- given_content_length = request.headers.get('Content-Length', body_length)
- given_content_length_value = int(given_content_length)
-
- if given_content_length_value < body_length:
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
- }
- return json_dump(body), codes.UNAUTHORIZED
-
- return wrapped(*args, **kwargs)
diff --git a/src/_mock_vws_server/vws/_services_validators/content_type_validators.py b/src/_mock_vws_server/vws/_services_validators/content_type_validators.py
deleted file mode 100644
index 0a59d9ebf..000000000
--- a/src/_mock_vws_server/vws/_services_validators/content_type_validators.py
+++ /dev/null
@@ -1,49 +0,0 @@
-"""
-Content-Type header validators to use in the mock.
-"""
-
-import uuid
-from typing import Any, Callable, Dict, Tuple
-
-import wrapt
-from flask import request
-from requests import codes
-from requests_mock import POST, PUT
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-from mock_vws._constants import ResultCodes
-from mock_vws._mock_common import json_dump
-
-
-@wrapt.decorator
-def validate_content_type_header_given(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate that there is a non-empty content type header given if required.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNAUTHORIZED` response if there is no "Content-Type" header or the
- given header is empty.
- """
-
- request_needs_content_type = bool(request.method in (POST, PUT))
- if request.headers.get('Content-Type') or not request_needs_content_type:
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
- }
- return json_dump(body), codes.UNAUTHORIZED
diff --git a/src/_mock_vws_server/vws/_services_validators/date_validators.py b/src/_mock_vws_server/vws/_services_validators/date_validators.py
deleted file mode 100644
index dfb4fcb16..000000000
--- a/src/_mock_vws_server/vws/_services_validators/date_validators.py
+++ /dev/null
@@ -1,127 +0,0 @@
-"""
-Validators of the date header to use in the mock services API.
-"""
-
-import datetime
-import uuid
-from typing import Any, Callable, Dict, Tuple
-
-import pytz
-import wrapt
-from flask import request
-from requests import codes
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-from mock_vws._constants import ResultCodes
-from mock_vws._mock_common import json_dump
-
-
-@wrapt.decorator
-def validate_date_header_given(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the date header is given to a VWS endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `BAD_REQUEST` response if the date is not given.
- """
- if 'Date' in request.headers:
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.BAD_REQUEST
-
-
-@wrapt.decorator
-def validate_date_format(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the format of the date header given to a VWS endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `BAD_REQUEST` response if the date is in the wrong format.
- A `FORBIDDEN` response if the date is out of range.
- """
-
- date_header = request.headers['Date']
- date_format = '%a, %d %b %Y %H:%M:%S GMT'
- try:
- datetime.datetime.strptime(date_header, date_format)
- except ValueError:
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.BAD_REQUEST
-
- return wrapped(*args, **kwargs)
-
-
-@wrapt.decorator
-def validate_date_in_range(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the date header given to a VWS endpoint is in range.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `FORBIDDEN` response if the date is out of range.
- """
-
- date_from_header = datetime.datetime.strptime(
- request.headers['Date'],
- '%a, %d %b %Y %H:%M:%S GMT',
- )
-
- gmt = pytz.timezone('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:
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.REQUEST_TIME_TOO_SKEWED.value,
- }
- return json_dump(body), codes.FORBIDDEN
-
- return wrapped(*args, **kwargs)
diff --git a/src/_mock_vws_server/vws/_services_validators/image_validators.py b/src/_mock_vws_server/vws/_services_validators/image_validators.py
deleted file mode 100644
index e1f613e62..000000000
--- a/src/_mock_vws_server/vws/_services_validators/image_validators.py
+++ /dev/null
@@ -1,276 +0,0 @@
-"""
-Image validators to use in the mock.
-"""
-
-import binascii
-import io
-import uuid
-from typing import Any, Callable, Dict, Tuple
-
-import wrapt
-from flask import request
-from PIL import Image
-from requests import codes
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-from mock_vws._base64_decoding import decode_base64
-from mock_vws._constants import ResultCodes
-from mock_vws._mock_common import json_dump
-
-
-@wrapt.decorator
-def validate_image_format(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the format of the image given to a VWS endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNPROCESSABLE_ENTITY` response if the image is given and is not
- either a PNG or a JPEG.
- """
-
- if not request.data:
- return wrapped(*args, **kwargs)
-
- image = request.get_json(force=True).get('image')
-
- if image is None:
- return wrapped(*args, **kwargs)
-
- decoded = decode_base64(encoded_data=image)
- image_file = io.BytesIO(decoded)
- pil_image = Image.open(image_file)
-
- if pil_image.format in ('PNG', 'JPEG'):
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.BAD_IMAGE.value,
- }
- return json_dump(body), codes.UNPROCESSABLE_ENTITY
-
-
-@wrapt.decorator
-def validate_image_color_space(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the color space of the image given to a VWS endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNPROCESSABLE_ENTITY` response if the image is given and is not
- in either the RGB or greyscale color space.
- """
-
- if not request.data:
- return wrapped(*args, **kwargs)
-
- image = request.get_json(force=True).get('image')
-
- if image is None:
- return wrapped(*args, **kwargs)
-
- decoded = decode_base64(encoded_data=image)
- image_file = io.BytesIO(decoded)
- pil_image = Image.open(image_file)
-
- if pil_image.mode in ('L', 'RGB'):
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.BAD_IMAGE.value,
- }
- return json_dump(body), codes.UNPROCESSABLE_ENTITY
-
-
-@wrapt.decorator
-def validate_image_size(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the file size of the image given to a VWS endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNPROCESSABLE_ENTITY` response if the image is given and is not
- under a certain file size threshold.
- """
-
- if not request.data:
- return wrapped(*args, **kwargs)
-
- image = request.get_json(force=True).get('image')
-
- if image is None:
- return wrapped(*args, **kwargs)
-
- decoded = decode_base64(encoded_data=image)
-
- if len(decoded) <= 2359293:
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.IMAGE_TOO_LARGE.value,
- }
- return json_dump(body), codes.UNPROCESSABLE_ENTITY
-
-
-@wrapt.decorator
-def validate_image_is_image(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate that the given image data is actually an image file.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNPROCESSABLE_ENTITY` response if image data is given and it is not
- an image file.
- """
-
- if not request.data:
- return wrapped(*args, **kwargs)
-
- image = request.get_json(force=True).get('image')
-
- if image is None:
- return wrapped(*args, **kwargs)
-
- decoded = decode_base64(encoded_data=image)
- image_file = io.BytesIO(decoded)
-
- try:
- Image.open(image_file)
- except OSError:
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.BAD_IMAGE.value,
- }
- return json_dump(body), codes.UNPROCESSABLE_ENTITY
-
- return wrapped(*args, **kwargs)
-
-
-@wrapt.decorator
-def validate_image_encoding(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate that the given image data can be base64 decoded.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNPROCESSABLE_ENTITY` response if image data is given and it cannot
- be base64 decoded.
- """
-
- if not request.data:
- return wrapped(*args, **kwargs)
-
- if 'image' not in request.get_json(force=True):
- return wrapped(*args, **kwargs)
-
- image = request.get_json(force=True).get('image')
-
- try:
- decode_base64(encoded_data=image)
- except binascii.Error:
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.UNPROCESSABLE_ENTITY
-
- return wrapped(*args, **kwargs)
-
-
-@wrapt.decorator
-def validate_image_data_type(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate that the given image data is a string.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `BAD_REQUEST` response if image data is given and it is not a
- string.
- """
-
- if not request.data:
- return wrapped(*args, **kwargs)
-
- if 'image' not in request.get_json(force=True):
- return wrapped(*args, **kwargs)
-
- image = request.get_json(force=True).get('image')
-
- if isinstance(image, str):
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.BAD_REQUEST
From 7a459f5a9f1874bc0a6e808e36b007629b16d408 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 25 Mar 2020 21:30:05 +0000
Subject: [PATCH 0086/3455] Progress towards using new style
---
src/_mock_vws_server/vws/__init__.py | 77 ++++------------------------
1 file changed, 11 insertions(+), 66 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index ddde3123f..ff39ad40e 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -19,43 +19,7 @@
from ._constants import STORAGE_BASE_URL
from ._databases import get_all_databases
-from ._services_validators import (
- validate_active_flag,
- validate_metadata_encoding,
- validate_metadata_size,
- validate_metadata_type,
- validate_name_characters_in_range,
- validate_name_length,
- validate_name_type,
- validate_not_invalid_json,
- validate_project_state,
- validate_width,
-)
-from ._services_validators.auth_validators import (
- validate_auth_header_exists,
- validate_auth_header_has_signature,
-)
-from ._services_validators.content_length_validators import (
- validate_content_length_header_is_int,
- validate_content_length_header_not_too_large,
- validate_content_length_header_not_too_small,
-)
-from ._services_validators.content_type_validators import (
- validate_content_type_header_given,
-)
-from ._services_validators.date_validators import (
- validate_date_format,
- validate_date_header_given,
- validate_date_in_range,
-)
-from ._services_validators.image_validators import (
- validate_image_color_space,
- validate_image_data_type,
- validate_image_encoding,
- validate_image_format,
- validate_image_is_image,
- validate_image_size,
-)
+from mock_vws._services_validators import run_services_validators
VWS_FLASK_APP = Flask(__name__)
JSON_SCHEMA = JsonSchema(VWS_FLASK_APP)
@@ -82,36 +46,17 @@
@VWS_FLASK_APP.before_request
-@validate_content_length_header_is_int
-@validate_content_length_header_not_too_large
-@validate_content_length_header_not_too_small
-@validate_auth_header_exists
-@validate_auth_header_has_signature
-# @validate_access_key_exists
-@validate_not_invalid_json
-@validate_date_header_given
-@validate_date_format
-@validate_date_in_range
-@validate_content_type_header_given
-@validate_width
-# TODO is validating the name type needed given JSON schema?
-@validate_name_type
-@validate_name_length
-@validate_name_characters_in_range
-@validate_image_data_type
-@validate_image_encoding
-@validate_image_is_image
-@validate_image_format
-@validate_image_color_space
-@validate_image_size
-@validate_active_flag
-@validate_metadata_type
-@validate_metadata_encoding
-@validate_metadata_size
-# @validate_authorization
-@validate_project_state
def validate_request() -> None:
- pass
+ databases = get_all_databases()
+ run_services_validators(
+ request_text=request.data.decode(),
+ request_headers=dict(request.headers),
+ # TODO not sure about this one
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
# decorators = [
# # parse_target_id,
# # update_request_count,
From 4ef11f1bb41115a8d4a05327a7bacbe94ff9f0da Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Thu, 26 Mar 2020 09:46:55 +0000
Subject: [PATCH 0087/3455] Progress towards new error handler
---
src/_mock_vws_server/vws/__init__.py | 57 ++++++++++++++++++++++++----
src/mock_vws/target.py | 2 +-
2 files changed, 51 insertions(+), 8 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index ff39ad40e..63fb2a510 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -20,6 +20,21 @@
from ._constants import STORAGE_BASE_URL
from ._databases import get_all_databases
from mock_vws._services_validators import run_services_validators
+from mock_vws._services_validators.exceptions import (
+ AuthenticationFailure,
+ BadImage,
+ ContentLengthHeaderNotInt,
+ ContentLengthHeaderTooLarge,
+ Fail,
+ ImageTooLarge,
+ MetadataTooLarge,
+ OopsErrorOccurredResponse,
+ ProjectInactive,
+ RequestTimeTooSkewed,
+ TargetNameExist,
+ UnknownTarget,
+ UnnecessaryRequestBody,
+)
VWS_FLASK_APP = Flask(__name__)
JSON_SCHEMA = JsonSchema(VWS_FLASK_APP)
@@ -63,13 +78,41 @@ def validate_request() -> None:
# ]
-@VWS_FLASK_APP.errorhandler(JsonValidationError)
-def validation_error(e: JsonValidationError) -> Tuple[str, int]:
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.FAIL.value,
- }
- return json_dump(body), codes.BAD_REQUEST
+@VWS_FLASK_APP.errorhandler(UnknownTarget)
+def handle_unknown_target(e: UnknownTarget) -> Tuple[str, int]:
+ return e.response_text, e.status_code
+
+@VWS_FLASK_APP.errorhandler(ProjectInactive)
+def handle_project_inactive(e: ProjectInactive) -> Tuple[str, int]:
+ return e.response_text, e.status_code
+
+@VWS_FLASK_APP.errorhandler(AuthenticationFailure)
+def handle_authentication_failure(e: AuthenticationFailure) -> Tuple[str, int]:
+ return e.response_text, e.status_code
+
+@VWS_FLASK_APP.errorhandler(Fail)
+def handle_fail(e: Fail) -> Tuple[str, int]:
+ return e.response_text, e.status_code
+
+@VWS_FLASK_APP.errorhandler(MetadataTooLarge)
+def handle_metadata_too_large(e: MetadataTooLarge) -> Tuple[str, int]:
+ return e.response_text, e.status_code
+
+@VWS_FLASK_APP.errorhandler(TargetNameExist)
+def handle_target_name_exist(e: TargetNameExist) -> Tuple[str, int]:
+ return e.response_text, e.status_code
+
+@VWS_FLASK_APP.errorhandler(BadImage)
+def handle_bad_image(e: BadImage) -> Tuple[str, int]:
+ return e.response_text, e.status_code
+
+@VWS_FLASK_APP.errorhandler(ImageTooLarge)
+def handle_image_too_large(e: ImageTooLarge) -> Tuple[str, int]:
+ return e.response_text, e.status_code
+
+@VWS_FLASK_APP.errorhandler(RequestTimeTooSkewed)
+def handle_request_time_too_skewed(e: RequestTimeTooSkewed) -> Tuple[str, int]:
+ return e.response_text, e.status_code
@VWS_FLASK_APP.after_request
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 83f00a927..b68af451d 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -180,7 +180,7 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
# as can e.g. processing time... maybe use dataclass but then
# https://github.com/agronholm/sphinx-autodoc-typehints/issues/123
if self.delete_date:
- delete_date: Optional[int] = datetime.datetime.isoformat(
+ delete_date: Optional[str] = datetime.datetime.isoformat(
self.delete_date,
)
else:
From 7399ce8834f5f96513cb1a9592477721d7c92e7e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Thu, 26 Mar 2020 10:46:56 +0000
Subject: [PATCH 0088/3455] Remove some of JSONSchema
---
src/_mock_vws_server/vws/__init__.py | 33 ++++++-------------
src/mock_vws/_services_validators/__init__.py | 4 +--
2 files changed, 12 insertions(+), 25 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 63fb2a510..4f7af91dd 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -35,30 +35,9 @@
UnknownTarget,
UnnecessaryRequestBody,
)
+from pathlib import Path
VWS_FLASK_APP = Flask(__name__)
-JSON_SCHEMA = JsonSchema(VWS_FLASK_APP)
-
-ADD_TARGET_SCHEMA = {
- 'required': ['name', 'image', 'width'],
- # TODO are the properties useful for fixing tests?
- 'properties': {
- # TODO maybe use more limits on types here and use a max length for
- # string?
- # TODO though actually - if authentication is wrong, surely that's the
- # first issue and then maybe we need to re-think this and not have
- # schema checks - or maybe not until later?
- 'name': {
- 'type': 'string',
- },
- 'image': {},
- 'width': {},
- 'active_flag': {},
- 'application_metadata': {},
- },
- 'additionalProperties': False,
-}
-
@VWS_FLASK_APP.before_request
def validate_request() -> None:
@@ -114,6 +93,15 @@ def handle_image_too_large(e: ImageTooLarge) -> Tuple[str, int]:
def handle_request_time_too_skewed(e: RequestTimeTooSkewed) -> Tuple[str, int]:
return e.response_text, e.status_code
+@VWS_FLASK_APP.errorhandler(OopsErrorOccurredResponse)
+def handle_oops_error_occurred(e: OopsErrorOccurredResponse) -> Tuple[str, int]:
+ resources_dir = Path(__file__).parent / 'resources'
+ filename = 'oops_error_occurred_response.html'
+ oops_resp_file = resources_dir / filename
+ content_type = 'text/html; charset=UTF-8'
+ context.headers['Content-Type'] = content_type
+ text = str(oops_resp_file.read_text())
+ return e.response_text, e.status_code
@VWS_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
@@ -129,7 +117,6 @@ def set_headers(response: Response) -> Response:
@VWS_FLASK_APP.route('/targets', methods=['POST'])
-@JSON_SCHEMA.validate(ADD_TARGET_SCHEMA)
def add_target() -> Tuple[str, int]:
"""
Add a target.
diff --git a/src/mock_vws/_services_validators/__init__.py b/src/mock_vws/_services_validators/__init__.py
index dfa5b6a26..54e6b2f52 100644
--- a/src/mock_vws/_services_validators/__init__.py
+++ b/src/mock_vws/_services_validators/__init__.py
@@ -2,7 +2,7 @@
Input validators to use in the mock.
"""
-from typing import Dict, List
+from typing import Dict, List, Set
from mock_vws.database import VuforiaDatabase
@@ -55,7 +55,7 @@ def run_services_validators(
request_headers: Dict[str, str],
request_body: bytes,
request_method: str,
- databases: List[VuforiaDatabase],
+ databases: Set[VuforiaDatabase],
) -> None:
"""
Run all validators.
From de82543be8dc23c0728e47bd76ebad33bca03b59 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Thu, 26 Mar 2020 18:05:38 +0000
Subject: [PATCH 0089/3455] Remove some useless variables
---
src/_mock_vws_server/vws/__init__.py | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 4f7af91dd..6aa76df93 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -95,12 +95,8 @@ def handle_request_time_too_skewed(e: RequestTimeTooSkewed) -> Tuple[str, int]:
@VWS_FLASK_APP.errorhandler(OopsErrorOccurredResponse)
def handle_oops_error_occurred(e: OopsErrorOccurredResponse) -> Tuple[str, int]:
- resources_dir = Path(__file__).parent / 'resources'
- filename = 'oops_error_occurred_response.html'
- oops_resp_file = resources_dir / filename
content_type = 'text/html; charset=UTF-8'
context.headers['Content-Type'] = content_type
- text = str(oops_resp_file.read_text())
return e.response_text, e.status_code
@VWS_FLASK_APP.after_request
From 560579adf1d8d4964484d0de74b1b3953b06e1cd Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Thu, 26 Mar 2020 18:39:38 +0000
Subject: [PATCH 0090/3455] Fix some tests
---
src/_mock_vws_server/vws/__init__.py | 7 ++++---
src/mock_vws/_services_validators/key_validators.py | 2 +-
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 6aa76df93..c19e25c22 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -6,7 +6,7 @@
from typing import Dict, List, Tuple, Union
import requests
-from flask import Flask, Response, request
+from flask import Flask, Response, request, make_response
from flask_json_schema import JsonSchema, JsonValidationError
from PIL import Image
from requests import codes
@@ -96,8 +96,9 @@ def handle_request_time_too_skewed(e: RequestTimeTooSkewed) -> Tuple[str, int]:
@VWS_FLASK_APP.errorhandler(OopsErrorOccurredResponse)
def handle_oops_error_occurred(e: OopsErrorOccurredResponse) -> Tuple[str, int]:
content_type = 'text/html; charset=UTF-8'
- context.headers['Content-Type'] = content_type
- return e.response_text, e.status_code
+ response = make_response(e.response_text, e.status_code)
+ response.headers['Content-Type'] = content_type
+ return response
@VWS_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
diff --git a/src/mock_vws/_services_validators/key_validators.py b/src/mock_vws/_services_validators/key_validators.py
index 554f1d079..23684a94c 100644
--- a/src/mock_vws/_services_validators/key_validators.py
+++ b/src/mock_vws/_services_validators/key_validators.py
@@ -141,7 +141,7 @@ def validate_keys(
optional_keys = matching_route.optional_keys
allowed_keys = mandatory_keys.union(optional_keys)
- if request_text is None and not allowed_keys:
+ if not request_text and not allowed_keys:
return
request_json = json.loads(request_text)
From 42764209f5275cca46a741c660ec97df8939167d Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 30 Mar 2020 17:25:13 +0100
Subject: [PATCH 0091/3455] Remove duplication
---
.../vwq/_query_validators/__init__.py | 309 ------------------
.../vwq/_query_validators/auth_validators.py | 216 ------------
.../content_length_validators.py | 124 -------
.../vwq/_query_validators/date_validators.py | 162 ---------
.../vwq/_query_validators/image_validators.py | 267 ---------------
.../resources/query_out_of_bounds_response | 34 --
6 files changed, 1112 deletions(-)
delete mode 100644 src/_mock_vws_server/vwq/_query_validators/__init__.py
delete mode 100644 src/_mock_vws_server/vwq/_query_validators/auth_validators.py
delete mode 100644 src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
delete mode 100644 src/_mock_vws_server/vwq/_query_validators/date_validators.py
delete mode 100644 src/_mock_vws_server/vwq/_query_validators/image_validators.py
delete mode 100644 src/_mock_vws_server/vwq/_query_validators/resources/query_out_of_bounds_response
diff --git a/src/_mock_vws_server/vwq/_query_validators/__init__.py b/src/_mock_vws_server/vwq/_query_validators/__init__.py
deleted file mode 100644
index d2d5f172b..000000000
--- a/src/_mock_vws_server/vwq/_query_validators/__init__.py
+++ /dev/null
@@ -1,309 +0,0 @@
-"""
-Input validators to use in the mock query API.
-"""
-
-import cgi
-import io
-import uuid
-from typing import Any, Callable, Dict, Tuple
-
-import wrapt
-from flask import request
-from requests import codes
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-from mock_vws.database import VuforiaDatabase
-from mock_vws.states import States
-
-from ...vws._databases import get_all_databases
-from .._constants import ResultCodes
-from .._database_matchers import get_database_matching_client_keys
-from .._mock_common import parse_multipart
-
-
-@wrapt.decorator
-def validate_project_state(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any,
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the state of the project.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `FORBIDDEN` response with an InactiveProject result code if the
- project is inactive.
- """
-
- databases = get_all_databases()
- database = get_database_matching_client_keys(
- request_headers=request.headers,
- request_body=request.input_stream.getvalue(),
- request_method=request.method,
- request_path=request.path,
- databases=databases,
- )
-
- assert isinstance(database, VuforiaDatabase)
- if database.state != States.PROJECT_INACTIVE:
- return wrapped(*args, **kwargs)
-
- context.status_code = codes.FORBIDDEN
- transaction_id = uuid.uuid4().hex
- result_code = ResultCodes.INACTIVE_PROJECT.value
-
- # The response has an unusual format of separators, so we construct it
- # manually.
- return (
- '{"transaction_id": '
- f'"{transaction_id}",'
- f'"result_code":"{result_code}"'
- '}'
- )
-
-
-@wrapt.decorator
-def validate_max_num_results(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the ``max_num_results`` field is either an integer within range or
- not given.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `BAD_REQUEST` response if the ``max_num_results`` field is either not
- an integer, or an integer out of range.
- """
-
- body_file = io.BytesIO(request.data)
-
- _, pdict = cgi.parse_header(request.headers['Content-Type'])
- parsed = parse_multipart(
- fp=body_file,
- pdict={
- 'boundary': pdict['boundary'].encode(),
- },
- )
- [max_num_results] = parsed.get('max_num_results', ['1'])
- assert isinstance(max_num_results, str)
- invalid_type_error = (
- f"Invalid value '{max_num_results}' in form data part "
- "'max_result'. "
- 'Expecting integer value in range from 1 to 50 (inclusive).'
- )
-
- try:
- max_num_results_int = int(max_num_results)
- except ValueError:
- context.status_code = codes.BAD_REQUEST
- return invalid_type_error
-
- java_max_int = 2147483647
- if max_num_results_int > java_max_int:
- context.status_code = codes.BAD_REQUEST
- return invalid_type_error
-
- if max_num_results_int < 1 or max_num_results_int > 50:
- context.status_code = codes.BAD_REQUEST
- out_of_range_error = (
- f'Integer out of range ({max_num_results_int}) in form data part '
- "'max_result'. Accepted range is from 1 to 50 (inclusive)."
- )
- return out_of_range_error
-
- return wrapped(*args, **kwargs)
-
-
-@wrapt.decorator
-def validate_include_target_data(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the ``include_target_data`` field is either an accepted value or
- not given.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `BAD_REQUEST` response if the ``include_target_data`` field is not an
- accepted value.
- """
-
- body_file = io.BytesIO(request.data)
-
- _, pdict = cgi.parse_header(request.headers['Content-Type'])
- parsed = parse_multipart(
- fp=body_file,
- pdict={
- 'boundary': pdict['boundary'].encode(),
- },
- )
-
- [include_target_data] = parsed.get('include_target_data', ['top'])
- lower_include_target_data = include_target_data.lower()
- allowed_included_target_data = {'top', 'all', 'none'}
- if lower_include_target_data in allowed_included_target_data:
- return wrapped(*args, **kwargs)
-
- assert isinstance(include_target_data, str)
- unexpected_target_data_message = (
- f"Invalid value '{include_target_data}' in form data part "
- "'include_target_data'. "
- "Expecting one of the (unquoted) string values 'all', 'none' or 'top'."
- )
- return unexpected_target_data_message, codes.BAD_REQUEST
-
-
-@wrapt.decorator
-def validate_content_type_header(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the ``Content-Type`` header.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An ``UNSUPPORTED_MEDIA_TYPE`` response if the ``Content-Type`` header
- main part is not 'multipart/form-data'.
- A ``BAD_REQUEST`` response if the ``Content-Type`` header does not
- contain a boundary which is in the request body.
- """
-
- main_value, pdict = cgi.parse_header(request.headers['Content-Type'])
- if main_value != 'multipart/form-data':
- # context.status_code = codes.UNSUPPORTED_MEDIA_TYPE
- # TODO Do this somehow
- # context.headers.pop('Content-Type')
- return '', codes.UNSUPPORTED_MEDIA_TYPE
-
- if 'boundary' not in pdict:
- # context.status_code = codes.BAD_REQUEST
- # context.headers['Content-Type'] = 'text/html;charset=UTF-8'
- content_type = 'text/html; charset=UTF-8'
- return (
- 'java.io.IOException: RESTEASY007550: '
- 'Unable to get boundary for multipart'
- ), codes.BAD_REQUEST, {
- 'Content-Type': content_type,
- }
-
- if pdict['boundary'].encode() not in request.input_stream.getvalue():
- # TODO
- # context.status_code = codes.BAD_REQUEST
- content_type = 'text/html; charset=UTF-8'
- # context.headers['Content-Type'] = content_type
- return (
- 'java.lang.RuntimeException: RESTEASY007500: '
- 'Could find no Content-Disposition header within part'
- ), codes.BAD_REQUEST, {
- 'Content-Type': content_type,
- }
-
- return wrapped(*args, **kwargs)
-
-
-@wrapt.decorator
-def validate_accept_header(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the accept header.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `NOT_ACCEPTABLE` response if the Accept header is given and is not
- 'application/json' or '*/*'.
- """
-
- accept = request.headers.get('Accept')
- if accept in ('application/json', '*/*', None):
- return wrapped(*args, **kwargs)
-
- context.headers.pop('Content-Type')
- context.status_code = codes.NOT_ACCEPTABLE
- return ''
-
-
-@wrapt.decorator
-def validate_extra_fields(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate that the no unknown fields are given.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A ``BAD_REQUEST`` response if extra fields are given.
- """
-
- body_file = io.BytesIO(request.data)
-
- _, pdict = cgi.parse_header(request.headers['Content-Type'])
- parsed = parse_multipart(
- fp=body_file,
- pdict={
- 'boundary': pdict['boundary'].encode(),
- },
- )
-
- known_parameters = {'image', 'max_num_results', 'include_target_data'}
-
- if not parsed.keys() - known_parameters:
- return wrapped(*args, **kwargs)
-
- context.status_code = codes.BAD_REQUEST
- return 'Unknown parameters in the request.'
diff --git a/src/_mock_vws_server/vwq/_query_validators/auth_validators.py b/src/_mock_vws_server/vwq/_query_validators/auth_validators.py
deleted file mode 100644
index 73517af68..000000000
--- a/src/_mock_vws_server/vwq/_query_validators/auth_validators.py
+++ /dev/null
@@ -1,216 +0,0 @@
-"""
-Authorization validators to use in the mock query API.
-"""
-
-import uuid
-from pathlib import Path
-from typing import Any, Callable, Dict, Tuple
-
-import wrapt
-from flask import request
-from requests import codes
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-from ...vws._databases import get_all_databases
-from .._constants import ResultCodes
-from .._database_matchers import get_database_matching_client_keys
-
-
-@wrapt.decorator
-def validate_auth_header_exists(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate that there is an authorization header given to the query endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNAUTHORIZED` response if there is no "Authorization" header.
- """
-
- if 'Authorization' in request.headers:
- return wrapped(*args, **kwargs)
-
- context.status_code = codes.UNAUTHORIZED
- text = 'Authorization header missing.'
- content_type = 'text/plain; charset=ISO-8859-1'
- context.headers['Content-Type'] = content_type
- context.headers['WWW-Authenticate'] = 'VWS'
- return text
-
-
-@wrapt.decorator
-def validate_auth_header_number_of_parts(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the authorization header includes text either side of a space.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An ``UNAUTHORIZED`` response if the "Authorization" header is not as
- expected.
- """
-
- header = request.headers['Authorization']
- parts = header.split(' ')
- if len(parts) == 2 and parts[1]:
- return wrapped(*args, **kwargs)
-
- context.status_code = codes.UNAUTHORIZED
- text = 'Malformed authorization header.'
- content_type = 'text/plain; charset=ISO-8859-1'
- context.headers['Content-Type'] = content_type
- context.headers['WWW-Authenticate'] = 'VWS'
- return text
-
-
-@wrapt.decorator
-def validate_client_key_exists(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any,
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the authorization header includes a client key for a database.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An ``UNAUTHORIZED`` response if the client key is unknown.
- """
-
- header = request.headers['Authorization']
- first_part, _ = header.split(':')
- _, access_key = first_part.split(' ')
- databases = get_all_databases()
- for database in databases:
- if access_key == database.client_access_key:
- return wrapped(*args, **kwargs)
-
- context.status_code = codes.UNAUTHORIZED
- context.headers['WWW-Authenticate'] = 'VWS'
- transaction_id = uuid.uuid4().hex
- result_code = ResultCodes.AUTHENTICATION_FAILURE.value
- text = (
- '{"transaction_id":'
- f'"{transaction_id}",'
- f'"result_code":"{result_code}"'
- '}'
- )
- return text
-
-
-@wrapt.decorator
-def validate_auth_header_has_signature(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the authorization header includes a signature.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An ``UNAUTHORIZED`` response if the "Authorization" header is not as
- expected.
- """
-
- header = request.headers['Authorization']
- if header.count(':') == 1 and header.split(':')[1]:
- return wrapped(*args, **kwargs)
-
- # context.status_code = codes.INTERNAL_SERVER_ERROR
- current_parent = Path(__file__).parent
- resources = current_parent / 'resources'
- known_response = resources / 'query_out_of_bounds_response'
- content_type = 'text/html; charset=ISO-8859-1'
- # TODO
- # context.headers['Content-Type'] = content_type
- cache_control = 'must-revalidate,no-cache,no-store'
- # context.headers['Cache-Control'] = cache_control
- return known_response.read_text(), codes.INTERNAL_SERVER_ERROR, {
- 'Content-Type': content_type,
- 'Cache-Control': cache_control,
- }
-
-
-@wrapt.decorator
-def validate_authorization(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any,
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the authorization header given to the query endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `BAD_REQUEST` response if the "Authorization" header is not as
- expected.
- """
-
- databases = get_all_databases()
- database = get_database_matching_client_keys(
- request_headers=request.headers,
- request_body=request.input_stream.getvalue(),
- request_method=request.method,
- request_path=request.path,
- databases=databases,
- )
-
- if database is not None:
- return wrapped(*args, **kwargs)
-
- # TODO
- # context.status_code = codes.UNAUTHORIZED
- # TODO
- # context.headers['WWW-Authenticate'] = 'VWS'
- transaction_id = uuid.uuid4().hex
- result_code = ResultCodes.AUTHENTICATION_FAILURE.value
- text = (
- '{"transaction_id":'
- f'"{transaction_id}",'
- f'"result_code":"{result_code}"'
- '}'
- )
- return text, codes.UNAUTHORIZED, {'WWW-Authenticate': 'VWS'}
diff --git a/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py b/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
deleted file mode 100644
index 4cc32b5e2..000000000
--- a/src/_mock_vws_server/vwq/_query_validators/content_length_validators.py
+++ /dev/null
@@ -1,124 +0,0 @@
-"""
-Content-Length header validators to use in the mock.
-"""
-
-import uuid
-from typing import Any, Callable, Dict, Tuple
-
-import wrapt
-from flask import request
-from requests import codes
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-from .._constants import ResultCodes
-from .._mock_common import json_dump
-
-
-@wrapt.decorator
-def validate_content_length_header_is_int(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the ``Content-Length`` header is an integer.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A ``BAD_REQUEST`` response if the content length header is not an
- integer.
- """
-
- given_content_length = request.headers['Content-Length']
-
- try:
- int(given_content_length)
- except ValueError:
- # TODO remove legacy
- # context.status_code = codes.BAD_REQUEST
- # context.headers = {'Connection': 'Close'}
- return '', codes.BAD_REQUEST, {'Connection': 'Close'}
-
- return wrapped(*args, **kwargs)
-
-
-@wrapt.decorator
-def validate_content_length_header_not_too_large(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the ``Content-Length`` header is not too large.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A ``GATEWAY_TIMEOUT`` response if the given content length header says
- that the content length is greater than the body length.
- """
-
- given_content_length = request.headers['Content-Length']
-
- body_length = len(request.input_stream.getvalue())
- given_content_length_value = int(given_content_length)
- if given_content_length_value > body_length:
- # TODO Remove legacy
- # context.status_code = codes.GATEWAY_TIMEOUT
- # context.headers = {'Connection': 'keep-alive'}
- return '', codes.GATEWAY_TIMEOUT, {'Connection': 'keep-alive'}
-
- return wrapped(*args, **kwargs)
-
-
-@wrapt.decorator
-def validate_content_length_header_not_too_small(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the ``Content-Length`` header is not too small.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An ``UNAUTHORIZED`` response if the given content length header says
- that the content length is smaller than the body length.
- """
-
- given_content_length = request.headers['Content-Length']
-
- body_length = len(request.input_stream.getvalue())
- given_content_length_value = int(given_content_length)
-
- if given_content_length_value < body_length:
- context.status_code = codes.UNAUTHORIZED
- context.headers['WWW-Authenticate'] = 'VWS'
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
- }
- return json_dump(body)
-
- return wrapped(*args, **kwargs)
diff --git a/src/_mock_vws_server/vwq/_query_validators/date_validators.py b/src/_mock_vws_server/vwq/_query_validators/date_validators.py
deleted file mode 100644
index 9e0de47f8..000000000
--- a/src/_mock_vws_server/vwq/_query_validators/date_validators.py
+++ /dev/null
@@ -1,162 +0,0 @@
-"""
-Validators of the date header to use in the mock query API.
-"""
-
-import datetime
-import uuid
-from typing import Any, Callable, Dict, Set, Tuple
-
-import pytz
-import wrapt
-from flask import request
-from requests import codes
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-from .._constants import ResultCodes
-from .._mock_common import json_dump
-
-
-@wrapt.decorator
-def validate_date_header_given(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the date header is given to the query endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `BAD_REQUEST` response if the date is not given.
- """
-
- if 'Date' in request.headers:
- return wrapped(*args, **kwargs)
-
- content_type = 'text/plain; charset=ISO-8859-1'
- # TODO remove legacy
- # context.headers['Content-Type'] = content_type
- return 'Date header required.', codes.BAD_REQUEST, {
- 'Content-Type': content_type,
- }
-
-
-def _accepted_date_formats() -> Set[str]:
- """
- Return all known accepted date formats.
-
- We expect that more formats than this will be accepted.
- These are the accepted ones we know of at the time of writing.
- """
- known_accepted_formats = {
- '%a, %b %d %H:%M:%S %Y',
- '%a %b %d %H:%M:%S %Y',
- '%a, %d %b %Y %H:%M:%S',
- '%a %d %b %Y %H:%M:%S',
- }
-
- known_accepted_formats = known_accepted_formats.union(
- set(date_format + ' GMT' for date_format in known_accepted_formats),
- )
-
- return known_accepted_formats
-
-
-@wrapt.decorator
-def validate_date_format(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the format of the date header given to the query endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNAUTHORIZED` response if the date is in the wrong format.
- """
-
- date_header = request.headers['Date']
-
- for date_format in _accepted_date_formats():
- try:
- datetime.datetime.strptime(date_header, date_format)
- except ValueError:
- pass
- else:
- return wrapped(*args, **kwargs)
-
- # context.status_code = codes.UNAUTHORIZED
- # TODO remove this legacy
- # context.headers['WWW-Authenticate'] = 'VWS'
- text = 'Malformed date header.'
- content_type = 'text/plain; charset=ISO-8859-1'
- # TODO remove this legacy
- # context.headers['Content-Type'] = content_type
- return text, codes.UNAUTHORIZED, {
- 'Content-Type': content_type,
- 'WWW-Authenticate': 'VWS',
- }
-
-
-@wrapt.decorator
-def validate_date_in_range(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate date in the date header given to the query endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A `FORBIDDEN` response if the date is out of range.
- """
-
- date_header = request.headers['Date']
-
- for date_format in _accepted_date_formats():
- try:
- date = datetime.datetime.strptime(date_header, date_format)
- # We could break here but that would give a coverage report that is
- # not 100%.
- except ValueError:
- pass
-
- gmt = pytz.timezone('GMT')
- now = datetime.datetime.now(tz=gmt)
- date_from_header = date.replace(tzinfo=gmt)
- time_difference = now - date_from_header
-
- maximum_time_difference = datetime.timedelta(minutes=65)
-
- if abs(time_difference) < maximum_time_difference:
- return wrapped(*args, **kwargs)
-
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.REQUEST_TIME_TOO_SKEWED.value,
- }
- return json_dump(body), codes.FORBIDDEN
diff --git a/src/_mock_vws_server/vwq/_query_validators/image_validators.py b/src/_mock_vws_server/vwq/_query_validators/image_validators.py
deleted file mode 100644
index 5da0ff2b0..000000000
--- a/src/_mock_vws_server/vwq/_query_validators/image_validators.py
+++ /dev/null
@@ -1,267 +0,0 @@
-"""
-Input validators for the image field use in the mock query API.
-"""
-
-import cgi
-import io
-import uuid
-from typing import Any, Callable, Dict, Tuple
-
-import requests
-import wrapt
-from flask import request
-from PIL import Image
-from requests import codes
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-from .._constants import ResultCodes
-from .._mock_common import parse_multipart
-
-
-@wrapt.decorator
-def validate_image_field_given(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate that the image field is given.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- A ``BAD_REQUEST`` response if the image field is not given.
- """
-
- body_file = io.BytesIO(request.input_stream.getvalue())
-
- _, pdict = cgi.parse_header(request.headers['Content-Type'])
- parsed = parse_multipart(
- fp=body_file,
- pdict={
- 'boundary': pdict['boundary'].encode(),
- },
- )
-
- if 'image' in parsed.keys():
- return wrapped(*args, **kwargs)
-
- return 'No image.', codes.BAD_REQUEST
-
-
-@wrapt.decorator
-def validate_image_file_size(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the file size of the image given to the query endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
-
- Raises:
- requests.exceptions.ConnectionError: The image file size is too large.
- """
-
- body_file = io.BytesIO(request.input_stream.getvalue())
-
- _, pdict = cgi.parse_header(request.headers['Content-Type'])
- parsed = parse_multipart(
- fp=body_file,
- pdict={
- 'boundary': pdict['boundary'].encode(),
- },
- )
-
- [image] = parsed['image']
-
- # This is the documented maximum size of a PNG as per.
- # https://library.vuforia.com/articles/Solution/How-To-Perform-an-Image-Recognition-Query.
- # However, the tests show that this maximum size also applies to JPEG
- # files.
- max_bytes = 2 * 1024 * 1024
- if len(image) > max_bytes:
- raise requests.exceptions.ConnectionError
- return wrapped(*args, **kwargs)
-
-
-@wrapt.decorator
-def validate_image_dimensions(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the dimensions the image given to the query endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
-
- Raises:
- The result of calling the endpoint.
- An ``UNPROCESSABLE_ENTITY`` response if the image is given and is not
- within the maximum width and height limits.
- """
-
- body_file = io.BytesIO(request.input_stream.getvalue())
-
- _, pdict = cgi.parse_header(request.headers['Content-Type'])
- parsed = parse_multipart(
- fp=body_file,
- pdict={
- 'boundary': pdict['boundary'].encode(),
- },
- )
-
- [image] = parsed['image']
- assert isinstance(image, bytes)
- image_file = io.BytesIO(image)
- pil_image = Image.open(image_file)
- max_width = 30000
- max_height = 30000
- if pil_image.height <= max_height and pil_image.width <= max_width:
- return wrapped(*args, **kwargs)
-
- transaction_id = uuid.uuid4().hex
- result_code = ResultCodes.BAD_IMAGE.value
-
- # The response has an unusual format of separators, so we construct it
- # manually.
- return (
- '{"transaction_id": '
- f'"{transaction_id}",'
- f'"result_code":"{result_code}"'
- '}'
- ), codes.UNPROCESSABLE_ENTITY
-
-
-@wrapt.decorator
-def validate_image_format(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate the format of the image given to the query endpoint.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNPROCESSABLE_ENTITY` response if the image is given and is not
- either a PNG or a JPEG.
- """
-
- body_file = io.BytesIO(request.input_stream.getvalue())
-
- _, pdict = cgi.parse_header(request.headers['Content-Type'])
- parsed = parse_multipart(
- fp=body_file,
- pdict={
- 'boundary': pdict['boundary'].encode(),
- },
- )
-
- [image] = parsed['image']
-
- assert isinstance(image, bytes)
- image_file = io.BytesIO(image)
- pil_image = Image.open(image_file)
-
- if pil_image.format in ('PNG', 'JPEG'):
- return wrapped(*args, **kwargs)
-
- transaction_id = uuid.uuid4().hex
- result_code = ResultCodes.BAD_IMAGE.value
-
- # The response has an unusual format of separators, so we construct it
- # manually.
- return (
- '{"transaction_id": '
- f'"{transaction_id}",'
- f'"result_code":"{result_code}"'
- '}'
- ), codes.UNPROCESSABLE_ENTITY
-
-
-@wrapt.decorator
-def validate_image_is_image(
- wrapped: Callable[..., Tuple[str, int]],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> Tuple[str, int]:
- """
- Validate that the given image data is actually an image file.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- An `UNPROCESSABLE_ENTITY` response if image data is given and it is not
- an image file.
- """
-
- body_file = io.BytesIO(request.input_stream.getvalue())
-
- _, pdict = cgi.parse_header(request.headers['Content-Type'])
- parsed = parse_multipart(
- fp=body_file,
- pdict={
- 'boundary': pdict['boundary'].encode(),
- },
- )
-
- [image] = parsed['image']
-
- assert isinstance(image, bytes)
- image_file = io.BytesIO(image)
-
- try:
- Image.open(image_file)
- except OSError:
- transaction_id = uuid.uuid4().hex
- result_code = ResultCodes.BAD_IMAGE.value
-
- # The response has an unusual format of separators, so we construct it
- # manually.
- return (
- '{"transaction_id": '
- f'"{transaction_id}",'
- f'"result_code":"{result_code}"'
- '}'
- ), codes.UNPROCESSABLE_ENTITY
-
- return wrapped(*args, **kwargs)
diff --git a/src/_mock_vws_server/vwq/_query_validators/resources/query_out_of_bounds_response b/src/_mock_vws_server/vwq/_query_validators/resources/query_out_of_bounds_response
deleted file mode 100644
index 7a97a1674..000000000
--- a/src/_mock_vws_server/vwq/_query_validators/resources/query_out_of_bounds_response
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-Error 500 Server Error
-
-HTTP ERROR 500
-Problem accessing /v1/query. Reason:
-
Server Error
Caused by:
java.lang.ArrayIndexOutOfBoundsException: 1
- at com.kooaba.queryservice.auth.KWSAuthFilter.doFilter(KWSAuthFilter.java:81)
- at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
- at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
- at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
- at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
- at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
- at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
- at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
- at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
- at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
- at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
- at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
- at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:110)
- at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
- at org.eclipse.jetty.server.Server.handle(Server.java:497)
- at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
- at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
- at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
- at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
- at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
- at java.lang.Thread.run(Thread.java:748)
-
-
Powered by Jetty://
-
-
-
From 988a40d5ebe47ed8a53937fcb1812c267d7f4900 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 30 Mar 2020 17:32:43 +0100
Subject: [PATCH 0092/3455] Fix some lint issues
---
src/_mock_vws_server/vws/__init__.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index c19e25c22..9cc5261f6 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -43,7 +43,6 @@
def validate_request() -> None:
databases = get_all_databases()
run_services_validators(
- request_text=request.data.decode(),
request_headers=dict(request.headers),
# TODO not sure about this one
request_body=request.data,
From 62485726df90f0226ea8a63cab11fa86426dce95 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 30 Mar 2020 17:33:50 +0100
Subject: [PATCH 0093/3455] Fix some lint issues
---
src/_mock_vws_server/vws/__init__.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/_mock_vws_server/vws/__init__.py
index 9cc5261f6..e1495a760 100644
--- a/src/_mock_vws_server/vws/__init__.py
+++ b/src/_mock_vws_server/vws/__init__.py
@@ -93,10 +93,11 @@ def handle_request_time_too_skewed(e: RequestTimeTooSkewed) -> Tuple[str, int]:
return e.response_text, e.status_code
@VWS_FLASK_APP.errorhandler(OopsErrorOccurredResponse)
-def handle_oops_error_occurred(e: OopsErrorOccurredResponse) -> Tuple[str, int]:
+def handle_oops_error_occurred(e: OopsErrorOccurredResponse) -> Response:
content_type = 'text/html; charset=UTF-8'
response = make_response(e.response_text, e.status_code)
response.headers['Content-Type'] = content_type
+ assert isinstance(response, Response)
return response
@VWS_FLASK_APP.after_request
From 67dca5ae73fbb07893ddc2a05e4131ebef654e71 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 30 Mar 2020 17:43:15 +0100
Subject: [PATCH 0094/3455] Remove unnecessary file
---
.../oops_error_occurred_response.html | 41 -------------------
1 file changed, 41 deletions(-)
delete mode 100644 src/_mock_vws_server/vws/resources/oops_error_occurred_response.html
diff --git a/src/_mock_vws_server/vws/resources/oops_error_occurred_response.html b/src/_mock_vws_server/vws/resources/oops_error_occurred_response.html
deleted file mode 100644
index e72b8fc60..000000000
--- a/src/_mock_vws_server/vws/resources/oops_error_occurred_response.html
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
- Error
-
-
-
- Oops, an error occurred
-
-
- This exception has been logged with id 7db293le3.
-
-
-
-
From bd67b5019a3cedec95e1b3960c4d8d38b161cb18 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 30 Mar 2020 17:43:43 +0100
Subject: [PATCH 0095/3455] Remove unnecessary file
---
.../vws/resources/match_processing_response | 78 -------------------
1 file changed, 78 deletions(-)
delete mode 100644 src/_mock_vws_server/vws/resources/match_processing_response
diff --git a/src/_mock_vws_server/vws/resources/match_processing_response b/src/_mock_vws_server/vws/resources/match_processing_response
deleted file mode 100644
index 33d48f115..000000000
--- a/src/_mock_vws_server/vws/resources/match_processing_response
+++ /dev/null
@@ -1,78 +0,0 @@
-'\n\n\nError 500 Server Error
-title>\n\nHTTP ERROR 500
\nProblem accessing /v1/query. Reason:\n
Server Error
-p>Caused by:
org.jboss.resteasy.spi.UnhandledException: com.fasterxml.jackson.databind.exc.MismatchedInpu
-tException: No content to map due to end-of-input\n at [Source: (byte[])""; line: 1, column: 0]\n\tat org.jboss.restea
-sy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:76)\n\tat org.jboss.resteasy.core.ExceptionH
-andler.handleException(ExceptionHandler.java:212)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.writeException(S
-ynchronousDispatcher.java:168)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:4
-11)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:202)\n\tat org.jboss.resteas
-y.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:221)\n\tat org.jboss.reste
-asy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)\n\tat org.jboss.resteasy.plugi
-ns.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)\n\tat javax.servlet.http.HttpServlet.se
-rvice(HttpServlet.java:790)\n\tat org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:808)\n\tat org.ecl
-ipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669)\n\tat com.kooaba.queryservice.auth.KW
-SAuthFilter.doFilter(KWSAuthFilter.java:171)\n\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(Servl
-etHandler.java:1652)\n\tat org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)\n\tat org.eclips
-e.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)\n\tat org.eclipse.jetty.security.SecurityHandler.h
-andle(SecurityHandler.java:577)\n\tat org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223
-)\n\tat org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)\n\tat org.eclipse.jetty.ser
-vlet.ServletHandler.doScope(ServletHandler.java:515)\n\tat org.eclipse.jetty.server.session.SessionHandler.doScope(Ses
-sionHandler.java:185)\n\tat org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)\n\tat or
-g.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)\n\tat org.eclipse.jetty.server.handler.Con
-textHandlerCollection.handle(ContextHandlerCollection.java:215)\n\tat org.eclipse.jetty.server.handler.HandlerCollecti
-on.handle(HandlerCollection.java:110)\n\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java
-:97)\n\tat org.eclipse.jetty.server.Server.handle(Server.java:497)\n\tat org.eclipse.jetty.server.HttpChannel.handle(H
-ttpChannel.java:310)\n\tat org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)\n\tat org.eclip
-se.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool
-.runJob(QueuedThreadPool.java:635)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:55
-5)\n\tat java.lang.Thread.run(Thread.java:748)\nCaused by: com.fasterxml.jackson.databind.exc.MismatchedInputException
-: No content to map due to end-of-input\n at [Source: (byte[])""; line: 1, column: 0]\n\tat com.fasterxml.jackson.data
-bind.exc.MismatchedInputException.from(MismatchedInputException.java:59)\n\tat com.fasterxml.jackson.databind.ObjectMa
-pper._initForReading(ObjectMapper.java:4133)\n\tat com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(Object
-Mapper.java:3988)\n\tat com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3094)\n\tat com.kooaba
-.queryservice.domain.WebResult.setTargetData(WebResult.java:44)\n\tat com.kooaba.queryservice.domain.WebQueryResultPro
-cessor.formatResult(WebQueryResultProcessor.java:81)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.query
-Common(QueryResourceVuforia.java:230)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.limitedConcurrencyQu
-ery(QueryResourceVuforia.java:77)\n\tat com.kooaba.queryservice.services.QueryResourceCloudRecoWebAPI.query(QueryResou
-rceCloudRecoWebAPI.java:55)\n\tat sun.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)\n\tat sun.reflect.Deleg
-atingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java
-:606)\n\tat org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:139)\n\tat org.jboss.resteasy.co
-re.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:295)\n\tat org.jboss.resteasy.core.ResourceMethodIn
-voker.invoke(ResourceMethodInvoker.java:249)\n\tat org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethod
-Invoker.java:236)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:395)\n\t... 28
- more\n
\nCaused by:
com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map
-due to end-of-input\n at [Source: (byte[])""; line: 1, column: 0]\n\tat com.fasterxml.jackson.databind.exc.MismatchedI
-nputException.from(MismatchedInputException.java:59)\n\tat com.fasterxml.jackson.databind.ObjectMapper._initForReading
-(ObjectMapper.java:4133)\n\tat com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3988)\n\
-tat com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3094)\n\tat com.kooaba.queryservice.domain
-.WebResult.setTargetData(WebResult.java:44)\n\tat com.kooaba.queryservice.domain.WebQueryResultProcessor.formatResult(
-WebQueryResultProcessor.java:81)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.queryCommon(QueryResource
-Vuforia.java:230)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.limitedConcurrencyQuery(QueryResourceVuf
-oria.java:77)\n\tat com.kooaba.queryservice.services.QueryResourceCloudRecoWebAPI.query(QueryResourceCloudRecoWebAPI.j
-ava:55)\n\tat sun.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorI
-mpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:606)\n\tat org.jbos
-s.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:139)\n\tat org.jboss.resteasy.core.ResourceMethodInv
-oker.invokeOnTarget(ResourceMethodInvoker.java:295)\n\tat org.jboss.resteasy.core.ResourceMethodInvoker.invoke(Resourc
-eMethodInvoker.java:249)\n\tat org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:236)\n\
-tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:395)\n\tat org.jboss.resteasy.core
-.SynchronousDispatcher.invoke(SynchronousDispatcher.java:202)\n\tat org.jboss.resteasy.plugins.server.servlet.ServletC
-ontainerDispatcher.service(ServletContainerDispatcher.java:221)\n\tat org.jboss.resteasy.plugins.server.servlet.HttpSe
-rvletDispatcher.service(HttpServletDispatcher.java:56)\n\tat org.jboss.resteasy.plugins.server.servlet.HttpServletDisp
-atcher.service(HttpServletDispatcher.java:51)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\tat
- org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:808)\n\tat org.eclipse.jetty.servlet.ServletHandler
-$CachedChain.doFilter(ServletHandler.java:1669)\n\tat com.kooaba.queryservice.auth.KWSAuthFilter.doFilter(KWSAuthFilte
-r.java:171)\n\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)\n\tat org.ec
-lipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)\n\tat org.eclipse.jetty.server.handler.ScopedHand
-ler.handle(ScopedHandler.java:143)\n\tat org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)\n
-\tat org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)\n\tat org.eclipse.jetty.server.
-handler.ContextHandler.doHandle(ContextHandler.java:1127)\n\tat org.eclipse.jetty.servlet.ServletHandler.doScope(Servl
-etHandler.java:515)\n\tat org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)\n\tat org.e
-clipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)\n\tat org.eclipse.jetty.server.handler.Sc
-opedHandler.handle(ScopedHandler.java:141)\n\tat org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(Cont
-extHandlerCollection.java:215)\n\tat org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:
-110)\n\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)\n\tat org.eclipse.jetty.serv
-er.Server.handle(Server.java:497)\n\tat org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)\n\tat org.ec
-lipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)\n\tat org.eclipse.jetty.io.AbstractConnection$2.
-run(AbstractConnection.java:540)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635
-)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)\n\tat java.lang.Thread.run(Thr
-ead.java:748)\n
\n
Powered by Jetty://
\n\n\n\n'
From b62458bd0c22e7517b08457db5aba8765d6505c1 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 30 Mar 2020 17:45:40 +0100
Subject: [PATCH 0096/3455] Remove unnecessary file
---
src/_mock_vws_server/vwq/_mock_common.py | 132 -----------------------
src/_mock_vws_server/vws/_mock_common.py | 132 -----------------------
2 files changed, 264 deletions(-)
delete mode 100644 src/_mock_vws_server/vwq/_mock_common.py
delete mode 100644 src/_mock_vws_server/vws/_mock_common.py
diff --git a/src/_mock_vws_server/vwq/_mock_common.py b/src/_mock_vws_server/vwq/_mock_common.py
deleted file mode 100644
index 4a13a3cb9..000000000
--- a/src/_mock_vws_server/vwq/_mock_common.py
+++ /dev/null
@@ -1,132 +0,0 @@
-"""
-Common utilities for creating mock routes.
-"""
-
-import cgi
-import email.utils
-import io
-import json
-from typing import Any, Callable, Dict, List, Mapping, Tuple, Union
-
-import wrapt
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-
-class Route:
- """
- A container for the route details which `requests_mock` needs.
-
- We register routes with names, and when we have an instance to work with
- later.
- """
-
- route_name: str
- path_pattern: str
- http_methods: List[str]
-
- def __init__(
- self,
- route_name: str,
- path_pattern: str,
- http_methods: List[str],
- ) -> None:
- """
- Args:
- route_name: The name of the method.
- path_pattern: The end part of a URL pattern. E.g. `/targets` or
- `/targets/.+`.
- http_methods: HTTP methods that map to the route function.
-
- Attributes:
- route_name: The name of the method.
- path_pattern: The end part of a URL pattern. E.g. `/targets` or
- `/targets/.+`.
- http_methods: HTTP methods that map to the route function.
- endpoint: The method `requests_mock` should call when the endpoint
- is requested.
- """
- self.route_name = route_name
- self.path_pattern = path_pattern
- self.http_methods = http_methods
-
-
-def json_dump(body: Dict[str, Any]) -> str:
- """
- Returns:
- JSON dump of data in the same way that Vuforia dumps data.
- """
- return json.dumps(obj=body, separators=(',', ':'))
-
-
-@wrapt.decorator
-def set_content_length_header(
- wrapped: Callable[..., str],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> str:
- """
- Set the `Content-Length` header.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- """
- _, context = args
-
- result = wrapped(*args, **kwargs)
- context.headers['Content-Length'] = str(len(result))
- return result
-
-
-@wrapt.decorator
-def set_date_header(
- wrapped: Callable[..., str],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> str:
- """
- Set the `Date` header.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- """
- _, context = args
- date = email.utils.formatdate(None, localtime=False, usegmt=True)
-
- result = wrapped(*args, **kwargs)
- context.headers['Date'] = date
- return result
-
-
-def parse_multipart( # pylint: disable=invalid-name
- fp: io.BytesIO,
- pdict: Mapping[str, bytes],
-) -> Dict[str, List[Union[str, bytes]]]:
- """
- Return parsed ``pdict``.
-
- Wrapper for ``_parse_multipart`` to work around
- https://bugs.python.org/issue34226.
-
- See https://docs.python.org/3.8/library/cgi.html#_parse_multipart.
- """
- pdict = {
- 'CONTENT-LENGTH': str(len(fp.getvalue())).encode(),
- **pdict,
- }
-
- return cgi.parse_multipart(fp=fp, pdict=pdict)
diff --git a/src/_mock_vws_server/vws/_mock_common.py b/src/_mock_vws_server/vws/_mock_common.py
deleted file mode 100644
index 4a13a3cb9..000000000
--- a/src/_mock_vws_server/vws/_mock_common.py
+++ /dev/null
@@ -1,132 +0,0 @@
-"""
-Common utilities for creating mock routes.
-"""
-
-import cgi
-import email.utils
-import io
-import json
-from typing import Any, Callable, Dict, List, Mapping, Tuple, Union
-
-import wrapt
-from requests_mock.request import _RequestObjectProxy
-from requests_mock.response import _Context
-
-
-class Route:
- """
- A container for the route details which `requests_mock` needs.
-
- We register routes with names, and when we have an instance to work with
- later.
- """
-
- route_name: str
- path_pattern: str
- http_methods: List[str]
-
- def __init__(
- self,
- route_name: str,
- path_pattern: str,
- http_methods: List[str],
- ) -> None:
- """
- Args:
- route_name: The name of the method.
- path_pattern: The end part of a URL pattern. E.g. `/targets` or
- `/targets/.+`.
- http_methods: HTTP methods that map to the route function.
-
- Attributes:
- route_name: The name of the method.
- path_pattern: The end part of a URL pattern. E.g. `/targets` or
- `/targets/.+`.
- http_methods: HTTP methods that map to the route function.
- endpoint: The method `requests_mock` should call when the endpoint
- is requested.
- """
- self.route_name = route_name
- self.path_pattern = path_pattern
- self.http_methods = http_methods
-
-
-def json_dump(body: Dict[str, Any]) -> str:
- """
- Returns:
- JSON dump of data in the same way that Vuforia dumps data.
- """
- return json.dumps(obj=body, separators=(',', ':'))
-
-
-@wrapt.decorator
-def set_content_length_header(
- wrapped: Callable[..., str],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> str:
- """
- Set the `Content-Length` header.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- """
- _, context = args
-
- result = wrapped(*args, **kwargs)
- context.headers['Content-Length'] = str(len(result))
- return result
-
-
-@wrapt.decorator
-def set_date_header(
- wrapped: Callable[..., str],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> str:
- """
- Set the `Date` header.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- """
- _, context = args
- date = email.utils.formatdate(None, localtime=False, usegmt=True)
-
- result = wrapped(*args, **kwargs)
- context.headers['Date'] = date
- return result
-
-
-def parse_multipart( # pylint: disable=invalid-name
- fp: io.BytesIO,
- pdict: Mapping[str, bytes],
-) -> Dict[str, List[Union[str, bytes]]]:
- """
- Return parsed ``pdict``.
-
- Wrapper for ``_parse_multipart`` to work around
- https://bugs.python.org/issue34226.
-
- See https://docs.python.org/3.8/library/cgi.html#_parse_multipart.
- """
- pdict = {
- 'CONTENT-LENGTH': str(len(fp.getvalue())).encode(),
- **pdict,
- }
-
- return cgi.parse_multipart(fp=fp, pdict=pdict)
From f5dd9a1f96e4a46101aaba26cd2bf68f50e62c5d Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 30 Mar 2020 17:47:14 +0100
Subject: [PATCH 0097/3455] Remove unnecessary file
---
src/mock_vws/target.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index b68af451d..5a0876c0b 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -123,6 +123,7 @@ def _post_processing_status(self) -> TargetStatuses:
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'.
From 2b1ecd91bfaecbfc87640acec2806eb992181ee1 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 30 Mar 2020 17:47:37 +0100
Subject: [PATCH 0098/3455] Remove unnecessary change
---
src/mock_vws/target.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 5a0876c0b..3cae48e51 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -22,9 +22,6 @@ class Target: # pylint: disable=too-many-instance-attributes
https://developer.vuforia.com/target-manager.
"""
- # TODO remove
- NUM = 1
-
name: str
target_id: str
active_flag: bool
From 11d1f9c9bae5e1674b3795bd23eb4f0dc871ca68 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 30 Mar 2020 17:48:48 +0100
Subject: [PATCH 0099/3455] Add docstring
---
src/mock_vws/target.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 3cae48e51..90fbe4654 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -91,7 +91,7 @@ def __init__( # pylint: disable=too-many-arguments
def __repr__(self) -> str:
"""
- XXX
+ Return a representation which includes the target ID.
"""
class_name = self.__class__.__name__
return f'<{class_name}: {self.target_id}>'
From 512824e4842849321f6104d2831e073a9b003ac4 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 30 Mar 2020 22:12:39 +0100
Subject: [PATCH 0100/3455] Revert "Revert "[WIP] Move logic out of the
requests_mock handlers""
---
src/mock_vws/_mock_common.py | 13 ++--------
src/mock_vws/_mock_web_services_api.py | 5 ++++
src/mock_vws/_query_tools.py | 34 ++++++++++++++++++++++++++
3 files changed, 41 insertions(+), 11 deletions(-)
create mode 100644 src/mock_vws/_query_tools.py
diff --git a/src/mock_vws/_mock_common.py b/src/mock_vws/_mock_common.py
index d1b36dc9d..5d7377326 100644
--- a/src/mock_vws/_mock_common.py
+++ b/src/mock_vws/_mock_common.py
@@ -2,6 +2,8 @@
Common utilities for creating mock routes.
"""
+# TODO split this up
+
import cgi
import email.utils
import io
@@ -116,14 +118,3 @@ def parse_multipart( # pylint: disable=invalid-name
return cgi.parse_multipart(fp=fp, pdict=pdict)
-def images_match(image: io.BytesIO, another_image: io.BytesIO) -> bool:
- """
- Given two images, return whether they are matching.
-
- In the real Vuforia, this matching is fuzzy.
- For now, we check exact byte matching.
-
- See https://github.com/adamtheturtle/vws-python-mock/issues/3 for changing
- that.
- """
- return bool(image.getvalue() == another_image.getvalue())
diff --git a/src/mock_vws/_mock_web_services_api.py b/src/mock_vws/_mock_web_services_api.py
index 003597e1a..b81eaf790 100644
--- a/src/mock_vws/_mock_web_services_api.py
+++ b/src/mock_vws/_mock_web_services_api.py
@@ -325,6 +325,8 @@ def delete_target(
}
return json_dump(body)
+ # TODO make this target.delete()
+ # and have this raise a target status processing exception
gmt = pytz.timezone('GMT')
now = datetime.datetime.now(tz=gmt)
target.delete_date = now
@@ -358,6 +360,8 @@ def database_summary(
)
assert isinstance(database, VuforiaDatabase)
+ # TODO make a helper to get a database summary report from a
+ # VuforiaDatabase
active_images = len(
[
target for target in database.targets
@@ -648,6 +652,7 @@ def target_summary(
)
assert isinstance(database, VuforiaDatabase)
+ # TODO have this be a helper
body = {
'status': target.status,
'transaction_id': uuid.uuid4().hex,
diff --git a/src/mock_vws/_query_tools.py b/src/mock_vws/_query_tools.py
new file mode 100644
index 000000000..8593878ef
--- /dev/null
+++ b/src/mock_vws/_query_tools.py
@@ -0,0 +1,34 @@
+"""
+Tools for making Vuforia queries.
+"""
+
+from typing import List
+
+class MatchingTargetsWithProcessingStatus(Exception):
+ pass
+
+class ActiveMatchingTargetsDeleteProcessing(Exception):
+ pass
+
+def _images_match(image: io.BytesIO, another_image: io.BytesIO) -> bool:
+ """
+ Given two images, return whether they are matching.
+
+ In the real Vuforia, this matching is fuzzy.
+ For now, we check exact byte matching.
+
+ See https://github.com/adamtheturtle/vws-python-mock/issues/3 for changing
+ that.
+ """
+ return bool(image.getvalue() == another_image.getvalue())
+
+
+def _get_query_matches(image: io.BytesIO, database: VuforiaDatabase) -> List[Target]:
+ """
+ Given an image and a database, return the matches for a query.
+ """
+ pass
+
+def _get_query_match_result_data(
+
+)
From 3080264b18bb37c4c5c1d20240ba87793b087269 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 30 Mar 2020 22:30:07 +0100
Subject: [PATCH 0101/3455] Move flask server into main mock_vws directory
---
src/{_mock_vws_server => mock_vws/_flask_server}/Dockerfile | 0
src/{_mock_vws_server => mock_vws/_flask_server}/__init__.py | 0
.../_flask_server}/storage/__init__.py | 0
src/{_mock_vws_server => mock_vws/_flask_server}/vwq/__init__.py | 0
.../_flask_server}/vwq/_constants.py | 0
.../_flask_server}/vwq/_database_matchers.py | 0
.../_flask_server}/vwq/resources/match_processing_response | 0
src/{_mock_vws_server => mock_vws/_flask_server}/vws/__init__.py | 0
.../_flask_server}/vws/_constants.py | 0
.../_flask_server}/vws/_databases.py | 0
10 files changed, 0 insertions(+), 0 deletions(-)
rename src/{_mock_vws_server => mock_vws/_flask_server}/Dockerfile (100%)
rename src/{_mock_vws_server => mock_vws/_flask_server}/__init__.py (100%)
rename src/{_mock_vws_server => mock_vws/_flask_server}/storage/__init__.py (100%)
rename src/{_mock_vws_server => mock_vws/_flask_server}/vwq/__init__.py (100%)
rename src/{_mock_vws_server => mock_vws/_flask_server}/vwq/_constants.py (100%)
rename src/{_mock_vws_server => mock_vws/_flask_server}/vwq/_database_matchers.py (100%)
rename src/{_mock_vws_server => mock_vws/_flask_server}/vwq/resources/match_processing_response (100%)
rename src/{_mock_vws_server => mock_vws/_flask_server}/vws/__init__.py (100%)
rename src/{_mock_vws_server => mock_vws/_flask_server}/vws/_constants.py (100%)
rename src/{_mock_vws_server => mock_vws/_flask_server}/vws/_databases.py (100%)
diff --git a/src/_mock_vws_server/Dockerfile b/src/mock_vws/_flask_server/Dockerfile
similarity index 100%
rename from src/_mock_vws_server/Dockerfile
rename to src/mock_vws/_flask_server/Dockerfile
diff --git a/src/_mock_vws_server/__init__.py b/src/mock_vws/_flask_server/__init__.py
similarity index 100%
rename from src/_mock_vws_server/__init__.py
rename to src/mock_vws/_flask_server/__init__.py
diff --git a/src/_mock_vws_server/storage/__init__.py b/src/mock_vws/_flask_server/storage/__init__.py
similarity index 100%
rename from src/_mock_vws_server/storage/__init__.py
rename to src/mock_vws/_flask_server/storage/__init__.py
diff --git a/src/_mock_vws_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
similarity index 100%
rename from src/_mock_vws_server/vwq/__init__.py
rename to src/mock_vws/_flask_server/vwq/__init__.py
diff --git a/src/_mock_vws_server/vwq/_constants.py b/src/mock_vws/_flask_server/vwq/_constants.py
similarity index 100%
rename from src/_mock_vws_server/vwq/_constants.py
rename to src/mock_vws/_flask_server/vwq/_constants.py
diff --git a/src/_mock_vws_server/vwq/_database_matchers.py b/src/mock_vws/_flask_server/vwq/_database_matchers.py
similarity index 100%
rename from src/_mock_vws_server/vwq/_database_matchers.py
rename to src/mock_vws/_flask_server/vwq/_database_matchers.py
diff --git a/src/_mock_vws_server/vwq/resources/match_processing_response b/src/mock_vws/_flask_server/vwq/resources/match_processing_response
similarity index 100%
rename from src/_mock_vws_server/vwq/resources/match_processing_response
rename to src/mock_vws/_flask_server/vwq/resources/match_processing_response
diff --git a/src/_mock_vws_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
similarity index 100%
rename from src/_mock_vws_server/vws/__init__.py
rename to src/mock_vws/_flask_server/vws/__init__.py
diff --git a/src/_mock_vws_server/vws/_constants.py b/src/mock_vws/_flask_server/vws/_constants.py
similarity index 100%
rename from src/_mock_vws_server/vws/_constants.py
rename to src/mock_vws/_flask_server/vws/_constants.py
diff --git a/src/_mock_vws_server/vws/_databases.py b/src/mock_vws/_flask_server/vws/_databases.py
similarity index 100%
rename from src/_mock_vws_server/vws/_databases.py
rename to src/mock_vws/_flask_server/vws/_databases.py
From 2f4a4847332106333f305c509cc5fd9998507a8d Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 30 Mar 2020 22:36:22 +0100
Subject: [PATCH 0102/3455] Progress towards working flask
---
.../_flask_server/storage/__init__.py | 2 +-
src/mock_vws/_flask_server/vwq/__init__.py | 66 +++----------------
src/mock_vws/_flask_server/vws/_databases.py | 2 +-
tests/mock_vws/fixtures/vuforia_backends.py | 6 +-
4 files changed, 15 insertions(+), 61 deletions(-)
diff --git a/src/mock_vws/_flask_server/storage/__init__.py b/src/mock_vws/_flask_server/storage/__init__.py
index 019fd3e8a..1e1dde770 100644
--- a/src/mock_vws/_flask_server/storage/__init__.py
+++ b/src/mock_vws/_flask_server/storage/__init__.py
@@ -71,7 +71,7 @@ def create_target(database_name: str) -> Tuple[str, int]:
application_metadata=request.json['application_metadata'],
)
target.target_id = request.json['target_id']
- database.targets.append(target)
+ database.targets.add(target)
return jsonify(target.to_dict()), codes.CREATED
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index a8a67bc24..621b9b660 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -17,69 +17,23 @@
from mock_vws._mock_common import json_dump, parse_multipart
from mock_vws.database import VuforiaDatabase
-# TODO move this
from ..vws._databases import get_all_databases
-from ._query_validators import (
- validate_accept_header,
- validate_content_type_header,
- validate_extra_fields,
- validate_include_target_data,
- validate_max_num_results,
- validate_project_state,
-)
-from ._query_validators.auth_validators import (
- validate_auth_header_exists,
- validate_auth_header_has_signature,
- validate_auth_header_number_of_parts,
- validate_authorization,
- validate_client_key_exists,
-)
-from ._query_validators.content_length_validators import (
- validate_content_length_header_is_int,
- validate_content_length_header_not_too_large,
- validate_content_length_header_not_too_small,
-)
-from ._query_validators.date_validators import (
- validate_date_format,
- validate_date_header_given,
- validate_date_in_range,
-)
-from ._query_validators.image_validators import (
- validate_image_dimensions,
- validate_image_field_given,
- validate_image_file_size,
- validate_image_format,
- validate_image_is_image,
-)
+from mock_vws._query_validators import run_query_validators
CLOUDRECO_FLASK_APP = Flask(__name__)
@CLOUDRECO_FLASK_APP.before_request
-@validate_content_length_header_is_int
-@validate_content_length_header_not_too_large
-@validate_content_length_header_not_too_small
-@validate_auth_header_exists
-@validate_auth_header_number_of_parts
-@validate_auth_header_has_signature
-@validate_client_key_exists
-@validate_authorization
-@validate_project_state
-@validate_accept_header
-@validate_content_type_header
-@validate_extra_fields
-@validate_image_field_given
-@validate_image_is_image
-@validate_image_format
-@validate_image_dimensions
-@validate_image_file_size
-@validate_max_num_results
-@validate_include_target_data
-@validate_date_header_given
-@validate_date_format
-@validate_date_in_range
def validate_request() -> None:
- pass
+ databases = get_all_databases()
+ run_query_validators(
+ request_headers=dict(request.headers),
+ # TODO not sure about this one
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
@CLOUDRECO_FLASK_APP.after_request
diff --git a/src/mock_vws/_flask_server/vws/_databases.py b/src/mock_vws/_flask_server/vws/_databases.py
index 8a701fc1c..f4b384d88 100644
--- a/src/mock_vws/_flask_server/vws/_databases.py
+++ b/src/mock_vws/_flask_server/vws/_databases.py
@@ -73,7 +73,7 @@ def get_all_databases() -> Set[VuforiaDatabase]:
delete_date_optional,
)
target.delete_date = target.delete_date.replace(tzinfo=gmt)
- new_database.targets.append(target)
+ new_database.targets.add(target)
databases.add(new_database)
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index 8c46feece..1d3a742fe 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -14,9 +14,9 @@
from requests import codes
from requests_mock_flask import add_flask_app_to_mock
-from _mock_vws_server.storage import STORAGE_FLASK_APP
-from _mock_vws_server.vwq import CLOUDRECO_FLASK_APP
-from _mock_vws_server.vws import STORAGE_BASE_URL, VWS_FLASK_APP
+from mock_vws._flask_server.storage import STORAGE_FLASK_APP
+from mock_vws._flask_server.vwq import CLOUDRECO_FLASK_APP
+from mock_vws._flask_server.vws import STORAGE_BASE_URL, VWS_FLASK_APP
from mock_vws import MockVWS
from mock_vws._constants import ResultCodes
from mock_vws.database import VuforiaDatabase
From 0e919eded8a44466a05cedadacc405a00b2df4f6 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 30 Mar 2020 22:44:39 +0100
Subject: [PATCH 0103/3455] Progress towards working flask
---
src/mock_vws/_flask_server/vwq/__init__.py | 28 ++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 621b9b660..51ce93436 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -19,6 +19,29 @@
from ..vws._databases import get_all_databases
from mock_vws._query_validators import run_query_validators
+from mock_vws._query_validators.exceptions import (
+ DateHeaderNotGiven,
+ DateFormatNotValid,
+ RequestTimeTooSkewed,
+ BadImage,
+ AuthenticationFailure,
+ AuthenticationFailureGoodFormatting,
+ ImageNotGiven,
+ AuthHeaderMissing,
+ MalformedAuthHeader,
+ UnknownParameters,
+ InactiveProject,
+ InvalidMaxNumResults,
+ MaxNumResultsOutOfRange,
+ InvalidIncludeTargetData,
+ UnsupportedMediaType,
+ InvalidAcceptHeader,
+ BoundaryNotInBody,
+ NoBoundaryFound,
+ QueryOutOfBounds,
+ ContentLengthHeaderTooLarge,
+ ContentLengthHeaderNotInt
+)
CLOUDRECO_FLASK_APP = Flask(__name__)
@@ -36,6 +59,11 @@ def validate_request() -> None:
)
+@CLOUDRECO_FLASK_APP.errorhandler(UnknownTarget)
+def handle_unknown_target(e: UnknownTarget) -> Tuple[str, int]:
+ return e.response_text, e.status_code
+
+
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
response.headers['Connection'] = 'keep-alive'
From d1d877e6572dd17f6b1f678ee6fc59fda58d7842 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 01:32:23 +0100
Subject: [PATCH 0104/3455] Fix a bunch of tests
---
dev-requirements.txt | 2 +-
src/mock_vws/_flask_server/vwq/__init__.py | 13 +++++++++----
src/mock_vws/_flask_server/vws/__init__.py | 1 -
src/mock_vws/_services_validators/key_validators.py | 2 +-
4 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 09757b222..c8defba4c 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -32,5 +32,5 @@ sphinx_paramlinks==0.3.7
sphinxcontrib-spelling==4.3.0
timeout-decorator==0.4.1 # Decorate functions to time out.
twine==3.1.1
-vulture==1.3
+vulture==1.4
yapf==0.29.0 # Automatic formatting for Python
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 51ce93436..443017d9a 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -8,7 +8,7 @@
from typing import Any, Dict, List, Tuple
import pytz
-from flask import Flask, Response, request
+from flask import Flask, Response, make_response, request
from requests import codes
from mock_vws._base64_decoding import decode_base64
@@ -59,9 +59,14 @@ def validate_request() -> None:
)
-@CLOUDRECO_FLASK_APP.errorhandler(UnknownTarget)
-def handle_unknown_target(e: UnknownTarget) -> Tuple[str, int]:
- return e.response_text, e.status_code
+# @CLOUDRECO_FLASK_APP.errorhandler(DateHeaderNotGiven)
+# def handle_date_header_not_given(e: DateHeaderNotGiven) -> Response:
+# content_type = 'text/plain; charset=ISO-8859-1'
+# response = make_response(e.response_text, e.status_code)
+# response.headers['Content-Type'] = content_type
+# response.headers['WWW-Authenticate'] = 'VWS'
+# assert isinstance(response, Response)
+# return response
@CLOUDRECO_FLASK_APP.after_request
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index e1495a760..3613ca276 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -7,7 +7,6 @@
import requests
from flask import Flask, Response, request, make_response
-from flask_json_schema import JsonSchema, JsonValidationError
from PIL import Image
from requests import codes
diff --git a/src/mock_vws/_services_validators/key_validators.py b/src/mock_vws/_services_validators/key_validators.py
index 4eac2bd12..fd2140171 100644
--- a/src/mock_vws/_services_validators/key_validators.py
+++ b/src/mock_vws/_services_validators/key_validators.py
@@ -141,7 +141,7 @@ def validate_keys(
optional_keys = matching_route.optional_keys
allowed_keys = mandatory_keys.union(optional_keys)
- if request_body is None and not allowed_keys:
+ if not request_body and not allowed_keys:
return
request_text = request_body.decode()
From a510605aa1a1e382987c7253b03511af60c7078e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 01:50:52 +0100
Subject: [PATCH 0105/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 443017d9a..88fa5930a 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -52,7 +52,7 @@ def validate_request() -> None:
run_query_validators(
request_headers=dict(request.headers),
# TODO not sure about this one
- request_body=request.data,
+ request_body=request.input_stream.getvalue(),
request_method=request.method,
request_path=request.path,
databases=databases,
@@ -68,6 +68,16 @@ def validate_request() -> None:
# assert isinstance(response, Response)
# return response
+@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
+def handle_content_length_header_too_large(
+ e: ContentLengthHeaderTooLarge,
+) -> Response:
+ # import pdb; pdb.set_trace()
+ response = make_response(e.response_text, e.status_code)
+ response.headers = {'Connection': 'keep-alive'}
+ assert isinstance(response, Response)
+ return response
+
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
From d9d8eb42a3925924a99e1a17f0b6be6cf2c66189 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 02:21:30 +0100
Subject: [PATCH 0106/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 31 +++++++++++-----------
1 file changed, 16 insertions(+), 15 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 88fa5930a..af2cdbc7d 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -59,40 +59,41 @@ def validate_request() -> None:
)
-# @CLOUDRECO_FLASK_APP.errorhandler(DateHeaderNotGiven)
-# def handle_date_header_not_given(e: DateHeaderNotGiven) -> Response:
-# content_type = 'text/plain; charset=ISO-8859-1'
-# response = make_response(e.response_text, e.status_code)
-# response.headers['Content-Type'] = content_type
-# response.headers['WWW-Authenticate'] = 'VWS'
-# assert isinstance(response, Response)
-# return response
+
+class MyResponse(Response):
+ default_mimetype = None#'FOOBAR'
+
+CLOUDRECO_FLASK_APP.response_class = MyResponse
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
def handle_content_length_header_too_large(
e: ContentLengthHeaderTooLarge,
) -> Response:
- # import pdb; pdb.set_trace()
response = make_response(e.response_text, e.status_code)
response.headers = {'Connection': 'keep-alive'}
assert isinstance(response, Response)
return response
+@CLOUDRECO_FLASK_APP.errorhandler(UnsupportedMediaType)
+def handle_unsupported_media_type(
+ e: UnsupportedMediaType,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ # del response.headers['Content-Type'] #= 'FOO'
+ assert isinstance(response, Response)
+ return response
+
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
response.headers['Connection'] = 'keep-alive'
- if response.status_code != codes.INTERNAL_SERVER_ERROR:
- response.headers['Content-Type'] = 'application/json'
- if response.status_code == codes.UNSUPPORTED_MEDIA_TYPE:
- # response.headers.pop('Content-Type')
- # TODO we need to remove this somehow but I don't know how
- response.headers['Content-Type'] = ''
response.headers['Server'] = 'nginx'
content_length = len(response.data)
response.headers['Content-Length'] = str(content_length)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
response.headers['Date'] = date
+ if response.status_code == codes.OK:
+ response.headers['Content-Type'] = 'application/json'
return response
From c65e174942d02118af069ea20c040494c5731471 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 02:24:21 +0100
Subject: [PATCH 0107/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index af2cdbc7d..818606d06 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -79,7 +79,14 @@ def handle_unsupported_media_type(
e: UnsupportedMediaType,
) -> Response:
response = make_response(e.response_text, e.status_code)
- # del response.headers['Content-Type'] #= 'FOO'
+ assert isinstance(response, Response)
+ return response
+
+@CLOUDRECO_FLASK_APP.errorhandler(BadImage)
+def handle_bad_image(
+ e: BadImage,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
@@ -92,7 +99,10 @@ def set_headers(response: Response) -> Response:
response.headers['Content-Length'] = str(content_length)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
response.headers['Date'] = date
- if response.status_code == codes.OK:
+ if response.status_code in (
+ codes.OK,
+ codes.UNPROCESSABLE_ENTITY,
+ ):
response.headers['Content-Type'] = 'application/json'
return response
From b6f8b803db6dd2f95e24429ab80a016f925b7103 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 10:02:07 +0100
Subject: [PATCH 0108/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 818606d06..964b0d24a 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -90,6 +90,14 @@ def handle_bad_image(
assert isinstance(response, Response)
return response
+@CLOUDRECO_FLASK_APP.errorhandler(UnknownParameters)
+def handle_unknown_parameters(
+ e: UnknownParameters,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ assert isinstance(response, Response)
+ return response
+
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
@@ -102,6 +110,7 @@ def set_headers(response: Response) -> Response:
if response.status_code in (
codes.OK,
codes.UNPROCESSABLE_ENTITY,
+ codes.BAD_REQUEST,
):
response.headers['Content-Type'] = 'application/json'
return response
@@ -202,6 +211,7 @@ def query() -> Tuple[str, int]:
content_type = 'text/html; charset=ISO-8859-1'
# TODO remove legacy
# context.headers['Content-Type'] = content_type
+ # TODO remove file copied to this dir
return (
Path(match_processing_resp_file).read_text(),
codes.INTERNAL_SERVER_ERROR,
From 02aa27792a7b8c349fdbdf69314af8b27992df4b Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 10:03:16 +0100
Subject: [PATCH 0109/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 964b0d24a..589754315 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -99,6 +99,24 @@ def handle_unknown_parameters(
return response
+@CLOUDRECO_FLASK_APP.errorhandler(RequestTimeTooSkewed)
+def handle_request_time_too_skewed(
+ e: RequestTimeTooSkewed,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ assert isinstance(response, Response)
+ return response
+
+
+@CLOUDRECO_FLASK_APP.errorhandler(ImageNotGiven)
+def handle_image_not_given(
+ e: ImageNotGiven,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ assert isinstance(response, Response)
+ return response
+
+
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
response.headers['Connection'] = 'keep-alive'
From 77abbb4092af0178e8582ccd3f91638d4ce7d279 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 10:06:48 +0100
Subject: [PATCH 0110/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 34 ++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 589754315..387749818 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -117,6 +117,40 @@ def handle_image_not_given(
return response
+@CLOUDRECO_FLASK_APP.errorhandler(InactiveProject)
+def handle_inactive_project(
+ e: InactiveProject,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ assert isinstance(response, Response)
+ return response
+
+
+@CLOUDRECO_FLASK_APP.errorhandler(InvalidIncludeTargetData)
+def handle_invalid_include_target_data(
+ e: InvalidIncludeTargetData,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ assert isinstance(response, Response)
+ return response
+
+@CLOUDRECO_FLASK_APP.errorhandler(InvalidMaxNumResults)
+def handle_invalid_max_num_results(
+ e: InvalidMaxNumResults,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ assert isinstance(response, Response)
+ return response
+
+@CLOUDRECO_FLASK_APP.errorhandler(MaxNumResultsOutOfRange)
+def handle_max_num_results_out_of_range(
+ e: MaxNumResultsOutOfRange,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ assert isinstance(response, Response)
+ return response
+
+
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
response.headers['Connection'] = 'keep-alive'
From c3395c26fd8ebe795e230f00d145421c9d595185 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 10:10:43 +0100
Subject: [PATCH 0111/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 23 +++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 387749818..28321d7b4 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -151,6 +151,27 @@ def handle_max_num_results_out_of_range(
return response
+@CLOUDRECO_FLASK_APP.errorhandler(NoBoundaryFound)
+def handle_no_boundary_found(
+ e: NoBoundaryFound,
+) -> Response:
+ content_type = 'text/html;charset=UTF-8'
+ response = make_response(e.response_text, e.status_code)
+ response.headers['Content-Type'] = content_type
+ assert isinstance(response, Response)
+ return response
+
+@CLOUDRECO_FLASK_APP.errorhandler(BoundaryNotInBody)
+def handle_boundary_not_in_body(
+ e: BoundaryNotInBody,
+) -> Response:
+ content_type = 'text/html;charset=UTF-8'
+ response = make_response(e.response_text, e.status_code)
+ response.headers['Content-Type'] = content_type
+ assert isinstance(response, Response)
+ return response
+
+
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
response.headers['Connection'] = 'keep-alive'
@@ -163,7 +184,7 @@ def set_headers(response: Response) -> Response:
codes.OK,
codes.UNPROCESSABLE_ENTITY,
codes.BAD_REQUEST,
- ):
+ ) and 'Content-Type' not in response.headers:
response.headers['Content-Type'] = 'application/json'
return response
From 5989bebb99f555424ede8918549d716ed2e761aa Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 10:12:08 +0100
Subject: [PATCH 0112/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 28321d7b4..4d8cf5c90 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -184,6 +184,7 @@ def set_headers(response: Response) -> Response:
codes.OK,
codes.UNPROCESSABLE_ENTITY,
codes.BAD_REQUEST,
+ codes.FORBIDDEN,
) and 'Content-Type' not in response.headers:
response.headers['Content-Type'] = 'application/json'
return response
From 5243bc7db8aaacd29e351538e175274c6cc36b8d Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 10:13:44 +0100
Subject: [PATCH 0113/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 4d8cf5c90..36940aa15 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -82,6 +82,14 @@ def handle_unsupported_media_type(
assert isinstance(response, Response)
return response
+@CLOUDRECO_FLASK_APP.errorhandler(InvalidAcceptHeader)
+def handle_invalid_accept_header(
+ e: InvalidAcceptHeader,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ assert isinstance(response, Response)
+ return response
+
@CLOUDRECO_FLASK_APP.errorhandler(BadImage)
def handle_bad_image(
e: BadImage,
From 33b65af46e3dc58a0dc03bc699a79829dcbb1cdf Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 10:17:07 +0100
Subject: [PATCH 0114/3455] Fix a bunch of tests
---
src/mock_vws/_query_validators/content_type_validators.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/mock_vws/_query_validators/content_type_validators.py b/src/mock_vws/_query_validators/content_type_validators.py
index 6d019de3c..c80490a72 100644
--- a/src/mock_vws/_query_validators/content_type_validators.py
+++ b/src/mock_vws/_query_validators/content_type_validators.py
@@ -37,7 +37,7 @@ def validate_content_type_header(
boundary.
BoundaryNotInBody: The boundary is not in the request body.
"""
- main_value, pdict = cgi.parse_header(request_headers['Content-Type'])
+ main_value, pdict = cgi.parse_header(request_headers.get('Content-Type', ''))
if main_value != 'multipart/form-data':
raise UnsupportedMediaType
From f3f6429549f9a65b159a347bc80a10d40c6c8a9b Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 10:32:50 +0100
Subject: [PATCH 0115/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 36940aa15..f6b26397a 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -10,6 +10,7 @@
import pytz
from flask import Flask, Response, make_response, request
from requests import codes
+import requests
from mock_vws._base64_decoding import decode_base64
from mock_vws._constants import ResultCodes, TargetStatuses
@@ -44,6 +45,7 @@
)
CLOUDRECO_FLASK_APP = Flask(__name__)
+CLOUDRECO_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
@CLOUDRECO_FLASK_APP.before_request
@@ -74,6 +76,18 @@ def handle_content_length_header_too_large(
assert isinstance(response, Response)
return response
+@CLOUDRECO_FLASK_APP.errorhandler(requests.exceptions.ConnectionError)
+def handle_connection_error(
+ e: requests.exceptions.ConnectionError,
+) -> Response:
+ raise e
+ # from flask import abort
+ # import pdb; pdb.set_trace()
+ response = make_response(e.response_text, e.status_code)
+ response.headers = {'Connection': 'keep-alive'}
+ assert isinstance(response, Response)
+ return response
+
@CLOUDRECO_FLASK_APP.errorhandler(UnsupportedMediaType)
def handle_unsupported_media_type(
e: UnsupportedMediaType,
@@ -182,6 +196,7 @@ def handle_boundary_not_in_body(
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
+ # raise requests.exceptions.ConnectionError
response.headers['Connection'] = 'keep-alive'
response.headers['Server'] = 'nginx'
content_length = len(response.data)
From ade70aaa0d825e337691de34fc1b6dad249f0268 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 10:37:15 +0100
Subject: [PATCH 0116/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index f6b26397a..c166eb851 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -194,6 +194,25 @@ def handle_boundary_not_in_body(
return response
+@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailure)
+def handle_authentication_failure(
+ e: AuthenticationFailure,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ response.headers['WWW-Authenticate'] = 'VWS'
+ assert isinstance(response, Response)
+ return response
+
+@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailureGoodFormatting)
+def handle_authentication_failure_good_formatting(
+ e: AuthenticationFailureGoodFormatting,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ response.headers['WWW-Authenticate'] = 'VWS'
+ assert isinstance(response, Response)
+ return response
+
+
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
# raise requests.exceptions.ConnectionError
@@ -208,6 +227,7 @@ def set_headers(response: Response) -> Response:
codes.UNPROCESSABLE_ENTITY,
codes.BAD_REQUEST,
codes.FORBIDDEN,
+ codes.UNAUTHORIZED,
) and 'Content-Type' not in response.headers:
response.headers['Content-Type'] = 'application/json'
return response
From ae4a3c63f92114211cc9437ff6e3d0e60bb08bf5 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 10:38:20 +0100
Subject: [PATCH 0117/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index c166eb851..39d295e8b 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -212,6 +212,18 @@ def handle_authentication_failure_good_formatting(
assert isinstance(response, Response)
return response
+@CLOUDRECO_FLASK_APP.errorhandler(QueryOutOfBounds)
+def handle_query_out_of_bounds(
+ e: QueryOutOfBounds,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ content_type = 'text/html; charset=ISO-8859-1'
+ response.headers['Content-Type'] = content_type
+ cache_control = 'must-revalidate,no-cache,no-store'
+ response.headers['Cache-Control'] = cache_control
+ assert isinstance(response, Response)
+ return response
+
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
From e05cebc7b38e0823d06e07551e4e1f79d46091b6 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 10:42:03 +0100
Subject: [PATCH 0118/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 33 ++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 39d295e8b..b84f315d3 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -224,6 +224,39 @@ def handle_query_out_of_bounds(
assert isinstance(response, Response)
return response
+@CLOUDRECO_FLASK_APP.errorhandler(AuthHeaderMissing)
+def handle_auth_header_missing(
+ e: AuthHeaderMissing,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ content_type = 'text/plain; charset=ISO-8859-1'
+ response.headers['Content-Type'] = content_type
+ response.headers['WWW-Authenticate'] = 'VWS'
+ assert isinstance(response, Response)
+ return response
+
+@CLOUDRECO_FLASK_APP.errorhandler(DateFormatNotValid)
+def handle_date_format_not_valid(
+ e: DateFormatNotValid,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ content_type = 'text/plain; charset=ISO-8859-1'
+ response.headers['Content-Type'] = content_type
+ response.headers['WWW-Authenticate'] = 'VWS'
+ assert isinstance(response, Response)
+ return response
+
+@CLOUDRECO_FLASK_APP.errorhandler(MalformedAuthHeader)
+def handle_malformed_auth_header(
+ e: MalformedAuthHeader,
+) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ content_type = 'text/plain; charset=ISO-8859-1'
+ response.headers['Content-Type'] = content_type
+ response.headers['WWW-Authenticate'] = 'VWS'
+ assert isinstance(response, Response)
+ return response
+
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
From 8cbdf2893eaf0aa1ccaed7fcadae5c8707faf272 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 10:46:29 +0100
Subject: [PATCH 0119/3455] Fix a bunch of tests
---
src/mock_vws/_flask_server/vwq/__init__.py | 4 +-
.../vwq/resources/match_processing_response | 78 -------------------
2 files changed, 2 insertions(+), 80 deletions(-)
delete mode 100644 src/mock_vws/_flask_server/vwq/resources/match_processing_response
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index b84f315d3..60f19a181 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -364,8 +364,8 @@ def query() -> Tuple[str, int]:
# processing status, but we choose to:
# * Do the most unexpected thing.
# * Be consistent with every response.
- resources_dir = Path(__file__).parent / 'resources'
- filename = 'match_processing_response'
+ resources_dir = Path(__file__).parent.parent.parent / 'resources'
+ filename = 'match_processing_response.html'
match_processing_resp_file = resources_dir / filename
cache_control = 'must-revalidate,no-cache,no-store'
# TODO remove legacy
diff --git a/src/mock_vws/_flask_server/vwq/resources/match_processing_response b/src/mock_vws/_flask_server/vwq/resources/match_processing_response
deleted file mode 100644
index 33d48f115..000000000
--- a/src/mock_vws/_flask_server/vwq/resources/match_processing_response
+++ /dev/null
@@ -1,78 +0,0 @@
-'\n\n\nError 500 Server Error
-title>\n\nHTTP ERROR 500
\nProblem accessing /v1/query. Reason:\n
Server Error
-p>Caused by:
org.jboss.resteasy.spi.UnhandledException: com.fasterxml.jackson.databind.exc.MismatchedInpu
-tException: No content to map due to end-of-input\n at [Source: (byte[])""; line: 1, column: 0]\n\tat org.jboss.restea
-sy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:76)\n\tat org.jboss.resteasy.core.ExceptionH
-andler.handleException(ExceptionHandler.java:212)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.writeException(S
-ynchronousDispatcher.java:168)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:4
-11)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:202)\n\tat org.jboss.resteas
-y.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:221)\n\tat org.jboss.reste
-asy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)\n\tat org.jboss.resteasy.plugi
-ns.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)\n\tat javax.servlet.http.HttpServlet.se
-rvice(HttpServlet.java:790)\n\tat org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:808)\n\tat org.ecl
-ipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669)\n\tat com.kooaba.queryservice.auth.KW
-SAuthFilter.doFilter(KWSAuthFilter.java:171)\n\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(Servl
-etHandler.java:1652)\n\tat org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)\n\tat org.eclips
-e.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)\n\tat org.eclipse.jetty.security.SecurityHandler.h
-andle(SecurityHandler.java:577)\n\tat org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223
-)\n\tat org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)\n\tat org.eclipse.jetty.ser
-vlet.ServletHandler.doScope(ServletHandler.java:515)\n\tat org.eclipse.jetty.server.session.SessionHandler.doScope(Ses
-sionHandler.java:185)\n\tat org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)\n\tat or
-g.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)\n\tat org.eclipse.jetty.server.handler.Con
-textHandlerCollection.handle(ContextHandlerCollection.java:215)\n\tat org.eclipse.jetty.server.handler.HandlerCollecti
-on.handle(HandlerCollection.java:110)\n\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java
-:97)\n\tat org.eclipse.jetty.server.Server.handle(Server.java:497)\n\tat org.eclipse.jetty.server.HttpChannel.handle(H
-ttpChannel.java:310)\n\tat org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)\n\tat org.eclip
-se.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool
-.runJob(QueuedThreadPool.java:635)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:55
-5)\n\tat java.lang.Thread.run(Thread.java:748)\nCaused by: com.fasterxml.jackson.databind.exc.MismatchedInputException
-: No content to map due to end-of-input\n at [Source: (byte[])""; line: 1, column: 0]\n\tat com.fasterxml.jackson.data
-bind.exc.MismatchedInputException.from(MismatchedInputException.java:59)\n\tat com.fasterxml.jackson.databind.ObjectMa
-pper._initForReading(ObjectMapper.java:4133)\n\tat com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(Object
-Mapper.java:3988)\n\tat com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3094)\n\tat com.kooaba
-.queryservice.domain.WebResult.setTargetData(WebResult.java:44)\n\tat com.kooaba.queryservice.domain.WebQueryResultPro
-cessor.formatResult(WebQueryResultProcessor.java:81)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.query
-Common(QueryResourceVuforia.java:230)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.limitedConcurrencyQu
-ery(QueryResourceVuforia.java:77)\n\tat com.kooaba.queryservice.services.QueryResourceCloudRecoWebAPI.query(QueryResou
-rceCloudRecoWebAPI.java:55)\n\tat sun.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)\n\tat sun.reflect.Deleg
-atingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java
-:606)\n\tat org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:139)\n\tat org.jboss.resteasy.co
-re.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:295)\n\tat org.jboss.resteasy.core.ResourceMethodIn
-voker.invoke(ResourceMethodInvoker.java:249)\n\tat org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethod
-Invoker.java:236)\n\tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:395)\n\t... 28
- more\n
\nCaused by:
com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map
-due to end-of-input\n at [Source: (byte[])""; line: 1, column: 0]\n\tat com.fasterxml.jackson.databind.exc.MismatchedI
-nputException.from(MismatchedInputException.java:59)\n\tat com.fasterxml.jackson.databind.ObjectMapper._initForReading
-(ObjectMapper.java:4133)\n\tat com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3988)\n\
-tat com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3094)\n\tat com.kooaba.queryservice.domain
-.WebResult.setTargetData(WebResult.java:44)\n\tat com.kooaba.queryservice.domain.WebQueryResultProcessor.formatResult(
-WebQueryResultProcessor.java:81)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.queryCommon(QueryResource
-Vuforia.java:230)\n\tat com.kooaba.queryservice.services.QueryResourceVuforia.limitedConcurrencyQuery(QueryResourceVuf
-oria.java:77)\n\tat com.kooaba.queryservice.services.QueryResourceCloudRecoWebAPI.query(QueryResourceCloudRecoWebAPI.j
-ava:55)\n\tat sun.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorI
-mpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:606)\n\tat org.jbos
-s.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:139)\n\tat org.jboss.resteasy.core.ResourceMethodInv
-oker.invokeOnTarget(ResourceMethodInvoker.java:295)\n\tat org.jboss.resteasy.core.ResourceMethodInvoker.invoke(Resourc
-eMethodInvoker.java:249)\n\tat org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:236)\n\
-tat org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:395)\n\tat org.jboss.resteasy.core
-.SynchronousDispatcher.invoke(SynchronousDispatcher.java:202)\n\tat org.jboss.resteasy.plugins.server.servlet.ServletC
-ontainerDispatcher.service(ServletContainerDispatcher.java:221)\n\tat org.jboss.resteasy.plugins.server.servlet.HttpSe
-rvletDispatcher.service(HttpServletDispatcher.java:56)\n\tat org.jboss.resteasy.plugins.server.servlet.HttpServletDisp
-atcher.service(HttpServletDispatcher.java:51)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\tat
- org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:808)\n\tat org.eclipse.jetty.servlet.ServletHandler
-$CachedChain.doFilter(ServletHandler.java:1669)\n\tat com.kooaba.queryservice.auth.KWSAuthFilter.doFilter(KWSAuthFilte
-r.java:171)\n\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)\n\tat org.ec
-lipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)\n\tat org.eclipse.jetty.server.handler.ScopedHand
-ler.handle(ScopedHandler.java:143)\n\tat org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)\n
-\tat org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)\n\tat org.eclipse.jetty.server.
-handler.ContextHandler.doHandle(ContextHandler.java:1127)\n\tat org.eclipse.jetty.servlet.ServletHandler.doScope(Servl
-etHandler.java:515)\n\tat org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)\n\tat org.e
-clipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)\n\tat org.eclipse.jetty.server.handler.Sc
-opedHandler.handle(ScopedHandler.java:141)\n\tat org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(Cont
-extHandlerCollection.java:215)\n\tat org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:
-110)\n\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)\n\tat org.eclipse.jetty.serv
-er.Server.handle(Server.java:497)\n\tat org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)\n\tat org.ec
-lipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)\n\tat org.eclipse.jetty.io.AbstractConnection$2.
-run(AbstractConnection.java:540)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635
-)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)\n\tat java.lang.Thread.run(Thr
-ead.java:748)\n
\n
Powered by Jetty://
\n\n\n\n'
From b5b20f0630848ea2ee9dce3584db9c41baf6a38b Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 31 Mar 2020 10:54:02 +0100
Subject: [PATCH 0120/3455] Remove some commented out code
---
src/mock_vws/_flask_server/vwq/__init__.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 60f19a181..c86b955a4 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -63,7 +63,7 @@ def validate_request() -> None:
class MyResponse(Response):
- default_mimetype = None#'FOOBAR'
+ default_mimetype = None
CLOUDRECO_FLASK_APP.response_class = MyResponse
@@ -81,8 +81,6 @@ def handle_connection_error(
e: requests.exceptions.ConnectionError,
) -> Response:
raise e
- # from flask import abort
- # import pdb; pdb.set_trace()
response = make_response(e.response_text, e.status_code)
response.headers = {'Connection': 'keep-alive'}
assert isinstance(response, Response)
From 23403e11a6420270baabda696518c56232887d5e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Thu, 2 Apr 2020 22:00:43 +0100
Subject: [PATCH 0121/3455] Progress towards new query stuff
---
src/mock_vws/_mock_common.py | 2 -
src/mock_vws/_query_tools.py | 154 +++++++++++++++++-
.../mock_web_query_api.py | 150 +++--------------
3 files changed, 171 insertions(+), 135 deletions(-)
diff --git a/src/mock_vws/_mock_common.py b/src/mock_vws/_mock_common.py
index 5d7377326..a0e3caa34 100644
--- a/src/mock_vws/_mock_common.py
+++ b/src/mock_vws/_mock_common.py
@@ -116,5 +116,3 @@ def parse_multipart( # pylint: disable=invalid-name
}
return cgi.parse_multipart(fp=fp, pdict=pdict)
-
-
diff --git a/src/mock_vws/_query_tools.py b/src/mock_vws/_query_tools.py
index 8593878ef..60c1d0188 100644
--- a/src/mock_vws/_query_tools.py
+++ b/src/mock_vws/_query_tools.py
@@ -2,14 +2,30 @@
Tools for making Vuforia queries.
"""
-from typing import List
+import base64
+import cgi
+import datetime
+import io
+import uuid
+from typing import Any, Dict, List, Set, Union
+
+import pytz
+
+from mock_vws._base64_decoding import decode_base64
+from mock_vws._constants import ResultCodes, TargetStatuses
+from mock_vws._database_matchers import get_database_matching_client_keys
+from mock_vws._mock_common import json_dump, parse_multipart
+from mock_vws.database import VuforiaDatabase
+
class MatchingTargetsWithProcessingStatus(Exception):
pass
+
class ActiveMatchingTargetsDeleteProcessing(Exception):
pass
+
def _images_match(image: io.BytesIO, another_image: io.BytesIO) -> bool:
"""
Given two images, return whether they are matching.
@@ -23,12 +39,138 @@ def _images_match(image: io.BytesIO, another_image: io.BytesIO) -> bool:
return bool(image.getvalue() == another_image.getvalue())
-def _get_query_matches(image: io.BytesIO, database: VuforiaDatabase) -> List[Target]:
+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:
"""
- Given an image and a database, return the matches for a query.
+ Args:
+ TODO
+
+ Raises:
+ MatchingTargetsWithProcessingStatus: TODO
+ ActiveMatchingTargetsDeleteProcessing: TODO
"""
- pass
+ body_file = io.BytesIO(request_body)
+
+ _, pdict = cgi.parse_header(request_headers['Content-Type'])
+ parsed = 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 = pytz.timezone('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)
-def _get_query_match_result_data(
+ 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
diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
index ca651fbb0..d25e6fb25 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
@@ -5,32 +5,25 @@
https://library.vuforia.com/articles/Solution/How-To-Perform-an-Image-Recognition-Query
"""
-import base64
-import cgi
-import datetime
-import io
-import uuid
from pathlib import Path
-from typing import Any, Callable, Dict, List, Set, Tuple, Union
+from typing import Any, Callable, Dict, Set, Tuple, Union
-import pytz
import wrapt
from requests import codes
from requests_mock import POST
from requests_mock.request import _RequestObjectProxy
from requests_mock.response import _Context
-from mock_vws._base64_decoding import decode_base64
-from mock_vws._constants import ResultCodes, TargetStatuses
-from mock_vws._database_matchers import get_database_matching_client_keys
from mock_vws._mock_common import (
Route,
- images_match,
- json_dump,
- parse_multipart,
set_content_length_header,
set_date_header,
)
+from mock_vws._query_tools import (
+ ActiveMatchingTargetsDeleteProcessing,
+ MatchingTargetsWithProcessingStatus,
+ get_query_match_response_text,
+)
from mock_vws._query_validators import run_query_validators
from mock_vws._query_validators.exceptions import (
AuthenticationFailure,
@@ -236,78 +229,22 @@ def query(
"""
Perform an image recognition query.
"""
- body_file = io.BytesIO(request.body)
-
- _, pdict = cgi.parse_header(request.headers['Content-Type'])
- parsed = 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 = pytz.timezone('GMT')
- now = datetime.datetime.now(tz=gmt)
-
- processing_timedelta = datetime.timedelta(
- seconds=self._query_processes_deletion_seconds,
- )
-
- recognition_timedelta = datetime.timedelta(
- seconds=self._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=self.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
- or active_matching_targets_delete_processing
- ):
+ try:
+ response_text = get_query_match_response_text(
+ request_headers=request.headers,
+ request_body=request.body,
+ request_method=request.method,
+ request_path=request.path,
+ databases=self.databases,
+ query_processes_deletion_seconds=self.
+ _query_processes_deletion_seconds,
+ query_recognizes_deletion_seconds=self.
+ _query_recognizes_deletion_seconds,
+ )
+ except (
+ ActiveMatchingTargetsDeleteProcessing,
+ MatchingTargetsWithProcessingStatus,
+ ) as exc:
# We return an example 500 response.
# Each response given by Vuforia is different.
#
@@ -325,45 +262,4 @@ def query(
context.headers['Content-Type'] = content_type
return Path(match_processing_resp_file).read_text()
- 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)
-
- body = {
- 'result_code': ResultCodes.SUCCESS.value,
- 'results': results[:int(max_num_results)],
- 'query_id': uuid.uuid4().hex,
- }
-
- value = json_dump(body)
- return value
+ return response_text
From 695d4bf33a7d0b59d78ce97fa6b4410d86c38104 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Thu, 2 Apr 2020 22:01:26 +0100
Subject: [PATCH 0122/3455] Progress towards new query stuff
---
src/mock_vws/_requests_mock_server/mock_web_query_api.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
index d25e6fb25..ce17d12e1 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
@@ -245,6 +245,10 @@ def query(
ActiveMatchingTargetsDeleteProcessing,
MatchingTargetsWithProcessingStatus,
) as exc:
+ # TODO put this into the exceptions
+ # TODO put all header stuff into the exceptions
+ # TODO header base class
+ #
# We return an example 500 response.
# Each response given by Vuforia is different.
#
From 53f6abd2182abbc6b10f4de21fd5cac68654d223 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 4 Apr 2020 22:52:54 +0100
Subject: [PATCH 0123/3455] Add more docstrings
---
src/mock_vws/_query_tools.py | 26 +++++++++++++++++++++-----
1 file changed, 21 insertions(+), 5 deletions(-)
diff --git a/src/mock_vws/_query_tools.py b/src/mock_vws/_query_tools.py
index 60c1d0188..4803b29d0 100644
--- a/src/mock_vws/_query_tools.py
+++ b/src/mock_vws/_query_tools.py
@@ -19,11 +19,15 @@
class MatchingTargetsWithProcessingStatus(Exception):
- pass
+ """
+ There is at least one matching target which has the status 'processing'.
+ """
class ActiveMatchingTargetsDeleteProcessing(Exception):
- pass
+ """
+ There is at least one active target which matches and was recently deleted.
+ """
def _images_match(image: io.BytesIO, another_image: io.BytesIO) -> bool:
@@ -50,11 +54,23 @@ def get_query_match_response_text(
) -> str:
"""
Args:
- TODO
+ 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.
Raises:
- MatchingTargetsWithProcessingStatus: TODO
- ActiveMatchingTargetsDeleteProcessing: TODO
+ 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)
From 663098ded628f37dcb151e58b385755bdf5954b8 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 4 Apr 2020 23:14:53 +0100
Subject: [PATCH 0124/3455] Add TODOs
---
src/mock_vws/_requests_mock_server/mock_web_services_api.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 0887b3b39..2eed1a0f6 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -510,6 +510,8 @@ def get_duplicates(
assert isinstance(database, VuforiaDatabase)
other_targets = set(database.targets) - set([target])
+ # TODO use the new image match function here
+ # TODO - add a test - is something a duplicate if it isn't exactly the same?
similar_targets: List[str] = [
other.target_id for other in other_targets
if Image.open(other.image) == Image.open(target.image) and
From f66df278819684e4244470724884f5f7eab057a1 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 4 Apr 2020 23:30:22 +0100
Subject: [PATCH 0125/3455] Use new helper for query
---
src/mock_vws/_flask_server/vwq/__init__.py | 137 ++++-----------------
1 file changed, 22 insertions(+), 115 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index c86b955a4..aa59c1e1a 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -11,6 +11,11 @@
from flask import Flask, Response, make_response, request
from requests import codes
import requests
+from mock_vws._query_tools import (
+ ActiveMatchingTargetsDeleteProcessing,
+ MatchingTargetsWithProcessingStatus,
+ get_query_match_response_text,
+)
from mock_vws._base64_decoding import decode_base64
from mock_vws._constants import ResultCodes, TargetStatuses
@@ -53,7 +58,6 @@ def validate_request() -> None:
databases = get_all_databases()
run_query_validators(
request_headers=dict(request.headers),
- # TODO not sure about this one
request_body=request.input_stream.getvalue(),
request_method=request.method,
request_path=request.path,
@@ -278,82 +282,26 @@ def set_headers(response: Response) -> Response:
@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
def query() -> Tuple[str, int]:
- body_file = io.BytesIO(request.input_stream.getvalue())
-
- _, pdict = cgi.parse_header(request.headers['Content-Type'])
- parsed = 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] = parsed['image']
- gmt = pytz.timezone('GMT')
- now = datetime.datetime.now(tz=gmt)
-
- processing_timedelta = datetime.timedelta(
- # TODO add this back
- # seconds=self._query_processes_deletion_seconds,
- seconds=0.2,
- )
-
- recognition_timedelta = datetime.timedelta(
- # TODO add this back
- # seconds=self._query_recognizes_deletion_seconds,
- seconds=0.2,
- )
+ # TODO these should be configurable
+ query_processes_deletion_seconds = 0.2
+ query_recognizes_deletion_seconds = 0.2
databases = get_all_databases()
- database = get_database_matching_client_keys(
- request_headers=dict(request.headers),
- request_body=request.input_stream.getvalue(),
- request_method=request.method,
- request_path=request.path,
- databases=databases,
- )
-
- assert isinstance(database, VuforiaDatabase)
-
- matching_targets = [
- target for target in database.targets
- if target.image.getvalue() == 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
- or active_matching_targets_delete_processing
+ try:
+ response_text = get_query_match_response_text(
+ request_headers=dict(request.headers),
+ request_body=request.input_stream.getvalue(),
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ query_processes_deletion_seconds=query_processes_deletion_seconds,
+ query_recognizes_deletion_seconds=query_recognizes_deletion_seconds,
+ )
+ except (
+ ActiveMatchingTargetsDeleteProcessing,
+ MatchingTargetsWithProcessingStatus,
):
# We return an example 500 response.
# Each response given by Vuforia is different.
@@ -381,45 +329,4 @@ def query() -> Tuple[str, int]:
},
)
- 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)
-
- body = {
- 'result_code': ResultCodes.SUCCESS.value,
- 'results': results[:int(max_num_results)],
- 'query_id': uuid.uuid4().hex,
- }
-
- value = json_dump(body)
- return value, codes.OK
+ return response_text
From e242e51bc028cf21948ffb4728694fb3b7e520b3 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 29 Aug 2020 10:01:39 +0100
Subject: [PATCH 0126/3455] Fix a couple of mypy issues
---
src/mock_vws/_flask_server/vwq/__init__.py | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index aa59c1e1a..9b08b7833 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -1,11 +1,12 @@
import base64
+import copy
import cgi
import datetime
import email.utils
import io
import uuid
from pathlib import Path
-from typing import Any, Dict, List, Tuple
+from typing import Any, Dict, List, Tuple, Union
import pytz
from flask import Flask, Response, make_response, request
@@ -20,7 +21,7 @@
from mock_vws._base64_decoding import decode_base64
from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_client_keys
-from mock_vws._mock_common import json_dump, parse_multipart
+from mock_vws._mock_common import json_dump
from mock_vws.database import VuforiaDatabase
from ..vws._databases import get_all_databases
@@ -55,10 +56,12 @@
@CLOUDRECO_FLASK_APP.before_request
def validate_request() -> None:
+ input_stream_copy = copy.copy(request.input_stream)
+ request_body = input_stream_copy.read()
databases = get_all_databases()
run_query_validators(
request_headers=dict(request.headers),
- request_body=request.input_stream.getvalue(),
+ request_body=request_body,
request_method=request.method,
request_path=request.path,
databases=databases,
@@ -281,7 +284,7 @@ def set_headers(response: Response) -> Response:
@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
-def query() -> Tuple[str, int]:
+def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
# TODO these should be configurable
@@ -329,4 +332,4 @@ def query() -> Tuple[str, int]:
},
)
- return response_text
+ return (response_text, codes.OK)
From e7484698a197b7eabe94858fea65f2a2a3a2a27a Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 29 Aug 2020 10:20:20 +0100
Subject: [PATCH 0127/3455] Fix a couple of mypy issues
---
src/mock_vws/_flask_server/vwq/__init__.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 9b08b7833..65eb4dfcc 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -291,11 +291,13 @@ def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
query_processes_deletion_seconds = 0.2
query_recognizes_deletion_seconds = 0.2
databases = get_all_databases()
+ input_stream_copy = copy.copy(request.input_stream)
+ request_body = input_stream_copy.read()
try:
response_text = get_query_match_response_text(
request_headers=dict(request.headers),
- request_body=request.input_stream.getvalue(),
+ request_body=request_body,
request_method=request.method,
request_path=request.path,
databases=databases,
From 9cae8469c55b9498e0a02d94f7919e1f0ecd2acd Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 30 Aug 2020 19:23:49 +0100
Subject: [PATCH 0128/3455] Fix a few mypy issues
---
src/mock_vws/_flask_server/vwq/__init__.py | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 65eb4dfcc..bff059059 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -12,6 +12,8 @@
from flask import Flask, Response, make_response, request
from requests import codes
import requests
+
+from werkzeug.datastructures import Headers
from mock_vws._query_tools import (
ActiveMatchingTargetsDeleteProcessing,
MatchingTargetsWithProcessingStatus,
@@ -79,7 +81,7 @@ def handle_content_length_header_too_large(
e: ContentLengthHeaderTooLarge,
) -> Response:
response = make_response(e.response_text, e.status_code)
- response.headers = {'Connection': 'keep-alive'}
+ response.headers = Headers({'Connection': 'keep-alive'})
assert isinstance(response, Response)
return response
@@ -88,10 +90,6 @@ def handle_connection_error(
e: requests.exceptions.ConnectionError,
) -> Response:
raise e
- response = make_response(e.response_text, e.status_code)
- response.headers = {'Connection': 'keep-alive'}
- assert isinstance(response, Response)
- return response
@CLOUDRECO_FLASK_APP.errorhandler(UnsupportedMediaType)
def handle_unsupported_media_type(
From 7f15513a0ce049aaf0cd70e1021a821d12984150 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 1 Sep 2020 14:49:40 +0100
Subject: [PATCH 0129/3455] Use new target.delete method
---
src/mock_vws/_flask_server/storage/__init__.py | 5 ++---
src/mock_vws/_flask_server/vws/__init__.py | 3 ---
2 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/src/mock_vws/_flask_server/storage/__init__.py b/src/mock_vws/_flask_server/storage/__init__.py
index 1e1dde770..c3a6b109f 100644
--- a/src/mock_vws/_flask_server/storage/__init__.py
+++ b/src/mock_vws/_flask_server/storage/__init__.py
@@ -4,6 +4,7 @@
import random
from typing import List, Tuple
+# TODO use ZoneInfo
import pytz
from flask import Flask, jsonify, request
from requests import codes
@@ -88,9 +89,7 @@ def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
[target] = [
target for target in database.targets if target.target_id == target_id
]
- gmt = pytz.timezone('GMT')
- now = datetime.datetime.now(tz=gmt)
- target.delete_date = now
+ target.delete()
return jsonify(target.to_dict()), codes.OK
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 3613ca276..e5244c11c 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -247,9 +247,6 @@ def delete_target(target_id: str) -> Tuple[str, int]:
}
return json_dump(body), codes.FORBIDDEN
- # gmt = pytz.timezone('GMT')
- # now = datetime.datetime.now(tz=gmt)
- # target.delete_date = now
delete_url = (
f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/'
f'{target_id}'
From 9606e631d303fc2289390e384d98460be5af415b Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 1 Sep 2020 14:51:48 +0100
Subject: [PATCH 0130/3455] Remove pytz
---
.../_flask_server/storage/__init__.py | 5 +-
src/mock_vws/_flask_server/vwq/__init__.py | 123 +++++++-----------
src/mock_vws/_flask_server/vws/__init__.py | 27 ++--
src/mock_vws/_flask_server/vws/_databases.py | 4 +-
src/mock_vws/database.py | 12 +-
tests/mock_vws/fixtures/vuforia_backends.py | 3 +-
6 files changed, 71 insertions(+), 103 deletions(-)
diff --git a/src/mock_vws/_flask_server/storage/__init__.py b/src/mock_vws/_flask_server/storage/__init__.py
index c3a6b109f..9b71a8392 100644
--- a/src/mock_vws/_flask_server/storage/__init__.py
+++ b/src/mock_vws/_flask_server/storage/__init__.py
@@ -4,8 +4,7 @@
import random
from typing import List, Tuple
-# TODO use ZoneInfo
-import pytz
+from backports.zoneinfo import ZoneInfo
from flask import Flask, jsonify, request
from requests import codes
@@ -129,7 +128,7 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
available_values = list(set(range(6)) - set([target.tracking_rating]))
target.processed_tracking_rating = random.choice(available_values)
- gmt = pytz.timezone('GMT')
+ gmt = ZoneInfo('GMT')
now = datetime.datetime.now(tz=gmt)
target.last_modified_date = now
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index bff059059..134c19c2e 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -1,57 +1,43 @@
-import base64
import copy
-import cgi
-import datetime
import email.utils
-import io
-import uuid
from pathlib import Path
-from typing import Any, Dict, List, Tuple, Union
+from typing import Any, Dict, Tuple, Union
-import pytz
+import requests
from flask import Flask, Response, make_response, request
from requests import codes
-import requests
-
from werkzeug.datastructures import Headers
+
from mock_vws._query_tools import (
ActiveMatchingTargetsDeleteProcessing,
MatchingTargetsWithProcessingStatus,
get_query_match_response_text,
)
-
-from mock_vws._base64_decoding import decode_base64
-from mock_vws._constants import ResultCodes, TargetStatuses
-from mock_vws._database_matchers import get_database_matching_client_keys
-from mock_vws._mock_common import json_dump
-from mock_vws.database import VuforiaDatabase
-
-from ..vws._databases import get_all_databases
from mock_vws._query_validators import run_query_validators
from mock_vws._query_validators.exceptions import (
- DateHeaderNotGiven,
- DateFormatNotValid,
- RequestTimeTooSkewed,
- BadImage,
AuthenticationFailure,
AuthenticationFailureGoodFormatting,
- ImageNotGiven,
AuthHeaderMissing,
- MalformedAuthHeader,
- UnknownParameters,
+ BadImage,
+ BoundaryNotInBody,
+ ContentLengthHeaderTooLarge,
+ DateFormatNotValid,
+ ImageNotGiven,
InactiveProject,
+ InvalidAcceptHeader,
+ InvalidIncludeTargetData,
InvalidMaxNumResults,
+ MalformedAuthHeader,
MaxNumResultsOutOfRange,
- InvalidIncludeTargetData,
- UnsupportedMediaType,
- InvalidAcceptHeader,
- BoundaryNotInBody,
NoBoundaryFound,
QueryOutOfBounds,
- ContentLengthHeaderTooLarge,
- ContentLengthHeaderNotInt
+ RequestTimeTooSkewed,
+ UnknownParameters,
+ UnsupportedMediaType,
)
+from ..vws._databases import get_all_databases
+
CLOUDRECO_FLASK_APP = Flask(__name__)
CLOUDRECO_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
@@ -70,12 +56,13 @@ def validate_request() -> None:
)
-
class MyResponse(Response):
default_mimetype = None
+
CLOUDRECO_FLASK_APP.response_class = MyResponse
+
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
def handle_content_length_header_too_large(
e: ContentLengthHeaderTooLarge,
@@ -85,67 +72,58 @@ def handle_content_length_header_too_large(
assert isinstance(response, Response)
return response
+
@CLOUDRECO_FLASK_APP.errorhandler(requests.exceptions.ConnectionError)
def handle_connection_error(
e: requests.exceptions.ConnectionError,
) -> Response:
raise e
+
@CLOUDRECO_FLASK_APP.errorhandler(UnsupportedMediaType)
-def handle_unsupported_media_type(
- e: UnsupportedMediaType,
-) -> Response:
+def handle_unsupported_media_type(e: UnsupportedMediaType, ) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
+
@CLOUDRECO_FLASK_APP.errorhandler(InvalidAcceptHeader)
-def handle_invalid_accept_header(
- e: InvalidAcceptHeader,
-) -> Response:
+def handle_invalid_accept_header(e: InvalidAcceptHeader, ) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
+
@CLOUDRECO_FLASK_APP.errorhandler(BadImage)
-def handle_bad_image(
- e: BadImage,
-) -> Response:
+def handle_bad_image(e: BadImage, ) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
+
@CLOUDRECO_FLASK_APP.errorhandler(UnknownParameters)
-def handle_unknown_parameters(
- e: UnknownParameters,
-) -> Response:
+def handle_unknown_parameters(e: UnknownParameters, ) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
@CLOUDRECO_FLASK_APP.errorhandler(RequestTimeTooSkewed)
-def handle_request_time_too_skewed(
- e: RequestTimeTooSkewed,
-) -> Response:
+def handle_request_time_too_skewed(e: RequestTimeTooSkewed, ) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
@CLOUDRECO_FLASK_APP.errorhandler(ImageNotGiven)
-def handle_image_not_given(
- e: ImageNotGiven,
-) -> Response:
+def handle_image_not_given(e: ImageNotGiven, ) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
@CLOUDRECO_FLASK_APP.errorhandler(InactiveProject)
-def handle_inactive_project(
- e: InactiveProject,
-) -> Response:
+def handle_inactive_project(e: InactiveProject, ) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
@@ -159,14 +137,14 @@ def handle_invalid_include_target_data(
assert isinstance(response, Response)
return response
+
@CLOUDRECO_FLASK_APP.errorhandler(InvalidMaxNumResults)
-def handle_invalid_max_num_results(
- e: InvalidMaxNumResults,
-) -> Response:
+def handle_invalid_max_num_results(e: InvalidMaxNumResults, ) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
+
@CLOUDRECO_FLASK_APP.errorhandler(MaxNumResultsOutOfRange)
def handle_max_num_results_out_of_range(
e: MaxNumResultsOutOfRange,
@@ -177,19 +155,16 @@ def handle_max_num_results_out_of_range(
@CLOUDRECO_FLASK_APP.errorhandler(NoBoundaryFound)
-def handle_no_boundary_found(
- e: NoBoundaryFound,
-) -> Response:
+def handle_no_boundary_found(e: NoBoundaryFound, ) -> Response:
content_type = 'text/html;charset=UTF-8'
response = make_response(e.response_text, e.status_code)
response.headers['Content-Type'] = content_type
assert isinstance(response, Response)
return response
+
@CLOUDRECO_FLASK_APP.errorhandler(BoundaryNotInBody)
-def handle_boundary_not_in_body(
- e: BoundaryNotInBody,
-) -> Response:
+def handle_boundary_not_in_body(e: BoundaryNotInBody, ) -> Response:
content_type = 'text/html;charset=UTF-8'
response = make_response(e.response_text, e.status_code)
response.headers['Content-Type'] = content_type
@@ -198,14 +173,13 @@ def handle_boundary_not_in_body(
@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailure)
-def handle_authentication_failure(
- e: AuthenticationFailure,
-) -> Response:
+def handle_authentication_failure(e: AuthenticationFailure, ) -> Response:
response = make_response(e.response_text, e.status_code)
response.headers['WWW-Authenticate'] = 'VWS'
assert isinstance(response, Response)
return response
+
@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailureGoodFormatting)
def handle_authentication_failure_good_formatting(
e: AuthenticationFailureGoodFormatting,
@@ -215,10 +189,9 @@ def handle_authentication_failure_good_formatting(
assert isinstance(response, Response)
return response
+
@CLOUDRECO_FLASK_APP.errorhandler(QueryOutOfBounds)
-def handle_query_out_of_bounds(
- e: QueryOutOfBounds,
-) -> Response:
+def handle_query_out_of_bounds(e: QueryOutOfBounds, ) -> Response:
response = make_response(e.response_text, e.status_code)
content_type = 'text/html; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
@@ -227,10 +200,9 @@ def handle_query_out_of_bounds(
assert isinstance(response, Response)
return response
+
@CLOUDRECO_FLASK_APP.errorhandler(AuthHeaderMissing)
-def handle_auth_header_missing(
- e: AuthHeaderMissing,
-) -> Response:
+def handle_auth_header_missing(e: AuthHeaderMissing, ) -> Response:
response = make_response(e.response_text, e.status_code)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
@@ -238,10 +210,9 @@ def handle_auth_header_missing(
assert isinstance(response, Response)
return response
+
@CLOUDRECO_FLASK_APP.errorhandler(DateFormatNotValid)
-def handle_date_format_not_valid(
- e: DateFormatNotValid,
-) -> Response:
+def handle_date_format_not_valid(e: DateFormatNotValid, ) -> Response:
response = make_response(e.response_text, e.status_code)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
@@ -249,10 +220,9 @@ def handle_date_format_not_valid(
assert isinstance(response, Response)
return response
+
@CLOUDRECO_FLASK_APP.errorhandler(MalformedAuthHeader)
-def handle_malformed_auth_header(
- e: MalformedAuthHeader,
-) -> Response:
+def handle_malformed_auth_header(e: MalformedAuthHeader, ) -> Response:
response = make_response(e.response_text, e.status_code)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
@@ -284,7 +254,6 @@ def set_headers(response: Response) -> Response:
@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
-
# TODO these should be configurable
query_processes_deletion_seconds = 0.2
query_recognizes_deletion_seconds = 0.2
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index e5244c11c..cf0608ca3 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -6,24 +6,17 @@
from typing import Dict, List, Tuple, Union
import requests
-from flask import Flask, Response, request, make_response
+from flask import Flask, Response, make_response, request
from PIL import Image
from requests import codes
from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_server_keys
from mock_vws._mock_common import json_dump
-from mock_vws.database import VuforiaDatabase
-from mock_vws.target import Target
-
-from ._constants import STORAGE_BASE_URL
-from ._databases import get_all_databases
from mock_vws._services_validators import run_services_validators
from mock_vws._services_validators.exceptions import (
AuthenticationFailure,
BadImage,
- ContentLengthHeaderNotInt,
- ContentLengthHeaderTooLarge,
Fail,
ImageTooLarge,
MetadataTooLarge,
@@ -32,12 +25,16 @@
RequestTimeTooSkewed,
TargetNameExist,
UnknownTarget,
- UnnecessaryRequestBody,
)
-from pathlib import Path
+from mock_vws.database import VuforiaDatabase
+from mock_vws.target import Target
+
+from ._constants import STORAGE_BASE_URL
+from ._databases import get_all_databases
VWS_FLASK_APP = Flask(__name__)
+
@VWS_FLASK_APP.before_request
def validate_request() -> None:
databases = get_all_databases()
@@ -59,38 +56,47 @@ def validate_request() -> None:
def handle_unknown_target(e: UnknownTarget) -> Tuple[str, int]:
return e.response_text, e.status_code
+
@VWS_FLASK_APP.errorhandler(ProjectInactive)
def handle_project_inactive(e: ProjectInactive) -> Tuple[str, int]:
return e.response_text, e.status_code
+
@VWS_FLASK_APP.errorhandler(AuthenticationFailure)
def handle_authentication_failure(e: AuthenticationFailure) -> Tuple[str, int]:
return e.response_text, e.status_code
+
@VWS_FLASK_APP.errorhandler(Fail)
def handle_fail(e: Fail) -> Tuple[str, int]:
return e.response_text, e.status_code
+
@VWS_FLASK_APP.errorhandler(MetadataTooLarge)
def handle_metadata_too_large(e: MetadataTooLarge) -> Tuple[str, int]:
return e.response_text, e.status_code
+
@VWS_FLASK_APP.errorhandler(TargetNameExist)
def handle_target_name_exist(e: TargetNameExist) -> Tuple[str, int]:
return e.response_text, e.status_code
+
@VWS_FLASK_APP.errorhandler(BadImage)
def handle_bad_image(e: BadImage) -> Tuple[str, int]:
return e.response_text, e.status_code
+
@VWS_FLASK_APP.errorhandler(ImageTooLarge)
def handle_image_too_large(e: ImageTooLarge) -> Tuple[str, int]:
return e.response_text, e.status_code
+
@VWS_FLASK_APP.errorhandler(RequestTimeTooSkewed)
def handle_request_time_too_skewed(e: RequestTimeTooSkewed) -> Tuple[str, int]:
return e.response_text, e.status_code
+
@VWS_FLASK_APP.errorhandler(OopsErrorOccurredResponse)
def handle_oops_error_occurred(e: OopsErrorOccurredResponse) -> Response:
content_type = 'text/html; charset=UTF-8'
@@ -99,6 +105,7 @@ def handle_oops_error_occurred(e: OopsErrorOccurredResponse) -> Response:
assert isinstance(response, Response)
return response
+
@VWS_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
response.headers['Connection'] = 'keep-alive'
diff --git a/src/mock_vws/_flask_server/vws/_databases.py b/src/mock_vws/_flask_server/vws/_databases.py
index f4b384d88..7979d95dd 100644
--- a/src/mock_vws/_flask_server/vws/_databases.py
+++ b/src/mock_vws/_flask_server/vws/_databases.py
@@ -3,8 +3,8 @@
import io
from typing import Set
-import pytz
import requests
+from backports.zoneinfo import ZoneInfo
from mock_vws.database import VuforiaDatabase
from mock_vws.states import States
@@ -56,7 +56,7 @@ def get_all_databases() -> Set[VuforiaDatabase]:
application_metadata=application_metadata,
)
target.target_id = target_dict['target_id']
- gmt = pytz.timezone('GMT')
+ gmt = ZoneInfo('GMT')
target.last_modified_date = datetime.datetime.fromisoformat(
target_dict['last_modified_date'],
)
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 098670358..7f8267397 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -3,9 +3,8 @@
"""
import uuid
-from typing import Dict, List, Optional, Set, Union
from dataclasses import dataclass, field
-from typing import Set
+from typing import Dict, List, Optional, Set, Union
from .states import States
from .target import Target
@@ -37,13 +36,8 @@ class VuforiaDatabase:
# TODO use built in dataclass to dict feature?
def to_dict(
self,
- ) -> Dict[
- str,
- Union[
- str,
- List[Dict[str, Optional[Union[str, int, bool, float]]]],
- ],
- ]:
+ ) -> Dict[str, Union[str, List[Dict[str, Optional[Union[str, int, bool,
+ float]]]], ], ]:
targets = [target.to_dict() for target in self.targets]
return {
'database_name': self.database_name,
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index b247a61cb..2ef98f657 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -15,11 +15,10 @@
from vws import VWS
from vws.exceptions import TargetStatusNotSuccess
-
+from mock_vws import MockVWS
from mock_vws._flask_server.storage import STORAGE_FLASK_APP
from mock_vws._flask_server.vwq import CLOUDRECO_FLASK_APP
from mock_vws._flask_server.vws import STORAGE_BASE_URL, VWS_FLASK_APP
-from mock_vws import MockVWS
from mock_vws.database import VuforiaDatabase
from mock_vws.states import States
From 95babd8eb1d712a99334ddcfa3e9770f74ac0ef4 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 1 Sep 2020 15:51:36 +0100
Subject: [PATCH 0131/3455] Remove notes about dataclasses
---
src/mock_vws/target.py | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 182415c06..06d2904ae 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -180,17 +180,10 @@ def tracking_rating(self) -> int:
return 0
def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
- # TODO e.g. processed tracking rating can surely change if
- # target is dumped then recreated.
- #
- # as can e.g. processing time... maybe use dataclass but then
- # https://github.com/agronholm/sphinx-autodoc-typehints/issues/123
+ delete_date: Optional[str] = None
if self.delete_date:
- delete_date: Optional[str] = datetime.datetime.isoformat(
- self.delete_date,
- )
- else:
- delete_date = None
+ delete_date = datetime.datetime.isoformat(self.delete_date)
+
return {
'name': self.name,
'width': self.width,
From b91d04d2d3b96aa323b3e645c04202a5d47c94f9 Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
<27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Mon, 7 Sep 2020 06:46:34 +0000
Subject: [PATCH 0132/3455] Bump sphinxcontrib-spelling from 5.3.0 to 5.4.0
Bumps [sphinxcontrib-spelling](https://github.com/sphinx-contrib/spelling) from 5.3.0 to 5.4.0.
- [Release notes](https://github.com/sphinx-contrib/spelling/releases)
- [Commits](https://github.com/sphinx-contrib/spelling/compare/5.3.0...5.4.0)
Signed-off-by: dependabot-preview[bot]
---
dev-requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index a52b24536..2e7205a6a 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -27,7 +27,7 @@ pytest-envfiles==0.1.0 # Use files for environment variables for tests
pytest==6.0.1 # Test runners
sphinx-autodoc-typehints==1.11.0
sphinx_paramlinks==0.4.2
-sphinxcontrib-spelling==5.3.0
+sphinxcontrib-spelling==5.4.0
timeout-decorator==0.4.1 # Decorate functions to time out.
twine==3.2.0
vulture==2.1
From 0b1d4f29498f3bdc7494a38186bc4b69f3209f5f Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
<27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Mon, 7 Sep 2020 06:46:56 +0000
Subject: [PATCH 0133/3455] Bump freezegun from 0.3.15 to 1.0.0
Bumps [freezegun](https://github.com/spulec/freezegun) from 0.3.15 to 1.0.0.
- [Release notes](https://github.com/spulec/freezegun/releases)
- [Changelog](https://github.com/spulec/freezegun/blob/master/CHANGELOG)
- [Commits](https://github.com/spulec/freezegun/compare/0.3.15...1.0.0)
Signed-off-by: dependabot-preview[bot]
---
dev-requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index a52b24536..51fa08f60 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -12,7 +12,7 @@ dodgy==0.2.1 # Look for uploaded secrets
flake8-commas==2.0.0 # Require silicon valley commas
flake8-quotes==3.2.0 # Require single quotes
flake8==3.8.3 # Lint
-freezegun==0.3.15 # Freeze time in tests
+freezegun==1.0.0 # Freeze time in tests
isort==5.5.0 # Lint imports
keyring==21.4.0
mypy==0.782 # Type checking
From 6afa4f12e8c7e3edcd1b8a4cb8de6a27755f16ab Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
<27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Mon, 7 Sep 2020 06:47:18 +0000
Subject: [PATCH 0134/3455] Bump attrs from 20.1.0 to 20.2.0
Bumps [attrs](https://github.com/python-attrs/attrs) from 20.1.0 to 20.2.0.
- [Release notes](https://github.com/python-attrs/attrs/releases)
- [Changelog](https://github.com/python-attrs/attrs/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/python-attrs/attrs/compare/20.1.0...20.2.0)
Signed-off-by: dependabot-preview[bot]
---
dev-requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index a52b24536..c0bae535b 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -3,7 +3,7 @@ Sphinx-Substitution-Extensions==2020.7.4.1
Sphinx==3.2.1
VWS-Auth-Tools==2020.5.31.0
VWS-Test-Fixtures==2020.8.2.0
-attrs==20.1.0 # Modern attrs is required for pytest
+attrs==20.2.0 # Modern attrs is required for pytest
autoflake==1.4
black==20.8b1
check-manifest==0.42
From 046924dfd61b56ba0994f0ac27a91153425822c0 Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
<27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Mon, 7 Sep 2020 18:57:44 +0000
Subject: [PATCH 0135/3455] Bump isort from 5.5.0 to 5.5.1
Bumps [isort](https://github.com/pycqa/isort) from 5.5.0 to 5.5.1.
- [Release notes](https://github.com/pycqa/isort/releases)
- [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/pycqa/isort/compare/5.5.0...5.5.1)
Signed-off-by: dependabot-preview[bot]
---
dev-requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 43fdc939d..321ec69d1 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -13,7 +13,7 @@ flake8-commas==2.0.0 # Require silicon valley commas
flake8-quotes==3.2.0 # Require single quotes
flake8==3.8.3 # Lint
freezegun==1.0.0 # Freeze time in tests
-isort==5.5.0 # Lint imports
+isort==5.5.1 # Lint imports
keyring==21.4.0
mypy==0.782 # Type checking
pip_check_reqs==2.1.1
From e0b86148e64abcd47f5c4483cea10bd73436ec8a Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 8 Sep 2020 12:37:37 +0100
Subject: [PATCH 0136/3455] Use new locations for exceptions in VWS-Python
---
dev-requirements.txt | 2 +-
tests/mock_vws/fixtures/vuforia_backends.py | 2 +-
tests/mock_vws/test_authorization_header.py | 2 +-
tests/mock_vws/test_database_summary.py | 2 +-
tests/mock_vws/test_delete_target.py | 2 +-
tests/mock_vws/test_get_duplicates.py | 2 +-
tests/mock_vws/test_get_target.py | 2 +-
tests/mock_vws/test_target_summary.py | 2 +-
tests/mock_vws/test_update_target.py | 2 +-
tests/mock_vws/test_usage.py | 2 +-
10 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index baab41478..613deb3ba 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -31,4 +31,4 @@ sphinxcontrib-spelling==5.4.0
timeout-decorator==0.4.1 # Decorate functions to time out.
twine==3.2.0
vulture==2.1
-vws-python==2020.8.21.0
+vws-python==2020.9.8.0
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index f469a505b..caf67885f 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -10,7 +10,7 @@
import pytest
from _pytest.fixtures import SubRequest
from vws import VWS
-from vws.exceptions import TargetStatusNotSuccess
+from vws.exceptions.vws_exceptions import TargetStatusNotSuccess
from mock_vws import MockVWS
from mock_vws.database import VuforiaDatabase
diff --git a/tests/mock_vws/test_authorization_header.py b/tests/mock_vws/test_authorization_header.py
index 2678ab08e..44f5ab80f 100644
--- a/tests/mock_vws/test_authorization_header.py
+++ b/tests/mock_vws/test_authorization_header.py
@@ -12,7 +12,7 @@
import requests
from requests.structures import CaseInsensitiveDict
from vws import VWS, CloudRecoService
-from vws.exceptions import AuthenticationFailure, Fail
+from vws.exceptions.vws_exceptions import AuthenticationFailure, Fail
from vws_auth_tools import rfc_1123_date
from mock_vws._constants import ResultCodes
diff --git a/tests/mock_vws/test_database_summary.py b/tests/mock_vws/test_database_summary.py
index d8cb67350..29c00f8e2 100644
--- a/tests/mock_vws/test_database_summary.py
+++ b/tests/mock_vws/test_database_summary.py
@@ -11,7 +11,7 @@
import pytest
import timeout_decorator
from vws import VWS, CloudRecoService
-from vws.exceptions import Fail
+from vws.exceptions.vws_exceptions import Fail
from mock_vws import MockVWS
from mock_vws.database import VuforiaDatabase
diff --git a/tests/mock_vws/test_delete_target.py b/tests/mock_vws/test_delete_target.py
index dc55c9a17..e0fc5f789 100644
--- a/tests/mock_vws/test_delete_target.py
+++ b/tests/mock_vws/test_delete_target.py
@@ -6,7 +6,7 @@
import pytest
from vws import VWS
-from vws.exceptions import (
+from vws.exceptions.vws_exceptions import (
ProjectInactive,
TargetStatusProcessing,
UnknownTarget,
diff --git a/tests/mock_vws/test_get_duplicates.py b/tests/mock_vws/test_get_duplicates.py
index 481bce14e..a068a3f6b 100644
--- a/tests/mock_vws/test_get_duplicates.py
+++ b/tests/mock_vws/test_get_duplicates.py
@@ -7,7 +7,7 @@
import pytest
from vws import VWS
-from vws.exceptions import ProjectInactive
+from vws.exceptions.vws_exceptions import ProjectInactive
from vws.reports import TargetStatuses
diff --git a/tests/mock_vws/test_get_target.py b/tests/mock_vws/test_get_target.py
index 799bab762..5c6c675f7 100644
--- a/tests/mock_vws/test_get_target.py
+++ b/tests/mock_vws/test_get_target.py
@@ -9,7 +9,7 @@
import pytest
from vws import VWS
-from vws.exceptions import UnknownTarget
+from vws.exceptions.vws_exceptions import UnknownTarget
from vws.reports import TargetRecord, TargetStatuses
diff --git a/tests/mock_vws/test_target_summary.py b/tests/mock_vws/test_target_summary.py
index d8bf09b24..83579b04a 100644
--- a/tests/mock_vws/test_target_summary.py
+++ b/tests/mock_vws/test_target_summary.py
@@ -10,7 +10,7 @@
from _pytest.fixtures import SubRequest
from backports.zoneinfo import ZoneInfo
from vws import VWS, CloudRecoService
-from vws.exceptions import UnknownTarget
+from vws.exceptions.vws_exceptions import UnknownTarget
from vws.reports import TargetStatuses
from mock_vws.database import VuforiaDatabase
diff --git a/tests/mock_vws/test_update_target.py b/tests/mock_vws/test_update_target.py
index 29e229a84..f72e9dbf3 100644
--- a/tests/mock_vws/test_update_target.py
+++ b/tests/mock_vws/test_update_target.py
@@ -15,7 +15,7 @@
from requests import Response
from requests_mock import PUT
from vws import VWS
-from vws.exceptions import BadImage, ProjectInactive
+from vws.exceptions.vws_exceptions import BadImage, ProjectInactive
from vws.reports import TargetStatuses
from vws_auth_tools import authorization_header, rfc_1123_date
diff --git a/tests/mock_vws/test_usage.py b/tests/mock_vws/test_usage.py
index e9ffcfce1..00cc631fb 100644
--- a/tests/mock_vws/test_usage.py
+++ b/tests/mock_vws/test_usage.py
@@ -13,7 +13,7 @@
from requests.exceptions import MissingSchema
from requests_mock.exceptions import NoMockAddress
from vws import VWS, CloudRecoService
-from vws.exceptions import MatchProcessing
+from vws.exceptions.cloud_reco_exceptions import MatchProcessing
from vws.reports import TargetStatuses
from vws_auth_tools import rfc_1123_date
From 3878553f1db1d9a44da7710ebb44a2122c75028f Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 8 Sep 2020 17:56:36 +0100
Subject: [PATCH 0137/3455] Change expected exception in pytest raises
---
tests/mock_vws/test_authorization_header.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/tests/mock_vws/test_authorization_header.py b/tests/mock_vws/test_authorization_header.py
index 44f5ab80f..6d247c545 100644
--- a/tests/mock_vws/test_authorization_header.py
+++ b/tests/mock_vws/test_authorization_header.py
@@ -12,6 +12,7 @@
import requests
from requests.structures import CaseInsensitiveDict
from vws import VWS, CloudRecoService
+from vws.exceptions import cloud_reco_exceptions
from vws.exceptions.vws_exceptions import AuthenticationFailure, Fail
from vws_auth_tools import rfc_1123_date
@@ -210,7 +211,7 @@ def test_bad_access_key_query(
client_secret_key=vuforia_database.client_secret_key,
)
- with pytest.raises(AuthenticationFailure) as exc:
+ with pytest.raises(cloud_reco_exceptions.AuthenticationFailure) as exc:
cloud_reco_client.query(image=high_quality_image)
response = exc.value.response
@@ -266,7 +267,7 @@ def test_bad_secret_key_query(
client_secret_key='example',
)
- with pytest.raises(AuthenticationFailure) as exc:
+ with pytest.raises(cloud_reco_exceptions.AuthenticationFailure) as exc:
cloud_reco_client.query(image=high_quality_image)
response = exc.value.response
From b680fd7f7e7919c01f4fde801d82a27dac1363b4 Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
<27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Thu, 10 Sep 2020 06:35:09 +0000
Subject: [PATCH 0138/3455] Bump isort from 5.5.1 to 5.5.2
Bumps [isort](https://github.com/pycqa/isort) from 5.5.1 to 5.5.2.
- [Release notes](https://github.com/pycqa/isort/releases)
- [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/pycqa/isort/compare/5.5.1...5.5.2)
Signed-off-by: dependabot-preview[bot]
---
dev-requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 613deb3ba..9ff71106c 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -13,7 +13,7 @@ flake8-commas==2.0.0 # Require silicon valley commas
flake8-quotes==3.2.0 # Require single quotes
flake8==3.8.3 # Lint
freezegun==1.0.0 # Freeze time in tests
-isort==5.5.1 # Lint imports
+isort==5.5.2 # Lint imports
keyring==21.4.0
mypy==0.782 # Type checking
pip_check_reqs==2.1.1
From 16ec051fad47d43b1fe8dba697447b694013feee Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 07:59:18 +0100
Subject: [PATCH 0139/3455] Start of doing database summary work (shared
between front-ends)
---
.../mock_web_services_api.py | 47 ++-------------
src/mock_vws/database.py | 58 ++++++++++++++++++-
2 files changed, 61 insertions(+), 44 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 231085cda..c0c402941 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -359,58 +359,19 @@ def database_summary(
)
assert isinstance(database, VuforiaDatabase)
- # TODO make a helper to get a database summary report from a
- # VuforiaDatabase
- active_images = len(
- [
- target
- for target in database.targets
- if target.status == TargetStatuses.SUCCESS.value
- and target.active_flag
- and not target.delete_date
- ],
- )
-
- failed_images = len(
- [
- target
- for target in database.targets
- if target.status == TargetStatuses.FAILED.value
- and not target.delete_date
- ],
- )
-
- inactive_images = len(
- [
- target
- for target in database.targets
- if target.status == TargetStatuses.SUCCESS.value
- and not target.active_flag
- and not target.delete_date
- ],
- )
-
- processing_images = len(
- [
- target
- for target in database.targets
- if target.status == TargetStatuses.PROCESSING.value
- and not target.delete_date
- ],
- )
body = {
'result_code': ResultCodes.SUCCESS.value,
'transaction_id': uuid.uuid4().hex,
'name': database.database_name,
- 'active_images': active_images,
- 'inactive_images': inactive_images,
- 'failed_images': failed_images,
+ 'active_images': len(database.active_targets),
+ 'inactive_images': len(database.inactive_targets),
+ 'failed_images': len(database.failed_targets),
'target_quota': 1000,
'total_recos': 0,
'current_month_recos': 0,
'previous_month_recos': 0,
- 'processing_images': processing_images,
+ 'processing_images': len(database.processing_targets),
'reco_threshold': 1000,
'request_quota': 100000,
# We have ``self.request_count`` but Vuforia always shows 0.
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index a22939d26..9bbdf6279 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -5,6 +5,7 @@
import uuid
from dataclasses import dataclass, field
from typing import Set
+from mock_vws._constants import ResultCodes, TargetStatuses
from .states import States
from .target import Target
@@ -20,7 +21,7 @@ def _random_hex() -> str:
@dataclass(eq=True, frozen=True)
class VuforiaDatabase:
"""
- Credentials for VWS APIs.
+ A representation of a Vuforia target database.
"""
# We hide a few things in the ``repr`` with ``repr=False`` so that they do
@@ -32,3 +33,58 @@ class VuforiaDatabase:
client_secret_key: str = field(default_factory=_random_hex, repr=False)
targets: Set[Target] = field(default_factory=set, hash=False)
state: States = States.WORKING
+
+ @property
+ def active_targets(self) -> Set[Target]:
+ """
+ """
+
+ return set(
+ [
+ target
+ for target in self.targets
+ if target.status == TargetStatuses.SUCCESS.value
+ and target.active_flag
+ and not target.delete_date
+ ],
+ )
+
+ @property
+ def inactive_targets(self) -> Set[Target]:
+ """
+ """
+
+ return set(
+ [
+ target
+ for target in self.targets
+ if target.status == TargetStatuses.SUCCESS.value
+ and not target.active_flag
+ and not target.delete_date
+ ],
+ )
+
+ @property
+ def failed_targets(self) -> Set[Target]:
+
+ return set(
+ [
+ target
+ for target in self.targets
+ if target.status == TargetStatuses.FAILED.value
+ and not target.delete_date
+ ],
+ )
+
+
+ @property
+ def processing_targets(self) -> Set[Target]:
+ return set(
+ [
+ target
+ for target in self.targets
+ if target.status == TargetStatuses.PROCESSING.value
+ and not target.delete_date
+ ],
+ )
+
From 71a64b7eb419969c88d13367afb426ec47415a7b Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 08:13:10 +0100
Subject: [PATCH 0140/3455] Add a few things to the VuforiaDatabase model
---
.../_requests_mock_server/mock_web_services_api.py | 12 ++++++------
src/mock_vws/database.py | 6 ++++++
2 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index c0c402941..47dc95a39 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -367,13 +367,13 @@ def database_summary(
'active_images': len(database.active_targets),
'inactive_images': len(database.inactive_targets),
'failed_images': len(database.failed_targets),
- 'target_quota': 1000,
- 'total_recos': 0,
- 'current_month_recos': 0,
- 'previous_month_recos': 0,
+ 'target_quota': database.target_quota,
+ 'total_recos': database.total_recos,
+ 'current_month_recos': database.current_month_recos,
+ 'previous_month_recos': database.previous_month_recos,
'processing_images': len(database.processing_targets),
- 'reco_threshold': 1000,
- 'request_quota': 100000,
+ 'reco_threshold': database.reco_threshold,
+ 'request_quota': database.request_quota,
# We have ``self.request_count`` but Vuforia always shows 0.
# This was not always the case.
'request_usage': 0,
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 9bbdf6279..1ec1bae4f 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -33,6 +33,12 @@ class VuforiaDatabase:
client_secret_key: str = field(default_factory=_random_hex, repr=False)
targets: Set[Target] = field(default_factory=set, hash=False)
state: States = States.WORKING
+ request_quota = 100000
+ reco_threshold = 1000
+ current_month_recos = 0
+ previous_month_recos = 0
+ total_recos = 0
+ target_quota = 1000
@property
def active_targets(self) -> Set[Target]:
From ae2b1902debd14d251bfd5adb312e9924fc33238 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 08:23:55 +0100
Subject: [PATCH 0141/3455] Move some details to the target db
---
.../_requests_mock_server/mock_web_services_api.py | 7 +++----
src/mock_vws/target.py | 3 +++
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 47dc95a39..57f09b8ca 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -625,7 +625,6 @@ def target_summary(
)
assert isinstance(database, VuforiaDatabase)
- # TODO have this be a helper
body = {
'status': target.status,
'transaction_id': uuid.uuid4().hex,
@@ -635,8 +634,8 @@ def target_summary(
'upload_date': target.upload_date.strftime('%Y-%m-%d'),
'active_flag': target.active_flag,
'tracking_rating': target.tracking_rating,
- 'total_recos': 0,
- 'current_month_recos': 0,
- 'previous_month_recos': 0,
+ 'total_recos': target.total_recos,
+ 'current_month_recos': target.current_month_recos,
+ 'previous_month_recos': target.previous_month_recos,
}
return json_dump(body)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 24feec48e..3fba7bc32 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -87,6 +87,9 @@ def __init__( # pylint: disable=too-many-arguments
self._processing_time_seconds = processing_time_seconds
self.application_metadata = application_metadata
self.delete_date: Optional[datetime.datetime] = None
+ self.total_recos: int = 0
+ self.current_month_recos : int = 0
+ self.previous_month_recos : int = 0
def __repr__(self) -> str:
"""
From b96708ed06263527deff291eadfca98a2b2f7986 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 08:43:46 +0100
Subject: [PATCH 0142/3455] Move a bunch of logic about target databases into
the VuforiaDatabase class, away from the requests-mock specific code
---
.../mock_web_services_api.py | 70 ++++---------------
src/mock_vws/database.py | 65 ++++++++++++++++-
2 files changed, 75 insertions(+), 60 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 08674d514..0c9e070e6 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -262,10 +262,7 @@ def add_target(
assert isinstance(database, VuforiaDatabase)
- targets = (
- target for target in database.targets if not target.delete_date
- )
- if any(target.name == name for target in targets):
+ if any(target.name == name for target in database.not_deleted_targets):
context.status_code = HTTPStatus.FORBIDDEN
body = {
'transaction_id': uuid.uuid4().hex,
@@ -359,58 +356,20 @@ def database_summary(
)
assert isinstance(database, VuforiaDatabase)
- active_images = len(
- [
- target
- for target in database.targets
- if target.status == TargetStatuses.SUCCESS.value
- and target.active_flag
- and not target.delete_date
- ],
- )
-
- failed_images = len(
- [
- target
- for target in database.targets
- if target.status == TargetStatuses.FAILED.value
- and not target.delete_date
- ],
- )
-
- inactive_images = len(
- [
- target
- for target in database.targets
- if target.status == TargetStatuses.SUCCESS.value
- and not target.active_flag
- and not target.delete_date
- ],
- )
-
- processing_images = len(
- [
- target
- for target in database.targets
- if target.status == TargetStatuses.PROCESSING.value
- and not target.delete_date
- ],
- )
-
body = {
'result_code': ResultCodes.SUCCESS.value,
'transaction_id': uuid.uuid4().hex,
'name': database.database_name,
- 'active_images': active_images,
- 'inactive_images': inactive_images,
- 'failed_images': failed_images,
- 'target_quota': 1000,
- 'total_recos': 0,
- 'current_month_recos': 0,
- 'previous_month_recos': 0,
- 'processing_images': processing_images,
- 'reco_threshold': 1000,
- 'request_quota': 100000,
+ 'active_images': len(database.active_targets),
+ 'inactive_images': len(database.inactive_targets),
+ 'failed_images': len(database.failed_targets),
+ 'target_quota': database.target_quota,
+ 'total_recos': database.total_recos,
+ 'current_month_recos': database.current_month_recos,
+ 'previous_month_recos': database.previous_month_recos,
+ 'processing_images': len(database.processing_targets),
+ 'reco_threshold': database.reco_threshold,
+ 'request_quota': database.request_quota,
# We have ``self.request_count`` but Vuforia always shows 0.
# This was not always the case.
'request_usage': 0,
@@ -438,16 +397,11 @@ def target_list(
)
assert isinstance(database, VuforiaDatabase)
- results = [
- target.target_id
- for target in database.targets
- if not target.delete_date
- ]
body: Dict[str, Union[str, List[str]]] = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.SUCCESS.value,
- 'results': results,
+ 'results': database.not_deleted_targets,
}
return json_dump(body)
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index a22939d26..2ada5fa6e 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -6,8 +6,9 @@
from dataclasses import dataclass, field
from typing import Set
-from .states import States
-from .target import Target
+from mock_vws._constants import TargetStatuses
+from mock_vws.states import States
+from mock_vws.target import Target
def _random_hex() -> str:
@@ -32,3 +33,63 @@ class VuforiaDatabase:
client_secret_key: str = field(default_factory=_random_hex, repr=False)
targets: Set[Target] = field(default_factory=set, hash=False)
state: States = States.WORKING
+
+ request_quota = 100000
+ reco_threshold = 1000
+ current_month_recos = 0
+ previous_month_recos = 0
+ total_recos = 0
+ target_quota = 1000
+
+ @property
+ def not_deleted_targets(self) -> Set[Target]:
+ """
+ All targets which have not been deleted.
+ """
+ return set(target for target in self.targets if not target.delete_date)
+
+ @property
+ def active_targets(self) -> Set[Target]:
+ """
+ All active targets.
+ """
+ return set(
+ target
+ for target in self.not_deleted_targets
+ if target.status == TargetStatuses.SUCCESS.value
+ and target.active_flag
+ )
+
+ @property
+ def inactive_targets(self) -> Set[Target]:
+ """
+ All inactive targets.
+ """
+ return set(
+ target
+ for target in self.not_deleted_targets
+ if target.status == TargetStatuses.SUCCESS.value
+ and not target.active_flag
+ )
+
+ @property
+ def failed_targets(self) -> Set[Target]:
+ """
+ All failed targets.
+ """
+ return set(
+ target
+ for target in self.not_deleted_targets
+ if target.status == TargetStatuses.FAILED.value
+ )
+
+ @property
+ def processing_targets(self) -> Set[Target]:
+ """
+ All processing targets.
+ """
+ return set(
+ target
+ for target in self.not_deleted_targets
+ if target.status == TargetStatuses.PROCESSING.value
+ )
From 81a93d7e30192ba316e25781ec56a5f8d023db86 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 08:46:09 +0100
Subject: [PATCH 0143/3455] Fix a return type
---
src/mock_vws/_requests_mock_server/mock_web_services_api.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 0c9e070e6..fa3e8228d 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -398,10 +398,11 @@ def target_list(
assert isinstance(database, VuforiaDatabase)
+ results = [target.target_id for target in database.not_deleted_targets]
body: Dict[str, Union[str, List[str]]] = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.SUCCESS.value,
- 'results': database.not_deleted_targets,
+ 'results': results,
}
return json_dump(body)
From 02ac418eea45300a4bb77ebe7b595c2832b5cf80 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 08:53:59 +0100
Subject: [PATCH 0144/3455] Use new helper functions to reduce duplication
---
src/mock_vws/_flask_server/vws/__init__.py | 52 +++++-----------------
1 file changed, 10 insertions(+), 42 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index cf0608ca3..27d119ac0 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -287,52 +287,20 @@ def database_summary() -> Tuple[str, int]:
)
assert isinstance(database, VuforiaDatabase)
- active_images = len(
- [
- target for target in database.targets
- if target.status == TargetStatuses.SUCCESS.value
- and target.active_flag and not target.delete_date
- ],
- )
-
- failed_images = len(
- [
- target for target in database.targets
- if target.status == TargetStatuses.FAILED.value
- and not target.delete_date
- ],
- )
-
- inactive_images = len(
- [
- target for target in database.targets
- if target.status == TargetStatuses.SUCCESS.value
- and not target.active_flag and not target.delete_date
- ],
- )
-
- processing_images = len(
- [
- target for target in database.targets
- if target.status == TargetStatuses.PROCESSING.value
- and not target.delete_date
- ],
- )
-
body = {
'result_code': ResultCodes.SUCCESS.value,
'transaction_id': uuid.uuid4().hex,
'name': database.database_name,
- 'active_images': active_images,
- 'inactive_images': inactive_images,
- 'failed_images': failed_images,
- 'target_quota': 1000,
- 'total_recos': 0,
- 'current_month_recos': 0,
- 'previous_month_recos': 0,
- 'processing_images': processing_images,
- 'reco_threshold': 1000,
- 'request_quota': 100000,
+ 'active_images': len(database.active_targets),
+ 'inactive_images': len(database.inactive_targets),
+ 'failed_images': len(database.failed_targets),
+ 'target_quota': database.target_quota,
+ 'total_recos': database.total_recos,
+ 'current_month_recos': database.current_month_recos,
+ 'previous_month_recos': database.previous_month_recos,
+ 'processing_images': len(database.processing_targets),
+ 'reco_threshold': database.reco_threshold,
+ 'request_quota': database.request_quota,
# We have ``self.request_count`` but Vuforia always shows 0.
# This was not always the case.
'request_usage': 0,
From 2de410bf3705515302a5ec871344e82c4af4d34e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 09:13:23 +0100
Subject: [PATCH 0145/3455] Remove request_count logic which isn't needed as
Vuforia doesn't update that
---
.../mock_web_services_api.py | 28 -------------------
1 file changed, 28 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index fa3e8228d..9fb301960 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -51,29 +51,6 @@
_TARGET_ID_PATTERN = '[A-Za-z0-9]+'
-@wrapt.decorator
-def update_request_count(
- wrapped: Callable[..., str],
- instance: Any,
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> str:
- """
- Add to the request count.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- """
- instance.request_count += 1
- return wrapped(*args, **kwargs)
-
-
@wrapt.decorator
def run_validators(
wrapped: Callable[..., str],
@@ -174,7 +151,6 @@ def decorator(method: Callable[..., str]) -> Callable[..., str]:
run_validators,
set_date_header,
set_content_length_header,
- update_request_count,
]
for decorator in decorators:
@@ -229,12 +205,10 @@ def __init__(
Attributes:
databases: Target databases.
routes: The `Route`s to be used in the mock.
- request_count: The number of requests made to this API.
"""
self.databases: Set[VuforiaDatabase] = set([])
self.routes: Set[Route] = ROUTES
self._processing_time_seconds = processing_time_seconds
- self.request_count = 0
@route(
path_pattern='/targets',
@@ -370,8 +344,6 @@ def database_summary(
'processing_images': len(database.processing_targets),
'reco_threshold': database.reco_threshold,
'request_quota': database.request_quota,
- # We have ``self.request_count`` but Vuforia always shows 0.
- # This was not always the case.
'request_usage': 0,
}
return json_dump(body)
From da8e4d1434219b6cb578424527b839dcc490f078 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 09:21:40 +0100
Subject: [PATCH 0146/3455] Use new helper to save a little code
---
.../_requests_mock_server/mock_web_services_api.py | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 9fb301960..93cb01dc9 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -525,12 +525,8 @@ def update_target(
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
- ):
+ other_targets = set(database.not_deleted_targets) - set([target])
+ if any(other.name == name for other in other_targets):
context.status_code = HTTPStatus.FORBIDDEN
body = {
'transaction_id': uuid.uuid4().hex,
From 10b9b30ac2d9b64a3a10641bf25b43bc0eb9adeb Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 09:27:35 +0100
Subject: [PATCH 0147/3455] Use new helper to save a little code
---
src/mock_vws/_flask_server/vws/__init__.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 27d119ac0..361f46b5e 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -142,8 +142,7 @@ def add_target() -> Tuple[str, int]:
assert isinstance(database, VuforiaDatabase)
- targets = (target for target in database.targets if not target.delete_date)
- if any(target.name == name for target in targets):
+ if any(target.name == name for target in database.not_deleted_targets):
body = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.TARGET_NAME_EXIST.value,
From ccde9408812dcbf004a3c6e9bdafd2291dd96351 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 13:33:16 +0100
Subject: [PATCH 0148/3455] Move some logic to name validators from the
requests_mock front-end
---
.../mock_web_services_api.py | 36 ++----
src/mock_vws/_services_validators/__init__.py | 16 +++
.../_services_validators/name_validators.py | 116 ++++++++++++++++++
.../_services_validators/target_validators.py | 4 +-
4 files changed, 147 insertions(+), 25 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 93cb01dc9..08ae77782 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -225,7 +225,6 @@ def add_target(
Fake implementation of
https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Add-a-Target
"""
- name = request.json()['name']
database = get_database_matching_server_keys(
request_headers=request.headers,
request_body=request.body,
@@ -236,29 +235,28 @@ def add_target(
assert isinstance(database, VuforiaDatabase)
- if any(target.name == name for target in database.not_deleted_targets):
- context.status_code = HTTPStatus.FORBIDDEN
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.TARGET_NAME_EXIST.value,
- }
- return json_dump(body)
-
- active_flag = request.json().get('active_flag')
- if active_flag is None:
- active_flag = True
+ given_active_flag = request.json().get('active_flag')
+ active_flag = {
+ None: True,
+ True: True,
+ False: False,
+ }[given_active_flag]
image = request.json()['image']
decoded = base64.b64decode(image)
image_file = io.BytesIO(decoded)
+ name = request.json()['name']
+ width = request.json()['width']
+ application_metadata=request.json().get('application_metadata')
+
new_target = Target(
- name=request.json()['name'],
- width=request.json()['width'],
+ name=name,
+ width=width,
image=image_file,
active_flag=active_flag,
processing_time_seconds=self._processing_time_seconds,
- application_metadata=request.json().get('application_metadata'),
+ application_metadata=application_metadata,
)
database.targets.add(new_target)
@@ -525,14 +523,6 @@ def update_target(
if 'name' in request.json():
name = request.json()['name']
- other_targets = set(database.not_deleted_targets) - set([target])
- if any(other.name == name for other in other_targets):
- 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():
diff --git a/src/mock_vws/_services_validators/__init__.py b/src/mock_vws/_services_validators/__init__.py
index b038f7b1f..654a997d4 100644
--- a/src/mock_vws/_services_validators/__init__.py
+++ b/src/mock_vws/_services_validators/__init__.py
@@ -43,6 +43,8 @@
validate_name_characters_in_range,
validate_name_length,
validate_name_type,
+ validate_name_does_not_exist_new_target,
+ validate_name_does_not_exist_existing_target,
)
from .project_state_validators import validate_project_state
from .target_validators import validate_target_id_exists
@@ -121,6 +123,20 @@ def run_services_validators(
request_method=request_method,
request_path=request_path,
)
+ validate_name_does_not_exist_new_target(
+ request_headers=request_headers,
+ request_body=request_body,
+ request_method=request_method,
+ request_path=request_path,
+ databases=databases,
+ )
+ validate_name_does_not_exist_existing_target(
+ request_headers=request_headers,
+ request_body=request_body,
+ request_method=request_method,
+ request_path=request_path,
+ databases=databases,
+ )
validate_width(request_body=request_body)
validate_content_type_header_given(
diff --git a/src/mock_vws/_services_validators/name_validators.py b/src/mock_vws/_services_validators/name_validators.py
index 5557b1154..781a9e827 100644
--- a/src/mock_vws/_services_validators/name_validators.py
+++ b/src/mock_vws/_services_validators/name_validators.py
@@ -2,8 +2,11 @@
Validators for target names.
"""
+from typing import Dict, Set
import json
from http import HTTPStatus
+from mock_vws._database_matchers import get_database_matching_server_keys
+from mock_vws.database import VuforiaDatabase
from mock_vws._services_validators.exceptions import (
Fail,
@@ -100,3 +103,116 @@ def validate_name_length(request_body: bytes) -> None:
return
raise Fail(status_code=HTTPStatus.BAD_REQUEST)
+
+
+def validate_name_does_not_exist_new_target(
+ databases: Set[VuforiaDatabase],
+ request_body: bytes,
+ request_headers: Dict[str, str],
+ request_method: str,
+ request_path: str,
+) -> None:
+ """
+ Validate that the name does not exist for any existing target.
+
+ Args:
+ databases: All Vuforia databases.
+ request_body: The body of the request.
+ request_headers: The headers sent with the request.
+ request_method: The HTTP method the request is using.
+ request_path: The path to the endpoint.
+
+ Raises:
+ TargetNameExist: The target name already exists.
+ """
+ if not request_body:
+ return
+
+ request_text = request_body.decode()
+ if 'name' not in json.loads(request_text):
+ return
+
+ split_path = request_path.split('/')
+ if len(split_path) != 2:
+ return
+
+ name = json.loads(request_text)['name']
+ database = get_database_matching_server_keys(
+ request_headers=request_headers,
+ request_body=request_body,
+ request_method=request_method,
+ request_path=request_path,
+ databases=databases,
+ )
+ assert isinstance(database, VuforiaDatabase)
+
+ matching_name_targets = [
+ target for target in database.not_deleted_targets if
+ target.name == name
+ ]
+
+ if not matching_name_targets:
+ return
+
+ raise TargetNameExist
+
+
+def validate_name_does_not_exist_existing_target(
+ request_headers: Dict[str, str],
+ request_body: bytes,
+ request_method: str,
+ request_path: str,
+ databases: Set[VuforiaDatabase],
+) -> None:
+ """
+ Validate that the name does not exist for any existing target apart from
+ the one being updated.
+
+ Args:
+ databases: All Vuforia databases.
+ request_body: The body of the request.
+ request_headers: The headers sent with the request.
+ request_method: The HTTP method the request is using.
+ request_path: The path to the endpoint.
+
+ Raises:
+ TargetNameExist: The target name is not the same as the name of the
+ target being updated but it is the same as another target.
+ """
+
+ if not request_body:
+ return
+
+ request_text = request_body.decode()
+ if 'name' not in json.loads(request_text):
+ return
+
+ split_path = request_path.split('/')
+ if len(split_path) == 2:
+ return
+
+ target_id = split_path[-1]
+
+ name = json.loads(request_text)['name']
+ database = get_database_matching_server_keys(
+ request_headers=request_headers,
+ request_body=request_body,
+ request_method=request_method,
+ request_path=request_path,
+ databases=databases,
+ )
+ assert isinstance(database, VuforiaDatabase)
+
+ matching_name_targets = [
+ target for target in database.not_deleted_targets if
+ target.name == name
+ ]
+
+ if not matching_name_targets:
+ return
+
+ [matching_name_target] = matching_name_targets
+ if matching_name_target.target_id == target_id:
+ return
+
+ raise TargetNameExist
diff --git a/src/mock_vws/_services_validators/target_validators.py b/src/mock_vws/_services_validators/target_validators.py
index 49a23aec1..f23a43811 100644
--- a/src/mock_vws/_services_validators/target_validators.py
+++ b/src/mock_vws/_services_validators/target_validators.py
@@ -48,8 +48,8 @@ def validate_target_id_exists(
try:
[_] = [
target
- for target in database.targets
- if target.target_id == target_id and not target.delete_date
+ for target in database.not_deleted_targets
+ if target.target_id == target_id
]
except ValueError as exc:
raise UnknownTarget from exc
From 6a9521734d10bd9598a2806c5ee68860583acc7e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 13:37:27 +0100
Subject: [PATCH 0149/3455] Remove duplication of timezone setting for target
---
src/mock_vws/target.py | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 24feec48e..078d1133d 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -77,7 +77,7 @@ def __init__( # pylint: disable=too-many-arguments
self.target_id = uuid.uuid4().hex
self.active_flag = active_flag
self.width = width
- gmt = ZoneInfo('GMT')
+ self.timezone = ZoneInfo('GMT')
now = datetime.datetime.now(tz=gmt)
self.upload_date: datetime.datetime = now
self.last_modified_date = self.upload_date
@@ -99,8 +99,7 @@ def delete(self) -> None:
"""
Mark the target as deleted.
"""
- gmt = ZoneInfo('GMT')
- now = datetime.datetime.now(tz=gmt)
+ now = datetime.datetime.now(tz=self._timezone)
self.delete_date = now
@property
@@ -139,8 +138,7 @@ def status(self) -> str:
seconds=self._processing_time_seconds,
)
- gmt = ZoneInfo('GMT')
- now = datetime.datetime.now(tz=gmt)
+ now = datetime.datetime.now(tz=self._timezone)
time_since_change = now - self.last_modified_date
if time_since_change <= processing_time:
@@ -167,8 +165,7 @@ def tracking_rating(self) -> int:
/ 2,
)
- gmt = ZoneInfo('GMT')
- now = datetime.datetime.now(tz=gmt)
+ now = datetime.datetime.now(tz=self._timezone)
time_since_upload = now - self.upload_date
if time_since_upload <= pre_rating_time:
From 9dc3822b3acb11702b0edbc52e6d566eaade466a Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 13:40:11 +0100
Subject: [PATCH 0150/3455] Fix use of a deleted variable
---
src/mock_vws/target.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 078d1133d..7953db873 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -77,8 +77,8 @@ def __init__( # pylint: disable=too-many-arguments
self.target_id = uuid.uuid4().hex
self.active_flag = active_flag
self.width = width
- self.timezone = ZoneInfo('GMT')
- now = datetime.datetime.now(tz=gmt)
+ self._timezone = ZoneInfo('GMT')
+ now = datetime.datetime.now(tz=self._timezone)
self.upload_date: datetime.datetime = now
self.last_modified_date = self.upload_date
self.processed_tracking_rating = random.randint(0, 5)
From 95f305fa5b883b050340309b4adfe0fb1f3024f2 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 13:42:30 +0100
Subject: [PATCH 0151/3455] Fix black
---
.../mock_web_services_api.py | 2 +-
src/mock_vws/_services_validators/__init__.py | 4 ++--
.../_services_validators/name_validators.py | 16 +++++++++-------
3 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 08ae77782..d6912d873 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -248,7 +248,7 @@ def add_target(
name = request.json()['name']
width = request.json()['width']
- application_metadata=request.json().get('application_metadata')
+ application_metadata = request.json().get('application_metadata')
new_target = Target(
name=name,
diff --git a/src/mock_vws/_services_validators/__init__.py b/src/mock_vws/_services_validators/__init__.py
index 654a997d4..387c4bd25 100644
--- a/src/mock_vws/_services_validators/__init__.py
+++ b/src/mock_vws/_services_validators/__init__.py
@@ -41,10 +41,10 @@
)
from .name_validators import (
validate_name_characters_in_range,
+ validate_name_does_not_exist_existing_target,
+ validate_name_does_not_exist_new_target,
validate_name_length,
validate_name_type,
- validate_name_does_not_exist_new_target,
- validate_name_does_not_exist_existing_target,
)
from .project_state_validators import validate_project_state
from .target_validators import validate_target_id_exists
diff --git a/src/mock_vws/_services_validators/name_validators.py b/src/mock_vws/_services_validators/name_validators.py
index 781a9e827..5a04c7f92 100644
--- a/src/mock_vws/_services_validators/name_validators.py
+++ b/src/mock_vws/_services_validators/name_validators.py
@@ -2,17 +2,17 @@
Validators for target names.
"""
-from typing import Dict, Set
import json
from http import HTTPStatus
-from mock_vws._database_matchers import get_database_matching_server_keys
-from mock_vws.database import VuforiaDatabase
+from typing import Dict, Set
+from mock_vws._database_matchers import get_database_matching_server_keys
from mock_vws._services_validators.exceptions import (
Fail,
OopsErrorOccurredResponse,
TargetNameExist,
)
+from mock_vws.database import VuforiaDatabase
def validate_name_characters_in_range(
@@ -147,8 +147,9 @@ def validate_name_does_not_exist_new_target(
assert isinstance(database, VuforiaDatabase)
matching_name_targets = [
- target for target in database.not_deleted_targets if
- target.name == name
+ target
+ for target in database.not_deleted_targets
+ if target.name == name
]
if not matching_name_targets:
@@ -204,8 +205,9 @@ def validate_name_does_not_exist_existing_target(
assert isinstance(database, VuforiaDatabase)
matching_name_targets = [
- target for target in database.not_deleted_targets if
- target.name == name
+ target
+ for target in database.not_deleted_targets
+ if target.name == name
]
if not matching_name_targets:
From 6f79a90ec98470287d08714b74eba0e261a046a6 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 13:52:10 +0100
Subject: [PATCH 0152/3455] Add target summary endpoint meaning far fewer
failures
---
src/mock_vws/_flask_server/vws/__init__.py | 54 ++++++++++++++--------
1 file changed, 36 insertions(+), 18 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 361f46b5e..09b482dc1 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -48,7 +48,6 @@ def validate_request() -> None:
)
# decorators = [
# # parse_target_id,
- # # update_request_count,
# ]
@@ -142,13 +141,6 @@ def add_target() -> Tuple[str, int]:
assert isinstance(database, VuforiaDatabase)
- if any(target.name == name for target in database.not_deleted_targets):
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.TARGET_NAME_EXIST.value,
- }
- return json_dump(body), codes.FORBIDDEN
-
active_flag = request_json.get('active_flag')
if active_flag is None:
active_flag = True
@@ -306,6 +298,42 @@ def database_summary() -> Tuple[str, int]:
}
return json_dump(body), codes.OK
+@VWS_FLASK_APP.route('/summary/', methods=['GET'])
+def target_summary(target_id: str) -> Tuple[str, int]:
+ """
+ Get a summary report for a target.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Retrieve-a-Target-Summary-Report
+ """
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+ [target] = [
+ target for target in database.targets if target.target_id == target_id
+ ]
+ body = {
+ 'status': target.status,
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.SUCCESS.value,
+ 'database_name': database.database_name,
+ 'target_name': target.name,
+ 'upload_date': target.upload_date.strftime('%Y-%m-%d'),
+ 'active_flag': target.active_flag,
+ 'tracking_rating': target.tracking_rating,
+ 'total_recos': 0,
+ 'current_month_recos': 0,
+ 'previous_month_recos': 0,
+ }
+ return json_dump(body)
+
@VWS_FLASK_APP.route('/duplicates/', methods=['GET'])
def get_duplicates(target_id: str) -> Tuple[str, int]:
@@ -448,16 +476,6 @@ def update_target(target_id: str) -> Tuple[str, int]:
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
- ):
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.TARGET_NAME_EXIST.value,
- }
- return json_dump(body), codes.FORBIDDEN
update_values['name'] = name
if 'image' in request_json:
From 0cda86c5dd7d9806dbbec8805b5d3ab059a3741e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 11 Sep 2020 21:47:46 +0100
Subject: [PATCH 0153/3455] Fix a mypy issue
---
src/mock_vws/_flask_server/vws/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 09b482dc1..6a047c605 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -332,7 +332,7 @@ def target_summary(target_id: str) -> Tuple[str, int]:
'current_month_recos': 0,
'previous_month_recos': 0,
}
- return json_dump(body)
+ return json_dump(body), codes.OK
@VWS_FLASK_APP.route('/duplicates/', methods=['GET'])
From c3fda78b2ad1ae4ee02cebff8ed723236983e5cd Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 13 Sep 2020 14:43:48 +0100
Subject: [PATCH 0154/3455] Fix a few tests by allowing drop of content type
header
---
src/mock_vws/_flask_server/vws/__init__.py | 30 ++++++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 6a047c605..9ca140f67 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -1,4 +1,9 @@
+"""
+TODO
+"""
+
import base64
+from http import HTTPStatus
import email.utils
import io
import json
@@ -25,6 +30,7 @@
RequestTimeTooSkewed,
TargetNameExist,
UnknownTarget,
+ UnnecessaryRequestBody,
)
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
@@ -50,6 +56,10 @@ def validate_request() -> None:
# # parse_target_id,
# ]
+class MyResponse(Response):
+ default_mimetype = None
+
+VWS_FLASK_APP.response_class = MyResponse
@VWS_FLASK_APP.errorhandler(UnknownTarget)
def handle_unknown_target(e: UnknownTarget) -> Tuple[str, int]:
@@ -95,6 +105,18 @@ def handle_image_too_large(e: ImageTooLarge) -> Tuple[str, int]:
def handle_request_time_too_skewed(e: RequestTimeTooSkewed) -> Tuple[str, int]:
return e.response_text, e.status_code
+@VWS_FLASK_APP.errorhandler(UnnecessaryRequestBody)
+def handle_unnecessary_request_body(e: UnnecessaryRequestBody) -> Tuple[str, int]:
+ # TODO not sure how to drop a header
+ # e.response_text == 'HELLOADAM'
+ new_response = Response()
+ new_response.status_code = e.status_code
+ new_response.set_data(e.response_text)
+ new_response.headers.pop('Content-Type')
+ # new_response.content_type = None
+ # import pdb; pdb.set_trace()
+ return new_response
+
@VWS_FLASK_APP.errorhandler(OopsErrorOccurredResponse)
def handle_oops_error_occurred(e: OopsErrorOccurredResponse) -> Response:
@@ -107,8 +129,12 @@ def handle_oops_error_occurred(e: OopsErrorOccurredResponse) -> Response:
@VWS_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
+ """
+ TODO
+ """
response.headers['Connection'] = 'keep-alive'
- if response.status_code != codes.INTERNAL_SERVER_ERROR:
+ # import pdb; pdb.set_trace()
+ if response.status_code != HTTPStatus.INTERNAL_SERVER_ERROR and len(response.data):
response.headers['Content-Type'] = 'application/json'
response.headers['Server'] = 'nginx'
content_length = len(response.data)
@@ -150,7 +176,7 @@ def add_target() -> Tuple[str, int]:
image_file = io.BytesIO(decoded)
new_target = Target(
- name=request_json['name'],
+ name=name,
width=request_json['width'],
image=image_file,
active_flag=active_flag,
From 08e1ee3fdda25219997343a57de52bd509c14f7f Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 13 Sep 2020 15:18:06 +0100
Subject: [PATCH 0155/3455] Handle date header not given on mock VWQ
---
src/mock_vws/_flask_server/vwq/__init__.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 134c19c2e..1c3372601 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -22,6 +22,7 @@
BoundaryNotInBody,
ContentLengthHeaderTooLarge,
DateFormatNotValid,
+ DateHeaderNotGiven,
ImageNotGiven,
InactiveProject,
InvalidAcceptHeader,
@@ -220,6 +221,14 @@ def handle_date_format_not_valid(e: DateFormatNotValid, ) -> Response:
assert isinstance(response, Response)
return response
+@CLOUDRECO_FLASK_APP.errorhandler(DateHeaderNotGiven)
+def handle_date_header_not_given(e: DateFormatNotValid, ) -> Response:
+ response = make_response(e.response_text, e.status_code)
+ content_type = 'text/plain; charset=ISO-8859-1'
+ response.headers['Content-Type'] = content_type
+ assert isinstance(response, Response)
+ return response
+
@CLOUDRECO_FLASK_APP.errorhandler(MalformedAuthHeader)
def handle_malformed_auth_header(e: MalformedAuthHeader, ) -> Response:
From b5079c505cf84a73aaf1d3017d35ed04a71eacec Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 13 Sep 2020 15:18:35 +0100
Subject: [PATCH 0156/3455] A little black reformatting
---
.../_flask_server/storage/__init__.py | 9 +-
src/mock_vws/_flask_server/vwq/__init__.py | 83 ++++++++++++++-----
src/mock_vws/_flask_server/vws/__init__.py | 28 +++++--
src/mock_vws/database.py | 9 +-
src/mock_vws/target.py | 5 +-
5 files changed, 96 insertions(+), 38 deletions(-)
diff --git a/src/mock_vws/_flask_server/storage/__init__.py b/src/mock_vws/_flask_server/storage/__init__.py
index 9b71a8392..2a210e323 100644
--- a/src/mock_vws/_flask_server/storage/__init__.py
+++ b/src/mock_vws/_flask_server/storage/__init__.py
@@ -56,7 +56,8 @@ def create_database() -> Tuple[str, int]:
)
def create_target(database_name: str) -> Tuple[str, int]:
[database] = [
- database for database in VUFORIA_DATABASES
+ database
+ for database in VUFORIA_DATABASES
if database.database_name == database_name
]
image_base64 = request.json['image_base64']
@@ -82,7 +83,8 @@ def create_target(database_name: str) -> Tuple[str, int]:
)
def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
[database] = [
- database for database in VUFORIA_DATABASES
+ database
+ for database in VUFORIA_DATABASES
if database.database_name == database_name
]
[target] = [
@@ -98,7 +100,8 @@ def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
)
def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
[database] = [
- database for database in VUFORIA_DATABASES
+ database
+ for database in VUFORIA_DATABASES
if database.database_name == database_name
]
[target] = [
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 1c3372601..a14cddd9f 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -82,49 +82,63 @@ def handle_connection_error(
@CLOUDRECO_FLASK_APP.errorhandler(UnsupportedMediaType)
-def handle_unsupported_media_type(e: UnsupportedMediaType, ) -> Response:
+def handle_unsupported_media_type(
+ e: UnsupportedMediaType,
+) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
@CLOUDRECO_FLASK_APP.errorhandler(InvalidAcceptHeader)
-def handle_invalid_accept_header(e: InvalidAcceptHeader, ) -> Response:
+def handle_invalid_accept_header(
+ e: InvalidAcceptHeader,
+) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
@CLOUDRECO_FLASK_APP.errorhandler(BadImage)
-def handle_bad_image(e: BadImage, ) -> Response:
+def handle_bad_image(
+ e: BadImage,
+) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
@CLOUDRECO_FLASK_APP.errorhandler(UnknownParameters)
-def handle_unknown_parameters(e: UnknownParameters, ) -> Response:
+def handle_unknown_parameters(
+ e: UnknownParameters,
+) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
@CLOUDRECO_FLASK_APP.errorhandler(RequestTimeTooSkewed)
-def handle_request_time_too_skewed(e: RequestTimeTooSkewed, ) -> Response:
+def handle_request_time_too_skewed(
+ e: RequestTimeTooSkewed,
+) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
@CLOUDRECO_FLASK_APP.errorhandler(ImageNotGiven)
-def handle_image_not_given(e: ImageNotGiven, ) -> Response:
+def handle_image_not_given(
+ e: ImageNotGiven,
+) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
@CLOUDRECO_FLASK_APP.errorhandler(InactiveProject)
-def handle_inactive_project(e: InactiveProject, ) -> Response:
+def handle_inactive_project(
+ e: InactiveProject,
+) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
@@ -140,7 +154,9 @@ def handle_invalid_include_target_data(
@CLOUDRECO_FLASK_APP.errorhandler(InvalidMaxNumResults)
-def handle_invalid_max_num_results(e: InvalidMaxNumResults, ) -> Response:
+def handle_invalid_max_num_results(
+ e: InvalidMaxNumResults,
+) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
@@ -156,7 +172,9 @@ def handle_max_num_results_out_of_range(
@CLOUDRECO_FLASK_APP.errorhandler(NoBoundaryFound)
-def handle_no_boundary_found(e: NoBoundaryFound, ) -> Response:
+def handle_no_boundary_found(
+ e: NoBoundaryFound,
+) -> Response:
content_type = 'text/html;charset=UTF-8'
response = make_response(e.response_text, e.status_code)
response.headers['Content-Type'] = content_type
@@ -165,7 +183,9 @@ def handle_no_boundary_found(e: NoBoundaryFound, ) -> Response:
@CLOUDRECO_FLASK_APP.errorhandler(BoundaryNotInBody)
-def handle_boundary_not_in_body(e: BoundaryNotInBody, ) -> Response:
+def handle_boundary_not_in_body(
+ e: BoundaryNotInBody,
+) -> Response:
content_type = 'text/html;charset=UTF-8'
response = make_response(e.response_text, e.status_code)
response.headers['Content-Type'] = content_type
@@ -174,7 +194,9 @@ def handle_boundary_not_in_body(e: BoundaryNotInBody, ) -> Response:
@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailure)
-def handle_authentication_failure(e: AuthenticationFailure, ) -> Response:
+def handle_authentication_failure(
+ e: AuthenticationFailure,
+) -> Response:
response = make_response(e.response_text, e.status_code)
response.headers['WWW-Authenticate'] = 'VWS'
assert isinstance(response, Response)
@@ -192,7 +214,9 @@ def handle_authentication_failure_good_formatting(
@CLOUDRECO_FLASK_APP.errorhandler(QueryOutOfBounds)
-def handle_query_out_of_bounds(e: QueryOutOfBounds, ) -> Response:
+def handle_query_out_of_bounds(
+ e: QueryOutOfBounds,
+) -> Response:
response = make_response(e.response_text, e.status_code)
content_type = 'text/html; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
@@ -203,7 +227,9 @@ def handle_query_out_of_bounds(e: QueryOutOfBounds, ) -> Response:
@CLOUDRECO_FLASK_APP.errorhandler(AuthHeaderMissing)
-def handle_auth_header_missing(e: AuthHeaderMissing, ) -> Response:
+def handle_auth_header_missing(
+ e: AuthHeaderMissing,
+) -> Response:
response = make_response(e.response_text, e.status_code)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
@@ -213,7 +239,9 @@ def handle_auth_header_missing(e: AuthHeaderMissing, ) -> Response:
@CLOUDRECO_FLASK_APP.errorhandler(DateFormatNotValid)
-def handle_date_format_not_valid(e: DateFormatNotValid, ) -> Response:
+def handle_date_format_not_valid(
+ e: DateFormatNotValid,
+) -> Response:
response = make_response(e.response_text, e.status_code)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
@@ -221,8 +249,11 @@ def handle_date_format_not_valid(e: DateFormatNotValid, ) -> Response:
assert isinstance(response, Response)
return response
+
@CLOUDRECO_FLASK_APP.errorhandler(DateHeaderNotGiven)
-def handle_date_header_not_given(e: DateFormatNotValid, ) -> Response:
+def handle_date_header_not_given(
+ e: DateFormatNotValid,
+) -> Response:
response = make_response(e.response_text, e.status_code)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
@@ -231,7 +262,9 @@ def handle_date_header_not_given(e: DateFormatNotValid, ) -> Response:
@CLOUDRECO_FLASK_APP.errorhandler(MalformedAuthHeader)
-def handle_malformed_auth_header(e: MalformedAuthHeader, ) -> Response:
+def handle_malformed_auth_header(
+ e: MalformedAuthHeader,
+) -> Response:
response = make_response(e.response_text, e.status_code)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
@@ -249,13 +282,17 @@ def set_headers(response: Response) -> Response:
response.headers['Content-Length'] = str(content_length)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
response.headers['Date'] = date
- if response.status_code in (
- codes.OK,
- codes.UNPROCESSABLE_ENTITY,
- codes.BAD_REQUEST,
- codes.FORBIDDEN,
- codes.UNAUTHORIZED,
- ) and 'Content-Type' not in response.headers:
+ if (
+ response.status_code
+ in (
+ codes.OK,
+ codes.UNPROCESSABLE_ENTITY,
+ codes.BAD_REQUEST,
+ codes.FORBIDDEN,
+ codes.UNAUTHORIZED,
+ )
+ and 'Content-Type' not in response.headers
+ ):
response.headers['Content-Type'] = 'application/json'
return response
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 9ca140f67..b9fb40847 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -3,11 +3,11 @@
"""
import base64
-from http import HTTPStatus
import email.utils
import io
import json
import uuid
+from http import HTTPStatus
from typing import Dict, List, Tuple, Union
import requests
@@ -56,11 +56,14 @@ def validate_request() -> None:
# # parse_target_id,
# ]
+
class MyResponse(Response):
default_mimetype = None
+
VWS_FLASK_APP.response_class = MyResponse
+
@VWS_FLASK_APP.errorhandler(UnknownTarget)
def handle_unknown_target(e: UnknownTarget) -> Tuple[str, int]:
return e.response_text, e.status_code
@@ -105,8 +108,11 @@ def handle_image_too_large(e: ImageTooLarge) -> Tuple[str, int]:
def handle_request_time_too_skewed(e: RequestTimeTooSkewed) -> Tuple[str, int]:
return e.response_text, e.status_code
+
@VWS_FLASK_APP.errorhandler(UnnecessaryRequestBody)
-def handle_unnecessary_request_body(e: UnnecessaryRequestBody) -> Tuple[str, int]:
+def handle_unnecessary_request_body(
+ e: UnnecessaryRequestBody,
+) -> Tuple[str, int]:
# TODO not sure how to drop a header
# e.response_text == 'HELLOADAM'
new_response = Response()
@@ -134,7 +140,9 @@ def set_headers(response: Response) -> Response:
"""
response.headers['Connection'] = 'keep-alive'
# import pdb; pdb.set_trace()
- if response.status_code != HTTPStatus.INTERNAL_SERVER_ERROR and len(response.data):
+ if response.status_code != HTTPStatus.INTERNAL_SERVER_ERROR and len(
+ response.data
+ ):
response.headers['Content-Type'] = 'application/json'
response.headers['Server'] = 'nginx'
content_length = len(response.data)
@@ -324,6 +332,7 @@ def database_summary() -> Tuple[str, int]:
}
return json_dump(body), codes.OK
+
@VWS_FLASK_APP.route('/summary/', methods=['GET'])
def target_summary(target_id: str) -> Tuple[str, int]:
"""
@@ -385,10 +394,12 @@ def get_duplicates(target_id: str) -> Tuple[str, int]:
other_targets = set(database.targets) - set([target])
similar_targets: List[str] = [
- other.target_id for other in other_targets
- if Image.open(other.image) == Image.open(target.image) and
- TargetStatuses.FAILED.value not in (target.status, other.status) and
- TargetStatuses.PROCESSING.value != other.status and other.active_flag
+ other.target_id
+ for other in other_targets
+ if Image.open(other.image) == Image.open(target.image)
+ and TargetStatuses.FAILED.value not in (target.status, other.status)
+ and TargetStatuses.PROCESSING.value != other.status
+ and other.active_flag
]
body = {
@@ -418,7 +429,8 @@ def target_list() -> Tuple[str, int]:
)
assert isinstance(database, VuforiaDatabase)
results = [
- target.target_id for target in database.targets
+ target.target_id
+ for target in database.targets
if not target.delete_date
]
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index c3182d8ce..166f5dd78 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -43,8 +43,13 @@ class VuforiaDatabase:
def to_dict(
self,
- ) -> Dict[str, Union[str, List[Dict[str, Optional[Union[str, int, bool,
- float]]]], ], ]:
+ ) -> Dict[
+ str,
+ Union[
+ str,
+ List[Dict[str, Optional[Union[str, int, bool, float]]]],
+ ],
+ ]:
targets = [target.to_dict() for target in self.targets]
return {
'database_name': self.database_name,
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index a2e4021b8..23b2621eb 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -185,8 +185,9 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
return {
'name': self.name,
'width': self.width,
- 'image_base64':
- base64.encodestring(self.image.getvalue()).decode(),
+ 'image_base64': base64.encodestring(
+ self.image.getvalue()
+ ).decode(),
'active_flag': self.active_flag,
'processing_time_seconds': self._processing_time_seconds,
'application_metadata': self.application_metadata,
From 4f9d1f7f3386fedca231ed02a96380d86f144d9b Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 13 Sep 2020 15:28:25 +0100
Subject: [PATCH 0157/3455] Remove a pytest warning
---
src/mock_vws/target.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 23b2621eb..63d577bdf 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -182,12 +182,13 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
if self.delete_date:
delete_date = datetime.datetime.isoformat(self.delete_date)
+ image_value = self.image.getvalue()
+ image_base64 = base64.encodebytes(image_value).decode()
+
return {
'name': self.name,
'width': self.width,
- 'image_base64': base64.encodestring(
- self.image.getvalue()
- ).decode(),
+ 'image_base64': image_base64,
'active_flag': self.active_flag,
'processing_time_seconds': self._processing_time_seconds,
'application_metadata': self.application_metadata,
From 28e480c87e5f8123d0c2413f163d2692395e9e11 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 13 Sep 2020 15:37:09 +0100
Subject: [PATCH 0158/3455] Keep processed tracking rating consistent across
flask calls
---
src/mock_vws/_flask_server/vws/_databases.py | 2 ++
src/mock_vws/target.py | 1 +
2 files changed, 3 insertions(+)
diff --git a/src/mock_vws/_flask_server/vws/_databases.py b/src/mock_vws/_flask_server/vws/_databases.py
index 7979d95dd..d24b2dc67 100644
--- a/src/mock_vws/_flask_server/vws/_databases.py
+++ b/src/mock_vws/_flask_server/vws/_databases.py
@@ -66,6 +66,8 @@ def get_all_databases() -> Set[VuforiaDatabase]:
target.upload_date = datetime.datetime.fromisoformat(
target_dict['upload_date'],
)
+ target.processed_tracking_rating = target_dict['processed_tracking_rating']
+ # import pdb; pdb.set_trace()
target.upload_date = target.upload_date.replace(tzinfo=gmt)
delete_date_optional = target_dict['delete_date_optional']
if delete_date_optional:
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 63d577bdf..fad885af6 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -191,6 +191,7 @@ def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
'image_base64': image_base64,
'active_flag': self.active_flag,
'processing_time_seconds': self._processing_time_seconds,
+ 'processed_tracking_rating': self.processed_tracking_rating,
'application_metadata': self.application_metadata,
'target_id': self.target_id,
'last_modified_date': self.last_modified_date.isoformat(),
From f7eadba3c78c1b9eabc88b187d24bc1bbb47b8b1 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 13 Sep 2020 20:21:57 +0100
Subject: [PATCH 0159/3455] Remove some useless commented out code
---
src/mock_vws/_flask_server/vws/__init__.py | 14 --------------
1 file changed, 14 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index b9fb40847..60935fd6d 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -139,7 +139,6 @@ def set_headers(response: Response) -> Response:
TODO
"""
response.headers['Connection'] = 'keep-alive'
- # import pdb; pdb.set_trace()
if response.status_code != HTTPStatus.INTERNAL_SERVER_ERROR and len(
response.data
):
@@ -442,20 +441,7 @@ def target_list() -> Tuple[str, int]:
return json_dump(body), codes.OK
-# @route(
-# path_pattern=f'/targets/{_TARGET_ID_PATTERN}',
-# http_methods=[PUT],
-# optional_keys={
-# 'active_flag',
-# 'application_metadata',
-# 'image',
-# 'name',
-# 'width',
-# },
-# )
@VWS_FLASK_APP.route('/targets/', methods=['PUT'])
-# TODO
-# @JSON_SCHEMA.validate(UPDATE_TARGET_SCHEMA)
def update_target(target_id: str) -> Tuple[str, int]:
"""
Update a target.
From f30568bbe5957161cabaff0281c1e64e49cb331b Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 13 Sep 2020 20:22:16 +0100
Subject: [PATCH 0160/3455] Remove some useless commented out code
---
src/mock_vws/_flask_server/vws/__init__.py | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 60935fd6d..4c1cb6e08 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -113,14 +113,10 @@ def handle_request_time_too_skewed(e: RequestTimeTooSkewed) -> Tuple[str, int]:
def handle_unnecessary_request_body(
e: UnnecessaryRequestBody,
) -> Tuple[str, int]:
- # TODO not sure how to drop a header
- # e.response_text == 'HELLOADAM'
new_response = Response()
new_response.status_code = e.status_code
new_response.set_data(e.response_text)
new_response.headers.pop('Content-Type')
- # new_response.content_type = None
- # import pdb; pdb.set_trace()
return new_response
From 0bb58071520bdda04a11da420e4b44c39c2bd52e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 13 Sep 2020 20:23:46 +0100
Subject: [PATCH 0161/3455] Remove some useless commented out code
---
src/mock_vws/_flask_server/vws/__init__.py | 5 -----
1 file changed, 5 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 4c1cb6e08..1c664d015 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -46,15 +46,11 @@ def validate_request() -> None:
databases = get_all_databases()
run_services_validators(
request_headers=dict(request.headers),
- # TODO not sure about this one
request_body=request.data,
request_method=request.method,
request_path=request.path,
databases=databases,
)
- # decorators = [
- # # parse_target_id,
- # ]
class MyResponse(Response):
@@ -203,7 +199,6 @@ def add_target() -> Tuple[str, int]:
@VWS_FLASK_APP.route('/targets/', methods=['GET'])
-# @JSON_SCHEMA.validate(ADD_TARGET_SCHEMA)
def get_target(target_id: str) -> Tuple[str, int]:
"""
Get details of a target.
From 21a87391f9e5dae2232609ff997139a9e2f64929 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 13 Sep 2020 20:35:05 +0100
Subject: [PATCH 0162/3455] Consolidate a bunch of error handling
---
src/mock_vws/_flask_server/vws/__init__.py | 36 ++--------------------
1 file changed, 3 insertions(+), 33 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 1c664d015..d3161319f 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -61,50 +61,20 @@ class MyResponse(Response):
@VWS_FLASK_APP.errorhandler(UnknownTarget)
-def handle_unknown_target(e: UnknownTarget) -> Tuple[str, int]:
- return e.response_text, e.status_code
-
-
@VWS_FLASK_APP.errorhandler(ProjectInactive)
-def handle_project_inactive(e: ProjectInactive) -> Tuple[str, int]:
- return e.response_text, e.status_code
-
-
@VWS_FLASK_APP.errorhandler(AuthenticationFailure)
-def handle_authentication_failure(e: AuthenticationFailure) -> Tuple[str, int]:
- return e.response_text, e.status_code
-
-
@VWS_FLASK_APP.errorhandler(Fail)
-def handle_fail(e: Fail) -> Tuple[str, int]:
- return e.response_text, e.status_code
-
-
@VWS_FLASK_APP.errorhandler(MetadataTooLarge)
-def handle_metadata_too_large(e: MetadataTooLarge) -> Tuple[str, int]:
- return e.response_text, e.status_code
-
-
@VWS_FLASK_APP.errorhandler(TargetNameExist)
-def handle_target_name_exist(e: TargetNameExist) -> Tuple[str, int]:
- return e.response_text, e.status_code
-
-
@VWS_FLASK_APP.errorhandler(BadImage)
-def handle_bad_image(e: BadImage) -> Tuple[str, int]:
- return e.response_text, e.status_code
-
-
@VWS_FLASK_APP.errorhandler(ImageTooLarge)
-def handle_image_too_large(e: ImageTooLarge) -> Tuple[str, int]:
- return e.response_text, e.status_code
-
-
@VWS_FLASK_APP.errorhandler(RequestTimeTooSkewed)
-def handle_request_time_too_skewed(e: RequestTimeTooSkewed) -> Tuple[str, int]:
+# TODO update name and type hint here
+def handle_unknown_target(e: UnknownTarget) -> Tuple[str, int]:
return e.response_text, e.status_code
+
@VWS_FLASK_APP.errorhandler(UnnecessaryRequestBody)
def handle_unnecessary_request_body(
e: UnnecessaryRequestBody,
From 7d429a8583bdfbef26d03c701ded7fe09bc0fab7 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 13 Sep 2020 20:38:32 +0100
Subject: [PATCH 0163/3455] Consolidate a bunch of error handling
---
src/mock_vws/_flask_server/vws/__init__.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index d3161319f..186763e40 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -106,8 +106,6 @@ def set_headers(response: Response) -> Response:
):
response.headers['Content-Type'] = 'application/json'
response.headers['Server'] = 'nginx'
- content_length = len(response.data)
- response.headers['Content-Length'] = str(content_length)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
response.headers['Date'] = date
return response
From 7636fc69ccaf92507104f8e56ae22c6bb7abd6b8 Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
<27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Mon, 14 Sep 2020 06:44:01 +0000
Subject: [PATCH 0164/3455] Bump pytest from 6.0.1 to 6.0.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.0.1...6.0.2)
Signed-off-by: dependabot-preview[bot]
---
dev-requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 9ff71106c..f050a1c78 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -24,7 +24,7 @@ pylint==2.6.0 # Lint
pyroma==2.6 # Packaging best practices checker
pytest-cov==2.10.1 # Measure code coverage
pytest-envfiles==0.1.0 # Use files for environment variables for tests
-pytest==6.0.1 # Test runners
+pytest==6.0.2 # Test runners
sphinx-autodoc-typehints==1.11.0
sphinx_paramlinks==0.4.2
sphinxcontrib-spelling==5.4.0
From d96b55a0670c4da4bbfd0572efd4c144ed4da090 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 14 Sep 2020 11:51:31 +0100
Subject: [PATCH 0165/3455] Simplify getting target list
---
src/mock_vws/_flask_server/vws/__init__.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 186763e40..6fdc5a68a 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -388,8 +388,7 @@ def target_list() -> Tuple[str, int]:
assert isinstance(database, VuforiaDatabase)
results = [
target.target_id
- for target in database.targets
- if not target.delete_date
+ for target in database.not_deleted_targets
]
body: Dict[str, Union[str, List[str]]] = {
From 0ff6f57381134eb937e4a5ce3e195756575e29db Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 09:23:47 +0100
Subject: [PATCH 0166/3455] Make tests pass when content length header is too
large
---
dev-requirements.txt | 4 ++--
src/mock_vws/_flask_server/vws/__init__.py | 23 ++++++++++++++++++++--
2 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index dff9cfa30..b922aa11d 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -24,8 +24,8 @@ pylint==2.6.0 # Lint
pyroma==2.6 # Packaging best practices checker
pytest-cov==2.10.1 # Measure code coverage
pytest-envfiles==0.1.0 # Use files for environment variables for tests
-pytest==6.0.1 # Test runners
-requests-mock-flask==2020.1.20.2
+pytest==6.0.2 # Test runners
+requests-mock-flask==2020.9.16.0
sphinx-autodoc-typehints==1.11.0
sphinx_paramlinks==0.4.2
sphinxcontrib-spelling==5.4.0
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 6fdc5a68a..a5155af74 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -12,6 +12,7 @@
import requests
from flask import Flask, Response, make_response, request
+import flask
from PIL import Image
from requests import codes
@@ -31,6 +32,8 @@
TargetNameExist,
UnknownTarget,
UnnecessaryRequestBody,
+ ContentLengthHeaderNotInt,
+ ContentLengthHeaderTooLarge,
)
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
@@ -38,7 +41,7 @@
from ._constants import STORAGE_BASE_URL
from ._databases import get_all_databases
-VWS_FLASK_APP = Flask(__name__)
+VWS_FLASK_APP = Flask(import_name=__name__)
@VWS_FLASK_APP.before_request
@@ -56,7 +59,6 @@ def validate_request() -> None:
class MyResponse(Response):
default_mimetype = None
-
VWS_FLASK_APP.response_class = MyResponse
@@ -74,6 +76,21 @@ def handle_unknown_target(e: UnknownTarget) -> Tuple[str, int]:
return e.response_text, e.status_code
+@VWS_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
+def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge):
+ new_response = Response()
+ new_response.status_code = e.status_code
+ new_response.set_data(e.response_text)
+ new_response.headers = {'Connection': 'keep-alive'}
+ return new_response
+
+@VWS_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
+def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt):
+ new_response = Response()
+ new_response.status_code = e.status_code
+ new_response.set_data(e.response_text)
+ new_response.headers = {'Connection': 'close'}
+ return new_response
@VWS_FLASK_APP.errorhandler(UnnecessaryRequestBody)
def handle_unnecessary_request_body(
@@ -100,6 +117,8 @@ def set_headers(response: Response) -> Response:
"""
TODO
"""
+ if response.headers == {'Connection': 'keep-alive'}:
+ return response
response.headers['Connection'] = 'keep-alive'
if response.status_code != HTTPStatus.INTERNAL_SERVER_ERROR and len(
response.data
From f217c457a2ec08252815ab0eb0ed1112c46cb7d9 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 09:38:21 +0100
Subject: [PATCH 0167/3455] Handle content length header too large in flask
query
---
src/mock_vws/_flask_server/vwq/__init__.py | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index a14cddd9f..42af4ab60 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -78,6 +78,10 @@ def handle_content_length_header_too_large(
def handle_connection_error(
e: requests.exceptions.ConnectionError,
) -> Response:
+ # TODO: Issue
+ # This is incorrect - it raises on the server but should raise on the
+ # client
+ # Look into how ``requests`` handles it
raise e
@@ -225,6 +229,14 @@ def handle_query_out_of_bounds(
assert isinstance(response, Response)
return response
+@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
+def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge):
+ new_response = Response()
+ new_response.status_code = e.status_code
+ new_response.set_data(e.response_text)
+ new_response.headers = {'Connection': 'keep-alive'}
+ return new_response
+
@CLOUDRECO_FLASK_APP.errorhandler(AuthHeaderMissing)
def handle_auth_header_missing(
@@ -276,6 +288,8 @@ def handle_malformed_auth_header(
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
# raise requests.exceptions.ConnectionError
+ if response.headers == {'Connection': 'keep-alive'}:
+ return response
response.headers['Connection'] = 'keep-alive'
response.headers['Server'] = 'nginx'
content_length = len(response.data)
From 2f91cf19a3f92eb3271efab6f9260c10db608fff Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 09:53:20 +0100
Subject: [PATCH 0168/3455] All tests passing on Flask mock!
---
src/mock_vws/_flask_server/vwq/__init__.py | 13 +++++++++++++
src/mock_vws/_flask_server/vws/__init__.py | 6 +++++-
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 42af4ab60..1b60b31de 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -21,6 +21,7 @@
BadImage,
BoundaryNotInBody,
ContentLengthHeaderTooLarge,
+ ContentLengthHeaderNotInt,
DateFormatNotValid,
DateHeaderNotGiven,
ImageNotGiven,
@@ -237,6 +238,14 @@ def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge):
new_response.headers = {'Connection': 'keep-alive'}
return new_response
+@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
+def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt):
+ new_response = Response()
+ new_response.status_code = e.status_code
+ new_response.set_data(e.response_text)
+ new_response.headers = {'Connection': 'Close'}
+ return new_response
+
@CLOUDRECO_FLASK_APP.errorhandler(AuthHeaderMissing)
def handle_auth_header_missing(
@@ -290,6 +299,10 @@ def set_headers(response: Response) -> Response:
# raise requests.exceptions.ConnectionError
if response.headers == {'Connection': 'keep-alive'}:
return response
+
+ if response.headers == {'Connection': 'Close'}:
+ return response
+
response.headers['Connection'] = 'keep-alive'
response.headers['Server'] = 'nginx'
content_length = len(response.data)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index a5155af74..3093ef9b7 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -89,7 +89,7 @@ def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt):
new_response = Response()
new_response.status_code = e.status_code
new_response.set_data(e.response_text)
- new_response.headers = {'Connection': 'close'}
+ new_response.headers = {'Connection': 'Close'}
return new_response
@VWS_FLASK_APP.errorhandler(UnnecessaryRequestBody)
@@ -119,6 +119,10 @@ def set_headers(response: Response) -> Response:
"""
if response.headers == {'Connection': 'keep-alive'}:
return response
+
+ if response.headers == {'Connection': 'Close'}:
+ return response
+
response.headers['Connection'] = 'keep-alive'
if response.status_code != HTTPStatus.INTERNAL_SERVER_ERROR and len(
response.data
From 4601fa838820246ad6d0af732e4f448840462a00 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 10:30:48 +0100
Subject: [PATCH 0169/3455] Simplify VWQ implementation for Flask by combining
some error handlers
---
src/mock_vws/_flask_server/vwq/__init__.py | 91 +++-------------------
1 file changed, 10 insertions(+), 81 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 1b60b31de..685605cd8 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -65,14 +65,6 @@ class MyResponse(Response):
CLOUDRECO_FLASK_APP.response_class = MyResponse
-@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
-def handle_content_length_header_too_large(
- e: ContentLengthHeaderTooLarge,
-) -> Response:
- response = make_response(e.response_text, e.status_code)
- response.headers = Headers({'Connection': 'keep-alive'})
- assert isinstance(response, Response)
- return response
@CLOUDRECO_FLASK_APP.errorhandler(requests.exceptions.ConnectionError)
@@ -87,95 +79,32 @@ def handle_connection_error(
@CLOUDRECO_FLASK_APP.errorhandler(UnsupportedMediaType)
-def handle_unsupported_media_type(
- e: UnsupportedMediaType,
-) -> Response:
- response = make_response(e.response_text, e.status_code)
- assert isinstance(response, Response)
- return response
-
-
@CLOUDRECO_FLASK_APP.errorhandler(InvalidAcceptHeader)
-def handle_invalid_accept_header(
- e: InvalidAcceptHeader,
-) -> Response:
- response = make_response(e.response_text, e.status_code)
- assert isinstance(response, Response)
- return response
-
-
@CLOUDRECO_FLASK_APP.errorhandler(BadImage)
-def handle_bad_image(
- e: BadImage,
-) -> Response:
- response = make_response(e.response_text, e.status_code)
- assert isinstance(response, Response)
- return response
-
-
-@CLOUDRECO_FLASK_APP.errorhandler(UnknownParameters)
-def handle_unknown_parameters(
- e: UnknownParameters,
-) -> Response:
- response = make_response(e.response_text, e.status_code)
- assert isinstance(response, Response)
- return response
-
-
@CLOUDRECO_FLASK_APP.errorhandler(RequestTimeTooSkewed)
-def handle_request_time_too_skewed(
- e: RequestTimeTooSkewed,
-) -> Response:
- response = make_response(e.response_text, e.status_code)
- assert isinstance(response, Response)
- return response
-
-
@CLOUDRECO_FLASK_APP.errorhandler(ImageNotGiven)
-def handle_image_not_given(
- e: ImageNotGiven,
-) -> Response:
- response = make_response(e.response_text, e.status_code)
- assert isinstance(response, Response)
- return response
-
-
@CLOUDRECO_FLASK_APP.errorhandler(InactiveProject)
-def handle_inactive_project(
- e: InactiveProject,
-) -> Response:
- response = make_response(e.response_text, e.status_code)
- assert isinstance(response, Response)
- return response
-
-
@CLOUDRECO_FLASK_APP.errorhandler(InvalidIncludeTargetData)
-def handle_invalid_include_target_data(
- e: InvalidIncludeTargetData,
-) -> Response:
- response = make_response(e.response_text, e.status_code)
- assert isinstance(response, Response)
- return response
-
-
@CLOUDRECO_FLASK_APP.errorhandler(InvalidMaxNumResults)
-def handle_invalid_max_num_results(
- e: InvalidMaxNumResults,
+@CLOUDRECO_FLASK_APP.errorhandler(UnknownParameters)
+@CLOUDRECO_FLASK_APP.errorhandler(MaxNumResultsOutOfRange)
+def handle_request_time_too_skewed(
+ e: RequestTimeTooSkewed,
) -> Response:
response = make_response(e.response_text, e.status_code)
assert isinstance(response, Response)
return response
-@CLOUDRECO_FLASK_APP.errorhandler(MaxNumResultsOutOfRange)
-def handle_max_num_results_out_of_range(
- e: MaxNumResultsOutOfRange,
+@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
+def handle_content_length_header_too_large(
+ e: ContentLengthHeaderTooLarge,
) -> Response:
response = make_response(e.response_text, e.status_code)
+ response.headers = Headers({'Connection': 'keep-alive'})
assert isinstance(response, Response)
return response
-
@CLOUDRECO_FLASK_APP.errorhandler(NoBoundaryFound)
def handle_no_boundary_found(
e: NoBoundaryFound,
@@ -305,8 +234,8 @@ def set_headers(response: Response) -> Response:
response.headers['Connection'] = 'keep-alive'
response.headers['Server'] = 'nginx'
- content_length = len(response.data)
- response.headers['Content-Length'] = str(content_length)
+ # content_length = len(response.data)
+ # response.headers['Content-Length'] = str(content_length)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
response.headers['Date'] = date
if (
From 0fef867efce2c38db283df0444e4d2c82055a1b8 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 10:50:55 +0100
Subject: [PATCH 0170/3455] Remove use of request.codes
---
src/mock_vws/_flask_server/vwq/__init__.py | 16 ++++++-------
src/mock_vws/_flask_server/vws/__init__.py | 26 ++++++++++------------
2 files changed, 20 insertions(+), 22 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 685605cd8..f29f4ef62 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -2,10 +2,10 @@
import email.utils
from pathlib import Path
from typing import Any, Dict, Tuple, Union
+from http import HTTPStatus
import requests
from flask import Flask, Response, make_response, request
-from requests import codes
from werkzeug.datastructures import Headers
from mock_vws._query_tools import (
@@ -241,11 +241,11 @@ def set_headers(response: Response) -> Response:
if (
response.status_code
in (
- codes.OK,
- codes.UNPROCESSABLE_ENTITY,
- codes.BAD_REQUEST,
- codes.FORBIDDEN,
- codes.UNAUTHORIZED,
+ HTTPStatus.OK,
+ HTTPStatus.UNPROCESSABLE_ENTITY,
+ HTTPStatus.BAD_REQUEST,
+ HTTPStatus.FORBIDDEN,
+ HTTPStatus.UNAUTHORIZED,
)
and 'Content-Type' not in response.headers
):
@@ -296,11 +296,11 @@ def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
# TODO remove file copied to this dir
return (
Path(match_processing_resp_file).read_text(),
- codes.INTERNAL_SERVER_ERROR,
+ HTTPStatus.INTERNAL_SERVER_ERROR,
{
'Cache-Control': cache_control,
'Content-Type': content_type,
},
)
- return (response_text, codes.OK)
+ return (response_text, HTTPStatus.OK)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 3093ef9b7..c5509f316 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -14,8 +14,6 @@
from flask import Flask, Response, make_response, request
import flask
from PIL import Image
-from requests import codes
-
from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_server_keys
from mock_vws._mock_common import json_dump
@@ -186,7 +184,7 @@ def add_target() -> Tuple[str, int]:
'result_code': ResultCodes.TARGET_CREATED.value,
'target_id': new_target.target_id,
}
- return json_dump(body), codes.CREATED
+ return json_dump(body), HTTPStatus.CREATED
@VWS_FLASK_APP.route('/targets/', methods=['GET'])
@@ -227,7 +225,7 @@ def get_target(target_id: str) -> Tuple[str, int]:
'status': target.status,
}
- return json_dump(body), codes.OK
+ return json_dump(body), HTTPStatus.OK
@VWS_FLASK_APP.route('/targets/', methods=['DELETE'])
@@ -258,7 +256,7 @@ def delete_target(target_id: str) -> Tuple[str, int]:
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.TARGET_STATUS_PROCESSING.value,
}
- return json_dump(body), codes.FORBIDDEN
+ return json_dump(body), HTTPStatus.FORBIDDEN
delete_url = (
f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/'
@@ -270,7 +268,7 @@ def delete_target(target_id: str) -> Tuple[str, int]:
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.SUCCESS.value,
}
- return json_dump(body), codes.OK
+ return json_dump(body), HTTPStatus.OK
@VWS_FLASK_APP.route('/summary', methods=['GET'])
@@ -311,7 +309,7 @@ def database_summary() -> Tuple[str, int]:
# This was not always the case.
'request_usage': 0,
}
- return json_dump(body), codes.OK
+ return json_dump(body), HTTPStatus.OK
@VWS_FLASK_APP.route('/summary/', methods=['GET'])
@@ -348,7 +346,7 @@ def target_summary(target_id: str) -> Tuple[str, int]:
'current_month_recos': 0,
'previous_month_recos': 0,
}
- return json_dump(body), codes.OK
+ return json_dump(body), HTTPStatus.OK
@VWS_FLASK_APP.route('/duplicates/', methods=['GET'])
@@ -389,7 +387,7 @@ def get_duplicates(target_id: str) -> Tuple[str, int]:
'similar_targets': similar_targets,
}
- return json_dump(body), codes.OK
+ return json_dump(body), HTTPStatus.OK
@VWS_FLASK_APP.route('/targets', methods=['GET'])
@@ -419,7 +417,7 @@ def target_list() -> Tuple[str, int]:
'result_code': ResultCodes.SUCCESS.value,
'results': results,
}
- return json_dump(body), codes.OK
+ return json_dump(body), HTTPStatus.OK
@VWS_FLASK_APP.route('/targets/', methods=['PUT'])
@@ -453,7 +451,7 @@ def update_target(target_id: str) -> Tuple[str, int]:
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.TARGET_STATUS_NOT_SUCCESS.value,
}
- return json_dump(body), codes.FORBIDDEN
+ return json_dump(body), HTTPStatus.FORBIDDEN
update_values = {}
if 'width' in request_json:
@@ -466,7 +464,7 @@ def update_target(target_id: str) -> Tuple[str, int]:
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.FAIL.value,
}
- return json_dump(body), codes.BAD_REQUEST
+ return json_dump(body), HTTPStatus.BAD_REQUEST
update_values['active_flag'] = active_flag
if 'application_metadata' in request_json:
@@ -475,7 +473,7 @@ def update_target(target_id: str) -> Tuple[str, int]:
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.FAIL.value,
}
- return json_dump(body), codes.BAD_REQUEST
+ return json_dump(body), HTTPStatus.BAD_REQUEST
application_metadata = request_json['application_metadata']
update_values['application_metadata'] = application_metadata
@@ -497,4 +495,4 @@ def update_target(target_id: str) -> Tuple[str, int]:
'result_code': ResultCodes.SUCCESS.value,
'transaction_id': uuid.uuid4().hex,
}
- return json_dump(body), codes.OK
+ return json_dump(body), HTTPStatus.OK
From 539eff5cb4d93255c45520f684bedbdf37968b59 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 10:55:16 +0100
Subject: [PATCH 0171/3455] Remove some commented out / junk code
---
src/mock_vws/_flask_server/vwq/__init__.py | 23 +++++++---------------
1 file changed, 7 insertions(+), 16 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index f29f4ef62..38f5a5450 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -225,7 +225,6 @@ def handle_malformed_auth_header(
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
- # raise requests.exceptions.ConnectionError
if response.headers == {'Connection': 'keep-alive'}:
return response
@@ -234,8 +233,6 @@ def set_headers(response: Response) -> Response:
response.headers['Connection'] = 'keep-alive'
response.headers['Server'] = 'nginx'
- # content_length = len(response.data)
- # response.headers['Content-Length'] = str(content_length)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
response.headers['Date'] = date
if (
@@ -288,19 +285,13 @@ def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
filename = 'match_processing_response.html'
match_processing_resp_file = resources_dir / filename
cache_control = 'must-revalidate,no-cache,no-store'
- # TODO remove legacy
- # context.headers['Cache-Control'] = cache_control
content_type = 'text/html; charset=ISO-8859-1'
- # TODO remove legacy
- # context.headers['Content-Type'] = content_type
- # TODO remove file copied to this dir
- return (
- Path(match_processing_resp_file).read_text(),
- HTTPStatus.INTERNAL_SERVER_ERROR,
- {
- 'Cache-Control': cache_control,
- 'Content-Type': content_type,
- },
- )
+ headers = {
+ 'Cache-Control': cache_control,
+ 'Content-Type': content_type,
+ }
+ response_text = match_processing_resp_file.read_text()
+ return (response_text, HTTPStatus.INTERNAL_SERVER_ERROR, headers)
+
return (response_text, HTTPStatus.OK)
From 6e17827a45d79716192cc49d11f85acd99ea8ec7 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:00:45 +0100
Subject: [PATCH 0172/3455] Fix some mypy issues
---
src/mock_vws/_flask_server/vwq/__init__.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 38f5a5450..a3ef95beb 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -160,7 +160,7 @@ def handle_query_out_of_bounds(
return response
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
-def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge):
+def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge) -> Response:
new_response = Response()
new_response.status_code = e.status_code
new_response.set_data(e.response_text)
@@ -168,7 +168,7 @@ def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge):
return new_response
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
-def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt):
+def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Response:
new_response = Response()
new_response.status_code = e.status_code
new_response.set_data(e.response_text)
From 1bf5ef840f4575fcc39e0a404881bfeeeb7e1fdd Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:04:43 +0100
Subject: [PATCH 0173/3455] Fix some mypy issues
---
src/mock_vws/_flask_server/vwq/__init__.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index a3ef95beb..4c9e2f2c3 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -164,7 +164,7 @@ def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge) -> Re
new_response = Response()
new_response.status_code = e.status_code
new_response.set_data(e.response_text)
- new_response.headers = {'Connection': 'keep-alive'}
+ new_response.headers = Headers({'Connection': 'keep-alive'})
return new_response
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
@@ -172,7 +172,7 @@ def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Respon
new_response = Response()
new_response.status_code = e.status_code
new_response.set_data(e.response_text)
- new_response.headers = {'Connection': 'Close'}
+ new_response.headers = Headers({'Connection': 'Close'})
return new_response
@@ -225,10 +225,10 @@ def handle_malformed_auth_header(
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
- if response.headers == {'Connection': 'keep-alive'}:
+ if dict(response.headers) == {'Connection': 'keep-alive'}:
return response
- if response.headers == {'Connection': 'Close'}:
+ if dict(response.headers) == {'Connection': 'Close'}:
return response
response.headers['Connection'] = 'keep-alive'
From a5fd907b05503f6bb6423fac20057b47b1da6f61 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:05:03 +0100
Subject: [PATCH 0174/3455] Fix some mypy issues
---
src/mock_vws/_flask_server/vwq/__init__.py | 9 ---------
1 file changed, 9 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 4c9e2f2c3..bdbfd3d2f 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -96,15 +96,6 @@ def handle_request_time_too_skewed(
return response
-@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
-def handle_content_length_header_too_large(
- e: ContentLengthHeaderTooLarge,
-) -> Response:
- response = make_response(e.response_text, e.status_code)
- response.headers = Headers({'Connection': 'keep-alive'})
- assert isinstance(response, Response)
- return response
-
@CLOUDRECO_FLASK_APP.errorhandler(NoBoundaryFound)
def handle_no_boundary_found(
e: NoBoundaryFound,
From f2bbfd5223d3e735be66258b90f5d18e1efb5c90 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:15:22 +0100
Subject: [PATCH 0175/3455] Fix some mypy issues
---
src/mock_vws/_flask_server/vws/__init__.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index c5509f316..d5741fd3d 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -35,6 +35,7 @@
)
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
+from werkzeug.datastructures import Headers
from ._constants import STORAGE_BASE_URL
from ._databases import get_all_databases
@@ -79,7 +80,7 @@ def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge):
new_response = Response()
new_response.status_code = e.status_code
new_response.set_data(e.response_text)
- new_response.headers = {'Connection': 'keep-alive'}
+ new_response.headers = Headers({'Connection': 'keep-alive'})
return new_response
@VWS_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
@@ -87,7 +88,7 @@ def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt):
new_response = Response()
new_response.status_code = e.status_code
new_response.set_data(e.response_text)
- new_response.headers = {'Connection': 'Close'}
+ new_response.headers = Headers({'Connection': 'Close'})
return new_response
@VWS_FLASK_APP.errorhandler(UnnecessaryRequestBody)
@@ -115,10 +116,10 @@ def set_headers(response: Response) -> Response:
"""
TODO
"""
- if response.headers == {'Connection': 'keep-alive'}:
+ if dict(response.headers) == {'Connection': 'keep-alive'}:
return response
- if response.headers == {'Connection': 'Close'}:
+ if dict(response.headers) == {'Connection': 'Close'}:
return response
response.headers['Connection'] = 'keep-alive'
From 9cde1faacf011a78a8dde11a8ec1864cc6495329 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:16:01 +0100
Subject: [PATCH 0176/3455] Fix some mypy issues
---
src/mock_vws/_flask_server/vws/__init__.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index d5741fd3d..f3746eb22 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -76,7 +76,7 @@ def handle_unknown_target(e: UnknownTarget) -> Tuple[str, int]:
@VWS_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
-def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge):
+def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge) -> Response:
new_response = Response()
new_response.status_code = e.status_code
new_response.set_data(e.response_text)
@@ -84,7 +84,7 @@ def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge):
return new_response
@VWS_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
-def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt):
+def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Response:
new_response = Response()
new_response.status_code = e.status_code
new_response.set_data(e.response_text)
From 46ba5bc62a9610a72f52ee21e2cfd424c7e1a553 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:16:30 +0100
Subject: [PATCH 0177/3455] Improve variable names
---
src/mock_vws/_flask_server/vws/__init__.py | 30 +++++++++++-----------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index f3746eb22..50b11e9e3 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -77,29 +77,29 @@ def handle_unknown_target(e: UnknownTarget) -> Tuple[str, int]:
@VWS_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge) -> Response:
- new_response = Response()
- new_response.status_code = e.status_code
- new_response.set_data(e.response_text)
- new_response.headers = Headers({'Connection': 'keep-alive'})
- return new_response
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
+ response.headers = Headers({'Connection': 'keep-alive'})
+ return response
@VWS_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Response:
- new_response = Response()
- new_response.status_code = e.status_code
- new_response.set_data(e.response_text)
- new_response.headers = Headers({'Connection': 'Close'})
- return new_response
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
+ response.headers = Headers({'Connection': 'Close'})
+ return response
@VWS_FLASK_APP.errorhandler(UnnecessaryRequestBody)
def handle_unnecessary_request_body(
e: UnnecessaryRequestBody,
) -> Tuple[str, int]:
- new_response = Response()
- new_response.status_code = e.status_code
- new_response.set_data(e.response_text)
- new_response.headers.pop('Content-Type')
- return new_response
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
+ response.headers.pop('Content-Type')
+ return response
@VWS_FLASK_APP.errorhandler(OopsErrorOccurredResponse)
From aafb5078ac10d18f87bd765b113f4b0b8685626c Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:22:06 +0100
Subject: [PATCH 0178/3455] Start removing make_response
---
src/mock_vws/_flask_server/vwq/__init__.py | 20 ++++++++++----------
src/mock_vws/_flask_server/vws/__init__.py | 5 +++--
2 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index bdbfd3d2f..d47d26bc2 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -152,19 +152,19 @@ def handle_query_out_of_bounds(
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge) -> Response:
- new_response = Response()
- new_response.status_code = e.status_code
- new_response.set_data(e.response_text)
- new_response.headers = Headers({'Connection': 'keep-alive'})
- return new_response
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
+ response.headers = Headers({'Connection': 'keep-alive'})
+ return response
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Response:
- new_response = Response()
- new_response.status_code = e.status_code
- new_response.set_data(e.response_text)
- new_response.headers = Headers({'Connection': 'Close'})
- return new_response
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
+ response.headers = Headers({'Connection': 'Close'})
+ return response
@CLOUDRECO_FLASK_APP.errorhandler(AuthHeaderMissing)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 50b11e9e3..3a35dcedb 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -104,10 +104,11 @@ def handle_unnecessary_request_body(
@VWS_FLASK_APP.errorhandler(OopsErrorOccurredResponse)
def handle_oops_error_occurred(e: OopsErrorOccurredResponse) -> Response:
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
content_type = 'text/html; charset=UTF-8'
- response = make_response(e.response_text, e.status_code)
response.headers['Content-Type'] = content_type
- assert isinstance(response, Response)
return response
From d8a923b6b765b717109190516c73cb18d6407cfd Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:25:00 +0100
Subject: [PATCH 0179/3455] Remove some uses of make_response
---
src/mock_vws/_flask_server/vwq/__init__.py | 52 +++++++++++++---------
src/mock_vws/_flask_server/vws/__init__.py | 4 +-
2 files changed, 33 insertions(+), 23 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index d47d26bc2..559cc5f0a 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -5,7 +5,7 @@
from http import HTTPStatus
import requests
-from flask import Flask, Response, make_response, request
+from flask import Flask, Response, request
from werkzeug.datastructures import Headers
from mock_vws._query_tools import (
@@ -91,8 +91,9 @@ def handle_connection_error(
def handle_request_time_too_skewed(
e: RequestTimeTooSkewed,
) -> Response:
- response = make_response(e.response_text, e.status_code)
- assert isinstance(response, Response)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
return response
@@ -101,9 +102,10 @@ def handle_no_boundary_found(
e: NoBoundaryFound,
) -> Response:
content_type = 'text/html;charset=UTF-8'
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
response.headers['Content-Type'] = content_type
- assert isinstance(response, Response)
return response
@@ -112,9 +114,10 @@ def handle_boundary_not_in_body(
e: BoundaryNotInBody,
) -> Response:
content_type = 'text/html;charset=UTF-8'
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
response.headers['Content-Type'] = content_type
- assert isinstance(response, Response)
return response
@@ -122,9 +125,10 @@ def handle_boundary_not_in_body(
def handle_authentication_failure(
e: AuthenticationFailure,
) -> Response:
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
response.headers['WWW-Authenticate'] = 'VWS'
- assert isinstance(response, Response)
return response
@@ -132,9 +136,10 @@ def handle_authentication_failure(
def handle_authentication_failure_good_formatting(
e: AuthenticationFailureGoodFormatting,
) -> Response:
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
response.headers['WWW-Authenticate'] = 'VWS'
- assert isinstance(response, Response)
return response
@@ -142,12 +147,13 @@ def handle_authentication_failure_good_formatting(
def handle_query_out_of_bounds(
e: QueryOutOfBounds,
) -> Response:
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
content_type = 'text/html; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
cache_control = 'must-revalidate,no-cache,no-store'
response.headers['Cache-Control'] = cache_control
- assert isinstance(response, Response)
return response
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
@@ -171,11 +177,12 @@ def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Respon
def handle_auth_header_missing(
e: AuthHeaderMissing,
) -> Response:
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
response.headers['WWW-Authenticate'] = 'VWS'
- assert isinstance(response, Response)
return response
@@ -183,11 +190,12 @@ def handle_auth_header_missing(
def handle_date_format_not_valid(
e: DateFormatNotValid,
) -> Response:
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
response.headers['WWW-Authenticate'] = 'VWS'
- assert isinstance(response, Response)
return response
@@ -195,10 +203,11 @@ def handle_date_format_not_valid(
def handle_date_header_not_given(
e: DateFormatNotValid,
) -> Response:
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
- assert isinstance(response, Response)
return response
@@ -206,11 +215,12 @@ def handle_date_header_not_given(
def handle_malformed_auth_header(
e: MalformedAuthHeader,
) -> Response:
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
response.headers['WWW-Authenticate'] = 'VWS'
- assert isinstance(response, Response)
return response
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 3a35dcedb..dc7ef50cb 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -11,7 +11,7 @@
from typing import Dict, List, Tuple, Union
import requests
-from flask import Flask, Response, make_response, request
+from flask import Flask, Response, request
import flask
from PIL import Image
from mock_vws._constants import ResultCodes, TargetStatuses
@@ -94,7 +94,7 @@ def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Respon
@VWS_FLASK_APP.errorhandler(UnnecessaryRequestBody)
def handle_unnecessary_request_body(
e: UnnecessaryRequestBody,
-) -> Tuple[str, int]:
+) -> Response:
response = Response()
response.status_code = e.status_code
response.set_data(e.response_text)
From e2a8c8253b4e2ffc307b6925615a11f70816210d Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:27:55 +0100
Subject: [PATCH 0180/3455] Revert "Remove some uses of make_response"
This reverts commit d8a923b6b765b717109190516c73cb18d6407cfd.
---
src/mock_vws/_flask_server/vwq/__init__.py | 52 +++++++++-------------
src/mock_vws/_flask_server/vws/__init__.py | 4 +-
2 files changed, 23 insertions(+), 33 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 559cc5f0a..d47d26bc2 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -5,7 +5,7 @@
from http import HTTPStatus
import requests
-from flask import Flask, Response, request
+from flask import Flask, Response, make_response, request
from werkzeug.datastructures import Headers
from mock_vws._query_tools import (
@@ -91,9 +91,8 @@ def handle_connection_error(
def handle_request_time_too_skewed(
e: RequestTimeTooSkewed,
) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
+ response = make_response(e.response_text, e.status_code)
+ assert isinstance(response, Response)
return response
@@ -102,10 +101,9 @@ def handle_no_boundary_found(
e: NoBoundaryFound,
) -> Response:
content_type = 'text/html;charset=UTF-8'
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
+ response = make_response(e.response_text, e.status_code)
response.headers['Content-Type'] = content_type
+ assert isinstance(response, Response)
return response
@@ -114,10 +112,9 @@ def handle_boundary_not_in_body(
e: BoundaryNotInBody,
) -> Response:
content_type = 'text/html;charset=UTF-8'
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
+ response = make_response(e.response_text, e.status_code)
response.headers['Content-Type'] = content_type
+ assert isinstance(response, Response)
return response
@@ -125,10 +122,9 @@ def handle_boundary_not_in_body(
def handle_authentication_failure(
e: AuthenticationFailure,
) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
+ response = make_response(e.response_text, e.status_code)
response.headers['WWW-Authenticate'] = 'VWS'
+ assert isinstance(response, Response)
return response
@@ -136,10 +132,9 @@ def handle_authentication_failure(
def handle_authentication_failure_good_formatting(
e: AuthenticationFailureGoodFormatting,
) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
+ response = make_response(e.response_text, e.status_code)
response.headers['WWW-Authenticate'] = 'VWS'
+ assert isinstance(response, Response)
return response
@@ -147,13 +142,12 @@ def handle_authentication_failure_good_formatting(
def handle_query_out_of_bounds(
e: QueryOutOfBounds,
) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
+ response = make_response(e.response_text, e.status_code)
content_type = 'text/html; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
cache_control = 'must-revalidate,no-cache,no-store'
response.headers['Cache-Control'] = cache_control
+ assert isinstance(response, Response)
return response
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
@@ -177,12 +171,11 @@ def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Respon
def handle_auth_header_missing(
e: AuthHeaderMissing,
) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
+ response = make_response(e.response_text, e.status_code)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
response.headers['WWW-Authenticate'] = 'VWS'
+ assert isinstance(response, Response)
return response
@@ -190,12 +183,11 @@ def handle_auth_header_missing(
def handle_date_format_not_valid(
e: DateFormatNotValid,
) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
+ response = make_response(e.response_text, e.status_code)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
response.headers['WWW-Authenticate'] = 'VWS'
+ assert isinstance(response, Response)
return response
@@ -203,11 +195,10 @@ def handle_date_format_not_valid(
def handle_date_header_not_given(
e: DateFormatNotValid,
) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
+ response = make_response(e.response_text, e.status_code)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
+ assert isinstance(response, Response)
return response
@@ -215,12 +206,11 @@ def handle_date_header_not_given(
def handle_malformed_auth_header(
e: MalformedAuthHeader,
) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
+ response = make_response(e.response_text, e.status_code)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
response.headers['WWW-Authenticate'] = 'VWS'
+ assert isinstance(response, Response)
return response
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index dc7ef50cb..3a35dcedb 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -11,7 +11,7 @@
from typing import Dict, List, Tuple, Union
import requests
-from flask import Flask, Response, request
+from flask import Flask, Response, make_response, request
import flask
from PIL import Image
from mock_vws._constants import ResultCodes, TargetStatuses
@@ -94,7 +94,7 @@ def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Respon
@VWS_FLASK_APP.errorhandler(UnnecessaryRequestBody)
def handle_unnecessary_request_body(
e: UnnecessaryRequestBody,
-) -> Response:
+) -> Tuple[str, int]:
response = Response()
response.status_code = e.status_code
response.set_data(e.response_text)
From d55eb36ec4f12a1fdb7e1998842ae3d24ac9c901 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:28:23 +0100
Subject: [PATCH 0181/3455] Start removing make_response
---
src/mock_vws/_flask_server/vws/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 3a35dcedb..5f2f96b84 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -94,7 +94,7 @@ def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Respon
@VWS_FLASK_APP.errorhandler(UnnecessaryRequestBody)
def handle_unnecessary_request_body(
e: UnnecessaryRequestBody,
-) -> Tuple[str, int]:
+) -> Response:
response = Response()
response.status_code = e.status_code
response.set_data(e.response_text)
From 9209abd048b1493af57925133962fd4dc7024d58 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:28:45 +0100
Subject: [PATCH 0182/3455] Start removing make_response
---
src/mock_vws/_flask_server/vws/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 5f2f96b84..dc7ef50cb 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -11,7 +11,7 @@
from typing import Dict, List, Tuple, Union
import requests
-from flask import Flask, Response, make_response, request
+from flask import Flask, Response, request
import flask
from PIL import Image
from mock_vws._constants import ResultCodes, TargetStatuses
From ba365166e49caedaa680e28533c910ea8fef19fe Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:38:56 +0100
Subject: [PATCH 0183/3455] One less make_response
---
src/mock_vws/_flask_server/vwq/__init__.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index d47d26bc2..41e1b6fe2 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -171,7 +171,9 @@ def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Respon
def handle_auth_header_missing(
e: AuthHeaderMissing,
) -> Response:
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
response.headers['WWW-Authenticate'] = 'VWS'
From bab0866d75ad0b7467cf6c64be664ee7cff71cb1 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:40:47 +0100
Subject: [PATCH 0184/3455] One less make_response
---
src/mock_vws/_flask_server/vwq/__init__.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 41e1b6fe2..692b564af 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -177,7 +177,6 @@ def handle_auth_header_missing(
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
response.headers['WWW-Authenticate'] = 'VWS'
- assert isinstance(response, Response)
return response
@@ -185,7 +184,9 @@ def handle_auth_header_missing(
def handle_date_format_not_valid(
e: DateFormatNotValid,
) -> Response:
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
response.headers['WWW-Authenticate'] = 'VWS'
From ef3d6091947c0b6b37d5f7be558b33875535b724 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:42:48 +0100
Subject: [PATCH 0185/3455] One less make_response
---
src/mock_vws/_flask_server/vwq/__init__.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 692b564af..1df1bfbfe 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -190,7 +190,6 @@ def handle_date_format_not_valid(
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
response.headers['WWW-Authenticate'] = 'VWS'
- assert isinstance(response, Response)
return response
@@ -198,10 +197,11 @@ def handle_date_format_not_valid(
def handle_date_header_not_given(
e: DateFormatNotValid,
) -> Response:
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
- assert isinstance(response, Response)
return response
From c6c5bf7d1f84f29677a32fb0e9d428faa59a1c52 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:46:50 +0100
Subject: [PATCH 0186/3455] One less make_response
---
src/mock_vws/_flask_server/vwq/__init__.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 1df1bfbfe..0ac7f9eba 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -101,9 +101,10 @@ def handle_no_boundary_found(
e: NoBoundaryFound,
) -> Response:
content_type = 'text/html;charset=UTF-8'
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
response.headers['Content-Type'] = content_type
- assert isinstance(response, Response)
return response
@@ -112,9 +113,10 @@ def handle_boundary_not_in_body(
e: BoundaryNotInBody,
) -> Response:
content_type = 'text/html;charset=UTF-8'
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
response.headers['Content-Type'] = content_type
- assert isinstance(response, Response)
return response
From e6592040aa9803dcab7d7af593f24b0dfd843452 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 11:49:12 +0100
Subject: [PATCH 0187/3455] One less make_response
---
src/mock_vws/_flask_server/vwq/__init__.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 0ac7f9eba..a25f21738 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -211,11 +211,12 @@ def handle_date_header_not_given(
def handle_malformed_auth_header(
e: MalformedAuthHeader,
) -> Response:
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
content_type = 'text/plain; charset=ISO-8859-1'
response.headers['Content-Type'] = content_type
response.headers['WWW-Authenticate'] = 'VWS'
- assert isinstance(response, Response)
return response
From 212546cd99319299688f55c18d71b986f04f6078 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 12:18:40 +0100
Subject: [PATCH 0188/3455] One less make_response
---
src/mock_vws/_flask_server/vwq/__init__.py | 15 ++++++++++---
src/mock_vws/_query_validators/exceptions.py | 22 ++++++++++++++++++++
2 files changed, 34 insertions(+), 3 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index a25f21738..c4e6b3880 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -91,8 +91,14 @@ def handle_connection_error(
def handle_request_time_too_skewed(
e: RequestTimeTooSkewed,
) -> Response:
- response = make_response(e.response_text, e.status_code)
- assert isinstance(response, Response)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
+ print(type(e))
+ if e.content_type is None:
+ response.headers.pop('Content-Type')
+ else:
+ response.content_type = e.content_type
return response
@@ -124,7 +130,10 @@ def handle_boundary_not_in_body(
def handle_authentication_failure(
e: AuthenticationFailure,
) -> Response:
- response = make_response(e.response_text, e.status_code)
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
+ response.content_type = e.content_type
response.headers['WWW-Authenticate'] = 'VWS'
assert isinstance(response, Response)
return response
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index a4cf34c5c..9ed303368 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -52,6 +52,8 @@ class RequestTimeTooSkewed(Exception):
'RequestTimeTooSkewed'.
"""
+ content_type = 'application/json'
+
def __init__(self) -> None:
"""
Attributes:
@@ -75,6 +77,8 @@ class BadImage(Exception):
'BadImage'.
"""
+ content_type = 'application/json'
+
def __init__(self) -> None:
"""
Attributes:
@@ -104,6 +108,8 @@ class AuthenticationFailure(Exception):
'AuthenticationFailure'.
"""
+ content_type = 'application/json'
+
def __init__(self) -> None:
"""
Attributes:
@@ -156,6 +162,8 @@ class ImageNotGiven(Exception):
Exception raised when an image is not given.
"""
+ content_type = 'application/json'
+
def __init__(self) -> None:
"""
Attributes:
@@ -210,6 +218,8 @@ class UnknownParameters(Exception):
Exception raised when unknown parameters are given.
"""
+ content_type = 'application/json'
+
def __init__(self) -> None:
"""
Attributes:
@@ -229,6 +239,8 @@ class InactiveProject(Exception):
'InactiveProject'.
"""
+ content_type = 'application/json'
+
def __init__(self) -> None:
"""
Attributes:
@@ -257,6 +269,8 @@ class InvalidMaxNumResults(Exception):
"max_num_results" field.
"""
+ content_type = 'application/json'
+
def __init__(self, given_value: str) -> None:
"""
Attributes:
@@ -280,6 +294,8 @@ class MaxNumResultsOutOfRange(Exception):
field which is out of range.
"""
+ content_type = 'application/json'
+
def __init__(self, given_value: str) -> None:
"""
Attributes:
@@ -303,6 +319,8 @@ class InvalidIncludeTargetData(Exception):
"include_target_data" field.
"""
+ content_type = 'application/json'
+
def __init__(self, given_value: str) -> None:
"""
Attributes:
@@ -327,6 +345,8 @@ class UnsupportedMediaType(Exception):
Exception raised when no boundary is found for multipart data.
"""
+ content_type = None
+
def __init__(self) -> None:
"""
Attributes:
@@ -345,6 +365,8 @@ class InvalidAcceptHeader(Exception):
Exception raised when there is an invalid accept header given.
"""
+ content_type = None
+
def __init__(self) -> None:
"""
Attributes:
From 011df6b4b4ed34e48c2201935895886a3bd4b063 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 12:21:45 +0100
Subject: [PATCH 0189/3455] One less make_response
---
src/mock_vws/_flask_server/vwq/__init__.py | 13 +++----------
src/mock_vws/_query_validators/exceptions.py | 2 ++
2 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index c4e6b3880..827c16130 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -114,6 +114,7 @@ def handle_no_boundary_found(
return response
+# TODO merge with main handler?
@CLOUDRECO_FLASK_APP.errorhandler(BoundaryNotInBody)
def handle_boundary_not_in_body(
e: BoundaryNotInBody,
@@ -127,7 +128,9 @@ def handle_boundary_not_in_body(
@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailure)
+@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailureGoodFormatting)
def handle_authentication_failure(
+ # TODO Update type hint but maybe merge with main handler?
e: AuthenticationFailure,
) -> Response:
response = Response()
@@ -135,19 +138,9 @@ def handle_authentication_failure(
response.set_data(e.response_text)
response.content_type = e.content_type
response.headers['WWW-Authenticate'] = 'VWS'
- assert isinstance(response, Response)
return response
-@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailureGoodFormatting)
-def handle_authentication_failure_good_formatting(
- e: AuthenticationFailureGoodFormatting,
-) -> Response:
- response = make_response(e.response_text, e.status_code)
- response.headers['WWW-Authenticate'] = 'VWS'
- assert isinstance(response, Response)
- return response
-
@CLOUDRECO_FLASK_APP.errorhandler(QueryOutOfBounds)
def handle_query_out_of_bounds(
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index 9ed303368..4cb74ca33 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -139,6 +139,8 @@ class AuthenticationFailureGoodFormatting(Exception):
'AuthenticationFailure' with a standard JSON formatting.
"""
+ content_type = 'application/json'
+
def __init__(self) -> None:
"""
Attributes:
From 4ce6db5dccf7aa7cb05f346b688f54e26883eda8 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 12:26:22 +0100
Subject: [PATCH 0190/3455] Remove last make_response
---
src/mock_vws/_flask_server/vwq/__init__.py | 7 ++++---
src/mock_vws/_query_validators/exceptions.py | 2 ++
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 827c16130..bd50761b4 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -146,9 +146,10 @@ def handle_authentication_failure(
def handle_query_out_of_bounds(
e: QueryOutOfBounds,
) -> Response:
- response = make_response(e.response_text, e.status_code)
- content_type = 'text/html; charset=ISO-8859-1'
- response.headers['Content-Type'] = content_type
+ response = Response()
+ response.status_code = e.status_code
+ response.set_data(e.response_text)
+ response.content_type = e.content_type
cache_control = 'must-revalidate,no-cache,no-store'
response.headers['Cache-Control'] = cache_control
assert isinstance(response, Response)
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index 4cb74ca33..d592d57c2 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -430,6 +430,8 @@ class QueryOutOfBounds(Exception):
particular out of bounds error.
"""
+ content_type = 'text/html; charset=ISO-8859-1'
+
def __init__(self) -> None:
"""
Attributes:
From 9c2cafbd77da7ac28fb5345f5aea63e635eb5ed9 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 13:30:17 +0100
Subject: [PATCH 0191/3455] Remove useless assertion
---
src/mock_vws/_flask_server/vwq/__init__.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index bd50761b4..fc64c59a2 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -152,7 +152,6 @@ def handle_query_out_of_bounds(
response.content_type = e.content_type
cache_control = 'must-revalidate,no-cache,no-store'
response.headers['Cache-Control'] = cache_control
- assert isinstance(response, Response)
return response
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
From 9978e1a09b5783d477d94c4334e5b31812b896e5 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 13:38:46 +0100
Subject: [PATCH 0192/3455] Combine more error handling
---
src/mock_vws/_flask_server/vwq/__init__.py | 27 ++------------------
src/mock_vws/_query_validators/exceptions.py | 4 +++
2 files changed, 6 insertions(+), 25 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index fc64c59a2..cc8a9519d 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -65,8 +65,6 @@ class MyResponse(Response):
CLOUDRECO_FLASK_APP.response_class = MyResponse
-
-
@CLOUDRECO_FLASK_APP.errorhandler(requests.exceptions.ConnectionError)
def handle_connection_error(
e: requests.exceptions.ConnectionError,
@@ -88,6 +86,8 @@ def handle_connection_error(
@CLOUDRECO_FLASK_APP.errorhandler(InvalidMaxNumResults)
@CLOUDRECO_FLASK_APP.errorhandler(UnknownParameters)
@CLOUDRECO_FLASK_APP.errorhandler(MaxNumResultsOutOfRange)
+@CLOUDRECO_FLASK_APP.errorhandler(NoBoundaryFound)
+@CLOUDRECO_FLASK_APP.errorhandler(BoundaryNotInBody)
def handle_request_time_too_skewed(
e: RequestTimeTooSkewed,
) -> Response:
@@ -102,29 +102,6 @@ def handle_request_time_too_skewed(
return response
-@CLOUDRECO_FLASK_APP.errorhandler(NoBoundaryFound)
-def handle_no_boundary_found(
- e: NoBoundaryFound,
-) -> Response:
- content_type = 'text/html;charset=UTF-8'
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- response.headers['Content-Type'] = content_type
- return response
-
-
-# TODO merge with main handler?
-@CLOUDRECO_FLASK_APP.errorhandler(BoundaryNotInBody)
-def handle_boundary_not_in_body(
- e: BoundaryNotInBody,
-) -> Response:
- content_type = 'text/html;charset=UTF-8'
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- response.headers['Content-Type'] = content_type
- return response
@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailure)
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index d592d57c2..0a19a9939 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -387,6 +387,8 @@ class BoundaryNotInBody(Exception):
Exception raised when the form boundary is not in the request body.
"""
+ content_type = 'text/html;charset=UTF-8'
+
def __init__(self) -> None:
"""
Attributes:
@@ -408,6 +410,8 @@ class NoBoundaryFound(Exception):
Exception raised when an invalid media type is given.
"""
+ content_type = 'text/html;charset=UTF-8'
+
def __init__(self) -> None:
"""
Attributes:
From 526887dbe1852dac3276f758d46d1a103edabd04 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 13:48:52 +0100
Subject: [PATCH 0193/3455] More consolidation of error handling
---
src/mock_vws/_flask_server/vwq/__init__.py | 59 +++-----------------
src/mock_vws/_query_validators/exceptions.py | 27 +++++++++
2 files changed, 34 insertions(+), 52 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index cc8a9519d..a27db84be 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -88,17 +88,23 @@ def handle_connection_error(
@CLOUDRECO_FLASK_APP.errorhandler(MaxNumResultsOutOfRange)
@CLOUDRECO_FLASK_APP.errorhandler(NoBoundaryFound)
@CLOUDRECO_FLASK_APP.errorhandler(BoundaryNotInBody)
+@CLOUDRECO_FLASK_APP.errorhandler(DateFormatNotValid)
+@CLOUDRECO_FLASK_APP.errorhandler(AuthHeaderMissing)
+@CLOUDRECO_FLASK_APP.errorhandler(DateHeaderNotGiven)
+@CLOUDRECO_FLASK_APP.errorhandler(MalformedAuthHeader)
def handle_request_time_too_skewed(
e: RequestTimeTooSkewed,
) -> Response:
response = Response()
response.status_code = e.status_code
response.set_data(e.response_text)
- print(type(e))
if e.content_type is None:
response.headers.pop('Content-Type')
else:
response.content_type = e.content_type
+
+ if e.www_authenticate:
+ response.headers['WWW-Authenticate'] = e.www_authenticate
return response
@@ -148,57 +154,6 @@ def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Respon
return response
-@CLOUDRECO_FLASK_APP.errorhandler(AuthHeaderMissing)
-def handle_auth_header_missing(
- e: AuthHeaderMissing,
-) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- content_type = 'text/plain; charset=ISO-8859-1'
- response.headers['Content-Type'] = content_type
- response.headers['WWW-Authenticate'] = 'VWS'
- return response
-
-
-@CLOUDRECO_FLASK_APP.errorhandler(DateFormatNotValid)
-def handle_date_format_not_valid(
- e: DateFormatNotValid,
-) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- content_type = 'text/plain; charset=ISO-8859-1'
- response.headers['Content-Type'] = content_type
- response.headers['WWW-Authenticate'] = 'VWS'
- return response
-
-
-@CLOUDRECO_FLASK_APP.errorhandler(DateHeaderNotGiven)
-def handle_date_header_not_given(
- e: DateFormatNotValid,
-) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- content_type = 'text/plain; charset=ISO-8859-1'
- response.headers['Content-Type'] = content_type
- return response
-
-
-@CLOUDRECO_FLASK_APP.errorhandler(MalformedAuthHeader)
-def handle_malformed_auth_header(
- e: MalformedAuthHeader,
-) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- content_type = 'text/plain; charset=ISO-8859-1'
- response.headers['Content-Type'] = content_type
- response.headers['WWW-Authenticate'] = 'VWS'
- return response
-
-
@CLOUDRECO_FLASK_APP.after_request
def set_headers(response: Response) -> Response:
if dict(response.headers) == {'Connection': 'keep-alive'}:
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index 0a19a9939..cae87c1a7 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -15,6 +15,9 @@ class DateHeaderNotGiven(Exception):
Exception raised when a date header is not given.
"""
+ content_type = 'text/plain; charset=ISO-8859-1'
+ www_authenticate = None
+
def __init__(self) -> None:
"""
Attributes:
@@ -33,6 +36,9 @@ class DateFormatNotValid(Exception):
Exception raised when the date format is not valid.
"""
+ www_authenticate = 'VWS'
+ content_type = 'text/plain; charset=ISO-8859-1'
+
def __init__(self) -> None:
"""
Attributes:
@@ -53,6 +59,7 @@ class RequestTimeTooSkewed(Exception):
"""
content_type = 'application/json'
+ www_authenticate = None
def __init__(self) -> None:
"""
@@ -78,6 +85,7 @@ class BadImage(Exception):
"""
content_type = 'application/json'
+ www_authenticate = None
def __init__(self) -> None:
"""
@@ -109,6 +117,7 @@ class AuthenticationFailure(Exception):
"""
content_type = 'application/json'
+ www_authenticate = None
def __init__(self) -> None:
"""
@@ -140,6 +149,7 @@ class AuthenticationFailureGoodFormatting(Exception):
"""
content_type = 'application/json'
+ www_authenticate = None
def __init__(self) -> None:
"""
@@ -165,6 +175,7 @@ class ImageNotGiven(Exception):
"""
content_type = 'application/json'
+ www_authenticate = None
def __init__(self) -> None:
"""
@@ -184,6 +195,9 @@ class AuthHeaderMissing(Exception):
Exception raised when an auth header is not given.
"""
+ content_type = 'text/plain; charset=ISO-8859-1'
+ www_authenticate = 'VWS'
+
def __init__(self) -> None:
"""
Attributes:
@@ -202,6 +216,9 @@ class MalformedAuthHeader(Exception):
Exception raised when an auth header is not given.
"""
+ content_type = 'text/plain; charset=ISO-8859-1'
+ www_authenticate = 'VWS'
+
def __init__(self) -> None:
"""
Attributes:
@@ -221,6 +238,7 @@ class UnknownParameters(Exception):
"""
content_type = 'application/json'
+ www_authenticate = None
def __init__(self) -> None:
"""
@@ -242,6 +260,7 @@ class InactiveProject(Exception):
"""
content_type = 'application/json'
+ www_authenticate = None
def __init__(self) -> None:
"""
@@ -272,6 +291,7 @@ class InvalidMaxNumResults(Exception):
"""
content_type = 'application/json'
+ www_authenticate = None
def __init__(self, given_value: str) -> None:
"""
@@ -297,6 +317,7 @@ class MaxNumResultsOutOfRange(Exception):
"""
content_type = 'application/json'
+ www_authenticate = None
def __init__(self, given_value: str) -> None:
"""
@@ -322,6 +343,7 @@ class InvalidIncludeTargetData(Exception):
"""
content_type = 'application/json'
+ www_authenticate = None
def __init__(self, given_value: str) -> None:
"""
@@ -348,6 +370,7 @@ class UnsupportedMediaType(Exception):
"""
content_type = None
+ www_authenticate = None
def __init__(self) -> None:
"""
@@ -368,6 +391,7 @@ class InvalidAcceptHeader(Exception):
"""
content_type = None
+ www_authenticate = None
def __init__(self) -> None:
"""
@@ -388,6 +412,7 @@ class BoundaryNotInBody(Exception):
"""
content_type = 'text/html;charset=UTF-8'
+ www_authenticate = None
def __init__(self) -> None:
"""
@@ -411,6 +436,7 @@ class NoBoundaryFound(Exception):
"""
content_type = 'text/html;charset=UTF-8'
+ www_authenticate = None
def __init__(self) -> None:
"""
@@ -435,6 +461,7 @@ class QueryOutOfBounds(Exception):
"""
content_type = 'text/html; charset=ISO-8859-1'
+ www_authenticate = None
def __init__(self) -> None:
"""
From 5331de1dee23605ece88e6a1107275b350afdde6 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 13:52:00 +0100
Subject: [PATCH 0194/3455] More consolidation of error handling
---
src/mock_vws/_flask_server/vwq/__init__.py | 36 ++++++--------------
src/mock_vws/_query_validators/exceptions.py | 4 +--
2 files changed, 13 insertions(+), 27 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index a27db84be..8c7f9406f 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -76,22 +76,24 @@ def handle_connection_error(
raise e
-@CLOUDRECO_FLASK_APP.errorhandler(UnsupportedMediaType)
-@CLOUDRECO_FLASK_APP.errorhandler(InvalidAcceptHeader)
+@CLOUDRECO_FLASK_APP.errorhandler(AuthHeaderMissing)
+@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailure)
+@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailureGoodFormatting)
@CLOUDRECO_FLASK_APP.errorhandler(BadImage)
-@CLOUDRECO_FLASK_APP.errorhandler(RequestTimeTooSkewed)
+@CLOUDRECO_FLASK_APP.errorhandler(BoundaryNotInBody)
+@CLOUDRECO_FLASK_APP.errorhandler(DateFormatNotValid)
+@CLOUDRECO_FLASK_APP.errorhandler(DateHeaderNotGiven)
@CLOUDRECO_FLASK_APP.errorhandler(ImageNotGiven)
@CLOUDRECO_FLASK_APP.errorhandler(InactiveProject)
+@CLOUDRECO_FLASK_APP.errorhandler(InvalidAcceptHeader)
@CLOUDRECO_FLASK_APP.errorhandler(InvalidIncludeTargetData)
@CLOUDRECO_FLASK_APP.errorhandler(InvalidMaxNumResults)
-@CLOUDRECO_FLASK_APP.errorhandler(UnknownParameters)
+@CLOUDRECO_FLASK_APP.errorhandler(MalformedAuthHeader)
@CLOUDRECO_FLASK_APP.errorhandler(MaxNumResultsOutOfRange)
@CLOUDRECO_FLASK_APP.errorhandler(NoBoundaryFound)
-@CLOUDRECO_FLASK_APP.errorhandler(BoundaryNotInBody)
-@CLOUDRECO_FLASK_APP.errorhandler(DateFormatNotValid)
-@CLOUDRECO_FLASK_APP.errorhandler(AuthHeaderMissing)
-@CLOUDRECO_FLASK_APP.errorhandler(DateHeaderNotGiven)
-@CLOUDRECO_FLASK_APP.errorhandler(MalformedAuthHeader)
+@CLOUDRECO_FLASK_APP.errorhandler(RequestTimeTooSkewed)
+@CLOUDRECO_FLASK_APP.errorhandler(UnknownParameters)
+@CLOUDRECO_FLASK_APP.errorhandler(UnsupportedMediaType)
def handle_request_time_too_skewed(
e: RequestTimeTooSkewed,
) -> Response:
@@ -109,22 +111,6 @@ def handle_request_time_too_skewed(
-
-@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailure)
-@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailureGoodFormatting)
-def handle_authentication_failure(
- # TODO Update type hint but maybe merge with main handler?
- e: AuthenticationFailure,
-) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- response.content_type = e.content_type
- response.headers['WWW-Authenticate'] = 'VWS'
- return response
-
-
-
@CLOUDRECO_FLASK_APP.errorhandler(QueryOutOfBounds)
def handle_query_out_of_bounds(
e: QueryOutOfBounds,
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index cae87c1a7..23eaa5a9d 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -117,7 +117,7 @@ class AuthenticationFailure(Exception):
"""
content_type = 'application/json'
- www_authenticate = None
+ www_authenticate = 'VWS'
def __init__(self) -> None:
"""
@@ -149,7 +149,7 @@ class AuthenticationFailureGoodFormatting(Exception):
"""
content_type = 'application/json'
- www_authenticate = None
+ www_authenticate = 'VWS'
def __init__(self) -> None:
"""
From 5fe4be398e14f62e00a2426c7338e7a3da5c1edd Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 16:47:42 +0100
Subject: [PATCH 0195/3455] Progress towards no mypy issues
---
src/mock_vws/_flask_server/vwq/__init__.py | 48 +++++++++++++++-------
1 file changed, 33 insertions(+), 15 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 8c7f9406f..e093fa390 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -1,12 +1,13 @@
import copy
import email.utils
from pathlib import Path
-from typing import Any, Dict, Tuple, Union
+from typing import Any, Dict, Tuple, Union, Optional
from http import HTTPStatus
import requests
from flask import Flask, Response, make_response, request
from werkzeug.datastructures import Headers
+from werkzeug.wsgi import ClosingIterator
from mock_vws._query_tools import (
ActiveMatchingTargetsDeleteProcessing,
@@ -58,8 +59,37 @@ def validate_request() -> None:
)
+# We use a custom response type.
+# Without this, a content type is added to all responses.
+# Some of our responses need to not have a "Content-Type" header.
class MyResponse(Response):
- default_mimetype = None
+ def __init__(
+ self,
+ response: Optional[ClosingIterator] = None,
+ status: Optional[str] = None,
+ headers: Optional[Headers] =None,
+ mimetype: Optional[str] =None,
+ content_type: Optional[str] =None,
+ direct_passthrough: bool=False,
+ ) -> None:
+ if headers:
+ content_type_from_headers = headers.get('Content-Type')
+ else:
+ content_type_from_headers = None
+
+ super().__init__(
+ response=response,
+ status=status,
+ headers=headers,
+ mimetype=mimetype,
+ content_type=content_type,
+ direct_passthrough=direct_passthrough,
+ )
+
+ if content_type is None and headers and not content_type_from_headers:
+ headers_dict = dict(headers)
+ headers_dict.pop('Content-Type')
+ self.headers = Headers(headers_dict)
CLOUDRECO_FLASK_APP.response_class = MyResponse
@@ -152,18 +182,6 @@ def set_headers(response: Response) -> Response:
response.headers['Server'] = 'nginx'
date = email.utils.formatdate(None, localtime=False, usegmt=True)
response.headers['Date'] = date
- if (
- response.status_code
- in (
- HTTPStatus.OK,
- HTTPStatus.UNPROCESSABLE_ENTITY,
- HTTPStatus.BAD_REQUEST,
- HTTPStatus.FORBIDDEN,
- HTTPStatus.UNAUTHORIZED,
- )
- and 'Content-Type' not in response.headers
- ):
- response.headers['Content-Type'] = 'application/json'
return response
@@ -211,4 +229,4 @@ def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
return (response_text, HTTPStatus.INTERNAL_SERVER_ERROR, headers)
- return (response_text, HTTPStatus.OK)
+ return (response_text, HTTPStatus.OK, {'Content-Type': 'application/json'})
From 7e8ed31d3acc03b98dc9f9e89802b47b19b8e494 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 17:08:33 +0100
Subject: [PATCH 0196/3455] Progress towards new system of putting headers into
exceptions
---
src/mock_vws/_flask_server/vwq/__init__.py | 8 +------
src/mock_vws/_query_validators/exceptions.py | 11 ++++++++++
.../mock_web_services_api.py | 22 +++++--------------
3 files changed, 17 insertions(+), 24 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index e093fa390..cd7c6d257 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -130,13 +130,7 @@ def handle_request_time_too_skewed(
response = Response()
response.status_code = e.status_code
response.set_data(e.response_text)
- if e.content_type is None:
- response.headers.pop('Content-Type')
- else:
- response.content_type = e.content_type
-
- if e.www_authenticate:
- response.headers['WWW-Authenticate'] = e.www_authenticate
+ response.headers = Headers(e.headers)
return response
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index 23eaa5a9d..22d29426b 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -2,6 +2,7 @@
Exceptions to raise from validators.
"""
+import email.utils
import uuid
from http import HTTPStatus
from pathlib import Path
@@ -30,6 +31,16 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = 'Date header required.'
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/plain; charset=ISO-8859-1',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class DateFormatNotValid(Exception):
"""
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index d6912d873..cb25a1e4a 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -89,25 +89,13 @@ def run_validators(
BadImage,
ImageTooLarge,
RequestTimeTooSkewed,
+ OopsErrorOccurredResponse,
+ ContentLengthHeaderTooLarge,
+ ContentLengthHeaderNotInt,
+ UnnecessaryRequestBody,
) as exc:
context.status_code = exc.status_code
- return exc.response_text
- except OopsErrorOccurredResponse as exc:
- content_type = 'text/html; charset=UTF-8'
- context.headers['Content-Type'] = content_type
- context.status_code = exc.status_code
- return exc.response_text
- except ContentLengthHeaderTooLarge as exc:
- context.headers = {'Connection': 'keep-alive'}
- context.status_code = exc.status_code
- return exc.response_text
- except ContentLengthHeaderNotInt as exc:
- context.headers = {'Connection': 'Close'}
- context.status_code = exc.status_code
- return exc.response_text
- except UnnecessaryRequestBody as exc:
- context.headers.pop('Content-Type')
- context.status_code = exc.status_code
+ context.headers = exc.headers
return exc.response_text
return wrapped(*args, **kwargs)
From 2709030ef78e2c1871ea29173059c14dc0d39a72 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 17:21:30 +0100
Subject: [PATCH 0197/3455] Use VWS auth tools where possible
---
src/mock_vws/_database_matchers.py | 64 ++----------------------------
1 file changed, 3 insertions(+), 61 deletions(-)
diff --git a/src/mock_vws/_database_matchers.py b/src/mock_vws/_database_matchers.py
index ee8084c17..a30ea45c0 100644
--- a/src/mock_vws/_database_matchers.py
+++ b/src/mock_vws/_database_matchers.py
@@ -6,69 +6,11 @@
import hashlib
import hmac
from typing import Dict, Iterable, Optional
+from vws_auth_tools import authorization_header
from mock_vws.database import VuforiaDatabase
-def _compute_hmac_base64(key: bytes, data: bytes) -> bytes:
- """
- Return the Base64 encoded HMAC-SHA1 hash of the given `data` using the
- provided `key`.
- """
- hashed = hmac.new(key=key, msg=None, digestmod=hashlib.sha1)
- hashed.update(msg=data)
- return base64.b64encode(s=hashed.digest())
-
-
-def _authorization_header( # pylint: disable=too-many-arguments
- access_key: str,
- secret_key: str,
- method: str,
- content: bytes,
- content_type: str,
- date: str,
- request_path: str,
-) -> str:
- """
- Return an `Authorization` header which can be used for a request made to
- the VWS API with the given attributes.
-
- Args:
- access_key: A VWS server or client access key.
- secret_key: A VWS server or client secret key.
- method: The HTTP method which will be used in the request.
- content: The request body which will be used in the request.
- content_type: The `Content-Type` header which will be used in the
- request.
- date: The current date which must exactly match the date sent in the
- `Date` header.
- request_path: The path to the endpoint which will be used in the
- request.
-
- Returns:
- An `Authorization` header which can be used for a request made to the
- VWS API with the given attributes.
- """
- hashed = hashlib.md5()
- hashed.update(content)
- content_md5_hex = hashed.hexdigest()
-
- components_to_sign = [
- method,
- content_md5_hex,
- content_type,
- date,
- request_path,
- ]
- string_to_sign = '\n'.join(components_to_sign)
- signature = _compute_hmac_base64(
- key=secret_key.encode(),
- data=bytes(string_to_sign, encoding='utf-8'),
- )
- auth_header = f'VWS {access_key}:{signature.decode()}'
- return auth_header
-
-
def get_database_matching_client_keys(
request_headers: Dict[str, str],
request_body: Optional[bytes],
@@ -96,7 +38,7 @@ def get_database_matching_client_keys(
date = request_headers.get('Date', '')
for database in databases:
- expected_authorization_header = _authorization_header(
+ expected_authorization_header = authorization_header(
access_key=database.client_access_key,
secret_key=database.client_secret_key,
method=request_method,
@@ -138,7 +80,7 @@ def get_database_matching_server_keys(
date = request_headers.get('Date', '')
for database in databases:
- expected_authorization_header = _authorization_header(
+ expected_authorization_header = authorization_header(
access_key=database.server_access_key,
secret_key=database.server_secret_key,
method=request_method,
From 11c8b3e74303bbc9d94b42e4af1a0c38a26cca2e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 20:52:32 +0100
Subject: [PATCH 0198/3455] Use headers property on all query validators
---
src/mock_vws/_flask_server/vwq/__init__.py | 63 ++---
src/mock_vws/_flask_server/vws/__init__.py | 3 +
src/mock_vws/_flask_server/vws/_databases.py | 3 -
src/mock_vws/_query_validators/exceptions.py | 252 ++++++++++++++-----
4 files changed, 214 insertions(+), 107 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index cd7c6d257..8c552aa7c 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -124,6 +124,9 @@ def handle_connection_error(
@CLOUDRECO_FLASK_APP.errorhandler(RequestTimeTooSkewed)
@CLOUDRECO_FLASK_APP.errorhandler(UnknownParameters)
@CLOUDRECO_FLASK_APP.errorhandler(UnsupportedMediaType)
+@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
+@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
+@CLOUDRECO_FLASK_APP.errorhandler(QueryOutOfBounds)
def handle_request_time_too_skewed(
e: RequestTimeTooSkewed,
) -> Response:
@@ -135,50 +138,6 @@ def handle_request_time_too_skewed(
-@CLOUDRECO_FLASK_APP.errorhandler(QueryOutOfBounds)
-def handle_query_out_of_bounds(
- e: QueryOutOfBounds,
-) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- response.content_type = e.content_type
- cache_control = 'must-revalidate,no-cache,no-store'
- response.headers['Cache-Control'] = cache_control
- return response
-
-@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
-def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- response.headers = Headers({'Connection': 'keep-alive'})
- return response
-
-@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
-def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- response.headers = Headers({'Connection': 'Close'})
- return response
-
-
-@CLOUDRECO_FLASK_APP.after_request
-def set_headers(response: Response) -> Response:
- if dict(response.headers) == {'Connection': 'keep-alive'}:
- return response
-
- if dict(response.headers) == {'Connection': 'Close'}:
- return response
-
- response.headers['Connection'] = 'keep-alive'
- response.headers['Server'] = 'nginx'
- date = email.utils.formatdate(None, localtime=False, usegmt=True)
- response.headers['Date'] = date
- return response
-
-
@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
@@ -188,6 +147,7 @@ def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
databases = get_all_databases()
input_stream_copy = copy.copy(request.input_stream)
request_body = input_stream_copy.read()
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
try:
response_text = get_query_match_response_text(
@@ -216,11 +176,20 @@ def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
cache_control = 'must-revalidate,no-cache,no-store'
content_type = 'text/html; charset=ISO-8859-1'
headers = {
- 'Cache-Control': cache_control,
- 'Content-Type': content_type,
+ 'Content-Type': 'text/html; charset=ISO-8859-1',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ 'Cache-Control': 'must-revalidate,no-cache,no-store',
}
response_text = match_processing_resp_file.read_text()
return (response_text, HTTPStatus.INTERNAL_SERVER_ERROR, headers)
- return (response_text, HTTPStatus.OK, {'Content-Type': 'application/json'})
+ headers = {
+ 'Content-Type': 'application/json',
+ 'Date': date,
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ }
+ return (response_text, HTTPStatus.OK, headers)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index dc7ef50cb..d62c6c04c 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -116,6 +116,9 @@ def handle_oops_error_occurred(e: OopsErrorOccurredResponse) -> Response:
def set_headers(response: Response) -> Response:
"""
TODO
+
+ GET RID!
+ At least of a lot of this?
"""
if dict(response.headers) == {'Connection': 'keep-alive'}:
return response
diff --git a/src/mock_vws/_flask_server/vws/_databases.py b/src/mock_vws/_flask_server/vws/_databases.py
index d24b2dc67..3c2c9ca14 100644
--- a/src/mock_vws/_flask_server/vws/_databases.py
+++ b/src/mock_vws/_flask_server/vws/_databases.py
@@ -25,7 +25,6 @@ def get_all_databases() -> Set[VuforiaDatabase]:
client_access_key = database_dict['client_access_key']
client_secret_key = database_dict['client_secret_key']
state = States(database_dict['state_value'])
- # TODO state
new_database = VuforiaDatabase(
database_name=database_name,
@@ -37,7 +36,6 @@ def get_all_databases() -> Set[VuforiaDatabase]:
)
for target_dict in database_dict['targets']:
- # TODO fill this in
name = target_dict['name']
active_flag = target_dict['active_flag']
width = target_dict['width']
@@ -67,7 +65,6 @@ def get_all_databases() -> Set[VuforiaDatabase]:
target_dict['upload_date'],
)
target.processed_tracking_rating = target_dict['processed_tracking_rating']
- # import pdb; pdb.set_trace()
target.upload_date = target.upload_date.replace(tzinfo=gmt)
delete_date_optional = target_dict['delete_date_optional']
if delete_date_optional:
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index 22d29426b..26edc9002 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -16,9 +16,6 @@ class DateHeaderNotGiven(Exception):
Exception raised when a date header is not given.
"""
- content_type = 'text/plain; charset=ISO-8859-1'
- www_authenticate = None
-
def __init__(self) -> None:
"""
Attributes:
@@ -47,9 +44,6 @@ class DateFormatNotValid(Exception):
Exception raised when the date format is not valid.
"""
- www_authenticate = 'VWS'
- content_type = 'text/plain; charset=ISO-8859-1'
-
def __init__(self) -> None:
"""
Attributes:
@@ -62,6 +56,17 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.UNAUTHORIZED
self.response_text = 'Malformed date header.'
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/plain; charset=ISO-8859-1',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ 'WWW-Authenticate': 'VWS',
+ }
+
class RequestTimeTooSkewed(Exception):
"""
@@ -69,9 +74,6 @@ class RequestTimeTooSkewed(Exception):
'RequestTimeTooSkewed'.
"""
- content_type = 'application/json'
- www_authenticate = None
-
def __init__(self) -> None:
"""
Attributes:
@@ -88,6 +90,16 @@ def __init__(self) -> None:
}
self.response_text = json_dump(body)
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class BadImage(Exception):
"""
@@ -95,9 +107,6 @@ class BadImage(Exception):
'BadImage'.
"""
- content_type = 'application/json'
- www_authenticate = None
-
def __init__(self) -> None:
"""
Attributes:
@@ -120,6 +129,16 @@ def __init__(self) -> None:
'}'
)
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class AuthenticationFailure(Exception):
"""
@@ -127,9 +146,6 @@ class AuthenticationFailure(Exception):
'AuthenticationFailure'.
"""
- content_type = 'application/json'
- www_authenticate = 'VWS'
-
def __init__(self) -> None:
"""
Attributes:
@@ -152,6 +168,17 @@ def __init__(self) -> None:
'}'
)
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ 'WWW-Authenticate': 'VWS',
+ }
+
class AuthenticationFailureGoodFormatting(Exception):
"""
@@ -159,9 +186,6 @@ class AuthenticationFailureGoodFormatting(Exception):
'AuthenticationFailure' with a standard JSON formatting.
"""
- content_type = 'application/json'
- www_authenticate = 'VWS'
-
def __init__(self) -> None:
"""
Attributes:
@@ -179,15 +203,23 @@ def __init__(self) -> None:
}
self.response_text = json_dump(body)
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ 'WWW-Authenticate': 'VWS',
+ }
+
class ImageNotGiven(Exception):
"""
Exception raised when an image is not given.
"""
- content_type = 'application/json'
- www_authenticate = None
-
def __init__(self) -> None:
"""
Attributes:
@@ -200,15 +232,22 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = 'No image.'
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class AuthHeaderMissing(Exception):
"""
Exception raised when an auth header is not given.
"""
- content_type = 'text/plain; charset=ISO-8859-1'
- www_authenticate = 'VWS'
-
def __init__(self) -> None:
"""
Attributes:
@@ -221,15 +260,23 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.UNAUTHORIZED
self.response_text = 'Authorization header missing.'
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/plain; charset=ISO-8859-1',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ 'WWW-Authenticate': 'VWS',
+ }
+
class MalformedAuthHeader(Exception):
"""
Exception raised when an auth header is not given.
"""
- content_type = 'text/plain; charset=ISO-8859-1'
- www_authenticate = 'VWS'
-
def __init__(self) -> None:
"""
Attributes:
@@ -242,15 +289,23 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.UNAUTHORIZED
self.response_text = 'Malformed authorization header.'
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/plain; charset=ISO-8859-1',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ 'WWW-Authenticate': 'VWS',
+ }
+
class UnknownParameters(Exception):
"""
Exception raised when unknown parameters are given.
"""
- content_type = 'application/json'
- www_authenticate = None
-
def __init__(self) -> None:
"""
Attributes:
@@ -263,6 +318,16 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = 'Unknown parameters in the request.'
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class InactiveProject(Exception):
"""
@@ -270,9 +335,6 @@ class InactiveProject(Exception):
'InactiveProject'.
"""
- content_type = 'application/json'
- www_authenticate = None
-
def __init__(self) -> None:
"""
Attributes:
@@ -294,6 +356,16 @@ def __init__(self) -> None:
'}'
)
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class InvalidMaxNumResults(Exception):
"""
@@ -301,9 +373,6 @@ class InvalidMaxNumResults(Exception):
"max_num_results" field.
"""
- content_type = 'application/json'
- www_authenticate = None
-
def __init__(self, given_value: str) -> None:
"""
Attributes:
@@ -320,6 +389,16 @@ def __init__(self, given_value: str) -> None:
)
self.response_text = invalid_value_message
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class MaxNumResultsOutOfRange(Exception):
"""
@@ -327,9 +406,6 @@ class MaxNumResultsOutOfRange(Exception):
field which is out of range.
"""
- content_type = 'application/json'
- www_authenticate = None
-
def __init__(self, given_value: str) -> None:
"""
Attributes:
@@ -346,6 +422,15 @@ def __init__(self, given_value: str) -> None:
)
self.response_text = integer_out_of_range_message
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
class InvalidIncludeTargetData(Exception):
"""
@@ -353,9 +438,6 @@ class InvalidIncludeTargetData(Exception):
"include_target_data" field.
"""
- content_type = 'application/json'
- www_authenticate = None
-
def __init__(self, given_value: str) -> None:
"""
Attributes:
@@ -374,15 +456,22 @@ def __init__(self, given_value: str) -> None:
)
self.response_text = unexpected_target_data_message
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class UnsupportedMediaType(Exception):
"""
Exception raised when no boundary is found for multipart data.
"""
- content_type = None
- www_authenticate = None
-
def __init__(self) -> None:
"""
Attributes:
@@ -395,15 +484,21 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.UNSUPPORTED_MEDIA_TYPE
self.response_text = ''
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class InvalidAcceptHeader(Exception):
"""
Exception raised when there is an invalid accept header given.
"""
- content_type = None
- www_authenticate = None
-
def __init__(self) -> None:
"""
Attributes:
@@ -416,15 +511,21 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.NOT_ACCEPTABLE
self.response_text = ''
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class BoundaryNotInBody(Exception):
"""
Exception raised when the form boundary is not in the request body.
"""
- content_type = 'text/html;charset=UTF-8'
- www_authenticate = None
-
def __init__(self) -> None:
"""
Attributes:
@@ -440,15 +541,22 @@ def __init__(self) -> None:
'Could find no Content-Disposition header within part'
)
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/html;charset=UTF-8',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class NoBoundaryFound(Exception):
"""
Exception raised when an invalid media type is given.
"""
- content_type = 'text/html;charset=UTF-8'
- www_authenticate = None
-
def __init__(self) -> None:
"""
Attributes:
@@ -464,6 +572,16 @@ def __init__(self) -> None:
'Unable to get boundary for multipart'
)
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/html;charset=UTF-8',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class QueryOutOfBounds(Exception):
"""
@@ -471,9 +589,6 @@ class QueryOutOfBounds(Exception):
particular out of bounds error.
"""
- content_type = 'text/html; charset=ISO-8859-1'
- www_authenticate = None
-
def __init__(self) -> None:
"""
Attributes:
@@ -490,6 +605,17 @@ def __init__(self) -> None:
text = str(oops_resp_file.read_text())
self.response_text = text
+ @property
+ def headers(self):
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/html; charset=ISO-8859-1',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ 'Cache-Control': 'must-revalidate,no-cache,no-store',
+ }
+
class ContentLengthHeaderTooLarge(Exception):
"""
@@ -508,6 +634,12 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.GATEWAY_TIMEOUT
self.response_text = ''
+ @property
+ def headers(self):
+ return {
+ 'Connection': 'keep-alive',
+ }
+
class ContentLengthHeaderNotInt(Exception):
"""
@@ -525,3 +657,9 @@ def __init__(self) -> None:
super().__init__()
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = ''
+
+ @property
+ def headers(self):
+ return {
+ 'Connection': 'Close',
+ }
From 3656d0831cd119658ccfcc252c3f5571f9cf8a3d Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 20:56:39 +0100
Subject: [PATCH 0199/3455] Progress towards moving to having headers as part
of exceptions
---
src/mock_vws/_database_matchers.py | 4 +-
src/mock_vws/_flask_server/vwq/__init__.py | 21 ++--
src/mock_vws/_flask_server/vws/__init__.py | 111 ++++++++-----------
src/mock_vws/_flask_server/vws/_databases.py | 4 +-
src/mock_vws/_query_validators/exceptions.py | 1 +
5 files changed, 59 insertions(+), 82 deletions(-)
diff --git a/src/mock_vws/_database_matchers.py b/src/mock_vws/_database_matchers.py
index a30ea45c0..afcd282c1 100644
--- a/src/mock_vws/_database_matchers.py
+++ b/src/mock_vws/_database_matchers.py
@@ -2,10 +2,8 @@
Helpers for getting databases which match keys given in requests.
"""
-import base64
-import hashlib
-import hmac
from typing import Dict, Iterable, Optional
+
from vws_auth_tools import authorization_header
from mock_vws.database import VuforiaDatabase
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 8c552aa7c..d68db815c 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -1,11 +1,11 @@
import copy
import email.utils
-from pathlib import Path
-from typing import Any, Dict, Tuple, Union, Optional
from http import HTTPStatus
+from pathlib import Path
+from typing import Any, Dict, Optional, Tuple, Union
import requests
-from flask import Flask, Response, make_response, request
+from flask import Flask, Response, request
from werkzeug.datastructures import Headers
from werkzeug.wsgi import ClosingIterator
@@ -21,8 +21,8 @@
AuthHeaderMissing,
BadImage,
BoundaryNotInBody,
- ContentLengthHeaderTooLarge,
ContentLengthHeaderNotInt,
+ ContentLengthHeaderTooLarge,
DateFormatNotValid,
DateHeaderNotGiven,
ImageNotGiven,
@@ -41,7 +41,7 @@
from ..vws._databases import get_all_databases
-CLOUDRECO_FLASK_APP = Flask(__name__)
+CLOUDRECO_FLASK_APP = Flask(import_name=__name__)
CLOUDRECO_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
@@ -67,10 +67,10 @@ def __init__(
self,
response: Optional[ClosingIterator] = None,
status: Optional[str] = None,
- headers: Optional[Headers] =None,
- mimetype: Optional[str] =None,
- content_type: Optional[str] =None,
- direct_passthrough: bool=False,
+ headers: Optional[Headers] = None,
+ mimetype: Optional[str] = None,
+ content_type: Optional[str] = None,
+ direct_passthrough: bool = False,
) -> None:
if headers:
content_type_from_headers = headers.get('Content-Type')
@@ -137,7 +137,6 @@ def handle_request_time_too_skewed(
return response
-
@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
@@ -173,7 +172,6 @@ def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
resources_dir = Path(__file__).parent.parent.parent / 'resources'
filename = 'match_processing_response.html'
match_processing_resp_file = resources_dir / filename
- cache_control = 'must-revalidate,no-cache,no-store'
content_type = 'text/html; charset=ISO-8859-1'
headers = {
'Content-Type': 'text/html; charset=ISO-8859-1',
@@ -185,7 +183,6 @@ def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
response_text = match_processing_resp_file.read_text()
return (response_text, HTTPStatus.INTERNAL_SERVER_ERROR, headers)
-
headers = {
'Content-Type': 'application/json',
'Date': date,
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index d62c6c04c..a9707a3ed 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -3,7 +3,6 @@
"""
import base64
-import email.utils
import io
import json
import uuid
@@ -12,8 +11,9 @@
import requests
from flask import Flask, Response, request
-import flask
from PIL import Image
+from werkzeug.datastructures import Headers
+
from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_server_keys
from mock_vws._mock_common import json_dump
@@ -21,6 +21,8 @@
from mock_vws._services_validators.exceptions import (
AuthenticationFailure,
BadImage,
+ ContentLengthHeaderNotInt,
+ ContentLengthHeaderTooLarge,
Fail,
ImageTooLarge,
MetadataTooLarge,
@@ -30,17 +32,50 @@
TargetNameExist,
UnknownTarget,
UnnecessaryRequestBody,
- ContentLengthHeaderNotInt,
- ContentLengthHeaderTooLarge,
)
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
-from werkzeug.datastructures import Headers
from ._constants import STORAGE_BASE_URL
from ._databases import get_all_databases
VWS_FLASK_APP = Flask(import_name=__name__)
+VWS_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
+
+# We use a custom response type.
+# Without this, a content type is added to all responses.
+# Some of our responses need to not have a "Content-Type" header.
+class MyResponse(Response):
+ def __init__(
+ self,
+ response: Optional[ClosingIterator] = None,
+ status: Optional[str] = None,
+ headers: Optional[Headers] = None,
+ mimetype: Optional[str] = None,
+ content_type: Optional[str] = None,
+ direct_passthrough: bool = False,
+ ) -> None:
+ if headers:
+ content_type_from_headers = headers.get('Content-Type')
+ else:
+ content_type_from_headers = None
+
+ super().__init__(
+ response=response,
+ status=status,
+ headers=headers,
+ mimetype=mimetype,
+ content_type=content_type,
+ direct_passthrough=direct_passthrough,
+ )
+
+ if content_type is None and headers and not content_type_from_headers:
+ headers_dict = dict(headers)
+ headers_dict.pop('Content-Type')
+ self.headers = Headers(headers_dict)
+
+
+CLOUDRECO_FLASK_APP.response_class = MyResponse
@VWS_FLASK_APP.before_request
@@ -58,6 +93,7 @@ def validate_request() -> None:
class MyResponse(Response):
default_mimetype = None
+
VWS_FLASK_APP.response_class = MyResponse
@@ -70,70 +106,16 @@ class MyResponse(Response):
@VWS_FLASK_APP.errorhandler(BadImage)
@VWS_FLASK_APP.errorhandler(ImageTooLarge)
@VWS_FLASK_APP.errorhandler(RequestTimeTooSkewed)
-# TODO update name and type hint here
-def handle_unknown_target(e: UnknownTarget) -> Tuple[str, int]:
- return e.response_text, e.status_code
-
-
@VWS_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
-def handle_content_length_header_too_large(e: ContentLengthHeaderTooLarge) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- response.headers = Headers({'Connection': 'keep-alive'})
- return response
-
@VWS_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
-def handle_content_length_header_not_int(e: ContentLengthHeaderNotInt) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- response.headers = Headers({'Connection': 'Close'})
- return response
-
@VWS_FLASK_APP.errorhandler(UnnecessaryRequestBody)
-def handle_unnecessary_request_body(
- e: UnnecessaryRequestBody,
-) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- response.headers.pop('Content-Type')
- return response
-
-
@VWS_FLASK_APP.errorhandler(OopsErrorOccurredResponse)
-def handle_oops_error_occurred(e: OopsErrorOccurredResponse) -> Response:
+# TODO update name and type hint here
+def handle_unknown_target(e: UnknownTarget) -> Response:
response = Response()
response.status_code = e.status_code
response.set_data(e.response_text)
- content_type = 'text/html; charset=UTF-8'
- response.headers['Content-Type'] = content_type
- return response
-
-
-@VWS_FLASK_APP.after_request
-def set_headers(response: Response) -> Response:
- """
- TODO
-
- GET RID!
- At least of a lot of this?
- """
- if dict(response.headers) == {'Connection': 'keep-alive'}:
- return response
-
- if dict(response.headers) == {'Connection': 'Close'}:
- return response
-
- response.headers['Connection'] = 'keep-alive'
- if response.status_code != HTTPStatus.INTERNAL_SERVER_ERROR and len(
- response.data
- ):
- response.headers['Content-Type'] = 'application/json'
- response.headers['Server'] = 'nginx'
- date = email.utils.formatdate(None, localtime=False, usegmt=True)
- response.headers['Date'] = date
+ response.headers = e.headers
return response
@@ -412,10 +394,7 @@ def target_list() -> Tuple[str, int]:
databases=databases,
)
assert isinstance(database, VuforiaDatabase)
- results = [
- target.target_id
- for target in database.not_deleted_targets
- ]
+ results = [target.target_id for target in database.not_deleted_targets]
body: Dict[str, Union[str, List[str]]] = {
'transaction_id': uuid.uuid4().hex,
diff --git a/src/mock_vws/_flask_server/vws/_databases.py b/src/mock_vws/_flask_server/vws/_databases.py
index 3c2c9ca14..546ae270e 100644
--- a/src/mock_vws/_flask_server/vws/_databases.py
+++ b/src/mock_vws/_flask_server/vws/_databases.py
@@ -64,7 +64,9 @@ def get_all_databases() -> Set[VuforiaDatabase]:
target.upload_date = datetime.datetime.fromisoformat(
target_dict['upload_date'],
)
- target.processed_tracking_rating = target_dict['processed_tracking_rating']
+ target.processed_tracking_rating = target_dict[
+ 'processed_tracking_rating'
+ ]
target.upload_date = target.upload_date.replace(tzinfo=gmt)
delete_date_optional = target_dict['delete_date_optional']
if delete_date_optional:
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index 26edc9002..1ada7a978 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -432,6 +432,7 @@ def headers(self):
'Date': date,
}
+
class InvalidIncludeTargetData(Exception):
"""
Exception raised when an invalid value is given as the
From 0f22708d7f8db2e895d4368b092cab5f43237ca1 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 20:59:16 +0100
Subject: [PATCH 0200/3455] Use vws_auth_tools where appropriate
---
.../_flask_server/vwq/_database_matchers.py | 66 +------------------
1 file changed, 3 insertions(+), 63 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/_database_matchers.py b/src/mock_vws/_flask_server/vwq/_database_matchers.py
index 0edb5890e..1db708149 100644
--- a/src/mock_vws/_flask_server/vwq/_database_matchers.py
+++ b/src/mock_vws/_flask_server/vwq/_database_matchers.py
@@ -7,70 +7,10 @@
import hmac
from typing import Dict, Iterable, Optional
+from vws_auth_tools import authorization_header
from mock_vws.database import VuforiaDatabase
-def _compute_hmac_base64(key: bytes, data: bytes) -> bytes:
- """
- Return the Base64 encoded HMAC-SHA1 hash of the given `data` using the
- provided `key`.
- """
- hashed = hmac.new(key=key, msg=None, digestmod=hashlib.sha1)
- hashed.update(msg=data)
- return base64.b64encode(s=hashed.digest())
-
-
-def _authorization_header( # pylint: disable=too-many-arguments
- access_key: str,
- secret_key: str,
- method: str,
- content: bytes,
- content_type: str,
- date: str,
- request_path: str,
-) -> str:
- """
- Return an `Authorization` header which can be used for a request made to
- the VWS API with the given attributes.
-
- Args:
- access_key: A VWS server or client access key.
- secret_key: A VWS server or client secret key.
- method: The HTTP method which will be used in the request.
- content: The request body which will be used in the request.
- content_type: The `Content-Type` header which will be used in the
- request.
- date: The current date which must exactly match the date sent in the
- `Date` header.
- request_path: The path to the endpoint which will be used in the
- request.
-
- Returns:
- An `Authorization` header which can be used for a request made to the
- VWS API with the given attributes.
- """
- hashed = hashlib.md5()
- hashed.update(content)
- content_md5_hex = hashed.hexdigest()
-
- components_to_sign = [
- method,
- content_md5_hex,
- content_type,
- date,
- request_path,
- ]
- string_to_sign = '\n'.join(components_to_sign)
- signature = _compute_hmac_base64(
- key=secret_key.encode(),
- data=bytes(
- string_to_sign,
- encoding='utf-8',
- ),
- )
- auth_header = f'VWS {access_key}:{signature.decode()}'
- return auth_header
-
def get_database_matching_client_keys(
request_headers: Dict[str, str],
@@ -99,7 +39,7 @@ def get_database_matching_client_keys(
date = request_headers.get('Date', '')
for database in databases:
- expected_authorization_header = _authorization_header(
+ expected_authorization_header = authorization_header(
access_key=database.client_access_key,
secret_key=database.client_secret_key,
method=request_method,
@@ -141,7 +81,7 @@ def get_database_matching_server_keys(
date = request_headers.get('Date', '')
for database in databases:
- expected_authorization_header = _authorization_header(
+ expected_authorization_header = authorization_header(
access_key=database.server_access_key,
secret_key=database.server_secret_key,
method=request_method,
From 70a3bbf27365ac10c356b5a3120e149c83aac145 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 21:02:10 +0100
Subject: [PATCH 0201/3455] Remove done TODOs
---
src/mock_vws/_flask_server/vws/_databases.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/vws/_databases.py b/src/mock_vws/_flask_server/vws/_databases.py
index 546ae270e..a0aa5b993 100644
--- a/src/mock_vws/_flask_server/vws/_databases.py
+++ b/src/mock_vws/_flask_server/vws/_databases.py
@@ -14,7 +14,6 @@
def get_all_databases() -> Set[VuforiaDatabase]:
- # TODO use the storage URL to get details then cast to VuforiaDatabase
response = requests.get(url=STORAGE_BASE_URL + '/databases')
response_json = response.json()
databases = set()
From 7990195ea148b6779d9597fe207016bcbea26a52 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 21:07:10 +0100
Subject: [PATCH 0202/3455] Fix a bunch of mypy issues
---
src/mock_vws/_flask_server/vws/__init__.py | 8 +---
src/mock_vws/_query_validators/exceptions.py | 41 ++++++++++----------
2 files changed, 22 insertions(+), 27 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index a9707a3ed..dd62ed997 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -75,7 +75,7 @@ def __init__(
self.headers = Headers(headers_dict)
-CLOUDRECO_FLASK_APP.response_class = MyResponse
+VWS_FLASK_APP.response_class = MyResponse
@VWS_FLASK_APP.before_request
@@ -90,12 +90,6 @@ def validate_request() -> None:
)
-class MyResponse(Response):
- default_mimetype = None
-
-
-VWS_FLASK_APP.response_class = MyResponse
-
@VWS_FLASK_APP.errorhandler(UnknownTarget)
@VWS_FLASK_APP.errorhandler(ProjectInactive)
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index 1ada7a978..086e57536 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -6,6 +6,7 @@
import uuid
from http import HTTPStatus
from pathlib import Path
+from typing import Dict
from mock_vws._constants import ResultCodes
from mock_vws._mock_common import json_dump
@@ -29,7 +30,7 @@ def __init__(self) -> None:
self.response_text = 'Date header required.'
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'text/plain; charset=ISO-8859-1',
@@ -57,7 +58,7 @@ def __init__(self) -> None:
self.response_text = 'Malformed date header.'
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'text/plain; charset=ISO-8859-1',
@@ -91,7 +92,7 @@ def __init__(self) -> None:
self.response_text = json_dump(body)
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'application/json',
@@ -130,7 +131,7 @@ def __init__(self) -> None:
)
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'application/json',
@@ -169,7 +170,7 @@ def __init__(self) -> None:
)
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'application/json',
@@ -204,7 +205,7 @@ def __init__(self) -> None:
self.response_text = json_dump(body)
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'application/json',
@@ -261,7 +262,7 @@ def __init__(self) -> None:
self.response_text = 'Authorization header missing.'
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'text/plain; charset=ISO-8859-1',
@@ -290,7 +291,7 @@ def __init__(self) -> None:
self.response_text = 'Malformed authorization header.'
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'text/plain; charset=ISO-8859-1',
@@ -319,7 +320,7 @@ def __init__(self) -> None:
self.response_text = 'Unknown parameters in the request.'
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'application/json',
@@ -357,7 +358,7 @@ def __init__(self) -> None:
)
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'application/json',
@@ -390,7 +391,7 @@ def __init__(self, given_value: str) -> None:
self.response_text = invalid_value_message
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'application/json',
@@ -423,7 +424,7 @@ def __init__(self, given_value: str) -> None:
self.response_text = integer_out_of_range_message
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'application/json',
@@ -458,7 +459,7 @@ def __init__(self, given_value: str) -> None:
self.response_text = unexpected_target_data_message
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'application/json',
@@ -486,7 +487,7 @@ def __init__(self) -> None:
self.response_text = ''
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Connection': 'keep-alive',
@@ -513,7 +514,7 @@ def __init__(self) -> None:
self.response_text = ''
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Connection': 'keep-alive',
@@ -543,7 +544,7 @@ def __init__(self) -> None:
)
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'text/html;charset=UTF-8',
@@ -574,7 +575,7 @@ def __init__(self) -> None:
)
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'text/html;charset=UTF-8',
@@ -607,7 +608,7 @@ def __init__(self) -> None:
self.response_text = text
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'text/html; charset=ISO-8859-1',
@@ -636,7 +637,7 @@ def __init__(self) -> None:
self.response_text = ''
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
return {
'Connection': 'keep-alive',
}
@@ -660,7 +661,7 @@ def __init__(self) -> None:
self.response_text = ''
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
return {
'Connection': 'Close',
}
From f0eacbe36cd36762d0c3bb4baf965a99857d23c5 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 21:07:33 +0100
Subject: [PATCH 0203/3455] Progress towards no mypy issues
---
src/mock_vws/_flask_server/vws/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index dd62ed997..f47216c81 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -7,7 +7,7 @@
import json
import uuid
from http import HTTPStatus
-from typing import Dict, List, Tuple, Union
+from typing import Dict, List, Tuple, Union, Optional
import requests
from flask import Flask, Response, request
From db9ab14831a90f0aef06ffcd5d9c39a8f1c5cdef Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 21:08:55 +0100
Subject: [PATCH 0204/3455] Progress towards no mypy issues
---
src/mock_vws/_flask_server/vws/__init__.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index f47216c81..0e9535c6b 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -13,6 +13,7 @@
from flask import Flask, Response, request
from PIL import Image
from werkzeug.datastructures import Headers
+from werkzeug.wsgi import ClosingIterator
from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_server_keys
From 4cb5807a4a393d5299615f5d66d4da3ea2d12536 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Wed, 16 Sep 2020 21:15:03 +0100
Subject: [PATCH 0205/3455] No more mypy issues
---
src/mock_vws/_flask_server/vws/__init__.py | 2 +-
src/mock_vws/_query_validators/exceptions.py | 2 +-
.../_services_validators/exceptions.py | 53 +++++++++++++++++++
3 files changed, 55 insertions(+), 2 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 0e9535c6b..6befd24c8 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -110,7 +110,7 @@ def handle_unknown_target(e: UnknownTarget) -> Response:
response = Response()
response.status_code = e.status_code
response.set_data(e.response_text)
- response.headers = e.headers
+ response.headers = Headers(e.headers)
return response
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index 086e57536..43e25bdac 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -234,7 +234,7 @@ def __init__(self) -> None:
self.response_text = 'No image.'
@property
- def headers(self):
+ def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
return {
'Content-Type': 'application/json',
diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py
index ef0cea89b..98e35867d 100644
--- a/src/mock_vws/_services_validators/exceptions.py
+++ b/src/mock_vws/_services_validators/exceptions.py
@@ -5,6 +5,7 @@
import uuid
from http import HTTPStatus
from pathlib import Path
+from typing import Dict
from mock_vws._constants import ResultCodes
from mock_vws._mock_common import json_dump
@@ -32,6 +33,10 @@ def __init__(self) -> None:
}
self.response_text = json_dump(body)
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {}
+
class ProjectInactive(Exception):
"""
@@ -55,6 +60,10 @@ def __init__(self) -> None:
}
self.response_text = json_dump(body)
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {}
+
class AuthenticationFailure(Exception):
"""
@@ -78,6 +87,10 @@ def __init__(self) -> None:
}
self.response_text = json_dump(body)
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {}
+
class Fail(Exception):
"""
@@ -100,6 +113,10 @@ def __init__(self, status_code: int) -> None:
}
self.response_text = json_dump(body)
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {}
+
class MetadataTooLarge(Exception):
"""
@@ -123,6 +140,10 @@ def __init__(self) -> None:
}
self.response_text = json_dump(body)
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {}
+
class TargetNameExist(Exception):
"""
@@ -146,6 +167,10 @@ def __init__(self) -> None:
}
self.response_text = json_dump(body)
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {}
+
class OopsErrorOccurredResponse(Exception):
"""
@@ -171,6 +196,10 @@ def __init__(self) -> None:
text = str(oops_resp_file.read_text())
self.response_text = text
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {}
+
class BadImage(Exception):
"""
@@ -194,6 +223,10 @@ def __init__(self) -> None:
}
self.response_text = json_dump(body)
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {}
+
class ImageTooLarge(Exception):
"""
@@ -217,6 +250,10 @@ def __init__(self) -> None:
}
self.response_text = json_dump(body)
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {}
+
class RequestTimeTooSkewed(Exception):
"""
@@ -240,6 +277,10 @@ def __init__(self) -> None:
}
self.response_text = json_dump(body)
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {}
+
class ContentLengthHeaderTooLarge(Exception):
"""
@@ -258,6 +299,10 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.GATEWAY_TIMEOUT
self.response_text = ''
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {}
+
class ContentLengthHeaderNotInt(Exception):
"""
@@ -276,6 +321,10 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = ''
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {}
+
class UnnecessaryRequestBody(Exception):
"""
@@ -293,3 +342,7 @@ def __init__(self) -> None:
super().__init__()
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = ''
+
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {}
From ece7368ba58486722e7235ecff1aaaa371ac7af8 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 11:18:06 +0100
Subject: [PATCH 0206/3455] Remove some uses of Headers
---
ci/custom_linters.py | 1 +
src/mock_vws/_flask_server/vwq/__init__.py | 15 ++++++++++++---
src/mock_vws/_flask_server/vws/__init__.py | 11 ++++++-----
3 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/ci/custom_linters.py b/ci/custom_linters.py
index eeead5e81..0ccb890ee 100644
--- a/ci/custom_linters.py
+++ b/ci/custom_linters.py
@@ -29,6 +29,7 @@ def _tests_from_pattern(ci_pattern: str) -> Set[str]:
From a CI pattern, get all tests ``pytest`` would collect.
"""
tests: Set[str] = set([])
+
args = ['pytest', '--collect-only', ci_pattern, '-q']
result = subprocess.run(args=args, stdout=subprocess.PIPE, check=True)
output = result.stdout
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index d68db815c..143d96cba 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -127,6 +127,7 @@ def handle_connection_error(
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
@CLOUDRECO_FLASK_APP.errorhandler(QueryOutOfBounds)
+# TODO use a base type for these requests
def handle_request_time_too_skewed(
e: RequestTimeTooSkewed,
) -> Response:
@@ -138,7 +139,7 @@ def handle_request_time_too_skewed(
@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
-def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
+def query() -> Response:
# TODO these should be configurable
query_processes_deletion_seconds = 0.2
@@ -181,7 +182,11 @@ def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
'Cache-Control': 'must-revalidate,no-cache,no-store',
}
response_text = match_processing_resp_file.read_text()
- return (response_text, HTTPStatus.INTERNAL_SERVER_ERROR, headers)
+ return Response(
+ status=HTTPStatus.INTERNAL_SERVER_ERROR,
+ response=response_text,
+ headers=headers,
+ )
headers = {
'Content-Type': 'application/json',
@@ -189,4 +194,8 @@ def query() -> Union[Tuple[str, int], Tuple[str, int, Dict[str, Any]]]:
'Connection': 'keep-alive',
'Server': 'nginx',
}
- return (response_text, HTTPStatus.OK, headers)
+ return Response(
+ status=HTTPStatus.OK,
+ response=response_text,
+ headers=headers,
+ )
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 6befd24c8..12853e715 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -13,6 +13,7 @@
from flask import Flask, Response, request
from PIL import Image
from werkzeug.datastructures import Headers
+# TODO see if we can go without any werkzeug imports and then no direct requirement
from werkzeug.wsgi import ClosingIterator
from mock_vws._constants import ResultCodes, TargetStatuses
@@ -107,11 +108,11 @@ def validate_request() -> None:
@VWS_FLASK_APP.errorhandler(OopsErrorOccurredResponse)
# TODO update name and type hint here
def handle_unknown_target(e: UnknownTarget) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- response.headers = Headers(e.headers)
- return response
+ return Response(
+ status=e.status_code,
+ response=e.response_text,
+ headers=e.headers,
+ )
@VWS_FLASK_APP.route('/targets', methods=['POST'])
From 15aee094a2c51486adedcf17c4a628d25327bec2 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 11:19:48 +0100
Subject: [PATCH 0207/3455] Undo unnecessary change
---
ci/custom_linters.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/ci/custom_linters.py b/ci/custom_linters.py
index 0ccb890ee..eeead5e81 100644
--- a/ci/custom_linters.py
+++ b/ci/custom_linters.py
@@ -29,7 +29,6 @@ def _tests_from_pattern(ci_pattern: str) -> Set[str]:
From a CI pattern, get all tests ``pytest`` would collect.
"""
tests: Set[str] = set([])
-
args = ['pytest', '--collect-only', ci_pattern, '-q']
result = subprocess.run(args=args, stdout=subprocess.PIPE, check=True)
output = result.stdout
From 317c37cf7581f81884cfce52f8d9c1bbb09cb559 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 11:22:53 +0100
Subject: [PATCH 0208/3455] Use vws_auth_tools for database matchers
---
src/mock_vws/_database_matchers.py | 68 ++----------------------------
1 file changed, 4 insertions(+), 64 deletions(-)
diff --git a/src/mock_vws/_database_matchers.py b/src/mock_vws/_database_matchers.py
index ee8084c17..afcd282c1 100644
--- a/src/mock_vws/_database_matchers.py
+++ b/src/mock_vws/_database_matchers.py
@@ -2,71 +2,11 @@
Helpers for getting databases which match keys given in requests.
"""
-import base64
-import hashlib
-import hmac
from typing import Dict, Iterable, Optional
-from mock_vws.database import VuforiaDatabase
-
-
-def _compute_hmac_base64(key: bytes, data: bytes) -> bytes:
- """
- Return the Base64 encoded HMAC-SHA1 hash of the given `data` using the
- provided `key`.
- """
- hashed = hmac.new(key=key, msg=None, digestmod=hashlib.sha1)
- hashed.update(msg=data)
- return base64.b64encode(s=hashed.digest())
-
+from vws_auth_tools import authorization_header
-def _authorization_header( # pylint: disable=too-many-arguments
- access_key: str,
- secret_key: str,
- method: str,
- content: bytes,
- content_type: str,
- date: str,
- request_path: str,
-) -> str:
- """
- Return an `Authorization` header which can be used for a request made to
- the VWS API with the given attributes.
-
- Args:
- access_key: A VWS server or client access key.
- secret_key: A VWS server or client secret key.
- method: The HTTP method which will be used in the request.
- content: The request body which will be used in the request.
- content_type: The `Content-Type` header which will be used in the
- request.
- date: The current date which must exactly match the date sent in the
- `Date` header.
- request_path: The path to the endpoint which will be used in the
- request.
-
- Returns:
- An `Authorization` header which can be used for a request made to the
- VWS API with the given attributes.
- """
- hashed = hashlib.md5()
- hashed.update(content)
- content_md5_hex = hashed.hexdigest()
-
- components_to_sign = [
- method,
- content_md5_hex,
- content_type,
- date,
- request_path,
- ]
- string_to_sign = '\n'.join(components_to_sign)
- signature = _compute_hmac_base64(
- key=secret_key.encode(),
- data=bytes(string_to_sign, encoding='utf-8'),
- )
- auth_header = f'VWS {access_key}:{signature.decode()}'
- return auth_header
+from mock_vws.database import VuforiaDatabase
def get_database_matching_client_keys(
@@ -96,7 +36,7 @@ def get_database_matching_client_keys(
date = request_headers.get('Date', '')
for database in databases:
- expected_authorization_header = _authorization_header(
+ expected_authorization_header = authorization_header(
access_key=database.client_access_key,
secret_key=database.client_secret_key,
method=request_method,
@@ -138,7 +78,7 @@ def get_database_matching_server_keys(
date = request_headers.get('Date', '')
for database in databases:
- expected_authorization_header = _authorization_header(
+ expected_authorization_header = authorization_header(
access_key=database.server_access_key,
secret_key=database.server_secret_key,
method=request_method,
From d97d1c763b2767d606c66e59b5ee7696e7652660 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 11:25:29 +0100
Subject: [PATCH 0209/3455] Move auth tools to main requirements
---
dev-requirements.txt | 1 -
requirements.txt | 3 ++-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index f050a1c78..c29a5a383 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,7 +1,6 @@
PyYAML==5.3.1
Sphinx-Substitution-Extensions==2020.7.4.1
Sphinx==3.2.1
-VWS-Auth-Tools==2020.5.31.0
VWS-Test-Fixtures==2020.8.2.0
attrs==20.2.0 # Modern attrs is required for pytest
autoflake==1.4
diff --git a/requirements.txt b/requirements.txt
index 57287aae3..a5641ccde 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,6 @@
-backports.zoneinfo==0.2.1
Pillow==7.2.0
+VWS-Auth-Tools==2020.5.31.0
+backports.zoneinfo==0.2.1
requests-mock==1.8.0
requests==2.24.0
wrapt==1.12.1
From ddf0e116d868bc022caaf0a9bfa2f7f170e58126 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 11:46:41 +0100
Subject: [PATCH 0210/3455] Add a few more exception headers
---
src/mock_vws/_services_validators/exceptions.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py
index 98e35867d..f891ddad1 100644
--- a/src/mock_vws/_services_validators/exceptions.py
+++ b/src/mock_vws/_services_validators/exceptions.py
@@ -301,7 +301,9 @@ def __init__(self) -> None:
@property
def headers(self) -> Dict[str, str]:
- return {}
+ return {
+ 'Connection': 'keep-alive',
+ }
class ContentLengthHeaderNotInt(Exception):
@@ -323,7 +325,9 @@ def __init__(self) -> None:
@property
def headers(self) -> Dict[str, str]:
- return {}
+ return {
+ 'Connection': 'Close',
+ }
class UnnecessaryRequestBody(Exception):
From 9311327e3447ee6047a906c6cfe8f519d4d0567c Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 11:54:27 +0100
Subject: [PATCH 0211/3455] Move headers for query errors into exceptions
---
src/mock_vws/_query_validators/exceptions.py | 208 ++++++++++++++++++
.../mock_web_query_api.py | 59 ++---
2 files changed, 223 insertions(+), 44 deletions(-)
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index a4cf34c5c..43e25bdac 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -2,9 +2,11 @@
Exceptions to raise from validators.
"""
+import email.utils
import uuid
from http import HTTPStatus
from pathlib import Path
+from typing import Dict
from mock_vws._constants import ResultCodes
from mock_vws._mock_common import json_dump
@@ -27,6 +29,16 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = 'Date header required.'
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/plain; charset=ISO-8859-1',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class DateFormatNotValid(Exception):
"""
@@ -45,6 +57,17 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.UNAUTHORIZED
self.response_text = 'Malformed date header.'
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/plain; charset=ISO-8859-1',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ 'WWW-Authenticate': 'VWS',
+ }
+
class RequestTimeTooSkewed(Exception):
"""
@@ -68,6 +91,16 @@ def __init__(self) -> None:
}
self.response_text = json_dump(body)
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class BadImage(Exception):
"""
@@ -97,6 +130,16 @@ def __init__(self) -> None:
'}'
)
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class AuthenticationFailure(Exception):
"""
@@ -126,6 +169,17 @@ def __init__(self) -> None:
'}'
)
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ 'WWW-Authenticate': 'VWS',
+ }
+
class AuthenticationFailureGoodFormatting(Exception):
"""
@@ -150,6 +204,17 @@ def __init__(self) -> None:
}
self.response_text = json_dump(body)
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ 'WWW-Authenticate': 'VWS',
+ }
+
class ImageNotGiven(Exception):
"""
@@ -168,6 +233,16 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = 'No image.'
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class AuthHeaderMissing(Exception):
"""
@@ -186,6 +261,17 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.UNAUTHORIZED
self.response_text = 'Authorization header missing.'
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/plain; charset=ISO-8859-1',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ 'WWW-Authenticate': 'VWS',
+ }
+
class MalformedAuthHeader(Exception):
"""
@@ -204,6 +290,17 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.UNAUTHORIZED
self.response_text = 'Malformed authorization header.'
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/plain; charset=ISO-8859-1',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ 'WWW-Authenticate': 'VWS',
+ }
+
class UnknownParameters(Exception):
"""
@@ -222,6 +319,16 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = 'Unknown parameters in the request.'
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class InactiveProject(Exception):
"""
@@ -250,6 +357,16 @@ def __init__(self) -> None:
'}'
)
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class InvalidMaxNumResults(Exception):
"""
@@ -273,6 +390,16 @@ def __init__(self, given_value: str) -> None:
)
self.response_text = invalid_value_message
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class MaxNumResultsOutOfRange(Exception):
"""
@@ -296,6 +423,16 @@ def __init__(self, given_value: str) -> None:
)
self.response_text = integer_out_of_range_message
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class InvalidIncludeTargetData(Exception):
"""
@@ -321,6 +458,16 @@ def __init__(self, given_value: str) -> None:
)
self.response_text = unexpected_target_data_message
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class UnsupportedMediaType(Exception):
"""
@@ -339,6 +486,15 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.UNSUPPORTED_MEDIA_TYPE
self.response_text = ''
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class InvalidAcceptHeader(Exception):
"""
@@ -357,6 +513,15 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.NOT_ACCEPTABLE
self.response_text = ''
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class BoundaryNotInBody(Exception):
"""
@@ -378,6 +543,16 @@ def __init__(self) -> None:
'Could find no Content-Disposition header within part'
)
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/html;charset=UTF-8',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class NoBoundaryFound(Exception):
"""
@@ -399,6 +574,16 @@ def __init__(self) -> None:
'Unable to get boundary for multipart'
)
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/html;charset=UTF-8',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
class QueryOutOfBounds(Exception):
"""
@@ -422,6 +607,17 @@ def __init__(self) -> None:
text = str(oops_resp_file.read_text())
self.response_text = text
+ @property
+ def headers(self) -> Dict[str, str]:
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ return {
+ 'Content-Type': 'text/html; charset=ISO-8859-1',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ 'Cache-Control': 'must-revalidate,no-cache,no-store',
+ }
+
class ContentLengthHeaderTooLarge(Exception):
"""
@@ -440,6 +636,12 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.GATEWAY_TIMEOUT
self.response_text = ''
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {
+ 'Connection': 'keep-alive',
+ }
+
class ContentLengthHeaderNotInt(Exception):
"""
@@ -457,3 +659,9 @@ def __init__(self) -> None:
super().__init__()
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = ''
+
+ @property
+ def headers(self) -> Dict[str, str]:
+ return {
+ 'Connection': 'Close',
+ }
diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
index f3ff80b63..6d92a470f 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
@@ -81,59 +81,30 @@ def run_validators(
request_method=request.method,
databases=instance.databases,
)
- except DateHeaderNotGiven as exc:
- content_type = 'text/plain; charset=ISO-8859-1'
- context.headers['Content-Type'] = content_type
- context.status_code = exc.status_code
- return exc.response_text
except (
AuthHeaderMissing,
+ AuthenticationFailure,
+ AuthenticationFailureGoodFormatting,
+ BadImage,
+ BoundaryNotInBody,
DateFormatNotValid,
- MalformedAuthHeader,
- ) as exc:
- content_type = 'text/plain; charset=ISO-8859-1'
- context.headers['Content-Type'] = content_type
- context.headers['WWW-Authenticate'] = 'VWS'
- context.status_code = exc.status_code
- return exc.response_text
- except (AuthenticationFailure, AuthenticationFailureGoodFormatting) as exc:
- context.headers['WWW-Authenticate'] = 'VWS'
- context.status_code = exc.status_code
- return exc.response_text
- except (
- RequestTimeTooSkewed,
+ DateHeaderNotGiven,
ImageNotGiven,
- UnknownParameters,
InactiveProject,
+ InvalidAcceptHeader,
InvalidIncludeTargetData,
InvalidMaxNumResults,
+ MalformedAuthHeader,
MaxNumResultsOutOfRange,
- BadImage,
+ NoBoundaryFound,
+ RequestTimeTooSkewed,
+ UnknownParameters,
+ UnsupportedMediaType,
+ ContentLengthHeaderNotInt,
+ ContentLengthHeaderTooLarge,
+ QueryOutOfBounds,
) as exc:
- context.status_code = exc.status_code
- return exc.response_text
- except (UnsupportedMediaType, InvalidAcceptHeader) as exc:
- context.headers.pop('Content-Type')
- context.status_code = exc.status_code
- return exc.response_text
- except (NoBoundaryFound, BoundaryNotInBody) as exc:
- content_type = 'text/html;charset=UTF-8'
- context.headers['Content-Type'] = content_type
- context.status_code = exc.status_code
- return exc.response_text
- except QueryOutOfBounds as exc:
- content_type = 'text/html; charset=ISO-8859-1'
- context.headers['Content-Type'] = content_type
- cache_control = 'must-revalidate,no-cache,no-store'
- context.headers['Cache-Control'] = cache_control
- context.status_code = exc.status_code
- return exc.response_text
- except ContentLengthHeaderNotInt as exc:
- context.headers = {'Connection': 'Close'}
- context.status_code = exc.status_code
- return exc.response_text
- except ContentLengthHeaderTooLarge as exc:
- context.headers = {'Connection': 'keep-alive'}
+ context.headers = exc.headers
context.status_code = exc.status_code
return exc.response_text
From 7f82a50645c6f91acc77cca09c183102899352e3 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 15:33:51 +0100
Subject: [PATCH 0212/3455] Remove useless header property functions
---
src/mock_vws/_query_validators/exceptions.py | 92 +++++---------------
1 file changed, 21 insertions(+), 71 deletions(-)
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index 43e25bdac..1b230bd2a 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -6,7 +6,6 @@
import uuid
from http import HTTPStatus
from pathlib import Path
-from typing import Dict
from mock_vws._constants import ResultCodes
from mock_vws._mock_common import json_dump
@@ -28,11 +27,8 @@ def __init__(self) -> None:
super().__init__()
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = 'Date header required.'
-
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'text/plain; charset=ISO-8859-1',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -56,11 +52,8 @@ def __init__(self) -> None:
super().__init__()
self.status_code = HTTPStatus.UNAUTHORIZED
self.response_text = 'Malformed date header.'
-
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'text/plain; charset=ISO-8859-1',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -90,11 +83,8 @@ def __init__(self) -> None:
'result_code': ResultCodes.REQUEST_TIME_TOO_SKEWED.value,
}
self.response_text = json_dump(body)
-
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -130,10 +120,8 @@ def __init__(self) -> None:
'}'
)
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -168,11 +156,8 @@ def __init__(self) -> None:
f'"result_code":"{result_code}"'
'}'
)
-
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -203,11 +188,8 @@ def __init__(self) -> None:
'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
}
self.response_text = json_dump(body)
-
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -233,10 +215,8 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = 'No image.'
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -261,10 +241,8 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.UNAUTHORIZED
self.response_text = 'Authorization header missing.'
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'text/plain; charset=ISO-8859-1',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -290,10 +268,8 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.UNAUTHORIZED
self.response_text = 'Malformed authorization header.'
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'text/plain; charset=ISO-8859-1',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -319,10 +295,8 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = 'Unknown parameters in the request.'
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -357,10 +331,8 @@ def __init__(self) -> None:
'}'
)
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -390,10 +362,8 @@ def __init__(self, given_value: str) -> None:
)
self.response_text = invalid_value_message
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -423,10 +393,8 @@ def __init__(self, given_value: str) -> None:
)
self.response_text = integer_out_of_range_message
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -458,10 +426,8 @@ def __init__(self, given_value: str) -> None:
)
self.response_text = unexpected_target_data_message
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -486,10 +452,8 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.UNSUPPORTED_MEDIA_TYPE
self.response_text = ''
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Connection': 'keep-alive',
'Server': 'nginx',
'Date': date,
@@ -513,10 +477,8 @@ def __init__(self) -> None:
self.status_code = HTTPStatus.NOT_ACCEPTABLE
self.response_text = ''
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Connection': 'keep-alive',
'Server': 'nginx',
'Date': date,
@@ -543,10 +505,8 @@ def __init__(self) -> None:
'Could find no Content-Disposition header within part'
)
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'text/html;charset=UTF-8',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -574,10 +534,8 @@ def __init__(self) -> None:
'Unable to get boundary for multipart'
)
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'text/html;charset=UTF-8',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -607,10 +565,8 @@ def __init__(self) -> None:
text = str(oops_resp_file.read_text())
self.response_text = text
- @property
- def headers(self) -> Dict[str, str]:
date = email.utils.formatdate(None, localtime=False, usegmt=True)
- return {
+ self.headers = {
'Content-Type': 'text/html; charset=ISO-8859-1',
'Connection': 'keep-alive',
'Server': 'nginx',
@@ -635,10 +591,7 @@ def __init__(self) -> None:
super().__init__()
self.status_code = HTTPStatus.GATEWAY_TIMEOUT
self.response_text = ''
-
- @property
- def headers(self) -> Dict[str, str]:
- return {
+ self.headers = {
'Connection': 'keep-alive',
}
@@ -659,9 +612,6 @@ def __init__(self) -> None:
super().__init__()
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = ''
-
- @property
- def headers(self) -> Dict[str, str]:
- return {
+ self.headers = {
'Connection': 'Close',
}
From d8455cd0b16542672b1e1b56eeb4c545dad2a8fd Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 17:02:24 +0100
Subject: [PATCH 0213/3455] For services exceptions use headers from exception
classes
---
.../mock_web_services_api.py | 23 ++----
.../_services_validators/exceptions.py | 79 +++++++++++++++++++
2 files changed, 85 insertions(+), 17 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index d6912d873..b7ef231be 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -89,26 +89,15 @@ def run_validators(
BadImage,
ImageTooLarge,
RequestTimeTooSkewed,
+ ContentLengthHeaderTooLarge,
+ ContentLengthHeaderNotInt,
+ OopsErrorOccurredResponse,
+ UnnecessaryRequestBody,
) as exc:
+ context.headers = exc.headers
context.status_code = exc.status_code
return exc.response_text
- except OopsErrorOccurredResponse as exc:
- content_type = 'text/html; charset=UTF-8'
- context.headers['Content-Type'] = content_type
- context.status_code = exc.status_code
- return exc.response_text
- except ContentLengthHeaderTooLarge as exc:
- context.headers = {'Connection': 'keep-alive'}
- context.status_code = exc.status_code
- return exc.response_text
- except ContentLengthHeaderNotInt as exc:
- context.headers = {'Connection': 'Close'}
- context.status_code = exc.status_code
- return exc.response_text
- except UnnecessaryRequestBody as exc:
- context.headers.pop('Content-Type')
- context.status_code = exc.status_code
- return exc.response_text
+
return wrapped(*args, **kwargs)
diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py
index ef0cea89b..807f1bb67 100644
--- a/src/mock_vws/_services_validators/exceptions.py
+++ b/src/mock_vws/_services_validators/exceptions.py
@@ -2,6 +2,7 @@
Exceptions to raise from validators.
"""
+import email.utils
import uuid
from http import HTTPStatus
from pathlib import Path
@@ -31,6 +32,13 @@ def __init__(self) -> None:
'result_code': ResultCodes.UNKNOWN_TARGET.value,
}
self.response_text = json_dump(body)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ self.headers = {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
class ProjectInactive(Exception):
@@ -54,6 +62,13 @@ def __init__(self) -> None:
'result_code': ResultCodes.PROJECT_INACTIVE.value,
}
self.response_text = json_dump(body)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ self.headers = {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
class AuthenticationFailure(Exception):
@@ -77,6 +92,13 @@ def __init__(self) -> None:
'result_code': ResultCodes.AUTHENTICATION_FAILURE.value,
}
self.response_text = json_dump(body)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ self.headers = {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
class Fail(Exception):
@@ -99,6 +121,13 @@ def __init__(self, status_code: int) -> None:
'result_code': ResultCodes.FAIL.value,
}
self.response_text = json_dump(body)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ self.headers = {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
class MetadataTooLarge(Exception):
@@ -122,6 +151,13 @@ def __init__(self) -> None:
'result_code': ResultCodes.METADATA_TOO_LARGE.value,
}
self.response_text = json_dump(body)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ self.headers = {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
class TargetNameExist(Exception):
@@ -145,6 +181,13 @@ def __init__(self) -> None:
'result_code': ResultCodes.TARGET_NAME_EXIST.value,
}
self.response_text = json_dump(body)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ self.headers = {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
class OopsErrorOccurredResponse(Exception):
@@ -170,6 +213,13 @@ def __init__(self) -> None:
oops_resp_file = resources_dir / filename
text = str(oops_resp_file.read_text())
self.response_text = text
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ self.headers = {
+ 'Content-Type': 'text/html; charset=UTF-8',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
class BadImage(Exception):
@@ -193,6 +243,13 @@ def __init__(self) -> None:
'result_code': ResultCodes.BAD_IMAGE.value,
}
self.response_text = json_dump(body)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ self.headers = {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
class ImageTooLarge(Exception):
@@ -216,6 +273,13 @@ def __init__(self) -> None:
'result_code': ResultCodes.IMAGE_TOO_LARGE.value,
}
self.response_text = json_dump(body)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ self.headers = {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
class RequestTimeTooSkewed(Exception):
@@ -239,6 +303,13 @@ def __init__(self) -> None:
'result_code': ResultCodes.REQUEST_TIME_TOO_SKEWED.value,
}
self.response_text = json_dump(body)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ self.headers = {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
class ContentLengthHeaderTooLarge(Exception):
@@ -257,6 +328,7 @@ def __init__(self) -> None:
super().__init__()
self.status_code = HTTPStatus.GATEWAY_TIMEOUT
self.response_text = ''
+ self.headers = {'Connection': 'keep-alive'}
class ContentLengthHeaderNotInt(Exception):
@@ -275,6 +347,7 @@ def __init__(self) -> None:
super().__init__()
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = ''
+ self.headers = {'Connection': 'Close'}
class UnnecessaryRequestBody(Exception):
@@ -293,3 +366,9 @@ def __init__(self) -> None:
super().__init__()
self.status_code = HTTPStatus.BAD_REQUEST
self.response_text = ''
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ self.headers = {
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
From afd811b58f6da5f5e25b147be4b37dc9edb494b2 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 17:19:30 +0100
Subject: [PATCH 0214/3455] More explicit header passing
---
.../_requests_mock_server/decorators.py | 7 ----
.../mock_web_query_api.py | 15 +++++--
.../mock_web_services_api.py | 41 +++++++++++++++++++
3 files changed, 52 insertions(+), 11 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/decorators.py b/src/mock_vws/_requests_mock_server/decorators.py
index 24d43991d..a331e3bdc 100644
--- a/src/mock_vws/_requests_mock_server/decorators.py
+++ b/src/mock_vws/_requests_mock_server/decorators.py
@@ -135,11 +135,6 @@ def __enter__(self) -> 'MockVWS':
Returns:
``self``.
"""
- headers = {
- 'Connection': 'keep-alive',
- 'Content-Type': 'application/json',
- 'Server': 'nginx',
- }
with Mocker(real_http=self._real_http) as mock:
for route in self._mock_vws_api.routes:
@@ -153,7 +148,6 @@ def __enter__(self) -> 'MockVWS':
method=http_method,
url=re.compile(url_pattern),
text=getattr(self._mock_vws_api, route.route_name),
- headers=headers,
)
for route in self._mock_vwq_api.routes:
@@ -167,7 +161,6 @@ def __enter__(self) -> 'MockVWS':
method=http_method,
url=re.compile(url_pattern),
text=getattr(self._mock_vwq_api, route.route_name),
- headers=headers,
)
self._mock = mock
diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
index 6d92a470f..37e473500 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
@@ -232,10 +232,17 @@ def query(
filename = 'match_processing_response.html'
match_processing_resp_file = resources_dir / filename
context.status_code = HTTPStatus.INTERNAL_SERVER_ERROR
- cache_control = 'must-revalidate,no-cache,no-store'
- context.headers['Cache-Control'] = cache_control
- content_type = 'text/html; charset=ISO-8859-1'
- context.headers['Content-Type'] = content_type
+ context.headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'text/html; charset=ISO-8859-1',
+ 'Server': 'nginx',
+ 'Cache-Control': 'must-revalidate,no-cache,no-store',
+ }
return Path(match_processing_resp_file).read_text()
+ context.headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ }
return response_text
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index b7ef231be..4222af1b4 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -249,6 +249,11 @@ def add_target(
)
database.targets.add(new_target)
+ context.headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ }
context.status_code = HTTPStatus.CREATED
body = {
'transaction_id': uuid.uuid4().hex,
@@ -277,6 +282,11 @@ def delete_target(
request_path=request.path,
databases=self.databases,
)
+ context.headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ }
if target.status == TargetStatuses.PROCESSING.value:
context.status_code = HTTPStatus.FORBIDDEN
@@ -317,6 +327,11 @@ def database_summary(
)
assert isinstance(database, VuforiaDatabase)
+ context.headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ }
body = {
'result_code': ResultCodes.SUCCESS.value,
'transaction_id': uuid.uuid4().hex,
@@ -356,6 +371,11 @@ def target_list(
)
assert isinstance(database, VuforiaDatabase)
+ context.headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ }
results = [target.target_id for target in database.not_deleted_targets]
body: Dict[str, Union[str, List[str]]] = {
@@ -390,6 +410,11 @@ def get_target(
'tracking_rating': target.tracking_rating,
'reco_rating': target.reco_rating,
}
+ context.headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ }
body = {
'result_code': ResultCodes.SUCCESS.value,
@@ -439,6 +464,11 @@ def get_duplicates(
and other.active_flag
]
+ context.headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ }
body = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.SUCCESS.value,
@@ -476,6 +506,11 @@ def update_target(
)
assert isinstance(database, VuforiaDatabase)
+ context.headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ }
if target.status != TargetStatuses.SUCCESS.value:
context.status_code = HTTPStatus.FORBIDDEN
@@ -561,6 +596,12 @@ def target_summary(
)
assert isinstance(database, VuforiaDatabase)
+ context.headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ }
+
body = {
'status': target.status,
'transaction_id': uuid.uuid4().hex,
From 0f563c81c825242cdc36c9008f0d3bb1d5ff4b23 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 17:28:09 +0100
Subject: [PATCH 0215/3455] Move MatchProcessing details to exceptions helper
---
src/mock_vws/_query_validators/exceptions.py | 34 +++++++++++++++++++
.../mock_web_query_api.py | 29 +++++-----------
2 files changed, 42 insertions(+), 21 deletions(-)
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index 1b230bd2a..79cd57cfb 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -615,3 +615,37 @@ def __init__(self) -> None:
self.headers = {
'Connection': 'Close',
}
+
+
+class MatchProcessing(Exception):
+ """
+ Exception raised a target is matched which is processing or recently
+ deleted.
+ """
+
+ def __init__(self) -> None:
+ """
+ Attributes:
+ status_code: The status code to use in a response if this is
+ raised.
+ response_text: The response text to use in a response if this is
+ raised.
+ """
+ self.status_code = HTTPStatus.INTERNAL_SERVER_ERROR
+ self.headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'text/html; charset=ISO-8859-1',
+ 'Server': 'nginx',
+ 'Cache-Control': 'must-revalidate,no-cache,no-store',
+ }
+ # We return an example 500 response.
+ # Each response given by Vuforia is different.
+ #
+ # Sometimes Vuforia will ignore matching targets with the
+ # processing status, but we choose to:
+ # * Do the most unexpected thing.
+ # * Be consistent with every response.
+ resources_dir = Path(__file__).parent.parent / 'resources'
+ filename = 'match_processing_response.html'
+ match_processing_resp_file = resources_dir / filename
+ self.response_text = Path(match_processing_resp_file).read_text()
diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
index 37e473500..4a7e26123 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
@@ -5,8 +5,6 @@
https://library.vuforia.com/articles/Solution/How-To-Perform-an-Image-Recognition-Query
"""
-from http import HTTPStatus
-from pathlib import Path
from typing import Any, Callable, Dict, Set, Tuple, Union
import wrapt
@@ -41,6 +39,7 @@
InvalidIncludeTargetData,
InvalidMaxNumResults,
MalformedAuthHeader,
+ MatchProcessing,
MaxNumResultsOutOfRange,
NoBoundaryFound,
QueryOutOfBounds,
@@ -108,7 +107,12 @@ def run_validators(
context.status_code = exc.status_code
return exc.response_text
- return wrapped(*args, **kwargs)
+ try:
+ return wrapped(*args, **kwargs)
+ except MatchProcessing as exc:
+ context.headers = exc.headers
+ context.status_code = exc.status_code
+ return exc.response_text
def route(
@@ -221,24 +225,7 @@ def query(
ActiveMatchingTargetsDeleteProcessing,
MatchingTargetsWithProcessingStatus,
):
- # We return an example 500 response.
- # Each response given by Vuforia is different.
- #
- # Sometimes Vuforia will ignore matching targets with the
- # processing status, but we choose to:
- # * Do the most unexpected thing.
- # * Be consistent with every response.
- resources_dir = Path(__file__).parent.parent / 'resources'
- filename = 'match_processing_response.html'
- match_processing_resp_file = resources_dir / filename
- context.status_code = HTTPStatus.INTERNAL_SERVER_ERROR
- context.headers = {
- 'Connection': 'keep-alive',
- 'Content-Type': 'text/html; charset=ISO-8859-1',
- 'Server': 'nginx',
- 'Cache-Control': 'must-revalidate,no-cache,no-store',
- }
- return Path(match_processing_resp_file).read_text()
+ raise MatchProcessing
context.headers = {
'Connection': 'keep-alive',
From 4e38815e85d5e17a42fa3b0896978c0d4b1030f4 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 17:36:13 +0100
Subject: [PATCH 0216/3455] Use new exceptions in more places
---
.../mock_web_services_api.py | 24 ++++++++-----------
1 file changed, 10 insertions(+), 14 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 4222af1b4..7650b7084 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -98,7 +98,12 @@ def run_validators(
context.status_code = exc.status_code
return exc.response_text
- return wrapped(*args, **kwargs)
+ try:
+ return wrapped(*args, **kwargs)
+ except Fail as exc:
+ context.headers = exc.headers
+ context.status_code = exc.status_code
+ return exc.response_text
ROUTES = set([])
@@ -526,23 +531,14 @@ def update_target(
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)
+ raise Fail(status_code=HTTPStatus.BAD_REQUEST)
+
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']
+ if application_metadata is None:
+ raise Fail(status_code=HTTPStatus.BAD_REQUEST)
target.application_metadata = application_metadata
if 'name' in request.json():
From 89dec44e78a7aa0acd029527d9c60b8b4ec234c8 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 17:41:40 +0100
Subject: [PATCH 0217/3455] use more of the validator exceptions
---
.../mock_web_services_api.py | 25 +++-----
.../_services_validators/exceptions.py | 59 +++++++++++++++++++
2 files changed, 67 insertions(+), 17 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 7650b7084..1c933215a 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -44,6 +44,7 @@
TargetNameExist,
UnknownTarget,
UnnecessaryRequestBody,
+ TargetStatusNotSuccess,
)
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
@@ -100,7 +101,7 @@ def run_validators(
try:
return wrapped(*args, **kwargs)
- except Fail as exc:
+ except (Fail, TargetStatusNotSuccess) as exc:
context.headers = exc.headers
context.status_code = exc.status_code
return exc.response_text
@@ -287,22 +288,17 @@ def delete_target(
request_path=request.path,
databases=self.databases,
)
+
+ if target.status == TargetStatuses.PROCESSING.value:
+ raise TargetStatusProcessing
+
+ target.delete()
context.headers = {
'Connection': 'keep-alive',
'Content-Type': 'application/json',
'Server': 'nginx',
}
- if target.status == TargetStatuses.PROCESSING.value:
- context.status_code = HTTPStatus.FORBIDDEN
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.TARGET_STATUS_PROCESSING.value,
- }
- return json_dump(body)
-
- target.delete()
-
body = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.SUCCESS.value,
@@ -518,12 +514,7 @@ def update_target(
}
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)
+ raise TargetStatusNotSuccess
if 'width' in request.json():
target.width = request.json()['width']
diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py
index 807f1bb67..495dd3c09 100644
--- a/src/mock_vws/_services_validators/exceptions.py
+++ b/src/mock_vws/_services_validators/exceptions.py
@@ -372,3 +372,62 @@ def __init__(self) -> None:
'Server': 'nginx',
'Date': date,
}
+
+
+class TargetStatusNotSuccess(Exception):
+ """
+ Exception raised when trying to update a target that does not have a
+ success status.
+ """
+
+ def __init__(self) -> None:
+ """
+ Attributes:
+ status_code: The status code to use in a response if this is
+ raised.
+ response_text: The response text to use in a response if this is
+ raised.
+ """
+ super().__init__()
+ self.status_code = HTTPStatus.FORBIDDEN
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.TARGET_STATUS_NOT_SUCCESS.value,
+ }
+ self.response_text = json_dump(body)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ self.headers = {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+
+
+class TargetStatusProcessing(Exception):
+ """
+ Exception raised when trying to delete a target which is processing.
+ """
+
+ def __init__(self) -> None:
+ """
+ Attributes:
+ status_code: The status code to use in a response if this is
+ raised.
+ response_text: The response text to use in a response if this is
+ raised.
+ """
+ super().__init__()
+ self.status_code = HTTPStatus.FORBIDDEN
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.TARGET_STATUS_PROCESSING.value,
+ }
+ self.response_text = json_dump(body)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ self.headers = {
+ 'Content-Type': 'application/json',
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
From 451c2dc56168374d0b4f11b869fb6e6a6b407eb5 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 17:50:57 +0100
Subject: [PATCH 0218/3455] Progress towards getting rid of set date header
decorator
---
.../mock_web_services_api.py | 22 +++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 1c933215a..ae809d38b 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -7,6 +7,7 @@
import base64
import datetime
+import email.utils
import io
import itertools
import random
@@ -45,6 +46,7 @@
UnknownTarget,
UnnecessaryRequestBody,
TargetStatusNotSuccess,
+ TargetStatusProcessing,
)
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
@@ -101,7 +103,7 @@ def run_validators(
try:
return wrapped(*args, **kwargs)
- except (Fail, TargetStatusNotSuccess) as exc:
+ except (Fail, TargetStatusNotSuccess,TargetStatusProcessing) as exc:
context.headers = exc.headers
context.status_code = exc.status_code
return exc.response_text
@@ -144,7 +146,7 @@ def decorator(method: Callable[..., str]) -> Callable[..., str]:
decorators = [
run_validators,
- set_date_header,
+ # set_date_header,
set_content_length_header,
]
@@ -255,10 +257,12 @@ def add_target(
)
database.targets.add(new_target)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
'Connection': 'keep-alive',
'Content-Type': 'application/json',
'Server': 'nginx',
+ 'Date': date,
}
context.status_code = HTTPStatus.CREATED
body = {
@@ -293,10 +297,12 @@ def delete_target(
raise TargetStatusProcessing
target.delete()
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
'Connection': 'keep-alive',
'Content-Type': 'application/json',
'Server': 'nginx',
+ 'Date': date,
}
body = {
@@ -328,10 +334,12 @@ def database_summary(
)
assert isinstance(database, VuforiaDatabase)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
'Connection': 'keep-alive',
'Content-Type': 'application/json',
'Server': 'nginx',
+ 'Date': date,
}
body = {
'result_code': ResultCodes.SUCCESS.value,
@@ -372,10 +380,12 @@ def target_list(
)
assert isinstance(database, VuforiaDatabase)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
'Connection': 'keep-alive',
'Content-Type': 'application/json',
'Server': 'nginx',
+ 'Date': date,
}
results = [target.target_id for target in database.not_deleted_targets]
@@ -411,10 +421,12 @@ def get_target(
'tracking_rating': target.tracking_rating,
'reco_rating': target.reco_rating,
}
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
'Connection': 'keep-alive',
'Content-Type': 'application/json',
'Server': 'nginx',
+ 'Date': date,
}
body = {
@@ -465,10 +477,12 @@ def get_duplicates(
and other.active_flag
]
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
'Connection': 'keep-alive',
'Content-Type': 'application/json',
'Server': 'nginx',
+ 'Date': date,
}
body = {
'transaction_id': uuid.uuid4().hex,
@@ -507,10 +521,12 @@ def update_target(
)
assert isinstance(database, VuforiaDatabase)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
'Connection': 'keep-alive',
'Content-Type': 'application/json',
'Server': 'nginx',
+ 'Date': date,
}
if target.status != TargetStatuses.SUCCESS.value:
@@ -583,10 +599,12 @@ def target_summary(
)
assert isinstance(database, VuforiaDatabase)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
'Connection': 'keep-alive',
'Content-Type': 'application/json',
'Server': 'nginx',
+ 'Date': date,
}
body = {
From ec62097c81c43522ffd33ad8eb1e5da1aeeddf20 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 17:59:32 +0100
Subject: [PATCH 0219/3455] Remove set date header decorator
---
src/mock_vws/_mock_common.py | 31 -------------------
src/mock_vws/_query_validators/exceptions.py | 2 ++
.../mock_web_query_api.py | 5 +--
.../mock_web_services_api.py | 2 --
4 files changed, 5 insertions(+), 35 deletions(-)
diff --git a/src/mock_vws/_mock_common.py b/src/mock_vws/_mock_common.py
index 1da413cc1..bbd5e6f76 100644
--- a/src/mock_vws/_mock_common.py
+++ b/src/mock_vws/_mock_common.py
@@ -62,34 +62,3 @@ def set_content_length_header(
result = wrapped(*args, **kwargs)
context.headers['Content-Length'] = str(len(result))
return result
-
-
-@wrapt.decorator
-def set_date_header(
- wrapped: Callable[..., str],
- instance: Any, # pylint: disable=unused-argument
- args: Tuple[_RequestObjectProxy, _Context],
- kwargs: Dict,
-) -> str:
- """
- Set the `Date` header.
-
- Args:
- wrapped: An endpoint function for `requests_mock`.
- instance: The class that the endpoint function is in.
- args: The arguments given to the endpoint function.
- kwargs: The keyword arguments given to the endpoint function.
-
- Returns:
- The result of calling the endpoint.
- """
- _, context = args
- date = email.utils.formatdate(None, localtime=False, usegmt=True)
-
- result = wrapped(*args, **kwargs)
- if (
- context.headers['Connection'] != 'Close'
- and context.status_code != HTTPStatus.GATEWAY_TIMEOUT
- ):
- context.headers['Date'] = date
- return result
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index 79cd57cfb..dac2163b1 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -632,11 +632,13 @@ def __init__(self) -> None:
raised.
"""
self.status_code = HTTPStatus.INTERNAL_SERVER_ERROR
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
self.headers = {
'Connection': 'keep-alive',
'Content-Type': 'text/html; charset=ISO-8859-1',
'Server': 'nginx',
'Cache-Control': 'must-revalidate,no-cache,no-store',
+ 'Date': date,
}
# We return an example 500 response.
# Each response given by Vuforia is different.
diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
index 4a7e26123..b327fd1d6 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
@@ -5,6 +5,7 @@
https://library.vuforia.com/articles/Solution/How-To-Perform-an-Image-Recognition-Query
"""
+import email.utils
from typing import Any, Callable, Dict, Set, Tuple, Union
import wrapt
@@ -15,7 +16,6 @@
from mock_vws._mock_common import (
Route,
set_content_length_header,
- set_date_header,
)
from mock_vws._query_tools import (
ActiveMatchingTargetsDeleteProcessing,
@@ -149,7 +149,6 @@ def decorator(method: Callable[..., str]) -> Callable[..., str]:
decorators = [
run_validators,
- set_date_header,
set_content_length_header,
]
@@ -227,9 +226,11 @@ def query(
):
raise MatchProcessing
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
'Connection': 'keep-alive',
'Content-Type': 'application/json',
'Server': 'nginx',
+ 'Date': date,
}
return response_text
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index ae809d38b..c97a912dd 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -28,7 +28,6 @@
Route,
json_dump,
set_content_length_header,
- set_date_header,
)
from mock_vws._services_validators import run_services_validators
from mock_vws._services_validators.exceptions import (
@@ -146,7 +145,6 @@ def decorator(method: Callable[..., str]) -> Callable[..., str]:
decorators = [
run_validators,
- # set_date_header,
set_content_length_header,
]
From 7f9aef5006a251fbbc3d2bdd18030f001715203a Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 23:01:30 +0100
Subject: [PATCH 0220/3455] Get all tests passing again
---
src/mock_vws/_flask_server/vwq/__init__.py | 30 +---
src/mock_vws/_flask_server/vws/__init__.py | 177 +++++++++++++++------
2 files changed, 136 insertions(+), 71 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 143d96cba..188ca3f3f 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -10,6 +10,7 @@
from werkzeug.wsgi import ClosingIterator
from mock_vws._query_tools import (
+ # TODO remove each of these and just raise the validator exception
ActiveMatchingTargetsDeleteProcessing,
MatchingTargetsWithProcessingStatus,
get_query_match_response_text,
@@ -37,6 +38,7 @@
RequestTimeTooSkewed,
UnknownParameters,
UnsupportedMediaType,
+ MatchProcessing,
)
from ..vws._databases import get_all_databases
@@ -47,6 +49,7 @@
@CLOUDRECO_FLASK_APP.before_request
def validate_request() -> None:
+ request.environ['wsgi.input_terminated'] = True
input_stream_copy = copy.copy(request.input_stream)
request_body = input_stream_copy.read()
databases = get_all_databases()
@@ -127,7 +130,9 @@ def handle_connection_error(
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
@CLOUDRECO_FLASK_APP.errorhandler(QueryOutOfBounds)
+@CLOUDRECO_FLASK_APP.errorhandler(MatchProcessing)
# TODO use a base type for these requests
+# TODO change the name and type hint of this function
def handle_request_time_too_skewed(
e: RequestTimeTooSkewed,
) -> Response:
@@ -163,30 +168,7 @@ def query() -> Response:
ActiveMatchingTargetsDeleteProcessing,
MatchingTargetsWithProcessingStatus,
):
- # We return an example 500 response.
- # Each response given by Vuforia is different.
- #
- # Sometimes Vuforia will ignore matching targets with the
- # processing status, but we choose to:
- # * Do the most unexpected thing.
- # * Be consistent with every response.
- resources_dir = Path(__file__).parent.parent.parent / 'resources'
- filename = 'match_processing_response.html'
- match_processing_resp_file = resources_dir / filename
- content_type = 'text/html; charset=ISO-8859-1'
- headers = {
- 'Content-Type': 'text/html; charset=ISO-8859-1',
- 'Connection': 'keep-alive',
- 'Server': 'nginx',
- 'Date': date,
- 'Cache-Control': 'must-revalidate,no-cache,no-store',
- }
- response_text = match_processing_resp_file.read_text()
- return Response(
- status=HTTPStatus.INTERNAL_SERVER_ERROR,
- response=response_text,
- headers=headers,
- )
+ raise MatchProcessing
headers = {
'Content-Type': 'application/json',
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 12853e715..44a810461 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -3,6 +3,7 @@
"""
import base64
+import email.utils
import io
import json
import uuid
@@ -34,6 +35,8 @@
TargetNameExist,
UnknownTarget,
UnnecessaryRequestBody,
+ TargetStatusNotSuccess,
+ TargetStatusProcessing,
)
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
@@ -47,12 +50,12 @@
# We use a custom response type.
# Without this, a content type is added to all responses.
# Some of our responses need to not have a "Content-Type" header.
-class MyResponse(Response):
+class ResponseNoContentTypeAdded(Response):
def __init__(
self,
- response: Optional[ClosingIterator] = None,
- status: Optional[str] = None,
- headers: Optional[Headers] = None,
+ response: Optional[str] = None,
+ status: Optional[int] = None,
+ headers: Optional[Dict[str, str]] = None,
mimetype: Optional[str] = None,
content_type: Optional[str] = None,
direct_passthrough: bool = False,
@@ -71,17 +74,23 @@ def __init__(
direct_passthrough=direct_passthrough,
)
- if content_type is None and headers and not content_type_from_headers:
- headers_dict = dict(headers)
+ if (
+ content_type is None and
+ self.headers and
+ 'Content-Type' in self.headers and
+ not content_type_from_headers
+ ):
+ headers_dict = dict(self.headers)
headers_dict.pop('Content-Type')
self.headers = Headers(headers_dict)
-VWS_FLASK_APP.response_class = MyResponse
+VWS_FLASK_APP.response_class = ResponseNoContentTypeAdded
@VWS_FLASK_APP.before_request
def validate_request() -> None:
+ request.environ['wsgi.input_terminated'] = True
databases = get_all_databases()
run_services_validators(
request_headers=dict(request.headers),
@@ -106,17 +115,19 @@ def validate_request() -> None:
@VWS_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
@VWS_FLASK_APP.errorhandler(UnnecessaryRequestBody)
@VWS_FLASK_APP.errorhandler(OopsErrorOccurredResponse)
+@VWS_FLASK_APP.errorhandler(TargetStatusProcessing)
+@VWS_FLASK_APP.errorhandler(TargetStatusNotSuccess)
# TODO update name and type hint here
def handle_unknown_target(e: UnknownTarget) -> Response:
- return Response(
- status=e.status_code,
+ return ResponseNoContentTypeAdded(
+ status=e.status_code.value,
response=e.response_text,
headers=e.headers,
)
@VWS_FLASK_APP.route('/targets', methods=['POST'])
-def add_target() -> Tuple[str, int]:
+def add_target() -> Response:
"""
Add a target.
@@ -162,16 +173,28 @@ def add_target() -> Tuple[str, int]:
json=new_target.to_dict(),
)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
body = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.TARGET_CREATED.value,
'target_id': new_target.target_id,
}
- return json_dump(body), HTTPStatus.CREATED
+
+ return Response(
+ status=HTTPStatus.CREATED,
+ response=json_dump(body),
+ headers=headers,
+ )
@VWS_FLASK_APP.route('/targets/', methods=['GET'])
-def get_target(target_id: str) -> Tuple[str, int]:
+def get_target(target_id: str) -> Response:
"""
Get details of a target.
@@ -201,18 +224,28 @@ def get_target(target_id: str) -> Tuple[str, int]:
'reco_rating': target.reco_rating,
}
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
body = {
'result_code': ResultCodes.SUCCESS.value,
'transaction_id': uuid.uuid4().hex,
'target_record': target_record,
'status': target.status,
}
-
- return json_dump(body), HTTPStatus.OK
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
@VWS_FLASK_APP.route('/targets/', methods=['DELETE'])
-def delete_target(target_id: str) -> Tuple[str, int]:
+def delete_target(target_id: str) -> Response:
"""
Delete a target.
@@ -235,11 +268,7 @@ def delete_target(target_id: str) -> Tuple[str, int]:
]
if target.status == TargetStatuses.PROCESSING.value:
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.TARGET_STATUS_PROCESSING.value,
- }
- return json_dump(body), HTTPStatus.FORBIDDEN
+ raise TargetStatusProcessing
delete_url = (
f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/'
@@ -251,11 +280,22 @@ def delete_target(target_id: str) -> Tuple[str, int]:
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.SUCCESS.value,
}
- return json_dump(body), HTTPStatus.OK
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
@VWS_FLASK_APP.route('/summary', methods=['GET'])
-def database_summary() -> Tuple[str, int]:
+def database_summary() -> Response:
"""
Get a database summary report.
@@ -292,11 +332,22 @@ def database_summary() -> Tuple[str, int]:
# This was not always the case.
'request_usage': 0,
}
- return json_dump(body), HTTPStatus.OK
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
@VWS_FLASK_APP.route('/summary/', methods=['GET'])
-def target_summary(target_id: str) -> Tuple[str, int]:
+def target_summary(target_id: str) -> Response:
"""
Get a summary report for a target.
@@ -329,11 +380,22 @@ def target_summary(target_id: str) -> Tuple[str, int]:
'current_month_recos': 0,
'previous_month_recos': 0,
}
- return json_dump(body), HTTPStatus.OK
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
@VWS_FLASK_APP.route('/duplicates/', methods=['GET'])
-def get_duplicates(target_id: str) -> Tuple[str, int]:
+def get_duplicates(target_id: str) -> Response:
"""
Get targets which may be considered duplicates of a given target.
@@ -370,11 +432,22 @@ def get_duplicates(target_id: str) -> Tuple[str, int]:
'similar_targets': similar_targets,
}
- return json_dump(body), HTTPStatus.OK
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
@VWS_FLASK_APP.route('/targets', methods=['GET'])
-def target_list() -> Tuple[str, int]:
+def target_list() -> Response:
"""
Get a list of all targets.
@@ -397,11 +470,22 @@ def target_list() -> Tuple[str, int]:
'result_code': ResultCodes.SUCCESS.value,
'results': results,
}
- return json_dump(body), HTTPStatus.OK
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
@VWS_FLASK_APP.route('/targets/', methods=['PUT'])
-def update_target(target_id: str) -> Tuple[str, int]:
+def update_target(target_id: str) -> Response:
"""
Update a target.
@@ -427,11 +511,7 @@ def update_target(target_id: str) -> Tuple[str, int]:
]
if target.status != TargetStatuses.SUCCESS.value:
- body = {
- 'transaction_id': uuid.uuid4().hex,
- 'result_code': ResultCodes.TARGET_STATUS_NOT_SUCCESS.value,
- }
- return json_dump(body), HTTPStatus.FORBIDDEN
+ raise TargetStatusNotSuccess
update_values = {}
if 'width' in request_json:
@@ -440,21 +520,13 @@ def update_target(target_id: str) -> Tuple[str, int]:
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,
- }
- return json_dump(body), HTTPStatus.BAD_REQUEST
+ raise Fail(status_code=HTTPStatus.BAD_REQUEST)
update_values['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,
- }
- return json_dump(body), HTTPStatus.BAD_REQUEST
application_metadata = request_json['application_metadata']
+ if application_metadata is None:
+ raise Fail(status_code=HTTPStatus.BAD_REQUEST)
update_values['application_metadata'] = application_metadata
if 'name' in request_json:
@@ -471,8 +543,19 @@ def update_target(target_id: str) -> Tuple[str, int]:
)
requests.put(url=put_url, json=update_values)
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
body = {
'result_code': ResultCodes.SUCCESS.value,
'transaction_id': uuid.uuid4().hex,
}
- return json_dump(body), HTTPStatus.OK
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
From c77bfeca43f0bc1465679115c5eadea6391eebce Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 23:04:25 +0100
Subject: [PATCH 0221/3455] Fix some lint issues
---
src/mock_vws/_flask_server/vwq/__init__.py | 8 +++---
.../_flask_server/vwq/_database_matchers.py | 5 +---
src/mock_vws/_flask_server/vws/__init__.py | 27 +++++++++----------
src/mock_vws/_mock_common.py | 2 --
.../mock_web_query_api.py | 5 +---
.../mock_web_services_api.py | 12 +++------
6 files changed, 21 insertions(+), 38 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 188ca3f3f..a9b71f295 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -1,16 +1,14 @@
import copy
import email.utils
from http import HTTPStatus
-from pathlib import Path
-from typing import Any, Dict, Optional, Tuple, Union
+from typing import Optional
import requests
from flask import Flask, Response, request
from werkzeug.datastructures import Headers
from werkzeug.wsgi import ClosingIterator
-from mock_vws._query_tools import (
- # TODO remove each of these and just raise the validator exception
+from mock_vws._query_tools import ( # TODO remove each of these and just raise the validator exception
ActiveMatchingTargetsDeleteProcessing,
MatchingTargetsWithProcessingStatus,
get_query_match_response_text,
@@ -32,13 +30,13 @@
InvalidIncludeTargetData,
InvalidMaxNumResults,
MalformedAuthHeader,
+ MatchProcessing,
MaxNumResultsOutOfRange,
NoBoundaryFound,
QueryOutOfBounds,
RequestTimeTooSkewed,
UnknownParameters,
UnsupportedMediaType,
- MatchProcessing,
)
from ..vws._databases import get_all_databases
diff --git a/src/mock_vws/_flask_server/vwq/_database_matchers.py b/src/mock_vws/_flask_server/vwq/_database_matchers.py
index 1db708149..afcd282c1 100644
--- a/src/mock_vws/_flask_server/vwq/_database_matchers.py
+++ b/src/mock_vws/_flask_server/vwq/_database_matchers.py
@@ -2,14 +2,11 @@
Helpers for getting databases which match keys given in requests.
"""
-import base64
-import hashlib
-import hmac
from typing import Dict, Iterable, Optional
from vws_auth_tools import authorization_header
-from mock_vws.database import VuforiaDatabase
+from mock_vws.database import VuforiaDatabase
def get_database_matching_client_keys(
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 44a810461..421cb364c 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -8,14 +8,12 @@
import json
import uuid
from http import HTTPStatus
-from typing import Dict, List, Tuple, Union, Optional
+from typing import Dict, List, Optional
import requests
from flask import Flask, Response, request
from PIL import Image
from werkzeug.datastructures import Headers
-# TODO see if we can go without any werkzeug imports and then no direct requirement
-from werkzeug.wsgi import ClosingIterator
from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_server_keys
@@ -33,10 +31,10 @@
ProjectInactive,
RequestTimeTooSkewed,
TargetNameExist,
- UnknownTarget,
- UnnecessaryRequestBody,
TargetStatusNotSuccess,
TargetStatusProcessing,
+ UnknownTarget,
+ UnnecessaryRequestBody,
)
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
@@ -44,6 +42,10 @@
from ._constants import STORAGE_BASE_URL
from ._databases import get_all_databases
+# TODO see if we can go without any werkzeug imports and then no direct requirement
+
+
+
VWS_FLASK_APP = Flask(import_name=__name__)
VWS_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
@@ -75,10 +77,10 @@ def __init__(
)
if (
- content_type is None and
- self.headers and
- 'Content-Type' in self.headers and
- not content_type_from_headers
+ content_type is None
+ and self.headers
+ and 'Content-Type' in self.headers
+ and not content_type_from_headers
):
headers_dict = dict(self.headers)
headers_dict.pop('Content-Type')
@@ -101,7 +103,6 @@ def validate_request() -> None:
)
-
@VWS_FLASK_APP.errorhandler(UnknownTarget)
@VWS_FLASK_APP.errorhandler(ProjectInactive)
@VWS_FLASK_APP.errorhandler(AuthenticationFailure)
@@ -252,7 +253,6 @@ def delete_target(target_id: str) -> Response:
Fake implementation of
https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Delete-a-Target
"""
- body: Dict[str, str] = {}
databases = get_all_databases()
database = get_database_matching_server_keys(
request_headers=dict(request.headers),
@@ -302,8 +302,6 @@ def database_summary() -> Response:
Fake implementation of
https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Get-a-Database-Summary-Report
"""
- body: Dict[str, Union[str, int]] = {}
-
databases = get_all_databases()
database = get_database_matching_server_keys(
request_headers=dict(request.headers),
@@ -465,7 +463,7 @@ def target_list() -> Response:
assert isinstance(database, VuforiaDatabase)
results = [target.target_id for target in database.not_deleted_targets]
- body: Dict[str, Union[str, List[str]]] = {
+ body = {
'transaction_id': uuid.uuid4().hex,
'result_code': ResultCodes.SUCCESS.value,
'results': results,
@@ -495,7 +493,6 @@ def update_target(target_id: str) -> Response:
# We do not use ``request.get_json(force=True)`` because this only works
# when the content type is given as ``application/json``.
request_json = json.loads(request.data)
- body: Dict[str, str] = {}
databases = get_all_databases()
database = get_database_matching_server_keys(
request_headers=dict(request.headers),
diff --git a/src/mock_vws/_mock_common.py b/src/mock_vws/_mock_common.py
index bbd5e6f76..21b4ed2ef 100644
--- a/src/mock_vws/_mock_common.py
+++ b/src/mock_vws/_mock_common.py
@@ -2,10 +2,8 @@
Common utilities for creating mock routes.
"""
-import email.utils
import json
from dataclasses import dataclass
-from http import HTTPStatus
from typing import Any, Callable, Dict, FrozenSet, Tuple
import wrapt
diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
index b327fd1d6..829fc3324 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
@@ -13,10 +13,7 @@
from requests_mock.request import _RequestObjectProxy
from requests_mock.response import _Context
-from mock_vws._mock_common import (
- Route,
- set_content_length_header,
-)
+from mock_vws._mock_common import Route, set_content_length_header
from mock_vws._query_tools import (
ActiveMatchingTargetsDeleteProcessing,
MatchingTargetsWithProcessingStatus,
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index c97a912dd..93f131326 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -24,11 +24,7 @@
from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_server_keys
-from mock_vws._mock_common import (
- Route,
- json_dump,
- set_content_length_header,
-)
+from mock_vws._mock_common import Route, json_dump, set_content_length_header
from mock_vws._services_validators import run_services_validators
from mock_vws._services_validators.exceptions import (
AuthenticationFailure,
@@ -42,10 +38,10 @@
ProjectInactive,
RequestTimeTooSkewed,
TargetNameExist,
- UnknownTarget,
- UnnecessaryRequestBody,
TargetStatusNotSuccess,
TargetStatusProcessing,
+ UnknownTarget,
+ UnnecessaryRequestBody,
)
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
@@ -102,7 +98,7 @@ def run_validators(
try:
return wrapped(*args, **kwargs)
- except (Fail, TargetStatusNotSuccess,TargetStatusProcessing) as exc:
+ except (Fail, TargetStatusNotSuccess, TargetStatusProcessing) as exc:
context.headers = exc.headers
context.status_code = exc.status_code
return exc.response_text
From f2181ffbf7cb33f9605a532fc43781638982ffd3 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 23:42:14 +0100
Subject: [PATCH 0222/3455] Use a base validator exception where appropriate
---
src/mock_vws/_flask_server/vwq/__init__.py | 83 +++++--------------
src/mock_vws/_flask_server/vws/__init__.py | 46 ++--------
src/mock_vws/_flask_server/vws/_databases.py | 1 +
src/mock_vws/_query_validators/exceptions.py | 52 +++++++-----
.../_services_validators/exceptions.py | 39 +++++----
src/mock_vws/target.py | 9 ++
6 files changed, 92 insertions(+), 138 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index a9b71f295..563a0d325 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -1,12 +1,11 @@
import copy
import email.utils
from http import HTTPStatus
-from typing import Optional
+from typing import Dict, Optional
import requests
from flask import Flask, Response, request
from werkzeug.datastructures import Headers
-from werkzeug.wsgi import ClosingIterator
from mock_vws._query_tools import ( # TODO remove each of these and just raise the validator exception
ActiveMatchingTargetsDeleteProcessing,
@@ -15,28 +14,8 @@
)
from mock_vws._query_validators import run_query_validators
from mock_vws._query_validators.exceptions import (
- AuthenticationFailure,
- AuthenticationFailureGoodFormatting,
- AuthHeaderMissing,
- BadImage,
- BoundaryNotInBody,
- ContentLengthHeaderNotInt,
- ContentLengthHeaderTooLarge,
- DateFormatNotValid,
- DateHeaderNotGiven,
- ImageNotGiven,
- InactiveProject,
- InvalidAcceptHeader,
- InvalidIncludeTargetData,
- InvalidMaxNumResults,
- MalformedAuthHeader,
MatchProcessing,
- MaxNumResultsOutOfRange,
- NoBoundaryFound,
- QueryOutOfBounds,
- RequestTimeTooSkewed,
- UnknownParameters,
- UnsupportedMediaType,
+ ValidatorException,
)
from ..vws._databases import get_all_databases
@@ -63,12 +42,12 @@ def validate_request() -> None:
# We use a custom response type.
# Without this, a content type is added to all responses.
# Some of our responses need to not have a "Content-Type" header.
-class MyResponse(Response):
+class ResponseNoContentTypeAdded(Response):
def __init__(
self,
- response: Optional[ClosingIterator] = None,
- status: Optional[str] = None,
- headers: Optional[Headers] = None,
+ response: Optional[str] = None,
+ status: Optional[int] = None,
+ headers: Optional[Dict[str, str]] = None,
mimetype: Optional[str] = None,
content_type: Optional[str] = None,
direct_passthrough: bool = False,
@@ -87,13 +66,18 @@ def __init__(
direct_passthrough=direct_passthrough,
)
- if content_type is None and headers and not content_type_from_headers:
- headers_dict = dict(headers)
+ if (
+ content_type is None
+ and self.headers
+ and 'Content-Type' in self.headers
+ and not content_type_from_headers
+ ):
+ headers_dict = dict(self.headers)
headers_dict.pop('Content-Type')
self.headers = Headers(headers_dict)
-CLOUDRECO_FLASK_APP.response_class = MyResponse
+CLOUDRECO_FLASK_APP.response_class = ResponseNoContentTypeAdded
@CLOUDRECO_FLASK_APP.errorhandler(requests.exceptions.ConnectionError)
@@ -107,38 +91,13 @@ def handle_connection_error(
raise e
-@CLOUDRECO_FLASK_APP.errorhandler(AuthHeaderMissing)
-@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailure)
-@CLOUDRECO_FLASK_APP.errorhandler(AuthenticationFailureGoodFormatting)
-@CLOUDRECO_FLASK_APP.errorhandler(BadImage)
-@CLOUDRECO_FLASK_APP.errorhandler(BoundaryNotInBody)
-@CLOUDRECO_FLASK_APP.errorhandler(DateFormatNotValid)
-@CLOUDRECO_FLASK_APP.errorhandler(DateHeaderNotGiven)
-@CLOUDRECO_FLASK_APP.errorhandler(ImageNotGiven)
-@CLOUDRECO_FLASK_APP.errorhandler(InactiveProject)
-@CLOUDRECO_FLASK_APP.errorhandler(InvalidAcceptHeader)
-@CLOUDRECO_FLASK_APP.errorhandler(InvalidIncludeTargetData)
-@CLOUDRECO_FLASK_APP.errorhandler(InvalidMaxNumResults)
-@CLOUDRECO_FLASK_APP.errorhandler(MalformedAuthHeader)
-@CLOUDRECO_FLASK_APP.errorhandler(MaxNumResultsOutOfRange)
-@CLOUDRECO_FLASK_APP.errorhandler(NoBoundaryFound)
-@CLOUDRECO_FLASK_APP.errorhandler(RequestTimeTooSkewed)
-@CLOUDRECO_FLASK_APP.errorhandler(UnknownParameters)
-@CLOUDRECO_FLASK_APP.errorhandler(UnsupportedMediaType)
-@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
-@CLOUDRECO_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
-@CLOUDRECO_FLASK_APP.errorhandler(QueryOutOfBounds)
-@CLOUDRECO_FLASK_APP.errorhandler(MatchProcessing)
-# TODO use a base type for these requests
-# TODO change the name and type hint of this function
-def handle_request_time_too_skewed(
- e: RequestTimeTooSkewed,
-) -> Response:
- response = Response()
- response.status_code = e.status_code
- response.set_data(e.response_text)
- response.headers = Headers(e.headers)
- return response
+@CLOUDRECO_FLASK_APP.errorhandler(ValidatorException)
+def handle_exceptions(exc: ValidatorException) -> Response:
+ return ResponseNoContentTypeAdded(
+ status=exc.status_code.value,
+ response=exc.response_text,
+ headers=exc.headers,
+ )
@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 421cb364c..6c5063d43 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -20,21 +20,10 @@
from mock_vws._mock_common import json_dump
from mock_vws._services_validators import run_services_validators
from mock_vws._services_validators.exceptions import (
- AuthenticationFailure,
- BadImage,
- ContentLengthHeaderNotInt,
- ContentLengthHeaderTooLarge,
Fail,
- ImageTooLarge,
- MetadataTooLarge,
- OopsErrorOccurredResponse,
- ProjectInactive,
- RequestTimeTooSkewed,
- TargetNameExist,
TargetStatusNotSuccess,
TargetStatusProcessing,
- UnknownTarget,
- UnnecessaryRequestBody,
+ ValidatorException,
)
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
@@ -42,10 +31,6 @@
from ._constants import STORAGE_BASE_URL
from ._databases import get_all_databases
-# TODO see if we can go without any werkzeug imports and then no direct requirement
-
-
-
VWS_FLASK_APP = Flask(import_name=__name__)
VWS_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
@@ -103,27 +88,12 @@ def validate_request() -> None:
)
-@VWS_FLASK_APP.errorhandler(UnknownTarget)
-@VWS_FLASK_APP.errorhandler(ProjectInactive)
-@VWS_FLASK_APP.errorhandler(AuthenticationFailure)
-@VWS_FLASK_APP.errorhandler(Fail)
-@VWS_FLASK_APP.errorhandler(MetadataTooLarge)
-@VWS_FLASK_APP.errorhandler(TargetNameExist)
-@VWS_FLASK_APP.errorhandler(BadImage)
-@VWS_FLASK_APP.errorhandler(ImageTooLarge)
-@VWS_FLASK_APP.errorhandler(RequestTimeTooSkewed)
-@VWS_FLASK_APP.errorhandler(ContentLengthHeaderTooLarge)
-@VWS_FLASK_APP.errorhandler(ContentLengthHeaderNotInt)
-@VWS_FLASK_APP.errorhandler(UnnecessaryRequestBody)
-@VWS_FLASK_APP.errorhandler(OopsErrorOccurredResponse)
-@VWS_FLASK_APP.errorhandler(TargetStatusProcessing)
-@VWS_FLASK_APP.errorhandler(TargetStatusNotSuccess)
-# TODO update name and type hint here
-def handle_unknown_target(e: UnknownTarget) -> Response:
+@VWS_FLASK_APP.errorhandler(ValidatorException)
+def handle_exceptions(exc: ValidatorException) -> Response:
return ResponseNoContentTypeAdded(
- status=e.status_code.value,
- response=e.response_text,
- headers=e.headers,
+ status=exc.status_code.value,
+ response=exc.response_text,
+ headers=exc.headers,
)
@@ -137,8 +107,6 @@ def add_target() -> Response:
"""
# We do not use ``request.get_json(force=True)`` because this only works
# when the content type is given as ``application/json``.
- request_json = json.loads(request.data)
- name = request_json['name']
databases = get_all_databases()
database = get_database_matching_server_keys(
request_headers=dict(request.headers),
@@ -150,6 +118,8 @@ def add_target() -> Response:
assert isinstance(database, VuforiaDatabase)
+ request_json = json.loads(request.data)
+ name = request_json['name']
active_flag = request_json.get('active_flag')
if active_flag is None:
active_flag = True
diff --git a/src/mock_vws/_flask_server/vws/_databases.py b/src/mock_vws/_flask_server/vws/_databases.py
index a0aa5b993..146191e21 100644
--- a/src/mock_vws/_flask_server/vws/_databases.py
+++ b/src/mock_vws/_flask_server/vws/_databases.py
@@ -35,6 +35,7 @@ def get_all_databases() -> Set[VuforiaDatabase]:
)
for target_dict in database_dict['targets']:
+ # TODO target.from_dict()
name = target_dict['name']
active_flag = target_dict['active_flag']
width = target_dict['width']
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index dac2163b1..922c5af6a 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -6,12 +6,19 @@
import uuid
from http import HTTPStatus
from pathlib import Path
+from typing import Dict
from mock_vws._constants import ResultCodes
from mock_vws._mock_common import json_dump
-class DateHeaderNotGiven(Exception):
+class ValidatorException(Exception):
+ status_code: HTTPStatus
+ response_text: str
+ headers: Dict[str, str]
+
+
+class DateHeaderNotGiven(ValidatorException):
"""
Exception raised when a date header is not given.
"""
@@ -36,7 +43,7 @@ def __init__(self) -> None:
}
-class DateFormatNotValid(Exception):
+class DateFormatNotValid(ValidatorException):
"""
Exception raised when the date format is not valid.
"""
@@ -62,7 +69,7 @@ def __init__(self) -> None:
}
-class RequestTimeTooSkewed(Exception):
+class RequestTimeTooSkewed(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'RequestTimeTooSkewed'.
@@ -92,7 +99,7 @@ def __init__(self) -> None:
}
-class BadImage(Exception):
+class BadImage(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'BadImage'.
@@ -129,7 +136,7 @@ def __init__(self) -> None:
}
-class AuthenticationFailure(Exception):
+class AuthenticationFailure(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'AuthenticationFailure'.
@@ -166,7 +173,7 @@ def __init__(self) -> None:
}
-class AuthenticationFailureGoodFormatting(Exception):
+class AuthenticationFailureGoodFormatting(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'AuthenticationFailure' with a standard JSON formatting.
@@ -198,7 +205,7 @@ def __init__(self) -> None:
}
-class ImageNotGiven(Exception):
+class ImageNotGiven(ValidatorException):
"""
Exception raised when an image is not given.
"""
@@ -224,7 +231,7 @@ def __init__(self) -> None:
}
-class AuthHeaderMissing(Exception):
+class AuthHeaderMissing(ValidatorException):
"""
Exception raised when an auth header is not given.
"""
@@ -251,7 +258,7 @@ def __init__(self) -> None:
}
-class MalformedAuthHeader(Exception):
+class MalformedAuthHeader(ValidatorException):
"""
Exception raised when an auth header is not given.
"""
@@ -278,7 +285,7 @@ def __init__(self) -> None:
}
-class UnknownParameters(Exception):
+class UnknownParameters(ValidatorException):
"""
Exception raised when unknown parameters are given.
"""
@@ -304,7 +311,7 @@ def __init__(self) -> None:
}
-class InactiveProject(Exception):
+class InactiveProject(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'InactiveProject'.
@@ -340,7 +347,7 @@ def __init__(self) -> None:
}
-class InvalidMaxNumResults(Exception):
+class InvalidMaxNumResults(ValidatorException):
"""
Exception raised when an invalid value is given as the
"max_num_results" field.
@@ -371,7 +378,7 @@ def __init__(self, given_value: str) -> None:
}
-class MaxNumResultsOutOfRange(Exception):
+class MaxNumResultsOutOfRange(ValidatorException):
"""
Exception raised when an integer value is given as the "max_num_results"
field which is out of range.
@@ -402,7 +409,7 @@ def __init__(self, given_value: str) -> None:
}
-class InvalidIncludeTargetData(Exception):
+class InvalidIncludeTargetData(ValidatorException):
"""
Exception raised when an invalid value is given as the
"include_target_data" field.
@@ -435,7 +442,7 @@ def __init__(self, given_value: str) -> None:
}
-class UnsupportedMediaType(Exception):
+class UnsupportedMediaType(ValidatorException):
"""
Exception raised when no boundary is found for multipart data.
"""
@@ -460,7 +467,7 @@ def __init__(self) -> None:
}
-class InvalidAcceptHeader(Exception):
+class InvalidAcceptHeader(ValidatorException):
"""
Exception raised when there is an invalid accept header given.
"""
@@ -485,7 +492,7 @@ def __init__(self) -> None:
}
-class BoundaryNotInBody(Exception):
+class BoundaryNotInBody(ValidatorException):
"""
Exception raised when the form boundary is not in the request body.
"""
@@ -514,7 +521,7 @@ def __init__(self) -> None:
}
-class NoBoundaryFound(Exception):
+class NoBoundaryFound(ValidatorException):
"""
Exception raised when an invalid media type is given.
"""
@@ -543,7 +550,7 @@ def __init__(self) -> None:
}
-class QueryOutOfBounds(Exception):
+class QueryOutOfBounds(ValidatorException):
"""
Exception raised when VWS returns an HTML page which says that there is a
particular out of bounds error.
@@ -575,7 +582,7 @@ def __init__(self) -> None:
}
-class ContentLengthHeaderTooLarge(Exception):
+class ContentLengthHeaderTooLarge(ValidatorException):
"""
Exception raised when the given content length header is too large.
"""
@@ -596,7 +603,7 @@ def __init__(self) -> None:
}
-class ContentLengthHeaderNotInt(Exception):
+class ContentLengthHeaderNotInt(ValidatorException):
"""
Exception raised when the given content length header is not an integer.
"""
@@ -617,7 +624,7 @@ def __init__(self) -> None:
}
-class MatchProcessing(Exception):
+class MatchProcessing(ValidatorException):
"""
Exception raised a target is matched which is processing or recently
deleted.
@@ -631,6 +638,7 @@ def __init__(self) -> None:
response_text: The response text to use in a response if this is
raised.
"""
+ super().__init__()
self.status_code = HTTPStatus.INTERNAL_SERVER_ERROR
date = email.utils.formatdate(None, localtime=False, usegmt=True)
self.headers = {
diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py
index 495dd3c09..14840df52 100644
--- a/src/mock_vws/_services_validators/exceptions.py
+++ b/src/mock_vws/_services_validators/exceptions.py
@@ -6,12 +6,19 @@
import uuid
from http import HTTPStatus
from pathlib import Path
+from typing import Dict
from mock_vws._constants import ResultCodes
from mock_vws._mock_common import json_dump
-class UnknownTarget(Exception):
+class ValidatorException(Exception):
+ status_code: HTTPStatus
+ response_text: str
+ headers: Dict[str, str]
+
+
+class UnknownTarget(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'UnknownTarget'.
@@ -41,7 +48,7 @@ def __init__(self) -> None:
}
-class ProjectInactive(Exception):
+class ProjectInactive(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'ProjectInactive'.
@@ -71,7 +78,7 @@ def __init__(self) -> None:
}
-class AuthenticationFailure(Exception):
+class AuthenticationFailure(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'AuthenticationFailure'.
@@ -101,12 +108,12 @@ def __init__(self) -> None:
}
-class Fail(Exception):
+class Fail(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code 'Fail'.
"""
- def __init__(self, status_code: int) -> None:
+ def __init__(self, status_code: HTTPStatus) -> None:
"""
Attributes:
status_code: The status code to use in a response if this is
@@ -130,7 +137,7 @@ def __init__(self, status_code: int) -> None:
}
-class MetadataTooLarge(Exception):
+class MetadataTooLarge(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'MetadataTooLarge'.
@@ -160,7 +167,7 @@ def __init__(self) -> None:
}
-class TargetNameExist(Exception):
+class TargetNameExist(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'TargetNameExist'.
@@ -190,7 +197,7 @@ def __init__(self) -> None:
}
-class OopsErrorOccurredResponse(Exception):
+class OopsErrorOccurredResponse(ValidatorException):
"""
Exception raised when VWS returns an HTML page which says "Oops, an error
occurred".
@@ -222,7 +229,7 @@ def __init__(self) -> None:
}
-class BadImage(Exception):
+class BadImage(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'BadImage'.
@@ -252,7 +259,7 @@ def __init__(self) -> None:
}
-class ImageTooLarge(Exception):
+class ImageTooLarge(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'ImageTooLarge'.
@@ -282,7 +289,7 @@ def __init__(self) -> None:
}
-class RequestTimeTooSkewed(Exception):
+class RequestTimeTooSkewed(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'RequestTimeTooSkewed'.
@@ -312,7 +319,7 @@ def __init__(self) -> None:
}
-class ContentLengthHeaderTooLarge(Exception):
+class ContentLengthHeaderTooLarge(ValidatorException):
"""
Exception raised when the given content length header is too large.
"""
@@ -331,7 +338,7 @@ def __init__(self) -> None:
self.headers = {'Connection': 'keep-alive'}
-class ContentLengthHeaderNotInt(Exception):
+class ContentLengthHeaderNotInt(ValidatorException):
"""
Exception raised when the given content length header is not an integer.
"""
@@ -350,7 +357,7 @@ def __init__(self) -> None:
self.headers = {'Connection': 'Close'}
-class UnnecessaryRequestBody(Exception):
+class UnnecessaryRequestBody(ValidatorException):
"""
Exception raised when a request body is given but not necessary.
"""
@@ -374,7 +381,7 @@ def __init__(self) -> None:
}
-class TargetStatusNotSuccess(Exception):
+class TargetStatusNotSuccess(ValidatorException):
"""
Exception raised when trying to update a target that does not have a
success status.
@@ -404,7 +411,7 @@ def __init__(self) -> None:
}
-class TargetStatusProcessing(Exception):
+class TargetStatusProcessing(ValidatorException):
"""
Exception raised when trying to delete a target which is processing.
"""
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index fad885af6..185993328 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -1,6 +1,7 @@
"""
A fake implementation of a target for the Vuforia Web Services API.
"""
+from __future__ import annotations
import base64
import datetime
@@ -177,6 +178,14 @@ def tracking_rating(self) -> int:
return 0
+ @classmethod
+ def from_dict(
+ cls, data: Dict[str, Optional[Union[str, int, bool, float]]]
+ ) -> Target:
+ """
+ TODO
+ """
+
def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
delete_date: Optional[str] = None
if self.delete_date:
From 003c5c7f66c2617c54668af144d6fed87df9a1a1 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 18 Sep 2020 23:45:45 +0100
Subject: [PATCH 0223/3455] Use a base validator exception where appropriate
---
.../mock_web_query_api.py | 52 +------------------
.../mock_web_services_api.py | 35 +------------
2 files changed, 4 insertions(+), 83 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
index 829fc3324..a5683d941 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
@@ -21,28 +21,8 @@
)
from mock_vws._query_validators import run_query_validators
from mock_vws._query_validators.exceptions import (
- AuthenticationFailure,
- AuthenticationFailureGoodFormatting,
- AuthHeaderMissing,
- BadImage,
- BoundaryNotInBody,
- ContentLengthHeaderNotInt,
- ContentLengthHeaderTooLarge,
- DateFormatNotValid,
- DateHeaderNotGiven,
- ImageNotGiven,
- InactiveProject,
- InvalidAcceptHeader,
- InvalidIncludeTargetData,
- InvalidMaxNumResults,
- MalformedAuthHeader,
MatchProcessing,
- MaxNumResultsOutOfRange,
- NoBoundaryFound,
- QueryOutOfBounds,
- RequestTimeTooSkewed,
- UnknownParameters,
- UnsupportedMediaType,
+ ValidatorException,
)
from mock_vws.database import VuforiaDatabase
@@ -77,36 +57,8 @@ def run_validators(
request_method=request.method,
databases=instance.databases,
)
- except (
- AuthHeaderMissing,
- AuthenticationFailure,
- AuthenticationFailureGoodFormatting,
- BadImage,
- BoundaryNotInBody,
- DateFormatNotValid,
- DateHeaderNotGiven,
- ImageNotGiven,
- InactiveProject,
- InvalidAcceptHeader,
- InvalidIncludeTargetData,
- InvalidMaxNumResults,
- MalformedAuthHeader,
- MaxNumResultsOutOfRange,
- NoBoundaryFound,
- RequestTimeTooSkewed,
- UnknownParameters,
- UnsupportedMediaType,
- ContentLengthHeaderNotInt,
- ContentLengthHeaderTooLarge,
- QueryOutOfBounds,
- ) as exc:
- context.headers = exc.headers
- context.status_code = exc.status_code
- return exc.response_text
-
- try:
return wrapped(*args, **kwargs)
- except MatchProcessing as exc:
+ except ValidatorException as exc:
context.headers = exc.headers
context.status_code = exc.status_code
return exc.response_text
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 93f131326..6ccd90ed5 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -27,21 +27,10 @@
from mock_vws._mock_common import Route, json_dump, set_content_length_header
from mock_vws._services_validators import run_services_validators
from mock_vws._services_validators.exceptions import (
- AuthenticationFailure,
- BadImage,
- ContentLengthHeaderNotInt,
- ContentLengthHeaderTooLarge,
Fail,
- ImageTooLarge,
- MetadataTooLarge,
- OopsErrorOccurredResponse,
- ProjectInactive,
- RequestTimeTooSkewed,
- TargetNameExist,
TargetStatusNotSuccess,
TargetStatusProcessing,
- UnknownTarget,
- UnnecessaryRequestBody,
+ ValidatorException,
)
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
@@ -77,28 +66,8 @@ def run_validators(
request_path=request.path,
databases=instance.databases,
)
- except (
- UnknownTarget,
- ProjectInactive,
- AuthenticationFailure,
- Fail,
- MetadataTooLarge,
- TargetNameExist,
- BadImage,
- ImageTooLarge,
- RequestTimeTooSkewed,
- ContentLengthHeaderTooLarge,
- ContentLengthHeaderNotInt,
- OopsErrorOccurredResponse,
- UnnecessaryRequestBody,
- ) as exc:
- context.headers = exc.headers
- context.status_code = exc.status_code
- return exc.response_text
-
- try:
return wrapped(*args, **kwargs)
- except (Fail, TargetStatusNotSuccess, TargetStatusProcessing) as exc:
+ except ValidatorException as exc:
context.headers = exc.headers
context.status_code = exc.status_code
return exc.response_text
From bd3190e2e0095331c3a77ab128f11d27f0226daf Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 07:28:58 +0100
Subject: [PATCH 0224/3455] Create a base exception for validator exceptions
---
src/mock_vws/_mock_common.py | 2 -
src/mock_vws/_query_validators/exceptions.py | 52 ++++++++++-------
.../mock_web_query_api.py | 57 +------------------
.../mock_web_services_api.py | 41 +------------
.../_services_validators/exceptions.py | 39 +++++++------
5 files changed, 59 insertions(+), 132 deletions(-)
diff --git a/src/mock_vws/_mock_common.py b/src/mock_vws/_mock_common.py
index bbd5e6f76..21b4ed2ef 100644
--- a/src/mock_vws/_mock_common.py
+++ b/src/mock_vws/_mock_common.py
@@ -2,10 +2,8 @@
Common utilities for creating mock routes.
"""
-import email.utils
import json
from dataclasses import dataclass
-from http import HTTPStatus
from typing import Any, Callable, Dict, FrozenSet, Tuple
import wrapt
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index dac2163b1..922c5af6a 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -6,12 +6,19 @@
import uuid
from http import HTTPStatus
from pathlib import Path
+from typing import Dict
from mock_vws._constants import ResultCodes
from mock_vws._mock_common import json_dump
-class DateHeaderNotGiven(Exception):
+class ValidatorException(Exception):
+ status_code: HTTPStatus
+ response_text: str
+ headers: Dict[str, str]
+
+
+class DateHeaderNotGiven(ValidatorException):
"""
Exception raised when a date header is not given.
"""
@@ -36,7 +43,7 @@ def __init__(self) -> None:
}
-class DateFormatNotValid(Exception):
+class DateFormatNotValid(ValidatorException):
"""
Exception raised when the date format is not valid.
"""
@@ -62,7 +69,7 @@ def __init__(self) -> None:
}
-class RequestTimeTooSkewed(Exception):
+class RequestTimeTooSkewed(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'RequestTimeTooSkewed'.
@@ -92,7 +99,7 @@ def __init__(self) -> None:
}
-class BadImage(Exception):
+class BadImage(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'BadImage'.
@@ -129,7 +136,7 @@ def __init__(self) -> None:
}
-class AuthenticationFailure(Exception):
+class AuthenticationFailure(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'AuthenticationFailure'.
@@ -166,7 +173,7 @@ def __init__(self) -> None:
}
-class AuthenticationFailureGoodFormatting(Exception):
+class AuthenticationFailureGoodFormatting(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'AuthenticationFailure' with a standard JSON formatting.
@@ -198,7 +205,7 @@ def __init__(self) -> None:
}
-class ImageNotGiven(Exception):
+class ImageNotGiven(ValidatorException):
"""
Exception raised when an image is not given.
"""
@@ -224,7 +231,7 @@ def __init__(self) -> None:
}
-class AuthHeaderMissing(Exception):
+class AuthHeaderMissing(ValidatorException):
"""
Exception raised when an auth header is not given.
"""
@@ -251,7 +258,7 @@ def __init__(self) -> None:
}
-class MalformedAuthHeader(Exception):
+class MalformedAuthHeader(ValidatorException):
"""
Exception raised when an auth header is not given.
"""
@@ -278,7 +285,7 @@ def __init__(self) -> None:
}
-class UnknownParameters(Exception):
+class UnknownParameters(ValidatorException):
"""
Exception raised when unknown parameters are given.
"""
@@ -304,7 +311,7 @@ def __init__(self) -> None:
}
-class InactiveProject(Exception):
+class InactiveProject(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'InactiveProject'.
@@ -340,7 +347,7 @@ def __init__(self) -> None:
}
-class InvalidMaxNumResults(Exception):
+class InvalidMaxNumResults(ValidatorException):
"""
Exception raised when an invalid value is given as the
"max_num_results" field.
@@ -371,7 +378,7 @@ def __init__(self, given_value: str) -> None:
}
-class MaxNumResultsOutOfRange(Exception):
+class MaxNumResultsOutOfRange(ValidatorException):
"""
Exception raised when an integer value is given as the "max_num_results"
field which is out of range.
@@ -402,7 +409,7 @@ def __init__(self, given_value: str) -> None:
}
-class InvalidIncludeTargetData(Exception):
+class InvalidIncludeTargetData(ValidatorException):
"""
Exception raised when an invalid value is given as the
"include_target_data" field.
@@ -435,7 +442,7 @@ def __init__(self, given_value: str) -> None:
}
-class UnsupportedMediaType(Exception):
+class UnsupportedMediaType(ValidatorException):
"""
Exception raised when no boundary is found for multipart data.
"""
@@ -460,7 +467,7 @@ def __init__(self) -> None:
}
-class InvalidAcceptHeader(Exception):
+class InvalidAcceptHeader(ValidatorException):
"""
Exception raised when there is an invalid accept header given.
"""
@@ -485,7 +492,7 @@ def __init__(self) -> None:
}
-class BoundaryNotInBody(Exception):
+class BoundaryNotInBody(ValidatorException):
"""
Exception raised when the form boundary is not in the request body.
"""
@@ -514,7 +521,7 @@ def __init__(self) -> None:
}
-class NoBoundaryFound(Exception):
+class NoBoundaryFound(ValidatorException):
"""
Exception raised when an invalid media type is given.
"""
@@ -543,7 +550,7 @@ def __init__(self) -> None:
}
-class QueryOutOfBounds(Exception):
+class QueryOutOfBounds(ValidatorException):
"""
Exception raised when VWS returns an HTML page which says that there is a
particular out of bounds error.
@@ -575,7 +582,7 @@ def __init__(self) -> None:
}
-class ContentLengthHeaderTooLarge(Exception):
+class ContentLengthHeaderTooLarge(ValidatorException):
"""
Exception raised when the given content length header is too large.
"""
@@ -596,7 +603,7 @@ def __init__(self) -> None:
}
-class ContentLengthHeaderNotInt(Exception):
+class ContentLengthHeaderNotInt(ValidatorException):
"""
Exception raised when the given content length header is not an integer.
"""
@@ -617,7 +624,7 @@ def __init__(self) -> None:
}
-class MatchProcessing(Exception):
+class MatchProcessing(ValidatorException):
"""
Exception raised a target is matched which is processing or recently
deleted.
@@ -631,6 +638,7 @@ def __init__(self) -> None:
response_text: The response text to use in a response if this is
raised.
"""
+ super().__init__()
self.status_code = HTTPStatus.INTERNAL_SERVER_ERROR
date = email.utils.formatdate(None, localtime=False, usegmt=True)
self.headers = {
diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
index b327fd1d6..a5683d941 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
@@ -13,10 +13,7 @@
from requests_mock.request import _RequestObjectProxy
from requests_mock.response import _Context
-from mock_vws._mock_common import (
- Route,
- set_content_length_header,
-)
+from mock_vws._mock_common import Route, set_content_length_header
from mock_vws._query_tools import (
ActiveMatchingTargetsDeleteProcessing,
MatchingTargetsWithProcessingStatus,
@@ -24,28 +21,8 @@
)
from mock_vws._query_validators import run_query_validators
from mock_vws._query_validators.exceptions import (
- AuthenticationFailure,
- AuthenticationFailureGoodFormatting,
- AuthHeaderMissing,
- BadImage,
- BoundaryNotInBody,
- ContentLengthHeaderNotInt,
- ContentLengthHeaderTooLarge,
- DateFormatNotValid,
- DateHeaderNotGiven,
- ImageNotGiven,
- InactiveProject,
- InvalidAcceptHeader,
- InvalidIncludeTargetData,
- InvalidMaxNumResults,
- MalformedAuthHeader,
MatchProcessing,
- MaxNumResultsOutOfRange,
- NoBoundaryFound,
- QueryOutOfBounds,
- RequestTimeTooSkewed,
- UnknownParameters,
- UnsupportedMediaType,
+ ValidatorException,
)
from mock_vws.database import VuforiaDatabase
@@ -80,36 +57,8 @@ def run_validators(
request_method=request.method,
databases=instance.databases,
)
- except (
- AuthHeaderMissing,
- AuthenticationFailure,
- AuthenticationFailureGoodFormatting,
- BadImage,
- BoundaryNotInBody,
- DateFormatNotValid,
- DateHeaderNotGiven,
- ImageNotGiven,
- InactiveProject,
- InvalidAcceptHeader,
- InvalidIncludeTargetData,
- InvalidMaxNumResults,
- MalformedAuthHeader,
- MaxNumResultsOutOfRange,
- NoBoundaryFound,
- RequestTimeTooSkewed,
- UnknownParameters,
- UnsupportedMediaType,
- ContentLengthHeaderNotInt,
- ContentLengthHeaderTooLarge,
- QueryOutOfBounds,
- ) as exc:
- context.headers = exc.headers
- context.status_code = exc.status_code
- return exc.response_text
-
- try:
return wrapped(*args, **kwargs)
- except MatchProcessing as exc:
+ except ValidatorException as exc:
context.headers = exc.headers
context.status_code = exc.status_code
return exc.response_text
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index c97a912dd..6ccd90ed5 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -24,28 +24,13 @@
from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_server_keys
-from mock_vws._mock_common import (
- Route,
- json_dump,
- set_content_length_header,
-)
+from mock_vws._mock_common import Route, json_dump, set_content_length_header
from mock_vws._services_validators import run_services_validators
from mock_vws._services_validators.exceptions import (
- AuthenticationFailure,
- BadImage,
- ContentLengthHeaderNotInt,
- ContentLengthHeaderTooLarge,
Fail,
- ImageTooLarge,
- MetadataTooLarge,
- OopsErrorOccurredResponse,
- ProjectInactive,
- RequestTimeTooSkewed,
- TargetNameExist,
- UnknownTarget,
- UnnecessaryRequestBody,
TargetStatusNotSuccess,
TargetStatusProcessing,
+ ValidatorException,
)
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
@@ -81,28 +66,8 @@ def run_validators(
request_path=request.path,
databases=instance.databases,
)
- except (
- UnknownTarget,
- ProjectInactive,
- AuthenticationFailure,
- Fail,
- MetadataTooLarge,
- TargetNameExist,
- BadImage,
- ImageTooLarge,
- RequestTimeTooSkewed,
- ContentLengthHeaderTooLarge,
- ContentLengthHeaderNotInt,
- OopsErrorOccurredResponse,
- UnnecessaryRequestBody,
- ) as exc:
- context.headers = exc.headers
- context.status_code = exc.status_code
- return exc.response_text
-
- try:
return wrapped(*args, **kwargs)
- except (Fail, TargetStatusNotSuccess,TargetStatusProcessing) as exc:
+ except ValidatorException as exc:
context.headers = exc.headers
context.status_code = exc.status_code
return exc.response_text
diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py
index 495dd3c09..14840df52 100644
--- a/src/mock_vws/_services_validators/exceptions.py
+++ b/src/mock_vws/_services_validators/exceptions.py
@@ -6,12 +6,19 @@
import uuid
from http import HTTPStatus
from pathlib import Path
+from typing import Dict
from mock_vws._constants import ResultCodes
from mock_vws._mock_common import json_dump
-class UnknownTarget(Exception):
+class ValidatorException(Exception):
+ status_code: HTTPStatus
+ response_text: str
+ headers: Dict[str, str]
+
+
+class UnknownTarget(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'UnknownTarget'.
@@ -41,7 +48,7 @@ def __init__(self) -> None:
}
-class ProjectInactive(Exception):
+class ProjectInactive(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'ProjectInactive'.
@@ -71,7 +78,7 @@ def __init__(self) -> None:
}
-class AuthenticationFailure(Exception):
+class AuthenticationFailure(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'AuthenticationFailure'.
@@ -101,12 +108,12 @@ def __init__(self) -> None:
}
-class Fail(Exception):
+class Fail(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code 'Fail'.
"""
- def __init__(self, status_code: int) -> None:
+ def __init__(self, status_code: HTTPStatus) -> None:
"""
Attributes:
status_code: The status code to use in a response if this is
@@ -130,7 +137,7 @@ def __init__(self, status_code: int) -> None:
}
-class MetadataTooLarge(Exception):
+class MetadataTooLarge(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'MetadataTooLarge'.
@@ -160,7 +167,7 @@ def __init__(self) -> None:
}
-class TargetNameExist(Exception):
+class TargetNameExist(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'TargetNameExist'.
@@ -190,7 +197,7 @@ def __init__(self) -> None:
}
-class OopsErrorOccurredResponse(Exception):
+class OopsErrorOccurredResponse(ValidatorException):
"""
Exception raised when VWS returns an HTML page which says "Oops, an error
occurred".
@@ -222,7 +229,7 @@ def __init__(self) -> None:
}
-class BadImage(Exception):
+class BadImage(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'BadImage'.
@@ -252,7 +259,7 @@ def __init__(self) -> None:
}
-class ImageTooLarge(Exception):
+class ImageTooLarge(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'ImageTooLarge'.
@@ -282,7 +289,7 @@ def __init__(self) -> None:
}
-class RequestTimeTooSkewed(Exception):
+class RequestTimeTooSkewed(ValidatorException):
"""
Exception raised when Vuforia returns a response with a result code
'RequestTimeTooSkewed'.
@@ -312,7 +319,7 @@ def __init__(self) -> None:
}
-class ContentLengthHeaderTooLarge(Exception):
+class ContentLengthHeaderTooLarge(ValidatorException):
"""
Exception raised when the given content length header is too large.
"""
@@ -331,7 +338,7 @@ def __init__(self) -> None:
self.headers = {'Connection': 'keep-alive'}
-class ContentLengthHeaderNotInt(Exception):
+class ContentLengthHeaderNotInt(ValidatorException):
"""
Exception raised when the given content length header is not an integer.
"""
@@ -350,7 +357,7 @@ def __init__(self) -> None:
self.headers = {'Connection': 'Close'}
-class UnnecessaryRequestBody(Exception):
+class UnnecessaryRequestBody(ValidatorException):
"""
Exception raised when a request body is given but not necessary.
"""
@@ -374,7 +381,7 @@ def __init__(self) -> None:
}
-class TargetStatusNotSuccess(Exception):
+class TargetStatusNotSuccess(ValidatorException):
"""
Exception raised when trying to update a target that does not have a
success status.
@@ -404,7 +411,7 @@ def __init__(self) -> None:
}
-class TargetStatusProcessing(Exception):
+class TargetStatusProcessing(ValidatorException):
"""
Exception raised when trying to delete a target which is processing.
"""
From 1e30411238cd11fb84d1cf884cce39e4f205d41c Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 07:41:04 +0100
Subject: [PATCH 0225/3455] Add back custom linters and fix lint issues
---
Makefile | 1 +
lint.mk | 4 ++++
src/mock_vws/_query_validators/exceptions.py | 5 +++++
.../_requests_mock_server/mock_web_query_api.py | 4 ++--
.../_requests_mock_server/mock_web_services_api.py | 10 +++++-----
src/mock_vws/_services_validators/exceptions.py | 4 ++++
6 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/Makefile b/Makefile
index cb1405459..4c4c47bc4 100644
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,7 @@ update-secrets:
.PHONY: lint
lint: \
black \
+ custom-linters \
check-manifest \
doc8 \
flake8 \
diff --git a/lint.mk b/lint.mk
index 810325e21..275fcb7c3 100644
--- a/lint.mk
+++ b/lint.mk
@@ -2,6 +2,10 @@
SHELL := /bin/bash -euxo pipefail
+.PHONY: custom-linters
+custom-linters:
+ pytest -s -vvv ci/custom_linters.py
+
.PHONY: black
black:
black --check .
diff --git a/src/mock_vws/_query_validators/exceptions.py b/src/mock_vws/_query_validators/exceptions.py
index 922c5af6a..f60f4daa2 100644
--- a/src/mock_vws/_query_validators/exceptions.py
+++ b/src/mock_vws/_query_validators/exceptions.py
@@ -13,6 +13,11 @@
class ValidatorException(Exception):
+ """
+ A base class for exceptions thrown from mock Vuforia cloud recognition
+ client endpoints.
+ """
+
status_code: HTTPStatus
response_text: str
headers: Dict[str, str]
diff --git a/src/mock_vws/_requests_mock_server/mock_web_query_api.py b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
index a5683d941..5cab753ad 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_query_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_query_api.py
@@ -172,8 +172,8 @@ def query(
except (
ActiveMatchingTargetsDeleteProcessing,
MatchingTargetsWithProcessingStatus,
- ):
- raise MatchProcessing
+ ) as exc:
+ raise MatchProcessing from exc
date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 6ccd90ed5..c7e6d056d 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -278,7 +278,7 @@ def delete_target(
def database_summary(
self,
request: _RequestObjectProxy,
- context: _Context, # pylint: disable=unused-argument
+ context: _Context,
) -> str:
"""
Get a database summary report.
@@ -326,7 +326,7 @@ def database_summary(
def target_list(
self,
request: _RequestObjectProxy,
- context: _Context, # pylint: disable=unused-argument
+ context: _Context,
) -> str:
"""
Get a list of all targets.
@@ -363,7 +363,7 @@ def target_list(
def get_target(
self,
request: _RequestObjectProxy,
- context: _Context, # pylint: disable=unused-argument
+ context: _Context,
) -> str:
"""
Get details of a target.
@@ -407,7 +407,7 @@ def get_target(
def get_duplicates(
self,
request: _RequestObjectProxy,
- context: _Context, # pylint: disable=unused-argument
+ context: _Context,
) -> str:
"""
Get targets which may be considered duplicates of a given target.
@@ -541,7 +541,7 @@ def update_target(
def target_summary(
self,
request: _RequestObjectProxy,
- context: _Context, # pylint: disable=unused-argument
+ context: _Context,
) -> str:
"""
Get a summary report for a target.
diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py
index 14840df52..6c5122af4 100644
--- a/src/mock_vws/_services_validators/exceptions.py
+++ b/src/mock_vws/_services_validators/exceptions.py
@@ -13,6 +13,10 @@
class ValidatorException(Exception):
+ """
+ A base class for exceptions thrown from mock Vuforia services endpoints.
+ """
+
status_code: HTTPStatus
response_text: str
headers: Dict[str, str]
From 84db787b8ba3790687f11d295abd2ae28d34c323 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 07:45:31 +0100
Subject: [PATCH 0226/3455] Lint the custom linters file
---
ci/custom_linters.py | 6 +++---
lint.mk | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/ci/custom_linters.py b/ci/custom_linters.py
index eeead5e81..77974b02f 100644
--- a/ci/custom_linters.py
+++ b/ci/custom_linters.py
@@ -12,7 +12,7 @@
def _ci_patterns() -> Set[str]:
"""
- Return the CI patterns given in the CI config file.
+ Return the CI patterns given in the CI configuration file.
"""
repository_root = Path(__file__).parent.parent
ci_file = repository_root / '.github' / 'workflows' / 'ci.yml'
@@ -41,8 +41,8 @@ def _tests_from_pattern(ci_pattern: str) -> Set[str]:
def test_ci_patterns_valid() -> None:
"""
- All of the CI patterns in the CI config match at least one test in the test
- suite.
+ All of the CI patterns in the CI configuration match at least one test in
+ the test suite.
"""
ci_patterns = _ci_patterns()
diff --git a/lint.mk b/lint.mk
index 275fcb7c3..10f47f7cc 100644
--- a/lint.mk
+++ b/lint.mk
@@ -4,7 +4,7 @@ SHELL := /bin/bash -euxo pipefail
.PHONY: custom-linters
custom-linters:
- pytest -s -vvv ci/custom_linters.py
+ pytest ci/custom_linters.py
.PHONY: black
black:
@@ -16,7 +16,7 @@ fix-black:
.PHONY: mypy
mypy:
- mypy *.py src/ tests/ docs/source/ admin
+ mypy *.py src/ tests/ docs/source/ admin ci/
.PHONY: check-manifest
check-manifest:
@@ -48,7 +48,7 @@ pip-missing-reqs:
.PHONY: pylint
pylint:
- pylint *.py src/ tests/ admin/ docs/
+ pylint *.py src/ tests/ admin/ docs/ ci/
.PHONY: pyroma
pyroma:
From ee0a4f4ab610870e28725ba721e312f8d762003e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 07:45:57 +0100
Subject: [PATCH 0227/3455] Run custom linters (slow) later in the lint process
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 4c4c47bc4..81bbf8cbe 100644
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,6 @@ update-secrets:
.PHONY: lint
lint: \
black \
- custom-linters \
check-manifest \
doc8 \
flake8 \
@@ -28,6 +27,7 @@ lint: \
vulture \
pylint \
pydocstyle \
+ custom-linters \
.PHONY: fix-lint
fix-lint: \
From a07c09bf2718a5487f753465412158991d860433 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 07:56:41 +0100
Subject: [PATCH 0228/3455] Start cleaning up for merge
---
src/mock_vws/_requests_mock_server/mock_web_services_api.py | 3 ---
src/mock_vws/target.py | 4 ++--
2 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 41ad92041..a0d715d40 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -430,9 +430,6 @@ def get_duplicates(
assert isinstance(database, VuforiaDatabase)
other_targets = set(database.targets) - set([target])
- # TODO use the new image match function here
- # TODO - add a test - is something a duplicate if it isn't exactly the
- # same?
similar_targets: List[str] = [
other.target_id
for other in other_targets
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 195e5f9f7..d910960b5 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -88,8 +88,8 @@ def __init__( # pylint: disable=too-many-arguments
self.application_metadata = application_metadata
self.delete_date: Optional[datetime.datetime] = None
self.total_recos: int = 0
- self.current_month_recos : int = 0
- self.previous_month_recos : int = 0
+ self.current_month_recos: int = 0
+ self.previous_month_recos: int = 0
def __repr__(self) -> str:
"""
From 08d42788b7809f692f8241af0a8fb88e963612a5 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 08:05:16 +0100
Subject: [PATCH 0229/3455] Do not use hardcoded recognition stats in Flask
---
src/mock_vws/_flask_server/vws/__init__.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 6c5063d43..ea7f4fedc 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -344,9 +344,9 @@ def target_summary(target_id: str) -> Response:
'upload_date': target.upload_date.strftime('%Y-%m-%d'),
'active_flag': target.active_flag,
'tracking_rating': target.tracking_rating,
- 'total_recos': 0,
- 'current_month_recos': 0,
- 'previous_month_recos': 0,
+ 'total_recos': target.total_recos,
+ 'current_month_recos': target.current_month_recos,
+ 'previous_month_recos': target.previous_month_recos,
}
date = email.utils.formatdate(None, localtime=False, usegmt=True)
headers = {
From 4d432a693a417854ed176fc5b32378eba86c97c2 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 08:14:32 +0100
Subject: [PATCH 0230/3455] Fix a few lint issues
---
.../_flask_server/storage/__init__.py | 32 +++++++++++++++----
1 file changed, 25 insertions(+), 7 deletions(-)
diff --git a/src/mock_vws/_flask_server/storage/__init__.py b/src/mock_vws/_flask_server/storage/__init__.py
index 2a210e323..5a383b730 100644
--- a/src/mock_vws/_flask_server/storage/__init__.py
+++ b/src/mock_vws/_flask_server/storage/__init__.py
@@ -2,11 +2,11 @@
import datetime
import io
import random
+from http import HTTPStatus
from typing import List, Tuple
from backports.zoneinfo import ZoneInfo
from flask import Flask, jsonify, request
-from requests import codes
from mock_vws.database import VuforiaDatabase
from mock_vws.states import States
@@ -19,18 +19,27 @@
@STORAGE_FLASK_APP.route('/reset', methods=['POST'])
def reset() -> Tuple[str, int]:
+ """
+ Reset the backend to a state of no databases.
+ """
VUFORIA_DATABASES.clear()
- return '', codes.OK
+ return '', HTTPStatus.OK
@STORAGE_FLASK_APP.route('/databases', methods=['GET'])
def get_databases() -> Tuple[str, int]:
+ """
+ Return a list of all databases.
+ """
databases = [database.to_dict() for database in VUFORIA_DATABASES]
- return jsonify(databases), codes.OK
+ return jsonify(databases), HTTPStatus.OK
@STORAGE_FLASK_APP.route('/databases', methods=['POST'])
def create_database() -> Tuple[str, int]:
+ """
+ Create a new database.
+ """
server_access_key = request.json['server_access_key']
server_secret_key = request.json['server_secret_key']
client_access_key = request.json['client_access_key']
@@ -47,7 +56,7 @@ def create_database() -> Tuple[str, int]:
state=state,
)
VUFORIA_DATABASES.append(database)
- return jsonify(database.to_dict()), codes.CREATED
+ return jsonify(database.to_dict()), HTTPStatus.CREATED
@STORAGE_FLASK_APP.route(
@@ -55,6 +64,9 @@ def create_database() -> Tuple[str, int]:
methods=['POST'],
)
def create_target(database_name: str) -> Tuple[str, int]:
+ """
+ Create a new target in a given database.
+ """
[database] = [
database
for database in VUFORIA_DATABASES
@@ -74,7 +86,7 @@ def create_target(database_name: str) -> Tuple[str, int]:
target.target_id = request.json['target_id']
database.targets.add(target)
- return jsonify(target.to_dict()), codes.CREATED
+ return jsonify(target.to_dict()), HTTPStatus.CREATED
@STORAGE_FLASK_APP.route(
@@ -82,6 +94,9 @@ def create_target(database_name: str) -> Tuple[str, int]:
methods=['DELETE'],
)
def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
+ """
+ Delete a target.
+ """
[database] = [
database
for database in VUFORIA_DATABASES
@@ -91,7 +106,7 @@ def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
target for target in database.targets if target.target_id == target_id
]
target.delete()
- return jsonify(target.to_dict()), codes.OK
+ return jsonify(target.to_dict()), HTTPStatus.OK
@STORAGE_FLASK_APP.route(
@@ -99,6 +114,9 @@ def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
methods=['PUT'],
)
def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
+ """
+ Update a target.
+ """
[database] = [
database
for database in VUFORIA_DATABASES
@@ -135,4 +153,4 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
now = datetime.datetime.now(tz=gmt)
target.last_modified_date = now
- return jsonify(target.to_dict()), codes.OK
+ return jsonify(target.to_dict()), HTTPStatus.OK
From 7e791d584f62a0dc33c2f98ef6db9c915dd9f5cc Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 08:39:46 +0100
Subject: [PATCH 0231/3455] Progress towards fixing lint issues
---
pyproject.toml | 4 ++-
.../_flask_server/storage/__init__.py | 2 +-
src/mock_vws/_flask_server/vwq/__init__.py | 25 +++++++++++++------
src/mock_vws/_flask_server/vws/__init__.py | 13 +++++++---
4 files changed, 31 insertions(+), 13 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 7968eaf3e..cd79bb38f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@
persistent = true
# Use multiple processes to speed up Pylint.
- jobs = 0
+ jobs = 1
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
@@ -40,7 +40,9 @@
disable = [
# Tests need `self` to be in a class but do not use it.
'no-self-use',
+ # Style issues that we can deal with ourselves
'too-few-public-methods',
+ 'too-many-ancestors',
'too-many-locals',
'too-many-arguments',
'too-many-instance-attributes',
diff --git a/src/mock_vws/_flask_server/storage/__init__.py b/src/mock_vws/_flask_server/storage/__init__.py
index 5a383b730..80700950e 100644
--- a/src/mock_vws/_flask_server/storage/__init__.py
+++ b/src/mock_vws/_flask_server/storage/__init__.py
@@ -20,7 +20,7 @@
@STORAGE_FLASK_APP.route('/reset', methods=['POST'])
def reset() -> Tuple[str, int]:
"""
- Reset the backend to a state of no databases.
+ Reset the back-end to a state of no databases.
"""
VUFORIA_DATABASES.clear()
return '', HTTPStatus.OK
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 563a0d325..82f2ce0ac 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -39,10 +39,14 @@ def validate_request() -> None:
)
-# We use a custom response type.
-# Without this, a content type is added to all responses.
-# Some of our responses need to not have a "Content-Type" header.
class ResponseNoContentTypeAdded(Response):
+ """
+ A custom response type.
+
+ Without this, a content type is added to all responses.
+ Some of our responses need to not have a "Content-Type" header.
+ """
+
def __init__(
self,
response: Optional[str] = None,
@@ -82,17 +86,20 @@ def __init__(
@CLOUDRECO_FLASK_APP.errorhandler(requests.exceptions.ConnectionError)
def handle_connection_error(
- e: requests.exceptions.ConnectionError,
+ exc: requests.exceptions.ConnectionError,
) -> Response:
# TODO: Issue
# This is incorrect - it raises on the server but should raise on the
# client
# Look into how ``requests`` handles it
- raise e
+ raise exc
@CLOUDRECO_FLASK_APP.errorhandler(ValidatorException)
def handle_exceptions(exc: ValidatorException) -> Response:
+ """
+ Return the error response associated with the given exception.
+ """
return ResponseNoContentTypeAdded(
status=exc.status_code.value,
response=exc.response_text,
@@ -102,7 +109,9 @@ def handle_exceptions(exc: ValidatorException) -> Response:
@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
def query() -> Response:
-
+ """
+ Perform an image recognition query.
+ """
# TODO these should be configurable
query_processes_deletion_seconds = 0.2
query_recognizes_deletion_seconds = 0.2
@@ -124,8 +133,8 @@ def query() -> Response:
except (
ActiveMatchingTargetsDeleteProcessing,
MatchingTargetsWithProcessingStatus,
- ):
- raise MatchProcessing
+ ) as exc:
+ raise MatchProcessing from exc
headers = {
'Content-Type': 'application/json',
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index ea7f4fedc..96d296b3b 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -34,10 +34,14 @@
VWS_FLASK_APP = Flask(import_name=__name__)
VWS_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
-# We use a custom response type.
-# Without this, a content type is added to all responses.
-# Some of our responses need to not have a "Content-Type" header.
class ResponseNoContentTypeAdded(Response):
+ """
+ A custom response type.
+
+ Without this, a content type is added to all responses.
+ Some of our responses need to not have a "Content-Type" header.
+ """
+
def __init__(
self,
response: Optional[str] = None,
@@ -90,6 +94,9 @@ def validate_request() -> None:
@VWS_FLASK_APP.errorhandler(ValidatorException)
def handle_exceptions(exc: ValidatorException) -> Response:
+ """
+ Return the error response associated with the given exception.
+ """
return ResponseNoContentTypeAdded(
status=exc.status_code.value,
response=exc.response_text,
From aaaf50327566dcef09021dfc97ae5362fd906903 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 08:50:25 +0100
Subject: [PATCH 0232/3455] Handle a key validator case
---
src/mock_vws/_services_validators/key_validators.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/mock_vws/_services_validators/key_validators.py b/src/mock_vws/_services_validators/key_validators.py
index 9869f2168..37acca67e 100644
--- a/src/mock_vws/_services_validators/key_validators.py
+++ b/src/mock_vws/_services_validators/key_validators.py
@@ -142,7 +142,7 @@ def validate_keys(
optional_keys = matching_route.optional_keys
allowed_keys = mandatory_keys.union(optional_keys)
- if request_body is None and not allowed_keys:
+ if not request_body and not allowed_keys:
return
request_text = request_body.decode()
From 5c9e1309ad8a94e556026602008f7d381cd71d20 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 08:58:31 +0100
Subject: [PATCH 0233/3455] Progress towards fixing pylint errors
---
src/mock_vws/_flask_server/vwq/__init__.py | 19 +++++++------------
src/mock_vws/_flask_server/vws/__init__.py | 1 +
2 files changed, 8 insertions(+), 12 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 82f2ce0ac..8f5965284 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -1,9 +1,15 @@
+"""
+A fake implementation of the Vuforia Web Query API using Flask.
+
+See
+https://library.vuforia.com/articles/Solution/How-To-Perform-an-Image-Recognition-Query
+"""
+
import copy
import email.utils
from http import HTTPStatus
from typing import Dict, Optional
-import requests
from flask import Flask, Response, request
from werkzeug.datastructures import Headers
@@ -84,17 +90,6 @@ def __init__(
CLOUDRECO_FLASK_APP.response_class = ResponseNoContentTypeAdded
-@CLOUDRECO_FLASK_APP.errorhandler(requests.exceptions.ConnectionError)
-def handle_connection_error(
- exc: requests.exceptions.ConnectionError,
-) -> Response:
- # TODO: Issue
- # This is incorrect - it raises on the server but should raise on the
- # client
- # Look into how ``requests`` handles it
- raise exc
-
-
@CLOUDRECO_FLASK_APP.errorhandler(ValidatorException)
def handle_exceptions(exc: ValidatorException) -> Response:
"""
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 96d296b3b..9660dbd26 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -34,6 +34,7 @@
VWS_FLASK_APP = Flask(import_name=__name__)
VWS_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
+
class ResponseNoContentTypeAdded(Response):
"""
A custom response type.
From bfbc6d13bd2e17e148364f97663ed996daf4e1bd Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 09:36:06 +0100
Subject: [PATCH 0234/3455] Progress towards moving logic of dict dumping to
Target and VuforiaDatabase classes
---
src/mock_vws/_flask_server/vws/_databases.py | 74 ++------------------
src/mock_vws/database.py | 68 ++++++++++++++++++
2 files changed, 72 insertions(+), 70 deletions(-)
diff --git a/src/mock_vws/_flask_server/vws/_databases.py b/src/mock_vws/_flask_server/vws/_databases.py
index 146191e21..2c28d8537 100644
--- a/src/mock_vws/_flask_server/vws/_databases.py
+++ b/src/mock_vws/_flask_server/vws/_databases.py
@@ -1,81 +1,15 @@
-import base64
-import datetime
-import io
from typing import Set
import requests
-from backports.zoneinfo import ZoneInfo
from mock_vws.database import VuforiaDatabase
-from mock_vws.states import States
-from mock_vws.target import Target
from ._constants import STORAGE_BASE_URL
def get_all_databases() -> Set[VuforiaDatabase]:
response = requests.get(url=STORAGE_BASE_URL + '/databases')
- response_json = response.json()
- databases = set()
- for database_dict in response_json:
- database_name = database_dict['database_name']
- server_access_key = database_dict['server_access_key']
- server_secret_key = database_dict['server_secret_key']
- client_access_key = database_dict['client_access_key']
- client_secret_key = database_dict['client_secret_key']
- state = States(database_dict['state_value'])
-
- new_database = VuforiaDatabase(
- database_name=database_name,
- server_access_key=server_access_key,
- server_secret_key=server_secret_key,
- client_access_key=client_access_key,
- client_secret_key=client_secret_key,
- state=state,
- )
-
- for target_dict in database_dict['targets']:
- # TODO target.from_dict()
- name = target_dict['name']
- active_flag = target_dict['active_flag']
- width = target_dict['width']
- image_base64 = target_dict['image_base64']
- image_bytes = base64.b64decode(image_base64)
- image = io.BytesIO(image_bytes)
- processing_time_seconds = target_dict['processing_time_seconds']
- application_metadata = target_dict['application_metadata']
-
- target = Target(
- name=name,
- active_flag=active_flag,
- width=width,
- image=image,
- processing_time_seconds=processing_time_seconds,
- application_metadata=application_metadata,
- )
- target.target_id = target_dict['target_id']
- gmt = ZoneInfo('GMT')
- target.last_modified_date = datetime.datetime.fromisoformat(
- target_dict['last_modified_date'],
- )
- target.last_modified_date = target.last_modified_date.replace(
- tzinfo=gmt,
- )
- target.upload_date = datetime.datetime.fromisoformat(
- target_dict['upload_date'],
- )
- target.processed_tracking_rating = target_dict[
- 'processed_tracking_rating'
- ]
- target.upload_date = target.upload_date.replace(tzinfo=gmt)
- delete_date_optional = target_dict['delete_date_optional']
- if delete_date_optional:
- target.delete_date = datetime.datetime.fromisoformat(
- delete_date_optional,
- )
- target.delete_date = target.delete_date.replace(tzinfo=gmt)
- new_database.targets.add(target)
-
- databases.add(new_database)
-
- return databases
+ return set(
+ VuforiaDatabase.from_dict(database_dict=database_dict)
+ for database_dict in response.json()
+ )
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 166f5dd78..8fdd2bcca 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -2,10 +2,17 @@
Utilities for managing mock Vuforia databases.
"""
+from __future__ import annotations
+
+import base64
+import datetime
+import io
import uuid
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Set, Union
+from backports.zoneinfo import ZoneInfo
+
from mock_vws._constants import TargetStatuses
from mock_vws.states import States
from mock_vws.target import Target
@@ -61,6 +68,67 @@ def to_dict(
'targets': targets,
}
+ @classmethod
+ def from_dict(cls, database_dict) -> VuforiaDatabase:
+ database_name = database_dict['database_name']
+ server_access_key = database_dict['server_access_key']
+ server_secret_key = database_dict['server_secret_key']
+ client_access_key = database_dict['client_access_key']
+ client_secret_key = database_dict['client_secret_key']
+ state = States(database_dict['state_value'])
+
+ new_database = cls(
+ database_name=database_name,
+ server_access_key=server_access_key,
+ server_secret_key=server_secret_key,
+ client_access_key=client_access_key,
+ client_secret_key=client_secret_key,
+ state=state,
+ )
+ for target_dict in database_dict['targets']:
+ # TODO target.from_dict()
+ name = target_dict['name']
+ active_flag = target_dict['active_flag']
+ width = target_dict['width']
+ image_base64 = target_dict['image_base64']
+ image_bytes = base64.b64decode(image_base64)
+ image = io.BytesIO(image_bytes)
+ processing_time_seconds = target_dict['processing_time_seconds']
+ application_metadata = target_dict['application_metadata']
+
+ target = Target(
+ name=name,
+ active_flag=active_flag,
+ width=width,
+ image=image,
+ processing_time_seconds=processing_time_seconds,
+ application_metadata=application_metadata,
+ )
+ target.target_id = target_dict['target_id']
+ gmt = ZoneInfo('GMT')
+ target.last_modified_date = datetime.datetime.fromisoformat(
+ target_dict['last_modified_date'],
+ )
+ target.last_modified_date = target.last_modified_date.replace(
+ tzinfo=gmt,
+ )
+ target.upload_date = datetime.datetime.fromisoformat(
+ target_dict['upload_date'],
+ )
+ target.processed_tracking_rating = target_dict[
+ 'processed_tracking_rating'
+ ]
+ target.upload_date = target.upload_date.replace(tzinfo=gmt)
+ delete_date_optional = target_dict['delete_date_optional']
+ if delete_date_optional:
+ target.delete_date = datetime.datetime.fromisoformat(
+ delete_date_optional,
+ )
+ target.delete_date = target.delete_date.replace(tzinfo=gmt)
+ new_database.targets.add(target)
+
+ return new_database
+
@property
def not_deleted_targets(self) -> Set[Target]:
"""
From d58f201ecc14a1b2493129d977083550a63e1151 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 09:40:46 +0100
Subject: [PATCH 0235/3455] Remove helper file
---
src/mock_vws/_flask_server/vwq/__init__.py | 17 +++++++++++++++--
src/mock_vws/_flask_server/vws/__init__.py | 17 ++++++++++++++---
src/mock_vws/_flask_server/vws/_databases.py | 15 ---------------
3 files changed, 29 insertions(+), 20 deletions(-)
delete mode 100644 src/mock_vws/_flask_server/vws/_databases.py
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 8f5965284..74bd0fe8e 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -8,8 +8,9 @@
import copy
import email.utils
from http import HTTPStatus
-from typing import Dict, Optional
+from typing import Dict, Optional, Set
+import requests
from flask import Flask, Response, request
from werkzeug.datastructures import Headers
@@ -23,13 +24,25 @@
MatchProcessing,
ValidatorException,
)
+from mock_vws.database import VuforiaDatabase
-from ..vws._databases import get_all_databases
+from .._constants import STORAGE_BASE_URL
CLOUDRECO_FLASK_APP = Flask(import_name=__name__)
CLOUDRECO_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
+def get_all_databases() -> Set[VuforiaDatabase]:
+ """
+ Get all database objects from the storage backend.
+ """
+ response = requests.get(url=STORAGE_BASE_URL + '/databases')
+ return set(
+ VuforiaDatabase.from_dict(database_dict=database_dict)
+ for database_dict in response.json()
+ )
+
+
@CLOUDRECO_FLASK_APP.before_request
def validate_request() -> None:
request.environ['wsgi.input_terminated'] = True
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 9660dbd26..1e9fb799b 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -8,7 +8,7 @@
import json
import uuid
from http import HTTPStatus
-from typing import Dict, List, Optional
+from typing import Dict, List, Optional, Set
import requests
from flask import Flask, Response, request
@@ -28,8 +28,19 @@
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
-from ._constants import STORAGE_BASE_URL
-from ._databases import get_all_databases
+from .._constants import STORAGE_BASE_URL
+
+
+def get_all_databases() -> Set[VuforiaDatabase]:
+ """
+ Get all database objects from the storage backend.
+ """
+ response = requests.get(url=STORAGE_BASE_URL + '/databases')
+ return set(
+ VuforiaDatabase.from_dict(database_dict=database_dict)
+ for database_dict in response.json()
+ )
+
VWS_FLASK_APP = Flask(import_name=__name__)
VWS_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
diff --git a/src/mock_vws/_flask_server/vws/_databases.py b/src/mock_vws/_flask_server/vws/_databases.py
deleted file mode 100644
index 2c28d8537..000000000
--- a/src/mock_vws/_flask_server/vws/_databases.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from typing import Set
-
-import requests
-
-from mock_vws.database import VuforiaDatabase
-
-from ._constants import STORAGE_BASE_URL
-
-
-def get_all_databases() -> Set[VuforiaDatabase]:
- response = requests.get(url=STORAGE_BASE_URL + '/databases')
- return set(
- VuforiaDatabase.from_dict(database_dict=database_dict)
- for database_dict in response.json()
- )
From f9e60012b28ab8898741ce864a9ef1d11489b75e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 10:46:52 +0100
Subject: [PATCH 0236/3455] Progress towards moving logic of dict dumping to
Target and VuforiaDatabase classes
---
.../_flask_server/{vws => }/_constants.py | 0
src/mock_vws/database.py | 68 ++++---------------
src/mock_vws/target.py | 42 +++++++++++-
3 files changed, 53 insertions(+), 57 deletions(-)
rename src/mock_vws/_flask_server/{vws => }/_constants.py (100%)
diff --git a/src/mock_vws/_flask_server/vws/_constants.py b/src/mock_vws/_flask_server/_constants.py
similarity index 100%
rename from src/mock_vws/_flask_server/vws/_constants.py
rename to src/mock_vws/_flask_server/_constants.py
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 8fdd2bcca..625c77d5b 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -70,64 +70,20 @@ def to_dict(
@classmethod
def from_dict(cls, database_dict) -> VuforiaDatabase:
- database_name = database_dict['database_name']
- server_access_key = database_dict['server_access_key']
- server_secret_key = database_dict['server_secret_key']
- client_access_key = database_dict['client_access_key']
- client_secret_key = database_dict['client_secret_key']
- state = States(database_dict['state_value'])
-
- new_database = cls(
- database_name=database_name,
- server_access_key=server_access_key,
- server_secret_key=server_secret_key,
- client_access_key=client_access_key,
- client_secret_key=client_secret_key,
- state=state,
+ database = cls(
+ database_name=database_dict['database_name'],
+ server_access_key=database_dict['server_access_key'],
+ server_secret_key=database_dict['server_secret_key'],
+ client_access_key=database_dict['client_access_key'],
+ client_secret_key=database_dict['client_secret_key'],
+ state=States(database_dict['state_value']),
)
+
for target_dict in database_dict['targets']:
- # TODO target.from_dict()
- name = target_dict['name']
- active_flag = target_dict['active_flag']
- width = target_dict['width']
- image_base64 = target_dict['image_base64']
- image_bytes = base64.b64decode(image_base64)
- image = io.BytesIO(image_bytes)
- processing_time_seconds = target_dict['processing_time_seconds']
- application_metadata = target_dict['application_metadata']
-
- target = Target(
- name=name,
- active_flag=active_flag,
- width=width,
- image=image,
- processing_time_seconds=processing_time_seconds,
- application_metadata=application_metadata,
- )
- target.target_id = target_dict['target_id']
- gmt = ZoneInfo('GMT')
- target.last_modified_date = datetime.datetime.fromisoformat(
- target_dict['last_modified_date'],
- )
- target.last_modified_date = target.last_modified_date.replace(
- tzinfo=gmt,
- )
- target.upload_date = datetime.datetime.fromisoformat(
- target_dict['upload_date'],
- )
- target.processed_tracking_rating = target_dict[
- 'processed_tracking_rating'
- ]
- target.upload_date = target.upload_date.replace(tzinfo=gmt)
- delete_date_optional = target_dict['delete_date_optional']
- if delete_date_optional:
- target.delete_date = datetime.datetime.fromisoformat(
- delete_date_optional,
- )
- target.delete_date = target.delete_date.replace(tzinfo=gmt)
- new_database.targets.add(target)
-
- return new_database
+ target = Target.from_dict(target_dict=target_dict)
+ database.targets.add(target)
+
+ return database
@property
def not_deleted_targets(self) -> Set[Target]:
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 4468dbc12..f8d006288 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -181,13 +181,53 @@ def tracking_rating(self) -> int:
return 0
+ # TODO use TypedDict here and all to_dict and from_dict
@classmethod
def from_dict(
- cls, data: Dict[str, Optional[Union[str, int, bool, float]]]
+ cls, target_dict: Dict[str, Optional[Union[str, int, bool, float]]]
) -> Target:
"""
TODO
"""
+ name = target_dict['name']
+ active_flag = target_dict['active_flag']
+ width = target_dict['width']
+ image_base64 = target_dict['image_base64']
+ image_bytes = base64.b64decode(image_base64)
+ image = io.BytesIO(image_bytes)
+ processing_time_seconds = target_dict['processing_time_seconds']
+ application_metadata = target_dict['application_metadata']
+
+ target = Target(
+ name=name,
+ active_flag=active_flag,
+ width=width,
+ image=image,
+ processing_time_seconds=processing_time_seconds,
+ application_metadata=application_metadata,
+ )
+ target.target_id = target_dict['target_id']
+ gmt = ZoneInfo('GMT')
+ target.last_modified_date = datetime.datetime.fromisoformat(
+ target_dict['last_modified_date'],
+ )
+ target.last_modified_date = target.last_modified_date.replace(
+ tzinfo=gmt,
+ )
+ target.upload_date = datetime.datetime.fromisoformat(
+ target_dict['upload_date'],
+ )
+ target.processed_tracking_rating = target_dict[
+ 'processed_tracking_rating'
+ ]
+ target.upload_date = target.upload_date.replace(tzinfo=gmt)
+ delete_date_optional = target_dict['delete_date_optional']
+ if delete_date_optional:
+ target.delete_date = datetime.datetime.fromisoformat(
+ delete_date_optional,
+ )
+ target.delete_date = target.delete_date.replace(tzinfo=gmt)
+ return target
def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
delete_date: Optional[str] = None
From 803cb78984440e599f597a55d4190a298753e6a7 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 10:57:51 +0100
Subject: [PATCH 0237/3455] Simple mypy hints using TypeDicts
---
src/mock_vws/_flask_server/vwq/_constants.py | 2 ++
src/mock_vws/database.py | 29 ++++++++++----------
src/mock_vws/target.py | 22 +++++++++++----
3 files changed, 33 insertions(+), 20 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/_constants.py b/src/mock_vws/_flask_server/vwq/_constants.py
index cbba98ffa..ad5b18eaa 100644
--- a/src/mock_vws/_flask_server/vwq/_constants.py
+++ b/src/mock_vws/_flask_server/vwq/_constants.py
@@ -4,6 +4,8 @@
from enum import Enum
+# TODO remove this and repeated duplication
+
class ResultCodes(Enum):
"""
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 625c77d5b..add91cf06 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -4,18 +4,23 @@
from __future__ import annotations
-import base64
-import datetime
-import io
import uuid
from dataclasses import dataclass, field
-from typing import Dict, List, Optional, Set, Union
-
-from backports.zoneinfo import ZoneInfo
+from typing import Dict, List, Set, TypedDict, Union
from mock_vws._constants import TargetStatuses
from mock_vws.states import States
-from mock_vws.target import Target
+from mock_vws.target import Target, TargetDict
+
+
+class DatabaseDict(TypedDict):
+ database_name: str
+ server_access_key: str
+ server_secret_key: str
+ client_access_key: str
+ client_secret_key: str
+ state_value: str
+ targets: List[TargetDict]
def _random_hex() -> str:
@@ -50,13 +55,7 @@ class VuforiaDatabase:
def to_dict(
self,
- ) -> Dict[
- str,
- Union[
- str,
- List[Dict[str, Optional[Union[str, int, bool, float]]]],
- ],
- ]:
+ ) -> Dict[str, Union[str, List[TargetDict]]]:
targets = [target.to_dict() for target in self.targets]
return {
'database_name': self.database_name,
@@ -69,7 +68,7 @@ def to_dict(
}
@classmethod
- def from_dict(cls, database_dict) -> VuforiaDatabase:
+ def from_dict(cls, database_dict: DatabaseDict) -> VuforiaDatabase:
database = cls(
database_name=database_dict['database_name'],
server_access_key=database_dict['server_access_key'],
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index f8d006288..4f0d75bd7 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -9,7 +9,7 @@
import random
import statistics
import uuid
-from typing import Dict, Optional, Union
+from typing import Optional, TypedDict, Union
from backports.zoneinfo import ZoneInfo
from PIL import Image, ImageStat
@@ -17,6 +17,20 @@
from mock_vws._constants import TargetStatuses
+class TargetDict(TypedDict):
+ name: str
+ width: float
+ image_base64: str
+ active_flag: bool
+ processing_time_seconds: Union[int, float]
+ processed_tracking_rating: int
+ application_metadata: str
+ target_id: str
+ last_modified_date: str
+ delete_date_optional: Optional[str]
+ upload_date: str
+
+
class Target: # pylint: disable=too-many-instance-attributes
"""
A Vuforia Target as managed in
@@ -183,9 +197,7 @@ def tracking_rating(self) -> int:
# TODO use TypedDict here and all to_dict and from_dict
@classmethod
- def from_dict(
- cls, target_dict: Dict[str, Optional[Union[str, int, bool, float]]]
- ) -> Target:
+ def from_dict(cls, target_dict: TargetDict) -> Target:
"""
TODO
"""
@@ -229,7 +241,7 @@ def from_dict(
target.delete_date = target.delete_date.replace(tzinfo=gmt)
return target
- def to_dict(self) -> Dict[str, Optional[Union[str, int, bool, float]]]:
+ def to_dict(self) -> TargetDict:
delete_date: Optional[str] = None
if self.delete_date:
delete_date = datetime.datetime.isoformat(self.delete_date)
From 58de906feae7359fcb14fd68858ad3ca5aa0b8e9 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 11:02:13 +0100
Subject: [PATCH 0238/3455] Progress towards passing pylint
---
src/mock_vws/_flask_server/vwq/__init__.py | 2 +-
src/mock_vws/_flask_server/vws/__init__.py | 2 +-
src/mock_vws/target.py | 19 ++++++-------------
3 files changed, 8 insertions(+), 15 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 74bd0fe8e..192f2f470 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -34,7 +34,7 @@
def get_all_databases() -> Set[VuforiaDatabase]:
"""
- Get all database objects from the storage backend.
+ Get all database objects from the storage back-end.
"""
response = requests.get(url=STORAGE_BASE_URL + '/databases')
return set(
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 1e9fb799b..1ea3cf2c2 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -33,7 +33,7 @@
def get_all_databases() -> Set[VuforiaDatabase]:
"""
- Get all database objects from the storage backend.
+ Get all database objects from the storage back-end.
"""
response = requests.get(url=STORAGE_BASE_URL + '/databases')
return set(
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 4f0d75bd7..1a720f031 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -195,7 +195,6 @@ def tracking_rating(self) -> int:
return 0
- # TODO use TypedDict here and all to_dict and from_dict
@classmethod
def from_dict(cls, target_dict: TargetDict) -> Target:
"""
@@ -205,6 +204,8 @@ def from_dict(cls, target_dict: TargetDict) -> Target:
active_flag = target_dict['active_flag']
width = target_dict['width']
image_base64 = target_dict['image_base64']
+ upload_date = target_dict['upload_date']
+ processed_tracking_rating = target_dict['processed_tracking_rating']
image_bytes = base64.b64decode(image_base64)
image = io.BytesIO(image_bytes)
processing_time_seconds = target_dict['processing_time_seconds']
@@ -222,23 +223,15 @@ def from_dict(cls, target_dict: TargetDict) -> Target:
gmt = ZoneInfo('GMT')
target.last_modified_date = datetime.datetime.fromisoformat(
target_dict['last_modified_date'],
- )
- target.last_modified_date = target.last_modified_date.replace(
- tzinfo=gmt,
- )
- target.upload_date = datetime.datetime.fromisoformat(
- target_dict['upload_date'],
- )
- target.processed_tracking_rating = target_dict[
- 'processed_tracking_rating'
- ]
+ ).replace(tzinfo=gmt)
+ target.upload_date = datetime.datetime.fromisoformat(upload_date)
+ target.processed_tracking_rating = processed_tracking_rating
target.upload_date = target.upload_date.replace(tzinfo=gmt)
delete_date_optional = target_dict['delete_date_optional']
if delete_date_optional:
target.delete_date = datetime.datetime.fromisoformat(
delete_date_optional,
- )
- target.delete_date = target.delete_date.replace(tzinfo=gmt)
+ ).replace(tzinfo=gmt)
return target
def to_dict(self) -> TargetDict:
From 5011674421627a7a67483778840ef9e230f2a27e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 11:04:25 +0100
Subject: [PATCH 0239/3455] Remove duplicate constants file
---
src/mock_vws/_flask_server/vwq/_constants.py | 51 --------------------
1 file changed, 51 deletions(-)
delete mode 100644 src/mock_vws/_flask_server/vwq/_constants.py
diff --git a/src/mock_vws/_flask_server/vwq/_constants.py b/src/mock_vws/_flask_server/vwq/_constants.py
deleted file mode 100644
index ad5b18eaa..000000000
--- a/src/mock_vws/_flask_server/vwq/_constants.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""
-Constants used to make the VWS mock.
-"""
-
-from enum import Enum
-
-# TODO remove this and repeated duplication
-
-
-class ResultCodes(Enum):
- """
- Constants representing various VWS result codes.
-
- See
- https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Interperete-VWS-API-Result-Codes
-
- Some codes here are not documented in the above link.
- """
-
- SUCCESS = 'Success'
- TARGET_CREATED = 'TargetCreated'
- AUTHENTICATION_FAILURE = 'AuthenticationFailure'
- REQUEST_TIME_TOO_SKEWED = 'RequestTimeTooSkewed'
- TARGET_NAME_EXIST = 'TargetNameExist'
- UNKNOWN_TARGET = 'UnknownTarget'
- BAD_IMAGE = 'BadImage'
- IMAGE_TOO_LARGE = 'ImageTooLarge'
- METADATA_TOO_LARGE = 'MetadataTooLarge'
- # The documentation says "Start date is after the end date" but, at the
- # time of writing, I do not know how to trigger that, therefore this is not
- # tested.
- DATE_RANGE_ERROR = 'DateRangeError'
- FAIL = 'Fail'
- TARGET_STATUS_PROCESSING = 'TargetStatusProcessing'
- REQUEST_QUOTA_REACHED = 'RequestQuotaReached'
- TARGET_STATUS_NOT_SUCCESS = 'TargetStatusNotSuccess'
- PROJECT_INACTIVE = 'ProjectInactive'
- INACTIVE_PROJECT = 'InactiveProject'
-
-
-class TargetStatuses(Enum):
- """
- Constants representing VWS target statuses.
-
- See the 'status' field in
- https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Retrieve-a-Target-Record
- """
-
- PROCESSING = 'processing'
- SUCCESS = 'success'
- FAILED = 'failed'
From 2f33c75d2837fa679e76f43f950c4f7d347696f9 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 11:08:22 +0100
Subject: [PATCH 0240/3455] Move a few things around from constants file
---
src/mock_vws/_flask_server/_constants.py | 54 ----------------------
src/mock_vws/_flask_server/vwq/__init__.py | 5 ++
src/mock_vws/_flask_server/vws/__init__.py | 12 +++--
3 files changed, 12 insertions(+), 59 deletions(-)
delete mode 100644 src/mock_vws/_flask_server/_constants.py
diff --git a/src/mock_vws/_flask_server/_constants.py b/src/mock_vws/_flask_server/_constants.py
deleted file mode 100644
index 8050841a3..000000000
--- a/src/mock_vws/_flask_server/_constants.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""
-Constants used to make the VWS mock.
-"""
-
-from enum import Enum
-
-
-class ResultCodes(Enum):
- """
- Constants representing various VWS result codes.
-
- See
- https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Interperete-VWS-API-Result-Codes
-
- Some codes here are not documented in the above link.
- """
-
- SUCCESS = 'Success'
- TARGET_CREATED = 'TargetCreated'
- AUTHENTICATION_FAILURE = 'AuthenticationFailure'
- REQUEST_TIME_TOO_SKEWED = 'RequestTimeTooSkewed'
- TARGET_NAME_EXIST = 'TargetNameExist'
- UNKNOWN_TARGET = 'UnknownTarget'
- BAD_IMAGE = 'BadImage'
- IMAGE_TOO_LARGE = 'ImageTooLarge'
- METADATA_TOO_LARGE = 'MetadataTooLarge'
- # The documentation says "Start date is after the end date" but, at the
- # time of writing, I do not know how to trigger that, therefore this is not
- # tested.
- DATE_RANGE_ERROR = 'DateRangeError'
- FAIL = 'Fail'
- TARGET_STATUS_PROCESSING = 'TargetStatusProcessing'
- REQUEST_QUOTA_REACHED = 'RequestQuotaReached'
- TARGET_STATUS_NOT_SUCCESS = 'TargetStatusNotSuccess'
- PROJECT_INACTIVE = 'ProjectInactive'
- INACTIVE_PROJECT = 'InactiveProject'
-
-
-class TargetStatuses(Enum):
- """
- Constants representing VWS target statuses.
-
- See the 'status' field in
- https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Retrieve-a-Target-Record
- """
-
- PROCESSING = 'processing'
- SUCCESS = 'success'
- FAILED = 'failed'
-
-
-# TODO choose something for this - it should actually work in a docker-compose
-# scenario.
-STORAGE_BASE_URL = 'http://todo.com'
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 192f2f470..424717e0f 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -32,6 +32,11 @@
CLOUDRECO_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
+# TODO choose something for this - it should actually work in a docker-compose
+# scenario.
+STORAGE_BASE_URL = 'http://todo.com'
+
+
def get_all_databases() -> Set[VuforiaDatabase]:
"""
Get all database objects from the storage back-end.
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 1ea3cf2c2..aeef150f6 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -28,7 +28,13 @@
from mock_vws.database import VuforiaDatabase
from mock_vws.target import Target
-from .._constants import STORAGE_BASE_URL
+VWS_FLASK_APP = Flask(import_name=__name__)
+VWS_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
+
+
+# TODO choose something for this - it should actually work in a docker-compose
+# scenario.
+STORAGE_BASE_URL = 'http://todo.com'
def get_all_databases() -> Set[VuforiaDatabase]:
@@ -42,10 +48,6 @@ def get_all_databases() -> Set[VuforiaDatabase]:
)
-VWS_FLASK_APP = Flask(import_name=__name__)
-VWS_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
-
-
class ResponseNoContentTypeAdded(Response):
"""
A custom response type.
From 01d3210bbb58de8d441079d7b304f0e99c040ab5 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 11:10:51 +0100
Subject: [PATCH 0241/3455] Add another docstring
---
src/mock_vws/target.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 1a720f031..c410ced17 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -198,7 +198,7 @@ def tracking_rating(self) -> int:
@classmethod
def from_dict(cls, target_dict: TargetDict) -> Target:
"""
- TODO
+ Load a target from a dictionary.
"""
name = target_dict['name']
active_flag = target_dict['active_flag']
From 551a7e304d961b11326b92b9a4d9af4cae59bbd9 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 11:14:26 +0100
Subject: [PATCH 0242/3455] Progress towards passing pylint
---
src/mock_vws/_flask_server/vwq/__init__.py | 2 --
src/mock_vws/database.py | 16 ++++++++++++----
src/mock_vws/target.py | 7 +++++++
3 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 424717e0f..b713bee8a 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -26,8 +26,6 @@
)
from mock_vws.database import VuforiaDatabase
-from .._constants import STORAGE_BASE_URL
-
CLOUDRECO_FLASK_APP = Flask(import_name=__name__)
CLOUDRECO_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index add91cf06..ef145508e 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -6,7 +6,7 @@
import uuid
from dataclasses import dataclass, field
-from typing import Dict, List, Set, TypedDict, Union
+from typing import List, Set, TypedDict
from mock_vws._constants import TargetStatuses
from mock_vws.states import States
@@ -14,6 +14,10 @@
class DatabaseDict(TypedDict):
+ """
+ A dictionary type which represents a database.
+ """
+
database_name: str
server_access_key: str
server_secret_key: str
@@ -53,9 +57,10 @@ class VuforiaDatabase:
total_recos = 0
target_quota = 1000
- def to_dict(
- self,
- ) -> Dict[str, Union[str, List[TargetDict]]]:
+ def to_dict(self) -> DatabaseDict:
+ """
+ Dump a target to a dictionary which can be loaded as JSON.
+ """
targets = [target.to_dict() for target in self.targets]
return {
'database_name': self.database_name,
@@ -69,6 +74,9 @@ def to_dict(
@classmethod
def from_dict(cls, database_dict: DatabaseDict) -> VuforiaDatabase:
+ """
+ Load a database from a dictionary.
+ """
database = cls(
database_name=database_dict['database_name'],
server_access_key=database_dict['server_access_key'],
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index c410ced17..9031d128a 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -18,6 +18,10 @@
class TargetDict(TypedDict):
+ """
+ A dictionary type which represents a target.
+ """
+
name: str
width: float
image_base64: str
@@ -235,6 +239,9 @@ def from_dict(cls, target_dict: TargetDict) -> Target:
return target
def to_dict(self) -> TargetDict:
+ """
+ Dump a target to a dictionary which can be loaded as JSON.
+ """
delete_date: Optional[str] = None
if self.delete_date:
delete_date = datetime.datetime.isoformat(self.delete_date)
From eb3f57cc1b52ce7beac4f35a83fbaa446dba8316 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 11:19:56 +0100
Subject: [PATCH 0243/3455] A few more lint fixes
---
requirements.txt | 1 +
src/mock_vws/_flask_server/vwq/__init__.py | 6 +++++-
src/mock_vws/_flask_server/vws/__init__.py | 6 +++++-
3 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index a5641ccde..babed4eff 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,4 +3,5 @@ VWS-Auth-Tools==2020.5.31.0
backports.zoneinfo==0.2.1
requests-mock==1.8.0
requests==2.24.0
+typing-extensions==3.7.4.3
wrapt==1.12.1
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index b713bee8a..f51b1912f 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -12,6 +12,7 @@
import requests
from flask import Flask, Response, request
+from typing_extensions import Final
from werkzeug.datastructures import Headers
from mock_vws._query_tools import ( # TODO remove each of these and just raise the validator exception
@@ -32,7 +33,7 @@
# TODO choose something for this - it should actually work in a docker-compose
# scenario.
-STORAGE_BASE_URL = 'http://todo.com'
+STORAGE_BASE_URL: Final[str] = 'http://todo.com'
def get_all_databases() -> Set[VuforiaDatabase]:
@@ -48,6 +49,9 @@ def get_all_databases() -> Set[VuforiaDatabase]:
@CLOUDRECO_FLASK_APP.before_request
def validate_request() -> None:
+ """
+ Run validators on the request.
+ """
request.environ['wsgi.input_terminated'] = True
input_stream_copy = copy.copy(request.input_stream)
request_body = input_stream_copy.read()
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index aeef150f6..8b729bb28 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -13,6 +13,7 @@
import requests
from flask import Flask, Response, request
from PIL import Image
+from typing_extensions import Final
from werkzeug.datastructures import Headers
from mock_vws._constants import ResultCodes, TargetStatuses
@@ -34,7 +35,7 @@
# TODO choose something for this - it should actually work in a docker-compose
# scenario.
-STORAGE_BASE_URL = 'http://todo.com'
+STORAGE_BASE_URL: Final[str] = 'http://todo.com'
def get_all_databases() -> Set[VuforiaDatabase]:
@@ -95,6 +96,9 @@ def __init__(
@VWS_FLASK_APP.before_request
def validate_request() -> None:
+ """
+ Run validators on the request.
+ """
request.environ['wsgi.input_terminated'] = True
databases = get_all_databases()
run_services_validators(
From 962c1c0bfbabc718f0ec2f175aad14af6f5a4506 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 11:25:33 +0100
Subject: [PATCH 0244/3455] Bump to newer Dockerfile
---
src/mock_vws/_flask_server/Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/Dockerfile b/src/mock_vws/_flask_server/Dockerfile
index 393ffa77a..79a9ea3f5 100644
--- a/src/mock_vws/_flask_server/Dockerfile
+++ b/src/mock_vws/_flask_server/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.7-slim-buster
+FROM python:3.8-slim-buster
COPY . /app
WORKDIR /app
RUN pip install .
From 14151ee0aa91b4cba4ff3646d80f8e8858492415 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 17:39:35 +0100
Subject: [PATCH 0245/3455] Remove a TODO I don't care about
---
src/mock_vws/_flask_server/vwq/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index f51b1912f..544f0f5d3 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -15,7 +15,7 @@
from typing_extensions import Final
from werkzeug.datastructures import Headers
-from mock_vws._query_tools import ( # TODO remove each of these and just raise the validator exception
+from mock_vws._query_tools import (
ActiveMatchingTargetsDeleteProcessing,
MatchingTargetsWithProcessingStatus,
get_query_match_response_text,
From 6f59c0d5ec2d256b072f9935ae87ad0abf2f4bed Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 17:51:31 +0100
Subject: [PATCH 0246/3455] Add a docstring
---
src/mock_vws/_flask_server/storage/__init__.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/mock_vws/_flask_server/storage/__init__.py b/src/mock_vws/_flask_server/storage/__init__.py
index 80700950e..f71520a80 100644
--- a/src/mock_vws/_flask_server/storage/__init__.py
+++ b/src/mock_vws/_flask_server/storage/__init__.py
@@ -1,3 +1,7 @@
+"""
+Storage layer for the mock Vuforia Flask application.
+"""
+
import base64
import datetime
import io
From 2829ca2c73972e47b717075bbbcc771e4723c078 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 20:53:23 +0100
Subject: [PATCH 0247/3455] Progress towards shipping Docker container
---
docs/source/docker.rst | 8 +++++---
requirements.txt | 2 ++
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/docs/source/docker.rst b/docs/source/docker.rst
index 661b8ec2a..806f49025 100644
--- a/docs/source/docker.rst
+++ b/docs/source/docker.rst
@@ -4,7 +4,11 @@ Running a server with Docker
Running the mock
----------------
-# TODO this won't work - we need some kind of storage backend thing
+# TODO Get a mock running with instructions here.
+# - Maybe mount a config file?
+# - Config must include:
+# - Initial databases
+# - Things like "query processing time"
.. code:: sh
@@ -36,8 +40,6 @@ The ``VWS_MOCK_DATABASES`` environment variable must be set to a JSON configurat
}
]
-TODO: Also processing time etc.
-
Ports
~~~~~
diff --git a/requirements.txt b/requirements.txt
index babed4eff..e11eda433 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,3 +5,5 @@ requests-mock==1.8.0
requests==2.24.0
typing-extensions==3.7.4.3
wrapt==1.12.1
+flask==1.1.2
+Werkzeug==1.0.1
From b086a5446b85016be94b066cce157047c75e8339 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 20:55:21 +0100
Subject: [PATCH 0248/3455] Sort requirements
---
requirements.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index e11eda433..02adaad31 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,9 +1,9 @@
Pillow==7.2.0
VWS-Auth-Tools==2020.5.31.0
+Werkzeug==1.0.1
backports.zoneinfo==0.2.1
+flask==1.1.2
requests-mock==1.8.0
requests==2.24.0
typing-extensions==3.7.4.3
wrapt==1.12.1
-flask==1.1.2
-Werkzeug==1.0.1
From 7f8136ba7d4745b20969d196d631aa9ed7efe831 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 19 Sep 2020 21:14:09 +0100
Subject: [PATCH 0249/3455] Progress towards allowing a running container
---
docs/source/docker.rst | 66 ++++++++-----------
.../_flask_server/storage/__init__.py | 2 +-
src/mock_vws/database.py | 6 +-
3 files changed, 33 insertions(+), 41 deletions(-)
diff --git a/docs/source/docker.rst b/docs/source/docker.rst
index 806f49025..be33cc709 100644
--- a/docs/source/docker.rst
+++ b/docs/source/docker.rst
@@ -5,43 +5,35 @@ Running the mock
----------------
# TODO Get a mock running with instructions here.
-# - Maybe mount a config file?
-# - Config must include:
-# - Initial databases
-# - Things like "query processing time"
+
+From source
+^^^^^^^^^^^
+
+.. code:: sh
+
+ docker build
+
+From pre-built containers
+^^^^^^^^^^^^^^^^^^^^^^^^^
.. code:: sh
- docker run adamtheturtle/mock-vuforia-storage-backend -e VWS_MOCK_DATABASES=$(cat vws-mock-config.json)
- docker run adamtheturtle/mock-vws -e VWS_MOCK_DATABASES=$(cat vws-mock-config.json)
- docker run adamtheturtle/mock-vwq -e VWS_MOCK_DATABASES=$(cat vws-mock-config.json)
-
-Configuration
--------------
-
-The ``VWS_MOCK_DATABASES`` environment variable must be set to a JSON configuration which looks like:
-
-.. code-block:: json
-
- [
- {
- "state": "working",
- "server_access_key": "my_server_access_key",
- "server_secret_key": "my_server_secret_key",
- "client_access_key": "my_client_access_key",
- "client_secret_key": "my_client_secret_key"
- },
- {
- "state": "inactive",
- "server_access_key": "my_server_access_key2",
- "server_secret_key": "my_server_secret_key2",
- "client_access_key": "my_client_access_key2",
- "client_secret_key": "my_client_secret_key2"
- }
- ]
-
-Ports
-~~~~~
-
-Using ``docker-compose``
-------------------------
+ docker run adamtheturtle/mock-vuforia-storage-backend
+ docker run adamtheturtle/mock-vws \
+ -e STORAGE_BACKEND=... \
+ -e QUERY_PROCESSES_DELETION_SECONDS=...
+ docker run adamtheturtle/mock-vwq \
+ -e STORAGE_BACKEND=... \
+ -e QUERY_PROCESSES_DELETION_SECONDS=...
+
+Creating a database
+-------------------
+
+Make a POST request to the storage backend ``/databases`` with the keys:
+
+* ``database_name``
+* ``server_access_key``
+* ``server_secret_key``
+* ``client_access_key``
+* ``client_secret_key``
+* ``state`` (this can be ``"WORKING"`` or ``"PROJECT_INACTIVE"``)
diff --git a/src/mock_vws/_flask_server/storage/__init__.py b/src/mock_vws/_flask_server/storage/__init__.py
index f71520a80..ce764ab0e 100644
--- a/src/mock_vws/_flask_server/storage/__init__.py
+++ b/src/mock_vws/_flask_server/storage/__init__.py
@@ -49,7 +49,7 @@ def create_database() -> Tuple[str, int]:
client_access_key = request.json['client_access_key']
client_secret_key = request.json['client_secret_key']
database_name = request.json['database_name']
- state = States(request.json['state_value'])
+ state = States[request.json['state_name']]
database = VuforiaDatabase(
server_access_key=server_access_key,
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index ef145508e..e9d2b9c53 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -23,7 +23,7 @@ class DatabaseDict(TypedDict):
server_secret_key: str
client_access_key: str
client_secret_key: str
- state_value: str
+ state_name: str
targets: List[TargetDict]
@@ -68,7 +68,7 @@ def to_dict(self) -> DatabaseDict:
'server_secret_key': self.server_secret_key,
'client_access_key': self.client_access_key,
'client_secret_key': self.client_secret_key,
- 'state_value': self.state.value,
+ 'state_name': self.state.name,
'targets': targets,
}
@@ -83,7 +83,7 @@ def from_dict(cls, database_dict: DatabaseDict) -> VuforiaDatabase:
server_secret_key=database_dict['server_secret_key'],
client_access_key=database_dict['client_access_key'],
client_secret_key=database_dict['client_secret_key'],
- state=States(database_dict['state_value']),
+ state=States[database_dict['state_name']],
)
for target_dict in database_dict['targets']:
From 36c1f73d8f5f7f89f47553c9c24d199eac9d9178 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 07:47:05 +0100
Subject: [PATCH 0250/3455] Remove direct werkzeug imports
---
requirements.txt | 1 -
src/mock_vws/_flask_server/vwq/__init__.py | 5 +----
src/mock_vws/_flask_server/vws/__init__.py | 5 +----
3 files changed, 2 insertions(+), 9 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index 02adaad31..76d3b23bc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,5 @@
Pillow==7.2.0
VWS-Auth-Tools==2020.5.31.0
-Werkzeug==1.0.1
backports.zoneinfo==0.2.1
flask==1.1.2
requests-mock==1.8.0
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 544f0f5d3..0aa429a08 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -13,7 +13,6 @@
import requests
from flask import Flask, Response, request
from typing_extensions import Final
-from werkzeug.datastructures import Headers
from mock_vws._query_tools import (
ActiveMatchingTargetsDeleteProcessing,
@@ -102,9 +101,7 @@ def __init__(
and 'Content-Type' in self.headers
and not content_type_from_headers
):
- headers_dict = dict(self.headers)
- headers_dict.pop('Content-Type')
- self.headers = Headers(headers_dict)
+ del self.headers['Content-Type']
CLOUDRECO_FLASK_APP.response_class = ResponseNoContentTypeAdded
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 8b729bb28..1fe1b4f12 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -14,7 +14,6 @@
from flask import Flask, Response, request
from PIL import Image
from typing_extensions import Final
-from werkzeug.datastructures import Headers
from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_server_keys
@@ -86,9 +85,7 @@ def __init__(
and 'Content-Type' in self.headers
and not content_type_from_headers
):
- headers_dict = dict(self.headers)
- headers_dict.pop('Content-Type')
- self.headers = Headers(headers_dict)
+ del self.headers['Content-Type']
VWS_FLASK_APP.response_class = ResponseNoContentTypeAdded
From 754bd93fccf76d095d20bb90f5f04fc8be09638d Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 09:35:42 +0100
Subject: [PATCH 0251/3455] Progress towards running application as a server
---
docs/source/docker.rst | 24 +++++++++++++++----
src/mock_vws/_flask_server/Dockerfile | 1 +
.../_flask_server/storage/__init__.py | 3 +++
src/mock_vws/_flask_server/vws/__init__.py | 3 +++
4 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/docs/source/docker.rst b/docs/source/docker.rst
index be33cc709..5af3b5685 100644
--- a/docs/source/docker.rst
+++ b/docs/source/docker.rst
@@ -11,18 +11,34 @@ From source
.. code:: sh
- docker build
+ docker build \
+ --file src/mock_vws/_flask_server/Dockerfile \
+ --tag vws-mock \
+ .
+
+ docker build \
+ --file src/mock_vws/_flask_server/Dockerfile \
+ --tag vws-storage \
+ src/mock_vws/_flask_server/vws
+
+ docker build \
+ --file src/mock_vws/_flask_server/Dockerfile \
+ --tag vws-storage \
+ src/mock_vws/_flask_server/vwq
From pre-built containers
^^^^^^^^^^^^^^^^^^^^^^^^^
.. code:: sh
- docker run adamtheturtle/mock-vuforia-storage-backend
- docker run adamtheturtle/mock-vws \
+ docker run vws-mock \
+ --entrypoint src/mock_vws/_flask_server/vws/__init__.py
+ -e
+ docker run vws-mock \
-e STORAGE_BACKEND=... \
-e QUERY_PROCESSES_DELETION_SECONDS=...
- docker run adamtheturtle/mock-vwq \
+ docker run \
+ adamtheturtle/mock-vwq \
-e STORAGE_BACKEND=... \
-e QUERY_PROCESSES_DELETION_SECONDS=...
diff --git a/src/mock_vws/_flask_server/Dockerfile b/src/mock_vws/_flask_server/Dockerfile
index 79a9ea3f5..1f1b8f547 100644
--- a/src/mock_vws/_flask_server/Dockerfile
+++ b/src/mock_vws/_flask_server/Dockerfile
@@ -2,3 +2,4 @@ FROM python:3.8-slim-buster
COPY . /app
WORKDIR /app
RUN pip install .
+ENTRYPOINT ["python"]
diff --git a/src/mock_vws/_flask_server/storage/__init__.py b/src/mock_vws/_flask_server/storage/__init__.py
index ce764ab0e..8c2148973 100644
--- a/src/mock_vws/_flask_server/storage/__init__.py
+++ b/src/mock_vws/_flask_server/storage/__init__.py
@@ -158,3 +158,6 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
target.last_modified_date = now
return jsonify(target.to_dict()), HTTPStatus.OK
+
+if __name == '__main__': # pragma: no cover
+ app.run(debug=True, host='0.0.0.0')
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 1fe1b4f12..546450de8 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -548,3 +548,6 @@ def update_target(target_id: str) -> Response:
response=json_dump(body),
headers=headers,
)
+
+if __name == '__main__': # pragma: no cover
+ app.run(debug=True, host='0.0.0.0')
From d063a7f0e8b4429f96207b391cb8b66e923708e0 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 09:38:41 +0100
Subject: [PATCH 0252/3455] Fix syntax error
---
.../_flask_server/storage/__init__.py | 2 +-
.../_flask_server/vwq/_database_matchers.py | 93 -------------------
src/mock_vws/_flask_server/vws/__init__.py | 2 +-
3 files changed, 2 insertions(+), 95 deletions(-)
delete mode 100644 src/mock_vws/_flask_server/vwq/_database_matchers.py
diff --git a/src/mock_vws/_flask_server/storage/__init__.py b/src/mock_vws/_flask_server/storage/__init__.py
index 8c2148973..2c0da3577 100644
--- a/src/mock_vws/_flask_server/storage/__init__.py
+++ b/src/mock_vws/_flask_server/storage/__init__.py
@@ -159,5 +159,5 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
return jsonify(target.to_dict()), HTTPStatus.OK
-if __name == '__main__': # pragma: no cover
+if __name__ == '__main__': # pragma: no cover
app.run(debug=True, host='0.0.0.0')
diff --git a/src/mock_vws/_flask_server/vwq/_database_matchers.py b/src/mock_vws/_flask_server/vwq/_database_matchers.py
deleted file mode 100644
index afcd282c1..000000000
--- a/src/mock_vws/_flask_server/vwq/_database_matchers.py
+++ /dev/null
@@ -1,93 +0,0 @@
-"""
-Helpers for getting databases which match keys given in requests.
-"""
-
-from typing import Dict, Iterable, Optional
-
-from vws_auth_tools import authorization_header
-
-from mock_vws.database import VuforiaDatabase
-
-
-def get_database_matching_client_keys(
- request_headers: Dict[str, str],
- request_body: Optional[bytes],
- request_method: str,
- request_path: str,
- databases: Iterable[VuforiaDatabase],
-) -> Optional[VuforiaDatabase]:
- """
- Return which, if any, of the given databases is being accessed by the given
- client request.
-
- Args:
- request_headers: The headers sent with the request.
- request_body: The request body.
- request_method: The HTTP method of the request.
- request_path: The path of the request.
- databases: The databases to check for matches.
-
- Returns:
- The database which is being accessed by the given client request.
- """
- content_type = request_headers.get('Content-Type', '').split(';')[0]
- auth_header = request_headers.get('Authorization')
- content = request_body or b''
- date = request_headers.get('Date', '')
-
- for database in databases:
- expected_authorization_header = authorization_header(
- access_key=database.client_access_key,
- secret_key=database.client_secret_key,
- method=request_method,
- content=content,
- content_type=content_type,
- date=date,
- request_path=request_path,
- )
-
- if auth_header == expected_authorization_header:
- return database
- return None
-
-
-def get_database_matching_server_keys(
- request_headers: Dict[str, str],
- request_body: Optional[bytes],
- request_method: str,
- request_path: str,
- databases: Iterable[VuforiaDatabase],
-) -> Optional[VuforiaDatabase]:
- """
- Return which, if any, of the given databases is being accessed by the given
- server request.
-
- Args:
- request_headers: The headers sent with the request.
- request_body: The request body.
- request_method: The HTTP method of the request.
- request_path: The path of the request.
- databases: The databases to check for matches.
-
- Returns:
- The database being accessed by the given server request.
- """
- content_type = request_headers.get('Content-Type', '').split(';')[0]
- auth_header = request_headers.get('Authorization')
- content = request_body or b''
- date = request_headers.get('Date', '')
-
- for database in databases:
- expected_authorization_header = authorization_header(
- access_key=database.server_access_key,
- secret_key=database.server_secret_key,
- method=request_method,
- content=content,
- content_type=content_type,
- date=date,
- request_path=request_path,
- )
-
- if auth_header == expected_authorization_header:
- return database
- return None
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index 546450de8..be0ea0a44 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -549,5 +549,5 @@ def update_target(target_id: str) -> Response:
headers=headers,
)
-if __name == '__main__': # pragma: no cover
+if __name__ == '__main__': # pragma: no cover
app.run(debug=True, host='0.0.0.0')
From 8ee53a906e2478296c14ecb83f227d0134297cbe Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 09:41:08 +0100
Subject: [PATCH 0253/3455] Ignore too-many-ancestors from pylint
---
pyproject.toml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/pyproject.toml b/pyproject.toml
index 7968eaf3e..28bd19cbd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -40,7 +40,9 @@
disable = [
# Tests need `self` to be in a class but do not use it.
'no-self-use',
+ # Style issues that we can deal with ourselves
'too-few-public-methods',
+ 'too-many-ancestors',
'too-many-locals',
'too-many-arguments',
'too-many-instance-attributes',
From bf26f9f02ee02ba990cb7e00417c03e3811e8d72 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 11:05:37 +0100
Subject: [PATCH 0254/3455] Start of having targets and databases dumpable to
dictionaries
---
src/mock_vws/database.py | 55 ++++++++++++++++++++-
src/mock_vws/target.py | 86 +++++++++++++++++++++++++++++++-
tests/mock_vws/test_usage.py | 96 ++++++++++++++++++++++++++++++++++++
3 files changed, 234 insertions(+), 3 deletions(-)
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 2ada5fa6e..e9d2b9c53 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -2,13 +2,29 @@
Utilities for managing mock Vuforia databases.
"""
+from __future__ import annotations
+
import uuid
from dataclasses import dataclass, field
-from typing import Set
+from typing import List, Set, TypedDict
from mock_vws._constants import TargetStatuses
from mock_vws.states import States
-from mock_vws.target import Target
+from mock_vws.target import Target, TargetDict
+
+
+class DatabaseDict(TypedDict):
+ """
+ A dictionary type which represents a database.
+ """
+
+ database_name: str
+ server_access_key: str
+ server_secret_key: str
+ client_access_key: str
+ client_secret_key: str
+ state_name: str
+ targets: List[TargetDict]
def _random_hex() -> str:
@@ -41,6 +57,41 @@ class VuforiaDatabase:
total_recos = 0
target_quota = 1000
+ def to_dict(self) -> DatabaseDict:
+ """
+ Dump a target to a dictionary which can be loaded as JSON.
+ """
+ targets = [target.to_dict() for target in self.targets]
+ return {
+ 'database_name': self.database_name,
+ 'server_access_key': self.server_access_key,
+ 'server_secret_key': self.server_secret_key,
+ 'client_access_key': self.client_access_key,
+ 'client_secret_key': self.client_secret_key,
+ 'state_name': self.state.name,
+ 'targets': targets,
+ }
+
+ @classmethod
+ def from_dict(cls, database_dict: DatabaseDict) -> VuforiaDatabase:
+ """
+ Load a database from a dictionary.
+ """
+ database = cls(
+ database_name=database_dict['database_name'],
+ server_access_key=database_dict['server_access_key'],
+ server_secret_key=database_dict['server_secret_key'],
+ client_access_key=database_dict['client_access_key'],
+ client_secret_key=database_dict['client_secret_key'],
+ state=States[database_dict['state_name']],
+ )
+
+ for target_dict in database_dict['targets']:
+ target = Target.from_dict(target_dict=target_dict)
+ database.targets.add(target)
+
+ return database
+
@property
def not_deleted_targets(self) -> Set[Target]:
"""
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index d910960b5..9031d128a 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -1,13 +1,15 @@
"""
A fake implementation of a target for the Vuforia Web Services API.
"""
+from __future__ import annotations
+import base64
import datetime
import io
import random
import statistics
import uuid
-from typing import Optional, Union
+from typing import Optional, TypedDict, Union
from backports.zoneinfo import ZoneInfo
from PIL import Image, ImageStat
@@ -15,6 +17,24 @@
from mock_vws._constants import TargetStatuses
+class TargetDict(TypedDict):
+ """
+ A dictionary type which represents a target.
+ """
+
+ name: str
+ width: float
+ image_base64: str
+ active_flag: bool
+ processing_time_seconds: Union[int, float]
+ processed_tracking_rating: int
+ application_metadata: str
+ target_id: str
+ last_modified_date: str
+ delete_date_optional: Optional[str]
+ upload_date: str
+
+
class Target: # pylint: disable=too-many-instance-attributes
"""
A Vuforia Target as managed in
@@ -178,3 +198,67 @@ def tracking_rating(self) -> int:
return self.processed_tracking_rating
return 0
+
+ @classmethod
+ def from_dict(cls, target_dict: TargetDict) -> Target:
+ """
+ Load a target from a dictionary.
+ """
+ name = target_dict['name']
+ active_flag = target_dict['active_flag']
+ width = target_dict['width']
+ image_base64 = target_dict['image_base64']
+ upload_date = target_dict['upload_date']
+ processed_tracking_rating = target_dict['processed_tracking_rating']
+ image_bytes = base64.b64decode(image_base64)
+ image = io.BytesIO(image_bytes)
+ processing_time_seconds = target_dict['processing_time_seconds']
+ application_metadata = target_dict['application_metadata']
+
+ target = Target(
+ name=name,
+ active_flag=active_flag,
+ width=width,
+ image=image,
+ processing_time_seconds=processing_time_seconds,
+ application_metadata=application_metadata,
+ )
+ target.target_id = target_dict['target_id']
+ gmt = ZoneInfo('GMT')
+ target.last_modified_date = datetime.datetime.fromisoformat(
+ target_dict['last_modified_date'],
+ ).replace(tzinfo=gmt)
+ target.upload_date = datetime.datetime.fromisoformat(upload_date)
+ target.processed_tracking_rating = processed_tracking_rating
+ target.upload_date = target.upload_date.replace(tzinfo=gmt)
+ delete_date_optional = target_dict['delete_date_optional']
+ if delete_date_optional:
+ target.delete_date = datetime.datetime.fromisoformat(
+ delete_date_optional,
+ ).replace(tzinfo=gmt)
+ return target
+
+ def to_dict(self) -> TargetDict:
+ """
+ Dump a target to a dictionary which can be loaded as JSON.
+ """
+ delete_date: Optional[str] = None
+ if self.delete_date:
+ delete_date = datetime.datetime.isoformat(self.delete_date)
+
+ image_value = self.image.getvalue()
+ image_base64 = base64.encodebytes(image_value).decode()
+
+ return {
+ 'name': self.name,
+ 'width': self.width,
+ 'image_base64': image_base64,
+ 'active_flag': self.active_flag,
+ 'processing_time_seconds': self._processing_time_seconds,
+ 'processed_tracking_rating': self.processed_tracking_rating,
+ 'application_metadata': self.application_metadata,
+ 'target_id': self.target_id,
+ 'last_modified_date': self.last_modified_date.isoformat(),
+ 'delete_date_optional': delete_date,
+ 'upload_date': self.upload_date.isoformat(),
+ }
diff --git a/tests/mock_vws/test_usage.py b/tests/mock_vws/test_usage.py
index 00cc631fb..f832346eb 100644
--- a/tests/mock_vws/test_usage.py
+++ b/tests/mock_vws/test_usage.py
@@ -4,6 +4,7 @@
import email.utils
import io
+import json
import socket
from datetime import datetime, timedelta
@@ -18,6 +19,7 @@
from vws_auth_tools import rfc_1123_date
from mock_vws import MockVWS
+from mock_vws.target import Target
from mock_vws.database import VuforiaDatabase
from mock_vws.states import States
@@ -533,6 +535,100 @@ def test_repr(self, high_quality_image: io.BytesIO) -> None:
(target,) = database.targets
assert repr(target) == f''
+ def test_to_dict(self, high_quality_image: io.BytesIO) -> None:
+ """
+ Test for dumping a target to a dictionary and loading it back.
+ """
+ database = VuforiaDatabase()
+
+ vws_client = VWS(
+ server_access_key=database.server_access_key,
+ server_secret_key=database.server_secret_key,
+ )
+
+ with MockVWS() as mock:
+ mock.add_database(database=database)
+ target_id = vws_client.add_target(
+ name='example',
+ width=1,
+ image=high_quality_image,
+ active_flag=True,
+ application_metadata=None,
+ )
+
+ (target,) = database.targets
+ target_dict = target.to_dict()
+
+ # The dictionary is JSON dump-able
+ assert json.dumps(target_dict)
+
+ new_target = Target.from_dict(target_dict=target_dict)
+ assert new_target.target_id == target.target_id
+ assert new_target.name == target.name
+ assert new_target.width == target.width
+ assert new_target.image.getvalue() == target.image.getvalue()
+ assert new_target.active_flag == target.active_flag
+ assert new_target._processing_time_seconds == (
+ target._processing_time_seconds
+ )
+ assert new_target.processed_tracking_rating == (
+ target.processed_tracking_rating
+ )
+ assert new_target.application_metadata == target.application_metadata
+ assert new_target.last_modified_date == target.last_modified_date
+ assert new_target.delete_date == target.delete_date
+ assert new_target.upload_date == target.upload_date
+
+ def test_to_dict_deleted(self, high_quality_image: io.BytesIO) -> None:
+ """
+ Test for dumping a deleted target to a dictionary and loading it back.
+ """
+ database = VuforiaDatabase()
+
+ vws_client = VWS(
+ server_access_key=database.server_access_key,
+ server_secret_key=database.server_secret_key,
+ )
+
+ with MockVWS() as mock:
+ mock.add_database(database=database)
+ target_id = vws_client.add_target(
+ name='example',
+ width=1,
+ image=high_quality_image,
+ active_flag=True,
+ application_metadata=None,
+ )
+ vws_client.wait_for_target_processed(target_id=target_id)
+ vws_client.delete_target(target_id=target_id)
+
+ (target,) = database.targets
+ target_dict = target.to_dict()
+
+ # The dictionary is JSON dump-able
+ assert json.dumps(target_dict)
+
+ new_target = Target.from_dict(target_dict=target_dict)
+ assert new_target.delete_date == target.delete_date
+
+
+class TestDatabaseToDict:
+ """
+ Tests for dumping a database to a dictionary.
+ """
+
+ def test_to_dict(self, high_quality_image: io.BytesIO) -> None:
+ """
+ Test for dumping a database to a dictionary and loading it back.
+ """
+ database = VuforiaDatabase()
+ database_dict = database.to_dict()
+
+ # The dictionary is JSON dump-able
+ assert json.dumps(database_dict)
+
+ # TODO test with targets added
+ assert new_database == database
class TestDateHeader:
"""
From a4a0b476d57d6de3c48c061162fd335fd3630a43 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 16:55:09 +0100
Subject: [PATCH 0255/3455] Progress towards target as a dataclass
---
src/mock_vws/target.py | 121 +++++++++++++----------------------
tests/mock_vws/test_usage.py | 4 +-
2 files changed, 45 insertions(+), 80 deletions(-)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 9031d128a..5348bb92a 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -4,12 +4,14 @@
from __future__ import annotations
import base64
+from functools import partial
+from dataclasses import dataclass, field
import datetime
import io
import random
import statistics
import uuid
-from typing import Optional, TypedDict, Union
+from typing import Optional, TypedDict, Union, Final
from backports.zoneinfo import ZoneInfo
from PIL import Image, ImageStat
@@ -35,81 +37,41 @@ class TargetDict(TypedDict):
upload_date: str
+def _random_hex() -> str:
+ """
+ Return a random hex value.
+ """
+ return uuid.uuid4().hex
+
+def _time_now() -> datetime.datetime:
+ gmt = ZoneInfo('GMT')
+ return datetime.datetime.now(tz=gmt)
+
+def _random_tracking_rating() -> int:
+ return random.randint(0, 5)
+
+@dataclass(unsafe_hash=True)
class Target: # pylint: disable=too-many-instance-attributes
"""
A Vuforia Target as managed in
https://developer.vuforia.com/target-manager.
"""
- name: str
- target_id: str
active_flag: bool
- width: float
- upload_date: datetime.datetime
- last_modified_date: datetime.datetime
- processed_tracking_rating: int
- image: io.BytesIO
- reco_rating: str
application_metadata: str
- delete_date: Optional[datetime.datetime]
-
- 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
- self._timezone = ZoneInfo('GMT')
- now = datetime.datetime.now(tz=self._timezone)
- 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
- self.total_recos: int = 0
- self.current_month_recos: int = 0
- self.previous_month_recos: int = 0
+ image: io.BytesIO
+ name : str
+ processing_time_seconds: float
+ width: float
+ current_month_recos: int = 0
+ delete_date: Optional[datetime.datetime] = None
+ last_modified_date: datetime.datetime = field(default_factory=_time_now)
+ previous_month_recos: int = 0
+ processed_tracking_rating: int = field(default_factory=_random_tracking_rating)
+ reco_rating: str = ''
+ target_id: str = field(default_factory=_random_hex)
+ total_recos: int = 0
+ upload_date: datetime.datetime = field(default_factory=_time_now)
def __repr__(self) -> str:
"""
@@ -122,7 +84,8 @@ def delete(self) -> None:
"""
Mark the target as deleted.
"""
- now = datetime.datetime.now(tz=self._timezone)
+ timezone = self.upload_date.tzinfo
+ now = datetime.datetime.now(tz=timezone)
self.delete_date = now
@property
@@ -158,10 +121,11 @@ def status(self) -> str:
target is for detection.
"""
processing_time = datetime.timedelta(
- seconds=self._processing_time_seconds,
+ seconds=self.processing_time_seconds,
)
- now = datetime.datetime.now(tz=self._timezone)
+ timezone = self.upload_date.tzinfo
+ now = datetime.datetime.now(tz=timezone)
time_since_change = now - self.last_modified_date
if time_since_change <= processing_time:
@@ -184,11 +148,12 @@ def tracking_rating(self) -> int:
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
+ seconds=self.processing_time_seconds
/ 2,
)
- now = datetime.datetime.now(tz=self._timezone)
+ timezone = self.upload_date.tzinfo
+ now = datetime.datetime.now(tz=timezone)
time_since_upload = now - self.upload_date
if time_since_upload <= pre_rating_time:
@@ -224,18 +189,18 @@ def from_dict(cls, target_dict: TargetDict) -> Target:
application_metadata=application_metadata,
)
target.target_id = target_dict['target_id']
- gmt = ZoneInfo('GMT')
+ timezone = ZoneInfo('GMT')
target.last_modified_date = datetime.datetime.fromisoformat(
target_dict['last_modified_date'],
- ).replace(tzinfo=gmt)
+ ).replace(tzinfo=timezone)
target.upload_date = datetime.datetime.fromisoformat(upload_date)
target.processed_tracking_rating = processed_tracking_rating
- target.upload_date = target.upload_date.replace(tzinfo=gmt)
+ target.upload_date = target.upload_date.replace(tzinfo=timezone)
delete_date_optional = target_dict['delete_date_optional']
if delete_date_optional:
target.delete_date = datetime.datetime.fromisoformat(
delete_date_optional,
- ).replace(tzinfo=gmt)
+ ).replace(tzinfo=timezone)
return target
def to_dict(self) -> TargetDict:
@@ -254,7 +219,7 @@ def to_dict(self) -> TargetDict:
'width': self.width,
'image_base64': image_base64,
'active_flag': self.active_flag,
- 'processing_time_seconds': self._processing_time_seconds,
+ 'processing_time_seconds': self.processing_time_seconds,
'processed_tracking_rating': self.processed_tracking_rating,
'application_metadata': self.application_metadata,
'target_id': self.target_id,
diff --git a/tests/mock_vws/test_usage.py b/tests/mock_vws/test_usage.py
index f832346eb..524799e0d 100644
--- a/tests/mock_vws/test_usage.py
+++ b/tests/mock_vws/test_usage.py
@@ -568,8 +568,8 @@ def test_to_dict(self, high_quality_image: io.BytesIO) -> None:
assert new_target.width == target.width
assert new_target.image.getvalue() == target.image.getvalue()
assert new_target.active_flag == target.active_flag
- assert new_target._processing_time_seconds == (
- target._processing_time_seconds
+ assert new_target.processing_time_seconds == (
+ target.processing_time_seconds
)
assert new_target.processed_tracking_rating == (
target.processed_tracking_rating
From dd14d4ed39ebcea6d815b4a1d5ba4515f89fa734 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 17:12:49 +0100
Subject: [PATCH 0256/3455] Add full tests for to_dict for VuforiaDatabase
---
src/mock_vws/target.py | 27 ++++++++--------
tests/mock_vws/test_usage.py | 62 ++++++++++++------------------------
2 files changed, 33 insertions(+), 56 deletions(-)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 5348bb92a..034c74dd4 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -4,14 +4,13 @@
from __future__ import annotations
import base64
-from functools import partial
-from dataclasses import dataclass, field
import datetime
import io
import random
import statistics
import uuid
-from typing import Optional, TypedDict, Union, Final
+from dataclasses import dataclass, field
+from typing import Optional, TypedDict, Union
from backports.zoneinfo import ZoneInfo
from PIL import Image, ImageStat
@@ -43,14 +42,17 @@ def _random_hex() -> str:
"""
return uuid.uuid4().hex
+
def _time_now() -> datetime.datetime:
gmt = ZoneInfo('GMT')
return datetime.datetime.now(tz=gmt)
+
def _random_tracking_rating() -> int:
return random.randint(0, 5)
-@dataclass(unsafe_hash=True)
+
+@dataclass(unsafe_hash=True, eq=True)
class Target: # pylint: disable=too-many-instance-attributes
"""
A Vuforia Target as managed in
@@ -59,27 +61,24 @@ class Target: # pylint: disable=too-many-instance-attributes
active_flag: bool
application_metadata: str
- image: io.BytesIO
- name : str
+ # Comparison of io.BytesIO compares the object, not the ``getvalue``
+ # data we care about, so we leave this.
+ image: io.BytesIO = field(compare=False)
+ name: str
processing_time_seconds: float
width: float
current_month_recos: int = 0
delete_date: Optional[datetime.datetime] = None
last_modified_date: datetime.datetime = field(default_factory=_time_now)
previous_month_recos: int = 0
- processed_tracking_rating: int = field(default_factory=_random_tracking_rating)
+ processed_tracking_rating: int = field(
+ default_factory=_random_tracking_rating
+ )
reco_rating: str = ''
target_id: str = field(default_factory=_random_hex)
total_recos: int = 0
upload_date: datetime.datetime = field(default_factory=_time_now)
- def __repr__(self) -> str:
- """
- Return a representation which includes the target ID.
- """
- class_name = self.__class__.__name__
- return f'<{class_name}: {self.target_id}>'
-
def delete(self) -> None:
"""
Mark the target as deleted.
diff --git a/tests/mock_vws/test_usage.py b/tests/mock_vws/test_usage.py
index 524799e0d..e0b3f2b8d 100644
--- a/tests/mock_vws/test_usage.py
+++ b/tests/mock_vws/test_usage.py
@@ -19,9 +19,9 @@
from vws_auth_tools import rfc_1123_date
from mock_vws import MockVWS
-from mock_vws.target import Target
from mock_vws.database import VuforiaDatabase
from mock_vws.states import States
+from mock_vws.target import Target
def request_unmocked_address() -> None:
@@ -511,30 +511,6 @@ class TestTargets:
Tests for target representations.
"""
- def test_repr(self, high_quality_image: io.BytesIO) -> None:
- """
- Test for the representation of a ``Target``.
- """
- database = VuforiaDatabase()
-
- vws_client = VWS(
- server_access_key=database.server_access_key,
- server_secret_key=database.server_secret_key,
- )
-
- with MockVWS() as mock:
- mock.add_database(database=database)
- target_id = vws_client.add_target(
- name='example',
- width=1,
- image=high_quality_image,
- active_flag=True,
- application_metadata=None,
- )
-
- (target,) = database.targets
- assert repr(target) == f''
-
def test_to_dict(self, high_quality_image: io.BytesIO) -> None:
"""
Test for dumping a target to a dictionary and loading it back.
@@ -548,7 +524,7 @@ def test_to_dict(self, high_quality_image: io.BytesIO) -> None:
with MockVWS() as mock:
mock.add_database(database=database)
- target_id = vws_client.add_target(
+ vws_client.add_target(
name='example',
width=1,
image=high_quality_image,
@@ -563,21 +539,8 @@ def test_to_dict(self, high_quality_image: io.BytesIO) -> None:
assert json.dumps(target_dict)
new_target = Target.from_dict(target_dict=target_dict)
- assert new_target.target_id == target.target_id
- assert new_target.name == target.name
- assert new_target.width == target.width
assert new_target.image.getvalue() == target.image.getvalue()
- assert new_target.active_flag == target.active_flag
- assert new_target.processing_time_seconds == (
- target.processing_time_seconds
- )
- assert new_target.processed_tracking_rating == (
- target.processed_tracking_rating
- )
- assert new_target.application_metadata == target.application_metadata
- assert new_target.last_modified_date == target.last_modified_date
- assert new_target.delete_date == target.delete_date
- assert new_target.upload_date == target.upload_date
+ assert new_target == target
def test_to_dict_deleted(self, high_quality_image: io.BytesIO) -> None:
"""
@@ -622,14 +585,29 @@ def test_to_dict(self, high_quality_image: io.BytesIO) -> None:
Test for dumping a database to a dictionary and loading it back.
"""
database = VuforiaDatabase()
- database_dict = database.to_dict()
+ vws_client = VWS(
+ server_access_key=database.server_access_key,
+ server_secret_key=database.server_secret_key,
+ )
+
+ with MockVWS() as mock:
+ mock.add_database(database=database)
+ target_id = vws_client.add_target(
+ name='example',
+ width=1,
+ image=high_quality_image,
+ active_flag=True,
+ application_metadata=None,
+ )
+ database_dict = database.to_dict()
# The dictionary is JSON dump-able
assert json.dumps(database_dict)
- # TODO test with targets added
+ new_database = VuforiaDatabase.from_dict(database_dict=database_dict)
assert new_database == database
+
class TestDateHeader:
"""
Tests for the date header in responses from mock routes.
From 33cd940f8a4f7fa4a1f2b6097fb587e26615cdbd Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 17:14:47 +0100
Subject: [PATCH 0257/3455] Fix some lint issues
---
src/mock_vws/target.py | 8 +++++++-
tests/mock_vws/test_usage.py | 3 ++-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 034c74dd4..c7eefa679 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -44,11 +44,17 @@ def _random_hex() -> str:
def _time_now() -> datetime.datetime:
+ """
+ Return the current time in the GMT time zone.
+ """
gmt = ZoneInfo('GMT')
return datetime.datetime.now(tz=gmt)
def _random_tracking_rating() -> int:
+ """
+ Return a random tracking rating.
+ """
return random.randint(0, 5)
@@ -72,7 +78,7 @@ class Target: # pylint: disable=too-many-instance-attributes
last_modified_date: datetime.datetime = field(default_factory=_time_now)
previous_month_recos: int = 0
processed_tracking_rating: int = field(
- default_factory=_random_tracking_rating
+ default_factory=_random_tracking_rating,
)
reco_rating: str = ''
target_id: str = field(default_factory=_random_hex)
diff --git a/tests/mock_vws/test_usage.py b/tests/mock_vws/test_usage.py
index e0b3f2b8d..259170eb1 100644
--- a/tests/mock_vws/test_usage.py
+++ b/tests/mock_vws/test_usage.py
@@ -590,9 +590,10 @@ def test_to_dict(self, high_quality_image: io.BytesIO) -> None:
server_secret_key=database.server_secret_key,
)
+ # We test a database with a target added.
with MockVWS() as mock:
mock.add_database(database=database)
- target_id = vws_client.add_target(
+ vws_client.add_target(
name='example',
width=1,
image=high_quality_image,
From 1bba8f894480018c83f5c340b57b64ad10b66d0f Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 17:17:49 +0100
Subject: [PATCH 0258/3455] Fix some lint issues
---
docs/source/mock-api-reference.rst | 3 +++
src/mock_vws/target.py | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/docs/source/mock-api-reference.rst b/docs/source/mock-api-reference.rst
index d3776274f..3a43ee56c 100644
--- a/docs/source/mock-api-reference.rst
+++ b/docs/source/mock-api-reference.rst
@@ -7,6 +7,9 @@ API Reference
:members:
:undoc-members:
+.. autoclass:: mock_vws.target.TargetDict
+ :members:
+
.. autoclass:: mock_vws.target.Target
:members:
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index c7eefa679..7bbedb56a 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -67,7 +67,7 @@ class Target: # pylint: disable=too-many-instance-attributes
active_flag: bool
application_metadata: str
- # Comparison of io.BytesIO compares the object, not the ``getvalue``
+ # Comparison of io.BytesIO compares the object, not the file contents.
# data we care about, so we leave this.
image: io.BytesIO = field(compare=False)
name: str
From 18e040601cdcfe53c0a870cdf35ac0fb99cbec04 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 17:23:55 +0100
Subject: [PATCH 0259/3455] Remove typing extensions requirement
---
requirements.txt | 1 -
src/mock_vws/_flask_server/storage/__init__.py | 2 +-
src/mock_vws/_flask_server/vwq/__init__.py | 6 ++++--
src/mock_vws/_flask_server/vws/__init__.py | 5 ++---
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index 76d3b23bc..4d13eefa0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,5 +4,4 @@ backports.zoneinfo==0.2.1
flask==1.1.2
requests-mock==1.8.0
requests==2.24.0
-typing-extensions==3.7.4.3
wrapt==1.12.1
diff --git a/src/mock_vws/_flask_server/storage/__init__.py b/src/mock_vws/_flask_server/storage/__init__.py
index 2c0da3577..adb1f3d98 100644
--- a/src/mock_vws/_flask_server/storage/__init__.py
+++ b/src/mock_vws/_flask_server/storage/__init__.py
@@ -160,4 +160,4 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
return jsonify(target.to_dict()), HTTPStatus.OK
if __name__ == '__main__': # pragma: no cover
- app.run(debug=True, host='0.0.0.0')
+ STORAGE_FLASK_APP.run(debug=True, host='0.0.0.0')
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq/__init__.py
index 0aa429a08..59749b9e4 100644
--- a/src/mock_vws/_flask_server/vwq/__init__.py
+++ b/src/mock_vws/_flask_server/vwq/__init__.py
@@ -8,11 +8,10 @@
import copy
import email.utils
from http import HTTPStatus
-from typing import Dict, Optional, Set
+from typing import Dict, Final, Optional, Set
import requests
from flask import Flask, Response, request
-from typing_extensions import Final
from mock_vws._query_tools import (
ActiveMatchingTargetsDeleteProcessing,
@@ -159,3 +158,6 @@ def query() -> Response:
response=response_text,
headers=headers,
)
+
+if __name__ == '__main__': # pragma: no cover
+ CLOUDRECO_FLASK_APP.run(debug=True, host='0.0.0.0')
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws/__init__.py
index be0ea0a44..90de7a524 100644
--- a/src/mock_vws/_flask_server/vws/__init__.py
+++ b/src/mock_vws/_flask_server/vws/__init__.py
@@ -8,12 +8,11 @@
import json
import uuid
from http import HTTPStatus
-from typing import Dict, List, Optional, Set
+from typing import Dict, Final, List, Optional, Set
import requests
from flask import Flask, Response, request
from PIL import Image
-from typing_extensions import Final
from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_server_keys
@@ -550,4 +549,4 @@ def update_target(target_id: str) -> Response:
)
if __name__ == '__main__': # pragma: no cover
- app.run(debug=True, host='0.0.0.0')
+ VWS_FLASK_APP.run(debug=True, host='0.0.0.0')
From 6208bc4f3064ec69975fefc39436375a8bb36467 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 21:10:06 +0100
Subject: [PATCH 0260/3455] Rename a few files
---
src/mock_vws/_flask_server/{storage/__init__.py => storage.py} | 0
src/mock_vws/_flask_server/{vwq/__init__.py => vwq.py} | 0
src/mock_vws/_flask_server/{vws/__init__.py => vws.py} | 0
3 files changed, 0 insertions(+), 0 deletions(-)
rename src/mock_vws/_flask_server/{storage/__init__.py => storage.py} (100%)
rename src/mock_vws/_flask_server/{vwq/__init__.py => vwq.py} (100%)
rename src/mock_vws/_flask_server/{vws/__init__.py => vws.py} (100%)
diff --git a/src/mock_vws/_flask_server/storage/__init__.py b/src/mock_vws/_flask_server/storage.py
similarity index 100%
rename from src/mock_vws/_flask_server/storage/__init__.py
rename to src/mock_vws/_flask_server/storage.py
diff --git a/src/mock_vws/_flask_server/vwq/__init__.py b/src/mock_vws/_flask_server/vwq.py
similarity index 100%
rename from src/mock_vws/_flask_server/vwq/__init__.py
rename to src/mock_vws/_flask_server/vwq.py
diff --git a/src/mock_vws/_flask_server/vws/__init__.py b/src/mock_vws/_flask_server/vws.py
similarity index 100%
rename from src/mock_vws/_flask_server/vws/__init__.py
rename to src/mock_vws/_flask_server/vws.py
From 07805d857bef89b11d0e0d82bdaa6ca41dd9c121 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 21:12:09 +0100
Subject: [PATCH 0261/3455] Remove irrelevant pylint change
---
pyproject.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyproject.toml b/pyproject.toml
index cd79bb38f..28bd19cbd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@
persistent = true
# Use multiple processes to speed up Pylint.
- jobs = 1
+ jobs = 0
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
From c76155d73e0d731eafc30163f449cfdb54021e2e Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 21:28:39 +0100
Subject: [PATCH 0262/3455] Progress towards immutable target object
---
src/mock_vws/target.py | 36 +++++++++++++++++++++---------------
1 file changed, 21 insertions(+), 15 deletions(-)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 7bbedb56a..62300fc29 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -58,7 +58,7 @@ def _random_tracking_rating() -> int:
return random.randint(0, 5)
-@dataclass(unsafe_hash=True, eq=True)
+@dataclass(frozen=True, eq=True)
class Target: # pylint: disable=too-many-instance-attributes
"""
A Vuforia Target as managed in
@@ -174,38 +174,44 @@ def from_dict(cls, target_dict: TargetDict) -> Target:
"""
Load a target from a dictionary.
"""
+ timezone = ZoneInfo('GMT')
name = target_dict['name']
active_flag = target_dict['active_flag']
width = target_dict['width']
image_base64 = target_dict['image_base64']
- upload_date = target_dict['upload_date']
processed_tracking_rating = target_dict['processed_tracking_rating']
image_bytes = base64.b64decode(image_base64)
image = io.BytesIO(image_bytes)
processing_time_seconds = target_dict['processing_time_seconds']
application_metadata = target_dict['application_metadata']
+ target_id = target_dict['target_id']
+ delete_date_optional = target_dict['delete_date_optional']
+ if delete_date_optional is None:
+ delete_date = None
+ else:
+ delete_date = datetime.datetime.fromisoformat(delete_date_optional)
+ delete_date = delete_date.replace(tzinfo=timezone)
+
+ last_modified_date = datetime.datetime.fromisoformat(
+ target_dict['last_modified_date'],
+ ).replace(tzinfo=timezone)
+ upload_date = datetime.datetime.fromisoformat(
+ target_dict['upload_date'],
+ ).replace(tzinfo=timezone)
target = Target(
+ target_id=target_id,
name=name,
active_flag=active_flag,
width=width,
image=image,
processing_time_seconds=processing_time_seconds,
application_metadata=application_metadata,
+ delete_date=delete_date,
+ last_modified_date=last_modified_date,
+ upload_date=upload_date,
+ processed_tracking_rating=processed_tracking_rating,
)
- target.target_id = target_dict['target_id']
- timezone = ZoneInfo('GMT')
- target.last_modified_date = datetime.datetime.fromisoformat(
- target_dict['last_modified_date'],
- ).replace(tzinfo=timezone)
- target.upload_date = datetime.datetime.fromisoformat(upload_date)
- target.processed_tracking_rating = processed_tracking_rating
- target.upload_date = target.upload_date.replace(tzinfo=timezone)
- delete_date_optional = target_dict['delete_date_optional']
- if delete_date_optional:
- target.delete_date = datetime.datetime.fromisoformat(
- delete_date_optional,
- ).replace(tzinfo=timezone)
return target
def to_dict(self) -> TargetDict:
From 87445954886273f5f8a9a0631252dec85a787a44 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 21:48:49 +0100
Subject: [PATCH 0263/3455] Make Target class immutable and safely hashable
---
.../mock_web_services_api.py | 68 ++++++++++++++++---
src/mock_vws/target.py | 8 ---
2 files changed, 58 insertions(+), 18 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index a0d715d40..e89af6858 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -251,6 +251,15 @@ def delete_target(
https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Delete-a-Target
"""
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)
target = _get_target_from_request(
request_path=request.path,
databases=self.databases,
@@ -259,7 +268,26 @@ def delete_target(
if target.status == TargetStatuses.PROCESSING.value:
raise TargetStatusProcessing
- target.delete()
+ now = datetime.datetime.now(tz=target.upload_date.tzinfo)
+ new_target = Target(
+ active_flag=target.active_flag,
+ application_metadata=target.application_metadata,
+ image=target.image,
+ name=target.name,
+ processing_time_seconds=target.processing_time_seconds,
+ width=target.width,
+ current_month_recos=target.current_month_recos,
+ delete_date=now,
+ last_modified_date=target.last_modified_date,
+ previous_month_recos=target.previous_month_recos,
+ processed_tracking_rating=target.processed_tracking_rating,
+ reco_rating=target.reco_rating,
+ target_id=target.target_id,
+ total_recos=target.total_recos,
+ upload_date=target.upload_date,
+ )
+ database.targets.remove(target)
+ database.targets.add(new_target)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
'Connection': 'keep-alive',
@@ -495,41 +523,61 @@ def update_target(
if target.status != TargetStatuses.SUCCESS.value:
raise TargetStatusNotSuccess
+ width = target.width
if 'width' in request.json():
- target.width = request.json()['width']
+ width = request.json()['width']
+ active_flag = target.active_flag
if 'active_flag' in request.json():
active_flag = request.json()['active_flag']
if active_flag is None:
raise Fail(status_code=HTTPStatus.BAD_REQUEST)
- target.active_flag = active_flag
-
+ application_metadata = target.application_metadata
if 'application_metadata' in request.json():
application_metadata = request.json()['application_metadata']
if application_metadata is None:
raise Fail(status_code=HTTPStatus.BAD_REQUEST)
- target.application_metadata = application_metadata
+ name = target.name
if 'name' in request.json():
name = request.json()['name']
- target.name = name
+ image_file = target.image
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)
+ processed_tracking_rating = random.choice(available_values)
gmt = ZoneInfo('GMT')
- now = datetime.datetime.now(tz=gmt)
- target.last_modified_date = now
+ last_modified_date = datetime.datetime.now(tz=gmt)
+
+ new_target = Target(
+ active_flag=active_flag,
+ application_metadata=application_metadata,
+ image=image_file,
+ name=name,
+ processing_time_seconds=target.processing_time_seconds,
+ width=width,
+ current_month_recos=target.current_month_recos,
+ delete_date=target.delete_date,
+ last_modified_date=last_modified_date,
+ previous_month_recos=target.previous_month_recos,
+ processed_tracking_rating=processed_tracking_rating,
+ reco_rating=target.reco_rating,
+ target_id=target.target_id,
+ total_recos=target.total_recos,
+ upload_date=target.upload_date,
+ )
+
+ database.targets.remove(target)
+ database.targets.add(new_target)
body = {
'result_code': ResultCodes.SUCCESS.value,
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 62300fc29..24aee7d5d 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -85,14 +85,6 @@ class Target: # pylint: disable=too-many-instance-attributes
total_recos: int = 0
upload_date: datetime.datetime = field(default_factory=_time_now)
- def delete(self) -> None:
- """
- Mark the target as deleted.
- """
- timezone = self.upload_date.tzinfo
- now = datetime.datetime.now(tz=timezone)
- self.delete_date = now
-
@property
def _post_processing_status(self) -> TargetStatuses:
"""
From aa9bdeb1727c9dd6a78581ede6fd12621e744298 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 21:54:57 +0100
Subject: [PATCH 0264/3455] Handle immutable Target class
---
src/mock_vws/_flask_server/storage.py | 80 +++++++++++++++++++++------
1 file changed, 62 insertions(+), 18 deletions(-)
diff --git a/src/mock_vws/_flask_server/storage.py b/src/mock_vws/_flask_server/storage.py
index adb1f3d98..e84882a74 100644
--- a/src/mock_vws/_flask_server/storage.py
+++ b/src/mock_vws/_flask_server/storage.py
@@ -86,8 +86,8 @@ def create_target(database_name: str) -> Tuple[str, int]:
active_flag=request.json['active_flag'],
processing_time_seconds=request.json['processing_time_seconds'],
application_metadata=request.json['application_metadata'],
+ target_id=request.json['target_id'],
)
- target.target_id = request.json['target_id']
database.targets.add(target)
return jsonify(target.to_dict()), HTTPStatus.CREATED
@@ -109,8 +109,27 @@ def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
[target] = [
target for target in database.targets if target.target_id == target_id
]
- target.delete()
- return jsonify(target.to_dict()), HTTPStatus.OK
+ now = datetime.datetime.now(tz=target.upload_date.tzinfo)
+ new_target = Target(
+ active_flag=target.active_flag,
+ application_metadata=target.application_metadata,
+ image=target.image,
+ name=target.name,
+ processing_time_seconds=target.processing_time_seconds,
+ width=target.width,
+ current_month_recos=target.current_month_recos,
+ delete_date=now,
+ last_modified_date=target.last_modified_date,
+ previous_month_recos=target.previous_month_recos,
+ processed_tracking_rating=target.processed_tracking_rating,
+ reco_rating=target.reco_rating,
+ target_id=target.target_id,
+ total_recos=target.total_recos,
+ upload_date=target.upload_date,
+ )
+ database.targets.remove(target)
+ database.targets.add(new_target)
+ return jsonify(new_target.to_dict()), HTTPStatus.OK
@STORAGE_FLASK_APP.route(
@@ -130,34 +149,59 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
target for target in database.targets if target.target_id == target_id
]
- if 'name' in request.json:
- target.name = request.json['name']
+ width = target.width
+ if 'width' in request.json():
+ width = request.json()['width']
- if 'active_flag' in request.json:
- target.active_flag = bool(request.json['active_flag'])
+ active_flag = target.active_flag
+ if 'active_flag' in request.json():
+ active_flag = request.json()['active_flag']
- if 'width' in request.json:
- target.width = float(request.json['width'])
+ application_metadata = target.application_metadata
+ if 'application_metadata' in request.json():
+ application_metadata = request.json()['application_metadata']
- if 'application_metadata' in request.json:
- target.application_metadata = request.json['application_metadata']
+ name = target.name
+ if 'name' in request.json():
+ name = request.json()['name']
- if 'image' in request.json:
- decoded = base64.b64decode(request.json['image'])
+ image_file = target.image
+ 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)
+ processed_tracking_rating = random.choice(available_values)
gmt = ZoneInfo('GMT')
- now = datetime.datetime.now(tz=gmt)
- target.last_modified_date = now
+ last_modified_date = datetime.datetime.now(tz=gmt)
+
+ new_target = Target(
+ active_flag=active_flag,
+ application_metadata=application_metadata,
+ image=image_file,
+ name=name,
+ processing_time_seconds=target.processing_time_seconds,
+ width=width,
+ current_month_recos=target.current_month_recos,
+ delete_date=target.delete_date,
+ last_modified_date=last_modified_date,
+ previous_month_recos=target.previous_month_recos,
+ processed_tracking_rating=processed_tracking_rating,
+ reco_rating=target.reco_rating,
+ target_id=target.target_id,
+ total_recos=target.total_recos,
+ upload_date=target.upload_date,
+ )
+
+ database.targets.remove(target)
+ database.targets.add(new_target)
- return jsonify(target.to_dict()), HTTPStatus.OK
+ return jsonify(new_target.to_dict()), HTTPStatus.OK
if __name__ == '__main__': # pragma: no cover
STORAGE_FLASK_APP.run(debug=True, host='0.0.0.0')
From 400df9c9041cd66a4f94cc2dfc6ed72d75150e1b Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sun, 20 Sep 2020 22:20:57 +0100
Subject: [PATCH 0265/3455] Fix incorrect calling of json function in storage
backend
---
src/mock_vws/_flask_server/storage.py | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/mock_vws/_flask_server/storage.py b/src/mock_vws/_flask_server/storage.py
index e84882a74..d57433ca9 100644
--- a/src/mock_vws/_flask_server/storage.py
+++ b/src/mock_vws/_flask_server/storage.py
@@ -150,24 +150,24 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
]
width = target.width
- if 'width' in request.json():
- width = request.json()['width']
+ if 'width' in request.json:
+ width = request.json['width']
active_flag = target.active_flag
- if 'active_flag' in request.json():
- active_flag = request.json()['active_flag']
+ if 'active_flag' in request.json:
+ active_flag = request.json['active_flag']
application_metadata = target.application_metadata
- if 'application_metadata' in request.json():
- application_metadata = request.json()['application_metadata']
+ if 'application_metadata' in request.json:
+ application_metadata = request.json['application_metadata']
name = target.name
- if 'name' in request.json():
- name = request.json()['name']
+ if 'name' in request.json:
+ name = request.json['name']
image_file = target.image
- if 'image' in request.json():
- image = request.json()['image']
+ if 'image' in request.json:
+ image = request.json['image']
decoded = base64.b64decode(image)
image_file = io.BytesIO(decoded)
From ea73770b18a9ba4c154253ebcfe883a9c7e51aba Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
<27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Mon, 21 Sep 2020 06:41:00 +0000
Subject: [PATCH 0266/3455] Bump isort from 5.5.2 to 5.5.3
Bumps [isort](https://github.com/pycqa/isort) from 5.5.2 to 5.5.3.
- [Release notes](https://github.com/pycqa/isort/releases)
- [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/pycqa/isort/compare/5.5.2...5.5.3)
Signed-off-by: dependabot-preview[bot]
---
dev-requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index c29a5a383..851cd9e84 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -12,7 +12,7 @@ flake8-commas==2.0.0 # Require silicon valley commas
flake8-quotes==3.2.0 # Require single quotes
flake8==3.8.3 # Lint
freezegun==1.0.0 # Freeze time in tests
-isort==5.5.2 # Lint imports
+isort==5.5.3 # Lint imports
keyring==21.4.0
mypy==0.782 # Type checking
pip_check_reqs==2.1.1
From f00d67c4433f66052ef157bb3e4d219d10b377ac Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 10:53:07 +0100
Subject: [PATCH 0267/3455] Progress towards frozen VuforiaDatabase
---
.../mock_web_services_api.py | 32 +++++++------------
src/mock_vws/database.py | 14 ++++----
2 files changed, 20 insertions(+), 26 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index e89af6858..a4de16662 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -7,6 +7,7 @@
import base64
import datetime
+import dataclass
import email.utils
import io
import itertools
@@ -218,7 +219,10 @@ def add_target(
processing_time_seconds=self._processing_time_seconds,
application_metadata=application_metadata,
)
- database.targets.add(new_target)
+ database_targets = frozenset(set(database.targets).union({new_target}))
+ new_database = dataclass.replace(database, targets=database_targets)
+ self.databases.remove(database)
+ self.databases.add(new_database)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
@@ -269,25 +273,13 @@ def delete_target(
raise TargetStatusProcessing
now = datetime.datetime.now(tz=target.upload_date.tzinfo)
- new_target = Target(
- active_flag=target.active_flag,
- application_metadata=target.application_metadata,
- image=target.image,
- name=target.name,
- processing_time_seconds=target.processing_time_seconds,
- width=target.width,
- current_month_recos=target.current_month_recos,
- delete_date=now,
- last_modified_date=target.last_modified_date,
- previous_month_recos=target.previous_month_recos,
- processed_tracking_rating=target.processed_tracking_rating,
- reco_rating=target.reco_rating,
- target_id=target.target_id,
- total_recos=target.total_recos,
- upload_date=target.upload_date,
- )
- database.targets.remove(target)
- database.targets.add(new_target)
+ new_target = dataclass.replace(target, delete_date=now)
+ new_database_targets = set(database.targets)
+ new_database_targets.remove()
+ database_targets = frozenset(set(database.targets).union({new_target}))
+ new_database = dataclass.replace(database, targets=database_targets)
+ self.databases.remove(database)
+ self.databases.add(new_database)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
'Connection': 'keep-alive',
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index e9d2b9c53..fd1908fb4 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -6,7 +6,7 @@
import uuid
from dataclasses import dataclass, field
-from typing import List, Set, TypedDict
+from typing import List, Set, TypedDict, FrozenSet
from mock_vws._constants import TargetStatuses
from mock_vws.states import States
@@ -47,7 +47,7 @@ class VuforiaDatabase:
server_secret_key: str = field(default_factory=_random_hex, repr=False)
client_access_key: str = field(default_factory=_random_hex, repr=False)
client_secret_key: str = field(default_factory=_random_hex, repr=False)
- targets: Set[Target] = field(default_factory=set, hash=False)
+ targets: FrozenSet[Target] = field(default_factory=frozenset)
state: States = States.WORKING
request_quota = 100000
@@ -77,6 +77,11 @@ def from_dict(cls, database_dict: DatabaseDict) -> VuforiaDatabase:
"""
Load a database from a dictionary.
"""
+ targets = set()
+ for target_dict in database_dict['targets']:
+ target = Target.from_dict(target_dict=target_dict)
+ targets.add(target)
+
database = cls(
database_name=database_dict['database_name'],
server_access_key=database_dict['server_access_key'],
@@ -84,12 +89,9 @@ def from_dict(cls, database_dict: DatabaseDict) -> VuforiaDatabase:
client_access_key=database_dict['client_access_key'],
client_secret_key=database_dict['client_secret_key'],
state=States[database_dict['state_name']],
+ targets=frozenset(targets),
)
- for target_dict in database_dict['targets']:
- target = Target.from_dict(target_dict=target_dict)
- database.targets.add(target)
-
return database
@property
From 8abfa43e04adf39ebbaef2aece9f9cd5140477c3 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 11:03:03 +0100
Subject: [PATCH 0268/3455] Take advantage of the dataclasses.replace feature
---
.../mock_web_services_api.py | 56 ++++++-------------
1 file changed, 18 insertions(+), 38 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index e89af6858..f0665b9fd 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -6,6 +6,7 @@
"""
import base64
+import dataclasses
import datetime
import email.utils
import io
@@ -269,23 +270,7 @@ def delete_target(
raise TargetStatusProcessing
now = datetime.datetime.now(tz=target.upload_date.tzinfo)
- new_target = Target(
- active_flag=target.active_flag,
- application_metadata=target.application_metadata,
- image=target.image,
- name=target.name,
- processing_time_seconds=target.processing_time_seconds,
- width=target.width,
- current_month_recos=target.current_month_recos,
- delete_date=now,
- last_modified_date=target.last_modified_date,
- previous_month_recos=target.previous_month_recos,
- processed_tracking_rating=target.processed_tracking_rating,
- reco_rating=target.reco_rating,
- target_id=target.target_id,
- total_recos=target.total_recos,
- upload_date=target.upload_date,
- )
+ new_target = dataclasses.replace(target, delete_date=now)
database.targets.remove(target)
database.targets.add(new_target)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
@@ -523,57 +508,52 @@ def update_target(
if target.status != TargetStatuses.SUCCESS.value:
raise TargetStatusNotSuccess
- width = target.width
+ new_target = target
+
if 'width' in request.json():
width = request.json()['width']
+ dataclasses.replace(new_target, width=width)
- active_flag = target.active_flag
if 'active_flag' in request.json():
active_flag = request.json()['active_flag']
if active_flag is None:
raise Fail(status_code=HTTPStatus.BAD_REQUEST)
+ dataclasses.replace(new_target, active_flag=active_flag)
- application_metadata = target.application_metadata
if 'application_metadata' in request.json():
application_metadata = request.json()['application_metadata']
if application_metadata is None:
raise Fail(status_code=HTTPStatus.BAD_REQUEST)
+ dataclasses.replace(
+ new_target,
+ application_metadata=application_metadata,
+ )
- name = target.name
if 'name' in request.json():
name = request.json()['name']
+ dataclasses.replace(new_target, name=name)
- image_file = target.image
if 'image' in request.json():
image = request.json()['image']
decoded = base64.b64decode(image)
image_file = io.BytesIO(decoded)
+ dataclasses.replace(new_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]))
processed_tracking_rating = random.choice(available_values)
+ dataclasses.replace(
+ new_target,
+ processed_tracking_rating=processed_tracking_rating,
+ )
gmt = ZoneInfo('GMT')
last_modified_date = datetime.datetime.now(tz=gmt)
-
- new_target = Target(
- active_flag=active_flag,
- application_metadata=application_metadata,
- image=image_file,
- name=name,
- processing_time_seconds=target.processing_time_seconds,
- width=width,
- current_month_recos=target.current_month_recos,
- delete_date=target.delete_date,
+ dataclasses.replace(
+ new_target,
last_modified_date=last_modified_date,
- previous_month_recos=target.previous_month_recos,
- processed_tracking_rating=processed_tracking_rating,
- reco_rating=target.reco_rating,
- target_id=target.target_id,
- total_recos=target.total_recos,
- upload_date=target.upload_date,
)
database.targets.remove(target)
From bdcdb49b6614409ef9fde9f408d709f6245fb3d9 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 11:37:00 +0100
Subject: [PATCH 0269/3455] Revert "Take advantage of the dataclasses.replace
feature"
This reverts commit 8abfa43e04adf39ebbaef2aece9f9cd5140477c3.
---
.../mock_web_services_api.py | 56 +++++++++++++------
1 file changed, 38 insertions(+), 18 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index f0665b9fd..e89af6858 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -6,7 +6,6 @@
"""
import base64
-import dataclasses
import datetime
import email.utils
import io
@@ -270,7 +269,23 @@ def delete_target(
raise TargetStatusProcessing
now = datetime.datetime.now(tz=target.upload_date.tzinfo)
- new_target = dataclasses.replace(target, delete_date=now)
+ new_target = Target(
+ active_flag=target.active_flag,
+ application_metadata=target.application_metadata,
+ image=target.image,
+ name=target.name,
+ processing_time_seconds=target.processing_time_seconds,
+ width=target.width,
+ current_month_recos=target.current_month_recos,
+ delete_date=now,
+ last_modified_date=target.last_modified_date,
+ previous_month_recos=target.previous_month_recos,
+ processed_tracking_rating=target.processed_tracking_rating,
+ reco_rating=target.reco_rating,
+ target_id=target.target_id,
+ total_recos=target.total_recos,
+ upload_date=target.upload_date,
+ )
database.targets.remove(target)
database.targets.add(new_target)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
@@ -508,52 +523,57 @@ def update_target(
if target.status != TargetStatuses.SUCCESS.value:
raise TargetStatusNotSuccess
- new_target = target
-
+ width = target.width
if 'width' in request.json():
width = request.json()['width']
- dataclasses.replace(new_target, width=width)
+ active_flag = target.active_flag
if 'active_flag' in request.json():
active_flag = request.json()['active_flag']
if active_flag is None:
raise Fail(status_code=HTTPStatus.BAD_REQUEST)
- dataclasses.replace(new_target, active_flag=active_flag)
+ application_metadata = target.application_metadata
if 'application_metadata' in request.json():
application_metadata = request.json()['application_metadata']
if application_metadata is None:
raise Fail(status_code=HTTPStatus.BAD_REQUEST)
- dataclasses.replace(
- new_target,
- application_metadata=application_metadata,
- )
+ name = target.name
if 'name' in request.json():
name = request.json()['name']
- dataclasses.replace(new_target, name=name)
+ image_file = target.image
if 'image' in request.json():
image = request.json()['image']
decoded = base64.b64decode(image)
image_file = io.BytesIO(decoded)
- dataclasses.replace(new_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]))
processed_tracking_rating = random.choice(available_values)
- dataclasses.replace(
- new_target,
- processed_tracking_rating=processed_tracking_rating,
- )
gmt = ZoneInfo('GMT')
last_modified_date = datetime.datetime.now(tz=gmt)
- dataclasses.replace(
- new_target,
+
+ new_target = Target(
+ active_flag=active_flag,
+ application_metadata=application_metadata,
+ image=image_file,
+ name=name,
+ processing_time_seconds=target.processing_time_seconds,
+ width=width,
+ current_month_recos=target.current_month_recos,
+ delete_date=target.delete_date,
last_modified_date=last_modified_date,
+ previous_month_recos=target.previous_month_recos,
+ processed_tracking_rating=processed_tracking_rating,
+ reco_rating=target.reco_rating,
+ target_id=target.target_id,
+ total_recos=target.total_recos,
+ upload_date=target.upload_date,
)
database.targets.remove(target)
From f4f27d6c3282de4e5b730db55c2c3914b18a84a4 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 11:44:56 +0100
Subject: [PATCH 0270/3455] Progress towards simplifying update_target
---
.../mock_web_services_api.py | 35 +++++++++----------
src/mock_vws/target.py | 2 +-
2 files changed, 17 insertions(+), 20 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index e89af6858..4ea548423 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -523,25 +523,22 @@ def update_target(
if target.status != TargetStatuses.SUCCESS.value:
raise TargetStatusNotSuccess
- width = target.width
- if 'width' in request.json():
- width = request.json()['width']
-
- active_flag = target.active_flag
- if 'active_flag' in request.json():
- active_flag = request.json()['active_flag']
- if active_flag is None:
- raise Fail(status_code=HTTPStatus.BAD_REQUEST)
-
- application_metadata = target.application_metadata
- if 'application_metadata' in request.json():
- application_metadata = request.json()['application_metadata']
- if application_metadata is None:
- raise Fail(status_code=HTTPStatus.BAD_REQUEST)
-
- name = target.name
- if 'name' in request.json():
- name = request.json()['name']
+ if 'active_flag' in request.json() and active_flag is None:
+ raise Fail(status_code=HTTPStatus.BAD_REQUEST)
+
+ if (
+ 'application_metadata' in request.json()
+ and application_metadata is None
+ ):
+ raise Fail(status_code=HTTPStatus.BAD_REQUEST)
+
+ width = request.json().get('width', target.width)
+ name = request.json().get('name', target.name)
+ active_flag = request.json().get('active_flag', target.active_flag)
+ application_metadata = request.json().get(
+ 'application_metadata',
+ target.application_metadata,
+ )
image_file = target.image
if 'image' in request.json():
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 24aee7d5d..40120ade1 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -66,7 +66,7 @@ class Target: # pylint: disable=too-many-instance-attributes
"""
active_flag: bool
- application_metadata: str
+ application_metadata: Optional[str]
# Comparison of io.BytesIO compares the object, not the file contents.
# data we care about, so we leave this.
image: io.BytesIO = field(compare=False)
From 1eeb286845d6eb9d0943219c5ea8480f9cde6819 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 11:52:44 +0100
Subject: [PATCH 0271/3455] Re-do dataclass.replace work
---
.../mock_web_services_api.py | 54 ++++++-------------
src/mock_vws/target.py | 2 +-
2 files changed, 17 insertions(+), 39 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 4ea548423..2160c3893 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -6,6 +6,7 @@
"""
import base64
+import dataclasses
import datetime
import email.utils
import io
@@ -269,23 +270,7 @@ def delete_target(
raise TargetStatusProcessing
now = datetime.datetime.now(tz=target.upload_date.tzinfo)
- new_target = Target(
- active_flag=target.active_flag,
- application_metadata=target.application_metadata,
- image=target.image,
- name=target.name,
- processing_time_seconds=target.processing_time_seconds,
- width=target.width,
- current_month_recos=target.current_month_recos,
- delete_date=now,
- last_modified_date=target.last_modified_date,
- previous_month_recos=target.previous_month_recos,
- processed_tracking_rating=target.processed_tracking_rating,
- reco_rating=target.reco_rating,
- target_id=target.target_id,
- total_recos=target.total_recos,
- upload_date=target.upload_date,
- )
+ new_target = dataclasses.replace(target, delete_date=now)
database.targets.remove(target)
database.targets.add(new_target)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
@@ -523,15 +508,6 @@ def update_target(
if target.status != TargetStatuses.SUCCESS.value:
raise TargetStatusNotSuccess
- if 'active_flag' in request.json() and active_flag is None:
- raise Fail(status_code=HTTPStatus.BAD_REQUEST)
-
- if (
- 'application_metadata' in request.json()
- and application_metadata is None
- ):
- raise Fail(status_code=HTTPStatus.BAD_REQUEST)
-
width = request.json().get('width', target.width)
name = request.json().get('name', target.name)
active_flag = request.json().get('active_flag', target.active_flag)
@@ -546,6 +522,15 @@ def update_target(
decoded = base64.b64decode(image)
image_file = io.BytesIO(decoded)
+ if 'active_flag' in request.json() and active_flag is None:
+ raise Fail(status_code=HTTPStatus.BAD_REQUEST)
+
+ if (
+ 'application_metadata' in request.json()
+ and application_metadata is None
+ ):
+ raise Fail(status_code=HTTPStatus.BAD_REQUEST)
+
# 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.
@@ -555,22 +540,15 @@ def update_target(
gmt = ZoneInfo('GMT')
last_modified_date = datetime.datetime.now(tz=gmt)
- new_target = Target(
+ new_target = dataclasses.replace(
+ target,
+ name=name,
+ width=width,
active_flag=active_flag,
application_metadata=application_metadata,
image=image_file,
- name=name,
- processing_time_seconds=target.processing_time_seconds,
- width=width,
- current_month_recos=target.current_month_recos,
- delete_date=target.delete_date,
- last_modified_date=last_modified_date,
- previous_month_recos=target.previous_month_recos,
processed_tracking_rating=processed_tracking_rating,
- reco_rating=target.reco_rating,
- target_id=target.target_id,
- total_recos=target.total_recos,
- upload_date=target.upload_date,
+ last_modified_date=last_modified_date,
)
database.targets.remove(target)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index 40120ade1..c9db3ba0d 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -29,7 +29,7 @@ class TargetDict(TypedDict):
active_flag: bool
processing_time_seconds: Union[int, float]
processed_tracking_rating: int
- application_metadata: str
+ application_metadata: Optional[str]
target_id: str
last_modified_date: str
delete_date_optional: Optional[str]
From 382d45cfdc53531d2eba7c4a1de2d3c1ff2facf7 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 12:00:22 +0100
Subject: [PATCH 0272/3455] Progress towards using dataclasses.replace
---
src/mock_vws/_flask_server/storage.py | 58 +++++++--------------------
1 file changed, 14 insertions(+), 44 deletions(-)
diff --git a/src/mock_vws/_flask_server/storage.py b/src/mock_vws/_flask_server/storage.py
index d57433ca9..de47a202c 100644
--- a/src/mock_vws/_flask_server/storage.py
+++ b/src/mock_vws/_flask_server/storage.py
@@ -3,6 +3,7 @@
"""
import base64
+import dataclasses
import datetime
import io
import random
@@ -110,23 +111,7 @@ def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
target for target in database.targets if target.target_id == target_id
]
now = datetime.datetime.now(tz=target.upload_date.tzinfo)
- new_target = Target(
- active_flag=target.active_flag,
- application_metadata=target.application_metadata,
- image=target.image,
- name=target.name,
- processing_time_seconds=target.processing_time_seconds,
- width=target.width,
- current_month_recos=target.current_month_recos,
- delete_date=now,
- last_modified_date=target.last_modified_date,
- previous_month_recos=target.previous_month_recos,
- processed_tracking_rating=target.processed_tracking_rating,
- reco_rating=target.reco_rating,
- target_id=target.target_id,
- total_recos=target.total_recos,
- upload_date=target.upload_date,
- )
+ new_target = dataclasses.replace(target, delete_date=now)
database.targets.remove(target)
database.targets.add(new_target)
return jsonify(new_target.to_dict()), HTTPStatus.OK
@@ -149,21 +134,13 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
target for target in database.targets if target.target_id == target_id
]
- width = target.width
- if 'width' in request.json:
- width = request.json['width']
-
- active_flag = target.active_flag
- if 'active_flag' in request.json:
- active_flag = request.json['active_flag']
-
- application_metadata = target.application_metadata
- if 'application_metadata' in request.json:
- application_metadata = request.json['application_metadata']
-
- name = target.name
- if 'name' in request.json:
- name = request.json['name']
+ width = request.json.get('width', target.width)
+ name = request.json.get('name', target.name)
+ active_flag = request.json.get('active_flag', target.active_flag)
+ application_metadata = request.json.get(
+ 'application_metadata',
+ target.application_metadata,
+ )
image_file = target.image
if 'image' in request.json:
@@ -180,22 +157,15 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
gmt = ZoneInfo('GMT')
last_modified_date = datetime.datetime.now(tz=gmt)
- new_target = Target(
+ new_target = dataclasses.replace(
+ target,
+ name=name,
+ width=width,
active_flag=active_flag,
application_metadata=application_metadata,
image=image_file,
- name=name,
- processing_time_seconds=target.processing_time_seconds,
- width=width,
- current_month_recos=target.current_month_recos,
- delete_date=target.delete_date,
- last_modified_date=last_modified_date,
- previous_month_recos=target.previous_month_recos,
processed_tracking_rating=processed_tracking_rating,
- reco_rating=target.reco_rating,
- target_id=target.target_id,
- total_recos=target.total_recos,
- upload_date=target.upload_date,
+ last_modified_date=last_modified_date,
)
database.targets.remove(target)
From 2cd42e67f7247435ab69928c755489e08cfe8989 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 13:07:29 +0100
Subject: [PATCH 0273/3455] Revert "Progress towards frozen VuforiaDatabase"
This reverts commit f00d67c4433f66052ef157bb3e4d219d10b377ac.
---
src/mock_vws/database.py | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index fd1908fb4..e9d2b9c53 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -6,7 +6,7 @@
import uuid
from dataclasses import dataclass, field
-from typing import List, Set, TypedDict, FrozenSet
+from typing import List, Set, TypedDict
from mock_vws._constants import TargetStatuses
from mock_vws.states import States
@@ -47,7 +47,7 @@ class VuforiaDatabase:
server_secret_key: str = field(default_factory=_random_hex, repr=False)
client_access_key: str = field(default_factory=_random_hex, repr=False)
client_secret_key: str = field(default_factory=_random_hex, repr=False)
- targets: FrozenSet[Target] = field(default_factory=frozenset)
+ targets: Set[Target] = field(default_factory=set, hash=False)
state: States = States.WORKING
request_quota = 100000
@@ -77,11 +77,6 @@ def from_dict(cls, database_dict: DatabaseDict) -> VuforiaDatabase:
"""
Load a database from a dictionary.
"""
- targets = set()
- for target_dict in database_dict['targets']:
- target = Target.from_dict(target_dict=target_dict)
- targets.add(target)
-
database = cls(
database_name=database_dict['database_name'],
server_access_key=database_dict['server_access_key'],
@@ -89,9 +84,12 @@ def from_dict(cls, database_dict: DatabaseDict) -> VuforiaDatabase:
client_access_key=database_dict['client_access_key'],
client_secret_key=database_dict['client_secret_key'],
state=States[database_dict['state_name']],
- targets=frozenset(targets),
)
+ for target_dict in database_dict['targets']:
+ target = Target.from_dict(target_dict=target_dict)
+ database.targets.add(target)
+
return database
@property
From 7c996a7f98f0247ca058802bc7e977d3caac0f19 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 13:47:46 +0100
Subject: [PATCH 0274/3455] Simpler creation of db from dict
---
src/mock_vws/database.py | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index e9d2b9c53..6435322f5 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -77,21 +77,19 @@ def from_dict(cls, database_dict: DatabaseDict) -> VuforiaDatabase:
"""
Load a database from a dictionary.
"""
- database = cls(
+ return cls(
database_name=database_dict['database_name'],
server_access_key=database_dict['server_access_key'],
server_secret_key=database_dict['server_secret_key'],
client_access_key=database_dict['client_access_key'],
client_secret_key=database_dict['client_secret_key'],
state=States[database_dict['state_name']],
+ targets=set(
+ Target.from_dict(target_dict=target_dict)
+ for target_dict in database_dict['targets']
+ ),
)
- for target_dict in database_dict['targets']:
- target = Target.from_dict(target_dict=target_dict)
- database.targets.add(target)
-
- return database
-
@property
def not_deleted_targets(self) -> Set[Target]:
"""
From 6a10dee7f3d7fc429e8757042d315c8025eeea0c Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 13:58:14 +0100
Subject: [PATCH 0275/3455] Simplify task of getting target from request
---
.../mock_web_services_api.py | 57 ++++++++++---------
1 file changed, 31 insertions(+), 26 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index 2160c3893..c8fbecb63 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -10,7 +10,6 @@
import datetime
import email.utils
import io
-import itertools
import random
import uuid
from http import HTTPStatus
@@ -127,21 +126,16 @@ def decorator(method: Callable[..., str]) -> Callable[..., str]:
def _get_target_from_request(
request_path: str,
- databases: Set[VuforiaDatabase],
+ database: VuforiaDatabase,
) -> Target:
"""
- Given a request path with a target ID in the path, and a list of databases,
- return the target with that ID from those databases.
+ Given a request path with a target ID in the path, return the target with
+ that ID from the given database.
"""
split_path = request_path.split('/')
target_id = split_path[-1]
- all_database_targets = itertools.chain.from_iterable(
- [database.targets for database in databases],
- )
[target] = [
- target
- for target in all_database_targets
- if target.target_id == target_id
+ target for target in database.targets if target.target_id == target_id
]
return target
@@ -263,7 +257,7 @@ def delete_target(
assert isinstance(database, VuforiaDatabase)
target = _get_target_from_request(
request_path=request.path,
- databases=self.databases,
+ database=database,
)
if target.status == TargetStatuses.PROCESSING.value:
@@ -384,10 +378,18 @@ def get_target(
Fake implementation of
https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Retrieve-a-Target-Record
"""
- target = _get_target_from_request(
+ 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)
+ target = _get_target_from_request(
+ request_path=request.path,
+ database=database,
+ )
target_record = {
'target_id': target.target_id,
@@ -428,10 +430,6 @@ def get_duplicates(
Fake implementation of
https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Check-for-Duplicate-Targets
"""
- target = _get_target_from_request(
- request_path=request.path,
- databases=self.databases,
- )
database = get_database_matching_server_keys(
request_headers=request.headers,
request_body=request.body,
@@ -439,8 +437,12 @@ def get_duplicates(
request_path=request.path,
databases=self.databases,
)
-
assert isinstance(database, VuforiaDatabase)
+ target = _get_target_from_request(
+ request_path=request.path,
+ database=database,
+ )
+
other_targets = set(database.targets) - set([target])
similar_targets: List[str] = [
@@ -483,11 +485,6 @@ def update_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,
@@ -497,6 +494,13 @@ def update_target(
)
assert isinstance(database, VuforiaDatabase)
+
+ target = _get_target_from_request(
+ request_path=request.path,
+ database=database,
+ )
+ body: Dict[str, str] = {}
+
date = email.utils.formatdate(None, localtime=False, usegmt=True)
context.headers = {
'Connection': 'keep-alive',
@@ -572,10 +576,6 @@ def target_summary(
Fake implementation of
https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Retrieve-a-Target-Summary-Report
"""
- target = _get_target_from_request(
- request_path=request.path,
- databases=self.databases,
- )
database = get_database_matching_server_keys(
request_headers=request.headers,
request_body=request.body,
@@ -583,6 +583,11 @@ def target_summary(
request_path=request.path,
databases=self.databases,
)
+ assert isinstance(database, VuforiaDatabase)
+ target = _get_target_from_request(
+ request_path=request.path,
+ database=database,
+ )
assert isinstance(database, VuforiaDatabase)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
From a7d8555222b1671dba4f54c9b15a930fbcba76e8 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 15:33:00 +0100
Subject: [PATCH 0276/3455] Move get_target method to VuforiaDatabase
---
.../mock_web_services_api.py | 46 ++++---------------
src/mock_vws/database.py | 9 ++++
2 files changed, 19 insertions(+), 36 deletions(-)
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index c8fbecb63..cee97c50c 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -124,22 +124,6 @@ def decorator(method: Callable[..., str]) -> Callable[..., str]:
return decorator
-def _get_target_from_request(
- request_path: str,
- database: VuforiaDatabase,
-) -> Target:
- """
- Given a request path with a target ID in the path, return the target with
- that ID from the given database.
- """
- split_path = request_path.split('/')
- target_id = split_path[-1]
- [target] = [
- target for target in database.targets if target.target_id == target_id
- ]
- return target
-
-
class MockVuforiaWebServicesAPI:
"""
A fake implementation of the Vuforia Web Services API.
@@ -255,10 +239,8 @@ def delete_target(
)
assert isinstance(database, VuforiaDatabase)
- target = _get_target_from_request(
- request_path=request.path,
- database=database,
- )
+ target_id = request.path.split('/')[-1]
+ target = database.get_target(target_id=target_id)
if target.status == TargetStatuses.PROCESSING.value:
raise TargetStatusProcessing
@@ -386,10 +368,8 @@ def get_target(
databases=self.databases,
)
assert isinstance(database, VuforiaDatabase)
- target = _get_target_from_request(
- request_path=request.path,
- database=database,
- )
+ target_id = request.path.split('/')[-1]
+ target = database.get_target(target_id=target_id)
target_record = {
'target_id': target.target_id,
@@ -438,10 +418,8 @@ def get_duplicates(
databases=self.databases,
)
assert isinstance(database, VuforiaDatabase)
- target = _get_target_from_request(
- request_path=request.path,
- database=database,
- )
+ target_id = request.path.split('/')[-1]
+ target = database.get_target(target_id=target_id)
other_targets = set(database.targets) - set([target])
@@ -495,10 +473,8 @@ def update_target(
assert isinstance(database, VuforiaDatabase)
- target = _get_target_from_request(
- request_path=request.path,
- database=database,
- )
+ target_id = request.path.split('/')[-1]
+ target = database.get_target(target_id=target_id)
body: Dict[str, str] = {}
date = email.utils.formatdate(None, localtime=False, usegmt=True)
@@ -584,10 +560,8 @@ def target_summary(
databases=self.databases,
)
assert isinstance(database, VuforiaDatabase)
- target = _get_target_from_request(
- request_path=request.path,
- database=database,
- )
+ target_id = request.path.split('/')[-1]
+ target = database.get_target(target_id=target_id)
assert isinstance(database, VuforiaDatabase)
date = email.utils.formatdate(None, localtime=False, usegmt=True)
diff --git a/src/mock_vws/database.py b/src/mock_vws/database.py
index 6435322f5..6e5f0ef4d 100644
--- a/src/mock_vws/database.py
+++ b/src/mock_vws/database.py
@@ -72,6 +72,15 @@ def to_dict(self) -> DatabaseDict:
'targets': targets,
}
+ def get_target(self, target_id: str) -> Target:
+ """
+ Return a target from the database with the given ID.
+ """
+ [target] = [
+ target for target in self.targets if target.target_id == target_id
+ ]
+ return target
+
@classmethod
def from_dict(cls, database_dict: DatabaseDict) -> VuforiaDatabase:
"""
From 03c725aab4d3f42d72ccb190a4b8c281dc9def84 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 15:38:40 +0100
Subject: [PATCH 0277/3455] Use new database helper
---
src/mock_vws/_flask_server/storage.py | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/src/mock_vws/_flask_server/storage.py b/src/mock_vws/_flask_server/storage.py
index de47a202c..fac5d8e6f 100644
--- a/src/mock_vws/_flask_server/storage.py
+++ b/src/mock_vws/_flask_server/storage.py
@@ -107,9 +107,7 @@ def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
for database in VUFORIA_DATABASES
if database.database_name == database_name
]
- [target] = [
- target for target in database.targets if target.target_id == target_id
- ]
+ target = database.get_target(target_id=target_id)
now = datetime.datetime.now(tz=target.upload_date.tzinfo)
new_target = dataclasses.replace(target, delete_date=now)
database.targets.remove(target)
@@ -130,9 +128,7 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
for database in VUFORIA_DATABASES
if database.database_name == database_name
]
- [target] = [
- target for target in database.targets if target.target_id == target_id
- ]
+ target = database.get_target(target_id=target_id)
width = request.json.get('width', target.width)
name = request.json.get('name', target.name)
From 7183ccfb8e5d41516ec4ecae59728b5982bf7be6 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 15:41:21 +0100
Subject: [PATCH 0278/3455] Create secrets file before running custom linters
---
lint.mk | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lint.mk b/lint.mk
index 10f47f7cc..449f60e82 100644
--- a/lint.mk
+++ b/lint.mk
@@ -4,6 +4,8 @@ SHELL := /bin/bash -euxo pipefail
.PHONY: custom-linters
custom-linters:
+ # Running pytest needs this file
+ touch vuforia_secrets.env
pytest ci/custom_linters.py
.PHONY: black
From 6dbf5587bbcdc366b31a920c9b4b8e5846726ce8 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 15:44:56 +0100
Subject: [PATCH 0279/3455] Add a module docstring
---
src/mock_vws/_flask_server/vws.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py
index 90de7a524..682b74538 100644
--- a/src/mock_vws/_flask_server/vws.py
+++ b/src/mock_vws/_flask_server/vws.py
@@ -1,5 +1,8 @@
"""
-TODO
+A fake implementation of the Vuforia Web Services API.
+
+See
+https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API
"""
import base64
From 446124aa2146f94e3cae05a56b8ab1e778663d4b Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 15:46:35 +0100
Subject: [PATCH 0280/3455] Start of running tests for Docker
---
tests/mock_vws/test_docker.py | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 tests/mock_vws/test_docker.py
diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py
new file mode 100644
index 000000000..0ed18695f
--- /dev/null
+++ b/tests/mock_vws/test_docker.py
@@ -0,0 +1,3 @@
+"""
+Tests for running the mock server in Docker.
+"""
From 226a31d4262fade5b473334d10165c8f5a731edd Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 15:48:59 +0100
Subject: [PATCH 0281/3455] Add a Flask-based backend with no user interface
yet which passes the mock tests
---
dev-requirements.txt | 1 +
requirements.txt | 1 +
src/mock_vws/_flask_server/__init__.py | 0
src/mock_vws/_flask_server/storage.py | 173 ++++++
src/mock_vws/_flask_server/vwq.py | 163 +++++
src/mock_vws/_flask_server/vws.py | 555 ++++++++++++++++++
.../_services_validators/key_validators.py | 2 +-
tests/mock_vws/fixtures/vuforia_backends.py | 49 ++
8 files changed, 943 insertions(+), 1 deletion(-)
create mode 100644 src/mock_vws/_flask_server/__init__.py
create mode 100644 src/mock_vws/_flask_server/storage.py
create mode 100644 src/mock_vws/_flask_server/vwq.py
create mode 100644 src/mock_vws/_flask_server/vws.py
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 851cd9e84..c3bee209f 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -24,6 +24,7 @@ pyroma==2.6 # Packaging best practices checker
pytest-cov==2.10.1 # Measure code coverage
pytest-envfiles==0.1.0 # Use files for environment variables for tests
pytest==6.0.2 # Test runners
+requests-mock-flask==2020.9.16.0
sphinx-autodoc-typehints==1.11.0
sphinx_paramlinks==0.4.2
sphinxcontrib-spelling==5.4.0
diff --git a/requirements.txt b/requirements.txt
index a5641ccde..4d13eefa0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,7 @@
Pillow==7.2.0
VWS-Auth-Tools==2020.5.31.0
backports.zoneinfo==0.2.1
+flask==1.1.2
requests-mock==1.8.0
requests==2.24.0
wrapt==1.12.1
diff --git a/src/mock_vws/_flask_server/__init__.py b/src/mock_vws/_flask_server/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/mock_vws/_flask_server/storage.py b/src/mock_vws/_flask_server/storage.py
new file mode 100644
index 000000000..fac5d8e6f
--- /dev/null
+++ b/src/mock_vws/_flask_server/storage.py
@@ -0,0 +1,173 @@
+"""
+Storage layer for the mock Vuforia Flask application.
+"""
+
+import base64
+import dataclasses
+import datetime
+import io
+import random
+from http import HTTPStatus
+from typing import List, Tuple
+
+from backports.zoneinfo import ZoneInfo
+from flask import Flask, jsonify, request
+
+from mock_vws.database import VuforiaDatabase
+from mock_vws.states import States
+from mock_vws.target import Target
+
+STORAGE_FLASK_APP = Flask(__name__)
+
+VUFORIA_DATABASES: List[VuforiaDatabase] = []
+
+
+@STORAGE_FLASK_APP.route('/reset', methods=['POST'])
+def reset() -> Tuple[str, int]:
+ """
+ Reset the back-end to a state of no databases.
+ """
+ VUFORIA_DATABASES.clear()
+ return '', HTTPStatus.OK
+
+
+@STORAGE_FLASK_APP.route('/databases', methods=['GET'])
+def get_databases() -> Tuple[str, int]:
+ """
+ Return a list of all databases.
+ """
+ databases = [database.to_dict() for database in VUFORIA_DATABASES]
+ return jsonify(databases), HTTPStatus.OK
+
+
+@STORAGE_FLASK_APP.route('/databases', methods=['POST'])
+def create_database() -> Tuple[str, int]:
+ """
+ Create a new database.
+ """
+ server_access_key = request.json['server_access_key']
+ server_secret_key = request.json['server_secret_key']
+ client_access_key = request.json['client_access_key']
+ client_secret_key = request.json['client_secret_key']
+ database_name = request.json['database_name']
+ state = States[request.json['state_name']]
+
+ database = VuforiaDatabase(
+ server_access_key=server_access_key,
+ server_secret_key=server_secret_key,
+ client_access_key=client_access_key,
+ client_secret_key=client_secret_key,
+ database_name=database_name,
+ state=state,
+ )
+ VUFORIA_DATABASES.append(database)
+ return jsonify(database.to_dict()), HTTPStatus.CREATED
+
+
+@STORAGE_FLASK_APP.route(
+ '/databases//targets',
+ methods=['POST'],
+)
+def create_target(database_name: str) -> Tuple[str, int]:
+ """
+ Create a new target in a given database.
+ """
+ [database] = [
+ database
+ for database in VUFORIA_DATABASES
+ if database.database_name == database_name
+ ]
+ image_base64 = request.json['image_base64']
+ image_bytes = base64.b64decode(image_base64)
+ image = io.BytesIO(image_bytes)
+ target = Target(
+ name=request.json['name'],
+ width=request.json['width'],
+ image=image,
+ active_flag=request.json['active_flag'],
+ processing_time_seconds=request.json['processing_time_seconds'],
+ application_metadata=request.json['application_metadata'],
+ target_id=request.json['target_id'],
+ )
+ database.targets.add(target)
+
+ return jsonify(target.to_dict()), HTTPStatus.CREATED
+
+
+@STORAGE_FLASK_APP.route(
+ '/databases//targets/',
+ methods=['DELETE'],
+)
+def delete_target(database_name: str, target_id: str) -> Tuple[str, int]:
+ """
+ Delete a target.
+ """
+ [database] = [
+ database
+ for database in VUFORIA_DATABASES
+ if database.database_name == database_name
+ ]
+ target = database.get_target(target_id=target_id)
+ now = datetime.datetime.now(tz=target.upload_date.tzinfo)
+ new_target = dataclasses.replace(target, delete_date=now)
+ database.targets.remove(target)
+ database.targets.add(new_target)
+ return jsonify(new_target.to_dict()), HTTPStatus.OK
+
+
+@STORAGE_FLASK_APP.route(
+ '/databases//targets/',
+ methods=['PUT'],
+)
+def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
+ """
+ Update a target.
+ """
+ [database] = [
+ database
+ for database in VUFORIA_DATABASES
+ if database.database_name == database_name
+ ]
+ target = database.get_target(target_id=target_id)
+
+ width = request.json.get('width', target.width)
+ name = request.json.get('name', target.name)
+ active_flag = request.json.get('active_flag', target.active_flag)
+ application_metadata = request.json.get(
+ 'application_metadata',
+ target.application_metadata,
+ )
+
+ image_file = target.image
+ if 'image' in request.json:
+ image = request.json['image']
+ decoded = base64.b64decode(image)
+ image_file = io.BytesIO(decoded)
+
+ # 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]))
+ processed_tracking_rating = random.choice(available_values)
+
+ gmt = ZoneInfo('GMT')
+ last_modified_date = datetime.datetime.now(tz=gmt)
+
+ new_target = dataclasses.replace(
+ target,
+ name=name,
+ width=width,
+ active_flag=active_flag,
+ application_metadata=application_metadata,
+ image=image_file,
+ processed_tracking_rating=processed_tracking_rating,
+ last_modified_date=last_modified_date,
+ )
+
+ database.targets.remove(target)
+ database.targets.add(new_target)
+
+ return jsonify(new_target.to_dict()), HTTPStatus.OK
+
+if __name__ == '__main__': # pragma: no cover
+ STORAGE_FLASK_APP.run(debug=True, host='0.0.0.0')
diff --git a/src/mock_vws/_flask_server/vwq.py b/src/mock_vws/_flask_server/vwq.py
new file mode 100644
index 000000000..59749b9e4
--- /dev/null
+++ b/src/mock_vws/_flask_server/vwq.py
@@ -0,0 +1,163 @@
+"""
+A fake implementation of the Vuforia Web Query API using Flask.
+
+See
+https://library.vuforia.com/articles/Solution/How-To-Perform-an-Image-Recognition-Query
+"""
+
+import copy
+import email.utils
+from http import HTTPStatus
+from typing import Dict, Final, Optional, Set
+
+import requests
+from flask import Flask, Response, request
+
+from mock_vws._query_tools import (
+ ActiveMatchingTargetsDeleteProcessing,
+ MatchingTargetsWithProcessingStatus,
+ get_query_match_response_text,
+)
+from mock_vws._query_validators import run_query_validators
+from mock_vws._query_validators.exceptions import (
+ MatchProcessing,
+ ValidatorException,
+)
+from mock_vws.database import VuforiaDatabase
+
+CLOUDRECO_FLASK_APP = Flask(import_name=__name__)
+CLOUDRECO_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
+
+
+# TODO choose something for this - it should actually work in a docker-compose
+# scenario.
+STORAGE_BASE_URL: Final[str] = 'http://todo.com'
+
+
+def get_all_databases() -> Set[VuforiaDatabase]:
+ """
+ Get all database objects from the storage back-end.
+ """
+ response = requests.get(url=STORAGE_BASE_URL + '/databases')
+ return set(
+ VuforiaDatabase.from_dict(database_dict=database_dict)
+ for database_dict in response.json()
+ )
+
+
+@CLOUDRECO_FLASK_APP.before_request
+def validate_request() -> None:
+ """
+ Run validators on the request.
+ """
+ request.environ['wsgi.input_terminated'] = True
+ input_stream_copy = copy.copy(request.input_stream)
+ request_body = input_stream_copy.read()
+ databases = get_all_databases()
+ run_query_validators(
+ request_headers=dict(request.headers),
+ request_body=request_body,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+
+class ResponseNoContentTypeAdded(Response):
+ """
+ A custom response type.
+
+ Without this, a content type is added to all responses.
+ Some of our responses need to not have a "Content-Type" header.
+ """
+
+ def __init__(
+ self,
+ response: Optional[str] = None,
+ status: Optional[int] = None,
+ headers: Optional[Dict[str, str]] = None,
+ mimetype: Optional[str] = None,
+ content_type: Optional[str] = None,
+ direct_passthrough: bool = False,
+ ) -> None:
+ if headers:
+ content_type_from_headers = headers.get('Content-Type')
+ else:
+ content_type_from_headers = None
+
+ super().__init__(
+ response=response,
+ status=status,
+ headers=headers,
+ mimetype=mimetype,
+ content_type=content_type,
+ direct_passthrough=direct_passthrough,
+ )
+
+ if (
+ content_type is None
+ and self.headers
+ and 'Content-Type' in self.headers
+ and not content_type_from_headers
+ ):
+ del self.headers['Content-Type']
+
+
+CLOUDRECO_FLASK_APP.response_class = ResponseNoContentTypeAdded
+
+
+@CLOUDRECO_FLASK_APP.errorhandler(ValidatorException)
+def handle_exceptions(exc: ValidatorException) -> Response:
+ """
+ Return the error response associated with the given exception.
+ """
+ return ResponseNoContentTypeAdded(
+ status=exc.status_code.value,
+ response=exc.response_text,
+ headers=exc.headers,
+ )
+
+
+@CLOUDRECO_FLASK_APP.route('/v1/query', methods=['POST'])
+def query() -> Response:
+ """
+ Perform an image recognition query.
+ """
+ # TODO these should be configurable
+ query_processes_deletion_seconds = 0.2
+ query_recognizes_deletion_seconds = 0.2
+ databases = get_all_databases()
+ input_stream_copy = copy.copy(request.input_stream)
+ request_body = input_stream_copy.read()
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+
+ try:
+ response_text = get_query_match_response_text(
+ request_headers=dict(request.headers),
+ request_body=request_body,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ query_processes_deletion_seconds=query_processes_deletion_seconds,
+ query_recognizes_deletion_seconds=query_recognizes_deletion_seconds,
+ )
+ except (
+ ActiveMatchingTargetsDeleteProcessing,
+ MatchingTargetsWithProcessingStatus,
+ ) as exc:
+ raise MatchProcessing from exc
+
+ headers = {
+ 'Content-Type': 'application/json',
+ 'Date': date,
+ 'Connection': 'keep-alive',
+ 'Server': 'nginx',
+ }
+ return Response(
+ status=HTTPStatus.OK,
+ response=response_text,
+ headers=headers,
+ )
+
+if __name__ == '__main__': # pragma: no cover
+ CLOUDRECO_FLASK_APP.run(debug=True, host='0.0.0.0')
diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py
new file mode 100644
index 000000000..682b74538
--- /dev/null
+++ b/src/mock_vws/_flask_server/vws.py
@@ -0,0 +1,555 @@
+"""
+A fake implementation of the Vuforia Web Services API.
+
+See
+https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API
+"""
+
+import base64
+import email.utils
+import io
+import json
+import uuid
+from http import HTTPStatus
+from typing import Dict, Final, List, Optional, Set
+
+import requests
+from flask import Flask, Response, request
+from PIL import Image
+
+from mock_vws._constants import ResultCodes, TargetStatuses
+from mock_vws._database_matchers import get_database_matching_server_keys
+from mock_vws._mock_common import json_dump
+from mock_vws._services_validators import run_services_validators
+from mock_vws._services_validators.exceptions import (
+ Fail,
+ TargetStatusNotSuccess,
+ TargetStatusProcessing,
+ ValidatorException,
+)
+from mock_vws.database import VuforiaDatabase
+from mock_vws.target import Target
+
+VWS_FLASK_APP = Flask(import_name=__name__)
+VWS_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
+
+
+# TODO choose something for this - it should actually work in a docker-compose
+# scenario.
+STORAGE_BASE_URL: Final[str] = 'http://todo.com'
+
+
+def get_all_databases() -> Set[VuforiaDatabase]:
+ """
+ Get all database objects from the storage back-end.
+ """
+ response = requests.get(url=STORAGE_BASE_URL + '/databases')
+ return set(
+ VuforiaDatabase.from_dict(database_dict=database_dict)
+ for database_dict in response.json()
+ )
+
+
+class ResponseNoContentTypeAdded(Response):
+ """
+ A custom response type.
+
+ Without this, a content type is added to all responses.
+ Some of our responses need to not have a "Content-Type" header.
+ """
+
+ def __init__(
+ self,
+ response: Optional[str] = None,
+ status: Optional[int] = None,
+ headers: Optional[Dict[str, str]] = None,
+ mimetype: Optional[str] = None,
+ content_type: Optional[str] = None,
+ direct_passthrough: bool = False,
+ ) -> None:
+ if headers:
+ content_type_from_headers = headers.get('Content-Type')
+ else:
+ content_type_from_headers = None
+
+ super().__init__(
+ response=response,
+ status=status,
+ headers=headers,
+ mimetype=mimetype,
+ content_type=content_type,
+ direct_passthrough=direct_passthrough,
+ )
+
+ if (
+ content_type is None
+ and self.headers
+ and 'Content-Type' in self.headers
+ and not content_type_from_headers
+ ):
+ del self.headers['Content-Type']
+
+
+VWS_FLASK_APP.response_class = ResponseNoContentTypeAdded
+
+
+@VWS_FLASK_APP.before_request
+def validate_request() -> None:
+ """
+ Run validators on the request.
+ """
+ request.environ['wsgi.input_terminated'] = True
+ databases = get_all_databases()
+ run_services_validators(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+
+@VWS_FLASK_APP.errorhandler(ValidatorException)
+def handle_exceptions(exc: ValidatorException) -> Response:
+ """
+ Return the error response associated with the given exception.
+ """
+ return ResponseNoContentTypeAdded(
+ status=exc.status_code.value,
+ response=exc.response_text,
+ headers=exc.headers,
+ )
+
+
+@VWS_FLASK_APP.route('/targets', methods=['POST'])
+def add_target() -> Response:
+ """
+ Add a target.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Add-a-Target
+ """
+ # We do not use ``request.get_json(force=True)`` because this only works
+ # when the content type is given as ``application/json``.
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+
+ request_json = json.loads(request.data)
+ name = request_json['name']
+ active_flag = request_json.get('active_flag')
+ if active_flag is None:
+ active_flag = True
+
+ image = request_json['image']
+ decoded = base64.b64decode(image)
+ image_file = io.BytesIO(decoded)
+
+ new_target = Target(
+ name=name,
+ width=request_json['width'],
+ image=image_file,
+ active_flag=active_flag,
+ processing_time_seconds=0.2,
+ # TODO add this back:
+ # processing_time_seconds=self._processing_time_seconds,
+ application_metadata=request_json.get('application_metadata'),
+ )
+
+ requests.post(
+ url=f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets',
+ json=new_target.to_dict(),
+ )
+
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.TARGET_CREATED.value,
+ 'target_id': new_target.target_id,
+ }
+
+ return Response(
+ status=HTTPStatus.CREATED,
+ response=json_dump(body),
+ headers=headers,
+ )
+
+
+@VWS_FLASK_APP.route('/targets/', methods=['GET'])
+def get_target(target_id: str) -> Response:
+ """
+ Get details of a target.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Retrieve-a-Target-Record
+ """
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+ [target] = [
+ target for target in database.targets if target.target_id == target_id
+ ]
+
+ target_record = {
+ 'target_id': target.target_id,
+ 'active_flag': target.active_flag,
+ 'name': target.name,
+ 'width': target.width,
+ 'tracking_rating': target.tracking_rating,
+ 'reco_rating': target.reco_rating,
+ }
+
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+ body = {
+ 'result_code': ResultCodes.SUCCESS.value,
+ 'transaction_id': uuid.uuid4().hex,
+ 'target_record': target_record,
+ 'status': target.status,
+ }
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
+
+
+@VWS_FLASK_APP.route('/targets/', methods=['DELETE'])
+def delete_target(target_id: str) -> Response:
+ """
+ Delete a target.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Delete-a-Target
+ """
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+ [target] = [
+ target for target in database.targets if target.target_id == target_id
+ ]
+
+ if target.status == TargetStatuses.PROCESSING.value:
+ raise TargetStatusProcessing
+
+ delete_url = (
+ f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/'
+ f'{target_id}'
+ )
+ requests.delete(url=delete_url)
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.SUCCESS.value,
+ }
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
+
+
+@VWS_FLASK_APP.route('/summary', methods=['GET'])
+def database_summary() -> Response:
+ """
+ Get a database summary report.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Get-a-Database-Summary-Report
+ """
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+ body = {
+ 'result_code': ResultCodes.SUCCESS.value,
+ 'transaction_id': uuid.uuid4().hex,
+ 'name': database.database_name,
+ 'active_images': len(database.active_targets),
+ 'inactive_images': len(database.inactive_targets),
+ 'failed_images': len(database.failed_targets),
+ 'target_quota': database.target_quota,
+ 'total_recos': database.total_recos,
+ 'current_month_recos': database.current_month_recos,
+ 'previous_month_recos': database.previous_month_recos,
+ 'processing_images': len(database.processing_targets),
+ 'reco_threshold': database.reco_threshold,
+ 'request_quota': database.request_quota,
+ # We have ``self.request_count`` but Vuforia always shows 0.
+ # This was not always the case.
+ 'request_usage': 0,
+ }
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
+
+
+@VWS_FLASK_APP.route('/summary/', methods=['GET'])
+def target_summary(target_id: str) -> Response:
+ """
+ Get a summary report for a target.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Retrieve-a-Target-Summary-Report
+ """
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+ [target] = [
+ target for target in database.targets if target.target_id == target_id
+ ]
+ body = {
+ 'status': target.status,
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.SUCCESS.value,
+ 'database_name': database.database_name,
+ 'target_name': target.name,
+ 'upload_date': target.upload_date.strftime('%Y-%m-%d'),
+ 'active_flag': target.active_flag,
+ 'tracking_rating': target.tracking_rating,
+ 'total_recos': target.total_recos,
+ 'current_month_recos': target.current_month_recos,
+ 'previous_month_recos': target.previous_month_recos,
+ }
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
+
+
+@VWS_FLASK_APP.route('/duplicates/', methods=['GET'])
+def get_duplicates(target_id: str) -> Response:
+ """
+ Get targets which may be considered duplicates of a given target.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Check-for-Duplicate-Targets
+ """
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+ [target] = [
+ target for target in database.targets if target.target_id == target_id
+ ]
+ other_targets = set(database.targets) - set([target])
+
+ similar_targets: List[str] = [
+ other.target_id
+ for other in other_targets
+ if Image.open(other.image) == Image.open(target.image)
+ and TargetStatuses.FAILED.value not in (target.status, other.status)
+ and TargetStatuses.PROCESSING.value != other.status
+ and other.active_flag
+ ]
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.SUCCESS.value,
+ 'similar_targets': similar_targets,
+ }
+
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
+
+
+@VWS_FLASK_APP.route('/targets', methods=['GET'])
+def target_list() -> Response:
+ """
+ Get a list of all targets.
+
+ Fake implementation of
+ https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Get-a-Target-List-for-a-Cloud-Database
+ """
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+ assert isinstance(database, VuforiaDatabase)
+ results = [target.target_id for target in database.not_deleted_targets]
+
+ body = {
+ 'transaction_id': uuid.uuid4().hex,
+ 'result_code': ResultCodes.SUCCESS.value,
+ 'results': results,
+ }
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
+
+
+@VWS_FLASK_APP.route('/targets/', methods=['PUT'])
+def update_target(target_id: str) -> Response:
+ """
+ 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
+ """
+ # We do not use ``request.get_json(force=True)`` because this only works
+ # when the content type is given as ``application/json``.
+ request_json = json.loads(request.data)
+ databases = get_all_databases()
+ database = get_database_matching_server_keys(
+ request_headers=dict(request.headers),
+ request_body=request.data,
+ request_method=request.method,
+ request_path=request.path,
+ databases=databases,
+ )
+
+ assert isinstance(database, VuforiaDatabase)
+ [target] = [
+ target for target in database.targets if target.target_id == target_id
+ ]
+
+ if target.status != TargetStatuses.SUCCESS.value:
+ raise TargetStatusNotSuccess
+
+ update_values = {}
+ if 'width' in request_json:
+ update_values['width'] = request_json['width']
+
+ if 'active_flag' in request_json:
+ active_flag = request_json['active_flag']
+ if active_flag is None:
+ raise Fail(status_code=HTTPStatus.BAD_REQUEST)
+ update_values['active_flag'] = active_flag
+
+ if 'application_metadata' in request_json:
+ application_metadata = request_json['application_metadata']
+ if application_metadata is None:
+ raise Fail(status_code=HTTPStatus.BAD_REQUEST)
+ update_values['application_metadata'] = application_metadata
+
+ if 'name' in request_json:
+ name = request_json['name']
+ update_values['name'] = name
+
+ if 'image' in request_json:
+ image = request_json['image']
+ update_values['image'] = image
+
+ put_url = (
+ f'{STORAGE_BASE_URL}/databases/{database.database_name}/targets/'
+ f'{target_id}'
+ )
+ requests.put(url=put_url, json=update_values)
+
+ date = email.utils.formatdate(None, localtime=False, usegmt=True)
+ headers = {
+ 'Connection': 'keep-alive',
+ 'Content-Type': 'application/json',
+ 'Server': 'nginx',
+ 'Date': date,
+ }
+ body = {
+ 'result_code': ResultCodes.SUCCESS.value,
+ 'transaction_id': uuid.uuid4().hex,
+ }
+ return Response(
+ status=HTTPStatus.OK,
+ response=json_dump(body),
+ headers=headers,
+ )
+
+if __name__ == '__main__': # pragma: no cover
+ VWS_FLASK_APP.run(debug=True, host='0.0.0.0')
diff --git a/src/mock_vws/_services_validators/key_validators.py b/src/mock_vws/_services_validators/key_validators.py
index 9869f2168..37acca67e 100644
--- a/src/mock_vws/_services_validators/key_validators.py
+++ b/src/mock_vws/_services_validators/key_validators.py
@@ -142,7 +142,7 @@ def validate_keys(
optional_keys = matching_route.optional_keys
allowed_keys = mandatory_keys.union(optional_keys)
- if request_body is None and not allowed_keys:
+ if not request_body and not allowed_keys:
return
request_text = request_body.decode()
diff --git a/tests/mock_vws/fixtures/vuforia_backends.py b/tests/mock_vws/fixtures/vuforia_backends.py
index caf67885f..053a663e0 100644
--- a/tests/mock_vws/fixtures/vuforia_backends.py
+++ b/tests/mock_vws/fixtures/vuforia_backends.py
@@ -8,11 +8,17 @@
from typing import Generator
import pytest
+import requests
+import requests_mock
from _pytest.fixtures import SubRequest
+from requests_mock_flask import add_flask_app_to_mock
from vws import VWS
from vws.exceptions.vws_exceptions import TargetStatusNotSuccess
from mock_vws import MockVWS
+from mock_vws._flask_server.storage import STORAGE_FLASK_APP
+from mock_vws._flask_server.vwq import CLOUDRECO_FLASK_APP
+from mock_vws._flask_server.vws import STORAGE_BASE_URL, VWS_FLASK_APP
from mock_vws.database import VuforiaDatabase
from mock_vws.states import States
@@ -83,6 +89,47 @@ def _enable_use_mock_vuforia(
yield
+def _enable_use_docker_in_memory(
+ working_database: VuforiaDatabase,
+ inactive_database: VuforiaDatabase,
+) -> Generator:
+ with requests_mock.Mocker(real_http=False) as mock:
+ add_flask_app_to_mock(
+ mock_obj=mock,
+ flask_app=VWS_FLASK_APP,
+ base_url='https://vws.vuforia.com',
+ )
+
+ add_flask_app_to_mock(
+ mock_obj=mock,
+ flask_app=CLOUDRECO_FLASK_APP,
+ base_url='https://cloudreco.vuforia.com',
+ )
+
+ add_flask_app_to_mock(
+ mock_obj=mock,
+ flask_app=STORAGE_FLASK_APP,
+ base_url=STORAGE_BASE_URL,
+ )
+
+ requests.post(url=STORAGE_BASE_URL + '/reset')
+
+ working_database_dict = working_database.to_dict()
+ inactive_database_dict = inactive_database.to_dict()
+
+ requests.post(
+ url=STORAGE_BASE_URL + '/databases',
+ json=working_database_dict,
+ )
+
+ requests.post(
+ url=STORAGE_BASE_URL + '/databases',
+ json=inactive_database_dict,
+ )
+
+ yield
+
+
class VuforiaBackend(Enum):
"""
Backends for tests.
@@ -90,6 +137,7 @@ class VuforiaBackend(Enum):
REAL = 'Real Vuforia'
MOCK = 'In Memory Mock Vuforia'
+ DOCKER_IN_MEMORY = 'In Memory version of Docker application'
@pytest.fixture(
@@ -115,6 +163,7 @@ def verify_mock_vuforia(
enable_function = {
VuforiaBackend.REAL: _enable_use_real_vuforia,
VuforiaBackend.MOCK: _enable_use_mock_vuforia,
+ VuforiaBackend.DOCKER_IN_MEMORY: _enable_use_docker_in_memory,
}[backend]
yield from enable_function(
From 047d167a227c894540ef2e741292454e21aec57f Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 15:51:40 +0100
Subject: [PATCH 0282/3455] Fix some lint issues
---
src/mock_vws/_flask_server/storage.py | 1 +
src/mock_vws/_flask_server/vwq.py | 1 +
src/mock_vws/_flask_server/vws.py | 1 +
3 files changed, 3 insertions(+)
diff --git a/src/mock_vws/_flask_server/storage.py b/src/mock_vws/_flask_server/storage.py
index fac5d8e6f..a070d4940 100644
--- a/src/mock_vws/_flask_server/storage.py
+++ b/src/mock_vws/_flask_server/storage.py
@@ -169,5 +169,6 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
return jsonify(new_target.to_dict()), HTTPStatus.OK
+
if __name__ == '__main__': # pragma: no cover
STORAGE_FLASK_APP.run(debug=True, host='0.0.0.0')
diff --git a/src/mock_vws/_flask_server/vwq.py b/src/mock_vws/_flask_server/vwq.py
index 59749b9e4..a8b7928c4 100644
--- a/src/mock_vws/_flask_server/vwq.py
+++ b/src/mock_vws/_flask_server/vwq.py
@@ -159,5 +159,6 @@ def query() -> Response:
headers=headers,
)
+
if __name__ == '__main__': # pragma: no cover
CLOUDRECO_FLASK_APP.run(debug=True, host='0.0.0.0')
diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py
index 682b74538..d1be57efd 100644
--- a/src/mock_vws/_flask_server/vws.py
+++ b/src/mock_vws/_flask_server/vws.py
@@ -551,5 +551,6 @@ def update_target(target_id: str) -> Response:
headers=headers,
)
+
if __name__ == '__main__': # pragma: no cover
VWS_FLASK_APP.run(debug=True, host='0.0.0.0')
From 4086e4a8f0321137a9ccba8b66c5310deac682cd Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 15:59:40 +0100
Subject: [PATCH 0283/3455] Use simpler method to have no default mimetype
---
src/mock_vws/_flask_server/vwq.py | 44 +++++--------------------------
src/mock_vws/_flask_server/vws.py | 44 +++++--------------------------
2 files changed, 13 insertions(+), 75 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq.py b/src/mock_vws/_flask_server/vwq.py
index a8b7928c4..2913c6d14 100644
--- a/src/mock_vws/_flask_server/vwq.py
+++ b/src/mock_vws/_flask_server/vwq.py
@@ -8,7 +8,7 @@
import copy
import email.utils
from http import HTTPStatus
-from typing import Dict, Final, Optional, Set
+from typing import Final, Set
import requests
from flask import Flask, Response, request
@@ -27,10 +27,6 @@
CLOUDRECO_FLASK_APP = Flask(import_name=__name__)
CLOUDRECO_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
-
-
-# TODO choose something for this - it should actually work in a docker-compose
-# scenario.
STORAGE_BASE_URL: Final[str] = 'http://todo.com'
@@ -71,36 +67,9 @@ class ResponseNoContentTypeAdded(Response):
Some of our responses need to not have a "Content-Type" header.
"""
- def __init__(
- self,
- response: Optional[str] = None,
- status: Optional[int] = None,
- headers: Optional[Dict[str, str]] = None,
- mimetype: Optional[str] = None,
- content_type: Optional[str] = None,
- direct_passthrough: bool = False,
- ) -> None:
- if headers:
- content_type_from_headers = headers.get('Content-Type')
- else:
- content_type_from_headers = None
-
- super().__init__(
- response=response,
- status=status,
- headers=headers,
- mimetype=mimetype,
- content_type=content_type,
- direct_passthrough=direct_passthrough,
- )
-
- if (
- content_type is None
- and self.headers
- and 'Content-Type' in self.headers
- and not content_type_from_headers
- ):
- del self.headers['Content-Type']
+ # When https://github.com/python/typeshed/pull/4563 is shipped in a future
+ # release of mypy, we can remove this ignore.
+ default_mimetype = None # type: ignore
CLOUDRECO_FLASK_APP.response_class = ResponseNoContentTypeAdded
@@ -123,7 +92,6 @@ def query() -> Response:
"""
Perform an image recognition query.
"""
- # TODO these should be configurable
query_processes_deletion_seconds = 0.2
query_recognizes_deletion_seconds = 0.2
databases = get_all_databases()
@@ -139,7 +107,9 @@ def query() -> Response:
request_path=request.path,
databases=databases,
query_processes_deletion_seconds=query_processes_deletion_seconds,
- query_recognizes_deletion_seconds=query_recognizes_deletion_seconds,
+ query_recognizes_deletion_seconds=(
+ query_recognizes_deletion_seconds
+ ),
)
except (
ActiveMatchingTargetsDeleteProcessing,
diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py
index d1be57efd..bcdfb7489 100644
--- a/src/mock_vws/_flask_server/vws.py
+++ b/src/mock_vws/_flask_server/vws.py
@@ -11,7 +11,7 @@
import json
import uuid
from http import HTTPStatus
-from typing import Dict, Final, List, Optional, Set
+from typing import Final, List, Set
import requests
from flask import Flask, Response, request
@@ -32,10 +32,6 @@
VWS_FLASK_APP = Flask(import_name=__name__)
VWS_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
-
-
-# TODO choose something for this - it should actually work in a docker-compose
-# scenario.
STORAGE_BASE_URL: Final[str] = 'http://todo.com'
@@ -58,36 +54,9 @@ class ResponseNoContentTypeAdded(Response):
Some of our responses need to not have a "Content-Type" header.
"""
- def __init__(
- self,
- response: Optional[str] = None,
- status: Optional[int] = None,
- headers: Optional[Dict[str, str]] = None,
- mimetype: Optional[str] = None,
- content_type: Optional[str] = None,
- direct_passthrough: bool = False,
- ) -> None:
- if headers:
- content_type_from_headers = headers.get('Content-Type')
- else:
- content_type_from_headers = None
-
- super().__init__(
- response=response,
- status=status,
- headers=headers,
- mimetype=mimetype,
- content_type=content_type,
- direct_passthrough=direct_passthrough,
- )
-
- if (
- content_type is None
- and self.headers
- and 'Content-Type' in self.headers
- and not content_type_from_headers
- ):
- del self.headers['Content-Type']
+ # When https://github.com/python/typeshed/pull/4563 is shipped in a future
+ # release of mypy, we can remove this ignore.
+ default_mimetype = None # type: ignore
VWS_FLASK_APP.response_class = ResponseNoContentTypeAdded
@@ -129,6 +98,7 @@ def add_target() -> Response:
Fake implementation of
https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Add-a-Target
"""
+ processing_time_seconds = 0.2
# We do not use ``request.get_json(force=True)`` because this only works
# when the content type is given as ``application/json``.
databases = get_all_databases()
@@ -157,9 +127,7 @@ def add_target() -> Response:
width=request_json['width'],
image=image_file,
active_flag=active_flag,
- processing_time_seconds=0.2,
- # TODO add this back:
- # processing_time_seconds=self._processing_time_seconds,
+ processing_time_seconds=processing_time_seconds,
application_metadata=request_json.get('application_metadata'),
)
From 33dbd39619bf8b1e7c020ce400eefcc0b27d102b Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 16:02:06 +0100
Subject: [PATCH 0284/3455] Add mypy to spelling private dict
---
spelling_private_dict.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/spelling_private_dict.txt b/spelling_private_dict.txt
index 0bbf08abe..aa8382783 100644
--- a/spelling_private_dict.txt
+++ b/spelling_private_dict.txt
@@ -55,6 +55,7 @@ metadata
mib
mockvws
multipart
+mypy
noqa
pdict
plugins
From 3b9daa3ee3dbca902a90d7aaa62a8f5a772ebf76 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 17:51:39 +0100
Subject: [PATCH 0285/3455] Add the start of a test for Docker
---
.github/workflows/ci.yml | 1 +
dev-requirements.txt | 1 +
tests/mock_vws/test_docker.py | 5 +++++
3 files changed, 7 insertions(+)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 447543fb4..e7fb98e3d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -64,6 +64,7 @@ jobs:
- test_update_target.py::TestWidth
- test_update_target.py::TestInactiveProject
- test_usage.py
+ - test_docker.py
steps:
# We share Vuforia credentials and therefore Vuforia databases across
diff --git a/dev-requirements.txt b/dev-requirements.txt
index c3bee209f..3529e1fd3 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -7,6 +7,7 @@ autoflake==1.4
black==20.8b1
check-manifest==0.42
doc8==0.8.1
+docker==4.3.1
dodgy==0.2.1 # Look for uploaded secrets
flake8-commas==2.0.0 # Require silicon valley commas
flake8-quotes==3.2.0 # Require single quotes
diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py
index 0ed18695f..2c33ff66f 100644
--- a/tests/mock_vws/test_docker.py
+++ b/tests/mock_vws/test_docker.py
@@ -1,3 +1,8 @@
"""
Tests for running the mock server in Docker.
"""
+
+import docker
+
+def test_build_and_run():
+ pass
From bf417ca7acd35542af1e7f7cada04d42bfd80f0d Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 20:18:48 +0100
Subject: [PATCH 0286/3455] Expanding test
---
tests/mock_vws/test_docker.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py
index 2c33ff66f..844c4d2ce 100644
--- a/tests/mock_vws/test_docker.py
+++ b/tests/mock_vws/test_docker.py
@@ -5,4 +5,12 @@
import docker
def test_build_and_run():
+ client = docker.from_env()
+ # Build containers
+ dockerfile_path = ...
+ image, build_logs = client.images.build(path=dockerfile_path)
+ container = client.containers.run(image=image, detach=True)
+ # Run containers
+
+ # Add target using vws_python
pass
From a519fcc95e038ad68c0742e080514babfa6129de Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Mon, 21 Sep 2020 22:51:44 +0100
Subject: [PATCH 0287/3455] Change target to remove unhashable type
---
src/mock_vws/_flask_server/storage.py | 12 ++++------
src/mock_vws/_flask_server/vws.py | 10 ++------
src/mock_vws/_query_tools.py | 20 +++-------------
.../mock_web_services_api.py | 24 ++++++-------------
src/mock_vws/target.py | 15 +++++-------
tests/mock_vws/test_usage.py | 1 -
6 files changed, 22 insertions(+), 60 deletions(-)
diff --git a/src/mock_vws/_flask_server/storage.py b/src/mock_vws/_flask_server/storage.py
index a070d4940..a74bbbbfe 100644
--- a/src/mock_vws/_flask_server/storage.py
+++ b/src/mock_vws/_flask_server/storage.py
@@ -5,7 +5,6 @@
import base64
import dataclasses
import datetime
-import io
import random
from http import HTTPStatus
from typing import List, Tuple
@@ -79,11 +78,10 @@ def create_target(database_name: str) -> Tuple[str, int]:
]
image_base64 = request.json['image_base64']
image_bytes = base64.b64decode(image_base64)
- image = io.BytesIO(image_bytes)
target = Target(
name=request.json['name'],
width=request.json['width'],
- image=image,
+ image_value=image_bytes,
active_flag=request.json['active_flag'],
processing_time_seconds=request.json['processing_time_seconds'],
application_metadata=request.json['application_metadata'],
@@ -138,11 +136,9 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
target.application_metadata,
)
- image_file = target.image
+ image_value = target.image_value
if 'image' in request.json:
- image = request.json['image']
- decoded = base64.b64decode(image)
- image_file = io.BytesIO(decoded)
+ image_value = base64.b64decode(request.json['image'])
# In the real implementation, the tracking rating can stay the same.
# However, for demonstration purposes, the tracking rating changes but
@@ -159,7 +155,7 @@ def update_target(database_name: str, target_id: str) -> Tuple[str, int]:
width=width,
active_flag=active_flag,
application_metadata=application_metadata,
- image=image_file,
+ image_value=image_value,
processed_tracking_rating=processed_tracking_rating,
last_modified_date=last_modified_date,
)
diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py
index bcdfb7489..3cc03614a 100644
--- a/src/mock_vws/_flask_server/vws.py
+++ b/src/mock_vws/_flask_server/vws.py
@@ -7,7 +7,6 @@
import base64
import email.utils
-import io
import json
import uuid
from http import HTTPStatus
@@ -15,7 +14,6 @@
import requests
from flask import Flask, Response, request
-from PIL import Image
from mock_vws._constants import ResultCodes, TargetStatuses
from mock_vws._database_matchers import get_database_matching_server_keys
@@ -118,14 +116,10 @@ def add_target() -> Response:
if active_flag is None:
active_flag = True
- image = request_json['image']
- decoded = base64.b64decode(image)
- image_file = io.BytesIO(decoded)
-
new_target = Target(
name=name,
width=request_json['width'],
- image=image_file,
+ image_value=base64.b64decode(request_json['image']),
active_flag=active_flag,
processing_time_seconds=processing_time_seconds,
application_metadata=request_json.get('application_metadata'),
@@ -380,7 +374,7 @@ def get_duplicates(target_id: str) -> Response:
similar_targets: List[str] = [
other.target_id
for other in other_targets
- if Image.open(other.image) == Image.open(target.image)
+ if other.image_value == target.image_value
and TargetStatuses.FAILED.value not in (target.status, other.status)
and TargetStatuses.PROCESSING.value != other.status
and other.active_flag
diff --git a/src/mock_vws/_query_tools.py b/src/mock_vws/_query_tools.py
index 102570639..246363a46 100644
--- a/src/mock_vws/_query_tools.py
+++ b/src/mock_vws/_query_tools.py
@@ -30,19 +30,6 @@ class ActiveMatchingTargetsDeleteProcessing(Exception):
"""
-def _images_match(image: io.BytesIO, another_image: io.BytesIO) -> bool:
- """
- Given two images, return whether they are matching.
-
- In the real Vuforia, this matching is fuzzy.
- For now, we check exact byte matching.
-
- See https://github.com/VWS-Python/vws-python-mock/issues/3 for changing
- that.
- """
- return bool(image.getvalue() == another_image.getvalue())
-
-
def get_query_match_response_text(
request_headers: Dict[str, str],
request_body: bytes,
@@ -90,9 +77,8 @@ def get_query_match_response_text(
[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)
+ [image_value] = parsed['image']
+ assert isinstance(image_value, bytes)
gmt = ZoneInfo('GMT')
now = datetime.datetime.now(tz=gmt)
@@ -117,7 +103,7 @@ def get_query_match_response_text(
matching_targets = [
target
for target in database.targets
- if _images_match(image=target.image, another_image=image)
+ if target.image_value == image_value
]
not_deleted_matches = [
diff --git a/src/mock_vws/_requests_mock_server/mock_web_services_api.py b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
index cee97c50c..3a7afd39b 100644
--- a/src/mock_vws/_requests_mock_server/mock_web_services_api.py
+++ b/src/mock_vws/_requests_mock_server/mock_web_services_api.py
@@ -9,7 +9,6 @@
import dataclasses
import datetime
import email.utils
-import io
import random
import uuid
from http import HTTPStatus
@@ -17,7 +16,6 @@
import wrapt
from backports.zoneinfo import ZoneInfo
-from PIL import Image
from requests_mock import DELETE, GET, POST, PUT
from requests_mock.request import _RequestObjectProxy
from requests_mock.response import _Context
@@ -181,18 +179,12 @@ def add_target(
False: False,
}[given_active_flag]
- image = request.json()['image']
- decoded = base64.b64decode(image)
- image_file = io.BytesIO(decoded)
-
- name = request.json()['name']
- width = request.json()['width']
application_metadata = request.json().get('application_metadata')
new_target = Target(
- name=name,
- width=width,
- image=image_file,
+ name=request.json()['name'],
+ width=request.json()['width'],
+ image_value=base64.b64decode(request.json()['image']),
active_flag=active_flag,
processing_time_seconds=self._processing_time_seconds,
application_metadata=application_metadata,
@@ -426,7 +418,7 @@ def get_duplicates(
similar_targets: List[str] = [
other.target_id
for other in other_targets
- if Image.open(other.image) == Image.open(target.image)
+ if other.image_value == target.image_value
and TargetStatuses.FAILED.value
not in (target.status, other.status)
and TargetStatuses.PROCESSING.value != other.status
@@ -496,11 +488,9 @@ def update_target(
target.application_metadata,
)
- image_file = target.image
+ image_value = target.image_value
if 'image' in request.json():
- image = request.json()['image']
- decoded = base64.b64decode(image)
- image_file = io.BytesIO(decoded)
+ image_value = base64.b64decode(request.json()['image'])
if 'active_flag' in request.json() and active_flag is None:
raise Fail(status_code=HTTPStatus.BAD_REQUEST)
@@ -526,7 +516,7 @@ def update_target(
width=width,
active_flag=active_flag,
application_metadata=application_metadata,
- image=image_file,
+ image_value=image_value,
processed_tracking_rating=processed_tracking_rating,
last_modified_date=last_modified_date,
)
diff --git a/src/mock_vws/target.py b/src/mock_vws/target.py
index c9db3ba0d..2103c1d53 100644
--- a/src/mock_vws/target.py
+++ b/src/mock_vws/target.py
@@ -67,9 +67,7 @@ class Target: # pylint: disable=too-many-instance-attributes
active_flag: bool
application_metadata: Optional[str]
- # Comparison of io.BytesIO compares the object, not the file contents.
- # data we care about, so we leave this.
- image: io.BytesIO = field(compare=False)
+ image_value: bytes
name: str
processing_time_seconds: float
width: float
@@ -95,7 +93,8 @@ def _post_processing_status(self) -> TargetStatuses:
How VWS determines this is unknown, but it relates to how suitable the
target is for detection.
"""
- image = Image.open(self.image)
+ image_file = io.BytesIO(self.image_value)
+ image = Image.open(image_file)
image_stat = ImageStat.Stat(image)
average_std_dev = statistics.mean(image_stat.stddev)
@@ -171,9 +170,8 @@ def from_dict(cls, target_dict: TargetDict) -> Target:
active_flag = target_dict['active_flag']
width = target_dict['width']
image_base64 = target_dict['image_base64']
+ image_value = base64.b64decode(image_base64)
processed_tracking_rating = target_dict['processed_tracking_rating']
- image_bytes = base64.b64decode(image_base64)
- image = io.BytesIO(image_bytes)
processing_time_seconds = target_dict['processing_time_seconds']
application_metadata = target_dict['application_metadata']
target_id = target_dict['target_id']
@@ -196,7 +194,7 @@ def from_dict(cls, target_dict: TargetDict) -> Target:
name=name,
active_flag=active_flag,
width=width,
- image=image,
+ image_value=image_value,
processing_time_seconds=processing_time_seconds,
application_metadata=application_metadata,
delete_date=delete_date,
@@ -214,8 +212,7 @@ def to_dict(self) -> TargetDict:
if self.delete_date:
delete_date = datetime.datetime.isoformat(self.delete_date)
- image_value = self.image.getvalue()
- image_base64 = base64.encodebytes(image_value).decode()
+ image_base64 = base64.encodebytes(self.image_value).decode()
return {
'name': self.name,
diff --git a/tests/mock_vws/test_usage.py b/tests/mock_vws/test_usage.py
index 259170eb1..3db1bc0fc 100644
--- a/tests/mock_vws/test_usage.py
+++ b/tests/mock_vws/test_usage.py
@@ -539,7 +539,6 @@ def test_to_dict(self, high_quality_image: io.BytesIO) -> None:
assert json.dumps(target_dict)
new_target = Target.from_dict(target_dict=target_dict)
- assert new_target.image.getvalue() == target.image.getvalue()
assert new_target == target
def test_to_dict_deleted(self, high_quality_image: io.BytesIO) -> None:
From bfd678dee05917c079cc7eac6b3021a2f63ad8e9 Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
<27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Tue, 22 Sep 2020 06:35:00 +0000
Subject: [PATCH 0288/3455] Bump requests-mock-flask from 2020.9.16.0 to
2020.9.18.0
Bumps [requests-mock-flask](https://github.com/adamtheturtle/requests-mock-flask) from 2020.9.16.0 to 2020.9.18.0.
- [Release notes](https://github.com/adamtheturtle/requests-mock-flask/releases)
- [Changelog](https://github.com/adamtheturtle/requests-mock-flask/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/adamtheturtle/requests-mock-flask/compare/2020.09.16.0...2020.09.18.0)
Signed-off-by: dependabot-preview[bot]
---
dev-requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index c3bee209f..3004d1b65 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -24,7 +24,7 @@ pyroma==2.6 # Packaging best practices checker
pytest-cov==2.10.1 # Measure code coverage
pytest-envfiles==0.1.0 # Use files for environment variables for tests
pytest==6.0.2 # Test runners
-requests-mock-flask==2020.9.16.0
+requests-mock-flask==2020.9.18.0
sphinx-autodoc-typehints==1.11.0
sphinx_paramlinks==0.4.2
sphinxcontrib-spelling==5.4.0
From 342a118f3f0635291f95f5773b1a00d6f91978cb Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
<27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Tue, 22 Sep 2020 06:35:30 +0000
Subject: [PATCH 0289/3455] Bump check-manifest from 0.42 to 0.43
Bumps [check-manifest](https://github.com/mgedmin/check-manifest) from 0.42 to 0.43.
- [Release notes](https://github.com/mgedmin/check-manifest/releases)
- [Changelog](https://github.com/mgedmin/check-manifest/blob/master/CHANGES.rst)
- [Commits](https://github.com/mgedmin/check-manifest/compare/0.42...0.43)
Signed-off-by: dependabot-preview[bot]
---
dev-requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index c3bee209f..2305749ec 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -5,7 +5,7 @@ VWS-Test-Fixtures==2020.8.2.0
attrs==20.2.0 # Modern attrs is required for pytest
autoflake==1.4
black==20.8b1
-check-manifest==0.42
+check-manifest==0.43
doc8==0.8.1
dodgy==0.2.1 # Look for uploaded secrets
flake8-commas==2.0.0 # Require silicon valley commas
From 8b1340b72184ed87fae1b41121ed68cb3d30bfb9 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Tue, 22 Sep 2020 08:34:00 +0100
Subject: [PATCH 0290/3455] Make docker build work in test
---
setup.py | 5 ++++-
tests/mock_vws/test_docker.py | 9 +++++++--
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/setup.py b/setup.py
index 5806738ff..3a5f06aa9 100644
--- a/setup.py
+++ b/setup.py
@@ -31,7 +31,10 @@ def _get_dependencies(requirements_file: Path) -> List[str]:
)
setup(
- use_scm_version=True,
+ # We use a dictionary with a fallback version rather than "True"
+ # like https://github.com/pypa/setuptools_scm/issues/77 so that we do not
+ # error in Docker.
+ use_scm_version={'fallback_version': 'FALLBACK_VERSION'},
setup_requires=SETUP_REQUIRES,
install_requires=INSTALL_REQUIRES,
extras_require={'dev': DEV_REQUIRES},
diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py
index 844c4d2ce..d24b9399e 100644
--- a/tests/mock_vws/test_docker.py
+++ b/tests/mock_vws/test_docker.py
@@ -3,12 +3,17 @@
"""
import docker
+from pathlib import Path
def test_build_and_run():
+ repository_root = Path(__file__).parent.parent.parent
client = docker.from_env()
# Build containers
- dockerfile_path = ...
- image, build_logs = client.images.build(path=dockerfile_path)
+ dockerfile_path = repository_root / 'src/mock_vws/_flask_server/Dockerfile'
+ image, build_logs = client.images.build(
+ path=str(repository_root),
+ dockerfile=str(dockerfile_path),
+ )
container = client.containers.run(image=image, detach=True)
# Run containers
From 6df5243a805c6957020afb8ca01d436c999de851 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 25 Sep 2020 09:46:25 +0100
Subject: [PATCH 0291/3455] Start of having separate Dockerfile for each
application
---
src/mock_vws/_flask_server/Dockerfile-base | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 src/mock_vws/_flask_server/Dockerfile-base
diff --git a/src/mock_vws/_flask_server/Dockerfile-base b/src/mock_vws/_flask_server/Dockerfile-base
new file mode 100644
index 000000000..1f1b8f547
--- /dev/null
+++ b/src/mock_vws/_flask_server/Dockerfile-base
@@ -0,0 +1,5 @@
+FROM python:3.8-slim-buster
+COPY . /app
+WORKDIR /app
+RUN pip install .
+ENTRYPOINT ["python"]
From c7eb4dc4155b1ea0dc47a73d77a64dfe2299630a Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 25 Sep 2020 09:47:19 +0100
Subject: [PATCH 0292/3455] Stop pinning requirements in order to satisfy new
pip resolver
---
requirements.txt | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index 4d13eefa0..550abd132 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
-Pillow==7.2.0
-VWS-Auth-Tools==2020.5.31.0
-backports.zoneinfo==0.2.1
-flask==1.1.2
-requests-mock==1.8.0
-requests==2.24.0
-wrapt==1.12.1
+Pillow
+VWS-Auth-Tools
+backports.zoneinfo
+flask
+requests-mock
+requests
+wrapt
From 40993d0196d22555675b7275e1cf1e4ebf7c8121 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 25 Sep 2020 10:30:03 +0100
Subject: [PATCH 0293/3455] Update for release 2020.09.25.0
---
CHANGELOG.rst | 3 +++
1 file changed, 3 insertions(+)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 9e529ec76..5de0dd158 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,6 +6,9 @@ Changelog
Next
----
+2020.09.25.0
+------------
+
2019.12.27.0
------------
From 993c81a2cfa393a0fab604941e7e385247a09a55 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 25 Sep 2020 11:59:27 +0100
Subject: [PATCH 0294/3455] Progress towards Docker images for each mock
service
---
src/mock_vws/_flask_server/Dockerfile | 5 ----
src/mock_vws/_flask_server/Dockerfile-storage | 0
src/mock_vws/_flask_server/Dockerfile-vwq | 0
src/mock_vws/_flask_server/Dockerfile-vws | 0
tests/mock_vws/test_docker.py | 23 +++++++++++++++----
5 files changed, 19 insertions(+), 9 deletions(-)
delete mode 100644 src/mock_vws/_flask_server/Dockerfile
create mode 100644 src/mock_vws/_flask_server/Dockerfile-storage
create mode 100644 src/mock_vws/_flask_server/Dockerfile-vwq
create mode 100644 src/mock_vws/_flask_server/Dockerfile-vws
diff --git a/src/mock_vws/_flask_server/Dockerfile b/src/mock_vws/_flask_server/Dockerfile
deleted file mode 100644
index 1f1b8f547..000000000
--- a/src/mock_vws/_flask_server/Dockerfile
+++ /dev/null
@@ -1,5 +0,0 @@
-FROM python:3.8-slim-buster
-COPY . /app
-WORKDIR /app
-RUN pip install .
-ENTRYPOINT ["python"]
diff --git a/src/mock_vws/_flask_server/Dockerfile-storage b/src/mock_vws/_flask_server/Dockerfile-storage
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/mock_vws/_flask_server/Dockerfile-vwq b/src/mock_vws/_flask_server/Dockerfile-vwq
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/mock_vws/_flask_server/Dockerfile-vws b/src/mock_vws/_flask_server/Dockerfile-vws
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py
index d24b9399e..b552ffcb9 100644
--- a/tests/mock_vws/test_docker.py
+++ b/tests/mock_vws/test_docker.py
@@ -9,12 +9,27 @@ def test_build_and_run():
repository_root = Path(__file__).parent.parent.parent
client = docker.from_env()
# Build containers
- dockerfile_path = repository_root / 'src/mock_vws/_flask_server/Dockerfile'
- image, build_logs = client.images.build(
+ dockerfile_dir = repository_root / 'src/mock_vws/_flask_server/Dockerfile'
+
+ base_dockerfile = dockerfile_dir / 'Dockerfile-base'
+ storage_dockerfile = dockerfile_dir / 'Dockerfile-storage'
+ vws_dockerfile = dockerfile_dir / 'Dockerfile-vws'
+ vwq_dockerfile = dockerfile_dir / 'Dockerfile-vwq'
+ storage_image, build_logs = client.images.build(
+ path=str(repository_root),
+ dockerfile=str(storage_dockerfile),
+ )
+ vws_image, build_logs = client.images.build(
+ path=str(repository_root),
+ dockerfile=str(vws_dockerfile),
+ )
+ vwq_image, build_logs = client.images.build(
path=str(repository_root),
- dockerfile=str(dockerfile_path),
+ dockerfile=str(vwq_dockerfile),
)
- container = client.containers.run(image=image, detach=True)
+ storage_container = client.containers.run(image=storage_image, detach=True)
+ vws_container = client.containers.run(image=vws_image, detach=True)
+ vwq_container = client.containers.run(image=vwq_image, detach=True)
# Run containers
# Add target using vws_python
From 9c4eaee1eb3d694fba4599dea2e9fab142ed909f Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Fri, 25 Sep 2020 16:58:29 +0100
Subject: [PATCH 0295/3455] Get test to the point of running a storage
container
---
src/mock_vws/_flask_server/Dockerfile-vws | 0
.../base/Dockerfile} | 0
.../dockerfiles/storage/Dockerfile | 2 +
.../vwq/Dockerfile} | 0
.../vws/Dockerfile} | 0
tests/mock_vws/test_docker.py | 38 +++++++++++--------
6 files changed, 25 insertions(+), 15 deletions(-)
delete mode 100644 src/mock_vws/_flask_server/Dockerfile-vws
rename src/mock_vws/_flask_server/{Dockerfile-base => dockerfiles/base/Dockerfile} (100%)
create mode 100644 src/mock_vws/_flask_server/dockerfiles/storage/Dockerfile
rename src/mock_vws/_flask_server/{Dockerfile-storage => dockerfiles/vwq/Dockerfile} (100%)
rename src/mock_vws/_flask_server/{Dockerfile-vwq => dockerfiles/vws/Dockerfile} (100%)
diff --git a/src/mock_vws/_flask_server/Dockerfile-vws b/src/mock_vws/_flask_server/Dockerfile-vws
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/mock_vws/_flask_server/Dockerfile-base b/src/mock_vws/_flask_server/dockerfiles/base/Dockerfile
similarity index 100%
rename from src/mock_vws/_flask_server/Dockerfile-base
rename to src/mock_vws/_flask_server/dockerfiles/base/Dockerfile
diff --git a/src/mock_vws/_flask_server/dockerfiles/storage/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/storage/Dockerfile
new file mode 100644
index 000000000..7cc4bffac
--- /dev/null
+++ b/src/mock_vws/_flask_server/dockerfiles/storage/Dockerfile
@@ -0,0 +1,2 @@
+FROM vws-mock:base
+CMD ["src/mock_vws/_flask_server/storage.py"]
diff --git a/src/mock_vws/_flask_server/Dockerfile-storage b/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile
similarity index 100%
rename from src/mock_vws/_flask_server/Dockerfile-storage
rename to src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile
diff --git a/src/mock_vws/_flask_server/Dockerfile-vwq b/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile
similarity index 100%
rename from src/mock_vws/_flask_server/Dockerfile-vwq
rename to src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile
diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py
index b552ffcb9..0442d8d3c 100644
--- a/tests/mock_vws/test_docker.py
+++ b/tests/mock_vws/test_docker.py
@@ -9,27 +9,35 @@ def test_build_and_run():
repository_root = Path(__file__).parent.parent.parent
client = docker.from_env()
# Build containers
- dockerfile_dir = repository_root / 'src/mock_vws/_flask_server/Dockerfile'
+ dockerfile_dir = repository_root / 'src/mock_vws/_flask_server/dockerfiles'
- base_dockerfile = dockerfile_dir / 'Dockerfile-base'
- storage_dockerfile = dockerfile_dir / 'Dockerfile-storage'
- vws_dockerfile = dockerfile_dir / 'Dockerfile-vws'
- vwq_dockerfile = dockerfile_dir / 'Dockerfile-vwq'
- storage_image, build_logs = client.images.build(
- path=str(repository_root),
- dockerfile=str(storage_dockerfile),
- )
- vws_image, build_logs = client.images.build(
+ base_dockerfile = dockerfile_dir / 'base/Dockerfile'
+ storage_dockerfile = dockerfile_dir / 'storage/Dockerfile'
+
+ base_tag = 'vws-mock:base'
+ base_image, build_logs = client.images.build(
path=str(repository_root),
- dockerfile=str(vws_dockerfile),
+ dockerfile=str(base_dockerfile),
+ tag=base_tag,
)
- vwq_image, build_logs = client.images.build(
+
+ # vws_dockerfile = dockerfile_dir / 'Dockerfile-vws'
+ # vwq_dockerfile = dockerfile_dir / 'Dockerfile-vwq'
+ storage_image, build_logs = client.images.build(
path=str(repository_root),
- dockerfile=str(vwq_dockerfile),
+ dockerfile=str(storage_dockerfile),
)
+ # vws_image, build_logs = client.images.build(
+ # path=str(repository_root),
+ # dockerfile=str(vws_dockerfile),
+ # )
+ # vwq_image, build_logs = client.images.build(
+ # path=str(repository_root),
+ # dockerfile=str(vwq_dockerfile),
+ # )
storage_container = client.containers.run(image=storage_image, detach=True)
- vws_container = client.containers.run(image=vws_image, detach=True)
- vwq_container = client.containers.run(image=vwq_image, detach=True)
+ # vws_container = client.containers.run(image=vws_image, detach=True)
+ # vwq_container = client.containers.run(image=vwq_image, detach=True)
# Run containers
# Add target using vws_python
From 821375bfda3d7d55152fbe2e20c6f897742080b7 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 26 Sep 2020 10:17:46 +0100
Subject: [PATCH 0296/3455] Reload to show ports
---
src/mock_vws/_flask_server/dockerfiles/base/Dockerfile | 1 +
tests/mock_vws/test_docker.py | 9 ++++++++-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/mock_vws/_flask_server/dockerfiles/base/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/base/Dockerfile
index 1f1b8f547..e87ff817b 100644
--- a/src/mock_vws/_flask_server/dockerfiles/base/Dockerfile
+++ b/src/mock_vws/_flask_server/dockerfiles/base/Dockerfile
@@ -2,4 +2,5 @@ FROM python:3.8-slim-buster
COPY . /app
WORKDIR /app
RUN pip install .
+EXPOSE 5000
ENTRYPOINT ["python"]
diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py
index 0442d8d3c..a9811cce8 100644
--- a/tests/mock_vws/test_docker.py
+++ b/tests/mock_vws/test_docker.py
@@ -35,7 +35,14 @@ def test_build_and_run():
# path=str(repository_root),
# dockerfile=str(vwq_dockerfile),
# )
- storage_container = client.containers.run(image=storage_image, detach=True)
+ storage_container = client.containers.run(
+ image=storage_image,
+ detach=True,
+ publish_all_ports=True,
+ # ports={'5000/tcp': None},
+ )
+ storage_container.reload()
+ import pdb; pdb.set_trace()
# vws_container = client.containers.run(image=vws_image, detach=True)
# vwq_container = client.containers.run(image=vwq_image, detach=True)
# Run containers
From 9c55be7f46f0c526e5b5fb7f92a9623a22441021 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 26 Sep 2020 10:41:21 +0100
Subject: [PATCH 0297/3455] Progress towards Docker images for each mock
service
---
.../_flask_server/dockerfiles/vwq/Dockerfile | 2 ++
.../_flask_server/dockerfiles/vws/Dockerfile | 2 ++
tests/mock_vws/test_docker.py | 29 +++++++++----------
3 files changed, 18 insertions(+), 15 deletions(-)
diff --git a/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile
index e69de29bb..4d2d3f495 100644
--- a/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile
+++ b/src/mock_vws/_flask_server/dockerfiles/vwq/Dockerfile
@@ -0,0 +1,2 @@
+FROM vws-mock:base
+CMD ["src/mock_vws/_flask_server/vwq.py"]
diff --git a/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile b/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile
index e69de29bb..2ec8085f4 100644
--- a/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile
+++ b/src/mock_vws/_flask_server/dockerfiles/vws/Dockerfile
@@ -0,0 +1,2 @@
+FROM vws-mock:base
+CMD ["src/mock_vws/_flask_server/vws.py"]
diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py
index a9811cce8..4c8bb7cfb 100644
--- a/tests/mock_vws/test_docker.py
+++ b/tests/mock_vws/test_docker.py
@@ -4,15 +4,17 @@
import docker
from pathlib import Path
+import requests
def test_build_and_run():
repository_root = Path(__file__).parent.parent.parent
client = docker.from_env()
- # Build containers
- dockerfile_dir = repository_root / 'src/mock_vws/_flask_server/dockerfiles'
- base_dockerfile = dockerfile_dir / 'base/Dockerfile'
- storage_dockerfile = dockerfile_dir / 'storage/Dockerfile'
+ dockerfile_dir = repository_root / 'src/mock_vws/_flask_server/dockerfiles'
+ base_dockerfile = dockerfile_dir / 'base' / 'Dockerfile'
+ storage_dockerfile = dockerfile_dir / 'storage' / 'Dockerfile'
+ vws_dockerfile = dockerfile_dir / 'vws' / 'Dockerfile'
+ vwq_dockerfile = dockerfile_dir / 'vwq' / 'Dockerfile'
base_tag = 'vws-mock:base'
base_image, build_logs = client.images.build(
@@ -21,25 +23,22 @@ def test_build_and_run():
tag=base_tag,
)
- # vws_dockerfile = dockerfile_dir / 'Dockerfile-vws'
- # vwq_dockerfile = dockerfile_dir / 'Dockerfile-vwq'
storage_image, build_logs = client.images.build(
path=str(repository_root),
dockerfile=str(storage_dockerfile),
)
- # vws_image, build_logs = client.images.build(
- # path=str(repository_root),
- # dockerfile=str(vws_dockerfile),
- # )
- # vwq_image, build_logs = client.images.build(
- # path=str(repository_root),
- # dockerfile=str(vwq_dockerfile),
- # )
+ vws_image, build_logs = client.images.build(
+ path=str(repository_root),
+ dockerfile=str(vws_dockerfile),
+ )
+ vwq_image, build_logs = client.images.build(
+ path=str(repository_root),
+ dockerfile=str(vwq_dockerfile),
+ )
storage_container = client.containers.run(
image=storage_image,
detach=True,
publish_all_ports=True,
- # ports={'5000/tcp': None},
)
storage_container.reload()
import pdb; pdb.set_trace()
From d4e9e4f57dc883b52c46c9a43a7731375f7f7d13 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 26 Sep 2020 10:43:14 +0100
Subject: [PATCH 0298/3455] Progress towards Docker images for each mock
service
---
docs/source/docker.rst | 24 +-----------------------
1 file changed, 1 insertion(+), 23 deletions(-)
diff --git a/docs/source/docker.rst b/docs/source/docker.rst
index 5af3b5685..63475a8a7 100644
--- a/docs/source/docker.rst
+++ b/docs/source/docker.rst
@@ -6,34 +6,12 @@ Running the mock
# TODO Get a mock running with instructions here.
-From source
-^^^^^^^^^^^
-
-.. code:: sh
-
- docker build \
- --file src/mock_vws/_flask_server/Dockerfile \
- --tag vws-mock \
- .
-
- docker build \
- --file src/mock_vws/_flask_server/Dockerfile \
- --tag vws-storage \
- src/mock_vws/_flask_server/vws
-
- docker build \
- --file src/mock_vws/_flask_server/Dockerfile \
- --tag vws-storage \
- src/mock_vws/_flask_server/vwq
-
From pre-built containers
^^^^^^^^^^^^^^^^^^^^^^^^^
.. code:: sh
- docker run vws-mock \
- --entrypoint src/mock_vws/_flask_server/vws/__init__.py
- -e
+ docker run --publish-all vws-mock-storage
docker run vws-mock \
-e STORAGE_BACKEND=... \
-e QUERY_PROCESSES_DELETION_SECONDS=...
From a0cebcf34c60f0c9afeffd3a6a90d1f3add4d539 Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 26 Sep 2020 11:13:32 +0100
Subject: [PATCH 0299/3455] Progress towards Docker images for each mock
service
---
src/mock_vws/_flask_server/vwq.py | 2 +-
src/mock_vws/_flask_server/vws.py | 3 ++-
tests/mock_vws/test_docker.py | 33 ++++++++++++++++++++++++++++---
3 files changed, 33 insertions(+), 5 deletions(-)
diff --git a/src/mock_vws/_flask_server/vwq.py b/src/mock_vws/_flask_server/vwq.py
index d8aaeb282..14be67846 100644
--- a/src/mock_vws/_flask_server/vwq.py
+++ b/src/mock_vws/_flask_server/vwq.py
@@ -29,7 +29,7 @@
CLOUDRECO_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
# TODO choose something for this - it should actually work in a docker-compose
# scenario.
-STORAGE_BASE_URL: Final[str] = 'http://todo.com'
+STORAGE_BASE_URL: Final[str] = 'http://vws-mock-storage:5000'
def get_all_databases() -> Set[VuforiaDatabase]:
diff --git a/src/mock_vws/_flask_server/vws.py b/src/mock_vws/_flask_server/vws.py
index 0832ed1c3..5954326e2 100644
--- a/src/mock_vws/_flask_server/vws.py
+++ b/src/mock_vws/_flask_server/vws.py
@@ -32,7 +32,8 @@
VWS_FLASK_APP.config['PROPAGATE_EXCEPTIONS'] = True
# TODO choose something for this - it should actually work in a docker-compose
# scenario.
-STORAGE_BASE_URL: Final[str] = 'http://todo.com'
+# TODO maybe set with env var
+STORAGE_BASE_URL: Final[str] = 'http://vws-mock-storage:5000'
def get_all_databases() -> Set[VuforiaDatabase]:
diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py
index 4c8bb7cfb..52410a7fb 100644
--- a/tests/mock_vws/test_docker.py
+++ b/tests/mock_vws/test_docker.py
@@ -5,6 +5,8 @@
import docker
from pathlib import Path
import requests
+from vws import VWS, CloudRecoService
+from mock_vws.database import VuforiaDatabase
def test_build_and_run():
repository_root = Path(__file__).parent.parent.parent
@@ -35,16 +37,41 @@ def test_build_and_run():
path=str(repository_root),
dockerfile=str(vwq_dockerfile),
)
+
+ database = VuforiaDatabase()
+ storage_container_name = 'vws-mock-storage'
+ storage_exposed_port = '5000'
+
storage_container = client.containers.run(
image=storage_image,
detach=True,
publish_all_ports=True,
+ name=storage_container_name,
)
storage_container.reload()
+ vws_container = client.containers.run(
+ image=vws_image,
+ detach=True,
+ )
+ vwq_container = client.containers.run(
+ image=vwq_image,
+ detach=True,
+ )
+
import pdb; pdb.set_trace()
- # vws_container = client.containers.run(image=vws_image, detach=True)
- # vwq_container = client.containers.run(image=vwq_image, detach=True)
- # Run containers
+ # Add database to storage
# Add target using vws_python
+ vws_client = VWS(
+ server_access_key=database.server_access_key,
+ server_secret_key=database.server_secret_key,
+ )
+
+ # Query for target
+ cloud_reco_client = CloudRecoService(
+ client_access_key=database.client_access_key,
+ client_secret_key=database.client_secret_key,
+ )
+
+ # Clean up containers
pass
From 95d9cd9150b78593ef6c287b4d63ad97eed96ddf Mon Sep 17 00:00:00 2001
From: Adam Dangoor
Date: Sat, 26 Sep 2020 11:25:01 +0100
Subject: [PATCH 0300/3455] Progress towards Docker images for each mock
service
---
tests/mock_vws/test_docker.py | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py
index 52410a7fb..f839322ab 100644
--- a/tests/mock_vws/test_docker.py
+++ b/tests/mock_vws/test_docker.py
@@ -45,10 +45,8 @@ def test_build_and_run():
storage_container = client.containers.run(
image=storage_image,
detach=True,
- publish_all_ports=True,
name=storage_container_name,
)
- storage_container.reload()
vws_container = client.containers.run(
image=vws_image,
detach=True,
@@ -58,6 +56,18 @@ def test_build_and_run():
detach=True,
)
+ add_database_cmd = [
+ 'curl',
+ '--request',
+ 'POST',
+ '--header',
+ '"Content-Type: application/json"',
+ '--data',
+ json.dumps(database.to_dict()),
+ f'127.0.0.1:{storage_exposed_port}',
+ ]
+ exit_code, output = storage_container.exec_run(cmd=add_database_cmd)
+
import pdb; pdb.set_trace()
# Add database to storage
From a92f4c09d724812fc946f3db1387d603d0afaf8b Mon Sep 17 00:00:00 2001
From: Adam Dangoor