Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-typedload for
openSUSE:Factory checked in at 2026-03-04 21:08:41
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-typedload (Old)
and /work/SRC/openSUSE:Factory/.python-typedload.new.561 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-typedload"
Wed Mar 4 21:08:41 2026 rev:8 rq:1336246 version:2.40
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-typedload/python-typedload.changes
2025-12-11 18:40:49.840185572 +0100
+++
/work/SRC/openSUSE:Factory/.python-typedload.new.561/python-typedload.changes
2026-03-04 21:09:32.587173100 +0100
@@ -1,0 +2,13 @@
+Tue Mar 3 23:27:34 UTC 2026 - Dirk Mรผller <[email protected]>
+
+- update to 2.40:
+ * Fix corner case of is_optional
+ * Drop LGPL3 license exception for Appgate Cybersecurity, Inc.
+ * The changes in this version do not carry the exception, so as a
+ result the entire project is GPL3 licensed.
+ LGPL3 allows relicensing under GPL3, but not viceversa.
+ * Improve documentation and examples
+ * Added moretypes module for types I might find convenient to
+ have Added HexRGB to the moretypes module
+
+-------------------------------------------------------------------
Old:
----
typedload_2.39.orig.tar.gz
New:
----
typedload_2.40.orig.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-typedload.spec ++++++
--- /var/tmp/diff_new_pack.klVOQN/_old 2026-03-04 21:09:33.419207488 +0100
+++ /var/tmp/diff_new_pack.klVOQN/_new 2026-03-04 21:09:33.419207488 +0100
@@ -1,7 +1,7 @@
#
# spec file for package python-typedload
#
-# Copyright (c) 2025 SUSE LLC and contributors
+# Copyright (c) 2026 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -17,7 +17,7 @@
Name: python-typedload
-Version: 2.39
+Version: 2.40
Release: 0
Summary: Load and dump data from json-like format into typed data
structures
License: GPL-3.0-only
++++++ typedload_2.39.orig.tar.gz -> typedload_2.40.orig.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/LICENSE new/typedload/LICENSE
--- old/typedload/LICENSE 2025-10-21 08:51:45.000000000 +0200
+++ new/typedload/LICENSE 2026-02-10 20:16:16.000000000 +0100
@@ -1,8 +1,6 @@
This software is released under the GNU General Public License 3.
-An exception to this is granted to Appgate Cybersecurity, Inc., which
-is allowed to use this software under the GNU Lesser General Public
-License 3. Because the author works there.
+GNU Lesser General Public License 3 can be granted, but none is in place.
Verbatim text of GNU GPL 3 and GNU LGPL 3 follows.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/Makefile new/typedload/Makefile
--- old/typedload/Makefile 2025-10-21 08:51:45.000000000 +0200
+++ new/typedload/Makefile 2026-02-10 23:39:12.000000000 +0100
@@ -116,7 +116,6 @@
docs/errors.md \
docs/examples.md \
docs/gpl3logo.png \
- docs/origin_story.md \
docs/performance.md \
docs/README.md \
docs/SECURITY.md \
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/docs/CHANGELOG.md
new/typedload/docs/CHANGELOG.md
--- old/typedload/docs/CHANGELOG.md 2025-10-21 09:03:19.000000000 +0200
+++ new/typedload/docs/CHANGELOG.md 2026-02-11 09:33:45.000000000 +0100
@@ -1,3 +1,14 @@
+2.40
+====
+* Fix corner case of is_optional
+* Drop LGPL3 license exception for Appgate Cybersecurity, Inc.
+ The changes in this version do not carry the exception, so as a
+ result the entire project is GPL3 licensed.
+ LGPL3 allows relicensing under GPL3, but not viceversa.
+* Improve documentation and examples
+* Added moretypes module for types I might find convenient to have
+* Added HexRGB to the moretypes module
+
2.39
====
* Re-release
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/docs/CONTRIBUTING.md
new/typedload/docs/CONTRIBUTING.md
--- old/typedload/docs/CONTRIBUTING.md 2025-10-21 08:51:45.000000000 +0200
+++ new/typedload/docs/CONTRIBUTING.md 2026-02-10 20:16:16.000000000 +0100
@@ -1,18 +1,25 @@
Contributing
============
+How
+---
+
All contributions must pass the test suite and must generate no warnings with
the latest available version of mypy.
The best way of sending changes is to use git-send-mail to [email protected]
It is acceptable also to use codeberg's pull request functionality.
-Contributors must accept that their changes can use both GPL3 and LGPL3.
-Currently the license is GPL3 with one exception being made for the company
where I work. In the future more LGPL3 exception could be made, but no other
license than those will be used.
+Copyright and license
+---------------------
+
+Contributors retain the copyright of their contributions. They must accept
that the project uses GPL3 and LGPL3 licenses; but their contributions must be
under LGPL3.
+
+As a consequence the owners of the project can grant LGPL3 exceptions by
giving permission to use the LGPL3 license to someone. But in general the
owners of the project grant the use under GPL3.
-In the event that new versions of GPL and LGPL licenses should be published by
the Free Software Foundation (FSF) in the future, contributors must accept that
their contribution might be licensed with those future versions of GPL and LGPL
in addition to the current version.
+Contributors have no control on who gets LGPL3 exceptions, but by retaining
copyright they know the project will not be able to change license to anything
more permissive than LGPL3.
-This will be decided by the owners of the project if and when new versions of
the licenses are created and is not automatic, to prevent the case where a new
board of the FSF should decide to abandon its mission and grant less freedom to
the users.
+Contributors contributing on behalf of companies are required to disclose the
fact and to assign their copyright to the project owners. This can be done in a
signed commit message.
-For new versions that aim at fixing corner cases (such as version 3), they
will be used and the software will be available under multiple licenses
versions (starting from 3). They will not be adopted should the FSF decide that
GPL4 should be a non-copyleft license or other similar spirit altering changes.
+Contributors must accept that the project might move to future versions of the
GPL and LGPL, published by the Free Software Foundation, but that will not
happen automatically and the decision will be made by the project owners if
that should happen.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/docs/errors.md new/typedload/docs/errors.md
--- old/typedload/docs/errors.md 2025-10-21 08:51:45.000000000 +0200
+++ new/typedload/docs/errors.md 2026-02-10 23:39:12.000000000 +0100
@@ -83,9 +83,9 @@
Union
-----
-Because it is normal for a union of n types to generate n-1 exceptions, a
union which fails generated n exceptions.
+Since it is normal for a union of n types to generate n-1 exceptions, a union
which fails generated n exceptions.
-Typedload has no way of knowing which of those is the important exception that
was expected to succeed and instead puts all the exceptions inside the
`exception` field of the parent exception.
+Typedload has no way of discerning which exception is actually relevant,
instead it stores all the exceptions inside the `exception` field of the parent
exception.
So all the sub exceptions can be investigated to decide which one is the most
relevant one.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/docs/origin_story.md
new/typedload/docs/origin_story.md
--- old/typedload/docs/origin_story.md 2025-10-21 08:51:45.000000000 +0200
+++ new/typedload/docs/origin_story.md 1970-01-01 01:00:00.000000000 +0100
@@ -1,20 +0,0 @@
-typedload's origin story
-========================
-
-At $DAYJOB there was a software written in Scala, that worked by mapping
objects into mongodb data.
-
-The only one person there knowing Scala decided to quit, and so we were in the
process of rewriting the entire thing in Python.
-
-I had been tasked to write a few hundreds methods `to_json()` and
`from_json()` for all the various objects that are used by this software.
-
-Since I thought it was going to be a terribly boring job in which I'd make
tens of typos, I looked for a library that did such a thing. But of course none
existed, so I started writing a module to do that.
-
-The `to_json()` part was rather easy, while the opposite wasn't, anyway after
a few days the module seemed to be working nicely.
-
-In fact the module worked so nicely that I wanted to use it in my personal
projects as well. However I couldn't, because $DAYJOB owned the copyright, and
I knew that getting the authorization to release it as open source would take
from 2 years to +โ.
-
-So, I just wrote a stand alone library outside of work to do the exact same
thing, but in a more generic and flexible way, rather than tied to the specific
software we had at $DAYJOB.
-
-The result of writing the same library twice was that the second time around
it came out better, and so the original version got completely discarded and at
$DAYJOB typedload is now in use.
-
-At the time pydantic existed but it was not available on any distribution and
was not production quality. I am a lazy person so if it had been usable I would
have used it instead. But in the end I think typedload is better. But that's
just my own opinion.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/docs/performance.md
new/typedload/docs/performance.md
--- old/typedload/docs/performance.md 2025-10-21 08:51:45.000000000 +0200
+++ new/typedload/docs/performance.md 2026-02-10 23:39:12.000000000 +0100
@@ -6,7 +6,7 @@
The tests are done on my PC. The following libraries are tested:
* `typedload`, the 3 most recent versions. It shines with tagged unions, which
is what I mostly use.
-* `pydantic2` years of work to rewrite it in Rust, [implemented detection of
tagged unions years after I did
it](https://github.com/pydantic/pydantic/issues/5163#issuecomment-1619203179),
still managing to lose some benchmarks ๐
(sorry I find it hilarious that a
company hiring a team of developers to do a Rust implementation cannot beat
*all* the benchmarks when typedload is pure python and I work on it every once
in a while).
+* `pydantic2` after years of work to rewrite it in Rust, [implemented
detection of tagged unions years after I did
it](https://github.com/pydantic/pydantic/issues/5163#issuecomment-1619203179),
still managing to lose some benchmarks ๐
(sorry I find it hilarious that a
company hiring a team of developers to do a Rust implementation cannot beat
*all* the benchmarks when typedload is pure python and I work on it every once
in a while).
* `apischema` is slower where there are unions, faster otherwise
Using Python 3.13
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/docs/supported_types.md
new/typedload/docs/supported_types.md
--- old/typedload/docs/supported_types.md 2025-10-21 08:51:45.000000000
+0200
+++ new/typedload/docs/supported_types.md 2026-02-11 09:33:45.000000000
+0100
@@ -565,3 +565,8 @@
Loads a str or bytes as a compiled Pattern object by passing through
re.compile.
When dumping gives back the original str or bytes pattern.
+
+typedload.moretypes.HexRGB
+--------------------------
+
+This class loads and dumps as a string, but also validates that the string is
a valid RGB colour in the form '#nnnnnn'.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/example.py new/typedload/example.py
--- old/typedload/example.py 2025-10-21 08:51:45.000000000 +0200
+++ new/typedload/example.py 2026-02-10 23:39:12.000000000 +0100
@@ -1,7 +1,7 @@
#!/usr/bin/python3
# typedload
-# Copyright (C) 2020-2024 Salvo "LtWorf" Tomaselli
+# Copyright (C) 2020-2026 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -32,7 +32,7 @@
from datetime import datetime
from uuid import UUID
import json
-from typing import *
+from typing import Any, NamedTuple
import urllib.request
import typedload
@@ -40,8 +40,8 @@
class CommandLine(NamedTuple):
full: bool
- project: Optional[str]
- username: Optional[str]
+ project: str | None
+ username: str | None
def get_url(self) -> str:
if self.username is None and self.project is None:
@@ -86,7 +86,7 @@
created_at: datetime
published_at: datetime
author: User
- assets: List[Asset]
+ assets: list[Asset]
def get_data(args: CommandLine) -> Any:
@@ -98,14 +98,14 @@
return json.load(f)
-def print_report(data: List[Release], args: CommandLine):
+def print_report(data: list[Release], args: CommandLine) -> None:
for i in data:
if i.draft or i.prerelease:
continue
- print('Release:', i.name, end=' ')
+ print(f'Release: "{i.name}"', end=' ')
if args.full:
- print('Created by:', i.author.login, 'on:', i.created_at)
+ print(f'Created by: {i.author.login} on: {i.created_at}')
else:
print()
@@ -114,8 +114,8 @@
print('\t%d\t%s' % (asset.download_count, asset.name))
-def main():
- parser = argparse.ArgumentParser()
+def main() -> None:
+ parser = argparse.ArgumentParser(description='Query the codeberg API to
list the releases and download counts of projects')
parser.add_argument('-u', '--username', help='The username to query')
parser.add_argument('-p', '--project', help='The project to query')
parser.add_argument('-f', '--full', help='Print the full report',
action='store_true')
@@ -124,12 +124,10 @@
args = typedload.load(parser.parse_args(), CommandLine)
data = get_data(args)
- # Github returns dates like this "2016-08-23T18:26:00Z", which are not
supported by typedload
- # So we make a custom handler for them.
loader = typedload.dataloader.Loader()
# We know what the API returns so we can load the json into typed data
- typed_data = loader.load(data, List[Release])
+ typed_data = loader.load(data, list[Release])
print_report(typed_data, args)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/mkdocs.yml new/typedload/mkdocs.yml
--- old/typedload/mkdocs.yml 2025-10-21 08:51:45.000000000 +0200
+++ new/typedload/mkdocs.yml 2026-02-10 23:39:12.000000000 +0100
@@ -2,12 +2,11 @@
site_dir: html
site_description: typedload documentation โ a python module to load untyped
data into typed data structures
site_author: Salvo 'LtWorf' Tomaselli <[email protected]>
-copyright: Copyright (C) 2018-2024 Salvo "LtWorf" Tomaselli
+copyright: Copyright (C) 2018-2025 Salvo "LtWorf" Tomaselli
theme: readthedocs
use_directory_urls: false
nav:
- Home: README.md
- #- Origin story: origin_story.md
- Changelog: CHANGELOG.md
- Using typedload:
- Examples: examples.md
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/pyproject.toml new/typedload/pyproject.toml
--- old/typedload/pyproject.toml 2025-10-21 09:06:49.000000000 +0200
+++ new/typedload/pyproject.toml 2026-02-11 09:35:47.000000000 +0100
@@ -1,15 +1,16 @@
[project]
name = "typedload"
-version = "2.39"
+version = "2.40"
authors = [
{ name="Salvo 'LtWorf' Tomaselli", email="[email protected]" },
]
description = "Load and dump data from json-like format into typed data
structures"
readme = "README.md"
requires-python = ">=3.10"
-classifiers = ['Development Status :: 5 - Production/Stable', 'Intended
Audience :: Developers', 'License :: OSI Approved :: GNU General Public License
v3 (GPLv3)', 'Typing :: Typed', 'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11', 'Programming Language :: Python ::
3.12', 'Programming Language :: Python :: 3.13', 'Programming Language ::
Python :: 3.14']
+classifiers = ['Development Status :: 5 - Production/Stable', 'Intended
Audience :: Developers', 'Typing :: Typed', 'Programming Language :: Python ::
3.10', 'Programming Language :: Python :: 3.11', 'Programming Language ::
Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming
Language :: Python :: 3.14']
keywords = ['typing', 'types', 'mypy', 'json', 'schema', 'json-schema',
'python3', 'namedtuple', 'enums', 'dataclass', 'pydantic']
-license = {file = "LICENSE"}
+license = "GPL-3.0-only"
+license-files = ["LICENSE"]
[project.urls]
"Homepage" = "https://ltworf.codeberg.page/typedload/"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/setup.py new/typedload/setup.py
--- old/typedload/setup.py 2025-10-21 09:06:49.000000000 +0200
+++ new/typedload/setup.py 2026-02-11 09:35:47.000000000 +0100
@@ -3,14 +3,14 @@
from setuptools import setup
setup(
name='typedload',
- version='2.39',
+ version='2.40',
description='Load and dump data from json-like format into typed data
structures',
readme='README.md',
url='https://ltworf.codeberg.page/typedload/',
author="Salvo 'LtWorf' Tomaselli",
author_email='[email protected]',
license='GPL-3.0-only',
- classifiers=['Development Status :: 5 - Production/Stable', 'Intended
Audience :: Developers', 'License :: OSI Approved :: GNU General Public License
v3 (GPLv3)', 'Typing :: Typed', 'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11', 'Programming Language :: Python ::
3.12', 'Programming Language :: Python :: 3.13', 'Programming Language ::
Python :: 3.14'],
+ classifiers=['Development Status :: 5 - Production/Stable', 'Intended
Audience :: Developers', 'Typing :: Typed', 'Programming Language :: Python ::
3.10', 'Programming Language :: Python :: 3.11', 'Programming Language ::
Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming
Language :: Python :: 3.14'],
keywords='typing types mypy json schema json-schema python3 namedtuple
enums dataclass pydantic',
packages=['typedload'],
package_data={"typedload": ["py.typed", "__init__.pyi"]},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/tests/__main__.py
new/typedload/tests/__main__.py
--- old/typedload/tests/__main__.py 2025-10-21 08:51:45.000000000 +0200
+++ new/typedload/tests/__main__.py 2026-02-11 09:33:45.000000000 +0100
@@ -1,5 +1,5 @@
# typedload
-# Copyright (C) 2018-2025 Salvo "LtWorf" Tomaselli
+# Copyright (C) 2018-2026 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -35,7 +35,7 @@
from .test_datetime import *
from .test_literal import *
from .test_typeddict import *
-from .test_orunion import *
+from .test_moretypes import *
if sys.version_info.minor >= 12:
from .test_typealias import *
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/tests/test_dataloader.py
new/typedload/tests/test_dataloader.py
--- old/typedload/tests/test_dataloader.py 2025-10-21 08:51:45.000000000
+0200
+++ new/typedload/tests/test_dataloader.py 2025-11-29 12:08:43.000000000
+0100
@@ -182,6 +182,12 @@
with self.assertRaises(TypeError):
loader.load({'a': 1}, Union[A, B])
+ def test_loadnewunion(self):
+ t = list[int] | str
+ assert load('ciao', t) == 'ciao'
+ assert load(['1', 1.0, 0], t) == [1, 1, 0]
+ assert load(('1', 1.0, 0), t) == [1, 1, 0]
+
class TestFastIterableLoad(unittest.TestCase):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/tests/test_moretypes.py
new/typedload/tests/test_moretypes.py
--- old/typedload/tests/test_moretypes.py 1970-01-01 01:00:00.000000000
+0100
+++ new/typedload/tests/test_moretypes.py 2026-02-11 09:33:45.000000000
+0100
@@ -0,0 +1,70 @@
+# typedload
+# Copyright (C) 2026 Salvo "LtWorf" Tomaselli
+#
+# typedload is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# author Salvo "LtWorf" Tomaselli <[email protected]>
+
+import unittest
+
+from typedload.moretypes import *
+from typedload import load, dump
+
+
+class TestHexRGB(unittest.TestCase):
+
+ def test_dump(self):
+ assert dump(HexRGB('#Ff0000')) == '#FF0000'
+ assert isinstance(dump(HexRGB('#Ff0000')), str)
+
+ def test_load(self):
+ assert load('#0000ff', HexRGB) == HexRGB('#0000Ff')
+ assert isinstance(load('#0000ff', HexRGB), HexRGB)
+
+ def test_values(self):
+ assert HexRGB('#Ff0000').rgb() == (255, 0, 0)
+ assert HexRGB('#0AFFFf').rgb() == (10, 255, 255)
+ assert HexRGB('#000000').rgb() == (0, 0, 0)
+ assert HexRGB('#000001').rgb() == (0, 0, 1)
+ assert HexRGB(7).rgb() == (0, 0, 7)
+
+ def test_equality(self):
+ assert HexRGB('#0000ff') == HexRGB('#0000FF')
+ assert HexRGB('#0000ff') != '#0000ff'
+ assert HexRGB('#0000ff') != 255
+ assert HexRGB('#0000ff').value == 255
+
+ def test_fail(self):
+ with self.assertRaises(TypeError):
+ HexRGB()
+ with self.assertRaises(ValueError):
+ HexRGB('#ffaab')
+ with self.assertRaises(ValueError):
+ HexRGB('FFAABB')
+ with self.assertRaises(ValueError):
+ HexRGB('#00000x')
+ with self.assertRaises(ValueError):
+ HexRGB(-1)
+ with self.assertRaises(ValueError):
+ HexRGB(16777216)
+ with self.assertRaises(TypeError):
+ HexRGB(4.1)
+
+ def test_immutable(self):
+ c = HexRGB(1)
+
+ with self.assertRaises(AttributeError):
+ c.qwe = 123
+ with self.assertRaises(AttributeError):
+ c.value = 2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/tests/test_orunion.py
new/typedload/tests/test_orunion.py
--- old/typedload/tests/test_orunion.py 2025-10-21 08:51:45.000000000 +0200
+++ new/typedload/tests/test_orunion.py 1970-01-01 01:00:00.000000000 +0100
@@ -1,48 +0,0 @@
-# typedload
-# Copyright (C) 2022 Salvo "LtWorf" Tomaselli
-#
-# typedload is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-# author Salvo "LtWorf" Tomaselli <[email protected]>
-
-
-import unittest
-
-from typedload import dataloader, load, dump, typechecks
-
-
-class TestOrUnion(unittest.TestCase):
- '''
- From Python3.10 unions can be written as A | B.
-
- That is a completely different internal than Union[A, B]
- '''
-
- def test_typechecker(self):
- assert typechecks.is_union(int | str)
- assert not typechecks.is_union(2 | 1)
-
- def test_uniontypes(self):
- u = int | str | float
- assert int in typechecks.uniontypes(u)
- assert str in typechecks.uniontypes(u)
- assert float in typechecks.uniontypes(u)
- assert bytes not in typechecks.uniontypes(u)
- assert bool not in typechecks.uniontypes(u)
-
- def test_loadnewunion(self):
- t = list[int] | str
- assert load('ciao', t) == 'ciao'
- assert load(['1', 1.0, 0], t) == [1, 1, 0]
- assert load(('1', 1.0, 0), t) == [1, 1, 0]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/tests/test_typechecks.py
new/typedload/tests/test_typechecks.py
--- old/typedload/tests/test_typechecks.py 2025-10-21 08:51:45.000000000
+0200
+++ new/typedload/tests/test_typechecks.py 2025-11-29 12:08:43.000000000
+0100
@@ -1,5 +1,5 @@
# typedload
-# Copyright (C) 2018-2024 Salvo "LtWorf" Tomaselli
+# Copyright (C) 2018-2025 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -156,14 +156,28 @@
assert typechecks.is_union(Union[str, int, float])
assert not typechecks.is_union(FrozenSet[int])
assert not typechecks.is_union(int)
+ # New syntax
+ assert typechecks.is_union(int | None)
+ assert typechecks.is_union(str | None)
+ assert typechecks.is_union(bytes | str)
+ assert typechecks.is_union(str | int | float)
+ assert typechecks.is_union(int | str)
+ assert not typechecks.is_union(2 | 1)
def test_is_optional(self):
+ T = int | str
+ assert typechecks.is_optional(Optional[T])
assert typechecks.is_optional(Optional[int])
assert typechecks.is_optional(Optional[str])
assert not typechecks.is_optional(Union[bytes, str])
assert not typechecks.is_optional(Union[str, int, float])
assert not typechecks.is_union(FrozenSet[int])
assert not typechecks.is_union(int)
+ # New syntax
+ assert typechecks.is_optional(None | int)
+ assert typechecks.is_optional(None | str)
+ assert not typechecks.is_optional(bytes | str)
+ assert not typechecks.is_optional(str | int | float)
def test_is_nonetype(self):
assert typechecks.is_nonetype(type(None))
@@ -201,6 +215,14 @@
with self.assertRaises(AttributeError):
typechecks.uniontypes(Union[int])
+ u = int | str | float
+ assert int in typechecks.uniontypes(u)
+ assert str in typechecks.uniontypes(u)
+ assert float in typechecks.uniontypes(u)
+ assert bytes not in typechecks.uniontypes(u)
+ assert bool not in typechecks.uniontypes(u)
+
+
def test_any(self):
assert typechecks.is_any(Any)
assert not typechecks.is_any(str)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/typedload/__init__.py
new/typedload/typedload/__init__.py
--- old/typedload/typedload/__init__.py 2025-10-21 08:51:45.000000000 +0200
+++ new/typedload/typedload/__init__.py 2026-02-11 09:33:45.000000000 +0100
@@ -156,7 +156,7 @@
to the loader/dumper.
"""
-# Copyright (C) 2018-2021 Salvo "LtWorf" Tomaselli
+# Copyright (C) 2018-2026 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -183,6 +183,7 @@
'datadumper',
'dump',
'typechecks',
+ 'moretypes',
]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/typedload/datadumper.py
new/typedload/typedload/datadumper.py
--- old/typedload/typedload/datadumper.py 2025-10-21 08:51:45.000000000
+0200
+++ new/typedload/typedload/datadumper.py 2026-02-11 09:33:45.000000000
+0100
@@ -3,7 +3,7 @@
This module is the inverse of dataloader. It converts typed
data structures to things that json can serialize.
"""
-# Copyright (C) 2018-2024 Salvo "LtWorf" Tomaselli
+# Copyright (C) 2018-2026 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@
from .exceptions import TypedloadValueError
from .typechecks import is_attrs, NONETYPE, is_literal
+from . import moretypes
__all__ = [
@@ -138,6 +139,7 @@
ipaddress.IPv4Interface,
ipaddress.IPv6Interface,
uuid.UUID,
+ moretypes.HexRGB,
}
self.handlers = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/typedload/dataloader.py
new/typedload/typedload/dataloader.py
--- old/typedload/typedload/dataloader.py 2025-10-21 08:51:45.000000000
+0200
+++ new/typedload/typedload/dataloader.py 2026-02-11 09:33:45.000000000
+0100
@@ -3,7 +3,7 @@
Module to load data into typed data structures
"""
-# Copyright (C) 2018-2025 Salvo "LtWorf" Tomaselli
+# Copyright (C) 2018-2026 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -34,6 +34,7 @@
from .typechecks import *
from .typechecks import discriminatorliterals
from .helpers import tname
+from . import moretypes
try:
# A dirty trick
@@ -222,6 +223,7 @@
ipaddress.IPv4Interface,
ipaddress.IPv6Interface,
uuid.UUID,
+ moretypes.HexRGB,
}
# Bah
@@ -862,11 +864,14 @@
return type_(l.load(value, t,
annotation=Annotation(AnnotationType.UNION, t)))
except Exception as e:
exceptions.append(e)
+
+ # For small enums of basic types, list the possible values
if len(type_.__members__) <= 10 and all(type(i.value) in l.basictypes for
i in type_.__members__.values()):
- lst = '\nValue %s not between: ' % repr(value) + \
+ lst = f'\nValue {value!r} not among: ' + \
', '.join(repr(i.value) for i in type_.__members__.values())
else:
lst = ''
+
raise TypedloadValueError(
'Value of %s could not be loaded into %s%s' % (tname(type(value)),
tname(type_), lst),
value=value,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/typedload/moretypes.py
new/typedload/typedload/moretypes.py
--- old/typedload/typedload/moretypes.py 1970-01-01 01:00:00.000000000
+0100
+++ new/typedload/typedload/moretypes.py 2026-02-11 09:33:45.000000000
+0100
@@ -0,0 +1,91 @@
+"""
+typedload
+
+Additional types
+"""
+
+# Copyright (C) 2026 Salvo "LtWorf" Tomaselli
+#
+# typedload is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# author Salvo "LtWorf" Tomaselli <[email protected]>
+
+__all__ = [
+ 'HexRGB'
+]
+
+
+class HexRGB:
+ '''
+ A class for RGB colours.
+
+ Can be initialised with:
+ HexRGB('#00ff00')
+ HexRGB(1234)
+ '''
+
+ __slots__ = ['value']
+ value: int
+
+ def __new__(cls, value: str | int):
+ if isinstance(value, str):
+ if len(value) != 7:
+ raise ValueError(f'Invalid length for HexRGB string:
{value!r}')
+ if value[0] != '#':
+ raise ValueError(f'Invalid prefix for HexRGB string:
{value[0]!r}')
+ v = int(value[1:], 16)
+ elif isinstance(value, int):
+ v = value
+ else:
+ raise TypeError(f'Invalid type {type(value)} to create HexRGB')
+
+ if v < 0 or v > 16777215:
+ raise ValueError(f'Colour value out of range: {v}')
+
+ # Some weird magic since this class is immutable
+ instance = object.__new__(cls)
+ object.__setattr__(instance, "value", v)
+ return instance
+
+ def __int__(self) -> int:
+ return self.value
+
+ def __eq__(self, o) -> bool:
+ if not isinstance(o, HexRGB):
+ return False
+ return self.value == o.value
+
+ def __hash__(self):
+ return hash(self.value)
+
+ def __setattr__(self, name, value) -> None:
+ raise AttributeError('HexRGB is read only')
+
+ def __repr__(self) -> str:
+ return f"HexRGB({self.value})"
+
+ def __str__(self) -> str:
+ return f'#{self.value:06X}'
+
+ def rgb(self) -> tuple[int, int, int]:
+ '''
+ Returns a tuple with the RGB values in integer format
+ '''
+ v = self.value
+ b = v & 255
+ v >>= 8
+ g = v & 255
+ v >>= 8
+ r = v
+ return r, g, b
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/typedload/typedload/typechecks.py
new/typedload/typedload/typechecks.py
--- old/typedload/typedload/typechecks.py 2025-10-21 08:51:45.000000000
+0200
+++ new/typedload/typedload/typechecks.py 2026-02-10 23:39:12.000000000
+0100
@@ -15,7 +15,7 @@
different versions of Python.
"""
-# Copyright (C) 2019-2024 Salvo "LtWorf" Tomaselli
+# Copyright (C) 2019-2026 Salvo "LtWorf" Tomaselli
#
# typedload is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -34,7 +34,8 @@
import sys
from enum import Enum
from re import Pattern
-from typing import Any, Tuple, Union, Set, List, Dict, Type, FrozenSet, NewType
+from typing import Any, Tuple, Set, List, Dict, Type, FrozenSet, NewType
+from typing import Union #TODO useless after python 3.13
__all__ = [
@@ -119,7 +120,7 @@
Note that Optional is just a Union, so if is_optional is True then
also is_union will be True
'''
- return is_union(type_) and (len(type_.__args__) == 2) and NONETYPE in
type_.__args__
+ return is_union(type_) and NONETYPE in type_.__args__
def is_nonetype(type_: Any) -> bool:
@@ -211,13 +212,8 @@
return hasattr(type_, '__attrs_attrs__')
-if sys.version_info > (3, 10, 0):
- def is_newtype(type_: Any) -> bool:
- return type(type_) == NewType
-
-else:
- def is_newtype(type_: Any) -> bool:
- return hasattr(type_, '__supertype__')
+def is_newtype(type_: Any) -> bool:
+ return type(type_) == NewType
def uniontypes(type_: Any) -> Tuple[Type[Any], ...]: