Merge pull request #22 from pkgw/image-editing

Implement basic image editing
This commit is contained in:
Peter Williams 2023-06-02 19:14:54 -04:00 коммит произвёл GitHub
Родитель 0f58984a6a 8e7aef5709
Коммит 4a68c86e8b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 349 добавлений и 23 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -0,0 +1,25 @@
ImageContentPermissions
=======================
.. currentmodule:: wwt_api_client.constellations.data
.. autoclass:: ImageContentPermissions
:show-inheritance:
.. rubric:: Methods Summary
.. autosummary::
~ImageContentPermissions.from_dict
~ImageContentPermissions.from_json
~ImageContentPermissions.schema
~ImageContentPermissions.to_dict
~ImageContentPermissions.to_json
.. rubric:: Methods Documentation
.. automethod:: from_dict
.. automethod:: from_json
.. automethod:: schema
.. automethod:: to_dict
.. automethod:: to_json

Просмотреть файл

@ -1,20 +1,20 @@
ImagePermissions
================
ImageUpdate
===========
.. currentmodule:: wwt_api_client.constellations.data
.. autoclass:: ImagePermissions
.. autoclass:: ImageUpdate
:show-inheritance:
.. rubric:: Methods Summary
.. autosummary::
~ImagePermissions.from_dict
~ImagePermissions.from_json
~ImagePermissions.schema
~ImagePermissions.to_dict
~ImagePermissions.to_json
~ImageUpdate.from_dict
~ImageUpdate.from_json
~ImageUpdate.schema
~ImageUpdate.to_dict
~ImageUpdate.to_json
.. rubric:: Methods Documentation

Просмотреть файл

@ -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

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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 `<a>`, `<b>`, `<br>`,
// `<em>`, `<i>`, and `<strong>`.
"credits": $string?,
// The SPDX License Identifier (https://spdx.org/licenses/) of the license

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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.

Просмотреть файл

@ -55,13 +55,19 @@ 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/get-image-_id-permissions
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:

Просмотреть файл

@ -20,11 +20,13 @@ HandlePermissions
HandleSceneStats
HandleStats
HandleUpdate
ImageApiPermissions
ImageContentPermissions
ImageInfo
ImageDisplayInfo
ImagePermissions
ImageStorage
ImageSummary
ImageUpdate
ImageWwt
SceneContent
SceneContentHydrated
@ -156,7 +158,7 @@ class ImageSummary:
@dataclass_json
@dataclass
class ImagePermissions:
class ImageContentPermissions:
copyright: str
credits: Optional[str]
license: str
@ -180,16 +182,34 @@ 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
creation_date: str # format: 2023-03-28T16:53:18.364Z
wwt: ImageWwt
permissions: ImagePermissions
permissions: ImageContentPermissions
storage: ImageStorage
note: str
@dataclass_json
@dataclass
class ImageUpdate:
note: Optional[str]
permissions: Optional[ImageContentPermissions]
@dataclass_json
@dataclass
class ImageApiPermissions:
id: str
edit: bool
@dataclass_json
@dataclass
class ScenePlace:

Просмотреть файл

@ -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,

Просмотреть файл

@ -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 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.
@ -94,3 +111,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

Просмотреть файл

@ -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": "<strong>Image Credit:</strong> 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"] = "<script>Some JS here</script><a>Credits Link</a>"
permissions = ImagePermissions(**permissions_data)
permissions = ImageContentPermissions(**permissions_data)
assert permissions.copyright == permissions_data["copyright"]
assert permissions.license == permissions_data["license"]
assert permissions.credits == "<a>Credits Link</a>"