chrisimcevoy / pyoda-time

A better date and time API for Python
https://pyodatime.org/
Apache License 2.0
1 stars 0 forks source link

PyCharm gets confused about metaclass properties #159

Open chrisimcevoy opened 1 month ago

chrisimcevoy commented 1 month ago

While porting Pyoda Time, we need to port static properties from C#, which are properties which are accessed on the class rather than on an instance.

Because these static properties often return instances of the type they are declared on (example), I consistently implement these in Python as properties on a metaclass. Doing this also sidesteps troublesome circular imports by delaying them (example) because in Python we can't always refer to classes the way you might in C#.

However, this probably isn't a very common technique, and IDEs such as PyCharm don't seem to know about the property's return type (even if they know about the property's existence).

Take a simple example like this:

from pyoda_time import Duration, PyodaConstants

foo = PyodaConstants.UNIX_EPOCH + Duration.epsilon

If you were to over over the variable, you would expect the IDE to tell you the type of the variable. But it can't work out what that is.

image

(Note that mypy understands the type of the variable just fine - this is merely a UX thing / IDE issue)

This means that e.g. code completion don't work as you might like.

image

One thing we might do is to add type hints to the classes which use , for example:

--- a/pyoda_time/_pyoda_constants.py
+++ b/pyoda_time/_pyoda_constants.py
@@ -44,6 +44,11 @@ class _PyodaConstantsMeta(type):

 class PyodaConstants(metaclass=_PyodaConstantsMeta):
+    # Type hints for metaclass properties
+    UNIX_EPOCH: Instant
+    JULIAN_EPOCH: Instant
+    BCL_EPOCH: Instant
+
     HOURS_PER_DAY: Final[int] = 24
     DAYS_PER_WEEK: Final[int] = 7
     SECONDS_PER_MINUTE: Final[int] = 60

and:

--- a/pyoda_time/_duration.py
+++ b/pyoda_time/_duration.py
@@ -64,6 +64,14 @@ class _DurationMeta(type):
 class Duration(metaclass=_DurationMeta):
     """Represents a fixed (and calendar-independent) length of time."""

+    # Type hints for metaclass properties
+    zero: Duration
+    epsilon: Duration
+    max_value: Duration
+    min_value: Duration
+    one_week: Duration
+    one_day: Duration
+
     # Implementation note:
     #
     # Noda Time's `Duration` far exceeds the range of the equivalent BCL type, namely `TimeSpan`.

Doing so makes PyCharm aware of the variable type:

image

And makes code completion work again:

image

What doesn't work is in-IDE warnings about assignment to properties with no setter. But you can't have everything, and such assignments will still raise at runtime (if they aren't caught in mypy or other static type checker first).

chrisimcevoy commented 1 month ago

Uh-oh, this doesn't look right in sphinx docs...

image

chrisimcevoy commented 1 month ago

Warning for the above when you build html docs

WARNING: duplicate object description of pyoda_time.SystemClock.instance, other instance in pyoda_time, use :no-index: for one of them