-
Notifications
You must be signed in to change notification settings - Fork 137
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
adding matcher POC #761
adding matcher POC #761
Conversation
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 is just to serve the expected response and would need to be heavily modified for a final product.
src/pact/v3/matchers/matchers.py
Outdated
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 implemented a generator option only in the regex
matcher again just as a POC.
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.
The Matcher class will (I think) be useful when implementing more complex logic, just as being able to "OR" together different matchers. As far as I can tell, there's no way to do the "OR" logic directly in parameter JSON (e.g. { "attribute": { "value": "expected", "pact:matcher:type": "regex", "regex": "expect.*" } }
.
84dbcae
to
e5939c3
Compare
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.
Love the PoC!
I have a few minor nitpicks, but otherwise this looks like an amazing start!
Let me know how you would like me to support you in this going forward 🚀
examples/tests/v3/test_matchers.py
Outdated
from pathlib import Path | ||
|
||
from examples.tests.v3.basic_flask_server import start_provider | ||
from pact.v3 import Pact, Verifier, matchers |
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 would want to avoid polluting the namespace, so I agree with your choice of importing a module and calling matchers.like
and matchers.regex
etc.
My nit here is that I would want to name it match
so that the syntax is match.regex(...)
and match.like(...)
, etc. This way, it reads a bit more like English :)
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 feel like the module that defines the actual classes (Matcher, MatcherEncoder, etc) should be called "matchers" since they are objects. I'd be happy to create a module called match and move the functions that return those matchers into that module.
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.
Just started going through your updated PR. I'm still thinking that we should expose the the functions behind a common match.{func}
interface, as I do think it becomes most intuitive. It also serves as a nice way to distinguish match.{func}
from similar generate.{func}
.
I also agree that having matchers
as the module name for the Matcher classes makes sense. Having said that, I don't think people would often (or ever?) need to interact with the class directly. So I'm liking what you've done whereby the matchers
are defined in a submodule, and functions re-exported at a higher level.
src/pact/v3/interaction/_base.py
Outdated
if not isinstance(body, str): | ||
body_str = json.dumps(body, cls=MatcherEncoder) |
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.
Nit: Avoid negative isinstance
assertions.
If Python was strongly typed, this wouldn't matter; however, the type annotations are merely hint and at runtime, end users can in fact input whatever they want. So we need to handle cases of with_body(MyClassWithouSerialization(...))
(which your if
statement would catch).
4a97348
to
a159b47
Compare
I've started working on the PR. Pushed a few changes (which are so far relatively minor). I do anticipate making some larger changes at a later date, but I just want to get a feel for it first :) |
The `dict`, `list` and `tuple` types require argument. Also, the use of the lowercase variants is not compatible with Python 3.8 (which will be dropped soon anyway... but still need to support it for a little while longer). Signed-off-by: JP-Ellis <[email protected]>
The `JSONEncoder` class uses `o` as the argument and does not enforce a positional argument. This means we need to support the possible use of `default(o=...)`. Signed-off-by: JP-Ellis <[email protected]>
The type annotation that goes alongside a `**kwargs` types the _values_. Therefore a `**kwargs: Foo` will result in `kwargs` being of type `dict[str, Foo]`. Signed-off-by: JP-Ellis <[email protected]>
While equivalent, Optional and Union quickly can become quite verbose and more difficult to parse. Compare ``` Optional[Union[str, bool]] ``` to ``` str | bool | None ``` Signed-off-by: JP-Ellis <[email protected]>
They are functionally equivalent, but with ABC being made to be more intuitive than the use of metaclass Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
In particular, splitting up the class, functions and types into separate modules. Signed-off-by: JP-Ellis <[email protected]>
c8baa3c
to
4cd67f5
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #761 +/- ##
======================================
- Coverage 78% 78% -1%
======================================
Files 23 29 +6
Lines 2673 3060 +387
======================================
+ Hits 2111 2394 +283
- Misses 562 666 +104
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
acf1710
to
57aeb3b
Compare
Strictly speaking, the typing annotation would allow for arrays to be used as keys in dictionaries, but as these are 'merely' typing hints, this should not matter much and should rarely lead to confusion. Instead, this will simplify some typing in cases dealing with arrays and mappings. Signed-off-by: JP-Ellis <[email protected]>
A massive thanks to Val from all of the groundwork! I'm playing around here with shadowing Python built-ins to provide a more Pythonic interface. E.g., `match.int` and `match.str`, etc. I've also adjusted a number of arguments to allow shadowing built-ins (e.g., min, max) as the limited context poses little risk of confusion. Lastly, restructed a number of the argument to ensure that the value is _always_ a position argument, and in most cases, the remaining arguments are _always_ key word arguments. This will prevent `match.int(1, 2, 3)` where it may not be immediately obvious what each argument corresponds to. Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
The Pact ecosystem has a number of commonly used names for matchers. Aliases are added to ensure compatibility with what one might expect from using another Pact implementation. Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
Instead of having multiple types sub-modules for the generators and matchers, create a single shared `pact.v3.types` module. Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
The generator and matcher behave very similarly, and this brings the generator in line with the matcher declaration. Signed-off-by: JP-Ellis <[email protected]>
As much as it would be nice to restrict a type var to only matchable types, there seems to be an issue when combining typevars with mappings/sequences. So instead, I'm removing `MatchableT` and will re-instate unrestricted `_T` types in the modules. Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
This brings the generators broadly in line with the matchers, including the shadowing of Python builtins to allow for `generate.int()` and `generate.float`, etc. Signed-off-by: JP-Ellis <[email protected]>
This incorporates a couple of recent changes in the recent of the codebase: - The addition of EachKeyMatcher and EachValueMatcher - The refactor of the generate module - The removal of the MatchableT TypeVar Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
src/pact/v3/generate/generator.py
Outdated
integration_fields: Mapping[str, Any] | None = None, | ||
generator_json_fields: Mapping[str, Any] | 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.
Is it possible to provide examples of how these two might be used?
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.
Yeah, good call.
The reasoning initially is that the way the generators/matchers are serialised into the 'integration JSON' format is slightly different to the way they are serialised when passed directly to the matching_rules
or generators
commands. E.g., in one, you have to use "pact:matcher:type": "..."
whereas the other you don't.
As far as I know, all the other fields are the same, but if there were others that had to be differentiated, then this is there.
Thinking back on it though, I'm concerned that this might be a case of premature generalisation.
Signed-off-by: JP-Ellis <[email protected]>
A generator is only really useful if a value is _not_ provided. Otherwise, the generator is likely to cause confusion when the test doesn't generator the expected result. Signed-off-by: JP-Ellis <[email protected]>
Signed-off-by: JP-Ellis <[email protected]>
The ability to add additional fields to either the integration JSON or FFI JSON representations is not used, and may never be needed. Signed-off-by: JP-Ellis <[email protected]>
Even though this is failing on Python 3.8, I will still merge this as Python 3.8 is being dropped imminently (#796) |
matcher POC
I just want to verify if this is headed in the right direction