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

Add support for update key #2

Merged
merged 12 commits into from
Jul 23, 2023
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## v0.4.0 (Jul 2023)

### Additions

- Add `UNDEFINED`, `UndefinedOr`, and `UndefinedNoneOr` types.
- Add `update_key` method to key service.
- Add `name` parameter to the `create_key` method.

### Changes

- Refactor existing methods to use the new `UNDEFINED` type.

---

## v0.3.0 (Jul 2023)

### Bugfixes
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.3.0"
version = "0.4.0"
description = "An asynchronous Python SDK for unkey.dev."
authors = ["Jonxslays"]
license = "GPL-3.0-only"
Expand Down
Empty file added tests/services/__init__.py
Empty file.
31 changes: 31 additions & 0 deletions tests/services/test_base_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from __future__ import annotations

from unittest import mock

import pytest

from unkey import UNDEFINED
from unkey import BaseService


@pytest.fixture()
def service() -> BaseService:
return BaseService(mock.Mock(), mock.Mock())


def test_generate_map(service: BaseService) -> None:
result = service._generate_map(one=1, two=2) # type: ignore

assert result == {"one": 1, "two": 2}


def test_generate_map_with_undefined(service: BaseService) -> None:
result = service._generate_map(one=1, two=UNDEFINED) # type: ignore

assert result == {"one": 1}


def test_generate_map_with_none(service: BaseService) -> None:
result = service._generate_map(one=1, two=None) # type: ignore

assert result == {"one": 1, "two": None}
100 changes: 100 additions & 0 deletions tests/test_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,103 @@ def test_to_ratelimit(
result = serializer.to_ratelimit(raw_ratelimit)

assert result == full_ratelimit


#################
# to_api_key_meta
#################


def _raw_api_key_meta() -> DictT:
return {
"id": "fxc_DDD",
"meta": {"test": 1},
"start": "fxc",
"apiId": "api_FFF",
"expires": 123,
"remaining": 12,
"ownerId": "jonxslays",
"createdAt": 456,
"workspaceId": "ws_GGG",
"ratelimit": {
"type": "fast",
"limit": 1,
"refillRate": 2,
"refillInterval": 3,
},
}


@pytest.fixture()
def raw_api_key_meta() -> DictT:
return _raw_api_key_meta()


def _full_api_key_meta() -> models.ApiKeyMeta:
model = models.ApiKeyMeta()
model.id = "fxc_DDD"
model.meta = {"test": 1}
model.start = "fxc"
model.api_id = "api_FFF"
model.expires = 123
model.remaining = 12
model.owner_id = "jonxslays"
model.created_at = 456
model.workspace_id = "ws_GGG"
model.ratelimit = models.Ratelimit(
models.RatelimitType.Fast,
limit=1,
refill_rate=2,
refill_interval=3,
)

return model


@pytest.fixture()
def full_api_key_meta() -> models.ApiKeyMeta:
return _full_api_key_meta()


def test_to_api_key_meta(
raw_api_key_meta: DictT,
full_api_key_meta: models.ApiKeyMeta,
) -> None:
result = serializer.to_api_key_meta(raw_api_key_meta)

assert result == full_api_key_meta


#################
# to_api_key_list
#################


def _raw_api_key_list() -> DictT:
return {"total": 1, "keys": [_raw_api_key_meta()]}


@pytest.fixture()
def raw_api_key_list() -> DictT:
return _raw_api_key_list()


def _full_api_key_list() -> models.ApiKeyList:
model = models.ApiKeyList()
model.total = 1
model.keys = [_full_api_key_meta()]
return model


@pytest.fixture()
def full_api_key_list() -> models.ApiKeyList:
return _full_api_key_list()


def test_to_api_key_list(
raw_api_key_list: DictT,
full_api_key_list: models.ApiKeyList,
) -> None:
result = serializer.to_api_key_list(raw_api_key_list)

assert result == full_api_key_list
9 changes: 8 additions & 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.3.0"
__version__: Final[str] = "0.4.0"
__author__: Final[str] = "Jonxslays"
__copyright__: Final[str] = "2023-present Jonxslays"
__description__: Final[str] = "An asynchronous Python SDK for unkey.dev."
Expand All @@ -21,13 +21,15 @@
from . import routes
from . import serializer
from . import services
from . import undefined
from .client import *
from .errors import *
from .models import *
from .result import *
from .routes import *
from .serializer import *
from .services import *
from .undefined import *

__all__ = (
"client",
Expand All @@ -38,6 +40,7 @@
"routes",
"serializer",
"services",
"undefined",
"Api",
"ApiKey",
"ApiKeyList",
Expand All @@ -55,11 +58,15 @@
"HttpResponse",
"HttpService",
"KeyService",
"MissingRequiredArgument",
"Ok",
"Ratelimit",
"RatelimitType",
"Result",
"Route",
"Serializer",
"UndefinedNoneOr",
"UndefinedOr",
"UnwrapError",
"UNDEFINED",
)
16 changes: 11 additions & 5 deletions unkey/errors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

__all__ = ("UnwrapError", "BaseError")
__all__ = ("BaseError", "MissingRequiredArgument", "UnwrapError")


class BaseError(Exception):
Expand All @@ -10,12 +10,18 @@ class BaseError(Exception):


class UnwrapError(BaseError):
"""Raised when calling unwrap or unwrap_err incorrectly.

message: The error message.
"""
"""Raised when calling unwrap or unwrap_err incorrectly."""

__slots__ = ()

def __init__(self, message: str) -> None:
super().__init__(f"Unwrap failed: {message}")


class MissingRequiredArgument(BaseError):
"""Raised when a required argument is missing."""

__slots__ = ()

def __init__(self, message: str) -> None:
super().__init__(f"Missing required argument: {message}")
1 change: 1 addition & 0 deletions unkey/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def compile(self, *args: t.Union[str, int]) -> CompiledRoute:
CREATE_KEY: t.Final[Route] = Route(c.POST, "/keys")
VERIFY_KEY: t.Final[Route] = Route(c.POST, "/keys/verify")
REVOKE_KEY: t.Final[Route] = Route(c.DELETE, "/keys/{}")
UPDATE_KEY: t.Final[Route] = Route(c.PUT, "/keys/{}")

# Apis
GET_API: t.Final[Route] = Route(c.GET, "/apis/{}")
Expand Down
8 changes: 7 additions & 1 deletion unkey/services/apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from unkey import models
from unkey import result
from unkey import routes
from unkey import undefined

from . import BaseService

Expand Down Expand Up @@ -46,7 +47,12 @@ async def get_api(self, api_id: str) -> ResultT[models.Api]:
return result.Ok(self._serializer.to_api(data))

async def list_keys(
self, api_id: str, *, owner_id: t.Optional[str] = None, limit: int = 100, offset: int = 0
self,
api_id: str,
*,
owner_id: undefined.UndefinedOr[str] = undefined.UNDEFINED,
limit: int = 100,
offset: int = 0,
) -> ResultT[models.ApiKeyList]:
"""Gets a paginated list of keys for the given api.

Expand Down
8 changes: 5 additions & 3 deletions unkey/services/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from datetime import datetime
from datetime import timedelta

from unkey import undefined

if t.TYPE_CHECKING:
from unkey import serializer

Expand All @@ -30,13 +32,13 @@ def __init__(self, http_service: HttpService, serializer: serializer.Serializer)
self._serializer = serializer

def _generate_map(self, **kwargs: t.Any) -> t.Dict[str, t.Any]:
return {k: v for k, v in kwargs.items() if v is not None}
return {k: v for k, v in kwargs.items() if v is not undefined.UNDEFINED}

def _expires_in(
self, *, milliseconds: int = 0, seconds: int = 0, minutes: int = 0, days: int = 0
) -> int | None:
) -> undefined.UndefinedOr[int]:
if not any({milliseconds, seconds, minutes, days}):
return None
return undefined.UNDEFINED

delta = timedelta(days=days, minutes=minutes, seconds=seconds, milliseconds=milliseconds)
return int((datetime.now() + delta).timestamp()) * 1000
Loading