Skip to content

Update SDK #6

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

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.venv
.tox
.pytest_cache
.vscode
13 changes: 13 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Build docker images
run: docker-compose build
- name: Run tests
run: |
docker-compose run app poetry run pytest tests
48 changes: 0 additions & 48 deletions .github/workflows/test.yml

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,4 @@ dmypy.json
# Cython debug symbols
cython_debug/

.DS_Store
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM python:3.11.4-alpine3.18
ENV PYTHONUNBUFFERED 1
ENV PYTHONFAULTHANDLER 1

WORKDIR /app
RUN apk add musl-dev linux-headers gcc g++ zlib-dev libffi-dev

RUN pip install --no-cache-dir poetry

COPY ./pyproject.toml /app/
COPY ./poetry.lock /app/

ARG VERSION
ENV VERSION $VERSION


RUN poetry install

COPY . .
4 changes: 2 additions & 2 deletions clerk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .client import Client
from .client import Client, Service

__version__ = "0.1.0"
__all__ = ["Client", "Service"]
40 changes: 21 additions & 19 deletions clerk/client.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import http
from collections.abc import Mapping
from contextlib import asynccontextmanager
from typing import Any, Mapping, Optional
from typing import Any

import aiohttp

from clerk.errors import ClerkAPIException
from .errors import ClerkAPIException

__all__ = ["Client", "Service"]

Expand All @@ -13,7 +14,10 @@ class Client:
"""An API client for the clerk.dev API"""

