From 26cffd3995c3a6a710236e03467df5b22a0596a8 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 7 Aug 2024 11:58:47 -0400 Subject: [PATCH 1/4] Set dev version to 0.6.0-dev --- piffle/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/piffle/__init__.py b/piffle/__init__.py index dd9b22c..6e4670d 100644 --- a/piffle/__init__.py +++ b/piffle/__init__.py @@ -1 +1 @@ -__version__ = "0.5.1" +__version__ = "0.6.0.dev0" From ae3d87a1151e60aadee2997845604f2e1eda51d2 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Fri, 9 Aug 2024 11:00:41 -0400 Subject: [PATCH 2/4] Update pypi publication workflow --- .github/workflows/python-publish.yml | 33 ++++++++++++++++++---------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 8550239..a525dab 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -4,28 +4,37 @@ name: Upload Python Package on: + # normal behavior: run when a new release is created release: types: [created] + # allow running manually on main + workflow_dispatch: + branches: [main] -jobs: - deploy: +permissions: + contents: read +jobs: + pypi-publish: + name: Upload release to PyPI runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/viapy/ + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: '3.x' + python-version: "3.11" - name: Install dependencies run: | python -m pip install --upgrade pip - pip install build twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python -m build - twine upload dist/* + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 From 02c9ebc3bcf71a924d2d4d2d4c205a7b70b47048 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Mon, 12 Aug 2024 15:05:13 -0400 Subject: [PATCH 3/4] Make get_iiif_url class method for extensibility Make at-dict base class for reuse on nested objects --- piffle/presentation.py | 101 ++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/piffle/presentation.py b/piffle/presentation.py index 14100ba..c5bd78a 100644 --- a/piffle/presentation.py +++ b/piffle/presentation.py @@ -11,15 +11,54 @@ class IIIFException(Exception): """Custom exception for IIIF errors""" -def get_iiif_url(url): - """Wrapper around :meth:`requests.get` to support conditionally - adding an auth tokens or other parameters.""" - request_options = {} - # TODO: need some way of configuring hooks for e.g. setting auth tokens - return requests.get(url, **request_options) +class AtDict(addict.Dict): + """Base attrdict class with handling for fields like @type, @id, etc""" + + at_fields = ["type", "id", "context"] + + def _key(self, key): + # convert key to @key if in the list of fields that requires it + if key in self.at_fields: + key = "@%s" % key + return key + + def __missing__(self, key): + raise KeyError(self._key(key)) + + def __getattr__(self, key): + try: + # addict getattr just calls getitem + return super().__getattr__(self._key(key)) + except KeyError: + # python hasattr checks for attribute error + # translate key error to attribute error, + # since in an attr dict it's kind of both + raise AttributeError + + def __getitem__(self, key): + """ + Access a value associated with a key. + """ + val = super().__getitem__(self._key(key)) + + if key == "seeAlso" and isinstance(val, list) and isinstance(val[0], dict): + return [AtDict(entry) for entry in val] + return val + + def __setitem__(self, key, value): + """ + Add a key-value pair to the instance. + """ + return super().__setitem__(self._key(key), value) + + def __delitem__(self, key): + """ + Delete a key-value pair + """ + super().__delitem__(self._key(key)) -class IIIFPresentation(addict.Dict): +class IIIFPresentation(AtDict): """:class:`addict.Dict` subclass for read access to IIIF Presentation content""" @@ -27,6 +66,14 @@ class IIIFPresentation(addict.Dict): at_fields = ["type", "id", "context"] + @classmethod + def get_iiif_url(cls, url): + """Wrapper around :meth:`requests.get` to support conditionally + adding an auth tokens or other parameters.""" + request_options = {} + # TODO: need some way of configuring hooks for e.g. setting auth tokens + return requests.get(url, **request_options) + @classmethod def from_file(cls, path): """Initialize :class:`IIIFPresentation` from a file.""" @@ -41,7 +88,7 @@ def from_url(cls, uri): :raises: :class:`IIIFException` if URL is not retrieved successfully, if the response is not JSON content, or if the JSON cannot be parsed. """ - response = get_iiif_url(uri) + response = cls.get_iiif_url(uri) if response.status_code == requests.codes.ok: try: return cls(response.json()) @@ -91,44 +138,6 @@ def short_id(cls, uri): # split on slashes and return the last portion return uri.split("/")[-1] - def __missing__(self, key): - raise KeyError(self._key(key)) - - def _key(self, key): - # convert key to @key if in the list of fields that requires it - if key in self.at_fields: - key = "@%s" % key - return key - - def __getattr__(self, key): - try: - # addict getattr just calls getitem - return super().__getattr__(self._key(key)) - except KeyError: - # python hasattr checks for attribute error - # translate key error to attribute error, - # since in an attr dict it's kind of both - raise AttributeError - - def __getitem__(self, key): - """ - Access a value associated with a key. - """ - val = super().__getitem__(self._key(key)) - return val - - def __setitem__(self, key, value): - """ - Add a key-value pair to the instance. - """ - return super().__setitem__(self._key(key), value) - - def __delitem__(self, key): - """ - Delete a key-value pair - """ - super().__delitem__(self._key(key)) - @property def first_label(self): # label can be a string or list of strings From 6ecf0af49cbdd8868b74294d437289beddb59fcf Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Mon, 12 Aug 2024 16:37:02 -0400 Subject: [PATCH 4/4] Set version to 0.6 and document changes --- CHANGELOG.md | 7 +++++++ piffle/__init__.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d40f311..7d2f363 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change & Version Information +## 0.6.0 + +* HTTP request method `get_iiif_url` is now a class method on + `piffle.presentation.IIIFPresentation`, which can be extended when request + customization is needed +* Manifests now supports attr-dict style access to `@id` in `seeAlso` list entries + ## 0.5.1 * Add explicit support and testing for python 3.12 diff --git a/piffle/__init__.py b/piffle/__init__.py index 6e4670d..906d362 100644 --- a/piffle/__init__.py +++ b/piffle/__init__.py @@ -1 +1 @@ -__version__ = "0.6.0.dev0" +__version__ = "0.6.0"