From ce568a5033711cb3a24a99aa57c58b5679bc49fa Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Fri, 2 Jun 2023 15:49:37 -0400 Subject: [PATCH 1/4] Implement `PATCH /image/:id` --- ...client.constellations.data.ImageUpdate.rst | 25 ++++++++++ .../constellations/patch-image-_id.rst | 49 +++++++++++++++++++ .../post-handle-_handle-image.rst | 7 ++- docs/index.rst | 1 + wwt_api_client/constellations/data.py | 12 +++++ wwt_api_client/constellations/images.py | 19 ++++++- 6 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 docs/api/wwt_api_client.constellations.data.ImageUpdate.rst create mode 100644 docs/endpoints/constellations/patch-image-_id.rst diff --git a/docs/api/wwt_api_client.constellations.data.ImageUpdate.rst b/docs/api/wwt_api_client.constellations.data.ImageUpdate.rst new file mode 100644 index 0000000..4d22231 --- /dev/null +++ b/docs/api/wwt_api_client.constellations.data.ImageUpdate.rst @@ -0,0 +1,25 @@ +ImageUpdate +=========== + +.. currentmodule:: wwt_api_client.constellations.data + +.. autoclass:: ImageUpdate + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~ImageUpdate.from_dict + ~ImageUpdate.from_json + ~ImageUpdate.schema + ~ImageUpdate.to_dict + ~ImageUpdate.to_json + + .. rubric:: Methods Documentation + + .. automethod:: from_dict + .. automethod:: from_json + .. automethod:: schema + .. automethod:: to_dict + .. automethod:: to_json diff --git a/docs/endpoints/constellations/patch-image-_id.rst b/docs/endpoints/constellations/patch-image-_id.rst new file mode 100644 index 0000000..b0b737e --- /dev/null +++ b/docs/endpoints/constellations/patch-image-_id.rst @@ -0,0 +1,49 @@ +.. _endpoint-PATCH-image-_id: + +================ +PATCH /image/:id +================ + +This API updates various attributes of the specified image. + + +Authorization +============= + +The request must be made by an account that has permissions to make *all* of the +requested modifications. If any of the requested modifications are disallowed, +the entire operation fails. + + +Request Structure +================= + +The URL parameter ``:id`` is the ID of the image to modify. + +The structure of the request is: + +.. code-block:: javascript + + { + // New textual note attached to the image + "note": $string?, + + // See docs for POST /handle/:handle/image for a description of + // the permissions object. + "permissions": $object?, + } + +For the definition of substructures such as ``permissions``, see +:ref:`endpoint-POST-handle-_handle-image`. + + +Response Structure +================== + +The structure of the response is: + +.. code-block:: javascript + + { + "error": $bool // Whether an error occurred + } diff --git a/docs/endpoints/constellations/post-handle-_handle-image.rst b/docs/endpoints/constellations/post-handle-_handle-image.rst index 64037d2..6803cd8 100644 --- a/docs/endpoints/constellations/post-handle-_handle-image.rst +++ b/docs/endpoints/constellations/post-handle-_handle-image.rst @@ -41,7 +41,7 @@ The structure of the request is: "width_factor": $number(int), }, "permissions": { - // Free text giving the copyright statement for this image. Preferred form is + // Free plain text giving the copyright statement for this image. Preferred form is // along the lines of "Copyright 2020 Henrietta Swan Leavitt" or "Public // domain". *Please* provide support in higher-level applications to allow // users to input valid information here — the correct information for this @@ -52,11 +52,14 @@ The structure of the request is: // their duties. "copyright": $string, - // Free text giving credits to be shown when displaying this image. This is + // HTML content giving credits to be shown when displaying this image. This is // different information than the copyright statement, which specifies who // "owns" the image. The credits have no legal significance, except that // some images are licensed in a way that requires that specific credit texts // are shown alongside them. + // + // Only a subset of HTML is allowed here. Allowed tags are ``, ``, `
`, + // ``, ``, and ``. "credits": $string?, // The SPDX License Identifier (https://spdx.org/licenses/) of the license diff --git a/docs/index.rst b/docs/index.rst index da2d2d0..3c8eba7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -55,6 +55,7 @@ Constellations API endpoints: endpoints/constellations/get-handle-_handle-stats endpoints/constellations/get-handle-_handle-timeline endpoints/constellations/get-image-_id + endpoints/constellations/patch-image-_id endpoints/constellations/get-image-_id-img_wtml endpoints/constellations/post-images-find-by-legacy-url endpoints/constellations/get-scene-_id diff --git a/wwt_api_client/constellations/data.py b/wwt_api_client/constellations/data.py index 4b17661..14f4906 100644 --- a/wwt_api_client/constellations/data.py +++ b/wwt_api_client/constellations/data.py @@ -25,6 +25,7 @@ ImageDisplayInfo ImagePermissions ImageStorage ImageSummary +ImageUpdate ImageWwt SceneContent SceneContentHydrated @@ -180,6 +181,10 @@ class ImageDisplayInfo: @dataclass_json @dataclass class ImageInfo: + """Note that this class is *not* what is returned by the + ``/handle/:handle/imageinfo`` endpoint. That returns a + :class:`ImageSummary`.""" + id: str # 24 hex digits handle_id: str # 24 hex digits handle: HandleInfo @@ -190,6 +195,13 @@ class ImageInfo: note: str +@dataclass_json +@dataclass +class ImageUpdate: + note: Optional[str] + permissions: Optional[ImagePermissions] + + @dataclass_json @dataclass class ScenePlace: diff --git a/wwt_api_client/constellations/images.py b/wwt_api_client/constellations/images.py index 4778644..2037b08 100644 --- a/wwt_api_client/constellations/images.py +++ b/wwt_api_client/constellations/images.py @@ -11,7 +11,7 @@ from wwt_data_formats.folder import Folder from wwt_data_formats.imageset import ImageSet from . import CxClient -from .data import ImageInfo +from .data import ImageInfo, ImageUpdate, _strip_nulls_in_place __all__ = """ ImageClient @@ -94,3 +94,20 @@ class ImageClient: imageset_wtml_url, imageset_folder """ return self.imageset_folder().children[0] + + def update(self, updates: ImageUpdate): + """ + Update various attributes of this image. + + This method corresponds to the :ref:`endpoint-PATCH-image-_id` API + endpoint. + """ + resp = self.client._send_and_check( + self._url_base, + http_method="PATCH", + json=_strip_nulls_in_place(updates.to_dict()), + ) + resp = resp.json() + resp.pop("error") + # Might as well return the response, although it's currently vacuous + return resp From 80276c9ad90031d5aa392e0b82d257ee4fefbbee Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Fri, 2 Jun 2023 16:07:36 -0400 Subject: [PATCH 2/4] docs: document endpoints added for likes/impressions --- .../constellations/delete-scene-_id-likes.rst | 32 +++++++++++++++++++ .../post-scene-_id-impressions.rst | 31 ++++++++++++++++++ .../constellations/post-scene-_id-likes.rst | 32 +++++++++++++++++++ .../constellations/post-session-init.rst | 28 ++++++++++++++++ docs/index.rst | 4 +++ 5 files changed, 127 insertions(+) create mode 100644 docs/endpoints/constellations/delete-scene-_id-likes.rst create mode 100644 docs/endpoints/constellations/post-scene-_id-impressions.rst create mode 100644 docs/endpoints/constellations/post-scene-_id-likes.rst create mode 100644 docs/endpoints/constellations/post-session-init.rst diff --git a/docs/endpoints/constellations/delete-scene-_id-likes.rst b/docs/endpoints/constellations/delete-scene-_id-likes.rst new file mode 100644 index 0000000..76cd3db --- /dev/null +++ b/docs/endpoints/constellations/delete-scene-_id-likes.rst @@ -0,0 +1,32 @@ +.. _endpoint-DELETE-scene-_id-likes: + +======================= +DELETE /scene/:id/likes +======================= + +This API attempts to remove a “like” for the specified scene. + +In order to succeed, the request must include a valid session cookie from the +WWT Constellations website, and various rate-limiting and self-consistency +measures apply. + + +Request Structure +================= + +The request takes no content. The ``:id`` URL parameter gives the ID of the +scene to update. + + +Response Structure +================== + +The response is as follows: + +.. code-block:: javascript + + { + "error": $bool // Whether an error occurred + "id": $string, // the ID of the scene that was updated + "update": $boolean, // whether a like was actually removed + } diff --git a/docs/endpoints/constellations/post-scene-_id-impressions.rst b/docs/endpoints/constellations/post-scene-_id-impressions.rst new file mode 100644 index 0000000..419e535 --- /dev/null +++ b/docs/endpoints/constellations/post-scene-_id-impressions.rst @@ -0,0 +1,31 @@ +.. _endpoint-POST-scene-_id-impressions: + +=========================== +POST /scene/:id/impressions +=========================== + +This API attempts to record an impression for the specified scene. + +In order to succeed, the request must include a valid session cookie from the +WWT Constellations website, and various rate-limiting measures apply. + + +Request Structure +================= + +The request takes no content. The ``:id`` URL parameter gives the ID of the +scene to update. + + +Response Structure +================== + +The response is as follows: + +.. code-block:: javascript + + { + "error": $bool // Whether an error occurred + "id": $string, // the ID of the scene that was updated + "update": $boolean, // whether an impression was actually recorded + } diff --git a/docs/endpoints/constellations/post-scene-_id-likes.rst b/docs/endpoints/constellations/post-scene-_id-likes.rst new file mode 100644 index 0000000..063da89 --- /dev/null +++ b/docs/endpoints/constellations/post-scene-_id-likes.rst @@ -0,0 +1,32 @@ +.. _endpoint-POST-scene-_id-likes: + +===================== +POST /scene/:id/likes +===================== + +This API attempts to record a “like” for the specified scene. + +In order to succeed, the request must include a valid session cookie from the +WWT Constellations website, and various rate-limiting and self-consistency +measures apply. + + +Request Structure +================= + +The request takes no content. The ``:id`` URL parameter gives the ID of the +scene to update. + + +Response Structure +================== + +The response is as follows: + +.. code-block:: javascript + + { + "error": $bool // Whether an error occurred + "id": $string, // the ID of the scene that was updated + "update": $boolean, // whether a like was actually recorded + } diff --git a/docs/endpoints/constellations/post-session-init.rst b/docs/endpoints/constellations/post-session-init.rst new file mode 100644 index 0000000..d3ebe93 --- /dev/null +++ b/docs/endpoints/constellations/post-session-init.rst @@ -0,0 +1,28 @@ +.. _endpoint-POST-session-init: + +================== +POST /session/init +================== + +This API initializes the WWT Constellations session cookie for session tracking. + + +Request Structure +================= + +The request takes no content. + + +Response Structure +================== + +The response content is vacuous JSON: + +.. code-block:: javascript + + { + "error": false + } + +The more important aspect of the response is that it will include a +``Set-Cookie`` header that sets a session cookie. diff --git a/docs/index.rst b/docs/index.rst index 3c8eba7..e43e5e6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -60,9 +60,13 @@ Constellations API endpoints: endpoints/constellations/post-images-find-by-legacy-url endpoints/constellations/get-scene-_id endpoints/constellations/patch-scene-_id + endpoints/constellations/post-scene-_id-impressions + endpoints/constellations/post-scene-_id-likes + endpoints/constellations/delete-scene-_id-likes endpoints/constellations/get-scene-_id-permissions endpoints/constellations/get-scene-_id-place_wtml endpoints/constellations/get-scenes-home-timeline + endpoints/constellations/post-session-init Legacy WWT APIs: From 6e8c1aa6a1dbdecbecf9793d61f9d84dcf7947f4 Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Fri, 2 Jun 2023 16:18:08 -0400 Subject: [PATCH 3/4] Rename ImagePermissions to ImageContentPermissions Since we're going to need to distinguish this from API permissions. --- ...stellations.data.ImageContentPermissions.rst} | 16 ++++++++-------- wwt_api_client/constellations/data.py | 8 ++++---- wwt_api_client/constellations/handles.py | 6 +++--- wwt_api_client/tests/test_constellations.py | 11 +++++------ 4 files changed, 20 insertions(+), 21 deletions(-) rename docs/api/{wwt_api_client.constellations.data.ImagePermissions.rst => wwt_api_client.constellations.data.ImageContentPermissions.rst} (52%) diff --git a/docs/api/wwt_api_client.constellations.data.ImagePermissions.rst b/docs/api/wwt_api_client.constellations.data.ImageContentPermissions.rst similarity index 52% rename from docs/api/wwt_api_client.constellations.data.ImagePermissions.rst rename to docs/api/wwt_api_client.constellations.data.ImageContentPermissions.rst index 57bb873..c558d40 100644 --- a/docs/api/wwt_api_client.constellations.data.ImagePermissions.rst +++ b/docs/api/wwt_api_client.constellations.data.ImageContentPermissions.rst @@ -1,20 +1,20 @@ -ImagePermissions -================ +ImageContentPermissions +======================= .. currentmodule:: wwt_api_client.constellations.data -.. autoclass:: ImagePermissions +.. autoclass:: ImageContentPermissions :show-inheritance: .. rubric:: Methods Summary .. autosummary:: - ~ImagePermissions.from_dict - ~ImagePermissions.from_json - ~ImagePermissions.schema - ~ImagePermissions.to_dict - ~ImagePermissions.to_json + ~ImageContentPermissions.from_dict + ~ImageContentPermissions.from_json + ~ImageContentPermissions.schema + ~ImageContentPermissions.to_dict + ~ImageContentPermissions.to_json .. rubric:: Methods Documentation diff --git a/wwt_api_client/constellations/data.py b/wwt_api_client/constellations/data.py index 14f4906..a23de7e 100644 --- a/wwt_api_client/constellations/data.py +++ b/wwt_api_client/constellations/data.py @@ -20,9 +20,9 @@ HandlePermissions HandleSceneStats HandleStats HandleUpdate +ImageContentPermissions ImageInfo ImageDisplayInfo -ImagePermissions ImageStorage ImageSummary ImageUpdate @@ -157,7 +157,7 @@ class ImageSummary: @dataclass_json @dataclass -class ImagePermissions: +class ImageContentPermissions: copyright: str credits: Optional[str] license: str @@ -190,7 +190,7 @@ class ImageInfo: handle: HandleInfo creation_date: str # format: 2023-03-28T16:53:18.364Z wwt: ImageWwt - permissions: ImagePermissions + permissions: ImageContentPermissions storage: ImageStorage note: str @@ -199,7 +199,7 @@ class ImageInfo: @dataclass class ImageUpdate: note: Optional[str] - permissions: Optional[ImagePermissions] + permissions: Optional[ImageContentPermissions] @dataclass_json diff --git a/wwt_api_client/constellations/handles.py b/wwt_api_client/constellations/handles.py index a3f6a8f..b2c4175 100644 --- a/wwt_api_client/constellations/handles.py +++ b/wwt_api_client/constellations/handles.py @@ -24,7 +24,7 @@ from .data import ( HandleStats, HandleUpdate, ImageWwt, - ImagePermissions, + ImageContentPermissions, ImageStorage, ImageSummary, SceneContent, @@ -50,7 +50,7 @@ D2R = math.pi / 180 @dataclass class AddImageRequest: wwt: ImageWwt - permissions: ImagePermissions + permissions: ImageContentPermissions storage: ImageStorage note: str @@ -351,7 +351,7 @@ class HandleClient: legacy_url_template=imageset.url, ) - permissions = ImagePermissions( + permissions = ImageContentPermissions( copyright=copyright, credits=imageset.credits, license=license_spdx_id, diff --git a/wwt_api_client/tests/test_constellations.py b/wwt_api_client/tests/test_constellations.py index 766ce89..6742aa0 100644 --- a/wwt_api_client/tests/test_constellations.py +++ b/wwt_api_client/tests/test_constellations.py @@ -1,7 +1,7 @@ from license_expression import ExpressionError import pytest -from ..constellations.data import ImagePermissions +from ..constellations.data import ImageContentPermissions @pytest.fixture @@ -9,12 +9,12 @@ def valid_permissions_data(): return { "copyright": "Some copyright information", "credits": "Image Credit: Someone", - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", } def test_valid_image_permissions(valid_permissions_data): - permissions = ImagePermissions(**valid_permissions_data) + permissions = ImageContentPermissions(**valid_permissions_data) assert permissions.copyright == valid_permissions_data["copyright"] assert permissions.credits == valid_permissions_data["credits"] @@ -25,15 +25,14 @@ def test_invalid_permissions_license(valid_permissions_data): with pytest.raises(ExpressionError): permissions_data = valid_permissions_data.copy() permissions_data["license"] = "NO SUCH LICENSE" - ImagePermissions(**permissions_data) + ImageContentPermissions(**permissions_data) def test_sanitize_permissions_html(valid_permissions_data): permissions_data = valid_permissions_data.copy() permissions_data["credits"] = "
Credits Link" - permissions = ImagePermissions(**permissions_data) + permissions = ImageContentPermissions(**permissions_data) assert permissions.copyright == permissions_data["copyright"] assert permissions.license == permissions_data["license"] assert permissions.credits == "Credits Link" - From 8e7aef5709178b24c3e3f443f02e9b29ba6e4bed Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Fri, 2 Jun 2023 16:20:58 -0400 Subject: [PATCH 4/4] constellations: add ImageClient.permissions() --- ...onstellations.data.ImageApiPermissions.rst | 25 ++++++++++++ ...ient.constellations.images.ImageClient.rst | 4 ++ .../get-image-_id-permissions.rst | 38 +++++++++++++++++++ docs/index.rst | 1 + wwt_api_client/constellations/data.py | 8 ++++ wwt_api_client/constellations/images.py | 19 +++++++++- 6 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 docs/api/wwt_api_client.constellations.data.ImageApiPermissions.rst create mode 100644 docs/endpoints/constellations/get-image-_id-permissions.rst diff --git a/docs/api/wwt_api_client.constellations.data.ImageApiPermissions.rst b/docs/api/wwt_api_client.constellations.data.ImageApiPermissions.rst new file mode 100644 index 0000000..21ec3e7 --- /dev/null +++ b/docs/api/wwt_api_client.constellations.data.ImageApiPermissions.rst @@ -0,0 +1,25 @@ +ImageApiPermissions +=================== + +.. currentmodule:: wwt_api_client.constellations.data + +.. autoclass:: ImageApiPermissions + :show-inheritance: + + .. rubric:: Methods Summary + + .. autosummary:: + + ~ImageApiPermissions.from_dict + ~ImageApiPermissions.from_json + ~ImageApiPermissions.schema + ~ImageApiPermissions.to_dict + ~ImageApiPermissions.to_json + + .. rubric:: Methods Documentation + + .. automethod:: from_dict + .. automethod:: from_json + .. automethod:: schema + .. automethod:: to_dict + .. automethod:: to_json diff --git a/docs/api/wwt_api_client.constellations.images.ImageClient.rst b/docs/api/wwt_api_client.constellations.images.ImageClient.rst index 22cca03..b23aa7e 100644 --- a/docs/api/wwt_api_client.constellations.images.ImageClient.rst +++ b/docs/api/wwt_api_client.constellations.images.ImageClient.rst @@ -14,6 +14,8 @@ ImageClient ~ImageClient.imageset_folder ~ImageClient.imageset_object ~ImageClient.imageset_wtml_url + ~ImageClient.permissions + ~ImageClient.update .. rubric:: Methods Documentation @@ -21,3 +23,5 @@ ImageClient .. automethod:: imageset_folder .. automethod:: imageset_object .. automethod:: imageset_wtml_url + .. automethod:: permissions + .. automethod:: update diff --git a/docs/endpoints/constellations/get-image-_id-permissions.rst b/docs/endpoints/constellations/get-image-_id-permissions.rst new file mode 100644 index 0000000..70af907 --- /dev/null +++ b/docs/endpoints/constellations/get-image-_id-permissions.rst @@ -0,0 +1,38 @@ +.. _endpoint-GET-image-_id-permissions: + +========================== +GET /image/:id/permissions +========================== + +This API returns JSON capturing the logged-in user's permissions with regards to +the specified image. + +The returned information is purely advisory. The final arbiters of what is +actually allowed are the checks implemented for the actual APIs. Furthermore, it +is always possible that the results of this API call end up being out-of-date by +the time that you attempt to act on them. So in almost all circumstances, you +should ignore this API and just attempt the operation that you want to attempt, +and handle any errors that might happen. This API should only be used to +construct UIs that may choose to hide or show certain elements based on the +user's permissions. + + +Request Structure +================= + +The request takes no content. The ``:id`` URL parameter gives the ID of the +image to query. + + +Response Structure +================== + +The response is as follows: + +.. code-block:: javascript + + { + "error": $bool // Whether an error occurred + "id": $string, // the ID of the image that was queried + "edit": $boolean, // whether the user can edit the image + } diff --git a/docs/index.rst b/docs/index.rst index e43e5e6..e9bc7af 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -57,6 +57,7 @@ Constellations API endpoints: endpoints/constellations/get-image-_id endpoints/constellations/patch-image-_id endpoints/constellations/get-image-_id-img_wtml + endpoints/constellations/get-image-_id-permissions endpoints/constellations/post-images-find-by-legacy-url endpoints/constellations/get-scene-_id endpoints/constellations/patch-scene-_id diff --git a/wwt_api_client/constellations/data.py b/wwt_api_client/constellations/data.py index a23de7e..9f03626 100644 --- a/wwt_api_client/constellations/data.py +++ b/wwt_api_client/constellations/data.py @@ -20,6 +20,7 @@ HandlePermissions HandleSceneStats HandleStats HandleUpdate +ImageApiPermissions ImageContentPermissions ImageInfo ImageDisplayInfo @@ -202,6 +203,13 @@ class ImageUpdate: permissions: Optional[ImageContentPermissions] +@dataclass_json +@dataclass +class ImageApiPermissions: + id: str + edit: bool + + @dataclass_json @dataclass class ScenePlace: diff --git a/wwt_api_client/constellations/images.py b/wwt_api_client/constellations/images.py index 2037b08..e12b928 100644 --- a/wwt_api_client/constellations/images.py +++ b/wwt_api_client/constellations/images.py @@ -11,7 +11,7 @@ from wwt_data_formats.folder import Folder from wwt_data_formats.imageset import ImageSet from . import CxClient -from .data import ImageInfo, ImageUpdate, _strip_nulls_in_place +from .data import ImageApiPermissions, ImageInfo, ImageUpdate, _strip_nulls_in_place __all__ = """ ImageClient @@ -53,6 +53,23 @@ class ImageClient: resp.pop("error") return ImageInfo.schema().load(resp) + def permissions(self) -> ImageApiPermissions: + """ + Get information about the logged-in user's permissions with regards to + this image. + + This method corresponds to the :ref:`endpoint-GET-image-_id-permissions` + API endpoint. See that documentation for important guidance about when + and how to use this API. In most cases you should not use it, and just + go ahead and attempt whatever operation wish to perform. + """ + resp = self.client._send_and_check( + self._url_base + "/permissions", http_method="GET" + ) + resp = resp.json() + resp.pop("error") + return ImageApiPermissions.schema().load(resp) + def imageset_wtml_url(self) -> str: """ Get a URL that will yield a WTML folder containing this image as an imageset.