Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
build:
strategy:
matrix:
python-version: ['3.13', '3.14']
python-version: ['3.14']
platform: [ubuntu-latest, windows-latest]

runs-on: ${{ matrix.platform }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
build:
strategy:
matrix:
python-version: ['3.13']
python-version: ['3.14']
platform: [ubuntu-latest, windows-latest]
hook-stage: [pre-commit, pre-push, manual]

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
with:
documentation_path: docs/source
pyproject_extras: dev
python_version: '3.13'
python_version: '3.14'
sphinx_build_options: -W
cache: true
publish: ${{ github.ref_name == 'main' }}
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,4 @@ See the `full documentation <https://vws-python.github.io/vws-python/>`__.
:target: https://github.com/VWS-Python/vws-python/actions
.. |PyPI| image:: https://badge.fury.io/py/VWS-Python.svg
:target: https://badge.fury.io/py/VWS-Python
.. |minimum-python-version| replace:: 3.13
.. |minimum-python-version| replace:: 3.14
4 changes: 2 additions & 2 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Setup for Sybil."""

import io
import io # noqa: TC003
import uuid
from collections.abc import Generator
from collections.abc import Generator # noqa: TC003
from doctest import ELLIPSIS
from pathlib import Path

Expand Down
1 change: 1 addition & 0 deletions newsfragments/822.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Drop Python 3.13 support, update VWS Python Mock, and test quota and project-state error responses against the mock.
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@ license = "MIT"
authors = [
{ name = "Adam Dangoor", email = "adamdangoor@gmail.com" },
]
requires-python = ">=3.13"
requires-python = ">=3.14"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
dynamic = [
Expand Down Expand Up @@ -90,7 +89,7 @@ optional-dependencies.dev = [
"ty==0.0.65",
"types-requests==2.33.0.20260712",
"vulture==2.16",
"vws-python-mock==2026.2.22.3",
"vws-python-mock==2026.8.4",
"vws-test-fixtures==2023.3.5",
"yamlfix==1.19.1",
"zizmor==1.28.0",
Expand Down
4 changes: 2 additions & 2 deletions src/vws/_async_vws_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from beartype import BeartypeConf, beartype
from vws_auth_tools import authorization_header, rfc_1123_date

from vws.response import Response
from vws.transports import AsyncTransport
from vws.response import Response # noqa: TC001
from vws.transports import AsyncTransport # noqa: TC001


@beartype(conf=BeartypeConf(is_pep484_tower=True))
Expand Down
4 changes: 2 additions & 2 deletions src/vws/_vws_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from beartype import BeartypeConf, beartype
from vws_auth_tools import authorization_header, rfc_1123_date

from vws.response import Response
from vws.transports import Transport
from vws.response import Response # noqa: TC001
from vws.transports import Transport # noqa: TC001


@beartype(conf=BeartypeConf(is_pep484_tower=True))
Expand Down
2 changes: 1 addition & 1 deletion src/vws/async_vumark_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from vws.exceptions.custom_exceptions import ServerError
from vws.exceptions.vws_exceptions import TooManyRequestsError
from vws.transports import AsyncHTTPXTransport, AsyncTransport
from vws.vumark_accept import VuMarkAccept
from vws.vumark_accept import VuMarkAccept # noqa: TC001


@beartype(conf=BeartypeConf(is_pep484_tower=True))
Expand Down
2 changes: 1 addition & 1 deletion src/vws/async_vws.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
TargetStatuses,
TargetSummaryReport,
)
from vws.response import Response
from vws.response import Response # noqa: TC001
from vws.transports import AsyncHTTPXTransport, AsyncTransport


Expand Down
10 changes: 5 additions & 5 deletions src/vws/exceptions/base_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
Cloud Recognition Web API.
"""

from collections.abc import Mapping
from collections.abc import Mapping # noqa: TC003
from typing import ClassVar

from beartype import beartype

from vws.response import Response
from vws.response import Response # noqa: TC001


@beartype
Expand Down Expand Up @@ -38,7 +38,7 @@ class VWSError(Exception):
https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api#result-codes.
"""

_exceptions_by_result_code: ClassVar[dict[str, type["VWSError"]]] = {}
_exceptions_by_result_code: ClassVar[dict[str, type[VWSError]]] = {}

def __init__(self, response: Response) -> None:
"""
Expand All @@ -52,7 +52,7 @@ def __init__(self, response: Response) -> None:
def register_exceptions_by_result_code(
cls,
*,
exceptions_by_result_code: Mapping[str, type["VWSError"]],
exceptions_by_result_code: Mapping[str, type[VWSError]],
) -> None:
"""Register ``result_code`` to exception mappings."""
cls._exceptions_by_result_code.update(exceptions_by_result_code)
Expand All @@ -63,7 +63,7 @@ def from_result_code(
*,
result_code: str,
response: Response,
) -> "VWSError":
) -> VWSError:
"""Create the mapped exception for a VWS ``result_code``."""
exception_type = cls._exceptions_by_result_code[result_code]
return exception_type(response=response)
Expand Down
2 changes: 1 addition & 1 deletion src/vws/exceptions/custom_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from beartype import beartype

