pax_global_header00006660000000000000000000000064142015477000014512gustar00rootroot0000000000000052 comment=6121a7395908359adf7f50ff0c7b78c2cdc3c69d matrix-python-common-1.1.0/000077500000000000000000000000001420154770000156225ustar00rootroot00000000000000matrix-python-common-1.1.0/.github/000077500000000000000000000000001420154770000171625ustar00rootroot00000000000000matrix-python-common-1.1.0/.github/CODEOWNERS000066400000000000000000000001651420154770000205570ustar00rootroot00000000000000 # Automatically request reviews from the synapse-core team when a pull request comes in. * @matrix-org/synapse-core matrix-python-common-1.1.0/.github/workflows/000077500000000000000000000000001420154770000212175ustar00rootroot00000000000000matrix-python-common-1.1.0/.github/workflows/ci.yml000066400000000000000000000021071420154770000223350ustar00rootroot00000000000000name: Linting and Tests on: push: branches: ["main"] pull_request: jobs: check-code-style: name: Check code style runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: "3.x" - run: python -m pip install tox - run: tox -e check_codestyle check-types: name: Check types with Mypy runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: "3.x" - run: python -m pip install tox - run: tox -e check_types unit-tests: name: Unit tests runs-on: ubuntu-latest strategy: matrix: # Run the unit tests both against our oldest supported Python version # and the newest stable. python_version: [ "3.6", "3.x" ] steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: ${{ matrix.python_version }} - run: python -m pip install tox - run: tox -e py matrix-python-common-1.1.0/.gitignore000066400000000000000000000001051420154770000176060ustar00rootroot00000000000000/.idea /.venv /*.egg-info /.envrc /.tox _trial_temp __pycache__ /distmatrix-python-common-1.1.0/LICENSE000066400000000000000000000236761420154770000166450ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS matrix-python-common-1.1.0/MANIFEST.in000066400000000000000000000001101420154770000173500ustar00rootroot00000000000000# Include tests in the source distribution recursive-include tests *.py matrix-python-common-1.1.0/README.md000066400000000000000000000026321420154770000171040ustar00rootroot00000000000000# matrix-python-common Common utilities for Synapse, Sydent and Sygnal. ## Installation ```shell pip install matrix-common ``` ## Usage ```py import matrix_common ``` ## Development In a virtual environment with pip ≥ 21.1, run ```shell pip install -e .[dev] ``` To run the unit tests, you can either use: ```shell tox -e py ``` or ```shell trial tests ``` To run the linters and `mypy` type checker, use `./scripts-dev/lint.sh`. ## Releasing The exact steps for releasing will vary; but this is an approach taken by the Synapse developers (assuming a Unix-like shell): 1. Set a shell variable to the version you are releasing (this just makes subsequent steps easier): ```shell version=X.Y.Z ``` 2. Update `setup.cfg` so that the `version` is correct. 3. Stage the changed files and commit. ```shell git add -u git commit -m v$version -n ``` 4. Push your changes. ```shell git push ``` 5. When ready, create a signed tag for the release: ```shell git tag -s v$version ``` Base the tag message on the changelog. 6. Push the tag. ```shell git push origin tag v$version ``` 7. If applicable: Create a *release*, based on the tag you just pushed, on GitHub or GitLab. 8. If applicable: Create a source distribution and upload it to PyPI: ```shell python -m build twine upload dist/matrix_common-$version* ``` matrix-python-common-1.1.0/matrix_common/000077500000000000000000000000001420154770000204765ustar00rootroot00000000000000matrix-python-common-1.1.0/matrix_common/__init__.py000066400000000000000000000000001420154770000225750ustar00rootroot00000000000000matrix-python-common-1.1.0/matrix_common/py.typed000066400000000000000000000000001420154770000221630ustar00rootroot00000000000000matrix-python-common-1.1.0/matrix_common/regex.py000066400000000000000000000067111420154770000221670ustar00rootroot00000000000000# Copyright 2021 The Matrix.org Foundation C.I.C. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re from typing import List, Pattern _WILDCARD_RUN = re.compile(r"([\?\*]+)") def glob_to_regex( glob: str, *, word_boundary: bool = False, ignore_case: bool = True, ) -> Pattern[str]: """Converts a glob to a compiled regex object. Args: glob: pattern to match word_boundary: If `True`, the pattern will be allowed to match at word boundaries anywhere in the string. Otherwise, the pattern is anchored at the start and end of the string. When using this option, the pattern may match up to one extra non-word character on either side. The matching substring may be obtained from a capture group. ignore_case: If `True`, the pattern will be case-insensitive. Defaults to `True`. Returns: compiled regex pattern """ # Patterns with wildcards must be simplified to avoid performance cliffs # - The glob `?**?**?` is equivalent to the glob `???*` # - The glob `???*` is equivalent to the regex `.{3,}` chunks: List[str] = [] for chunk in _WILDCARD_RUN.split(glob): # No wildcards? re.escape() if not _WILDCARD_RUN.match(chunk): chunks.append(re.escape(chunk)) continue # Wildcards? Simplify. question_marks = chunk.count("?") if "*" in chunk: chunks.append(".{%d,}" % (question_marks,)) else: chunks.append(".{%d}" % (question_marks,)) pattern = "".join(chunks) if word_boundary: pattern = to_word_pattern(pattern) else: # `\A` anchors at start of string, `\Z` at end of string # `\Z` is not the same as `$`! The latter will match the position before # a `\n` at the end of the string. pattern = rf"\A({pattern})\Z" return re.compile(pattern, re.IGNORECASE if ignore_case else 0) def to_word_pattern(pattern: str) -> str: """Converts the given pattern to one that only matches on whole words. Adds word boundary characters to the start and end of a pattern to require that the match occur as a whole word. If the start or end of the pattern is a non-word character, then a word boundary is not required to precede or succeed it. A word boundary is considered to be the boundary between a word and non-word character. As such, the returned pattern is not appropriate for internationalized text search because there are languages which do not use spaces between words. Args: pattern: The pattern to wrap. Returns: A new pattern that only matches on whole words. The new pattern may match up to one extra non-word character on either side. The exact match is provided by a capture group. """ # `^|\W` and `\W|$` handle the case where `pattern` starts or ends with a non-word # character. return rf"(?:^|\W|\b)({pattern})(?:\b|\W|$)" matrix-python-common-1.1.0/matrix_common/versionstring.py000066400000000000000000000065041420154770000237710ustar00rootroot00000000000000# Copyright 2016 OpenMarket Ltd # Copyright 2021-2022 The Matrix.org Foundation C.I.C. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import functools import logging import subprocess try: from importlib.metadata import distribution except ImportError: from importlib_metadata import distribution # type: ignore __all__ = ["get_distribution_version_string"] logger = logging.getLogger(__name__) @functools.lru_cache() def get_distribution_version_string(distribution_name: str) -> str: """Calculate a git-aware version string for a distribution package. A "distribution package" is a thing that you can e.g. install and manage with pip. It can contain modules, an "import package" of multiple modules, and arbitrary resource data. See the glossary at https://packaging.python.org/en/latest/glossary/#term-Distribution-Package for all your taxonomic needs. Often a distribution package contains exactly import package---possibly with _different_ names. For example, one can install the "matrix-sydent" distribution package from PyPI using pip, and doing so makes the "sydent" import package available to import. Args: distribution_name: The name of the distribution package to check the version of Raises: importlib.metadata.PackageNotFoundError if the given distribution name doesn't exist. Returns: The module version, possibly with git version information included. """ dist = distribution(distribution_name) version_string = dist.version cwd = dist.locate_file(".") try: def _run_git_command(prefix: str, *params: str) -> str: try: result = ( subprocess.check_output( ["git", *params], stderr=subprocess.DEVNULL, cwd=cwd ) .strip() .decode("ascii") ) return prefix + result except (subprocess.CalledProcessError, FileNotFoundError): return "" git_branch = _run_git_command("b=", "rev-parse", "--abbrev-ref", "HEAD") git_tag = _run_git_command("t=", "describe", "--exact-match") git_commit = _run_git_command("", "rev-parse", "--short", "HEAD") dirty_string = "-this_is_a_dirty_checkout" is_dirty = _run_git_command("", "describe", "--dirty=" + dirty_string).endswith( dirty_string ) git_dirty = "dirty" if is_dirty else "" if git_branch or git_tag or git_commit or git_dirty: git_version = ",".join( s for s in (git_branch, git_tag, git_commit, git_dirty) if s ) version_string = f"{version_string} ({git_version})" except Exception as e: logger.info("Failed to check for git repository: %s", e) return version_string matrix-python-common-1.1.0/mypy.ini000066400000000000000000000000601420154770000173150ustar00rootroot00000000000000[mypy] strict = true warn_unused_ignores = falsematrix-python-common-1.1.0/pyproject.toml000066400000000000000000000002631420154770000205370ustar00rootroot00000000000000[build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" [tool.isort] profile = "black" known_first_party = [ "matrix_common", "tests" ] matrix-python-common-1.1.0/scripts-dev/000077500000000000000000000000001420154770000200655ustar00rootroot00000000000000matrix-python-common-1.1.0/scripts-dev/lint.sh000077500000000000000000000005771420154770000214030ustar00rootroot00000000000000#!/usr/bin/env bash # Runs linting scripts and type checking # isort - sorts import statements # black - opinionated code formatter # flake8 - lints and finds mistakes # mypy - checks type annotations set -e files=( "matrix_common" "tests" ) # Print out the commands being run set -x isort "${files[@]}" python3 -m black "${files[@]}" flake8 "${files[@]}" mypy "${files[@]}" matrix-python-common-1.1.0/setup.cfg000066400000000000000000000021121420154770000174370ustar00rootroot00000000000000[metadata] name = matrix_common description = Common utilities for Synapse, Sydent and Sygnal long_description = file: README.md long_description_content_type = text/markdown url = https://github.com/matrix-org/matrix-python-common version = 1.1.0 classifiers = License :: OSI Approved :: Apache Software License [options] packages = matrix_common python_requires = >= 3.6 install_requires = attrs importlib_metadata >= 1.4; python_version < '3.8' [options.package_data] matrix_common = py.typed [options.extras_require] test = tox twisted aiounittest dev = %(test)s # for type checking mypy == 0.910 # for linting black == 21.9b0 flake8 == 4.0.1 isort == 5.9.3 [flake8] # see https://pycodestyle.readthedocs.io/en/latest/intro.html#error-codes # for error codes. The ones we ignore are: # W503: line break before binary operator # W504: line break after binary operator # E203: whitespace before ':' (which is contrary to pep8?) # E501: Line too long (black enforces this for us) # (this is a subset of those ignored in Synapse) ignore=W503,W504,E203,E501 matrix-python-common-1.1.0/tests/000077500000000000000000000000001420154770000167645ustar00rootroot00000000000000matrix-python-common-1.1.0/tests/__init__.py000066400000000000000000000000001420154770000210630ustar00rootroot00000000000000matrix-python-common-1.1.0/tests/test_regex.py000066400000000000000000000101531420154770000215070ustar00rootroot00000000000000# Copyright 2021 The Matrix.org Foundation C.I.C. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re from unittest import TestCase from matrix_common.regex import glob_to_regex, to_word_pattern class GlobToRegexTestCase(TestCase): def test_literal_match(self) -> None: """Tests matching against a literal.""" pattern = glob_to_regex("foobaz") self.assertRegex( "FoobaZ", pattern, "patterns should match and be case-insensitive" ) self.assertNotRegex( "x foobaz", pattern, "pattern should not match at word boundaries" ) def test_wildcard_match(self) -> None: """Tests matching with wildcards.""" pattern = glob_to_regex("f?o*baz") self.assertRegex( "FoobarbaZ", pattern, "* should match string and pattern should be case-insensitive", ) self.assertRegex("foobaz", pattern, "* should match 0 characters") self.assertNotRegex("fooxaz", pattern, "the character after * must match") self.assertNotRegex("fobbaz", pattern, "? should not match 0 characters") self.assertNotRegex("fiiobaz", pattern, "? should not match 2 characters") def test_multi_wildcard(self) -> None: """Tests matching with multiple wildcards in a row.""" pattern = glob_to_regex("**baz") self.assertRegex("agsgsbaz", pattern, "** should match any string") self.assertRegex("baz", pattern, "** should match the empty string") self.assertEqual(pattern.pattern, r"\A(.{0,}baz)\Z") pattern = glob_to_regex("*?baz") self.assertRegex("agsgsbaz", pattern, "*? should match any string") self.assertRegex("abaz", pattern, "*? should match a single char") self.assertNotRegex("baz", pattern, "*? should not match the empty string") self.assertEqual(pattern.pattern, r"\A(.{1,}baz)\Z") pattern = glob_to_regex("a?*?*?baz") self.assertRegex("a g baz", pattern, "?*?*? should match 3 chars") self.assertNotRegex("a..baz", pattern, "?*?*? should not match 2 chars") self.assertRegex("a.gg.baz", pattern, "?*?*? should match 4 chars") self.assertEqual(pattern.pattern, r"\A(a.{3,}baz)\Z") def test_ignore_case(self) -> None: """Tests case sensitivity.""" pattern = glob_to_regex("foobaz", ignore_case=False) self.assertEqual(pattern.flags & re.IGNORECASE, 0) pattern = glob_to_regex("foobaz", ignore_case=True) self.assertEqual(pattern.flags & re.IGNORECASE, re.IGNORECASE) class WordPatternTestCase(TestCase): def test_whole_word(self) -> None: """Tests matching on whole words.""" pattern = to_word_pattern("foo bar") self.assertRegex("foo bar", pattern) self.assertRegex(" foo bar ", pattern) self.assertRegex("baz foo bar baz", pattern) self.assertNotRegex("foo baré", pattern, "é should be seen as part of a word") self.assertNotRegex("bar foo", pattern, "Pattern should match words in order") def test_ends_with_non_letter(self) -> None: """Tests matching on whole words when the pattern ends with a space.""" pattern = to_word_pattern("foo ") self.assertRegex( "foo bar", pattern, "Pattern should be able to end its match on a word boundary", ) self.assertRegex( "foo ", pattern, "Pattern should be able to end its match at the end of a string", ) self.assertRegex( "foo ", pattern, "Pattern should be able to end its match anywhere", ) matrix-python-common-1.1.0/tests/test_versionstring.py000066400000000000000000000020611420154770000233100ustar00rootroot00000000000000# Copyright 2022 The Matrix.org Foundation C.I.C. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from unittest import TestCase from matrix_common.versionstring import get_distribution_version_string class TestVersionString(TestCase): def test_our_own_version_string(self) -> None: """Sanity check that we get the version string for our own package. Check that it's a nonempty string. """ version = get_distribution_version_string("matrix-common") self.assertIsInstance(version, str) self.assertTrue(version) matrix-python-common-1.1.0/tox.ini000066400000000000000000000015611420154770000171400ustar00rootroot00000000000000[tox] envlist = py, check_codestyle, check_types # required for PEP 517 (pyproject.toml-style) builds isolated_build = true [testenv:py] # As of twisted 16.4, trial tries to import the tests as a package (previously # it loaded the files explicitly), which means they need to be on the # pythonpath. Our sdist doesn't include the 'tests' package, so normally it # doesn't work within the tox virtualenv. # # As a workaround, we tell tox to do install with 'pip -e', which just # creates a symlink to the project directory instead of unpacking the sdist. usedevelop=true extras = test commands = python -m twisted.trial tests [testenv:check_codestyle] extras = dev commands = flake8 matrix_common tests black --check --diff matrix_common tests isort --check-only --diff matrix_common tests [testenv:check_types] extras = dev commands = mypy matrix_common tests