-
Notifications
You must be signed in to change notification settings - Fork 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
Port to pydantic 2 #192
Port to pydantic 2 #192
Changes from 14 commits
6df13ed
1420646
0ba9802
e89d8c4
e63bbaf
56efd78
5b13527
93fdd56
e14498e
ae96905
ef8707d
cf2d481
8474af4
58cd581
533c8b1
e0502d1
d85d35a
c4e9aa8
0d87526
fa69c5d
2576e1c
1c28928
bfaf6ee
fab8b64
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,6 @@ | |
|
||
import httpx | ||
import orjson | ||
from pydantic import BaseModel, parse_obj_as | ||
from sending.backends.websocket import WebsocketManager | ||
from websockets.client import WebSocketClientProtocol | ||
|
||
|
@@ -51,7 +50,7 @@ | |
KernelStatusUpdateResponse, | ||
) | ||
from origami.models.rtu.channels.system import AuthenticateReply, AuthenticateRequest | ||
from origami.models.rtu.discriminators import RTURequest, RTUResponse | ||
from origami.models.rtu.discriminators import RTURequest, RTUResponse, RTUResponseParser | ||
from origami.models.rtu.errors import InconsistentStateEvent | ||
from origami.notebook.builder import CellNotFound, NotebookBuilder | ||
|
||
|
@@ -87,7 +86,8 @@ async def inbound_message_hook(self, contents: str) -> RTUResponse: | |
# to error or BaseRTUResponse) | ||
data: dict = orjson.loads(contents) | ||
data["channel_prefix"] = data.get("channel", "").split("/")[0] | ||
rtu_event = parse_obj_as(RTUResponse, data) | ||
|
||
rtu_event = RTUResponseParser.validate_python(data) | ||
|
||
# Debug Logging | ||
extra_dict = { | ||
|
@@ -98,15 +98,18 @@ async def inbound_message_hook(self, contents: str) -> RTUResponse: | |
if isinstance(rtu_event, NewDeltaEvent): | ||
extra_dict["delta_type"] = rtu_event.data.delta_type | ||
extra_dict["delta_action"] = rtu_event.data.delta_action | ||
logger.debug(f"Received: {data}\nParsed: {rtu_event.dict()}", extra=extra_dict) | ||
|
||
if logging.DEBUG >= logging.root.level: | ||
logger.debug(f"Received: {data}\nParsed: {rtu_event.model_dump()}", extra=extra_dict) | ||
|
||
return rtu_event | ||
|
||
async def outbound_message_hook(self, contents: RTURequest) -> str: | ||
""" | ||
Hook applied to every message we send out over the websocket. | ||
- Anything calling .send() should pass in an RTU Request pydantic model | ||
""" | ||
return contents.json() | ||
return contents.model_dump_json() | ||
|
||
def send(self, message: RTURequest) -> None: | ||
"""Override WebsocketManager-defined method for type hinting and logging.""" | ||
|
@@ -118,7 +121,9 @@ def send(self, message: RTURequest) -> None: | |
if message.event == "new_delta_request": | ||
extra_dict["delta_type"] = message.data.delta.delta_type | ||
extra_dict["delta_action"] = message.data.delta.delta_action | ||
|
||
logger.debug("Sending: RTU request", extra=extra_dict) | ||
|
||
super().send(message) # the .outbound_message_hook handles serializing this to json | ||
|
||
async def on_exception(self, exc: Exception): | ||
|
@@ -143,12 +148,21 @@ class DeltaRejected(Exception): | |
|
||
|
||
# Used in registering callback functions that get called right after squashing a Delta | ||
class DeltaCallback(BaseModel): | ||
class DeltaCallback: | ||
# callback function should be async and expect one argument: a FileDelta | ||
# Doesn't matter what it returns. Pydantic doesn't validate Callable args/return. | ||
delta_class: Type[FileDelta] | ||
fn: Callable[[FileDelta], Awaitable[None]] | ||
|
||
def __init__(self, delta_class: Type[FileDelta], fn: Callable[[FileDelta], Awaitable[None]]): | ||
# With pydantic2, raises: "TypeError: Subscripted generics cannot be used with class and instance checks" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Had to comment out this well meaning check, 'cause pydantic 2 base implementation using parameterized types is incompatible with Would happily be open to suggestion on respelling it to pivot off of, say, a constant in the class or something to get the same effect? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. need to pair with you on this to better understand what's hpapening There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, whenevs. If left in, the isinstance() check now raises an exception 'cause isinstance() can't be used with parameterized types. |
||
# Sigh. | ||
# if not issubclass(delta_class, FileDelta): | ||
# raise ValueError(f"delta_class must be a FileDelta subclass, got {delta_class}") | ||
|
||
self.delta_class = delta_class | ||
self.fn = fn | ||
|
||
|
||
class DeltaRequestCallbackManager: | ||
""" | ||
|
@@ -453,7 +467,7 @@ async def load_seed_notebook(self): | |
resp = await plain_http_client.get(file.presigned_download_url) | ||
resp.raise_for_status() | ||
|
||
seed_notebook = Notebook.parse_obj(resp.json()) | ||
seed_notebook = Notebook.model_validate(resp.json()) | ||
self.builder = NotebookBuilder(seed_notebook=seed_notebook) | ||
|
||
# See Sending backends.websocket for details but a quick refresher on hook timing: | ||
|
@@ -492,7 +506,7 @@ async def auth_hook(self, *args, **kwargs): | |
# we observe the auth reply. Instead use the unauth_ws directly and manually serialize | ||
ws: WebSocketClientProtocol = await self.manager.unauth_ws | ||
logger.info(f"Sending auth request with jwt {jwt[:5]}...{jwt[-5:]}") | ||
await ws.send(auth_request.json()) | ||
await ws.send(auth_request.model_dump_json()) | ||
|
||
async def on_auth(self, msg: AuthenticateReply): | ||
# hook for Application code to override, consider catastrophic failure on auth failure | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,19 @@ | ||
import os | ||
from typing import Optional | ||
|
||
from pydantic import validator | ||
from pydantic import model_validator | ||
|
||
from origami.models.api.base import ResourceBase | ||
|
||
|
||
class Space(ResourceBase): | ||
name: str | ||
description: Optional[str] | ||
description: Optional[str] = None | ||
url: Optional[str] = None | ||
|
||
@validator("url", always=True) | ||
def construct_url(cls, v, values): | ||
@model_validator(mode="after") | ||
def construct_url(self): | ||
noteable_url = os.environ.get("PUBLIC_NOTEABLE_URL", "https://app.noteable.io") | ||
return f"{noteable_url}/s/{values['id']}" | ||
self.url = f"{noteable_url}/s/{self.id}" | ||
|
||
return self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't make the logging call that includes dumping the model if our current logging level is lower than DEBUG.