Skip to content

Commit

Permalink
update to pydantic > 2 and koil 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jhnnsrs committed Sep 19, 2024
1 parent 56b5261 commit 5977418
Show file tree
Hide file tree
Showing 24 changed files with 1,106 additions and 1,204 deletions.
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.10
2,122 changes: 1,051 additions & 1,071 deletions poetry.lock

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ packages = [{ include = "rath" }]

[tool.poetry.dependencies]
python = "^3.8"
koil = ">=0.3.5"
koil = ">=1.0.0"
graphql-core = "^3.2.0"
pydantic = "^1.9.0"
pydantic = ">2"
websockets = { version = "^10.2", optional = true }
aiohttp = { version = "^3.8.2", optional = true }
certifi = { version = ">2021", optional = true }
Expand All @@ -31,16 +31,15 @@ pytest = "^7.2.0"
testcontainers = "^3.7.0"
pytest-qt = "^4.2.0"
pytest-asyncio = "^0.20.2"
turms = { version = ">=0.2.3", python = "^3.9" }
aiohttp = "^3.8.3"
websockets = "^10.4"
black = "^22.10.0"
pytest-cov = "^4.0.0"
ruff = "^0.0.282"
cryptography = "^41.0.3"
pyjwt = "^2.8.0"
fakts = "^0.3.48"
herre = "^0.3.31"
fakts = "1.0.0"
herre = "1.0.0"
mypy = "^1.7.1"

[build-system]
Expand Down
5 changes: 0 additions & 5 deletions rath/links/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,3 @@ async def aexecute(self, operation: Operation) -> AsyncIterator[GraphQLResult]:

yield GraphQLResult(data=json_response["data"])

class Config:
"""pydantic config"""

