-
Notifications
You must be signed in to change notification settings - Fork 14.2k
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
Fix mypy errors in airflow/utils/ #20482
Conversation
@@ -48,7 +48,7 @@ class VariableAccessor: | |||
class ConnectionAccessor: | |||
def get(self, key: str, default_conn: Any = None) -> Any: ... | |||
|
|||
class Context(TypedDict, total=False): | |||
class Context(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.
Not sure of this one cc: @uranusjr
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.
Why would you need that ? This means that we can omit any of the fields but IMHO that would be great if we can make assumption that all fields are present in Context - so that whenever we need, we just add empty/default values for them whenever we find it necessary.
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 shouldn’t really matter because totality is checked on initialisation, and we don’t actually instantiate the context object directly, only force-cast a dict into one. And I chose total=False
because it is arguably more correct semantically—the context does not always contain all the keys listed in this fake TypedDict.
What is the reason behind this change?
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 error:
airflow/utils/context.pyi:51: error: Unexpected keyword argument "total" for "__init_subclass__"
of "object"
class Context(TypedDict, total=False):
^
Found 1 error in 1 file (checked 60 source files)
try_number=try_number, | ||
) | ||
else: | ||
return "" # mypy |
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 also another place I'm confused about the best approach to it and whether to work on the parse_template_string
itself
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 way parse_template_string
works - it guarantess that either filename_jinja_template
or filename_template
is set. There is no way both will be None. The only "real" case where (currently) the if can be reached is if the "template_string" is "". But this is wrong anyway.
I'd say:
- raise RuntimeError in
parse_template_string
iftamplate_string
is "" - raise RuntimeError instead of
return ""'
- with "This should never happen" kind of message. - change conditions for
self.filename_template
andself.filename_jinja_template
tois not None
explicitly.
airflow/utils/timezone.py
Outdated
@@ -133,10 +138,10 @@ def make_aware(value: Optional[dt.datetime], timezone: Optional["Timezone"] = No | |||
value = value.replace(fold=1) | |||
if hasattr(timezone, 'localize'): | |||
# This method is available for pytz time zones. | |||
return timezone.localize(value) | |||
return cast(Any, timezone).localize(value) |
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.
Use #type: ignore instead?
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.
There is a discussion about MyPy supporting this case here: python/mypy#1424
One of the nice solutions suggested in the thred as a workaround would translate to:
localize = getattr(timezone, 'localize', None)
if localize is not None:
return localize(value)
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.
+1 to getattr
. A less-known fact: For user-defined classes (i.e. not something like int
or str
), hasattr
is actually roughly equivalent to
def hasattr(obj, name):
try:
getattr(obj, name)
except AttributeError:
return False
return True
So if you need to get the value at some point, getattr
is always superior to hasattr
.
@@ -82,13 +82,15 @@ def _render_filename(self, ti: "TaskInstance", try_number: int) -> str: | |||
context = Context(ti=ti, ts=ti.get_dagrun().logical_date.isoformat()) |
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 guess "total = False" came from here. I'd say we should have a way to create "default" Context with all the fields present with default values and override the fields needed.
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 found this on mypy python/mypy#7722 which fixes this but it seems it didn't fix it for stub file. Should we instead ignore the error and create issue in mypy? I'm thinking it's a bug considering the fix on the linked PR to the issue
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 think we should refactor Context.__init__()
and how it interacts with TaskInstance.get_template_context()
. Let’s have this now and I’ll do the refactoring later.
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.
But removing total=False
shouldn’t work here—it should be the other way around because this requires non-totality! We should keep total=False
and find another solution.
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 have ignored it, for now, to wait for refactoring in another PR. Hope I understand correctly?
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.
Few suggestions left :)
Thank you! Will work on them. |
7ab58ee
to
7de29e2
Compare
airflow/utils/context.pyi
Outdated
class Context(TypedDict, total=False): | ||
class Context(TypedDict, total=False): # type: ignore |
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.
What is the error you get if you don’t ignore here? (For debugging reference later.)
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.
Here:
airflow/utils/context.pyi:51: error: Unexpected keyword argument "total" for "__init_subclass__"
of "object"
class Context(TypedDict, total=False):
^
Found 1 error in 1 file (checked 60 source files)
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.
total=False
is Python 3.8 + feature https://docs.python.org/3/whatsnew/3.8.html
Not sure if it will be really helpful. I'd say we should specify all possible keys in Context.
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.
Have removed it since we still support python 3.7
airflow/utils/timezone.py
Outdated
def coerce_datetime(v: Optional[dt.datetime]) -> Optional[DateTime]: | ||
def coerce_datetime(v: Optional[dt.datetime]) -> Optional[dt.datetime]: |
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 shouldn’t be necessary; coerce_datetime
always returns None or a pendulum.DateTime
. Also I believe this will cause some errors to pop up elsewhere because there are functions expecting this to return a pendulum.DateTime
specifically.
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.
See the error here when changed back
airflow/utils/timezone.py:213: error: Incompatible return value type (got "datetime", expected
"Optional[DateTime]")
return v if v.tzinfo else make_aware(v)
^
Found 1 error in 1 file (checked 60 source files)
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 think we need additional overload of make_aware
with DateTime
which should fix it. https://mypy.readthedocs.io/en/stable/more_types.html#function-overloading
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 tried adding an additional overload of make_aware
but mypy errors that it'll never be matched because of the arguments.
airflow/utils/timezone.py:113: error: Overloaded function signature 3 will
never be matched: signature 2's parameter type(s) are the same or broader
def make_aware(value: dt.datetime, timezone: Optional[dt.tzinfo] = N...
^
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.
OK. I figured it out. The preoblm is that DateTime derives from dt.datetime. And we have to specify DateTime overloads before datetimes ones.
What worked for me:
- make_aware overloads:
@overload
def make_aware(value: None, timezone: Optional[dt.tzinfo] = None) -> None:
...
@overload
def make_aware(value: DateTime, timezone: Optional[dt.tzinfo] = None) -> DateTime:
...
@overload
def make_aware(value: dt.datetime, timezone: Optional[dt.tzinfo] = None) -> dt.datetime:
...
def make_aware(value: Optional[dt.datetime], timezone: Optional[dt.tzinfo] = None) -> Optional[dt.datetime]:
- coerce_datetime overloads:
@overload
def coerce_datetime(v: None) -> None:
...
@overload
def coerce_datetime(v: DateTime) -> DateTime:
...
@overload
def coerce_datetime(v: dt.datetime) -> DateTime:
...
def coerce_datetime(v: Optional[dt.datetime]) -> Optional[DateTime]:
The PR most likely needs to run full matrix of tests because it modifies parts of the core of Airflow. However, committers might decide to merge it quickly and take the risk. If they don't merge it quickly - please rebase it to the latest main at your convenience, or amend the last commit of the PR, and push it with --force-with-lease. |
No worries. I will close that. Please take a look to that anyway to see if you want to amend or express anything here differently, particularly those overloadings. I am not sure why they are necessary |
The overloads are because we don't want a breaking change here(I think). |
7670a41
to
520e81b
Compare
airflow/utils/timezone.py
Outdated
if TYPE_CHECKING: | ||
from pendulum.tz.timezone import Timezone | ||
pass |
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 still needed?
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.
Can be removed I guess.
520e81b
to
4f4ef54
Compare
^ Add meaningful description above
Read the Pull Request Guidelines for more information.
In case of fundamental code change, Airflow Improvement Proposal (AIP) is needed.
In case of a new dependency, check compliance with the ASF 3rd Party License Policy.
In case of backwards incompatible changes please leave a note in UPDATING.md.