Types in middle
¶
Typing hints (from PEP 484) are a major improvement to the Python language when dealing with documentation, code readability, static (and also runtime) code analysis and, in some cases, to provide extra logic to your code (which is, of course, the goal of middle
).
middle
supports a lot of builtin types, some from regularly used modules. There’s also the possibility of customize middle
to support many other types, including custom ones.
Supported types¶
str
int
float
bool
bytes
datetime.date
datetime.datetime
decimal.Decimal
enum.Enum
typing.Dict
typing.List
typing.Set
typing.Tuple
typing.Union
dict
, list
, set
¶
Since dict
, list
and set
can’t have a distinguished type, they will not be supported by middle
. Instead, use typing.Dict
, typing.List
and typing.Set
, respectively.
datetime.date
and datetime.datetime
¶
For now, middle
depends on one extra requirements to properly handle date
and datetime
objects, which is python-dateutil
(to properly format datetime
string representations into datetime
instances), and it is generally found on most Python projects / libraries already (that handles datetime
string parsing).
Important
All naive datetime
string or timestamp representations will be considered as UTC (and converted accordingly) by middle
, thus any of this objects or representations that are not naive will be automatically converted to UTC for uniformity. Naive datetime
objects will transit with the current machine timezone and converted to UTC for uniformity (this transition can be modified to be considered as UTC, see more about configuring middle).
Warning
Even though the datetime
API provides us two methods for getting the current date and time, now
and utcnow
, both instances will be created without tzinfo
, thus making them naive. Basically, it means that no one can determine if a datetime object is not naive if the timezone is not explicitly provided:
>>> from datetime import datetime
>>> x = datetime.now()
>>> x
datetime.datetime(2018, 7, 9, 14, 21, 44, 624833)
>>> x.tzinfo is None
True
>>> y = datetime.utcnow()
>>> y
datetime.datetime(2018, 7, 9, 17, 22, 12, 673103)
>>> y.tzinfo is None
True
The recomended way to get an aware UTC datetime object would be:
>>> from datetime import datetime, timezone
>>> datetime.now(timezone.utc)
datetime.datetime(2018, 7, 9, 17, 26, 8, 874805, tzinfo=datetime.timezone.utc)
Examples¶
Considering a machine configured to GMT-0300 timezone at 10:30 AM local time:
>>> import datetime
... import pytz
>>> from middle.dtutils import dt_convert_to_utc
... from middle.dtutils import dt_from_iso_string
... from middle.dtutils import dt_from_timestamp
... from middle.dtutils import dt_to_iso_string
>>> dt_to_iso_string(datetime.datetime.now())
'2018-07-10T13:30:00+00:00'
>>> dt_to_iso_string(datetime.datetime.utcnow())
'2018-07-10T16:30:00+00:00'
>>> dt_from_iso_string("2018-07-02T08:30:00+01:00")
datetime.datetime(2018, 7, 2, 7, 30, tzinfo=datetime.timezone.utc)
>>> dt_from_iso_string("2018-07-02T08:30:00")
datetime.datetime(2018, 7, 2, 8, 30, tzinfo=datetime.timezone.utc)
>>> dt_from_timestamp(1530520200)
datetime.datetime(2018, 7, 2, 8, 30, tzinfo=datetime.timezone.utc)
>>> dt_from_timestamp(1530520200.000123)
datetime.datetime(2018, 7, 2, 8, 30, 0, 123, tzinfo=datetime.timezone.utc)
>>> dt_convert_to_utc(datetime.datetime(2018, 7, 2, 8, 30, 0, 0, pytz.timezone("CET")))
datetime.datetime(2018, 7, 2, 7, 30, tzinfo=datetime.timezone.utc)
>>> dt_convert_to_utc(dt_from_iso_string("2018-07-02T08:30:00+01:00"))
datetime.datetime(2018, 7, 2, 7, 30, tzinfo=datetime.timezone.utc)
One plus of using datetime
in middle
is that it accepts a wide range of inputs, having in mind that we’re talking about Python here (see the datetime
constructor to understand why):
>>> from datetime import datetime, timezone
>>> import middle
>>> class TestModel(middle.Model):
... created_on: datetime = middle.field() # for Python 3.6+
... created_on = middle.field(type=datetime) # for Python 3.5
>>> TestModel(created_on=datetime.now())
TestModel(created_on=datetime.datetime(2018, 7, 10, 15, 1, 6, 121325, tzinfo=datetime.timezone.utc))
>>> TestModel(created_on=datetime.now(timezone.utc))
TestModel(created_on=datetime.datetime(2018, 7, 10, 15, 1, 40, 769369, tzinfo=datetime.timezone.utc))
>>> TestModel(created_on="2018-7-7 4:42pm")
TestModel(created_on=datetime.datetime(2018, 7, 7, 16, 42, tzinfo=datetime.timezone.utc))
>>> TestModel(created_on=1530520200)
TestModel(created_on=datetime.datetime(2018, 7, 2, 8, 30, tzinfo=datetime.timezone.utc))
>>> TestModel(created_on=(2018, 7, 9, 10))
TestModel(created_on=datetime.datetime(2018, 7, 9, 13, 0, tzinfo=datetime.timezone.utc))
>>> TestModel(created_on=(2018, 7, 9, 10, 30, 0, 0, 1))
TestModel(created_on=datetime.datetime(2018, 7, 9, 9, 30, tzinfo=datetime.timezone.utc))
Important
In the last input (in the example above), where a tuple of 8 integers were given for the created_on
parameter, the last value corresponds to the UTC offset in hours.
“But I only trust on [arrow|momentum|maya]”¶
Well, I don’t blame you. These operations regarding date
and datetime
were created for middle
to provide an out-of-the-box solution for the most used types in Python, but, don’t worry, you can override these operations with your own. Just head out to extending and catch up some examples.
Enum¶
Most enum types will be directly available from and to primitives by acessing the .value
attribute of each instance. A lot of complex examples can work out of the box:
>>> import enum
... import middle
>>> class AutoName(enum.Enum):
... def _generate_next_value_(name, start, count, last_values):
... return name
>>> class TestAutoEnum(AutoName):
... FOO = enum.auto()
... BAR = enum.auto()
... BAZ = enum.auto()
>>> @enum.unique
... class TestStrEnum(str, enum.Enum):
... CAT = "CAT"
... DOG = "DOG"
... BIRD = "BIRD"
>>> @enum.unique
... class TestIntEnum(enum.IntEnum):
... FIRST = 1
... SECOND = 2
... THIRD = 3
>>> class TestFlagEnum(enum.IntFlag):
... R = 4
... W = 2
... X = 1
>>> instance = TestModel(auto_enum=TestAutoEnum.FOO, str_enum=TestStrEnum.CAT, int_enum=TestIntEnum.FIRST, flg_enum=TestFlagEnum.R | TestFlagEnum.W)
>>> instance
TestModel(auto_enum=<TestAutoEnum.FOO: 'FOO'>, str_enum=<TestStrEnum.CAT: 'CAT'>, int_enum=<TestIntEnum.FIRST: 1>, flg_enum=<TestFlagEnum.R|W: 6>)
>>> data = middle.asdict(instance)
>>> data
{'auto_enum': 'FOO', 'str_enum': 'CAT', 'int_enum': 1, 'flg_enum': 6}
>>> TestModel(**data) # to test if flg_enum=6 would work
TestModel(auto_enum=<TestAutoEnum.FOO: 'FOO'>, str_enum=<TestStrEnum.CAT: 'CAT'>, int_enum=<TestIntEnum.FIRST: 1>, flg_enum=<TestFlagEnum.R|W: 6>)
Future plans on types¶
There are some types in the Python stdlib that are planned to be part of middle
in the near future:
- uuid.uuid[1,3-5]
If there’s a type you would like to see on middle
, feel free to open an issue or submit a PR.