New Upstream Release - python-marshmallow
Ready changes
Summary
Merged new upstream version: 3.19.0 (was: 3.18.0).
Resulting package
Built on 2022-12-30T23:17 (took 3m8s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases python3-marshmallow-docapt install -t fresh-releases python3-marshmallow
Lintian Result
Diff
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3d06386
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,81 @@
+*.py[cod]
+
+# C extensions
+*.so
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+lib
+lib64
+pip-wheel-metadata
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+htmlcov
+.tox
+nosetests.xml
+.cache
+.pytest_cache
+
+# Translations
+*.mo
+
+# Mr Developer
+.mr.developer.cfg
+
+# IDE
+.project
+.pydevproject
+.idea
+
+# Coverage
+cover
+.coveragerc
+
+# Sphinx
+docs/_build
+README.html
+
+*.ipynb
+.ipynb_checkpoints
+
+Vagrantfile
+.vagrant
+
+*.db
+*.ai
+.konchrc
+_sandbox
+pylintrc
+
+# Virtualenvs
+env
+venv
+
+# pyenv
+.python-version
+
+# pytest
+.pytest_cache
+
+# Other
+.directory
+*.pprof
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
diff --git a/AUTHORS.rst b/AUTHORS.rst
index f4aec16..a0a928a 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -170,3 +170,4 @@ Contributors (chronological)
- Isira Seneviratne `@Isira-Seneviratne <https://github.com/Isira-Seneviratne>`_
- Karthikeyan Singaravelan `@tirkarthi <https://github.com/tirkarthi>`_
- Marco Satti `@marcosatti <https://github.com/marcosatti>`_
+- Ivo Reumkens `@vanHoi <https://github.com/vanHoi>`_
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index ec2a084..2ae6c8a 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,16 @@
Changelog
---------
+3.19.0 (2022-11-11)
+*******************
+
+Features:
+
+- Add ``timestamp`` and ``timestamp_ms`` formats to `fields.DateTime`
+ (:issue:`612`).
+ Thanks :user:`vgavro` for the suggestion and thanks :user:`vanHoi` for
+ the PR.
+
3.18.0 (2022-09-15)
*******************
diff --git a/debian/changelog b/debian/changelog
index ecdafe2..667a2bc 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-marshmallow (3.19.0-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Fri, 30 Dec 2022 23:14:33 -0000
+
python-marshmallow (3.18.0-1) unstable; urgency=medium
* New upstream release
diff --git a/debian/patches/0001-remove-privacy-breaches b/debian/patches/0001-remove-privacy-breaches
index fb53228..e419ea4 100644
--- a/debian/patches/0001-remove-privacy-breaches
+++ b/debian/patches/0001-remove-privacy-breaches
@@ -1,9 +1,11 @@
From: Federico Ceratto <federico@debian.org>
Subject: Remove privacy breaches
---- a/docs/conf.py
-+++ b/docs/conf.py
-@@ -13,9 +13,6 @@
+Index: python-marshmallow.git/docs/conf.py
+===================================================================
+--- python-marshmallow.git.orig/docs/conf.py
++++ python-marshmallow.git/docs/conf.py
+@@ -13,9 +13,6 @@ extensions = [
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"alabaster",
@@ -13,7 +15,7 @@ Subject: Remove privacy breaches
]
primary_domain = "py"
-@@ -59,10 +56,6 @@
+@@ -59,10 +56,6 @@ html_theme_options = {
"logo": "marshmallow-logo.png",
"description": "Object serialization and deserialization, lightweight and fluffy.",
"description_font_style": "italic",
@@ -24,9 +26,11 @@ Subject: Remove privacy breaches
"opencollective": "marshmallow",
"tidelift_url": (
"https://tidelift.com/subscription/pkg/pypi-marshmallow"
---- a/docs/_templates/donate.html
-+++ b/docs/_templates/donate.html
-@@ -17,7 +17,6 @@
+Index: python-marshmallow.git/docs/_templates/donate.html
+===================================================================
+--- python-marshmallow.git.orig/docs/_templates/donate.html
++++ python-marshmallow.git/docs/_templates/donate.html
+@@ -17,7 +17,6 @@ If you find marshmallow useful, please c
{% if theme_opencollective %}
<p>
<a class="badge" href="https://opencollective.com/{{ theme_opencollective }}" target="_blank">
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 0000000..50441ab
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,2 @@
+marshmallow.docset
+marshmallow.tgz
diff --git a/setup.py b/setup.py
index 286ec75..44e8546 100644
--- a/setup.py
+++ b/setup.py
@@ -4,13 +4,13 @@ from setuptools import setup, find_packages
EXTRAS_REQUIRE = {
"tests": ["pytest", "pytz", "simplejson"],
"lint": [
- "mypy==0.971",
+ "mypy==0.990",
"flake8==5.0.4",
- "flake8-bugbear==22.9.11",
+ "flake8-bugbear==22.10.25",
"pre-commit~=2.4",
],
"docs": [
- "sphinx==5.1.1",
+ "sphinx==5.3.0",
"sphinx-issues==3.0.1",
"alabaster==0.7.12",
"sphinx-version-warning==1.1.2",
diff --git a/src/marshmallow/__init__.py b/src/marshmallow/__init__.py
index 6ff7589..3ec24fe 100644
--- a/src/marshmallow/__init__.py
+++ b/src/marshmallow/__init__.py
@@ -16,7 +16,7 @@ from marshmallow.utils import EXCLUDE, INCLUDE, RAISE, missing, pprint
from . import fields
-__version__ = "3.18.0"
+__version__ = "3.19.0"
__parsed_version__ = Version(__version__)
__version_info__: tuple[int, int, int] | tuple[
int, int, int, str, int
diff --git a/src/marshmallow/fields.py b/src/marshmallow/fields.py
index f153b5f..b53562a 100644
--- a/src/marshmallow/fields.py
+++ b/src/marshmallow/fields.py
@@ -1218,11 +1218,14 @@ class DateTime(Field):
Example: ``'2014-12-22T03:12:58.019077+00:00'``
:param format: Either ``"rfc"`` (for RFC822), ``"iso"`` (for ISO8601),
- or a date format string. If `None`, defaults to "iso".
+ ``"timestamp"``, ``"timestamp_ms"`` (for a POSIX timestamp) or a date format string.
+ If `None`, defaults to "iso".
:param kwargs: The same keyword arguments that :class:`Field` receives.
.. versionchanged:: 3.0.0rc9
Does not modify timezone information on (de)serialization.
+ .. versionchanged:: 3.19
+ Add timestamp as a format.
"""
SERIALIZATION_FUNCS = {
@@ -1230,13 +1233,17 @@ class DateTime(Field):
"iso8601": utils.isoformat,
"rfc": utils.rfcformat,
"rfc822": utils.rfcformat,
- } # type: typing.Dict[str, typing.Callable[[typing.Any], str]]
+ "timestamp": utils.timestamp,
+ "timestamp_ms": utils.timestamp_ms,
+ } # type: typing.Dict[str, typing.Callable[[typing.Any], str | float]]
DESERIALIZATION_FUNCS = {
"iso": utils.from_iso_datetime,
"iso8601": utils.from_iso_datetime,
"rfc": utils.from_rfc,
"rfc822": utils.from_rfc,
+ "timestamp": utils.from_timestamp,
+ "timestamp_ms": utils.from_timestamp_ms,
} # type: typing.Dict[str, typing.Callable[[str], typing.Any]]
DEFAULT_FORMAT = "iso"
@@ -1252,7 +1259,7 @@ class DateTime(Field):
"format": '"{input}" cannot be formatted as a {obj_type}.',
}
- def __init__(self, format: str | None = None, **kwargs):
+ def __init__(self, format: str | None = None, **kwargs) -> None:
super().__init__(**kwargs)
# Allow this to be None. It may be set later in the ``_serialize``
# or ``_deserialize`` methods. This allows a Schema to dynamically set the
@@ -1267,7 +1274,7 @@ class DateTime(Field):
or self.DEFAULT_FORMAT
)
- def _serialize(self, value, attr, obj, **kwargs):
+ def _serialize(self, value, attr, obj, **kwargs) -> str | float | None:
if value is None:
return None
data_format = self.format or self.DEFAULT_FORMAT
@@ -1277,7 +1284,7 @@ class DateTime(Field):
else:
return value.strftime(data_format)
- def _deserialize(self, value, attr, data, **kwargs):
+ def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
if not value: # Falsy values, e.g. '', None, [] are not valid
raise self.make_error("invalid", input=value, obj_type=self.OBJ_TYPE)
data_format = self.format or self.DEFAULT_FORMAT
@@ -1298,7 +1305,7 @@ class DateTime(Field):
) from error
@staticmethod
- def _make_object_from_format(value, data_format):
+ def _make_object_from_format(value, data_format) -> dt.datetime:
return dt.datetime.strptime(value, data_format)
@@ -1323,11 +1330,11 @@ class NaiveDateTime(DateTime):
*,
timezone: dt.timezone | None = None,
**kwargs,
- ):
+ ) -> None:
super().__init__(format=format, **kwargs)
self.timezone = timezone
- def _deserialize(self, value, attr, data, **kwargs):
+ def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
ret = super()._deserialize(value, attr, data, **kwargs)
if is_aware(ret):
if self.timezone is None:
@@ -1360,11 +1367,11 @@ class AwareDateTime(DateTime):
*,
default_timezone: dt.tzinfo | None = None,
**kwargs,
- ):
+ ) -> None:
super().__init__(format=format, **kwargs)
self.default_timezone = default_timezone
- def _deserialize(self, value, attr, data, **kwargs):
+ def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
ret = super()._deserialize(value, attr, data, **kwargs)
if not is_aware(ret):
if self.default_timezone is None:
diff --git a/src/marshmallow/utils.py b/src/marshmallow/utils.py
index fd65133..7a16ff6 100644
--- a/src/marshmallow/utils.py
+++ b/src/marshmallow/utils.py
@@ -190,6 +190,34 @@ def from_iso_date(value):
return dt.date(**kw)
+def from_timestamp(value: typing.Any) -> dt.datetime:
+ value = float(value)
+ if value < 0:
+ raise ValueError("Not a valid POSIX timestamp")
+
+ # Load a timestamp with utc as timezone to prevent using system timezone.
+ # Then set timezone to None, to let the Field handle adding timezone info.
+ return dt.datetime.fromtimestamp(value, tz=dt.timezone.utc).replace(tzinfo=None)
+
+
+def from_timestamp_ms(value: typing.Any) -> dt.datetime:
+ value = float(value)
+ return from_timestamp(value / 1000)
+
+
+def timestamp(
+ value: dt.datetime,
+) -> float:
+ if not is_aware(value):
+ # When a date is naive, use UTC as zone info to prevent using system timezone.
+ value = value.replace(tzinfo=dt.timezone.utc)
+ return value.timestamp()
+
+
+def timestamp_ms(value: dt.datetime) -> float:
+ return timestamp(value) * 1000
+
+
def isoformat(datetime: dt.datetime) -> str:
"""Return the ISO8601-formatted representation of a datetime object.
diff --git a/tests/test_deserialization.py b/tests/test_deserialization.py
index c80a258..32aa099 100644
--- a/tests/test_deserialization.py
+++ b/tests/test_deserialization.py
@@ -526,6 +526,50 @@ class TestFieldDeserialization:
else:
assert field.deserialize(value) == expected
+ @pytest.mark.parametrize(
+ ("fmt", "value", "expected"),
+ [
+ ("timestamp", 1384043025, dt.datetime(2013, 11, 10, 0, 23, 45)),
+ ("timestamp", "1384043025", dt.datetime(2013, 11, 10, 0, 23, 45)),
+ ("timestamp", 1384043025, dt.datetime(2013, 11, 10, 0, 23, 45)),
+ ("timestamp", 1384043025.12, dt.datetime(2013, 11, 10, 0, 23, 45, 120000)),
+ (
+ "timestamp",
+ 1384043025.123456,
+ dt.datetime(2013, 11, 10, 0, 23, 45, 123456),
+ ),
+ ("timestamp", 1, dt.datetime(1970, 1, 1, 0, 0, 1)),
+ ("timestamp_ms", 1384043025000, dt.datetime(2013, 11, 10, 0, 23, 45)),
+ ("timestamp_ms", 1000, dt.datetime(1970, 1, 1, 0, 0, 1)),
+ ],
+ )
+ def test_timestamp_field_deserialization(self, fmt, value, expected):
+ field = fields.DateTime(format=fmt)
+ assert field.deserialize(value) == expected
+
+ # By default, a datetime from a timestamp is never aware.
+ field = fields.NaiveDateTime(format=fmt)
+ assert field.deserialize(value) == expected
+
+ field = fields.AwareDateTime(format=fmt)
+ with pytest.raises(ValidationError, match="Not a valid aware datetime."):
+ field.deserialize(value)
+
+ # But it can be added by providing a default.
+ field = fields.AwareDateTime(format=fmt, default_timezone=central)
+ expected_aware = expected.replace(tzinfo=central)
+ assert field.deserialize(value) == expected_aware
+
+ @pytest.mark.parametrize("fmt", ["timestamp", "timestamp_ms"])
+ @pytest.mark.parametrize(
+ "in_value",
+ ["", "!@#", 0, -1, dt.datetime(2013, 11, 10, 1, 23, 45)],
+ )
+ def test_invalid_timestamp_field_deserialization(self, fmt, in_value):
+ field = fields.DateTime(format="timestamp")
+ with pytest.raises(ValidationError, match="Not a valid datetime."):
+ field.deserialize(in_value)
+
@pytest.mark.parametrize(
("fmt", "timezone", "value", "expected"),
[
diff --git a/tests/test_serialization.py b/tests/test_serialization.py
index 51671dd..56f0773 100644
--- a/tests/test_serialization.py
+++ b/tests/test_serialization.py
@@ -579,6 +579,38 @@ class TestFieldSerialization:
field = fields.DateTime(format=fmt)
assert field.serialize("d", {"d": value}) == expected
+ @pytest.mark.parametrize(
+ ("fmt", "value", "expected"),
+ [
+ ("timestamp", dt.datetime(1970, 1, 1), 0),
+ ("timestamp", dt.datetime(2013, 11, 10, 0, 23, 45), 1384043025),
+ (
+ "timestamp",
+ dt.datetime(2013, 11, 10, 0, 23, 45, tzinfo=dt.timezone.utc),
+ 1384043025,
+ ),
+ (
+ "timestamp",
+ central.localize(dt.datetime(2013, 11, 10, 0, 23, 45), is_dst=False),
+ 1384064625,
+ ),
+ ("timestamp_ms", dt.datetime(2013, 11, 10, 0, 23, 45), 1384043025000),
+ (
+ "timestamp_ms",
+ dt.datetime(2013, 11, 10, 0, 23, 45, tzinfo=dt.timezone.utc),
+ 1384043025000,
+ ),
+ (
+ "timestamp_ms",
+ central.localize(dt.datetime(2013, 11, 10, 0, 23, 45), is_dst=False),
+ 1384064625000,
+ ),
+ ],
+ )
+ def test_datetime_field_timestamp(self, fmt, value, expected):
+ field = fields.DateTime(format=fmt)
+ assert field.serialize("d", {"d": value}) == expected
+
@pytest.mark.parametrize("fmt", ["iso", "iso8601", None])
@pytest.mark.parametrize(
("value", "expected"),
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/lib/python3/dist-packages/marshmallow-3.19.0.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/marshmallow-3.19.0.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/marshmallow-3.19.0.egg-info/not-zip-safe -rw-r--r-- root/root /usr/lib/python3/dist-packages/marshmallow-3.19.0.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/marshmallow-3.19.0.egg-info/top_level.txt lrwxrwxrwx root/root /usr/share/doc/python3-marshmallow/html/_static/_sphinx_javascript_frameworks_compat.js -> ../../../../javascript/sphinxdoc/1.0/_sphinx_javascript_frameworks_compat.js
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/lib/python3/dist-packages/marshmallow-3.18.0.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/marshmallow-3.18.0.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/marshmallow-3.18.0.egg-info/not-zip-safe -rw-r--r-- root/root /usr/lib/python3/dist-packages/marshmallow-3.18.0.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/marshmallow-3.18.0.egg-info/top_level.txt
No differences were encountered between the control files of package python3-marshmallow
Control files of package python3-marshmallow-doc: lines which differ (wdiff format)
Depends: libjs-sphinxdoc (>= 4.3), 5.0), libjs-jquery, libjs-underscore