-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
A third batch of Pydantic validation for rest/client/account.py #13736
Changes from 1 commit
f39bfc5
b854c09
cdeb0a3
92f7f01
e584fa0
76d615e
3defe6f
35d664a
8143310
38ae8c7
a840e8c
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 |
---|---|---|
|
@@ -11,14 +11,47 @@ | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
import unittest | ||
import unittest as stdlib_unittest | ||
|
||
from pydantic import ValidationError | ||
from pydantic import BaseModel, ValidationError | ||
|
||
from synapse.rest.client.models import EmailRequestTokenBody | ||
from synapse.rest.client.models import EmailRequestTokenBody, ThreepidMedium | ||
|
||
|
||
class EmailRequestTokenBodyTestCase(unittest.TestCase): | ||
class ThreepidMediumEnumTestCase(stdlib_unittest.TestCase): | ||
class Model(BaseModel): | ||
medium: ThreepidMedium | ||
|
||
def test_accepts_valid_medium_string(self) -> None: | ||
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. Wonder if we should roll our own 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. Note that 3.11 will have a StrEnum in the stdlib: https://docs.python.org/3.11/library/enum.html?highlight=strenum#enum.StrEnum There's a backport here. If we do this I'd prefer to import from stdlib and fallback to the backport. 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. absolutely fine with that idea, as long as we have confidence that the usual pitfall around |
||
"""Sanity check that Pydantic behaves sensibly with an enum-of-str | ||
|
||
This is arguably more of a test of a class that inherits from str and Enum | ||
simultaneously. | ||
""" | ||
model = self.Model.parse_obj({"medium": "email"}) | ||
self.assertIsInstance(model.medium, str) | ||
self.assertEqual(model.medium, "email") | ||
self.assertEqual(model.medium, ThreepidMedium.email) | ||
self.assertIs(model.medium, ThreepidMedium.email) | ||
|
||
self.assertNotEqual(model.medium, "msisdn") | ||
self.assertNotEqual(model.medium, ThreepidMedium.msisdn) | ||
self.assertIsNot(model.medium, ThreepidMedium.msisdn) | ||
|
||
def test_accepts_valid_medium_enum(self) -> None: | ||
model = self.Model.parse_obj({"medium": ThreepidMedium.email}) | ||
self.assertIs(model.medium, ThreepidMedium.email) | ||
|
||
def test_rejects_invalid_medium_value(self) -> None: | ||
with self.assertRaises(ValidationError): | ||
self.Model.parse_obj({"medium": "interpretive_dance"}) | ||
|
||
def test_rejects_invalid_medium_type(self) -> None: | ||
with self.assertRaises(ValidationError): | ||
self.Model.parse_obj({"medium": 123}) | ||
|
||
|
||
class EmailRequestTokenBodyTestCase(stdlib_unittest.TestCase): | ||
base_request = { | ||
"client_secret": "hunter2", | ||
"email": "alice@wonderland.com", | ||
|
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.
can you be specific about what advice you're applying / what this is commenting on?
The constants being lowercased perhaps (main thing that strikes me as weird)?
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.
Sure. My first thought was to say
medium: StrictStr
but I wanted to see if there was a way to say that "medium
should be one of these specific strings". The link seems to be the blessed way to do this.What I was suspicious of is the multiple inheritance from
str
andEnum
. TheseThreepidMedium
types will get passed through to the rest of the application, where I want them to be treated like any otherstr
. I'm not fully convinced that the rest of the application will be happy with that tbh... but I don't have anything more concrete than a bad feeling about this.Having said that, looking here it might be possible to write
medium: Literal["email", "msisdn"]
. If it works, that seems a bit less magic.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.
This seems to work pretty well:
The error messages generated are
(the errors are a little technical, but see #13337)
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.
I was actually totally fine with the enum, but enum constants usually have uppercase names — that's what I was mostly wondering about (alongside a slightly underspecified comment).
Inheriting from both str and Enum and supplying a proper
__str__
is, as far as I remember, the sensible way to do it.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.
(In passing, I noticed pydantic/pydantic#4505)
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.
Oh I see. That's fair.
I really want to like stdlib enums, but I've been bitten by their sharp corners in the past. We've even seen it ourselves with
IntEnum
a few times I think? In that light, I think I prefer theLiteral
approach. But I can also see that a properEnum
can be passed throughout the rest of the application.Maybe we should dwell on this for a bit. (I'd be interested in other opinions too!)