from vws.response import Response
from vws.response import Response # noqa: TC001


@beartype
Expand Down
12 changes: 4 additions & 8 deletions src/vws/exceptions/vws_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,8 @@ class AuthenticationFailureError(VWSError):
"""


# See https://github.com/VWS-Python/vws-python/issues/822.
@beartype
class RequestQuotaReachedError(VWSError): # pragma: no cover
class RequestQuotaReachedError(VWSError):
"""Exception raised when Vuforia returns a response with a result code
'RequestQuotaReached'.
"""
Expand All @@ -93,25 +92,22 @@ class DateRangeError(VWSError): # pragma: no cover
"""


# This is not simulated by the mock.
@beartype
class TargetQuotaReachedError(VWSError): # pragma: no cover
class TargetQuotaReachedError(VWSError):
"""Exception raised when Vuforia returns a response with a result code
'TargetQuotaReached'.
"""


# This is not simulated by the mock.
@beartype
class ProjectSuspendedError(VWSError): # pragma: no cover
class ProjectSuspendedError(VWSError):
"""Exception raised when Vuforia returns a response with a result code
'ProjectSuspended'.
"""


# This is not simulated by the mock.
@beartype
class ProjectHasNoAPIAccessError(VWSError): # pragma: no cover
class ProjectHasNoAPIAccessError(VWSError):
"""Exception raised when Vuforia returns a response with a result code
'ProjectHasNoAPIAccess'.
"""
Expand Down
6 changes: 4 additions & 2 deletions src/vws/transports.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
"""HTTP transport implementations for VWS clients."""

from collections.abc import Awaitable
from typing import Protocol, Self, runtime_checkable
from typing import TYPE_CHECKING, Protocol, Self, runtime_checkable

import httpx
import requests
from beartype import BeartypeConf, beartype

from vws.response import Response

if TYPE_CHECKING:
from collections.abc import Awaitable


@runtime_checkable
class Transport(Protocol):
Expand Down
2 changes: 1 addition & 1 deletion src/vws/vumark_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from vws.exceptions.custom_exceptions import ServerError
from vws.exceptions.vws_exceptions import TooManyRequestsError
from vws.transports import RequestsTransport, Transport
from vws.vumark_accept import VuMarkAccept
from vws.vumark_accept import VuMarkAccept # noqa: TC001


@beartype(conf=BeartypeConf(is_pep484_tower=True))
Expand Down
2 changes: 1 addition & 1 deletion src/vws/vws.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
TargetStatuses,
TargetSummaryReport,
)
from vws.response import Response
from vws.response import Response # noqa: TC001
from vws.transports import RequestsTransport, Transport


Expand Down
6 changes: 3 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Configuration, plugins and fixtures for `pytest`."""

import io
from collections.abc import AsyncGenerator, Generator
from pathlib import Path
import io # noqa: TC003
from collections.abc import AsyncGenerator, Generator # noqa: TC003
from pathlib import Path # noqa: TC003
from typing import BinaryIO, Literal

import pytest
Expand Down
2 changes: 1 addition & 1 deletion tests/test_async_cloud_reco_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
AsyncCloudRecoService.
"""

import io
import io # noqa: TC003
import uuid
from http import HTTPStatus