def __init__(
self, token: str, base_url: str = "https://api.clerk.dev/v1/", timeout_seconds: float = 30.0
self,
token: str,
base_url: str = "https://api.clerk.dev/v1/",
timeout_seconds: float = 30.0,
) -> None:
self._session = aiohttp.ClientSession(
headers={"Authorization": f"Bearer {token}"},
Expand All @@ -29,55 +33,53 @@ async def __aexit__(self, exc_type, exc_val, exc_tb):

@property
def verification(self):
from clerk.verification import VerificationService
from .verification import VerificationService

return VerificationService(self)

@property
def session(self):
from clerk.sessions import SessionsService
from .sessions import SessionsService

return SessionsService(self)

@property
def clients(self):
from clerk.clients import ClientsService
from .clients import ClientsService

return ClientsService(self)

@property
def users(self):
from clerk.users import UsersService
from .users import UsersService

return UsersService(self)

@asynccontextmanager
async def get(
self, endpoint: str, params: Optional[Mapping[str, str]] = None
) -> aiohttp.ClientResponse:
async def get(self, endpoint: str, params: Mapping[str, str] | None = None):
async with self._session.get(self._make_url(endpoint), params=params) as r:
await self._check_response_err(r)
yield r

@asynccontextmanager
async def post(
self, endpoint: str, data: Any = None, json: Any = None
) -> aiohttp.ClientResponse:
async with self._session.post(self._make_url(endpoint), data=data, json=json) as r:
async def post(self, endpoint: str, data: Any = None, json: Any = None):
async with self._session.post(
self._make_url(endpoint), data=data, json=json
) as r:
await self._check_response_err(r)
yield r

@asynccontextmanager
async def delete(self, endpoint: str) -> aiohttp.ClientResponse:
async def delete(self, endpoint: str):
async with self._session.delete(self._make_url(endpoint)) as r:
await self._check_response_err(r)
yield r

@asynccontextmanager
async def patch(
self, endpoint: str, data: Any = None, json: Any = None
) -> aiohttp.ClientResponse:
async with self._session.patch(self._make_url(endpoint), data=data, json=json) as r:
async def patch(self, endpoint: str, data: Any = None, json: Any = None):
async with self._session.patch(
self._make_url(endpoint), data=data, json=json
) as r:
await self._check_response_err(r)
yield r

Expand Down
14 changes: 6 additions & 8 deletions clerk/clients.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
from typing import List

from clerk import types
from clerk.client import Service
from . import types
from .client import Service


class ClientsService(Service):
endpoint = "clients"
verify_endpoint = endpoint + "/verify"

async def list(self) -> List[types.Client]:
async def list(self) -> list[types.Client]:
"""Retrieve a list of all clients"""
async with self._client.get(self.endpoint) as r:
return [types.Client.parse_obj(s) for s in await r.json()]
return [types.Client.model_validate(s) for s in await r.json()]

async def get(self, client_id: str) -> types.Client:
"""Retrieve a client by its id"""
async with self._client.get(f"{self.endpoint}/{client_id}") as r:
return types.Client.parse_obj(await r.json())
return types.Client.model_validate(await r.json())

async def verify(self, token: str) -> types.Client:
"""Verify a token and return its associated client, if valid"""
request = types.VerifyRequest(token=token)

async with self._client.post(self.verify_endpoint, data=request.json()) as r:
return types.Client.parse_obj(await r.json())
return types.Client.model_validate(await r.json())
8 changes: 5 additions & 3 deletions clerk/errors.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import aiohttp

from clerk import types
from . import types

__all__ = ["ClerkAPIException", "NoActiveSessionException"]


class ClerkAPIException(Exception):
def __init__(self, status: int, method: str, url: str, *api_errors: types.Error) -> None:
def __init__(
self, status: int, method: str, url: str, *api_errors: types.Error
) -> None:
self.status = status
self.method = method
self.url = url
Expand All @@ -21,7 +23,7 @@ async def from_response(cls, resp: aiohttp.ClientResponse) -> "ClerkAPIException
api_errors = []
else:
errors = data.get("errors", [])
api_errors = [types.Error.parse_obj(e) for e in errors]
api_errors = [types.Error.model_validate(e) for e in errors]

return ClerkAPIException(resp.status, resp.method, str(resp.url), *api_errors)

Expand Down
16 changes: 7 additions & 9 deletions clerk/sessions.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
from typing import List

from clerk import types
from clerk.client import Service
from . import types
from .client import Service


class SessionsService(Service):
endpoint = "sessions"

async def list(self) -> List[types.Session]:
async def list(self) -> list[types.Session]:
"""Retrieve a list of all sessions"""
async with self._client.get(self.endpoint) as r:
return [types.Session.parse_obj(s) for s in await r.json()]
return [types.Session.model_validate(s) for s in await r.json()]

async def get(self, session_id: str) -> types.Session:
"""Retrieve a session by its id"""
async with self._client.get(f"{self.endpoint}/{session_id}") as r:
return types.Session.parse_obj(await r.json())
return types.Session.model_validate(await r.json())

async def revoke(self, session_id: str) -> types.Session:
"""Revoke a session by its id"""
async with self._client.post(f"{self.endpoint}/{session_id}/revoke") as r:
return types.Session.parse_obj(await r.json())
return types.Session.model_validate(await r.json())

async def verify(self, session_id: str, token: str) -> types.Session:
"""Verify a session by its id and a given token"""
Expand All @@ -29,4 +27,4 @@ async def verify(self, session_id: str, token: str) -> types.Session:
async with self._client.post(
f"{self.endpoint}/{session_id}/verify", data=request.json()
) as r:
return types.Session.parse_obj(await r.json())
return types.Session.model_validate(await r.json())
49 changes: 25 additions & 24 deletions clerk/types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, List, Optional
from typing import Any

from pydantic import BaseModel

Expand All @@ -17,9 +17,9 @@ class Session(BaseModel):
class Client(BaseModel):
object: str
id: str
last_active_session_id: Optional[str] = None
sign_in_attempt_id: Optional[str] = None
sign_up_attempt_id: Optional[str] = None
last_active_session_id: str | None = None
sign_in_attempt_id: str | None = None
sign_up_attempt_id: str | None = None
ended: bool = False


Expand All @@ -39,34 +39,35 @@ class PhoneNumber(BaseModel):
phone_number: str
reserved_for_second_factor: bool
verification: Verification
linked_to: List[IdentificationLink]
linked_to: list[IdentificationLink]


class EmailAddress(BaseModel):
object: str
id: str
email_address: str
verification: Verification
linked_to: List[IdentificationLink]
linked_to: list[IdentificationLink]


class User(BaseModel):
object: str
id: str
username: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
gender: Optional[str] = None
birthday: Optional[str] = None
username: str | None = None
first_name: str | None = None
last_name: str | None = None
gender: str | None = None
birthday: str | None = None
profile_image_url: str
primary_email_address_id: Optional[str] = None
primary_phone_number_id: Optional[str] = None
primary_email_address_id: str | None = None
primary_phone_number_id: str | None = None
password_enabled: bool
two_factor_enabled: bool
email_addresses: List[EmailAddress]
phone_numbers: List[PhoneNumber]
external_accounts: List[Any]
metadata: Any
email_addresses: list[EmailAddress]
phone_numbers: list[PhoneNumber]
external_accounts: list[Any]
public_metadata: Any
unsafe_metadata: Any
private_metadata: Any
created_at: int
updated_at: int
Expand All @@ -76,7 +77,7 @@ class Error(BaseModel):
message: str
long_message: str
code: str
meta: Optional[Any] = None
meta: Any | None = None


class VerifyRequest(BaseModel):
Expand All @@ -90,9 +91,9 @@ class DeleteUserResponse(BaseModel):


class UpdateUserRequest(BaseModel):
first_name: Optional[str] = None
last_name: Optional[str] = None
primary_email_address_id: Optional[str] = None
primary_phone_number_id: Optional[str] = None
profile_image: Optional[str] = None
password: Optional[str] = None
first_name: str | None = None
last_name: str | None = None
primary_email_address_id: str | None = None
primary_phone_number_id: str | None = None
profile_image: str | None = None
password: str | None = None
Loading