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