Expand Down
2 changes: 1 addition & 1 deletion tests/test_async_query.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Tests for the ``AsyncCloudRecoService`` querying functionality."""

import io
import io # noqa: TC003
import uuid
from typing import BinaryIO

Expand Down
2 changes: 1 addition & 1 deletion tests/test_async_vws.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Tests for async helper functions for managing a Vuforia database."""

import base64
import io
import io # noqa: TC003
import uuid
from typing import BinaryIO

Expand Down
75 changes: 75 additions & 0 deletions tests/test_async_vws_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from mock_vws.states import States

from vws import AsyncVuMarkService, AsyncVWS
from vws.exceptions.base_exceptions import VWSError # noqa: TC001
from vws.exceptions.custom_exceptions import (
ServerError,
)
Expand All @@ -20,8 +21,12 @@
ImageTooLargeError,
InvalidInstanceIdError,
MetadataTooLargeError,
ProjectHasNoAPIAccessError,
ProjectInactiveError,
ProjectSuspendedError,
RequestQuotaReachedError,
TargetNameExistError,
TargetQuotaReachedError,
TargetStatusProcessingError,
UnknownTargetError,
)
Expand Down Expand Up @@ -94,6 +99,76 @@ async def test_add_bad_name(
assert exc.value.response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR


@pytest.mark.asyncio
async def test_request_quota_reached() -> None:
"""A ``RequestQuotaReached`` exception is raised at the quota."""
database = CloudDatabase(request_quota=0)
with MockVWS() as mock:
mock.add_cloud_database(cloud_database=database)
async_vws_client = AsyncVWS(
server_access_key=database.server_access_key,
server_secret_key=database.server_secret_key,
)

with pytest.raises(expected_exception=RequestQuotaReachedError) as exc:
await async_vws_client.list_targets()

assert exc.value.response.status_code == HTTPStatus.FORBIDDEN


@pytest.mark.asyncio
async def test_target_quota_reached(
high_quality_image: io.BytesIO,
) -> None:
"""A ``TargetQuotaReached`` exception is raised at the quota."""
database = CloudDatabase(target_quota=0)
with MockVWS() as mock:
mock.add_cloud_database(cloud_database=database)
async_vws_client = AsyncVWS(
server_access_key=database.server_access_key,
server_secret_key=database.server_secret_key,
)

with pytest.raises(expected_exception=TargetQuotaReachedError) as exc:
await async_vws_client.add_target(
name="x",
width=1,
image=high_quality_image,
active_flag=True,
application_metadata=None,
)

assert exc.value.response.status_code == HTTPStatus.FORBIDDEN


@pytest.mark.asyncio
@pytest.mark.parametrize(
argnames=("state", "expected_exception"),
argvalues=[
(States.PROJECT_SUSPENDED, ProjectSuspendedError),
(States.PROJECT_HAS_NO_API_ACCESS, ProjectHasNoAPIAccessError),
],
)
async def test_project_state_error(
*,
state: States,
expected_exception: type[VWSError],
) -> None:
"""Configured project states raise their matching exceptions."""
database = CloudDatabase(state=state)
with MockVWS() as mock:
mock.add_cloud_database(cloud_database=database)
async_vws_client = AsyncVWS(
server_access_key=database.server_access_key,
server_secret_key=database.server_secret_key,
)

with pytest.raises(expected_exception=expected_exception) as exc:
await async_vws_client.list_targets()

assert exc.value.response.status_code == HTTPStatus.FORBIDDEN


@pytest.mark.asyncio
async def test_fail(high_quality_image: io.BytesIO) -> None:
"""A ``Fail`` exception is raised when the server access key
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cloud_reco_exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Tests for exceptions raised when using the CloudRecoService."""

import io
import io # noqa: TC003
import uuid
from http import HTTPStatus

Expand Down
2 changes: 1 addition & 1 deletion tests/test_query.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Tests for the ``CloudRecoService`` querying functionality."""

import datetime
import io
import io # noqa: TC003
import uuid
from typing import BinaryIO

Expand Down
2 changes: 1 addition & 1 deletion tests/test_transports.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Tests for HTTP transport implementations."""

import io
import io # noqa: TC003
import uuid
from http import HTTPStatus

Expand Down
Loading
Loading