├── .codecov.yml ├── .github └── workflows │ ├── build.yml │ └── deploy.yml ├── .gitignore ├── .pyspelling.yml ├── CHANGES.md ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── docs └── src │ └── dictionary │ └── en-custom.txt ├── pep562 └── __init__.py ├── project.toml ├── requirements ├── docs.txt ├── flake8.txt ├── tests.txt └── tools.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── not_overridden.py ├── overridden.py ├── overridden_helper.py ├── test_pep562.py └── test_version.py ├── tools ├── __init__.py └── gh_labels.py └── tox.ini /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | patch: false 6 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | tags: 8 | - '**' 9 | pull_request: 10 | branches: 11 | - '**' 12 | 13 | jobs: 14 | tests: 15 | strategy: 16 | fail-fast: false 17 | max-parallel: 4 18 | matrix: 19 | platform: [ubuntu-latest, windows-latest] 20 | python-version: [2.7, 3.6, 3.7, 3.8, 3.9] 21 | include: 22 | - python-version: 2.7 23 | tox-env: py27 24 | - python-version: 3.6 25 | tox-env: py36 26 | - python-version: 3.7 27 | tox-env: py37 28 | - python-version: 3.8 29 | tox-env: py38 30 | - python-version: 3.9 31 | tox-env: py39 32 | 33 | env: 34 | TOXENV: ${{ matrix.tox-env }} 35 | 36 | runs-on: ${{ matrix.platform }} 37 | 38 | steps: 39 | - uses: actions/checkout@v2 40 | - name: Set up Python ${{ matrix.python-version }} 41 | uses: actions/setup-python@v2 42 | with: 43 | python-version: ${{ matrix.python-version }} 44 | - name: Install dependencies 45 | run: | 46 | python -m pip install --upgrade pip setuptools tox coverage codecov 47 | - name: Test 48 | run: | 49 | python -m tox 50 | - name: Upload Results 51 | if: success() 52 | uses: codecov/codecov-action@v1 53 | with: 54 | file: ./coverage.xml 55 | flags: unittests 56 | name: ${{ matrix.platform }}-${{ matrix.tox-env }} 57 | fail_ci_if_error: false 58 | 59 | lint: 60 | strategy: 61 | max-parallel: 4 62 | matrix: 63 | python-version: [3.9] 64 | 65 | env: 66 | TOXENV: lint 67 | 68 | runs-on: ubuntu-latest 69 | 70 | steps: 71 | - uses: actions/checkout@v2 72 | - name: Set up Python ${{ matrix.python-version }} 73 | uses: actions/setup-python@v2 74 | with: 75 | python-version: ${{ matrix.python-version }} 76 | - name: Install dependencies 77 | run: | 78 | python -m pip install --upgrade pip setuptools tox 79 | - name: Lint 80 | run: | 81 | python -m tox 82 | 83 | documents: 84 | strategy: 85 | max-parallel: 4 86 | matrix: 87 | python-version: [3.9] 88 | 89 | env: 90 | TOXENV: documents 91 | 92 | runs-on: ubuntu-latest 93 | 94 | steps: 95 | - uses: actions/checkout@v2 96 | - name: Set up Python ${{ matrix.python-version }} 97 | uses: actions/setup-python@v2 98 | with: 99 | python-version: ${{ matrix.python-version }} 100 | - name: Install dependencies 101 | run: | 102 | python -m pip install --upgrade pip setuptools tox 103 | - name: Install Aspell 104 | run: | 105 | sudo apt-get install aspell aspell-en 106 | - name: Build documents 107 | run: | 108 | python -m tox 109 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | 10 | pypi: 11 | strategy: 12 | matrix: 13 | distribution: [bdist_wheel, sdist] 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-python@v2 20 | with: 21 | python-version: 3.9 22 | - name: Package 23 | run: | 24 | pip install --upgrade setuptools wheel 25 | python setup.py ${{ matrix.distribution }} 26 | - name: Publish 27 | uses: pypa/gh-action-pypi-publish@v1.4.1 28 | with: 29 | user: __token__ 30 | password: ${{ secrets.PYPI_PWD }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /.pyspelling.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | - name: markdown 3 | sources: 4 | - '{README,CHANGES}.md' 5 | aspell: 6 | lang: en 7 | dictionary: 8 | lang: en 9 | wordlists: 10 | - docs/src/dictionary/en-custom.txt 11 | output: build/dictionary/mkdocs.dic 12 | pipeline: 13 | - pyspelling.filters.markdown: 14 | markdown_extensions: 15 | - pymdownx.superfences: 16 | - pymdownx.highlight: 17 | - pyspelling.filters.html: 18 | comments: false 19 | attributes: 20 | - title 21 | - alt 22 | ignores: 23 | - ':matches(code, pre)' 24 | - pyspelling.filters.url: 25 | 26 | - name: python 27 | sources: 28 | - '{pep562,tests}/**/*.py' 29 | aspell: 30 | lang: en 31 | dictionary: 32 | wordlists: 33 | - docs/src/dictionary/en-custom.txt 34 | output: build/dictionary/python.dic 35 | pipeline: 36 | - pyspelling.filters.python: 37 | group_comments: true 38 | - pyspelling.flow_control.wildcard: 39 | allow: 40 | - py-comment 41 | - pyspelling.filters.context: 42 | context_visible_first: true 43 | delimiters: 44 | # Ignore lint (noqa) and coverage (pragma) as well as shebang (#!) 45 | - open: '^(?: *(?:noqa\b|pragma: no cover)|!)' 46 | close: '$' 47 | # Ignore Python encoding string -*- encoding stuff -*- 48 | - open: '^ *-\*-' 49 | close: '-\*-$' 50 | - pyspelling.filters.context: 51 | context_visible_first: true 52 | escapes: '\\[\\`]' 53 | delimiters: 54 | # Ignore multiline content between fences (fences can have 3 or more back ticks) 55 | # ``` 56 | # content 57 | # ``` 58 | - open: '(?s)^(?P *`{3,})$' 59 | close: '^(?P=open)$' 60 | # Ignore text between inline back ticks 61 | - open: '(?P`+)' 62 | close: '(?P=open)' 63 | - pyspelling.filters.url: 64 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.1 4 | 5 | - **NEW**: Add helper function that auto checks if Pep562 needs to be applied. 6 | 7 | ## 1.0 8 | 9 | - **NEW**: Works in Python 3.7 as well and is the user's responsibility to provide conditionals to exclude it on Python 10 | 3.7 if they want to use the default implementation. 11 | - **NEW**: Rework the version info object. 12 | - **NEW**: Now one file and easy to vendor. 13 | 14 | ## 1.0b1 15 | 16 | - **NEW**: Prerelease of PEP 562 backport. 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 - 2021 Isaac Muse 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include pep562 *.py 2 | recursive-include tests *.py 3 | recursive-include requirements *.txt 4 | recursive-include docs *.txt 5 | include setup.py 6 | include setup.cfg 7 | include tox.ini 8 | include LICENSE.md 9 | include README.md 10 | include CHANGES.md 11 | include MANIFEST.in 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Donate via PayPal][donate-image]][donate-link] 2 | [![Discord][discord-image]][discord-link] 3 | [![Build][github-ci-image]][github-ci-link] 4 | [![Coverage Status][codecov-image]][codecov-link] 5 | [![PyPI Version][pypi-image]][pypi-link] 6 | [![PyPI - Python Version][python-image]][pypi-link] 7 | ![License][license-image-mit] 8 | 9 | # PEP 562 10 | 11 | ## Overview 12 | 13 | A backport of PEP 562. Allows controlling a module's `__dir__` and `__getattr__`. Useful for deprecating attributes. 14 | Works for Python 2.7+. And while it works on Python 3.7, it is recommended to use the official Python 3.7 implementation 15 | where applicable. 16 | 17 | This module can be installed and used as a dependency, or if desired, it is easy to vendor as the license is quite 18 | permissible, and the code is contained in a single file. 19 | 20 | Once Python 3.6 is end of life, this module will be irrelevant and will no longer receive active support. 21 | 22 | ## Install 23 | 24 | Installation is done with `pip`: 25 | 26 | ``` 27 | pip install pep562 28 | ``` 29 | 30 | ## Vendoring 31 | 32 | Simply copy the file `pep562/__init__.py` to your project and rename to `pep562`. Import and use as needed. 33 | 34 | ## Usage 35 | 36 | Here is a simple example where we deprecate the attribute `version` for the more standardized `__version__`. 37 | 38 | ```py 39 | from pep562 import pep562 40 | import warnings 41 | 42 | __version__ = (1, 0, 0) 43 | __all__ = ("__version__",) 44 | __deprecated__ = { 45 | "version": ("__version__", __version__) 46 | } 47 | 48 | PY37 = sys.version_info >= (3, 7) 49 | 50 | 51 | def __getattr__(name): 52 | """Get attribute.""" 53 | 54 | deprecated = __deprecated__.get(name) 55 | if deprecated: 56 | stacklevel = 3 if PY37 else 4 57 | warnings.warn( 58 | "'{}' is deprecated. Use '{}' instead.".format(name, deprecated[0]), 59 | category=DeprecationWarning, 60 | stacklevel=stacklevel 61 | ) 62 | return deprecated[1] 63 | raise AttributeError("module '{}' has no attribute '{}'".format(__name__, name)) 64 | 65 | 66 | def __dir__(): 67 | """Module directory.""" 68 | 69 | return sorted(list(__all__) + list(__deprecated__.keys())) 70 | 71 | 72 | pep562(__name__) 73 | ``` 74 | 75 | ## License 76 | 77 | MIT License 78 | 79 | Copyright (c) 2018 - 2021 Isaac Muse 80 | 81 | Permission is hereby granted, free of charge, to any person obtaining a copy 82 | of this software and associated documentation files (the "Software"), to deal 83 | in the Software without restriction, including without limitation the rights 84 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 85 | copies of the Software, and to permit persons to whom the Software is 86 | furnished to do so, subject to the following conditions: 87 | 88 | The above copyright notice and this permission notice shall be included in all 89 | copies or substantial portions of the Software. 90 | 91 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 92 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 93 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 94 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 95 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 96 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 97 | SOFTWARE. 98 | 99 | [github-ci-image]: https://github.com/facelessuser/pep562/workflows/build/badge.svg?branch=master&event=push 100 | [github-ci-link]: https://github.com/facelessuser/pep562/actions?query=workflow%3Abuild+branch%3Amaster 101 | [discord-image]: https://img.shields.io/discord/678289859768745989?logo=discord&logoColor=aaaaaa&color=mediumpurple&labelColor=333333 102 | [discord-link]:https://discord.gg/TWs8Tgr 103 | [codecov-image]: https://img.shields.io/codecov/c/github/facelessuser/pep562/master.svg?logo=codecov&logoColor=aaaaaa&labelColor=333333 104 | [codecov-link]: https://codecov.io/github/facelessuser/pep562 105 | [pypi-image]: https://img.shields.io/pypi/v/pep562.svg?logo=pypi&logoColor=aaaaaa&labelColor=333333 106 | [pypi-link]: https://pypi.python.org/pypi/pep562 107 | [python-image]: https://img.shields.io/pypi/pyversions/pep562?logo=python&logoColor=aaaaaa&labelColor=333333 108 | [license-image-mit]: https://img.shields.io/badge/license-MIT-blue.svg?labelColor=333333 109 | [donate-image]: https://img.shields.io/badge/Donate-PayPal-3fabd1?logo=paypal 110 | [donate-link]: https://www.paypal.me/facelessuser 111 | -------------------------------------------------------------------------------- /docs/src/dictionary/en-custom.txt: -------------------------------------------------------------------------------- 1 | Accessors 2 | Changelog 3 | MERCHANTABILITY 4 | NONINFRINGEMENT 5 | Pre 6 | PyPI 7 | Vendoring 8 | accessor 9 | backport 10 | pre 11 | prerelease 12 | prereleases 13 | sortable 14 | sublicense 15 | tuple 16 | -------------------------------------------------------------------------------- /pep562/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Backport of PEP 562. 3 | 4 | https://pypi.org/search/?q=pep562 5 | 6 | Licensed under MIT 7 | Copyright (c) 2018 Isaac Muse 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 10 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 12 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 18 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 20 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | IN THE SOFTWARE. 22 | """ 23 | from __future__ import unicode_literals 24 | import sys 25 | from collections import namedtuple 26 | import re 27 | 28 | __all__ = ('Pep562',) 29 | 30 | RE_VER = re.compile( 31 | r'''(?x) 32 | (?P\d+)(?:\.(?P\d+))?(?:\.(?P\d+))? 33 | (?:(?Pa|b|rc)(?P
\d+))?
 34 |     (?:\.post(?P\d+))?
 35 |     (?:\.dev(?P\d+))?
 36 |     '''
 37 | )
 38 | 
 39 | REL_MAP = {
 40 |     ".dev": "",
 41 |     ".dev-alpha": "a",
 42 |     ".dev-beta": "b",
 43 |     ".dev-candidate": "rc",
 44 |     "alpha": "a",
 45 |     "beta": "b",
 46 |     "candidate": "rc",
 47 |     "final": ""
 48 | }
 49 | 
 50 | DEV_STATUS = {
 51 |     ".dev": "2 - Pre-Alpha",
 52 |     ".dev-alpha": "2 - Pre-Alpha",
 53 |     ".dev-beta": "2 - Pre-Alpha",
 54 |     ".dev-candidate": "2 - Pre-Alpha",
 55 |     "alpha": "3 - Alpha",
 56 |     "beta": "4 - Beta",
 57 |     "candidate": "4 - Beta",
 58 |     "final": "5 - Production/Stable"
 59 | }
 60 | 
 61 | PRE_REL_MAP = {"a": 'alpha', "b": 'beta', "rc": 'candidate'}
 62 | 
 63 | 
 64 | class Version(namedtuple("Version", ["major", "minor", "micro", "release", "pre", "post", "dev"])):
 65 |     """
 66 |     Get the version (PEP 440).
 67 | 
 68 |     A biased approach to the PEP 440 semantic version.
 69 | 
 70 |     Provides a tuple structure which is sorted for comparisons `v1 > v2` etc.
 71 |       (major, minor, micro, release type, pre-release build, post-release build, development release build)
 72 |     Release types are named in is such a way they are comparable with ease.
 73 |     Accessors to check if a development, pre-release, or post-release build. Also provides accessor to get
 74 |     development status for setup files.
 75 | 
 76 |     How it works (currently):
 77 | 
 78 |     - You must specify a release type as either `final`, `alpha`, `beta`, or `candidate`.
 79 |     - To define a development release, you can use either `.dev`, `.dev-alpha`, `.dev-beta`, or `.dev-candidate`.
 80 |       The dot is used to ensure all development specifiers are sorted before `alpha`.
 81 |       You can specify a `dev` number for development builds, but do not have to as implicit development releases
 82 |       are allowed.
 83 |     - You must specify a `pre` value greater than zero if using a prerelease as this project (not PEP 440) does not
 84 |       allow implicit prereleases.
 85 |     - You can optionally set `post` to a value greater than zero to make the build a post release. While post releases
 86 |       are technically allowed in prereleases, it is strongly discouraged, so we are rejecting them. It should be
 87 |       noted that we do not allow `post0` even though PEP 440 does not restrict this. This project specifically
 88 |       does not allow implicit post releases.
 89 |     - It should be noted that we do not support epochs `1!` or local versions `+some-custom.version-1`.
 90 | 
 91 |     Acceptable version releases:
 92 | 
 93 |     ```
 94 |     Version(1, 0, 0, "final")                    1.0
 95 |     Version(1, 2, 0, "final")                    1.2
 96 |     Version(1, 2, 3, "final")                    1.2.3
 97 |     Version(1, 2, 0, ".dev-alpha", pre=4)        1.2a4
 98 |     Version(1, 2, 0, ".dev-beta", pre=4)         1.2b4
 99 |     Version(1, 2, 0, ".dev-candidate", pre=4)    1.2rc4
100 |     Version(1, 2, 0, "final", post=1)            1.2.post1
101 |     Version(1, 2, 3, ".dev")                     1.2.3.dev0
102 |     Version(1, 2, 3, ".dev", dev=1)              1.2.3.dev1
103 |     ```
104 | 
105 |     """
106 | 
107 |     def __new__(cls, major, minor, micro, release="final", pre=0, post=0, dev=0):
108 |         """Validate version info."""
109 | 
110 |         # Ensure all parts are positive integers.
111 |         for value in (major, minor, micro, pre, post):
112 |             if not (isinstance(value, int) and value >= 0):
113 |                 raise ValueError("All version parts except 'release' should be integers.")
114 | 
115 |         if release not in REL_MAP:
116 |             raise ValueError("'{}' is not a valid release type.".format(release))
117 | 
118 |         # Ensure valid pre-release (we do not allow implicit pre-releases).
119 |         if ".dev-candidate" < release < "final":
120 |             if pre == 0:
121 |                 raise ValueError("Implicit pre-releases not allowed.")
122 |             elif dev:
123 |                 raise ValueError("Version is not a development release.")
124 |             elif post:
125 |                 raise ValueError("Post-releases are not allowed with pre-releases.")
126 | 
127 |         # Ensure valid development or development/pre release
128 |         elif release < "alpha":
129 |             if release > ".dev" and pre == 0:
130 |                 raise ValueError("Implicit pre-release not allowed.")
131 |             elif post:
132 |                 raise ValueError("Post-releases are not allowed with pre-releases.")
133 | 
134 |         # Ensure a valid normal release
135 |         else:
136 |             if pre:
137 |                 raise ValueError("Version is not a pre-release.")
138 |             elif dev:
139 |                 raise ValueError("Version is not a development release.")
140 | 
141 |         return super(Version, cls).__new__(cls, major, minor, micro, release, pre, post, dev)
142 | 
143 |     def _is_pre(self):
144 |         """Is prerelease."""
145 | 
146 |         return self.pre > 0
147 | 
148 |     def _is_dev(self):
149 |         """Is development."""
150 | 
151 |         return bool(self.release < "alpha")
152 | 
153 |     def _is_post(self):
154 |         """Is post."""
155 | 
156 |         return self.post > 0
157 | 
158 |     def _get_dev_status(self):  # pragma: no cover
159 |         """Get development status string."""
160 | 
161 |         return DEV_STATUS[self.release]
162 | 
163 |     def _get_canonical(self):
164 |         """Get the canonical output string."""
165 | 
166 |         # Assemble major, minor, micro version and append `pre`, `post`, or `dev` if needed..
167 |         if self.micro == 0 and self.major != 0:
168 |             ver = "{}.{}".format(self.major, self.minor)
169 |         else:
170 |             ver = "{}.{}.{}".format(self.major, self.minor, self.micro)
171 |         if self._is_pre():
172 |             ver += '{}{}'.format(REL_MAP[self.release], self.pre)
173 |         if self._is_post():
174 |             ver += ".post{}".format(self.post)
175 |         if self._is_dev():
176 |             ver += ".dev{}".format(self.dev)
177 | 
178 |         return ver
179 | 
180 | 
181 | def parse_version(ver):
182 |     """Parse version into a comparable Version tuple."""
183 | 
184 |     m = RE_VER.match(ver)
185 | 
186 |     if m is None:
187 |         raise ValueError("'{}' is not a valid version".format(ver))
188 | 
189 |     # Handle major, minor, micro
190 |     major = int(m.group('major'))
191 |     minor = int(m.group('minor')) if m.group('minor') else 0
192 |     micro = int(m.group('micro')) if m.group('micro') else 0
193 | 
194 |     # Handle pre releases
195 |     if m.group('type'):
196 |         release = PRE_REL_MAP[m.group('type')]
197 |         pre = int(m.group('pre'))
198 |     else:
199 |         release = "final"
200 |         pre = 0
201 | 
202 |     # Handle development releases
203 |     dev = m.group('dev') if m.group('dev') else 0
204 |     if m.group('dev'):
205 |         dev = int(m.group('dev'))
206 |         release = '.dev-' + release if pre else '.dev'
207 |     else:
208 |         dev = 0
209 | 
210 |     # Handle post
211 |     post = int(m.group('post')) if m.group('post') else 0
212 | 
213 |     return Version(major, minor, micro, release, pre, post, dev)
214 | 
215 | 
216 | class Pep562(object):
217 |     """
218 |     Backport of PEP 562 .
219 | 
220 |     Wraps the module in a class that exposes the mechanics to override `__dir__` and `__getattr__`.
221 |     The given module will be searched for overrides of `__dir__` and `__getattr__` and use them when needed.
222 |     """
223 | 
224 |     def __init__(self, name):
225 |         """Acquire `__getattr__` and `__dir__`, but only replace module for versions less than Python 3.7."""
226 | 
227 |         self._module = sys.modules[name]
228 |         self._get_attr = getattr(self._module, '__getattr__', None)
229 |         self._get_dir = getattr(self._module, '__dir__', None)
230 |         sys.modules[name] = self
231 | 
232 |     def __dir__(self):
233 |         """Return the overridden `dir` if one was provided, else apply `dir` to the module."""
234 | 
235 |         return self._get_dir() if self._get_dir else dir(self._module)
236 | 
237 |     def __getattr__(self, name):
238 |         """Attempt to retrieve the attribute from the module, and if missing, use the overridden function if present."""
239 | 
240 |         try:
241 |             return getattr(self._module, name)
242 |         except AttributeError:
243 |             if self._get_attr:
244 |                 return self._get_attr(name)
245 |             raise
246 | 
247 | 
248 | def pep562(module_name):
249 |     """Helper function to apply PEP 562."""
250 | 
251 |     if sys.version_info < (3, 7):
252 |         Pep562(module_name)
253 | 
254 | 
255 | __version_info__ = Version(1, 1, 0, "final")
256 | __version__ = __version_info__._get_canonical()
257 | 


--------------------------------------------------------------------------------
/project.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = [
3 |     "setuptools>=42",
4 |     "wheel"
5 | ]
6 | 
7 | build-backend = "setuptools.build_meta"
8 | 


--------------------------------------------------------------------------------
/requirements/docs.txt:
--------------------------------------------------------------------------------
1 | pyspelling
2 | pymdown-extensions
3 | 


--------------------------------------------------------------------------------
/requirements/flake8.txt:
--------------------------------------------------------------------------------
1 | flake8
2 | flake8_docstrings
3 | pep8-naming
4 | flake8-mutable
5 | flake8-builtins
6 | 


--------------------------------------------------------------------------------
/requirements/tests.txt:
--------------------------------------------------------------------------------
1 | pytest
2 | pytest-cov
3 | coverage
4 | 


--------------------------------------------------------------------------------
/requirements/tools.txt:
--------------------------------------------------------------------------------
1 | pygithub
2 | 


--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [wheel]
2 | universal = 1
3 | 
4 | [metadata]
5 | license_file = LICENSE.md
6 | 


--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env python
 2 | """Setup Pep562."""
 3 | 
 4 | from setuptools import setup, find_packages
 5 | import os
 6 | import imp
 7 | import traceback
 8 | 
 9 | 
10 | def get_version():
11 |     """Get version and version_info without importing the entire module."""
12 | 
13 |     path = os.path.join(os.path.dirname(__file__), 'pep562')
14 |     fp, pathname, desc = imp.find_module('__init__', [path])
15 |     try:
16 |         module = imp.load_module('__init__', fp, pathname, desc)
17 |         return module.__version__, module.__version_info__._get_dev_status()
18 |     except Exception:
19 |         print(traceback.format_exc())
20 |     finally:
21 |         fp.close()
22 | 
23 | 
24 | def get_requirements(req):
25 |     """Load list of dependencies."""
26 | 
27 |     install_requires = []
28 |     with open(req) as f:
29 |         for line in f:
30 |             if not line.startswith("#"):
31 |                 install_requires.append(line.strip())
32 |     return install_requires
33 | 
34 | 
35 | def get_description():
36 |     """Get long description."""
37 | 
38 |     with open("README.md", 'r') as f:
39 |         desc = f.read()
40 |     return desc
41 | 
42 | 
43 | VER, DEVSTATUS = get_version()
44 | 
45 | 
46 | setup(
47 |     name='pep562',
48 |     version=VER,
49 |     keywords='pep 562 backport',
50 |     description='Backport of PEP 562.',
51 |     long_description=get_description(),
52 |     long_description_content_type='text/markdown',
53 |     author='Isaac Muse',
54 |     author_email='Isaac.Muse@gmail.com',
55 |     url='https://github.com/facelessuser/pep562',
56 |     packages=find_packages(exclude=['tests', 'tools']),
57 |     license='MIT License',
58 |     classifiers=[
59 |         'Development Status :: %s' % DEVSTATUS,
60 |         'Intended Audience :: Developers',
61 |         'License :: OSI Approved :: MIT License',
62 |         'Operating System :: OS Independent',
63 |         'Programming Language :: Python :: 2',
64 |         'Programming Language :: Python :: 2.7',
65 |         'Programming Language :: Python :: 3',
66 |         'Programming Language :: Python :: 3.3',
67 |         'Programming Language :: Python :: 3.4',
68 |         'Programming Language :: Python :: 3.5',
69 |         'Programming Language :: Python :: 3.6',
70 |         'Programming Language :: Python :: 3.7',
71 |         'Programming Language :: Python :: 3.8',
72 |         'Programming Language :: Python :: 3.9'
73 |     ]
74 | )
75 | 


--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | """Tests."""
2 | 


--------------------------------------------------------------------------------
/tests/not_overridden.py:
--------------------------------------------------------------------------------
1 | """No overrides."""
2 | from pep562 import Pep562
3 | 
4 | __version__ = (1, 0, 0)
5 | __all__ = ("__version__")
6 | 
7 | Pep562(__name__)
8 | 


--------------------------------------------------------------------------------
/tests/overridden.py:
--------------------------------------------------------------------------------
 1 | """Overrides."""
 2 | from pep562 import Pep562
 3 | import sys
 4 | import warnings
 5 | 
 6 | __version__ = (1, 0, 0)
 7 | __all__ = ("__version__",)
 8 | __deprecated__ = {
 9 |     "version": ("__version__", __version__)
10 | }
11 | 
12 | PY37 = sys.version_info >= (3, 7)
13 | 
14 | 
15 | def __getattr__(name):  # noqa: N807
16 |     """Get attribute."""
17 | 
18 |     deprecated = __deprecated__.get(name)
19 |     if deprecated:
20 |         stacklevel = 3 if PY37 else 4
21 |         warnings.warn(
22 |             "'{}' is deprecated. Use '{}' instead.".format(name, deprecated[0]),
23 |             category=DeprecationWarning,
24 |             stacklevel=stacklevel
25 |         )
26 |         return deprecated[1]
27 |     raise AttributeError("module '{}' has no attribute '{}'".format(__name__, name))
28 | 
29 | 
30 | def __dir__():  # noqa: N807
31 |     """Module directory."""
32 | 
33 |     return sorted(list(__all__) + list(__deprecated__.keys()))
34 | 
35 | 
36 | Pep562(__name__)
37 | 


--------------------------------------------------------------------------------
/tests/overridden_helper.py:
--------------------------------------------------------------------------------
 1 | """Overrides."""
 2 | from pep562 import pep562
 3 | import sys
 4 | import warnings
 5 | 
 6 | __version__ = (1, 0, 0)
 7 | __all__ = ("__version__",)
 8 | __deprecated__ = {
 9 |     "version": ("__version__", __version__)
10 | }
11 | 
12 | PY37 = sys.version_info >= (3, 7)
13 | 
14 | 
15 | def __getattr__(name):  # noqa: N807
16 |     """Get attribute."""
17 | 
18 |     deprecated = __deprecated__.get(name)
19 |     if deprecated:
20 |         stacklevel = 3 if PY37 else 4
21 |         warnings.warn(
22 |             "'{}' is deprecated. Use '{}' instead.".format(name, deprecated[0]),
23 |             category=DeprecationWarning,
24 |             stacklevel=stacklevel
25 |         )
26 |         return deprecated[1]
27 |     raise AttributeError("module '{}' has no attribute '{}'".format(__name__, name))
28 | 
29 | 
30 | def __dir__():  # noqa: N807
31 |     """Module directory."""
32 | 
33 |     return sorted(list(__all__) + list(__deprecated__.keys()))
34 | 
35 | 
36 | pep562(__name__)
37 | 


--------------------------------------------------------------------------------
/tests/test_pep562.py:
--------------------------------------------------------------------------------
 1 | """Test for PEP 562 backport."""
 2 | from __future__ import unicode_literals
 3 | import unittest
 4 | import warnings
 5 | 
 6 | 
 7 | class TestPep562(unittest.TestCase):
 8 |     """Tests for the PEP 562 package."""
 9 | 
10 |     def test_overrides(self):
11 |         """Test overrides."""
12 | 
13 |         from . import overridden
14 | 
15 |         # Capture overridden `__dir__` results
16 |         self.assertEqual(['__version__', 'version'], dir(overridden))
17 | 
18 |         # Capture a successful attribute access
19 |         self.assertEqual((1, 0, 0), overridden.__version__)
20 | 
21 |         # Capture the overridden deprecation
22 |         with warnings.catch_warnings(record=True) as w:
23 |             # Cause all warnings to always be triggered.
24 |             warnings.simplefilter("always")
25 |             # Trigger a warning.
26 |             version = overridden.version
27 |             # Verify some things
28 |             self.assertTrue(len(w) == 1)
29 |             self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
30 |             self.assertEqual(overridden.__version__, version)
31 | 
32 |         # Capture a failed attribute access
33 |         with self.assertRaises(AttributeError):
34 |             overridden.not_here
35 | 
36 |     def test_overrides_helper(self):
37 |         """Test overrides with helper."""
38 | 
39 |         from . import overridden_helper
40 | 
41 |         # Capture overridden `__dir__` results
42 |         self.assertEqual(['__version__', 'version'], dir(overridden_helper))
43 | 
44 |         # Capture a successful attribute access
45 |         self.assertEqual((1, 0, 0), overridden_helper.__version__)
46 | 
47 |         # Capture the overridden deprecation
48 |         with warnings.catch_warnings(record=True) as w:
49 |             # Cause all warnings to always be triggered.
50 |             warnings.simplefilter("always")
51 |             # Trigger a warning.
52 |             version = overridden_helper.version
53 |             # Verify some things
54 |             self.assertTrue(len(w) == 1)
55 |             self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
56 |             self.assertEqual(overridden_helper.__version__, version)
57 | 
58 |         # Capture a failed attribute access
59 |         with self.assertRaises(AttributeError):
60 |             overridden_helper.not_here
61 | 
62 |     def test_no_overrides(self):
63 |         """Test no overrides."""
64 | 
65 |         from . import not_overridden
66 | 
67 |         # Capture `__dir__` results
68 |         self.assertNotEqual(['__version__'], dir(not_overridden))
69 |         self.assertTrue('__version__' in dir(not_overridden))
70 | 
71 |         # Capture a successful attribute access
72 |         self.assertEqual((1, 0, 0), not_overridden.__version__)
73 | 
74 |         # Capture a failed attribute access
75 |         with self.assertRaises(AttributeError):
76 |             not_overridden.not_here
77 | 


--------------------------------------------------------------------------------
/tests/test_version.py:
--------------------------------------------------------------------------------
 1 | """Version tests."""
 2 | from __future__ import unicode_literals
 3 | import unittest
 4 | from pep562 import Version, parse_version
 5 | 
 6 | 
 7 | class TestVersion(unittest.TestCase):
 8 |     """Test versions."""
 9 | 
10 |     def test_version_output(self):
11 |         """Test that versions generate proper strings."""
12 | 
13 |         assert Version(1, 0, 0, "final")._get_canonical() == "1.0"
14 |         assert Version(1, 2, 0, "final")._get_canonical() == "1.2"
15 |         assert Version(1, 2, 3, "final")._get_canonical() == "1.2.3"
16 |         assert Version(1, 2, 0, "alpha", pre=4)._get_canonical() == "1.2a4"
17 |         assert Version(1, 2, 0, "beta", pre=4)._get_canonical() == "1.2b4"
18 |         assert Version(1, 2, 0, "candidate", pre=4)._get_canonical() == "1.2rc4"
19 |         assert Version(1, 2, 0, "final", post=1)._get_canonical() == "1.2.post1"
20 |         assert Version(1, 2, 3, ".dev-alpha", pre=1)._get_canonical() == "1.2.3a1.dev0"
21 |         assert Version(1, 2, 3, ".dev")._get_canonical() == "1.2.3.dev0"
22 |         assert Version(1, 2, 3, ".dev", dev=1)._get_canonical() == "1.2.3.dev1"
23 | 
24 |     def test_version_comparison(self):
25 |         """Test that versions compare proper."""
26 | 
27 |         assert Version(1, 0, 0, "final") < Version(1, 2, 0, "final")
28 |         assert Version(1, 2, 0, "alpha", pre=4) < Version(1, 2, 0, "final")
29 |         assert Version(1, 2, 0, "final") < Version(1, 2, 0, "final", post=1)
30 |         assert Version(1, 2, 3, ".dev-beta", pre=2) < Version(1, 2, 3, "beta", pre=2)
31 |         assert Version(1, 2, 3, ".dev") < Version(1, 2, 3, ".dev-beta", pre=2)
32 |         assert Version(1, 2, 3, ".dev") < Version(1, 2, 3, ".dev", dev=1)
33 | 
34 |     def test_version_parsing(self):
35 |         """Test version parsing."""
36 | 
37 |         assert parse_version(
38 |             Version(1, 0, 0, "final")._get_canonical()
39 |         ) == Version(1, 0, 0, "final")
40 |         assert parse_version(
41 |             Version(1, 2, 0, "final")._get_canonical()
42 |         ) == Version(1, 2, 0, "final")
43 |         assert parse_version(
44 |             Version(1, 2, 3, "final")._get_canonical()
45 |         ) == Version(1, 2, 3, "final")
46 |         assert parse_version(
47 |             Version(1, 2, 0, "alpha", pre=4)._get_canonical()
48 |         ) == Version(1, 2, 0, "alpha", pre=4)
49 |         assert parse_version(
50 |             Version(1, 2, 0, "beta", pre=4)._get_canonical()
51 |         ) == Version(1, 2, 0, "beta", pre=4)
52 |         assert parse_version(
53 |             Version(1, 2, 0, "candidate", pre=4)._get_canonical()
54 |         ) == Version(1, 2, 0, "candidate", pre=4)
55 |         assert parse_version(
56 |             Version(1, 2, 0, "final", post=1)._get_canonical()
57 |         ) == Version(1, 2, 0, "final", post=1)
58 |         assert parse_version(
59 |             Version(1, 2, 3, ".dev-alpha", pre=1)._get_canonical()
60 |         ) == Version(1, 2, 3, ".dev-alpha", pre=1)
61 |         assert parse_version(
62 |             Version(1, 2, 3, ".dev")._get_canonical()
63 |         ) == Version(1, 2, 3, ".dev")
64 |         assert parse_version(
65 |             Version(1, 2, 3, ".dev", dev=1)._get_canonical()
66 |         ) == Version(1, 2, 3, ".dev", dev=1)
67 | 
68 |     def test_asserts(self):
69 |         """Test asserts."""
70 | 
71 |         with self.assertRaises(ValueError):
72 |             Version("1", "2", "3")
73 |         with self.assertRaises(ValueError):
74 |             Version(1, 2, 3, 1)
75 |         with self.assertRaises(ValueError):
76 |             Version("1", "2", "3")
77 |         with self.assertRaises(ValueError):
78 |             Version(1, 2, 3, "bad")
79 |         with self.assertRaises(ValueError):
80 |             Version(1, 2, 3, "alpha")
81 |         with self.assertRaises(ValueError):
82 |             Version(1, 2, 3, "alpha", pre=1, dev=1)
83 |         with self.assertRaises(ValueError):
84 |             Version(1, 2, 3, "alpha", pre=1, post=1)
85 |         with self.assertRaises(ValueError):
86 |             Version(1, 2, 3, ".dev-alpha")
87 |         with self.assertRaises(ValueError):
88 |             Version(1, 2, 3, ".dev-alpha", pre=1, post=1)
89 |         with self.assertRaises(ValueError):
90 |             Version(1, 2, 3, pre=1)
91 |         with self.assertRaises(ValueError):
92 |             Version(1, 2, 3, dev=1)
93 |         with self.assertRaises(ValueError):
94 |             parse_version('bad&version')
95 | 


--------------------------------------------------------------------------------
/tools/__init__.py:
--------------------------------------------------------------------------------
1 | """Tools."""
2 | 


--------------------------------------------------------------------------------
/tools/gh_labels.py:
--------------------------------------------------------------------------------
  1 | """Populate GitHub labels for issue tracker."""
  2 | from github import Github
  3 | from collections import namedtuple
  4 | import sys
  5 | import os
  6 | 
  7 | # Repository name
  8 | REPO_NAME = 'pep562'
  9 | 
 10 | # Options
 11 | DELETE_UNSPECIFIED = True
 12 | 
 13 | # Colors
 14 | BUG = 'c45b46'
 15 | FEATURE = '7b17d8'
 16 | SUPPORT = 'efbe62'
 17 | MAINTENANCE = 'b2ffeb'
 18 | 
 19 | CATEGORY = '709ad8'
 20 | SUBCATEGORY = 'bfd4f2'
 21 | 
 22 | PENDING = 'f0f49a'
 23 | REJECTED = 'f7c7be'
 24 | APPROVED = 'beed6d'
 25 | 
 26 | LOW = 'dddddd'
 27 | 
 28 | # Labels.
 29 | # To rename a label, use ('old_name', 'new_name') as the key.
 30 | label_list = {
 31 |     # Issue type
 32 |     'bug': (BUG, "Bug report."),
 33 |     'feature': (FEATURE, "Feature request."),
 34 |     'maintenance': (MAINTENANCE, "Maintenance chore."),
 35 |     'support': (SUPPORT, "Support request."),
 36 | 
 37 |     # Category
 38 |     'core': (CATEGORY, "Related to the core."),
 39 |     'integration': (CATEGORY, "Related to packaging and/or testing."),
 40 |     'docs': (CATEGORY, "Related to documentation."),
 41 | 
 42 |     # Sub categories
 43 | 
 44 |     # Issue status
 45 |     'more-info-needed': (PENDING, "More information is required."),
 46 |     'needs-confirmation': (PENDING, "The alleged behavior needs to be confirmed."),
 47 |     'needs-decision': (PENDING, "A decision needs to be made regarding request."),
 48 |     'confirmed': (APPROVED, "Confirmed bug report or approved feature request."),
 49 |     'maybe': (LOW, "Pending approval of low priority request."),
 50 |     'duplicate': (REJECTED, "The issue has been previously reported."),
 51 |     'wontfix': (REJECTED, "The issue will not be fixed for the stated reasons."),
 52 |     'invalid': (REJECTED, "Invalid report (user error, upstream issue, etc)."),
 53 | 
 54 |     # Pull request status
 55 |     'work-in-progress': (PENDING, "A partial solution. More changes will be coming."),
 56 |     'needs-review': (PENDING, "Needs to be reviewed and/or approved."),
 57 |     'requires-changes': (PENDING, "Awaiting updates after a review."),
 58 |     'approved': (APPROVED, "The pull request is ready to be merged."),
 59 |     'rejected': (REJECTED, "The pull request is rejected for the stated reasons.")
 60 | }
 61 | 
 62 | 
 63 | # Label handling
 64 | class LabelEdit(namedtuple('LabelEdit', ['old', 'new', 'color', 'description'])):
 65 |     """Label Edit tuple."""
 66 | 
 67 | 
 68 | def find_label(label, label_color, label_description):
 69 |     """Find label."""
 70 |     edit = None
 71 |     for name, values in label_list.items():
 72 |         color, description = values
 73 |         if isinstance(name, tuple):
 74 |             old_name = name[0]
 75 |             new_name = name[1]
 76 |         else:
 77 |             old_name = name
 78 |             new_name = name
 79 |         if label.lower() == old_name.lower():
 80 |             edit = LabelEdit(old_name, new_name, color, description)
 81 |             break
 82 |     return edit
 83 | 
 84 | 
 85 | def update_labels(repo):
 86 |     """Update labels."""
 87 |     updated = set()
 88 |     for label in repo.get_labels():
 89 |         edit = find_label(label.name, label.color, label.description)
 90 |         if edit is not None:
 91 |             print('    Updating {}: #{} "{}"'.format(edit.new, edit.color, edit.description))
 92 |             label.edit(edit.new, edit.color, edit.description)
 93 |             updated.add(edit.old)
 94 |             updated.add(edit.new)
 95 |         else:
 96 |             if DELETE_UNSPECIFIED:
 97 |                 print('    Deleting {}: #{} "{}"'.format(label.name, label.color, label.description))
 98 |                 label.delete()
 99 |             else:
100 |                 print('    Skipping {}: #{} "{}"'.format(label.name, label.color, label.description))
101 |             updated.add(label.name)
102 |     for name, values in label_list.items():
103 |         color, description = values
104 |         if isinstance(name, tuple):
105 |             new_name = name[1]
106 |         else:
107 |             new_name = name
108 |         if new_name not in updated:
109 |             print('    Creating {}: #{} "{}"'.format(new_name, color, description))
110 |             repo.create_label(new_name, color, description)
111 | 
112 | 
113 | # Authentication
114 | def get_auth():
115 |     """Get authentication."""
116 |     import getpass
117 |     user = input("User Name: ")  # noqa
118 |     pswd = getpass.getpass('Password: ')
119 |     return Github(user, pswd)
120 | 
121 | 
122 | def main():
123 |     """Main."""
124 | 
125 |     if len(sys.argv) > 1 and os.path.exists(sys.argv[1]):
126 |         try:
127 |             with open(sys.argv[1], 'r') as f:
128 |                 user_name, password = f.read().strip().split(':')
129 |             git = Github(user_name, password)
130 |             password = None
131 |         except Exception:
132 |             git = get_auth()
133 |     else:
134 |         git = get_auth()
135 | 
136 |     user = git.get_user()
137 | 
138 |     print('Finding repo...')
139 |     for repo in user.get_repos():
140 |         if repo.owner.name == user.name:
141 |             if repo.name == REPO_NAME:
142 |                 print(repo.name)
143 |                 update_labels(repo)
144 |                 break
145 | 
146 | 
147 | if __name__ == "__main__":
148 |     main()
149 | 


--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
 1 | [tox]
 2 | envlist =
 3 |     {py27,py36,py37,py38,py39}, lint, documents
 4 | 
 5 | [testenv]
 6 | passenv = LANG
 7 | deps=
 8 |     -rrequirements/tests.txt
 9 | commands=
10 |     {envbindir}/py.test --cov pep562 --cov-append {toxinidir}
11 |     {envbindir}/coverage html -d {envtmpdir}/coverage
12 |     {envbindir}/coverage xml
13 |     {envbindir}/coverage report --show-missing
14 | 
15 | [testenv:documents]
16 | deps=
17 |     -rrequirements/docs.txt
18 | commands=
19 |     {envpython} -m pyspelling
20 | 
21 | [testenv:lint]
22 | deps=
23 |     -rrequirements/flake8.txt
24 | commands=
25 |     {envbindir}/flake8 {toxinidir}
26 | 
27 | [flake8]
28 | exclude=build/*,.tox/*
29 | max-line-length=120
30 | ignore=D202,N802,D203,D401,W504
31 | 


--------------------------------------------------------------------------------