arbitrary_types_allowed = True
underscore_attrs_are_private = True
5 changes: 0 additions & 5 deletions rath/links/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,6 @@ async def aexecute(
async for result in self.aexecute(operation, retry=retry + 1):
yield result

class Config:
"""pydantic configuration for the AuthTokenLink"""

underscore_attrs_are_private = True
arbitary_types_allowed = True


class ComposedAuthLink(AuthTokenLink):
Expand Down
10 changes: 3 additions & 7 deletions rath/links/compose.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import AsyncIterator, List, Optional, Type, Any

from pydantic import validator
from pydantic import field_validator
from rath.links.base import ContinuationLink, Link, TerminatingLink
from rath.operation import GraphQLResult, Operation
from rath.errors import NotComposedError
Expand All @@ -19,8 +19,8 @@ class ComposedLink(TerminatingLink):
"""The links that are composed to form the chain. pydantic will validate
that the last link is a terminating link."""

@validator("links")
def validate(cls: Type["ComposedLink"], value: Any) -> List[Link]:
@field_validator("links")
def validate(cls: Type["ComposedLink"], value: Any, info) -> List[Link]:
"""Validate that the links are valid"""
if not value:
raise ValueError("ComposedLink requires at least one link")
Expand Down Expand Up @@ -119,10 +119,6 @@ async def aexecute(self, operation: Operation) -> AsyncIterator[GraphQLResult]:
async for result in self._firstlink.aexecute(operation):
yield result

class Config:
"""pydantic config"""

underscore_attrs_are_private = True


def compose(*links: Link) -> ComposedLink:
Expand Down
2 changes: 1 addition & 1 deletion rath/links/dictinglink.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def recurse_extract(obj: ValidNestedTypes) -> ValidNestedTypes:
nulled_obj[key] = value
return nulled_obj
elif isinstance(obj, BaseModel):
return json.loads(obj.json(by_alias=by_alias))
return json.loads(obj.model_dump_json(by_alias=by_alias))
else:
# base case: pass through unchanged
return obj
Expand Down
5 changes: 0 additions & 5 deletions rath/links/foward.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,3 @@ async def aexecute(self, operation: Operation) -> AsyncIterator[GraphQLResult]:
async for result in self.next.aexecute(operation):
yield result

class Config:
"""pydantic config for the link"""

underscore_attrs_are_private = True
arbitary_types_allowed = True
16 changes: 6 additions & 10 deletions rath/links/graphql_ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,20 +79,22 @@ class GraphQLWSLink(AsyncTerminatingLink):
""" The endpoint url to connect to """
allow_reconnect: bool = True
""" Should the websocket try to reconnect if it fails """
time_between_retries = 4
time_between_retries: float = 4
""" The sleep time between retries """
max_retries = 3
max_retries: int = 3
""" The maximum amount of retries before giving up """
ssl_context: SSLContext = Field(
default_factory=lambda: ssl.create_default_context(cafile=certifi.where())
)

on_connect: Optional[Callable[[InitialConnectPayload], Awaitable[None]]] = Field(
exclude=True
exclude=True, default=None
)
""" A function that is called before the connection is established. If an exception is raised, the connection is not established. Return is ignored."""

on_pong: Optional[Callable[[PongPayload], Awaitable[None]]] = Field(exclude=True)
on_pong: Optional[Callable[[PongPayload], Awaitable[None]]] = Field(
exclude=True, default=None
)
""" A function that is called before a pong is received. If an exception is raised, the connection is not established. Return is ignored."""
heartbeat_interval_ms: Optional[int] = None
""" The heartbeat interval in milliseconds (None means no heartbeats are
Expand Down Expand Up @@ -473,9 +475,3 @@ async def aexecute(self, operation: Operation) -> AsyncIterator[GraphQLResult]:
logger.debug(f"Subcription ended {operation}")
await self.aforward(json.dumps({"id": id, "type": GQL_STOP}))
raise e

class Config:
"""pydantic config"""

arbitrary_types_allowed = True
underscore_attrs_are_private = True
6 changes: 0 additions & 6 deletions rath/links/httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,3 @@ async def aexecute(self, operation: Operation) -> AsyncIterator[GraphQLResult]:
raise Exception(f"Response does not contain data {json_response}")

yield GraphQLResult(data=json_response["data"])

class Config:
"""the config for the link"""

arbitrary_types_allowed = True
underscore_attrs_are_private = True
6 changes: 0 additions & 6 deletions rath/links/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,3 @@ async def aexecute(
await self.log(operation)
async for result in self.next.aexecute(operation, **kwargs):
yield result

class Config:
"""pydantic config for the link"""

underscore_attrs_are_private = True
arbitary_types_allowed = True
12 changes: 3 additions & 9 deletions rath/links/retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ class RetryLink(ContinuationLink):
This link is stateful, and will keep track of the number of times the
subscription has been retried."""

maximum_retry_attempts = 3
maximum_retry_attempts: int = 3
"""The maximum number of times the operation function will be called, before the operation fails."""
sleep_interval: Optional[int]
sleep_interval: Optional[int] = None
"""The number of seconds to wait before retrying the operation."""

async def aexecute(
Expand Down Expand Up @@ -61,10 +61,4 @@ async def aexecute(

logger.info(f"Subscription {operation} disconnected. Retrying {retry}")
async for result in self.aexecute(operation, retry=retry + 1):
yield result

class Config:
"""pydantic config for the link"""

underscore_attrs_are_private = True
arbitary_types_allowed = True
yield result
4 changes: 2 additions & 2 deletions rath/links/sign_local_link.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .auth import AuthTokenLink
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from pydantic import validator
from pydantic import field_validator
from cryptography.hazmat.primitives.asymmetric import rsa
from typing import Any, Callable, Awaitable, Dict, Type
import jwt
Expand All @@ -19,7 +19,7 @@ class SignLocalLink(AuthTokenLink):

private_key: rsa.RSAPrivateKey

@validator("private_key", pre=True, always=True)
@field_validator("private_key", mode="before")
def must_be_valid_pem_key(cls: Type["SignLocalLink"], v: Any) -> rsa.RSAPrivateKey:
"""Validates that the private key is a valid PEM key"""
try:
Expand Down
10 changes: 2 additions & 8 deletions rath/links/subscription_transport_ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ class SubscriptionTransportWsLink(AsyncTerminatingLink):
""" The endpoint url to connect to """
allow_reconnect: bool = True
""" Should the websocket try to reconnect if it fails """
time_between_retries = 4
time_between_retries: float = 4
""" The sleep time between retries """
max_retries = 3
max_retries: int = 3
""" The maximum amount of retries before giving up """
ssl_context: SSLContext = Field(
default_factory=lambda: ssl.create_default_context(cafile=certifi.where())
Expand Down Expand Up @@ -475,9 +475,3 @@ async def aexecute(self, operation: Operation) -> AsyncIterator[GraphQLResult]:
logger.debug(f"Subcription ended {operation}")
await self.aforward(json.dumps({"id": id, "type": GQL_STOP}))
raise e

class Config:
"""The config for the link"""

arbitrary_types_allowed = True
underscore_attrs_are_private = True
6 changes: 3 additions & 3 deletions rath/links/testing/mock.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import asyncio
from typing import AsyncIterator, Awaitable, Callable, Dict, Type, Any

from pydantic import Field, validator
from pydantic import Field, field_validator
from rath.links.base import AsyncTerminatingLink
from rath.operation import GraphQLResult, Operation
from graphql import FieldNode, OperationType
Expand Down Expand Up @@ -57,12 +57,12 @@ class AsyncMockLink(AsyncTerminatingLink):
)
resolver: ResolverDict = Field(default_factory=dict, exclude=True)

@validator(
@field_validator(
"query_resolver",
"mutation_resolver",
"subscription_resolver",
"resolver",
pre=True,
mode="before",
)
@classmethod
def coerce_resolver(cls: Type["AsyncMockLink"], v: Any) -> ResolverDict:
Expand Down
12 changes: 3 additions & 9 deletions rath/links/testing/statefulmock.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import asyncio
from typing import AsyncIterator, Awaitable, Callable, Dict, Optional, Type, Any

from pydantic import Field, validator
from pydantic import Field, field_validator
from rath.links.base import AsyncTerminatingLink
from rath.links.testing.mock import AsyncMockResolver
from rath.operation import GraphQLResult, Operation
Expand Down Expand Up @@ -60,12 +60,12 @@ class AsyncStatefulMockLink(AsyncTerminatingLink):
_inqueue: Optional[asyncio.Queue] = None
_connection_task: Optional[asyncio.Task] = None

@validator(
@field_validator(
"query_resolver",
"mutation_resolver",
"subscription_resolver",
"resolver",
pre=True,
mode="before",
)
@classmethod
def coerce_resolver(cls: Type["AsyncStatefulMockLink"], v: Any) -> Dict[str, Any]:
Expand Down Expand Up @@ -245,9 +245,3 @@ async def aexecute(self, operation: Operation) -> AsyncIterator[GraphQLResult]:

else:
raise NotImplementedError("Only subscription are mocked")

class Config:
"""Pydantic Config"""

arbitrary_types_allowed = True
underscore_attrs_are_private = True
17 changes: 3 additions & 14 deletions rath/links/transpile.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
OperationDefinitionNode,
TypeNode,
)
from pydantic import BaseModel, Field
from pydantic import BaseModel, ConfigDict, Field
from rath.links.parsing import ParsingLink
from rath.operation import Operation

Expand All @@ -21,34 +21,23 @@ class TranspileHandler(BaseModel):
The default TranspileHandler is the identity function, which returns the
type passed to it.
"""
model_config = ConfigDict(arbitrary_types_allowed=True)

graphql_type: str
name: str
predicate: Callable[[Any], bool] = Field(exclude=True)
parser: Callable[[Any], Any] = Field(exclude=True)

class Config:
"""pydantic config"""

arbitrary_types_allowed = True


class ListTranspileHandler(BaseModel):
"""A List Transpile Handler
Similar to a TranspileHandler, but takes act on GraphqQLList Type of that type
"""

model_config = ConfigDict(arbitrary_types_allowed=True)
graphql_type: str
name: str
predicate: Callable[[Any, int], bool] = Field(exclude=True)
parser: Callable[[Any, int], Any] = Field(exclude=True)

class Config:
"""pydantic config"""

arbitrary_types_allowed = True


class TranspilationError(Exception):
"""A transpilation Exception"""
Expand Down
23 changes: 9 additions & 14 deletions rath/links/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
IntrospectionQuery,
)
from graphql.language.parser import parse
from pydantic import root_validator
from pydantic import model_validator
from rath.links.base import ContinuationLink
from rath.links.errors import ContinuationLinkError
from rath.operation import GraphQLResult, Operation, opify
Expand Down Expand Up @@ -75,23 +75,23 @@ class ValidatingLink(ContinuationLink):
graphql_schema: Optional[GraphQLSchema] = None
""" The schema to validate against. If not provided, the link will introspect the server to get the schema if allow_introspection is set to True."""

@root_validator(allow_reuse=True)
@model_validator(mode="after")
@classmethod
def check_schema_dsl_or_schema_glob(cls: Type["ValidatingLink"], values: Dict[str, Any]) -> Dict[str, Any]: # type: ignore
def check_schema_dsl_or_schema_glob(cls: Type["ValidatingLink"], self: "ValidatingLink", *info) -> Dict[str, Any]: # type: ignore
"""Validates and checks that either a schema_dsl or schema_glob is provided, or that allow_introspection is set to True"""
if not values.get("schema_dsl") and not values.get("schema_glob"):
if not values.get("allow_introspection"):
if not self.schema_dsl and not self.schema_glob:
if not self.allow_introspection:
raise ValueError(
"Please provide either a schema_dsl or schema_glob or allow introspection"
)

else:
values["graphql_schema"] = schemify(
schema_dsl=values.get("schema_dsl"),
schema_glob=values.get("schema_glob"),
self.graphql_schema = schemify(
schema_dsl=self.schema_dsl,
schema_glob=self.schema_glob,
)

return values
return self

async def introspect(self, starting_operation: Operation) -> GraphQLSchema: # type: ignore
"""Introspects the server to get the schema
Expand Down Expand Up @@ -148,8 +148,3 @@ async def aexecute(self, operation: Operation) -> AsyncIterator[GraphQLResult]:

async for result in self.next.aexecute(operation):
yield result

class Config:
"""Config for pydantic"""

arbitrary_types_allowed = True
Loading

0 comments on commit 5977418

Please sign in to comment.