-
Notifications
You must be signed in to change notification settings - Fork 122
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
A few minor tweaks now that we're on Python 3.8 #890
Conversation
This includes: * Use TypedDict classes to simplify type definitions * Use of "typing" module instead of "typing_extensions" where possible * Use of set literals and comprehensions instead of set(...) * Remove 't' arg to open() as it's the default with encoding * Use OSError instead of IOError as the latter is now an alias * Use b'' instead of bytes() * One use of f-string that apparently flynt didn't pick up Will follow up this commit with another on the same PR to fix styling.
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'm not very sure about the TypedDict changes, and changing LayerDict to _LayerDict, I'd like to understand the rationale a bit better.
ops/charm.py
Outdated
scope: _Scopes | ||
|
||
class _MultipleRange(TypedDict): | ||
range: str |
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 looks weird. Why is _MultipleRange turned into a type, but _StorageMetaDict is not? Also, I think there should be another space here.
Note that I didn't try validating all the changes here, I just happened to see this one.
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, that was weird, wasn't it! It turned out it was because some are to define the shape of dicts that have "-" in their keys, like user-id
and group-id
, which of course can't be identifiers/class attributes. So those have to remain non-class syntax. I figured it was too confusing and weird to have both types, so I backed this change out.
Which doesn't leave much of a change, but oh well. :-)
ops/charm.py
Outdated
'required': List[str]}, | ||
total=False) | ||
|
||
class _ActionMetaDict(TypedDict, total=False): |
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 the first that I've seen meta arguments to classes (total=False
).
Apparently __init_subclass__
has been a thing since about python 3.6.
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.
From the arg, I'd suspect that TypedDict
is @dataclass
, though I haven't actually looked at the class definition.
Do we have use for pseudo-metaclass stuff other than the __set_name__
changes from the last PR (part of the same PEP as __init_subclass__
, IIRC)?
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.
Keep up with the times, @jameinel. ;-) Nah, seriously, Python moves way too fast these days IMO, particularly in the typing world.
ops/charm.py
Outdated
_ContainerMetaDict = TypedDict( | ||
'_ContainerMetaDict', {'mounts': List[_MountDict]}) | ||
class _ContainerMetaDict(TypedDict): | ||
mounts: List[_MountDict] | ||
|
||
_CharmMetaDict = TypedDict( |
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 should also be a class, vs a TypedDict instantiation
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.
Not quite sure what you mean here?
ops/framework.py
Outdated
@@ -613,7 +612,7 @@ def __init__(self, storage: Union[SQLiteStorage, JujuStorage], | |||
|
|||
# Parse the env var once, which may be used multiple times later | |||
debug_at = os.environ.get('JUJU_DEBUG_AT') | |||
self._juju_debug_at = (set(x.strip() for x in debug_at.split(',')) | |||
self._juju_debug_at = ({x.strip() for x in debug_at.split(',')} |
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 do wonder if this is actually better syntax :) But it exists, and I wouldn't block if someone was using 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.
Yeah, same. But speaking of better syntax, looking at it again I didn't like the nested inline if-else with set comprehension, so I've also split it onto multiple lines. Hopefully clearer now.
ops/lib/__init__.py
Outdated
@@ -185,7 +185,7 @@ def _parse_lib(spec): | |||
logger.debug(" Parsing %r", spec.name) | |||
|
|||
try: | |||
with open(spec.origin, 'rt', encoding='utf-8') as f: | |||
with open(spec.origin, encoding='utf-8') as f: |
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 feels like something that might be redundant with a default, but I don't mind being explicit to the developers as to expected behavior. Do you know why this was removed?
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, I guess pyupgrade removes it because it's the default (now? not sure how long that's been). Read/r has been the default forever, maybe the text/t default came later. But I can see where this is debatable, so I've reverted this change and just left it explicit.
ops/pebble.py
Outdated
http: Optional[_HttpDict] | ||
tcp: Optional[_TcpDict] | ||
exec: Optional[_ExecDict] | ||
threshold: Optional[int] | ||
|
||
_AuthDict = TypedDict('_AuthDict', |
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 really don't understand how it found all the others, but keeps missing some of them.
Is there a special field that isn't supported by the class syntax? (user-id looks suspicious). Hm, that makes me wonder if the new syntax is actually better, or whether it just sets you up to think of things in terms of a Type, when it really is just a dict. (it is far fewer quotes and commas, but that seems to be the only real difference)
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 haven't gone through an automated process that closely -- iterative improvements will take a bit, and it's easier (from my POV) to take a single "I ran this tool" PR and merge it into main
with a smaller PR to reconcile what it may have missed later than to line-item them.
In general, tend to look at stuff like this and say "well, since Python is still pretty dependent on the exact ordering of class/variable definition in a single file in 2023, maybe converting this particular one to a class would result in some impossible intermediate bytecode". I don't know why a fully-completed class definition if (if this were just class _AuthDict(TypedDict, ...
) wouldn't have finished bytecode evaluation by then, but it's sometimes surprisingly fragile.
It doesn't hurt to see what happens if it's replaced by hand, but I'd also expect the tool to be a little conservative.
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.
ops/pebble.py
Outdated
@@ -702,12 +712,12 @@ class Layer: | |||
#: Mapping of check to :class:`Check` defined by this layer. | |||
checks: Dict[str, 'Check'] | |||
|
|||
def __init__(self, raw: Optional[Union[str, 'LayerDict']] = None): | |||
def __init__(self, raw: Optional[Union[str, '_LayerDict']] = 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.
Given the number of references to LayerDict, why are we making it 'private' ? I'm not sure if any of these should be prefixed with _ if the intent is that people are going to use them outside of 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.
I actually really like this change. The Python ecosystem is always frantic, and DeepDiff
requires a Rust compiler as of... sometime last week. "Is this dict equal to that dict?" gets really snarly when there are dicts nested under it, and encouraging to avoid it by making it more annoying is a plus from my point of view.
In addition, the fields under it are comprised of dunder types anyway.
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 wanted to make it private because I don't really want us to commit to the type annotations being stable just yet. But it's unnecessary churn and could potentially break people (even if only typing it doesn't seem worth it). So I've reversed that part of the change.
…fixes" This reverts commit b2f6832.
This reverts commit 8bf0b57.
This excludes: * The TypedDict changes, only half of which could be converted to the class-based syntax anyway, due to many keys having '-' in them, like "user-id". So keep them consistent. * Keep the "rt" and similar mode arguments to open() to be explicit, even when they're default. Also simplify the nested inline if-else with set comprehension for _juju_debug_at into multiple lines -- simpler and clearer.
Originally this PR was running
pyupgrade --py38-plus
on the codebase, to pick up a few new features in Python 3.7 and 3.8 (apart from f-strings, which was done in #889). However, in particular we didn't like how it convertedTypedDict
to the class syntax, because then half of them were converted (the ones without-
in the field names can't be converted). So avoid that so they're all consistent.This includes:
This PR also: