pax_global_header00006660000000000000000000000064143100442010014477gustar00rootroot0000000000000052 comment=6efb256e6041a2c8fe6f10a4866468b123c4ad1d matrix-python-common-1.3.0/000077500000000000000000000000001431004420100156115ustar00rootroot00000000000000matrix-python-common-1.3.0/.github/000077500000000000000000000000001431004420100171515ustar00rootroot00000000000000matrix-python-common-1.3.0/.github/CODEOWNERS000066400000000000000000000001651431004420100205460ustar00rootroot00000000000000 # Automatically request reviews from the synapse-core team when a pull request comes in. * @matrix-org/synapse-core matrix-python-common-1.3.0/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000002341431004420100227510ustar00rootroot00000000000000### Pull Request Checklist * [ ] Pull request includes a [sign off](https://github.com/matrix-org/matrix-python-common/blob/main/CONTRIBUTING.md#sign-off) matrix-python-common-1.3.0/.github/workflows/000077500000000000000000000000001431004420100212065ustar00rootroot00000000000000matrix-python-common-1.3.0/.github/workflows/ci.yml000066400000000000000000000021071431004420100223240ustar00rootroot00000000000000name: 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.7", "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.3.0/.gitignore000066400000000000000000000001051431004420100175750ustar00rootroot00000000000000/.idea /.venv /*.egg-info /.envrc /.tox _trial_temp __pycache__ /distmatrix-python-common-1.3.0/CONTRIBUTING.md000066400000000000000000000147541431004420100200550ustar00rootroot00000000000000# Contributing code to matrix-python-common Everyone is welcome to contribute code to matrix-python-common, provided you are willing to license your contributions under the same license as the project itself. In this case, the [Apache Software License v2](LICENSE). ## Set up your development environment To contribute to matrix-python-common, ensure you have Python 3.7 and `git` available on your system. You'll need to clone the source code first: ```shell git clone https://github.com/matrix-org/matrix-python-common.git ``` ## Create a virtualenv To contribute to matrix-python-common, ensure you have Python 3.7 or newer and then run: ```bash python3 -m venv .venv .venv/bin/pip install -e .[dev] ``` This creates an isolated virtual Python environment ("virtualenv") just for use with matrix-python-common, then installs matrix-python-common along with its dependencies, and lastly installs a handful of useful tools Finally, activate the virtualenv by running: ```bash source .venv/bin/activate ``` Be sure to do this _every time_ you open a new terminal window for working on matrix-python-common. Activating the venv ensures that any Python commands you run (`pip`, `python`, etc.) use the versions inside your venv, and not your system Python. When you're done, you can close your terminal or run `deactivate` to disable the virtualenv. ## Run the unit tests To make sure everything is working as expected, run the unit tests: ```bash tox -e py ``` If you see a message like: ``` ------------------------------------------------------------------------------- Ran 25 tests in 0.324s PASSED (successes=25) ``` Then all is well and you're ready to work! ## How to contribute The preferred and easiest way to contribute changes is to fork the relevant project on github, and then [create a pull request]( https://help.github.com/articles/using-pull-requests/) to ask us to pull your changes into our repo. Some other points to follow: * Please base your changes on the `main` branch. * Please follow the [code style requirements]( #code-style-and-continuous-integration). * Please [sign off](#sign-off) your contribution. * Please keep an eye on the pull request for feedback from the [continuous integration system](#code-style-and-continuous-integration) and try to fix any errors that come up. * If you need to [update your PR](#updating-your-pull-request), just add new commits to your branch rather than rebasing. ## Code style and continuous integration matrix-python-common uses `black`, `isort` and `flake8` to enforce code style. Use the following script to enforce these style guides: ```shell scripts-dev/lint.sh ``` (This also runs `mypy`, our preferred typechecker.) All of these checks are automatically run against any pull request via GitHub Actions. If your change breaks the build, this will be shown in GitHub, with links to the build results. If your build fails, please try to fix the errors and update your branch. ## Sign off In order to have a concrete record that your contribution is intentional and you agree to license it under the same terms as the project's license, we've adopted the same lightweight approach that the Linux Kernel [submitting patches process]( https://www.kernel.org/doc/html/latest/process/submitting-patches.html#sign-your-work-the-developer-s-certificate-of-origin>), [Docker](https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other projects use: the DCO (Developer Certificate of Origin: https://developercertificate.org/). This is a simple declaration that you wrote the contribution or otherwise have the right to contribute it to Matrix: ``` Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 660 York Street, Suite 102, San Francisco, CA 94110 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` If you agree to this for your contribution, then all that's needed is to include the line in your commit or pull request comment: ``` Signed-off-by: Your Name ``` We accept contributions under a legally identifiable name, such as your name on government documentation or common-law names (names claimed by legitimate usage or repute). Unfortunately, we cannot accept anonymous contributions at this time. Git allows you to add this signoff automatically when using the `-s` flag to `git commit`, which uses the name and email set in your `user.name` and `user.email` git configs. ## Updating your pull request If you decide to make changes to your pull request - perhaps to address issues raised in a review, or to fix problems highlighted by [continuous integration](#continuous-integration-and-testing) - just add new commits to your branch, and push to GitHub. The pull request will automatically be updated. Please **avoid** rebasing your branch, especially once the PR has been reviewed: doing so makes it very difficult for a reviewer to see what has changed since a previous review. ## Conclusion That's it! Matrix is a very open and collaborative project as you might expect given our obsession with open communication. If we're going to successfully matrix together all the fragmented communication technologies out there we are reliant on contributions and collaboration from the community to do so. So please get involved - and we hope you have as much fun hacking on Matrix as we do! matrix-python-common-1.3.0/LICENSE000066400000000000000000000236761431004420100166340ustar00rootroot00000000000000 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.3.0/MANIFEST.in000066400000000000000000000001101431004420100173370ustar00rootroot00000000000000# Include tests in the source distribution recursive-include tests *.py matrix-python-common-1.3.0/README.md000066400000000000000000000026321431004420100170730ustar00rootroot00000000000000# 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.3.0/mypy.ini000066400000000000000000000000601431004420100173040ustar00rootroot00000000000000[mypy] strict = true warn_unused_ignores = falsematrix-python-common-1.3.0/pyproject.toml000066400000000000000000000002631431004420100205260ustar00rootroot00000000000000[build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" [tool.isort] profile = "black" known_first_party = [ "matrix_common", "tests" ] matrix-python-common-1.3.0/scripts-dev/000077500000000000000000000000001431004420100200545ustar00rootroot00000000000000matrix-python-common-1.3.0/scripts-dev/lint.sh000077500000000000000000000005651431004420100213670ustar00rootroot00000000000000#!/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=( "src" "tests" ) # Print out the commands being run set -x isort "${files[@]}" python3 -m black "${files[@]}" flake8 "${files[@]}" mypy "${files[@]}" matrix-python-common-1.3.0/setup.cfg000066400000000000000000000021451431004420100174340ustar00rootroot00000000000000[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.3.0 classifiers = License :: OSI Approved :: Apache Software License [options] python_requires = >= 3.7 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 == 22.3.0 flake8 == 4.0.1 isort == 5.9.3 # release process build == 0.8.0 twine == 4.0.1 [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.3.0/src/000077500000000000000000000000001431004420100164005ustar00rootroot00000000000000matrix-python-common-1.3.0/src/matrix_common/000077500000000000000000000000001431004420100212545ustar00rootroot00000000000000matrix-python-common-1.3.0/src/matrix_common/__init__.py000066400000000000000000000000001431004420100233530ustar00rootroot00000000000000matrix-python-common-1.3.0/src/matrix_common/py.typed000066400000000000000000000000001431004420100227410ustar00rootroot00000000000000matrix-python-common-1.3.0/src/matrix_common/regex.py000066400000000000000000000067111431004420100227450ustar00rootroot00000000000000# 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.3.0/src/matrix_common/types/000077500000000000000000000000001431004420100224205ustar00rootroot00000000000000matrix-python-common-1.3.0/src/matrix_common/types/__init__.py000066400000000000000000000000001431004420100245170ustar00rootroot00000000000000matrix-python-common-1.3.0/src/matrix_common/types/mxc_uri.py000066400000000000000000000057171431004420100244520ustar00rootroot00000000000000# 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 typing import Type, TypeVar from urllib.parse import urlparse import attr MU = TypeVar("MU", bound="MXCUri") @attr.s(frozen=True, slots=True, auto_attribs=True) class MXCUri: """Represents a URI that points to a media resource in matrix. MXC URIs take the form 'mxc://server_name/media_id'. """ server_name: str media_id: str @classmethod def from_str(cls: Type[MU], mxc_uri_str: str) -> MU: """ Given a str in the form "mxc:///", return an equivalent MXCUri. Args: mxc_uri_str: The MXC Uri as a str. Returns: An MXCUri object with matching attributes. Raises: ValueError: If the str was not a valid MXC Uri. """ # Attempt to parse the given URI. This will raise a ValueError if the uri is # particularly malformed. parsed_mxc_uri = urlparse(mxc_uri_str) # MXC Uri's are pretty bare bones. The scheme must be "mxc", and we don't allow # any fragments, query parameters or other features. if ( # The scheme must be "mxc". parsed_mxc_uri.scheme != "mxc" # There must be a host and path provided. or not parsed_mxc_uri.netloc or not parsed_mxc_uri.path or not parsed_mxc_uri.path.startswith("/") or len(parsed_mxc_uri.path) == 1 # if the path is only '/', aka no Media ID # There cannot be any fragments, queries or parameters. or parsed_mxc_uri.fragment or parsed_mxc_uri.query or parsed_mxc_uri.params ): raise ValueError( f"Found invalid structure when parsing MXC Uri: {mxc_uri_str}" ) # We use the parsed 'network location' as the server name server_name = parsed_mxc_uri.netloc # urlparse adds a '/' to the beginning of the path, so let's remove that and use # it as the media_id media_id = parsed_mxc_uri.path[1:] # The media ID should not contain a '/' if "/" in media_id: raise ValueError( f"Found invalid character in media ID portion of MXC Uri: {mxc_uri_str}" ) return cls(server_name, media_id) def __str__(self) -> str: """Convert an MXCUri object to a str.""" return f"mxc://{self.server_name}/{self.media_id}" matrix-python-common-1.3.0/src/matrix_common/versionstring.py000066400000000000000000000100411431004420100245360ustar00rootroot00000000000000# 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 os.path import subprocess from typing import Optional 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, cwd: Optional[str] = None ) -> 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 cwd: if provided, the directory to run all git commands in. `cwd` may also be the path to a file, in which case `os.path.dirname(cwd)` is used instead. If omitted, the function will attempt to locate the distribution's source on disk and use that location instead---but this fallback is not reliable. 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 if cwd is None: # This used to work for Synapse, but seems to have broken between versions 1.56 # and 1.57. I suspect that the cause is a difference in the metadata generated # by `setuptools` and `poetry-core` at package-install time. cwd = dist.locate_file(".").__fspath__() cwd = os.path.dirname(cwd) 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.3.0/tests/000077500000000000000000000000001431004420100167535ustar00rootroot00000000000000matrix-python-common-1.3.0/tests/__init__.py000066400000000000000000000000001431004420100210520ustar00rootroot00000000000000matrix-python-common-1.3.0/tests/test_regex.py000066400000000000000000000101531431004420100214760ustar00rootroot00000000000000# 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.3.0/tests/test_versionstring.py000066400000000000000000000020611431004420100232770ustar00rootroot00000000000000# 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.3.0/tests/types/000077500000000000000000000000001431004420100201175ustar00rootroot00000000000000matrix-python-common-1.3.0/tests/types/__init__.py000066400000000000000000000000001431004420100222160ustar00rootroot00000000000000matrix-python-common-1.3.0/tests/types/test_mxc_uri.py000066400000000000000000000077461431004420100232140ustar00rootroot00000000000000# 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.types.mxc_uri import MXCUri class MXCUriTestCase(TestCase): def test_valid_mxc_uris_to_str(self) -> None: """Tests that a series of valid mxc are converted to a str correctly.""" # Converting an MXCUri to its str representation mxc_0 = MXCUri(server_name="example.com", media_id="84n8493hnfsjkbcu") self.assertEqual(str(mxc_0), "mxc://example.com/84n8493hnfsjkbcu") mxc_1 = MXCUri( server_name="192.168.1.17:8008", media_id="bajkad89h31ausdhoqqasd" ) self.assertEqual(str(mxc_1), "mxc://192.168.1.17:8008/bajkad89h31ausdhoqqasd") mxc_2 = MXCUri(server_name="123.123.123.123", media_id="000000000000") self.assertEqual(str(mxc_2), "mxc://123.123.123.123/000000000000") def test_valid_mxc_uris_from_str(self) -> None: """Tests that a series of valid mxc uris strs are parsed correctly.""" # Converting a str to its MXCUri representation mxcuri_0 = MXCUri.from_str("mxc://example.com/g12789g890ajksjk") self.assertEqual(mxcuri_0.server_name, "example.com") self.assertEqual(mxcuri_0.media_id, "g12789g890ajksjk") mxcuri_1 = MXCUri.from_str("mxc://localhost:8448/abcdefghijklmnopqrstuvwxyz") self.assertEqual(mxcuri_1.server_name, "localhost:8448") self.assertEqual(mxcuri_1.media_id, "abcdefghijklmnopqrstuvwxyz") mxcuri_2 = MXCUri.from_str("mxc://[::1]/abcdefghijklmnopqrstuvwxyz") self.assertEqual(mxcuri_2.server_name, "[::1]") self.assertEqual(mxcuri_2.media_id, "abcdefghijklmnopqrstuvwxyz") mxcuri_3 = MXCUri.from_str("mxc://123.123.123.123:32112/12893y81283781023") self.assertEqual(mxcuri_3.server_name, "123.123.123.123:32112") self.assertEqual(mxcuri_3.media_id, "12893y81283781023") mxcuri_4 = MXCUri.from_str("mxc://domain/abcdefg") self.assertEqual(mxcuri_4.server_name, "domain") self.assertEqual(mxcuri_4.media_id, "abcdefg") def test_invalid_mxc_uris_from_str(self) -> None: """Tests that a series of invalid mxc uris are appropriately rejected.""" # Converting invalid MXC URI strs to MXCUri representations with self.assertRaises(ValueError): MXCUri.from_str("http://example.com/abcdef") with self.assertRaises(ValueError): MXCUri.from_str("mxc:///example.com/abcdef") with self.assertRaises(ValueError): MXCUri.from_str("mxc://example.com//abcdef") with self.assertRaises(ValueError): MXCUri.from_str("mxc://example.com/abcdef/") with self.assertRaises(ValueError): MXCUri.from_str("mxc://example.com/abc/abcdef") with self.assertRaises(ValueError): MXCUri.from_str("mxc://example.com/abc/abcdef") with self.assertRaises(ValueError): MXCUri.from_str("mxc:///abcdef") with self.assertRaises(ValueError): MXCUri.from_str("mxc://example.com") with self.assertRaises(ValueError): MXCUri.from_str("mxc://example.com/") with self.assertRaises(ValueError): MXCUri.from_str("mxc:///") with self.assertRaises(ValueError): MXCUri.from_str("example.com/abc") with self.assertRaises(ValueError): MXCUri.from_str("") with self.assertRaises(ValueError): MXCUri.from_str(None) # type: ignore matrix-python-common-1.3.0/tox.ini000066400000000000000000000006271431004420100171310ustar00rootroot00000000000000[tox] envlist = py, check_codestyle, check_types # required for PEP 517 (pyproject.toml-style) builds isolated_build = true [testenv:py] extras = test commands = python -m twisted.trial tests [testenv:check_codestyle] extras = dev commands = flake8 src tests black --check --diff src tests isort --check-only --diff src tests [testenv:check_types] extras = dev commands = mypy src tests