From bbacdcbe7f00b254d9cd4e04db0d79c400ef8666 Mon Sep 17 00:00:00 2001 From: bssyousefi Date: Fri, 12 Jul 2024 10:40:20 -0400 Subject: [PATCH 1/7] add query strings to docs/user/tutorial --- docs/user/tutorial.rst | 195 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 192 insertions(+), 3 deletions(-) diff --git a/docs/user/tutorial.rst b/docs/user/tutorial.rst index 70b132e8d..a277e732c 100644 --- a/docs/user/tutorial.rst +++ b/docs/user/tutorial.rst @@ -1295,10 +1295,199 @@ Inspecting the application now returns: ⇒ /images/{name} - Item: └── GET - on_get -.. Query Strings -.. ------------- +Query Strings +------------- +Now that we are able to get the images from the service, we need a way to get a list available images. We have already set up this route. Before testing this route let's change its output format back to JSON to have a more terminal-friendly output. The top of file ``images.py`` should look like this: + +.. code:: python + + import io + import os + import re + import uuid + import mimetypes + + import falcon + import json + + + class Collection: + + def __init__(self, image_store): + self._image_store = image_store + + def on_get(self, req, resp): + max_size = int(req.params.get("maxsize", 0)) + images = self._image_store.list(max_size) + doc = { + 'images': [ + {'href': '/images/'+image} for image in images + ] + } + + resp.text = json.dumps(doc, ensure_ascii=False) + resp.status = falcon.HTTP_200 + + def on_post(self, req, resp): + name = self._image_store.save(req.stream, req.content_type) + resp.status = falcon.HTTP_201 + resp.location = '/images/' + name + + +Now try the following: + +.. code:: bash + + http localhost:8000/images + +In response you should get the following data that we statically have put in the code. + +.. code:: + + { + "images": [ + { + "href": "/images/1eaf6ef1-7f2d-4ecc-a8d5-6e8adba7cc0e.png" + } + ] + } + +Let's go back to the ``on_get`` method and create a dynamic response. We can use query strings to set maximum image size and get the list of all images smaller than the specified value. + +.. code:: python + + import io + import os + import re + import uuid + import mimetypes + + import falcon + import json + + + class Collection: + + def __init__(self, image_store): + self._image_store = image_store + + def on_get(self, req, resp): + max_size = int(req.params.get("maxsize", 0)) + images = self._image_store.list(max_size) + doc = { + 'images': [ + {'href': '/images/'+image} for image in images + ] + } + + resp.text = json.dumps(doc, ensure_ascii=False) + resp.status = falcon.HTTP_200 + + def on_post(self, req, resp): + name = self._image_store.save(req.stream, req.content_type) + resp.status = falcon.HTTP_201 + resp.location = '/images/' + name + + + class Item: + + def __init__(self, image_store): + self._image_store = image_store + + def on_get(self, req, resp, name): + resp.content_type = mimetypes.guess_type(name)[0] + resp.stream, resp.content_length = self._image_store.open(name) + + + class ImageStore: + + _CHUNK_SIZE_BYTES = 4096 + _IMAGE_NAME_PATTERN = re.compile( + '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[a-z]{2,4}$' + ) + + def __init__(self, storage_path, uuidgen=uuid.uuid4, fopen=io.open): + self._storage_path = storage_path + self._uuidgen = uuidgen + self._fopen = fopen + + def save(self, image_stream, image_content_type): + ext = mimetypes.guess_extension(image_content_type) + name = '{uuid}{ext}'.format(uuid=self._uuidgen(), ext=ext) + image_path = os.path.join(self._storage_path, name) + + with self._fopen(image_path, 'wb') as image_file: + while True: + chunk = image_stream.read(self._CHUNK_SIZE_BYTES) + if not chunk: + break + + image_file.write(chunk) + + return name + + def open(self, name): + # Always validate untrusted input! + if not self._IMAGE_NAME_PATTERN.match(name): + raise IOError('File not found') + + image_path = os.path.join(self._storage_path, name) + stream = self._fopen(image_path, 'rb') + content_length = os.path.getsize(image_path) + + return stream, content_length + + def list(self, max_size=0): + images = [ + image for image in os.listdir(self._storage_path) + if self._IMAGE_NAME_PATTERN.match(image) + and ( + max_size == 0 + or os.path.getsize(os.path.join(self._storage_path, image)) <= max_size + ) + ] + return images + +As you can see the method ``list`` has been added to ``ImageStore`` in order to return list of available images smaller than ``max_size`` unless it is not zero, in which case it will behave like there was no predicament of image size. +Let's try to save some binary data as images in the service and then try to retrieve their list. Execute the following commands in order to make 3 files as images with different sizes. + +.. code:: bash + + echo "First Case" > 1.png + echo "Second Case" > 2.png + echo "3rd Case" > 3.png + +Now we need to store these files using ``POST`` request: + +.. code:: bash + + http POST localhost:8000/images Content-Type:image/png < 1.png + http POST localhost:8000/images Content-Type:image/png < 2.png + http POST localhost:8000/images Content-Type:image/png < 3.png + +If we check the size of these files, we will see that they are 11, 12, 9 bytes respectively. Let's try to get the list of the images which are smaller or equal to 11 bytes. + +.. code:: bash + + http localhost:8000/images?maxsize=11 + +We expect to get a list of 2 files, which will be similar to the following: + +.. code:: + + { + "images": [ + { + "href": "/images/7ba2ebc9-726f-46b0-9615-a69824f5089b.png" + }, + { + "href": "/images/e4354a31-2161-4064-805c-3bc7c332e7e6.png" + } + ] + } + +You could also now validate the response with getting the image files using the ``href`` value in the response and compare them with the original files. -.. *Coming soon...* Introducing Hooks ----------------- From 3be386a6f11668d2a1c2e1e331b5995d5c95cff1 Mon Sep 17 00:00:00 2001 From: bssyousefi Date: Sat, 13 Jul 2024 19:00:30 -0400 Subject: [PATCH 2/7] fix some parts in the docs --- docs/user/tutorial.rst | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/docs/user/tutorial.rst b/docs/user/tutorial.rst index a277e732c..008df7dbd 100644 --- a/docs/user/tutorial.rst +++ b/docs/user/tutorial.rst @@ -1317,16 +1317,18 @@ Now that we are able to get the images from the service, we need a way to get a self._image_store = image_store def on_get(self, req, resp): - max_size = int(req.params.get("maxsize", 0)) - images = self._image_store.list(max_size) + # TODO: Modify this to return a list of href's based on + # what images are actually available. doc = { 'images': [ - {'href': '/images/'+image} for image in images - ] - } + { + 'href': '/images/1eaf6ef1-7f2d-4ecc-a8d5-6e8adba7cc0e.png' + } + ] + } - resp.text = json.dumps(doc, ensure_ascii=False) - resp.status = falcon.HTTP_200 + resp.text = json.dumps(doc, ensure_ascii=False) + resp.status = falcon.HTTP_200 def on_post(self, req, resp): name = self._image_store.save(req.stream, req.content_type) @@ -1352,7 +1354,7 @@ In response you should get the following data that we statically have put in the ] } -Let's go back to the ``on_get`` method and create a dynamic response. We can use query strings to set maximum image size and get the list of all images smaller than the specified value. +Let's go back to the ``on_get`` method and create a dynamic response. We can use query strings to set maximum image size and get the list of all images smaller than the specified value. We will use method ``get_param_as_int`` to set a default value of ``-1`` in case no ``maxsize`` query string was provided and also to enable a minimum value validation. .. code:: python @@ -1372,11 +1374,11 @@ Let's go back to the ``on_get`` method and create a dynamic response. We can use self._image_store = image_store def on_get(self, req, resp): - max_size = int(req.params.get("maxsize", 0)) + max_size = req.get_param_as_int("maxsize", min_value=1, default=-1)) images = self._image_store.list(max_size) doc = { 'images': [ - {'href': '/images/'+image} for image in images + {'href': '/images/' + image} for image in images ] } @@ -1437,33 +1439,33 @@ Let's go back to the ``on_get`` method and create a dynamic response. We can use return stream, content_length - def list(self, max_size=0): + def list(self, max_size): images = [ image for image in os.listdir(self._storage_path) if self._IMAGE_NAME_PATTERN.match(image) and ( - max_size == 0 + max_size == -1 or os.path.getsize(os.path.join(self._storage_path, image)) <= max_size ) ] return images -As you can see the method ``list`` has been added to ``ImageStore`` in order to return list of available images smaller than ``max_size`` unless it is not zero, in which case it will behave like there was no predicament of image size. -Let's try to save some binary data as images in the service and then try to retrieve their list. Execute the following commands in order to make 3 files as images with different sizes. +As you can see the method ``list`` has been added to ``ImageStore`` in order to return list of available images smaller than ``max_size`` unless it is not ``-1``, in which case it will behave like there was no predicament of image size. +Let's try to save some binary data as images in the service and then try to retrieve their list. Execute the following commands in order to simulate the creation of 3 files as images with different sizes. While these are not valid PNG files, they will work for this tutorial. .. code:: bash - echo "First Case" > 1.png - echo "Second Case" > 2.png - echo "3rd Case" > 3.png + echo "First Case" > pseudo-image-1.png + echo "Second Case" > pseudo-image-2.png + echo "3rd Case" > pseudo-image-3.png Now we need to store these files using ``POST`` request: .. code:: bash - http POST localhost:8000/images Content-Type:image/png < 1.png - http POST localhost:8000/images Content-Type:image/png < 2.png - http POST localhost:8000/images Content-Type:image/png < 3.png + http POST localhost:8000/images Content-Type:image/png < pseudo-image-1.png + http POST localhost:8000/images Content-Type:image/png < pseudo-image-2.png + http POST localhost:8000/images Content-Type:image/png < pseudo-image-3.png If we check the size of these files, we will see that they are 11, 12, 9 bytes respectively. Let's try to get the list of the images which are smaller or equal to 11 bytes. From d599b1e9aba36c679065129c76b5bbcb78124b9f Mon Sep 17 00:00:00 2001 From: bssyousefi Date: Sun, 25 Aug 2024 23:28:03 -0400 Subject: [PATCH 3/7] shorten rst lines to around 80 --- docs/user/tutorial.rst | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/user/tutorial.rst b/docs/user/tutorial.rst index 089b67be5..2df08cebf 100644 --- a/docs/user/tutorial.rst +++ b/docs/user/tutorial.rst @@ -1306,7 +1306,10 @@ Inspecting the application now returns: Query Strings ------------- -Now that we are able to get the images from the service, we need a way to get a list available images. We have already set up this route. Before testing this route let's change its output format back to JSON to have a more terminal-friendly output. The top of file ``images.py`` should look like this: +Now that we are able to get the images from the service, we need a way to get +a list available images. We have already set up this route. Before testing this +route let's change its output format back to JSON to have a more +terminal-friendly output. The top of file ``images.py`` should look like this: .. code:: python @@ -1363,7 +1366,11 @@ In response you should get the following data that we statically have put in the ] } -Let's go back to the ``on_get`` method and create a dynamic response. We can use query strings to set maximum image size and get the list of all images smaller than the specified value. We will use method ``get_param_as_int`` to set a default value of ``-1`` in case no ``maxsize`` query string was provided and also to enable a minimum value validation. +Let's go back to the ``on_get`` method and create a dynamic response. We can +use query strings to set maximum image size and get the list of all images +smaller than the specified value. We will use method ``get_param_as_int`` to +set a default value of ``-1`` in case no ``maxsize`` query string was provided +and also to enable a minimum value validation. .. code:: python @@ -1459,8 +1466,13 @@ Let's go back to the ``on_get`` method and create a dynamic response. We can use ] return images -As you can see the method ``list`` has been added to ``ImageStore`` in order to return list of available images smaller than ``max_size`` unless it is not ``-1``, in which case it will behave like there was no predicament of image size. -Let's try to save some binary data as images in the service and then try to retrieve their list. Execute the following commands in order to simulate the creation of 3 files as images with different sizes. While these are not valid PNG files, they will work for this tutorial. +As you can see the method ``list`` has been added to ``ImageStore`` in order +to return list of available images smaller than ``max_size`` unless it is not +``-1``, in which case it will behave like there was no predicament of image size. +Let's try to save some binary data as images in the service and then try to +retrieve their list. Execute the following commands in order to simulate the +creation of 3 files as images with different sizes. While these are not valid +PNG files, they will work for this tutorial. .. code:: bash @@ -1476,7 +1488,9 @@ Now we need to store these files using ``POST`` request: http POST localhost:8000/images Content-Type:image/png < pseudo-image-2.png http POST localhost:8000/images Content-Type:image/png < pseudo-image-3.png -If we check the size of these files, we will see that they are 11, 12, 9 bytes respectively. Let's try to get the list of the images which are smaller or equal to 11 bytes. +If we check the size of these files, we will see that they are 11, 12, 9 bytes +respectively. Let's try to get the list of the images which are smaller or +equal to 11 bytes. .. code:: bash @@ -1497,7 +1511,8 @@ We expect to get a list of 2 files, which will be similar to the following: ] } -You could also now validate the response with getting the image files using the ``href`` value in the response and compare them with the original files. +You could also now validate the response with getting the image files using +the ``href`` value in the response and compare them with the original files. Introducing Hooks From 636ac6cb31f5d76bb37aef37bf87be22444bbda5 Mon Sep 17 00:00:00 2001 From: bssyousefi Date: Sun, 1 Sep 2024 12:25:41 -0400 Subject: [PATCH 4/7] update examples/look and tests --- docs/user/tutorial.rst | 8 ++-- examples/look/look/app.py | 8 ++-- examples/look/look/images.py | 52 ++++++++++++++++++---- examples/look/tests/test_app.py | 77 ++++++++++++++++++++++++++++----- 4 files changed, 118 insertions(+), 27 deletions(-) diff --git a/docs/user/tutorial.rst b/docs/user/tutorial.rst index 2df08cebf..9e483d3ed 100644 --- a/docs/user/tutorial.rst +++ b/docs/user/tutorial.rst @@ -1174,7 +1174,7 @@ Go ahead and edit your ``images.py`` file to look something like this: _CHUNK_SIZE_BYTES = 4096 _IMAGE_NAME_PATTERN = re.compile( - '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[a-z]{2,4}$' + r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[a-z]{2,4}$' ) def __init__(self, storage_path, uuidgen=uuid.uuid4, fopen=io.open): @@ -1307,7 +1307,7 @@ Inspecting the application now returns: Query Strings ------------- Now that we are able to get the images from the service, we need a way to get -a list available images. We have already set up this route. Before testing this +a list of available images. We have already set up this route. Before testing this route let's change its output format back to JSON to have a more terminal-friendly output. The top of file ``images.py`` should look like this: @@ -1390,7 +1390,7 @@ and also to enable a minimum value validation. self._image_store = image_store def on_get(self, req, resp): - max_size = req.get_param_as_int("maxsize", min_value=1, default=-1)) + max_size = req.get_param_as_int("maxsize", min_value=1, default=-1) images = self._image_store.list(max_size) doc = { 'images': [ @@ -1421,7 +1421,7 @@ and also to enable a minimum value validation. _CHUNK_SIZE_BYTES = 4096 _IMAGE_NAME_PATTERN = re.compile( - '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[a-z]{2,4}$' + r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[a-z]{2,4}$' ) def __init__(self, storage_path, uuidgen=uuid.uuid4, fopen=io.open): diff --git a/examples/look/look/app.py b/examples/look/look/app.py index abce808bd..c395c4446 100644 --- a/examples/look/look/app.py +++ b/examples/look/look/app.py @@ -2,14 +2,13 @@ import falcon -from .images import ImageStore -from .images import Resource +from .images import Collection, ImageStore, Item def create_app(image_store): - image_resource = Resource(image_store) app = falcon.App() - app.add_route('/images', image_resource) + app.add_route('/images', Collection(image_store)) + app.add_route('/images/{name}', Item(image_store)) return app @@ -17,3 +16,4 @@ def get_app(): storage_path = os.environ.get('LOOK_STORAGE_PATH', '.') image_store = ImageStore(storage_path) return create_app(image_store) + diff --git a/examples/look/look/images.py b/examples/look/look/images.py index 31466d93c..8d099d99f 100644 --- a/examples/look/look/images.py +++ b/examples/look/look/images.py @@ -1,28 +1,28 @@ import io import mimetypes import os +import re import uuid -import msgpack +import json import falcon -class Resource: +class Collection: def __init__(self, image_store): self._image_store = image_store def on_get(self, req, resp): + max_size = req.get_param_as_int("maxsize", min_value=1, default=-1) + images = self._image_store.list(max_size) doc = { 'images': [ - { - 'href': '/images/1eaf6ef1-7f2d-4ecc-a8d5-6e8adba7cc0e.png', - }, - ], + {'href': '/images/' + image} for image in images + ] } - resp.data = msgpack.packb(doc, use_bin_type=True) - resp.content_type = 'application/msgpack' + resp.text = json.dumps(doc, ensure_ascii=False) resp.status = falcon.HTTP_200 def on_post(self, req, resp): @@ -31,8 +31,21 @@ def on_post(self, req, resp): resp.location = '/images/' + name +class Item: + + def __init__(self, image_store): + self._image_store = image_store + + def on_get(self, req, resp, name): + resp.content_type = mimetypes.guess_type(name)[0] + resp.stream, resp.content_length = self._image_store.open(name) + + class ImageStore: _CHUNK_SIZE_BYTES = 4096 + _IMAGE_NAME_PATTERN = re.compile( + r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[a-z]{2,4}$' + ) # Note the use of dependency injection for standard library # methods. We'll use these later to avoid monkey-patching. @@ -55,3 +68,26 @@ def save(self, image_stream, image_content_type): image_file.write(chunk) return name + + def open(self, name): + # Always validate untrusted input! + if not self._IMAGE_NAME_PATTERN.match(name): + raise IOError('File not found') + + image_path = os.path.join(self._storage_path, name) + stream = self._fopen(image_path, 'rb') + content_length = os.path.getsize(image_path) + + return stream, content_length + + def list(self, max_size): + images = [ + image for image in os.listdir(self._storage_path) + if self._IMAGE_NAME_PATTERN.match(image) + and ( + max_size == -1 + or os.path.getsize(os.path.join(self._storage_path, image)) <= max_size + ) + ] + return images + diff --git a/examples/look/tests/test_app.py b/examples/look/tests/test_app.py index c6db6451c..8fce0828e 100644 --- a/examples/look/tests/test_app.py +++ b/examples/look/tests/test_app.py @@ -1,4 +1,8 @@ import io +import json +import os +import uuid +from unittest import TestCase from unittest.mock import call from unittest.mock import MagicMock from unittest.mock import mock_open @@ -25,19 +29,17 @@ def client(mock_store): return testing.TestClient(api) -def test_list_images(client): - doc = { - 'images': [ - { - 'href': '/images/1eaf6ef1-7f2d-4ecc-a8d5-6e8adba7cc0e.png', - }, - ], - } +def test_list_images(client, mock_store): + images = ['first-file', 'second-file', 'third-file'] + image_docs = [{'href': '/images/' + image} for image in images] + + mock_store.list.return_value = images response = client.simulate_get('/images') - result_doc = msgpack.unpackb(response.content, raw=False) - assert result_doc == doc + result = json.loads(response.content) + + assert result['images'] == image_docs assert response.status == falcon.HTTP_OK @@ -64,7 +66,7 @@ def test_post_image(client, mock_store): assert saver_call[0][1] == image_content_type -def test_saving_image(monkeypatch): +def test_saving_image(): # This still has some mocks, but they are more localized and do not # have to be monkey-patched into standard library modules (always a # risky business). @@ -84,3 +86,56 @@ def mock_uuidgen(): assert store.save(fake_request_stream, 'image/png') == fake_uuid + '.png' assert call().write(fake_image_bytes) in mock_file_open.mock_calls + + +def test_get_image(client, mock_store): + file_bytes = b'fake-image-bytes' + + mock_store.open.return_value = ((file_bytes,), 17) + + response = client.simulate_get('/images/filename.png') + + assert response.status == falcon.HTTP_OK + assert response.content == file_bytes + + +def test_opening_image(): + file_name = f'{uuid.uuid4()}.png' + storage_path = '.' + file_path = f'{storage_path}/{file_name}' + fake_image_bytes = b'fake-image-bytes' + with open(file_path, 'wb') as image_file: + file_length = image_file.write(fake_image_bytes) + + store = look.images.ImageStore(storage_path) + + file_reader, content_length = store.open(file_name) + assert content_length == file_length + assert file_reader.read() == fake_image_bytes + os.remove(file_path) + + with TestCase().assertRaises(IOError): + store.open('wrong_file_name_format') + + +def test_listing_images(): + file_names = [f'{uuid.uuid4()}.png' for _ in range(2)] + storage_path = '.' + file_paths = [f'{storage_path}/{name}' for name in file_names] + fake_images_bytes = [ + b'fake-image-bytes', # 17 + b'fake-image-bytes-with-more-length', # 34 + ] + for i in range(2): + with open(file_paths[i], 'wb') as image_file: + file_length = image_file.write(fake_images_bytes[i]) + + store = look.images.ImageStore(storage_path) + assert store.list(10) == [] + assert store.list(20) == [file_names[0]] + assert len(store.list(40)) == 2 + assert sorted(store.list(40)) == sorted(file_names) + + for file_path in file_paths: + os.remove(file_path) + From ddfb9045127b19e1331dfbe7b819b95d3cece618 Mon Sep 17 00:00:00 2001 From: bssyousefi Date: Mon, 23 Sep 2024 13:29:55 -0400 Subject: [PATCH 5/7] fix linter error; remove unnecessary variable --- examples/look/tests/test_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/look/tests/test_app.py b/examples/look/tests/test_app.py index 8fce0828e..645969a1d 100644 --- a/examples/look/tests/test_app.py +++ b/examples/look/tests/test_app.py @@ -128,7 +128,7 @@ def test_listing_images(): ] for i in range(2): with open(file_paths[i], 'wb') as image_file: - file_length = image_file.write(fake_images_bytes[i]) + image_file.write(fake_images_bytes[i]) store = look.images.ImageStore(storage_path) assert store.list(10) == [] From dfa33fd3b110a453a492b5c7b99cad8b88031db3 Mon Sep 17 00:00:00 2001 From: bssyousefi Date: Mon, 23 Sep 2024 13:50:09 -0400 Subject: [PATCH 6/7] fix other linter errors --- examples/look/look/app.py | 5 +++-- examples/look/look/images.py | 16 +++++----------- examples/look/tests/test_app.py | 8 +++----- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/examples/look/look/app.py b/examples/look/look/app.py index c395c4446..7b591ce40 100644 --- a/examples/look/look/app.py +++ b/examples/look/look/app.py @@ -2,7 +2,9 @@ import falcon -from .images import Collection, ImageStore, Item +from .images import Collection +from .images import ImageStore +from .images import Item def create_app(image_store): @@ -16,4 +18,3 @@ def get_app(): storage_path = os.environ.get('LOOK_STORAGE_PATH', '.') image_store = ImageStore(storage_path) return create_app(image_store) - diff --git a/examples/look/look/images.py b/examples/look/look/images.py index 8d099d99f..8f2332fc2 100644 --- a/examples/look/look/images.py +++ b/examples/look/look/images.py @@ -1,11 +1,10 @@ import io +import json import mimetypes import os import re import uuid -import json - import falcon @@ -14,13 +13,9 @@ def __init__(self, image_store): self._image_store = image_store def on_get(self, req, resp): - max_size = req.get_param_as_int("maxsize", min_value=1, default=-1) + max_size = req.get_param_as_int('maxsize', min_value=1, default=-1) images = self._image_store.list(max_size) - doc = { - 'images': [ - {'href': '/images/' + image} for image in images - ] - } + doc = {'images': [{'href': '/images/' + image} for image in images]} resp.text = json.dumps(doc, ensure_ascii=False) resp.status = falcon.HTTP_200 @@ -32,7 +27,6 @@ def on_post(self, req, resp): class Item: - def __init__(self, image_store): self._image_store = image_store @@ -82,7 +76,8 @@ def open(self, name): def list(self, max_size): images = [ - image for image in os.listdir(self._storage_path) + image + for image in os.listdir(self._storage_path) if self._IMAGE_NAME_PATTERN.match(image) and ( max_size == -1 @@ -90,4 +85,3 @@ def list(self, max_size): ) ] return images - diff --git a/examples/look/tests/test_app.py b/examples/look/tests/test_app.py index 645969a1d..2202a8ee4 100644 --- a/examples/look/tests/test_app.py +++ b/examples/look/tests/test_app.py @@ -1,14 +1,13 @@ import io import json import os -import uuid from unittest import TestCase from unittest.mock import call from unittest.mock import MagicMock from unittest.mock import mock_open +import uuid from wsgiref.validate import InputWrapper -import msgpack import pytest import falcon @@ -123,8 +122,8 @@ def test_listing_images(): storage_path = '.' file_paths = [f'{storage_path}/{name}' for name in file_names] fake_images_bytes = [ - b'fake-image-bytes', # 17 - b'fake-image-bytes-with-more-length', # 34 + b'fake-image-bytes', # 17 + b'fake-image-bytes-with-more-length', # 34 ] for i in range(2): with open(file_paths[i], 'wb') as image_file: @@ -138,4 +137,3 @@ def test_listing_images(): for file_path in file_paths: os.remove(file_path) - From b49d8290349acb72380e10ecf4044c32510f87dc Mon Sep 17 00:00:00 2001 From: bssyousefi Date: Mon, 23 Sep 2024 15:02:23 -0400 Subject: [PATCH 7/7] refactor: use dedicated attribute --- examples/look/tests/test_app.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/look/tests/test_app.py b/examples/look/tests/test_app.py index 2202a8ee4..419fe8371 100644 --- a/examples/look/tests/test_app.py +++ b/examples/look/tests/test_app.py @@ -1,5 +1,4 @@ import io -import json import os from unittest import TestCase from unittest.mock import call @@ -36,7 +35,7 @@ def test_list_images(client, mock_store): response = client.simulate_get('/images') - result = json.loads(response.content) + result = response.json assert result['images'] == image_docs assert response.status == falcon.HTTP_OK