Skip to content
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

Add messages implementation for python #165

Merged
merged 5 commits into from
Jan 29, 2025

Conversation

elchupanebrej
Copy link
Contributor

πŸ€” What's changed?

Add python implementation

🏷️ What kind of change is this?

  • ⚑ New feature (non-breaking change which adds new behaviour)

πŸ“‹ Checklist:

  • I agree to respect and uphold the Cucumber Community Code of Conduct
  • I've changed the behaviour of the code
    • I have added/updated tests to cover my changes.
  • Users should know about my change
    • I have added an entry to the "Unreleased" section of the CHANGELOG, linking to this pull request.

This text was originally generated from a template, then edited by hand. You can modify the template here.

@elchupanebrej
Copy link
Contributor Author

This address to #162

Copy link
Contributor

@mpkorstanje mpkorstanje left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At a glance this doesn't follow the pattern used by the other language implementations in quite a few ways. Please follow up the directions from #162 around code generation.

I also don't understand the purpose of the samples directory.

@elchupanebrej
Copy link
Contributor Author

@mpkorstanje

  1. For Python exists a tool that allows generating Pydantic models directly from json schema https://github.com/koxudaxi/datamodel-code-generator - So this allows not including an extra layer with templating. If you insist - I'll rewrite this by that approach.

  2. Samples are taken from gherkin repository to validate if serialization/deserialization works well. Adding external data to a python package is always an egg-chicken problem. I don't like to add external files by makefiles or any kind of scripts because they are always platform dependent. If another approach is used in cucumber - please let me know, and I'll adapt this PR

@elchupanebrej elchupanebrej marked this pull request as draft July 19, 2023 15:08
@mpkorstanje
Copy link
Contributor

mpkorstanje commented Jul 19, 2023

For Python exists a tool that allows generating Pydantic models directly from json schema

You can use Pydantic if you can make it fit into the make clean-all generate-all workflow. Though I suspect your manual edits might pose a problem.

Samples are taken from gherkin repository to validate if serialization/deserialization works well.

Consider narrowing this down to a few representative examples. Currently it is hard to see the forest for the trees.

Copy link
Contributor

@luke-hill luke-hill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're going to copy lots of the cck it would be better to fetch the data using some form of call rather than C+P as this is currently being rapidly updated

python/pyproject.toml Show resolved Hide resolved
.github/workflows/test-python.yml Outdated Show resolved Hide resolved
@elchupanebrej elchupanebrej force-pushed the python-impl branch 2 times, most recently from ee63f2a to 358b36b Compare December 31, 2023 18:50
python/RELEASING.md Outdated Show resolved Hide resolved
@mpkorstanje
Copy link
Contributor

mpkorstanje commented Jan 4, 2024

@elchupanebrej

Samples are used in tests. More complex tests could exist. I insist to include them for now

What purpose do these tests serve? They'll be a hassle to update if/when the schema changes.

@luke-hill
Copy link
Contributor

Hi @elchupanebrej - Just checking in to see where you're up to with this. Is this something you're still working on?

@elchupanebrej
Copy link
Contributor Author

Hi @elchupanebrej - Just checking in to see where you're up to with this. Is this something you're still working on?

Hi @luke-hill, sorry for the long response, hadn't time to work on the project. I'll try to create another merge request that will conform to the building process.

@elchupanebrej elchupanebrej force-pushed the python-impl branch 2 times, most recently from 3256104 to effdd2b Compare September 4, 2024 20:54
@elchupanebrej
Copy link
Contributor Author

The PR was updated with Makefile. Model is stable, so generated code is totally same to version, which was generated at first try

@mpkorstanje I kindly ask you to review the code and take a release part. I didn't get into all deps&relations between release tools.

@mpkorstanje mpkorstanje marked this pull request as ready for review September 4, 2024 21:26
@mpkorstanje
Copy link
Contributor

