diff --git a/.flake8 b/.flake8
deleted file mode 100644
index 0a96006..0000000
--- a/.flake8
+++ /dev/null
@@ -1,5 +0,0 @@
-[flake8]
-# From https://github.com/4teamwork/ftw-buildouts/blob/master/pycodestyle.cfg
-ignore = E121,E122,E123,E125,E126,E127,E128,E203,E301,W503,W606
-max-line-length = 125
-exclude = .git,__pycache__,venv,.tox,manage.py,include,lib,javascript,lib,node_modules,scss,bin,.eggs,wsgi.py,wsgi.py,src,migrations,docs,gever,bootstrap.py
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index b7f1453..0a6c798 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -23,11 +23,9 @@ jobs:
poetry-version: 2.1
- name: Install dependencies
run: poetry install -E dev
- - name: isort
- run: poetry run isort --check-only --quiet --settings pyproject.toml .
- - name: flake8
- run: poetry run flake8
- - name: black
- run: poetry run black --check --config pyproject.toml .
- - name: tests
+ - name: Ruff Linter
+ run: poetry run ruff check
+ - name: Ruff Formatter
+ run: poetry run ruff format --check
+ - name: Tests
run: poetry run pytest
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d8d17f3..c9b9537 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+Changelog
+=========
+
+.. towncrier release notes start
2.0.2 (2026-02-17)
------------------
@@ -28,10 +32,6 @@ Other changes:
- Use poetry for dependecy management and towncrier and zest.releaser for release managment. [jch]
-Changelog
-=========
-
-
1.4.0 (2022-12-14)
------------------
diff --git a/changes/python310.other b/changes/python310.other
new file mode 100644
index 0000000..ebc4b58
--- /dev/null
+++ b/changes/python310.other
@@ -0,0 +1 @@
+Lowered the minimum required Python version from 3.12 to 3.10. [buchi]
diff --git a/changes/six-removal.other b/changes/six-removal.other
new file mode 100644
index 0000000..6e80494
--- /dev/null
+++ b/changes/six-removal.other
@@ -0,0 +1 @@
+Removed dependency on six. [buchi]
diff --git a/docker-bake.hcl b/docker-bake.hcl
index eafbc5a..6690fe0 100644
--- a/docker-bake.hcl
+++ b/docker-bake.hcl
@@ -37,4 +37,13 @@ target "default" {
"linux/amd64",
strlen(GIT_TAG) > 0 ? "linux/arm64" : "",
]
+ attest = [
+ {
+ type = "provenance"
+ mode = "max"
+ },
+ {
+ type = "sbom"
+ }
+ ]
}
diff --git a/docxcompose/command.py b/docxcompose/command.py
index 3b57a84..0b2e253 100644
--- a/docxcompose/command.py
+++ b/docxcompose/command.py
@@ -11,12 +11,12 @@ def setup_parser():
parser = ArgumentParser(description="compose multiple docx files into one file.")
parser.add_argument(
"master",
- help="path to master template that defines styles, " "headings and so on",
+ help="path to master template that defines styles, headings and so on",
)
parser.add_argument(
"files",
nargs="+",
- help="path to one or more word-files to be appended " "to the master template",
+ help="path to one or more word-files to be appended to the master template",
metavar="file",
)
parser.add_argument(
diff --git a/docxcompose/composer.py b/docxcompose/composer.py
index 64c0174..05a1088 100644
--- a/docxcompose/composer.py
+++ b/docxcompose/composer.py
@@ -36,7 +36,6 @@
class Composer(object):
-
def __init__(self, doc):
self.doc = doc
self.pkg = doc.part.package
diff --git a/docxcompose/properties.py b/docxcompose/properties.py
index c3039c4..74b620b 100644
--- a/docxcompose/properties.py
+++ b/docxcompose/properties.py
@@ -13,8 +13,6 @@
from docx.oxml.coreprops import CT_CoreProperties
from lxml.etree import FunctionNamespace
from lxml.etree import QName
-from six import binary_type
-from six import text_type
from docxcompose.utils import NS
from docxcompose.utils import word_to_python_date_format
@@ -38,17 +36,17 @@ def value2vt(value):
el.text = "true" if value else "false"
elif isinstance(value, int):
el = parse_xml(CUSTOM_PROPERTY_TYPES["int"])
- el.text = text_type(value)
+ el.text = str(value)
elif isinstance(value, float):
el = parse_xml(CUSTOM_PROPERTY_TYPES["float"])
- el.text = text_type(value)
+ el.text = str(value)
elif isinstance(value, datetime):
el = parse_xml(CUSTOM_PROPERTY_TYPES["datetime"])
el.text = value.strftime("%Y-%m-%dT%H:%M:%SZ")
- elif isinstance(value, text_type):
+ elif isinstance(value, str):
el = parse_xml(CUSTOM_PROPERTY_TYPES["text"])
el.text = value
- elif isinstance(value, binary_type):
+ elif isinstance(value, bytes):
value = value.decode("utf-8")
el = parse_xml(CUSTOM_PROPERTY_TYPES["text"])
el.text = value
@@ -171,7 +169,7 @@ def __delitem__(self, key):
# Renumber pids
pid = MIN_PID
for prop in self._element:
- prop.set("pid", text_type(pid))
+ prop.set("pid", str(pid))
pid += 1
self._update_part()
@@ -235,7 +233,7 @@ def add(self, name, value):
prop = parse_xml(''.format(NS["cp"]))
prop.set("fmtid", CUSTOM_PROPERTY_FMTID)
prop.set("name", name)
- prop.set("pid", text_type(pid))
+ prop.set("pid", str(pid))
value_el = value2vt(value)
prop.append(value_el)
self._element.append(prop)
@@ -345,7 +343,7 @@ class FieldBase(object):
"""Class used to represent a docproperty field in the document.xml."""
fieldname_and_format_search_expr = re.compile(
- r'DOCPROPERTY +"{0,1}([^\\]*?)"{0,1} +(?:\\\@ +"{0,1}([^\\]*?)"{0,1} +){0,1}\\\* MERGEFORMAT',
+ r'DOCPROPERTY +"{0,1}([^\\]*?)"{0,1} +(?:\\\@ +"{0,1}([^\\]*?)"{0,1} +){0,1}\\\* MERGEFORMAT', # noqa
flags=re.UNICODE,
)
@@ -365,7 +363,7 @@ def _format_value(self, value, language=None):
return format_datetime(value, self.date_format, locale=language)
return format_datetime(value, self.date_format)
else:
- return text_type(value)
+ return str(value)
def update(self, value, language=None):
"""Sets the value of the docproperty in the document"""
diff --git a/docxcompose/server.py b/docxcompose/server.py
index f27aee3..4a0a8cb 100644
--- a/docxcompose/server.py
+++ b/docxcompose/server.py
@@ -20,7 +20,6 @@
async def compose(request):
documents = []
- temp_dir = None
if not request.content_type == "multipart/form-data":
logger.info(
@@ -81,7 +80,7 @@ async def stream_file(request, filename, content_type):
reason="OK",
headers={
"Content-Type": content_type,
- "Content-Disposition": f'attachment; filename="{os.path.basename(filename)}"',
+ "Content-Disposition": f'attachment; filename="{os.path.basename(filename)}"', # noqa
},
)
await response.prepare(request)
diff --git a/poetry.lock b/poetry.lock
index cddecad..2878654 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
[[package]]
name = "aiohappyeyeballs"
@@ -147,6 +147,7 @@ files = [
[package.dependencies]
aiohappyeyeballs = ">=2.5.0"
aiosignal = ">=1.4.0"
+async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""}
attrs = ">=17.3.0"
frozenlist = ">=1.1.1"
multidict = ">=4.5,<7.0"
@@ -173,6 +174,19 @@ files = [
frozenlist = ">=1.1.0"
typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""}
+[[package]]
+name = "async-timeout"
+version = "5.0.1"
+description = "Timeout context manager for asyncio programs"
+optional = true
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "(extra == \"dev\" or extra == \"server\") and python_version == \"3.10\""
+files = [
+ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
+ {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
+]
+
[[package]]
name = "attrs"
version = "25.4.0"
@@ -202,56 +216,34 @@ files = [
dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""]
[[package]]
-name = "black"
-version = "25.12.0"
-description = "The uncompromising code formatter."
+name = "backports-asyncio-runner"
+version = "1.2.0"
+description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle."
optional = true
-python-versions = ">=3.10"
+python-versions = "<3.11,>=3.8"
groups = ["main"]
-markers = "extra == \"dev\""
+markers = "extra == \"dev\" and python_version == \"3.10\""
files = [
- {file = "black-25.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f85ba1ad15d446756b4ab5f3044731bf68b777f8f9ac9cdabd2425b97cd9c4e8"},
- {file = "black-25.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546eecfe9a3a6b46f9d69d8a642585a6eaf348bcbbc4d87a19635570e02d9f4a"},
- {file = "black-25.12.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17dcc893da8d73d8f74a596f64b7c98ef5239c2cd2b053c0f25912c4494bf9ea"},
- {file = "black-25.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:09524b0e6af8ba7a3ffabdfc7a9922fb9adef60fed008c7cd2fc01f3048e6e6f"},
- {file = "black-25.12.0-cp310-cp310-win_arm64.whl", hash = "sha256:b162653ed89eb942758efeb29d5e333ca5bb90e5130216f8369857db5955a7da"},
- {file = "black-25.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0cfa263e85caea2cff57d8f917f9f51adae8e20b610e2b23de35b5b11ce691a"},
- {file = "black-25.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a2f578ae20c19c50a382286ba78bfbeafdf788579b053d8e4980afb079ab9be"},
- {file = "black-25.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e1b65634b0e471d07ff86ec338819e2ef860689859ef4501ab7ac290431f9b"},
- {file = "black-25.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a3fa71e3b8dd9f7c6ac4d818345237dfb4175ed3bf37cd5a581dbc4c034f1ec5"},
- {file = "black-25.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:51e267458f7e650afed8445dc7edb3187143003d52a1b710c7321aef22aa9655"},
- {file = "black-25.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f96b7c98c1ddaeb07dc0f56c652e25bdedaac76d5b68a059d998b57c55594a"},
- {file = "black-25.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05dd459a19e218078a1f98178c13f861fe6a9a5f88fc969ca4d9b49eb1809783"},
- {file = "black-25.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1f68c5eff61f226934be6b5b80296cf6939e5d2f0c2f7d543ea08b204bfaf59"},
- {file = "black-25.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:274f940c147ddab4442d316b27f9e332ca586d39c85ecf59ebdea82cc9ee8892"},
- {file = "black-25.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:169506ba91ef21e2e0591563deda7f00030cb466e747c4b09cb0a9dae5db2f43"},
- {file = "black-25.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a05ddeb656534c3e27a05a29196c962877c83fa5503db89e68857d1161ad08a5"},
- {file = "black-25.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ec77439ef3e34896995503865a85732c94396edcc739f302c5673a2315e1e7f"},
- {file = "black-25.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e509c858adf63aa61d908061b52e580c40eae0dfa72415fa47ac01b12e29baf"},
- {file = "black-25.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:252678f07f5bac4ff0d0e9b261fbb029fa530cfa206d0a636a34ab445ef8ca9d"},
- {file = "black-25.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bc5b1c09fe3c931ddd20ee548511c64ebf964ada7e6f0763d443947fd1c603ce"},
- {file = "black-25.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0a0953b134f9335c2434864a643c842c44fba562155c738a2a37a4d61f00cad5"},
- {file = "black-25.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2355bbb6c3b76062870942d8cc450d4f8ac71f9c93c40122762c8784df49543f"},
- {file = "black-25.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9678bd991cc793e81d19aeeae57966ee02909877cb65838ccffef24c3ebac08f"},
- {file = "black-25.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:97596189949a8aad13ad12fcbb4ae89330039b96ad6742e6f6b45e75ad5cfd83"},
- {file = "black-25.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:778285d9ea197f34704e3791ea9404cd6d07595745907dd2ce3da7a13627b29b"},
- {file = "black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828"},
- {file = "black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7"},
+ {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"},
+ {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"},
]
-[package.dependencies]
-click = ">=8.0.0"
-mypy-extensions = ">=0.4.3"
-packaging = ">=22.0"
-pathspec = ">=0.9.0"
-platformdirs = ">=2"
-pytokens = ">=0.3.0"
+[[package]]
+name = "backports-tarfile"
+version = "1.2.0"
+description = "Backport of CPython tarfile module"
+optional = true
+python-versions = ">=3.8"
+groups = ["main"]
+markers = "extra == \"dev\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version < \"3.12\""
+files = [
+ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"},
+ {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"},
+]
[package.extras]
-colorama = ["colorama (>=0.4.3)"]
-d = ["aiohttp (>=3.10)"]
-jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
-uvloop = ["uvloop (>=0.15.2)"]
+docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"]
[[package]]
name = "build"
@@ -268,8 +260,10 @@ files = [
[package.dependencies]
colorama = {version = "*", markers = "os_name == \"nt\""}
+importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""}
packaging = ">=24.0"
pyproject_hooks = "*"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
[package.extras]
uv = ["uv (>=0.1.18)"]
@@ -665,6 +659,7 @@ files = [
[package.dependencies]
cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and platform_python_implementation != \"PyPy\""}
+typing-extensions = {version = ">=4.13.2", markers = "python_full_version < \"3.11.0\""}
[package.extras]
docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"]
@@ -690,22 +685,23 @@ files = [
]
[[package]]
-name = "flake8"
-version = "7.3.0"
-description = "the modular source code checker: pep8 pyflakes and co"
+name = "exceptiongroup"
+version = "1.3.1"
+description = "Backport of PEP 654 (exception groups)"
optional = true
-python-versions = ">=3.9"
+python-versions = ">=3.7"
groups = ["main"]
-markers = "extra == \"dev\""
+markers = "extra == \"dev\" and python_version == \"3.10\""
files = [
- {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"},
- {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"},
+ {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"},
+ {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"},
]
[package.dependencies]
-mccabe = ">=0.7.0,<0.8.0"
-pycodestyle = ">=2.14.0,<2.15.0"
-pyflakes = ">=3.4.0,<3.5.0"
+typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""}
+
+[package.extras]
+test = ["pytest (>=6)"]
[[package]]
name = "frozenlist"
@@ -886,35 +882,43 @@ files = [
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
-name = "iniconfig"
-version = "2.3.0"
-description = "brain-dead simple config-ini parsing"
+name = "importlib-metadata"
+version = "8.7.1"
+description = "Read metadata from Python packages"
optional = true
-python-versions = ">=3.10"
+python-versions = ">=3.9"
groups = ["main"]
-markers = "extra == \"dev\""
+markers = "extra == \"dev\" and (python_full_version < \"3.10.2\" or platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.12\""
files = [
- {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"},
- {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
+ {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"},
+ {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"},
]
+[package.dependencies]
+zipp = ">=3.20"
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+enabler = ["pytest-enabler (>=3.4)"]
+perf = ["ipython"]
+test = ["flufl.flake8", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
+type = ["mypy (<1.19) ; platform_python_implementation == \"PyPy\"", "pytest-mypy (>=1.0.1)"]
+
[[package]]
-name = "isort"
-version = "6.1.0"
-description = "A Python utility / library to sort Python imports."
+name = "iniconfig"
+version = "2.3.0"
+description = "brain-dead simple config-ini parsing"
optional = true
-python-versions = ">=3.9.0"
+python-versions = ">=3.10"
groups = ["main"]
markers = "extra == \"dev\""
files = [
- {file = "isort-6.1.0-py3-none-any.whl", hash = "sha256:58d8927ecce74e5087aef019f778d4081a3b6c98f15a80ba35782ca8a2097784"},
- {file = "isort-6.1.0.tar.gz", hash = "sha256:9b8f96a14cfee0677e78e941ff62f03769a06d412aabb9e2a90487b3b7e8d481"},
+ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"},
+ {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
]
-[package.extras]
-colors = ["colorama"]
-plugins = ["setuptools"]
-
[[package]]
name = "jaraco-classes"
version = "3.4.0"
@@ -948,6 +952,9 @@ files = [
{file = "jaraco_context-6.1.0.tar.gz", hash = "sha256:129a341b0a85a7db7879e22acd66902fda67882db771754574338898b2d5d86f"},
]
+[package.dependencies]
+"backports.tarfile" = {version = "*", markers = "python_version < \"3.12\""}
+
[package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
cover = ["pytest-cov"]
@@ -1030,6 +1037,7 @@ files = [
]
[package.dependencies]
+importlib_metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""}
"jaraco.classes" = "*"
"jaraco.context" = "*"
"jaraco.functools" = "*"
@@ -1327,19 +1335,6 @@ files = [
{file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"},
]
-[[package]]
-name = "mccabe"
-version = "0.7.0"
-description = "McCabe checker, plugin for flake8"
-optional = true
-python-versions = ">=3.6"
-groups = ["main"]
-markers = "extra == \"dev\""
-files = [
- {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
- {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
-]
-
[[package]]
name = "mdurl"
version = "0.1.2"
@@ -1523,18 +1518,8 @@ files = [
{file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"},
]
-[[package]]
-name = "mypy-extensions"
-version = "1.1.0"
-description = "Type system extensions for programs checked with the mypy type checker."
-optional = true
-python-versions = ">=3.8"
-groups = ["main"]
-markers = "extra == \"dev\""
-files = [
- {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
- {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
-]
+[package.dependencies]
+typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""}
[[package]]
name = "nh3"
@@ -1586,43 +1571,6 @@ files = [
{file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"},
]
-[[package]]
-name = "pathspec"
-version = "1.0.4"
-description = "Utility library for gitignore style pattern matching of file paths."
-optional = true
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "extra == \"dev\""
-files = [
- {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"},
- {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"},
-]
-
-[package.extras]
-hyperscan = ["hyperscan (>=0.7)"]
-optional = ["typing-extensions (>=4)"]
-re2 = ["google-re2 (>=1.1)"]
-tests = ["pytest (>=9)", "typing-extensions (>=4.15)"]
-
-[[package]]
-name = "platformdirs"
-version = "4.5.1"
-description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
-optional = true
-python-versions = ">=3.10"
-groups = ["main"]
-markers = "extra == \"dev\""
-files = [
- {file = "platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31"},
- {file = "platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda"},
-]
-
-[package.extras]
-docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"]
-type = ["mypy (>=1.18.2)"]
-
[[package]]
name = "pluggy"
version = "1.6.0"
@@ -1773,19 +1721,6 @@ files = [
{file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"},
]
-[[package]]
-name = "pycodestyle"
-version = "2.14.0"
-description = "Python style guide checker"
-optional = true
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "extra == \"dev\""
-files = [
- {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"},
- {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"},
-]
-
[[package]]
name = "pycparser"
version = "3.0"
@@ -1799,19 +1734,6 @@ files = [
{file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"},
]
-[[package]]
-name = "pyflakes"
-version = "3.4.0"
-description = "passive checker of Python programs"
-optional = true
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "extra == \"dev\""
-files = [
- {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"},
- {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"},
-]
-
[[package]]
name = "pygments"
version = "2.19.2"
@@ -1856,10 +1778,12 @@ files = [
[package.dependencies]
colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""}
iniconfig = ">=1.0.1"
packaging = ">=22"
pluggy = ">=1.5,<2"
pygments = ">=2.7.2"
+tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
@@ -1899,6 +1823,7 @@ files = [
]
[package.dependencies]
+backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""}
pytest = ">=8.2,<10"
typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""}
@@ -1922,62 +1847,6 @@ files = [
lxml = ">=3.1.0"
typing_extensions = ">=4.9.0"
-[[package]]
-name = "pytokens"
-version = "0.4.1"
-description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons."
-optional = true
-python-versions = ">=3.8"
-groups = ["main"]
-markers = "extra == \"dev\""
-files = [
- {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"},
- {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"},
- {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c"},
- {file = "pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7"},
- {file = "pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2"},
- {file = "pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440"},
- {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc"},
- {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d"},
- {file = "pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16"},
- {file = "pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6"},
- {file = "pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083"},
- {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1"},
- {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1"},
- {file = "pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9"},
- {file = "pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68"},
- {file = "pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b"},
- {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f"},
- {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1"},
- {file = "pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4"},
- {file = "pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78"},
- {file = "pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321"},
- {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa"},
- {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d"},
- {file = "pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324"},
- {file = "pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9"},
- {file = "pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb"},
- {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3"},
- {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975"},
- {file = "pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a"},
- {file = "pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918"},
- {file = "pytokens-0.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:da5baeaf7116dced9c6bb76dc31ba04a2dc3695f3d9f74741d7910122b456edc"},
- {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009"},
- {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1"},
- {file = "pytokens-0.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dcafc12c30dbaf1e2af0490978352e0c4041a7cde31f4f81435c2a5e8b9cabb6"},
- {file = "pytokens-0.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:42f144f3aafa5d92bad964d471a581651e28b24434d184871bd02e3a0d956037"},
- {file = "pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3"},
- {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1"},
- {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db"},
- {file = "pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1"},
- {file = "pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a"},
- {file = "pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de"},
- {file = "pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a"},
-]
-
-[package.extras]
-dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"]
-
[[package]]
name = "pywin32-ctypes"
version = "0.2.3"
@@ -2088,6 +1957,35 @@ pygments = ">=2.13.0,<3.0.0"
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"]
+[[package]]
+name = "ruff"
+version = "0.15.6"
+description = "An extremely fast Python linter and code formatter, written in Rust."
+optional = true
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "extra == \"dev\""
+files = [
+ {file = "ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff"},
+ {file = "ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3"},
+ {file = "ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb"},
+ {file = "ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8"},
+ {file = "ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e"},
+ {file = "ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15"},
+ {file = "ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9"},
+ {file = "ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab"},
+ {file = "ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e"},
+ {file = "ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c"},
+ {file = "ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512"},
+ {file = "ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0"},
+ {file = "ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb"},
+ {file = "ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0"},
+ {file = "ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c"},
+ {file = "ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406"},
+ {file = "ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837"},
+ {file = "ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4"},
+]
+
[[package]]
name = "secretstorage"
version = "3.5.0"
@@ -2127,18 +2025,6 @@ enabler = ["pytest-enabler (>=2.2)"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.18.*)", "pytest-mypy"]
-[[package]]
-name = "six"
-version = "1.17.0"
-description = "Python 2 and 3 compatibility utilities"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-groups = ["main"]
-files = [
- {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
- {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
-]
-
[[package]]
name = "tomli"
version = "2.4.0"
@@ -2213,6 +2099,7 @@ files = [
[package.dependencies]
click = "*"
jinja2 = "*"
+tomli = {version = "*", markers = "python_version < \"3.11\""}
[package.extras]
dev = ["furo (>=2024.05.06)", "nox", "packaging", "sphinx (>=5)", "twisted"]
@@ -2458,6 +2345,7 @@ packaging = "*"
readme_renderer = {version = ">=40", extras = ["md"]}
requests = "*"
setuptools = ">=61.0.0"
+tomli = {version = "*", markers = "python_version < \"3.11\""}
twine = ">=1.6.0"
wheel = "*"
@@ -2483,11 +2371,32 @@ tomli = {version = "*", markers = "python_version >= \"3.6\""}
towncrier = ">=19.9.0"
"zest.releaser" = ">=6.17.0"
+[[package]]
+name = "zipp"
+version = "3.23.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+optional = true
+python-versions = ">=3.9"
+groups = ["main"]
+markers = "extra == \"dev\" and (python_full_version < \"3.10.2\" or platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.12\""
+files = [
+ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"},
+ {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"},
+]
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+enabler = ["pytest-enabler (>=2.2)"]
+test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
+type = ["pytest-mypy"]
+
[extras]
-dev = ["black", "flake8", "isort", "pytest", "pytest-aiohttp", "towncrier", "zest-releaser", "zestreleaser-towncrier"]
+dev = ["pytest", "pytest-aiohttp", "ruff", "towncrier", "zest-releaser", "zestreleaser-towncrier"]
server = ["aiohttp"]
[metadata]
lock-version = "2.1"
-python-versions = ">=3.12,<4.0"
-content-hash = "7fded638ffa1c154b8d944c3e40636f809eebdf41a85459d1593102cfbffc897"
+python-versions = ">=3.10,<4.0"
+content-hash = "d867eea87a7089e1dcfa470ba56db8319705359d51242369a0eef1523f6d4b59"
diff --git a/pyproject.toml b/pyproject.toml
index 6bc8c97..12f4cf4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,25 +1,22 @@
[project]
name = "docxcompose"
-version = "2.0.2"
+version = "2.1.0.dev0"
description = "Compose .docx documents"
authors = [{name="Thomas Buchberger", email=""}]
license = "MIT license"
readme = "README.rst"
-requires-python = ">=3.12,<4.0"
+requires-python = ">=3.10,<4.0"
dependencies = [
"babel (>=2.10.0)",
"lxml",
"python-docx (>=1.0.0)",
- "six",
]
[project.optional-dependencies]
dev = [
- "black (>=25.1.0)",
- "flake8 (>=7.3.0,<8.0.0)",
- "isort (>=6.0.1,<7.0.0)",
"pytest (>=9.0.2,<10.0.0)",
"pytest-aiohttp",
+ "ruff (>=0.15.0)",
"towncrier (>=25.8.0)",
"zest-releaser (>=9.6.2,<10.0.0)",
"zestreleaser-towncrier (>=1.3.0,<2.0.0)",
@@ -39,13 +36,22 @@ packages = [{include = "docxcompose"}]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
-[tool.isort]
-force_alphabetical_sort_within_sections = true
-force_single_line = true
-from_first = false
-line_length = 200
-known_first_party = "docxcompose"
-lines_after_imports = 2
+[tool.ruff]
+target-version = "py310"
+
+[tool.ruff.lint]
+select = ["B", "E", "F", "I"]
+ignore = ["B904", "B905"]
+
+[tool.ruff.lint.per-file-ignores]
+"tests/*" = ["E501"]
+
+[tool.ruff.lint.isort]
+force-single-line = true
+from-first = false
+known-first-party = ["docxcompose"]
+lines-after-imports = 2
+order-by-type = false
[tool.pytest.ini_options]
python_files = ["test_*.py"]
@@ -73,3 +79,7 @@ showcontent = true
directory = "other"
name = "Other changes:"
showcontent = true
+
+[tool.zest-releaser]
+release = "false"
+history-file = "CHANGELOG.md"
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 8d49e27..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,4 +0,0 @@
-[zest.releaser]
-release = no
-history-file = CHANGELOG.md
-push-changes = yes
diff --git a/tests/conftest.py b/tests/conftest.py
index 0aad03e..4f8af86 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -18,7 +18,6 @@ def pytest_assertrepr_compare(config, op, left, right):
and isinstance(right, ComparableDocument)
and op == "=="
):
-
left.post_compare_failed(right)
right.post_compare_failed(left)
@@ -47,7 +46,6 @@ def pytest_assertrepr_compare(config, op, left, right):
diffs = []
for lpart, rpart in left.neq_parts:
-
if not lpart.partname.endswith(".xml"):
diffs.append("Binary parts differ {}".format(lpart.partname))
diffs.append("")
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 135d961..c0406db 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -7,13 +7,11 @@
class FieldForTesting(FieldBase):
-
def _get_fieldname_string(self):
return self.node
class TestFieldNameParsing(object):
-
def test_can_parse_quoted_property_names(self):
node = ' DOCPROPERTY "Propertyname" \\* MERGEFORMAT '
assert FieldForTesting(node).name == "Propertyname"
@@ -40,7 +38,6 @@ def test_can_parse_unquoted_property_names_with_extra_spaces(self):
class TestFieldDateFormatParsing(object):
-
def test_can_parse_quoted_date_format(self):
node = ' DOCPROPERTY "Propertyname" \\@ "ddd-yy-MM" \\* MERGEFORMAT '
assert FieldForTesting(node).name == "Propertyname"
@@ -73,7 +70,6 @@ def test_can_parse_unquoted_date_format_with_extra_spaces(self):
class TestFieldDateFormatMapping(object):
-
def test_correctly_maps_simple_date(self):
date = datetime(2020, 11, 19)
diff --git a/tests/test_properties.py b/tests/test_properties.py
index baadd91..a95083f 100644
--- a/tests/test_properties.py
+++ b/tests/test_properties.py
@@ -23,7 +23,6 @@
class TestIdentifyDocpropertiesInDocument(object):
-
def test_identifies_simple_fields_correctly(self):
document = Document(docx_path("outdated_docproperty_with_umlauts.docx"))
properties = CustomProperties(document).find_docprops_in_document()
@@ -39,9 +38,9 @@ def test_identifies_complex_fields_correctly(self):
properties = CustomProperties(document).find_docprops_in_document()
assert len(document.paragraphs) == 1, "input file should contains one paragraph"
- assert (
- len(properties) == 3
- ), "input should contain three complex field docproperties"
+ assert len(properties) == 3, (
+ "input should contain three complex field docproperties"
+ )
# check that all fields were identified as complex fields
for prop in properties:
@@ -72,9 +71,9 @@ def test_finds_run_nodes_in_complex_fields_correctly(self):
document = Document(docx_path("three_props_in_same_paragraph.docx"))
properties = CustomProperties(document).find_docprops_in_document()
- assert (
- len(properties) == 3
- ), "input should contain three complex field docproperties"
+ assert len(properties) == 3, (
+ "input should contain three complex field docproperties"
+ )
# In the first field, there are the following runs: begin, docprop,
# a separate, 3 runs for the value (because of spellcheck) and end
@@ -125,21 +124,21 @@ def test_finds_run_nodes_in_complex_field_without_separate_correctly(self):
document = Document(docx_path("complex_field_without_separate.docx"))
properties = CustomProperties(document).find_docprops_in_document()
- assert (
- len(properties) == 2
- ), "input should contain two complex field docproperties"
+ assert len(properties) == 2, (
+ "input should contain two complex field docproperties"
+ )
# The "User.FullName" docproperty should be the one without a separate run
# In this field, there are the following runs: begin, docprop and end
matches = [prop for prop in properties if prop.name == "User.FullName"]
assert len(matches) == 1, "There should be only one User.FullName docproperty"
prop = matches[0]
- assert (
- prop.get_separate_run() is None
- ), "This complex field should not have a separate run."
- assert (
- prop.get_runs_for_update() == []
- ), "As there is no separate run, there should be no run to update"
+ assert prop.get_separate_run() is None, (
+ "This complex field should not have a separate run."
+ )
+ assert prop.get_runs_for_update() == [], (
+ "As there is no separate run, there should be no run to update"
+ )
# As there are no separate, all runs should be removed when dissolving
# the property.
@@ -153,9 +152,9 @@ def test_finds_field_name_and_format_when_split_in_several_runs(self):
document = Document(docx_path("complex_field_with_split_fieldname.docx"))
properties = CustomProperties(document).find_docprops_in_document()
- assert (
- len(properties) == 1
- ), "input should contain one complex field docproperties"
+ assert len(properties) == 1, (
+ "input should contain one complex field docproperties"
+ )
prop = properties[0]
@@ -187,9 +186,9 @@ def test_finds_header_footer_of_different_sections(self):
document = Document(docx_path("docproperties_header_footer_3_sections.docx"))
properties = CustomProperties(document).find_docprops_in_document()
- assert (
- len(properties) == 5
- ), "input should contain 5 properties in header/footer and body"
+ assert len(properties) == 5, (
+ "input should contain 5 properties in header/footer and body"
+ )
expected_properties = [
"Text Property",
@@ -203,7 +202,6 @@ def test_finds_header_footer_of_different_sections(self):
class TestUpdateAllDocproperties(object):
-
def test_updates_doc_properties_in_header(self):
document = Document(docx_path("docproperties_header.docx"))
@@ -410,9 +408,9 @@ def test_complex_docprop_fields_with_multiple_textnodes_are_updated(self):
document = Document(docx_path("spellchecked_docproperty.docx"))
paragraphs = xpath(document.element.body, "//w:p")
assert len(paragraphs) == 1, "input file contains one paragraph"
- assert (
- len(xpath(document.element.body, "//w:instrText")) == 1
- ), "input contains one complex field docproperty"
+ assert len(xpath(document.element.body, "//w:instrText")) == 1, (
+ "input contains one complex field docproperty"
+ )
w_p = paragraphs[0]
cached_values = cached_complex_field_values(w_p)
@@ -423,18 +421,18 @@ def test_complex_docprop_fields_with_multiple_textnodes_are_updated(self):
w_p = xpath(document.element.body, "//w:p")[0]
cached_values = cached_complex_field_values(w_p)
- assert (
- len(cached_values) == 1
- ), "doc property value has been reset to one cached value"
+ assert len(cached_values) == 1, (
+ "doc property value has been reset to one cached value"
+ )
assert cached_values[0] == "i will be spllchecked!"
def test_complex_docprop_with_multiple_textnode_in_same_run_are_updated(self):
document = Document(docx_path("two_textnodes_in_run_docproperty.docx"))
paragraphs = xpath(document.element.body, "//w:p")
assert len(paragraphs) == 1, "input file contains one paragraph"
- assert (
- len(xpath(document.element.body, "//w:instrText")) == 1
- ), "input contains one complex field docproperty"
+ assert len(xpath(document.element.body, "//w:instrText")) == 1, (
+ "input contains one complex field docproperty"
+ )
w_p = paragraphs[0]
cached_values = cached_complex_field_values(w_p)
@@ -445,9 +443,9 @@ def test_complex_docprop_with_multiple_textnode_in_same_run_are_updated(self):
w_p = xpath(document.element.body, "//w:p")[0]
cached_values = cached_complex_field_values(w_p)
- assert (
- len(cached_values) == 1
- ), "doc property value has been reset to one cached value"
+ assert len(cached_values) == 1, (
+ "doc property value has been reset to one cached value"
+ )
assert cached_values[0] == "i will be spllchecked!"
def test_three_complex_docprop_in_same_paragraph(self):
@@ -456,9 +454,9 @@ def test_three_complex_docprop_in_same_paragraph(self):
assert len(document.paragraphs) == 1, "input file should contains one paragraph"
paragraph = document.paragraphs[0]
- assert (
- len(properties.find_docprops_in_document()) == 3
- ), "input should contain three complex field docproperties"
+ assert len(properties.find_docprops_in_document()) == 3, (
+ "input should contain three complex field docproperties"
+ )
text = "{text} / {num} mor between the fields {text} and some afte the three fields"
assert paragraph.text == text.format(text="I was spellcecked", num=0)
@@ -471,9 +469,9 @@ def test_multiple_identical_docprops_get_updated(self):
document = Document(docx_path("multiple_identical_properties.docx"))
assert len(document.paragraphs) == 3, "input file should contain 3 paragraphs"
for paragraph in document.paragraphs:
- assert (
- len(xpath(paragraph._p, ".//w:instrText")) == 1
- ), "paragraph should contain one complex field docproperties"
+ assert len(xpath(paragraph._p, ".//w:instrText")) == 1, (
+ "paragraph should contain one complex field docproperties"
+ )
assert paragraph.text == "Foo"
@@ -496,17 +494,17 @@ def test_docproperty_without_separate_does_get_updated(self):
matches = [prop for prop in properties if prop.name == "User.FullName"]
assert len(matches) == 1, "There should be only one User.FullName docproperty"
fullname = matches[0]
- assert (
- fullname.get_separate_run() is None
- ), "This complex field should not have a separate run."
+ assert fullname.get_separate_run() is None, (
+ "This complex field should not have a separate run."
+ )
# Make sure that 'Dossier.Title' field has a separate node
matches = [prop for prop in properties if prop.name == "Dossier.Title"]
assert len(matches) == 1, "There should be only one Dossier.Title docproperty"
title = matches[0]
- assert (
- title.get_separate_run() is not None
- ), "This complex field should have a separate run."
+ assert title.get_separate_run() is not None, (
+ "This complex field should have a separate run."
+ )
# Check the content of the paragraphs before update
assert len(paragraphs) == 2
@@ -526,9 +524,7 @@ def test_date_docprops_with_format_get_updated(self):
assert len(document.paragraphs) == 3, "input file should contain 3 paragraph"
expected_values = ["11.06.19", "mardi 11 juin 2019", "11-6-19 0:0:0"]
- for i, (expected, paragraph) in enumerate(
- zip(expected_values, document.paragraphs)
- ):
+ for expected, paragraph in zip(expected_values, document.paragraphs):
assert paragraph.text == expected
CustomProperties(document).update_all()
@@ -537,28 +533,28 @@ def test_date_docprops_with_format_get_updated(self):
for i, (expected, paragraph) in enumerate(
zip(expected_values, document.paragraphs)
):
- assert (
- paragraph.text == expected
- ), "docprop {} was not updated correctly".format(i + 1)
+ assert paragraph.text == expected, (
+ "docprop {} was not updated correctly".format(i + 1)
+ )
def test_date_docprops_respect_language(self):
document = Document(docx_path("date_docproperties_with_format.docx"))
assert len(document.paragraphs) == 3, "input file should contain 3 paragraph"
CustomProperties(document).update_all()
- document.paragraphs[1].text == "jeudi 23 janvier 2020"
+ assert document.paragraphs[1].text == "jeudi 23 janvier 2020"
document.element.xpath(".//w:lang")[0].set(
docx.oxml.shared.qn("w:val"), "de-CH"
)
CustomProperties(document).update_all()
- document.paragraphs[1].text == "Donnerstag 23 Januar 2020"
+ assert document.paragraphs[1].text == "Donnerstag 23 Januar 2020"
document.element.xpath(".//w:lang")[0].set(
docx.oxml.shared.qn("w:val"), "en-US"
)
CustomProperties(document).update_all()
- document.paragraphs[1].text == "Thursday 23 January 2020"
+ assert document.paragraphs[1].text == "Thursday 23 January 2020"
def test_docprops_with_split_fieldname_get_updated(self):
document = Document(docx_path("complex_field_with_split_fieldname.docx"))
@@ -572,7 +568,6 @@ def test_docprops_with_split_fieldname_get_updated(self):
class TestUpdateSpecificDocproperty(object):
-
def test_simple_field_gets_updated(self):
document = Document(docx_path("outdated_docproperty_with_umlauts.docx"))
assert_simple_field_value("xxx", document.element.body, "F\xfc\xfc")
@@ -586,9 +581,9 @@ def test_complex_field_gets_updated(self):
assert len(document.paragraphs) == 6, "input file should contain 6 paragraphs"
properties = xpath(document.element.body, ".//w:instrText")
- assert (
- len(properties) == 5
- ), "input should contain five complex field docproperties"
+ assert len(properties) == 5, (
+ "input should contain five complex field docproperties"
+ )
expected_paragraphs = [
"Custom Doc Properties",
@@ -611,9 +606,9 @@ def test_multiple_identical_docprops_get_updated(self):
document = Document(docx_path("multiple_identical_properties.docx"))
assert len(document.paragraphs) == 3, "input file should contain 3 paragraphs"
for paragraph in document.paragraphs:
- assert (
- len(xpath(paragraph._p, ".//w:instrText")) == 1
- ), "paragraph should contain one complex field docproperties"
+ assert len(xpath(paragraph._p, ".//w:instrText")) == 1, (
+ "paragraph should contain one complex field docproperties"
+ )
assert paragraph.text == "Foo"
@@ -626,7 +621,6 @@ def test_multiple_identical_docprops_get_updated(self):
class TestDissolveField(object):
-
def test_removes_simple_field_but_keeps_value(self):
document = Document(docx_path("outdated_docproperty_with_umlauts.docx"))
assert len(document.paragraphs) == 1, "input file should contain 1 paragraph"
@@ -648,9 +642,9 @@ def test_removes_complex_field_but_keeps_value(self):
assert len(document.paragraphs) == 6, "input file should contain 6 paragraphs"
properties = xpath(document.element.body, ".//w:instrText")
- assert (
- len(properties) == 5
- ), "input should contain five complex field docproperties"
+ assert len(properties) == 5, (
+ "input should contain five complex field docproperties"
+ )
expected_paragraphs = [
"Custom Doc Properties",
@@ -674,9 +668,9 @@ def test_removes_complex_field_but_keeps_value(self):
def test_dissolves_all_instances_of_given_field(self):
document = Document(docx_path("multiple_identical_properties.docx"))
assert len(document.paragraphs) == 3, "input file should contain 3 paragraphs"
- assert (
- len(xpath(document.element.body, ".//w:instrText")) == 3
- ), "document should contain three complex field docproperties"
+ assert len(xpath(document.element.body, ".//w:instrText")) == 3, (
+ "document should contain three complex field docproperties"
+ )
for paragraph in document.paragraphs:
assert paragraph.text == "Foo"
@@ -684,9 +678,9 @@ def test_dissolves_all_instances_of_given_field(self):
CustomProperties(document).dissolve_fields("Text Property")
assert len(document.paragraphs) == 3
- assert (
- len(xpath(document.element.body, ".//w:instrText")) == 0
- ), "document should not contain any complex field anymore"
+ assert len(xpath(document.element.body, ".//w:instrText")) == 0, (
+ "document should not contain any complex field anymore"
+ )
for paragraph in document.paragraphs:
assert paragraph.text == "Foo", "value should have been kept in document"
@@ -695,9 +689,9 @@ def test_dissolving_field_when_three_complex_docprop_in_same_paragraph(self):
assert len(document.paragraphs) == 1, "input file should contains one paragraph"
paragraph = document.paragraphs[0]
properties = CustomProperties(document)
- assert (
- len(properties.find_docprops_in_document()) == 3
- ), "input should contain three complex field docproperties"
+ assert len(properties.find_docprops_in_document()) == 3, (
+ "input should contain three complex field docproperties"
+ )
text = "{text} / {num} mor between the fields {text} and some afte the three fields"
assert paragraph.text == text.format(text="I was spellcecked", num=0)
@@ -705,9 +699,9 @@ def test_dissolving_field_when_three_complex_docprop_in_same_paragraph(self):
properties.dissolve_fields("Text Property")
assert len(document.paragraphs) == 1
- assert (
- len(properties.find_docprops_in_document()) == 1
- ), "document should contain one complex field after removal"
+ assert len(properties.find_docprops_in_document()) == 1, (
+ "document should contain one complex field after removal"
+ )
assert paragraph.text == text.format(text="I was spellcecked", num=0)
diff --git a/tests/utils.py b/tests/utils.py
index f8b5aee..090ea17 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -102,9 +102,9 @@ class ComposedDocument(ComparableDocument):
"""
- def __init__(self, master_filename, filename, *filenames):
+ def __init__(self, master_filename, *filenames):
composer = Composer(Document(docx_path(master_filename)))
- for filename in (filename,) + filenames:
+ for filename in filenames:
composer.append(Document(docx_path(filename)))
super(ComposedDocument, self).__init__(composer.doc)
diff --git a/version.txt b/version.txt
deleted file mode 100644
index eb5820c..0000000
--- a/version.txt
+++ /dev/null
@@ -1 +0,0 @@
-2.1.0.dev0