From b5767590b4c4792b7676fff48ed0b4f16a2ca0fc Mon Sep 17 00:00:00 2001 From: Igor Davydenko Date: Thu, 26 Mar 2020 22:31:35 +0100 Subject: [PATCH] feature: Allow to provide custom upload resource name As sometimes it is useful to allow tus upload url to be URL reversed via `request.app[].url_for(...)` --- CHANGELOG.rst | 1 + aiohttp_tus/data.py | 16 +++++++++++++--- aiohttp_tus/tus.py | 6 ++++++ docs/api.rst | 7 +++++++ docs/usage.rst | 36 ++++++++++++++++++++++++++++++++++++ tests/test_tus.py | 15 +++++++++++++++ 6 files changed, 78 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 333865d..1aa04d9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,7 @@ ChangeLog - Fix resuming uploads by passing missed ``Upload-Length`` header: `#5 `_ - Add documentation about `CORS Headers `_ +- Allow to provide upload resource name, which can be lately used for URL reversing 1.0.0b2 (2020-03-18) ==================== diff --git a/aiohttp_tus/data.py b/aiohttp_tus/data.py index 3128c28..29f1229 100644 --- a/aiohttp_tus/data.py +++ b/aiohttp_tus/data.py @@ -18,6 +18,8 @@ class Config: upload_path: Path upload_url: str + upload_resource_name: Optional[str] = None + allow_overwrite_files: bool = False on_upload_done: Optional["ResourceCallback"] = None @@ -41,11 +43,19 @@ def resolve_upload_path(self, match_info: web.UrlMappingMatchInfo) -> Path: @property def resource_tus_resource_name(self) -> str: - return f"tus_resource_{self.upload_url_id}" + return ( + f"tus_resource_{self.upload_url_id}" + if self.upload_resource_name is None + else f"{self.upload_resource_name}_resource" + ) @property def resource_tus_upload_name(self) -> str: - return f"tus_upload_{self.upload_url_id}" + return ( + f"tus_upload_{self.upload_url_id}" + if self.upload_resource_name is None + else self.upload_resource_name + ) @property def upload_url_id(self) -> str: @@ -161,7 +171,7 @@ def get_config(request: web.Request) -> Config: info = route.get_info() config_key = info.get("formatter") or info["path"] - if route.resource.name.startswith("tus_resource_"): + if config_key.endswith(r"/{resource_uid}"): config_key = get_upload_url(config_key) try: diff --git a/aiohttp_tus/tus.py b/aiohttp_tus/tus.py index 4172ca0..f5ded15 100644 --- a/aiohttp_tus/tus.py +++ b/aiohttp_tus/tus.py @@ -14,6 +14,7 @@ def setup_tus( *, upload_path: Path, upload_url: str = "/uploads", + upload_resource_name: str = None, allow_overwrite_files: bool = False, decorator: Decorator = None, on_upload_done: ResourceCallback = None, @@ -35,6 +36,10 @@ def setup_tus( :param upload_url: tus.io upload URL. Can be plain as ``/uploads`` or named as ``/users/{username}/uploads``. By default: ``"/uploads"`` + :param upload_resource_name: + By default ``aiohttp-tus`` will provide auto name for the upload resource, as + well as for the chunk resource. But sometimes it might be useful to provide + exact name, which can lately be used for URL reversing. :param allow_overwrite_files: When enabled allow to overwrite already uploaded files. This may harm consistency of stored data, cause please use this param with caution. By @@ -78,6 +83,7 @@ def decorate(handler: Handler) -> Handler: config = Config( upload_path=upload_path, upload_url=upload_url, + upload_resource_name=upload_resource_name, allow_overwrite_files=allow_overwrite_files, on_upload_done=on_upload_done, json_dumps=json_dumps, diff --git a/docs/api.rst b/docs/api.rst index 201b865..175d3c7 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2,5 +2,12 @@ API Reference ============= +aiohttp_tus +=========== + .. autofunction:: aiohttp_tus.setup_tus + +aiohttp_tus.data +================ + .. autoclass:: aiohttp_tus.data.Resource diff --git a/docs/usage.rst b/docs/usage.rst index 5ce1382..c54db70 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -189,3 +189,39 @@ achieve anonymous & authenticated uploads in same time for one upload_url=r"/users/{username}/uploads", decorator=upload_user_required, ) + +Upload resource name +==================== + +In most cases there is no need to specify :class:`aiohttp.web.Resource` name for upload +resource, but when it is necessary, it is possible to specify custom +``upload_resource_name`` and lately use it for URL reversing. + +Example below illustrates how to achieve it, + +In ``app.py``, + +.. code-block:: python + + setup_tus( + web.Application(), + upload_path=( + Path(__file__).parent.parent / "uploads" / r"{username}" + ), + upload_url="/user/{username}/uploads", + upload_resource_name="user_upload", + ) + +In ``views.py``, + +.. code-block:: python + + async def user_profile(request: web.Request) -> web.Response: + upload_url = request.app.router["uploads"].url_for( + username=request.match_info["username"] + ) + return aiohttp_jinja2.render( + "users/profile.html", + request, + {"upload_url": upload_url}, + ) diff --git a/tests/test_tus.py b/tests/test_tus.py index 97cb85b..12d520f 100644 --- a/tests/test_tus.py +++ b/tests/test_tus.py @@ -36,6 +36,7 @@ def aiohttp_test_client(aiohttp_client): async def factory( *, upload_url: str, + upload_resource_name: str = None, upload_suffix: str = None, allow_overwrite_files: bool = False, on_upload_done: ResourceCallback = None, @@ -47,6 +48,7 @@ async def factory( web.Application(), upload_path=base_path / upload_suffix if upload_suffix else base_path, upload_url=upload_url, + upload_resource_name=upload_resource_name, allow_overwrite_files=allow_overwrite_files, on_upload_done=on_upload_done, decorator=decorator, @@ -232,3 +234,16 @@ async def test_upload_large_file(aiohttp_test_client, loop): await loop.run_in_executor( None, upload, handler, get_upload_url(client, TEST_UPLOAD_URL) ) + + +async def test_upload_resource_name(aiohttp_test_client, loop): + upload = partial(tus.upload, file_name=TEST_FILE_NAME) + + async with aiohttp_test_client( + upload_url=TEST_UPLOAD_URL, upload_resource_name="upload" + ) as client: + upload_url = client.app.router["upload"].url_for() + with open(TEST_FILE_PATH, "rb") as handler: + await loop.run_in_executor( + None, upload, handler, get_upload_url(client, upload_url) + )