feat: support for generating charmcraft YAML from Python classes
A new package, ops-tools is added. This currently only provides a script to update charmcraft.yaml based on config and action Python classes, but is designed to also offer other tools in the future. It is released simultaneously with the other ops-* packages, but is not required by any of the others, or offered as an extra.
Charmers can use the exported functions (which take a Python class object and return a dictionary suitable for serialisation to YAML), but are expected to use the provided script (that works with a charmcraft.yaml file and Python modules).
Classes can provide a to_juju_schema() method if they need to provide YAML in a different way. The script will pass the generated config/action as a base to optionally work from.
The implementation is designed to work with the load_config and load_params functionality in ops. In particular, that means working with four types of class:
- A generic Python class (no inheritance other than object).
- Standard library dataclasses (I personally believe these are the best choice for almost all cases).
- Pydantic dataclasses
- Pydantic 2.x BaseModel classes
Only reference documentation is included in this PR (Preview). The intention is that there will be a follow-up PR that includes at least how-to documentation.
More details are available in the spec. This PR replaces #1702.
- Pydantic dataclasses
- Pydantic 2.x BaseModel classes
IIRC these two actually differ in minutae when class is instantiated.
It's something along the lines of:
- pydantic dataclass can be instantiated with wrong types, doesn't automatically raise ValueError (?)
- pydantic BaseModel coerces types, for example either str --> int or int --> str, sorry I don't recall which way, I think each field applies own type as "constructor" with raw value as the parameter
A decent set of tests is needed here, even if it feels like we're testing Pydantic and now our code.
P.S. Refs: https://docs.pydantic.dev/latest/concepts/conversion_table/ https://docs.pydantic.dev/latest/concepts/strict_mode/ https://docs.pydantic.dev/latest/concepts/types/#strict-types (the last feels like advice to the charmer, rather than something we can do on our end; but we do need to cover these with tests)
IIRC these two actually differ in minutae when class is instantiated.
None of the classes are actually instantiated in the YAML generation.
A decent set of tests is needed here, even if it feels like we're testing Pydantic and now our code.
There are tests for both pydantic dataclasses and BaseModel subclasses. For BaseModel with actions, I don't think we need much more than what we have, because the bulk of the work is done by pydantic rather than this code. For the others, if you'd like to suggest specific cases to check I'm happy to add additional tests.
this brings back some memories: https://github.com/PietroPasotti/jinx
Tracking issue: https://github.com/canonical/operator/issues/2163