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
Replace custom models with msgspec #2157
base: main
Are you sure you want to change the base?
Conversation
To avoid import cycles, we need this basic type to be available without importing the full audio system.
We need to do a major revamp of the model creation in our test suite before we make URI required.
I see that I've broken Mopidy-Iris by removing the JSON encoder/decoder. I think I might need to reintroduce those anyway to unbreak web clients in general, as I don't think I can have |
To be able to go from model name to class.
Thanks to tips in jcrist/msgspec#652.
The HTTP frontend seems to be sending empty responses:
Which mopidy.js doesn't like:
EDIT: |
Nope! That was wrong. All API methods in mopidy.js are requests, not notifications. And the old RPC handler works OK with the same request.
Seems this new handler omits a None
So we probably need the response class to be more like the old version? |
I added a test case and made sure JSON-RPC responses with |
As discussed on Zulip, the JSON-RPC parameter decoding doesn't work for
For example, for
And this response isn't handled well by mopidyjs because it's missing the
|
type=Request | list[Request], | ||
) | ||
except msgspec.ValidationError as exc: | ||
response = JsonRpcInvalidRequestError(data=str(exc)).get_response() |
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.
response = JsonRpcInvalidRequestError(data=str(exc)).get_response() | |
# Try and get any request ID that was present (non-batched only). | |
# This JSON is safe to decode. | |
id = msgspec.json.decode(request_json).get('id') | |
response = JsonRpcInvalidRequestError(data=str(exc)).get_response(id) |
|
||
JsonRpcRequestId: TypeAlias = str | int | float | ||
|
||
params: list[Param] | dict[str, Param] | None = None |
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.
params: list[Param] | dict[str, Param] | None = None | |
params: list[Param] | dict[str, Any] | None = None |
Workaround for methods that have a dict
parameter:
def build( | ||
cls, | ||
method: str, | ||
params: list[Param] | dict[str, Param] | None = None, |
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.
params: list[Param] | dict[str, Param] | None = None, | |
params: list[Param] | dict[str, Any] | None = None, |
Goes with https://github.com/mopidy/mopidy/pull/2157/files#r1526655797
I would very much like to get rid of our own custom model implementation.
This is a proof of concept using the
msgspec
library to implement our models.It passes all tests and linters, and it is still able to play music.
I'm not married to
msgspec
yet, but I think this was still worth it, asswitching to e.g. Pydantic would probably mostly require us to rewrite the
models themselves, and not all the other fixes I did.
Requirements
Memory usage
One of the primary goals of our old bespoke model implementation was to save
memory.
By switching to
msgspec
, our models still take the same amount of memory asslot classes. However, we no longer cache identical models.
I have not measured the difference this makes.
Validation at deserialization
We need to validate input data, e.g. from HTTP clients. This is now done by
msgspec
, whose JSON decoder is now used for all HTTP requests.Validation at object instantiation
msgspec
does not do any validation on object instantiation. This is mostlysomething we do in our tests, and there are still a lot of passing tests doing
things that the type checker is not happy about.
Since the type checker points out where the problems are, I am not certain we
need runtime validation of Python object instantiation.
If we want to be really strict and crash early, where the objects are
instantiated (e.g. in extensions) and not where they are used (e.g. core), then
we should probably switch to something like Pydantic, that does validation at
object instantiation.
Debian packaging
Neither
msgspec
or Pydantic v2 is packed in Debian.Pydantic v1: We could go with Pydantic v1 for now, until v2 is packaged. That
would be a tiny upgrade later in time.
Road ahead
Please test this, and help find issues.
Let's continue discussing exactly which library we want to use for our models. They all have pros and cons. This is not a clear choice.