Left a few quick remarks, will have to take a deeper look later.

python/Makefile Outdated Show resolved Hide resolved
@@ -0,0 +1,12 @@
{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.3.0"},"os":{"name":"darwin","version":"22.4.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"19.7.0"}}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good process what you've done here. Just commenting for documentation.

I think as/when you have gotten this all working, it would be good to migrate this and others to the CCK proper. WDYT? (Maybe something for 2025?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

migrate this and others to the CCK proper
It must work with CCK now in all possible cases. If it doesn't - let write tests & fix

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elchupanebrej As a test I'm not happy with a "sample test". As said before this create a circular dependency between the code that generates the samples and messages.

Tests for messages can be limited to testing whether the code was generated and serialization works correctly. This is does not test those things specifically while still testing many other - less relevant things.

@luke-hill what exactly do you mean by "migrating this and others to the cck"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mpkorstanje sorry for bothering you, it seems I can't catch a point:

Samples of messages in the CCK repository are stored as examples. Every tool that uses messages has to use them (at least serialize when some event is emitted, and deserialize when this message comes to some reporter). So I took the full suite of test data from the CCK repo and checked that the models generated were successfully parsed that messages into the model, and after that deserialized them to totally the same JSON. Could you please describe more precisely what kind of tests would be OK: would be enough if some model(for every kind of message) would be created, serialized and deserialized perfectly to the totally same model?

Copy link
Contributor

@mpkorstanje mpkorstanje Oct 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CCK uses the messages to generate the output of a canonical cucumber execution. For this is needs the messages. The value the CCK adds isn't that it generates a sample of each messages, but rather that the collection of messages as a whole. So it can for example express relationships between messages.

This dependency also means that it can't be used as test data in messages. That would result in a circular dependency.

Now for messages the exact testing strategy depends on the framework and language used.

For example for Javascript, the object and it's json representation are almost identical so there is little to test at all. And because the code is generated, it doesn't seem nesesary to test every message either.

So you can see we do a round trip test of one moderately complex message and not much more.

https://github.com/cucumber/messages/blob/main/javascript/test/messagesTest.ts

For Java serialization is more complicated. It does not have a concept of undefined. So we got tests to check for that.

https://github.com/cucumber/messages/blob/main/java/src/test/java/io/cucumber/messages/NdjsonSerializationTest.java

Now I don't know enough about Python to tell you exactly what to test. I can't tell you about pitfalls I don't know about. But I imagine if third party code generator is used, a simple round trip should be enough.

Copy link
Contributor

@luke-hill luke-hill Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To add as well. For the ruby implementation we don't even do something that complex. All we do is "open" the message (Which is an Envelope class), then reclose it again. And check that in doing so we don't change anything. https://github.com/cucumber/messages/blob/main/ruby/spec/cucumber/messages/acceptance_spec.rb

If you're finding that you're going down the rabbit hole of doing lots of testing here, then something is likely going wrong. Messages are the building block that everything else is based off, not the other way around.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copied ruby approach

@elchupanebrej elchupanebrej force-pushed the python-impl branch 2 times, most recently from ae519d7 to 99e72d6 Compare September 7, 2024 15:39
Copy link
Contributor

@mpkorstanje mpkorstanje left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to have been a misunderstanding.

So just to clarify.

Either:

  • Source is generated by the Ruby codegen script
  • Generated source is checked in

Or:

  • Source is generated by the python build process.
  • Generated source is not checked in.
  • Make targets print a message that code code gen is handled by Python.

Which option are you going for now?

.github/workflows/test-python.yml Outdated Show resolved Hide resolved
python/Makefile Outdated Show resolved Hide resolved
python/pyproject.toml Show resolved Hide resolved
@@ -0,0 +1,12 @@
{"meta":{"ci":{"buildNumber":"154666429","git":{"remote":"https://github.com/cucumber-ltd/shouty.rb.git","revision":"99684bcacf01d95875834d87903dcb072306c9ad"},"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429"},"cpu":{"name":"x64"},"implementation":{"name":"fake-cucumber","version":"16.3.0"},"os":{"name":"darwin","version":"22.4.0"},"protocolVersion":"22.0.0","runtime":{"name":"node.js","version":"19.7.0"}}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elchupanebrej As a test I'm not happy with a "sample test". As said before this create a circular dependency between the code that generates the samples and messages.

Tests for messages can be limited to testing whether the code was generated and serialization works correctly. This is does not test those things specifically while still testing many other - less relevant things.

@luke-hill what exactly do you mean by "migrating this and others to the cck"?

python/src/message_samples/minimal/minimal.feature.ts Outdated Show resolved Hide resolved
python/src/messages.py Outdated Show resolved Hide resolved
python/src/_messages.py Outdated Show resolved Hide resolved
]
dependencies = [
"importlib_resources",
"pydantic>=2.0.3"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really necessary to use and add pydantic as a dependency ?
Many people are still on pydantic v1, and this would require pytest-bdd users to upgrade to pydantic v2 since pytest-bdd will soon depend on gherkin

Aren’t stdlib dataclasses enough?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

importlib_resources is also, from what I can see, only used for tests which I'm not sure is needed either

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@youtux Yes, this is technically possible, but such realization will be dependent on some library like https://github.com/lidatong/dataclasses-json (the best option for now), which are not as good supported as pydantic

From another perspective - testing utilities are selected at the start of a project, so if the messages package will be used somewhere - it most probably would be dependent on the new version of Pydantic

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but there are many projects using pytest-bdd for years, and this would be an issue.
We can do without pydantic in a very simple way. We can use data classes, then when we need to serialise to json we call asdict(model). If we need custom encoders (e.g. for date times) we can implement a simple JsONEncoder and pass that to json.dumps(asdict(model), encoder=…).

Or also just implement custom serialiser for each object

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this case, we have to implement dict_factory for dataclass.asdict, which will have to take in count Enums, or there would be an issue with serialization to JSON. And deserialisation to the dataclass also will be an issue (Enums again)
And pydantic covers both of this issues

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really think we should not bring in a big dependency like pydantic here, especially since it has made a big API change in v2, and I can see it make it difficult for users to adopt this library if it conflicts with their pydantic v1 requirement.

What's the use of pydantic here? I don't see it being used for serialisation / deserialisation here.
What's the API of this library going to look like?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Messages library is hardly used for serialization/deserialization, for example:

  • Test runner must produce messages in the ndjson format, so it uses model of "messages" lib to represent outcomes, messages lib serializes and validates against Json schema (non-directly).
  • Test reporter consumes ndjson stream of messages and uses "messages" library to deserialize inputs and validate them.

So "messages" lib is a bridge between test runner and test reporter (potentially from different languages ecosystems)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, but how is the API of this lib supposed to look like?

from cucumber_messages import ???

???

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@youtux , please check python/tests/test_model_load.py test in this PR (I'll rework tests later).

For example reporting in the pytest-bdd-ng uses this particular model:
https://github.com/elchupanebrej/pytest-bdd-ng/blob/default/src/pytest_bdd/message_plugin.py

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exrta dependencies were declined

@elchupanebrej
Copy link
Contributor Author

Thanks for great review, return later this week and will update all things accordingly πŸ˜€

@elchupanebrej
Copy link
Contributor Author

This would use lidatong/dataclasses-json#442 in future

@elchupanebrej
Copy link
Contributor Author

elchupanebrej commented Dec 19, 2024

@mpkorstanje @luke-hill @jsa34 @youtux
Please make a new review for this PR:

  1. Model doesn't use Pydantic anymore (dataclasses are used)
  2. Model generation depends on ruby templates (so no more Makefile workarounds)
  3. Tests are provided in the same manner as at other language implementations

Copy link
Contributor

@luke-hill luke-hill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed 2/3rds of this PR so far and only got a couple of questions. So I'll pass that in for now. The other items are a bit more complex and I'll review at a later date (Might be after xmas now).

.github/workflows/test-codegen.yml Outdated Show resolved Hide resolved
.pre-commit-config.yaml Show resolved Hide resolved
codegen/templates/python.py.erb Show resolved Hide resolved
python/pyproject.toml Outdated Show resolved Hide resolved
Copy link
Contributor

@luke-hill luke-hill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and signed off 17/21 files, still got a few things to query

.github/workflows/test-python.yml Outdated Show resolved Hide resolved
codegen/templates/python.py.erb Show resolved Hide resolved
@youtux
Copy link
Contributor

youtux commented Jan 11, 2025

I must have missed something. Why are we not using the code generator from https://github.com/koxudaxi/datamodel-code-generator anymore?

It would look more maintainable to me to use that one, as supporting the current proposed solution requires people that both both Ruby and Python, rather than just Python.

Copy link
Contributor

@luke-hill luke-hill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a few other minor things, but it'll be easier for me to just run rubocop or something on it afterwards as it's just syntax-y stuff.

I've ignored the generated files and the remaining 3 files to sign off have review stuff, so I've finished on this now. Great stuff.

.github/workflows/test-python.yml Show resolved Hide resolved
codegen/generators/python.rb Outdated Show resolved Hide resolved
codegen/generators/python.rb Outdated Show resolved Hide resolved
codegen/generators/python.rb Outdated Show resolved Hide resolved
@luke-hill
Copy link
Contributor

I've resolved the generator issues I raised because I'd rather get this merged in and then just tackle those as a patch release later on myself as it'll speed things up and allow the python people to work on other items such as expressions and suchlike

* Fixup property type definitions
* Fixup property descriptions
  * Descriptions inlined where possible
  * Property descriptions are placed after properties per se
* Remove redundant double-quotes at type definitions
* Split enums and model templates
* Simplify gh-action test matrix
* Fixup empty project.toml settings
@elchupanebrej
Copy link
Contributor Author

I must have missed something. Why are we not using the code generator from https://github.com/koxudaxi/datamodel-code-generator anymore?

It would look more maintainable to me to use that one, as supporting the current proposed solution requires people that both Ruby and Python, rather than just Python.

  • Python environment to generate code was unwanted
  • There was an extra dependency

Copy link
Contributor

@luke-hill luke-hill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

22/23 files I'm good with. Few questions raised on final one - but might all be redundant. Top work.

As an aside - as/when I come to refactor the templates/generators, do you want the messages in 1 file per class like they are in some of the other languages. Not important for now, but just thought I'd make you aware that's quite easy with a bit of chopping that we do (Not required for this PR)

python/pyproject.toml Show resolved Hide resolved
python/pyproject.toml Outdated Show resolved Hide resolved
python/pyproject.toml Show resolved Hide resolved
@youtux
Copy link
Contributor

youtux commented Jan 23, 2025

I must have missed something. Why are we not using the code generator from https://github.com/koxudaxi/datamodel-code-generator anymore?

It would look more maintainable to me to use that one, as supporting the current proposed solution requires people that both Ruby and Python, rather than just Python.

  • Python environment to generate code was unwanted

  • There was an extra dependency

The extra dependency would only be a build-time dependency, so it wouldn't be needed by downstream users.

Copy link
Contributor

@luke-hill luke-hill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Top stuff. Please get a review from Rien/David also as they are the primary maintainers for Java/JS.

I don't foresee any major issues with other flavours such as go/dotnet e.t.c.

@luke-hill luke-hill dismissed mpkorstanje’s stale review January 29, 2025 17:27

Review items implemented

@luke-hill luke-hill merged commit 4ed7f02 into cucumber:main Jan 29, 2025
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants