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

Model config types are incorrect #1182

Closed
tonyandrewmeyer opened this issue Apr 10, 2024 · 5 comments · Fixed by #1183
Closed

Model config types are incorrect #1182

tonyandrewmeyer opened this issue Apr 10, 2024 · 5 comments · Fixed by #1183
Assignees
Labels
bug Something isn't working ops-next To be done before shipping the next version of ops

Comments

@tonyandrewmeyer
Copy link
Contributor

tonyandrewmeyer commented Apr 10, 2024

Model.config is typed to return a Model.ConfigData, which is a subclass of Model.LazyMapping, which is an ABC of Mapping[str, str]. However, the config is actually Mapping[str, int|float|str|bool].

RelationDataContent is also a LazyMapping subclass, and there the data is always [str, str].

The data is loaded into the lazy mapping via _ModelBackend.config_get, which is typed to return a dict[str, Model._ConfigOption]. Model._ConfigOption has a type literal string, a description string, and a default value (str, int, float, bool) - but that's the format for a dict loaded from the config/charmcraft YAML file. What we get from config-get, which is how the data is loaded, is:

root@secretconfig-0:/var/lib/juju/agents/unit-secretconfig-0/charm# ../../../tools/unit-secretconfig-0/config-get --format=json | jq
{
  "booleanopt": false,
  "floatopt": 3.14,
  "intopt": 42,
  "secretopt": "secret:co6tjmvmp25c77dhf980",
  "stringopt": "hello"
}

Problems:

  • LazyMapping should be more loosely typed, e.g. Mapping[str, int|float|str|bool]
  • _ConfigOption.type can be secret
  • _ModelBackend.config_get should return a dict[str, int|float|str|bool]
@tonyandrewmeyer tonyandrewmeyer added bug Something isn't working ops-next To be done before shipping the next version of ops labels Apr 10, 2024
@tonyandrewmeyer tonyandrewmeyer self-assigned this Apr 10, 2024
@tonyandrewmeyer
Copy link
Contributor Author

It seems like pyright should be picking up at least some of these, so that should be checked as well.

@tonyandrewmeyer
Copy link
Contributor Author

_ConfigOption is odd - it's not actually used in model.py at all, so it feels like it's in the wrong file. We don't load the config in CharmMeta, so it's only used in Harness, and only to define _RawConfig:

_RawConfig = TypedDict("_RawConfig", {'options': Dict[str, _ConfigOption]})

pyright doesn't pick up this issue because what we're basically doing is saying "give me a dict from this YAML string and you should believe it's this type". We don't have any schema validation for the config metadata.

@tonyandrewmeyer
Copy link
Contributor Author

pyright can't pick up the wrong type on config_get for a similar reason - we don't do any schema validation of what we get back from config-get - we just cast it to the type that we're expecting. There's no return type on ConfigData._load so pyright implicitly sets it to whatever the type of config_get is (even though that's different than the parent class). I think it fails to notice that data = self._lazy_data = self._load() has mismatching types because of the parent class definition. With ConfigData._load explicitly typed it does pick up problems.

@tonyandrewmeyer
Copy link
Contributor Author

For LazyMapping, pyright doesn't pick it up because anywhere config is being used, you need to cast (or type:ignore) it to something other than str to do anything non-string with it, since it could actually be a string. So that masks the problem. For example, you can have code like:

p = ops.Port('tcp', self.config["port"])

And pyright will complain, because self.config[x] isn't an int - but it still might not be after the fix, either. Basically, the config typing is too dynamic for pyright to figure it out, unless we put in some sort of schema validation.

@benhoyt
Copy link
Collaborator

benhoyt commented Apr 10, 2024

Excellent report and Pyright investigation -- thanks Tony.

tonyandrewmeyer added a commit that referenced this issue Apr 17, 2024
Corrects types related to the model configuration:

* `LazyMapping` may have Boolean, integer, or float values, as well as
strings
* `_ConfigOption` may have a `secret` value for `type - also move this
to [testing.py](ops/testing.py) since that's the only place it's used
and it's private
* `_ModelBackend.config_get` returns a dict with Boolean, integer,
float, or string values

The PR also sets a missing return type so that pyright would detect the
`config_get` issue.

There are no tests or pyright changes for the `_ConfigOption` change: we
don't generally rely on pyright rather than unit tests for ensuring the
typing is correct, but that doesn't seem feasible here. Let me know if
you think we do need something added here to prevent regression.

There are no tests or pyright changes for the `LazyMapping` change,
other than making sure we do have [test_model.py](tests/test_model.py)
exercise each of the config types. I don't think it's possible to have
pyright detect this issue - e.g. you can get it to show an error if you
try to use an integer config value as an integer
(`self.config["int-opt"] + 1`), but that's still the case after the fix,
since it could be a string (float, Boolean, secret) option. The test
could have `typing.assert_type` calls but (a) that's only in Python
3.11, and (b) I'm not convinced we really want to do that. Let me know
if you think we do need something added here to prevent regression.

Fixes #1182
chanchiwai-ray added a commit to chanchiwai-ray/hardware-observer-operator that referenced this issue May 2, 2024
After operator framework fixes the incorrect types in `model.config`, it
introduces some issues in our existing codebases since we used to assume
them to have type of `str` which is not correct in the first place.

[1] canonical/operator#1182
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working ops-next To be done before shipping the next version of ops
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants