Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix race condition in protected decorator #14

Merged
merged 4 commits into from
May 7, 2024
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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## v0.7.2 (May 2024)

### Fixes

- Fix race condition for concurrent requests using the `protected` decorator.

### Additions

- The `Client` can now be used as an async context manager which starts
and closes the client automatically.

### Changes

- The `InvalidKeyHandlerT` and `ExcHandlerT` types no longer include `Optional`,
and instead are wrapped in `Optional` in the function signature.

---

## v0.7.1 (Feb 2024)

### Fixes
Expand Down
10 changes: 8 additions & 2 deletions noxfile.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import functools
import platform
from typing import Callable
from pathlib import Path

Expand Down Expand Up @@ -75,10 +76,15 @@ def types(session: nox.Session) -> None:


@nox.session(reuse_venv=True)
@install("black", "len8")
@install("black")
def formatting(session: nox.Session) -> None:
session.run("black", ".", "--check")
session.run("len8")

major, minor, *_ = platform.python_version_tuple()
if major == "3" and int(minor) < 12:
# This is a hack but it doesnt support python 3.12
session.install(DEPS["len8"])
session.run("len8")


@nox.session(reuse_venv=True)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "unkey.py"
version = "0.7.1"
version = "0.7.2"
description = "An asynchronous Python SDK for unkey.dev."
authors = ["Jonxslays"]
license = "GPL-3.0-only"
Expand Down
2 changes: 1 addition & 1 deletion unkey/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Final

__packagename__: Final[str] = "unkey.py"
__version__: Final[str] = "0.7.1"
__version__: Final[str] = "0.7.2"
__author__: Final[str] = "Jonxslays"
__copyright__: Final[str] = "2023-present Jonxslays"
__description__: Final[str] = "An asynchronous Python SDK for unkey.dev."
Expand Down
7 changes: 7 additions & 0 deletions unkey/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ def __init_service(self, service: t.Type[ServiceT]) -> ServiceT:

return service(self._http, self._serializer) # type: ignore[return-value]

async def __aenter__(self) -> Client:
await self.start()
return self

async def __aexit__(self, *_args: t.Any, **_kwargs: t.Any) -> None:
await self.close()

@property
def keys(self) -> services.KeyService:
"""The key service used to make key related requests."""
Expand Down
14 changes: 6 additions & 8 deletions unkey/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@
functions `*args` and `**kwargs`.
"""

InvalidKeyHandlerT = Optional[Callable[[Dict[str, Any], Optional[models.ApiKeyVerification]], Any]]
InvalidKeyHandlerT = Callable[[Dict[str, Any], Optional[models.ApiKeyVerification]], Any]
"""The type of a callback used to handle cases where the key was invalid."""

ExcHandlerT = Optional[Callable[[Exception], Any]]
ExcHandlerT = Callable[[Exception], Any]
"""The type of a callback used to handle exceptions during verification."""


def protected(
api_id: str,
key_extractor: ExtractorT,
on_invalid_key: InvalidKeyHandlerT = None,
on_exc: ExcHandlerT = None,
on_invalid_key: Optional[InvalidKeyHandlerT] = None,
on_exc: Optional[ExcHandlerT] = None,
) -> DecoratorT:
"""A framework agnostic second order decorator that is used to protect
api routes with Unkey key verification.
Expand Down Expand Up @@ -82,7 +82,6 @@ def protected(
altered by `on_invalid_key` if it was passed. If verification
succeeds the original functions return value is returned.
"""
_client = client.Client()

def _on_invalid_key(
data: Dict[str, Any], verification: Optional[models.ApiKeyVerification] = None
Expand All @@ -108,9 +107,8 @@ async def inner(*args: Any, **kwargs: Any) -> VerificationResponseT[T]:
message = "Failed to extract API key"
return _on_invalid_key({"code": None, "message": message})

await _client.start()
result = await _client.keys.verify_key(key, api_id)
await _client.close()
async with client.Client() as c:
result = await c.keys.verify_key(key, api_id)

if result.is_err:
err = result.unwrap_err()
Expand Down
Loading