diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..f9c71a7f --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +max-line-length = 120 +per-file-ignores = + **/*.pyi:E252,E301,E302,E305,E501,E701,E704,F401,F811,F821 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..564aa0c5 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,66 @@ +name: build + +on: [push, pull_request] + +jobs: + build: + + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + python: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "pypy3"] + os: [ubuntu-latest, windows-latest] + include: + - python: "2.7" + tox_env: "py27-pytest30" + - python: "3.5" + tox_env: "py35-pytest30" + - python: "3.6" + tox_env: "py36-pytest30" + - python: "3.7" + tox_env: "py37-pytest30" + - python: "3.8" + tox_env: "py38-pytest30" + - python: "3.9" + tox_env: "py39-pytest30" + - python: "pypy3" + tox_env: "pypy3-pytest30" + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Test + run: | + pipx run tox -e ${{ matrix.tox_env }} + + deploy: + + if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') + + runs-on: ubuntu-latest + + needs: build + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.7" + - name: Install wheel + run: | + python -m pip install --upgrade pip + pip install wheel + - name: Build package + run: | + python setup.py sdist bdist_wheel + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.pypi_token }} diff --git a/.gitignore b/.gitignore index 375476fd..fa936f15 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .cache/ .tox/ __pycache__/ +.mypy_cache/ *.pyc *.pyo diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ea75028d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,44 +0,0 @@ -sudo: false -language: python -python: -- '2.7' -- '3.4' -- '3.5' -- '3.6' -- 'pypy-5.4' -env: -- DEPS="pytest~=2.9.0" -- DEPS="pytest~=3.0.0" -#- DEPS="pytest~=3.1.0" - -matrix: - - include: - - python: '2.7' - # using a different option due to pytest-addopts pytester issues - env: PYTEST_XADDOPTS="-n 3 --runslowtests" DEPS="pytest~=3.0.0 pytest-xdist" - - - stage: deploy - python: '3.6' - env: - install: pip install -U setuptools setuptools_scm - script: skip - deploy: - provider: pypi - user: nicoddemus - distributions: sdist bdist_wheel - skip_upload_docs: true - password: - secure: VNYW/sZoD+9DzKCe6vANNXXJR7jP7rwySafQ33N1jAnCrdylQjEN/p6tSfUe8jDi3wDpLPL9h8pwfxuUT7CRxglHov3Qe7zSeywixvHan5aFahQiQ8+gucYIM7wITHH3oQs7jN35pnhdnF+QlW2+eDCL6qOLU5XwuRhsDKXjQ/hUWR5hlX5EniD1gzyKEf6j1YCpST87tKpeLwVEYEmsucdkUZuXhxDtyaWQHWiPsLWwh/slQtUJEHeLF26r8UxFy0RiGne9jR+CzRfH5ktcA9/pArvp4VuwOii+1TDxVSYP7+I8Z+eUKN9JBg12QLaHwoIN/8J+MvHCkuf+OGSLM3sEyNRJGDev372xg3K7ylIkeeK4WXirKEp2ojgN8tniloDjnwdu/gPWBnrXuooA60tNoByHFa8KbMZAr2B2sQeMxD4VZGr1N8l0rX4gRTrwvdk3i3ulLKVSwkXaGn+GrfZTTboa7dEnpuma8tv1niNCSpStYIy7atS8129+5ijV3OC8DzOMh/rVbO9WsDb/RPG3yjFiDvEJPIPeE0l/m5u42QBqtdZSS2ia7UWTJBiEY09uFMTRmH5hhE/1aiYBbvAztf5CReUbeKdSQz3L8TTSZqewtFZmXTkX97/xQnrEpsnGezIM2DNuMEuQG3MxGkNCxwbQKpx/bkHdrD75yMk= - on: - tags: true - repo: pytest-dev/py - - allow_failures: - - python: 'pypy-5.4' -install: -- pip install -U setuptools setuptools_scm -- pip install $DEPS -- pip install -U . --force-reinstall -script: -- py.test --lsof $PYTEST_XADDOPTS diff --git a/AUTHORS b/AUTHORS index 8c0cf9b7..9c5dda9c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -22,3 +22,4 @@ Jan Balster Grig Gheorghiu Bob Ippolito Christian Tismer +Wim Glenn diff --git a/CHANGELOG b/CHANGELOG.rst similarity index 93% rename from CHANGELOG rename to CHANGELOG.rst index a17cdb59..47c6fdb7 100644 --- a/CHANGELOG +++ b/CHANGELOG.rst @@ -1,3 +1,79 @@ +1.11.0 (2021-11-04) +=================== + +- Support Python 3.11 +- Support ``NO_COLOR`` environment variable +- Update vendored apipkg: 1.5 => 2.0 + +1.10.0 (2020-12-12) +=================== + +- Fix a regular expression DoS vulnerability in the py.path.svnwc SVN blame functionality (CVE-2020-29651) +- Update vendored apipkg: 1.4 => 1.5 +- Update vendored iniconfig: 1.0.0 => 1.1.1 + +1.9.0 (2020-06-24) +================== + +- Add type annotation stubs for the following modules: + + * ``py.error`` + * ``py.iniconfig`` + * ``py.path`` (not including SVN paths) + * ``py.io`` + * ``py.xml`` + + There are no plans to type other modules at this time. + + The type annotations are provided in external .pyi files, not inline in the + code, and may therefore contain small errors or omissions. If you use ``py`` + in conjunction with a type checker, and encounter any type errors you believe + should be accepted, please report it in an issue. + +1.8.2 (2020-06-15) +================== + +- On Windows, ``py.path.local``s which differ only in case now have the same + Python hash value. Previously, such paths were considered equal but had + different hashes, which is not allowed and breaks the assumptions made by + dicts, sets and other users of hashes. + +1.8.1 (2019-12-27) +================== + +- Handle ``FileNotFoundError`` when trying to import pathlib in ``path.common`` + on Python 3.4 (#207). + +- ``py.path.local.samefile`` now works correctly in Python 3 on Windows when dealing with symlinks. + +1.8.0 (2019-02-21) +================== + +- add ``"importlib"`` pyimport mode for python3.5+, allowing unimportable test suites + to contain identically named modules. + +- fix ``LocalPath.as_cwd()`` not calling ``os.chdir()`` with ``None``, when + being invoked from a non-existing directory. + + +1.7.0 (2018-10-11) +================== + +- fix #174: use ``shutil.get_terminal_size()`` in Python 3.3+ to determine the size of the + terminal, which produces more accurate results than the previous method. + +- fix pytest-dev/pytest#2042: introduce new ``PY_IGNORE_IMPORTMISMATCH`` environment variable + that suppresses ``ImportMismatchError`` exceptions when set to ``1``. + + +1.6.0 (2018-08-27) +================== + +- add ``TerminalWriter.width_of_current_line`` (i18n version of + ``TerminalWriter.chars_on_current_line``), a read-only property + that tracks how wide the current line is, attempting to take + into account international characters in the calculation. + 1.5.4 (2018-06-27) ================== @@ -80,7 +156,7 @@ This issue was fixed in the 1.5.1 release. 1.4.30 ================================================== -- fix issue68 an assert with a multiline list comprehension +- fix issue68 an assert with a multiline list comprehension was not reported correctly. Thanks Henrik Heibuerger. @@ -108,7 +184,7 @@ This issue was fixed in the 1.5.1 release. - allow a new ensuresyspath="append" mode for py.path.local.pyimport() so that a neccessary import path is appended instead of prepended to - sys.path + sys.path - strike undocumented, untested argument to py.path.local.pypkgpath @@ -156,10 +232,10 @@ This issue was fixed in the 1.5.1 release. thus triggering the alias module to resolve and blowing up with ImportError. The negative side is that something like "py.test.X" will now result in None instead of "importerror: pytest" - if pytest is not installed. But you shouldn't import "py.test" + if pytest is not installed. But you shouldn't import "py.test" anyway anymore. -- adapt one svn test to only check for any exception instead +- adapt one svn test to only check for any exception instead of specific ones because different svn versions cause different errors and we don't care. @@ -182,8 +258,8 @@ This issue was fixed in the 1.5.1 release. its output even if it didn't flush itself. - refactor traceback generation in light of pytest issue 364 - (shortening tracebacks). you can now set a new traceback style - on a per-entry basis such that a caller can force entries to be + (shortening tracebacks). you can now set a new traceback style + on a per-entry basis such that a caller can force entries to be isplayed as short or long entries. - win32: py.path.local.sysfind(name) will preferrably return files with @@ -195,7 +271,7 @@ This issue was fixed in the 1.5.1 release. - ignore unicode decode errors in xmlescape. Thanks Anatoly Bubenkoff. -- on python2 modify traceback.format_exception_only to match python3 +- on python2 modify traceback.format_exception_only to match python3 behaviour, namely trying to print unicode for Exception instances - use a safer way for serializing exception reports (helps to fix @@ -225,7 +301,7 @@ Changes between 1.4.17 and 1.4.18 - introduce path.ensure_dir() as a synonym for ensure(..., dir=1) - some unicode/python3 related fixes wrt to path manipulations - (if you start passing unicode particular in py2 you might + (if you start passing unicode particular in py2 you might still get problems, though) Changes between 1.4.16 and 1.4.17 @@ -292,7 +368,7 @@ Changes between 1.4.12 and 1.4.13 Changes between 1.4.11 and 1.4.12 ================================================== -- fix python2.4 support - for pre-AST interpreters re-introduce +- fix python2.4 support - for pre-AST interpreters re-introduce old way to find statements in exceptions (closes pytest issue 209) - add tox.ini to distribution - fix issue23 - print *,** args information in tracebacks, @@ -310,7 +386,7 @@ Changes between 1.4.10 and 1.4.11 unicodeencode/decode problems, amend according test - introduce py.builtin.text and py.builtin.bytes to point to respective str/unicode (py2) and bytes/str (py3) types -- fix error handling on win32/py33 for ENODIR +- fix error handling on win32/py33 for ENODIR Changes between 1.4.9 and 1.4.10 ================================================== @@ -347,12 +423,12 @@ Changes between 1.4.6 and 1.4.7 Changes between 1.4.5 and 1.4.6 ================================================== -- help to fix pytest issue99: unify output of +- help to fix pytest issue99: unify output of ExceptionInfo.getrepr(style="native") with ...(style="long") - fix issue7: source.getstatementrange() now raises proper error if no valid statement can be found -- fix issue8: fix code and tests of svnurl/svnwc to work on subversion 1.7 - - note that path.status(updates=1) will not properly work svn-17's status +- fix issue8: fix code and tests of svnurl/svnwc to work on subversion 1.7 - + note that path.status(updates=1) will not properly work svn-17's status --xml output is broken. - make source.getstatementrange() more resilent about non-python code frames (as seen from jnja2) @@ -421,7 +497,7 @@ Changes between 1.3.4 and 1.4.0 - py.test was moved to a separate "pytest" package. What remains is a stub hook which will proxy ``import py.test`` to ``pytest``. -- all command line tools ("py.cleanup/lookup/countloc/..." moved +- all command line tools ("py.cleanup/lookup/countloc/..." moved to "pycmd" package) - removed the old and deprecated "py.magic" namespace - use apipkg-1.1 and make py.apipkg.initpkg|ApiModule available diff --git a/MANIFEST.in b/MANIFEST.in index 239ad228..6d255b1a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,10 +1,11 @@ -include CHANGELOG +include CHANGELOG.rst include AUTHORS include README.rst include setup.py include LICENSE include conftest.py include tox.ini +recursive-include py *.pyi graft doc graft testing global-exclude *.pyc diff --git a/README.rst b/README.rst index 3d9ec0fa..80800b2b 100644 --- a/README.rst +++ b/README.rst @@ -1,17 +1,14 @@ .. image:: https://img.shields.io/pypi/v/py.svg :target: https://pypi.org/project/py -.. image:: https://anaconda.org/conda-forge/py/badges/version.svg +.. image:: https://img.shields.io/conda/vn/conda-forge/py.svg :target: https://anaconda.org/conda-forge/py -.. image:: https://img.shields.io/pypi/pyversions/pytest.svg +.. image:: https://img.shields.io/pypi/pyversions/py.svg :target: https://pypi.org/project/py -.. image:: https://img.shields.io/travis/pytest-dev/py.svg - :target: https://travis-ci.org/pytest-dev/py - -.. image:: https://ci.appveyor.com/api/projects/status/10keglan6uqwj5al/branch/master?svg=true - :target: https://ci.appveyor.com/project/pytestbot/py +.. image:: https://github.com/pytest-dev/py/workflows/build/badge.svg + :target: https://github.com/pytest-dev/py/actions **NOTE**: this library is in **maintenance mode** and should not be used in new code. @@ -25,9 +22,9 @@ the following tools and modules: * ``py.code``: dynamic code generation and introspection (deprecated, moved to ``pytest`` as a implementation detail). **NOTE**: prior to the 1.4 release this distribution used to -contain py.test which is now its own package, see http://pytest.org +contain py.test which is now its own package, see https://docs.pytest.org -For questions and more information please visit http://py.readthedocs.org +For questions and more information please visit https://py.readthedocs.io Bugs and issues: https://github.com/pytest-dev/py diff --git a/HOWTORELEASE.rst b/RELEASING.rst similarity index 90% rename from HOWTORELEASE.rst rename to RELEASING.rst index 8d023163..fb588e3a 100644 --- a/HOWTORELEASE.rst +++ b/RELEASING.rst @@ -3,7 +3,7 @@ Release Procedure #. Create a branch ``release-X.Y.Z`` from the latest ``master``. -#. Manually update the ``CHANGELOG`` and commit. +#. Manually update the ``CHANGELOG.rst`` and commit. #. Open a PR for this branch targeting ``master``. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 5fbeca9a..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,26 +0,0 @@ -environment: - matrix: - # note: please use "tox --listenvs" to populate the build matrix below - - TOXENV: "py27-pytest29" - - TOXENV: "py27-pytest30" - - TOXENV: "py27-pytest31" - - TOXENV: "py34-pytest29" - - TOXENV: "py34-pytest30" - - TOXENV: "py34-pytest31" - - TOXENV: "py35-pytest29" - - TOXENV: "py35-pytest30" - - TOXENV: "py35-pytest31" - - TOXENV: "py36-pytest29" - - TOXENV: "py36-pytest30" - - TOXENV: "py36-pytest31" - -install: - - echo Installed Pythons - - dir c:\Python* - - - C:\Python36\python -m pip install --upgrade --pre tox - -build: false # Not a C# project, build stuff at the test step instead. - -test_script: - - C:\Python36\python -m tox diff --git a/bench/localpath.py b/bench/localpath.py index ad4fbd8e..aad44f2e 100644 --- a/bench/localpath.py +++ b/bench/localpath.py @@ -1,6 +1,4 @@ - import py -import timeit class Listdir: numiter = 100000 @@ -70,6 +68,6 @@ def run(self): for i in xrange(cls.numiter): inst.run() elapsed = time.time() - now - print "%s: %d loops took %.2f seconds, per call %.6f" %( + print("%s: %d loops took %.2f seconds, per call %.6f" %( cls.__name__, - cls.numiter, elapsed, elapsed / cls.numiter) + cls.numiter, elapsed, elapsed / cls.numiter)) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..a0a30858 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,7 @@ +coverage: + status: + project: true + patch: true + changes: true + +comment: off diff --git a/doc/announce/release-0.9.2.txt b/doc/announce/release-0.9.2.txt index bc2d2ef2..8340dc44 100644 --- a/doc/announce/release-0.9.2.txt +++ b/doc/announce/release-0.9.2.txt @@ -16,7 +16,7 @@ Here is a quick summary of what the py lib provides: See here for more information: -Pypi pages: http://pypi.python.org/pypi/py/ +Pypi pages: https://pypi.org/project/py/ Download/Install: http://codespeak.net/py/0.9.2/download.html diff --git a/doc/announce/release-1.0.0.txt b/doc/announce/release-1.0.0.txt index 7024255a..aef25ec2 100644 --- a/doc/announce/release-1.0.0.txt +++ b/doc/announce/release-1.0.0.txt @@ -58,6 +58,6 @@ holger .. _`default plugins`: http://codespeak.net/py/dist/test/plugin/index.html .. _`distributed testing`: http://codespeak.net/py/dist/test/dist.html .. _`elastic distributed execution`: http://codespeak.net/py/dist/execnet.html -.. _`1.0.0 py lib release`: http://pypi.python.org/pypi/py +.. _`1.0.0 py lib release`: https://pypi.org/project/py/ .. _`oejskit`: http://codespeak.net/py/dist/test/plugin/oejskit.html diff --git a/doc/announce/release-1.4.0.txt b/doc/announce/release-1.4.0.txt index 6f9a7714..1c9fa756 100644 --- a/doc/announce/release-1.4.0.txt +++ b/doc/announce/release-1.4.0.txt @@ -22,7 +22,7 @@ as "pytest-2.0.0", see here for the revamped docs: http://pytest.org And "py.cleanup|py.lookup|py.countloc" etc. helpers are now part of -the pycmd distribution, see http://pypi.python.org/pypi/pycmd +the pycmd distribution, see https://pypi.org/project/pycmd/ This makes "py-1.4.0" a simple library which does not install any command line utilities anymore. diff --git a/doc/announce/release-1.4.1.txt b/doc/announce/release-1.4.1.txt index a5aa76b1..6ed72aa4 100644 --- a/doc/announce/release-1.4.1.txt +++ b/doc/announce/release-1.4.1.txt @@ -23,7 +23,7 @@ comes as its own separate "pytest" distribution, see: http://pytest.org Also, the "py.cleanup|py.lookup|py.countloc" helpers are now part of -the pycmd distribution, see http://pypi.python.org/pypi/pycmd +the pycmd distribution, see https://pypi.org/project/pycmd/ Changes between 1.4.0 and 1.4.1 diff --git a/doc/changelog.txt b/doc/changelog.txt index 237daca3..0c9d0928 100644 --- a/doc/changelog.txt +++ b/doc/changelog.txt @@ -1,3 +1,3 @@ .. _`changelog`: -.. include:: ../CHANGELOG +.. include:: ../CHANGELOG.rst diff --git a/doc/example/genhtml.py b/doc/example/genhtml.py index b5c8f525..7a6d4934 100644 --- a/doc/example/genhtml.py +++ b/doc/example/genhtml.py @@ -8,6 +8,6 @@ html.body( [html.p(p) for p in paras])) -print unicode(doc).encode('latin1') +print(unicode(doc).encode('latin1')) diff --git a/doc/example/genhtmlcss.py b/doc/example/genhtmlcss.py index 3e6d0af5..facca77b 100644 --- a/doc/example/genhtmlcss.py +++ b/doc/example/genhtmlcss.py @@ -20,4 +20,4 @@ class p(html.p): ) ) -print doc.unicode(indent=2) +print(doc.unicode(indent=2)) diff --git a/doc/example/genxml.py b/doc/example/genxml.py index 5f754e88..444a4ca5 100644 --- a/doc/example/genxml.py +++ b/doc/example/genxml.py @@ -12,6 +12,6 @@ class ns(py.xml.Namespace): ns.title("Java for Python programmers"),), publisher="N.N", ) -print doc.unicode(indent=2).encode('utf8') +print(doc.unicode(indent=2).encode('utf8')) diff --git a/doc/faq.txt b/doc/faq.txt index 52cb4b3f..6d374e1d 100644 --- a/doc/faq.txt +++ b/doc/faq.txt @@ -41,9 +41,9 @@ as a clone of ``py.test`` when py.test was in the ``0.8`` release cycle so some of the newer features_ introduced with py.test-1.0 and py.test-1.1 have no counterpart in nose_. -.. _nose: http://somethingaboutorange.com/mrl/projects/nose/0.11.1/ +.. _nose: https://nose.readthedocs.io/ .. _features: test/features.html -.. _apipkg: http://pypi.python.org/pypi/apipkg +.. _apipkg: https://pypi.org/project/apipkg/ What's this "magic" with py.test? @@ -112,7 +112,7 @@ and will safely find all factory functions for the ``MYARG`` function argument. It helps to alleviate the de-coupling of function argument usage and creation. -.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration +.. _`Convention over Configuration`: https://en.wikipedia.org/wiki/Convention_over_configuration Can I yield multiple values from a factory function? ----------------------------------------------------- @@ -134,7 +134,7 @@ Use the `pytest_generate_tests`_ hook to solve both issues and implement the `parametrization scheme of your choice`_. .. _`pytest_generate_tests`: test/funcargs.html#parametrizing-tests -.. _`parametrization scheme of your choice`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/ +.. _`parametrization scheme of your choice`: https://holgerkrekel.net/2009/05/13/parametrizing-python-tests-generalized/ py.test interaction with other packages @@ -167,6 +167,4 @@ script with this content and invoke that with the python version:: .. _`directly use a checkout`: install.html#directly-use-a-checkout -.. _`install distribute`: http://pypi.python.org/pypi/distribute#installation-instructions - - +.. _`install distribute`: https://pypi.org/project/distribute/ diff --git a/doc/install.txt b/doc/install.txt index fb4056d1..93c79e3b 100644 --- a/doc/install.txt +++ b/doc/install.txt @@ -1,13 +1,13 @@ .. _`py`: -.. _`index page`: http://pypi.python.org/pypi/py/ +.. _`index page`: https://pypi.org/project/py/ installation info in a nutshell =================================================== **PyPI name**: py_ -**Pythons**: CPython 2.7, 3.4, 3.5, 3.6, PyPy-5.4 +**Pythons**: CPython 2.7, 3.5, 3.6, 3.7, PyPy-5.4 **Operating systems**: Linux, Windows, OSX, Unix @@ -15,7 +15,7 @@ installation info in a nutshell **Installers**: ``easy_install`` and ``pip`` -**hg repository**: https://bitbucket.org/hpk42/py +**Code repository**: https://github.com/pytest-dev/py easy install or pip ``py`` ----------------------------- @@ -39,16 +39,16 @@ Working from version control or a tarball ----------------------------------------------- To follow development or start experiments, checkout the -complete code and documentation source with mercurial_:: +complete code and documentation source:: - hg clone https://bitbucket.org/hpk42/py + git clone https://github.com/pytest-dev/py -Development takes place on the 'trunk' branch. +Development takes place on the 'master' branch. You can also go to the python package index and download and unpack a TAR file:: - http://pypi.python.org/pypi/py/ + https://pypi.org/project/py/ activating a checkout with setuptools -------------------------------------------- @@ -63,7 +63,7 @@ in order to work inline with the tools and the lib of your checkout. .. _`directly use a checkout`: -.. _`setuptools`: http://pypi.python.org/pypi/setuptools +.. _`setuptools`: https://pypi.org/project/setuptools/ Mailing list and issue tracker @@ -71,12 +71,15 @@ Mailing list and issue tracker - `py-dev developers list`_ and `commit mailing list`_. -- #pylib on irc.freenode.net IRC channel for random questions. +- ``#pytest`` `on irc.libera.chat `_ IRC + channel for random questions (using an IRC client, `via webchat + `_, or `via Matrix + `_). -- `bitbucket issue tracker`_ use this bitbucket issue tracker to report +- `issue tracker`_ use the issue tracker to report bugs or request features. -.. _`bitbucket issue tracker`: http://bitbucket.org/hpk42/py/issues/ +.. _`issue tracker`: https://github.com/pytest-dev/py/issues .. _codespeak: http://codespeak.net/ .. _`py-dev`: diff --git a/doc/links.inc b/doc/links.inc index 9bcfe5cf..b61d01c6 100644 --- a/doc/links.inc +++ b/doc/links.inc @@ -1,16 +1,15 @@ .. _`skipping plugin`: plugin/skipping.html .. _`funcargs mechanism`: funcargs.html -.. _`doctest.py`: http://docs.python.org/library/doctest.html +.. _`doctest.py`: https://docs.python.org/library/doctest.html .. _`xUnit style setup`: xunit_setup.html .. _`pytest_nose`: plugin/nose.html .. _`reStructured Text`: http://docutils.sourceforge.net .. _`Python debugger`: http://docs.python.org/lib/module-pdb.html -.. _nose: http://somethingaboutorange.com/mrl/projects/nose/ -.. _pytest: http://pypi.python.org/pypi/pytest -.. _mercurial: http://mercurial.selenic.com/wiki/ -.. _`setuptools`: http://pypi.python.org/pypi/setuptools -.. _`distribute`: http://pypi.python.org/pypi/distribute -.. _`pip`: http://pypi.python.org/pypi/pip -.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv +.. _nose: https://nose.readthedocs.io/ +.. _pytest: https://pypi.org/project/pytest/ +.. _`setuptools`: https://pypi.org/project/setuptools/ +.. _`distribute`: https://pypi.org/project/distribute/ +.. _`pip`: https://pypi.org/project/pip/ +.. _`virtualenv`: https://pypi.org/project/virtualenv/ .. _hudson: http://hudson-ci.org/ diff --git a/doc/path.txt b/doc/path.txt index 3c1d321a..8f506d49 100644 --- a/doc/path.txt +++ b/doc/path.txt @@ -5,7 +5,7 @@ py.path **Note**: The 'py' library is in "maintenance mode" and so is not recommended for new projects. Please check out `pathlib `_ or - `pathlib2 `_ for path + `pathlib2 `_ for path operations. The 'py' lib provides a uniform high-level api to deal with filesystems diff --git a/doc/style.css b/doc/style.css index 1faf762c..95e3ef07 100644 --- a/doc/style.css +++ b/doc/style.css @@ -546,7 +546,7 @@ div.message { } strong.highlight { background-color: #FFBBBB; -/* as usual, NetScape fucks up with innocent CSS +/* as usual, NetScape breaks with innocent CSS border-color: #FFAAAA; border-style: solid; border-width: 1pt; diff --git a/py/__init__.pyi b/py/__init__.pyi new file mode 100644 index 00000000..96859e31 --- /dev/null +++ b/py/__init__.pyi @@ -0,0 +1,20 @@ +from typing import Any + +# py allows to use e.g. py.path.local even without importing py.path. +# So import implicitly. +from . import error +from . import iniconfig +from . import path +from . import io +from . import xml + +__version__: str + +# Untyped modules below here. +std: Any +test: Any +process: Any +apipkg: Any +code: Any +builtin: Any +log: Any diff --git a/py/_builtin.py b/py/_builtin.py index 52ee9d79..ddc89fc7 100644 --- a/py/_builtin.py +++ b/py/_builtin.py @@ -1,120 +1,21 @@ import sys -try: - reversed = reversed -except NameError: - def reversed(sequence): - """reversed(sequence) -> reverse iterator over values of the sequence - - Return a reverse iterator - """ - if hasattr(sequence, '__reversed__'): - return sequence.__reversed__() - if not hasattr(sequence, '__getitem__'): - raise TypeError("argument to reversed() must be a sequence") - return reversed_iterator(sequence) - - class reversed_iterator(object): - - def __init__(self, seq): - self.seq = seq - self.remaining = len(seq) - - def __iter__(self): - return self - - def next(self): - i = self.remaining - if i > 0: - i -= 1 - item = self.seq[i] - self.remaining = i - return item - raise StopIteration - - def __length_hint__(self): - return self.remaining - -try: - any = any -except NameError: - def any(iterable): - for x in iterable: - if x: - return True - return False - -try: - all = all -except NameError: - def all(iterable): - for x in iterable: - if not x: - return False - return True - -try: - sorted = sorted -except NameError: - builtin_cmp = cmp # need to use cmp as keyword arg - - def sorted(iterable, cmp=None, key=None, reverse=0): - use_cmp = None - if key is not None: - if cmp is None: - def use_cmp(x, y): - return builtin_cmp(x[0], y[0]) - else: - def use_cmp(x, y): - return cmp(x[0], y[0]) - l = [(key(element), element) for element in iterable] - else: - if cmp is not None: - use_cmp = cmp - l = list(iterable) - if use_cmp is not None: - l.sort(use_cmp) - else: - l.sort() - if reverse: - l.reverse() - if key is not None: - return [element for (_, element) in l] - return l - -try: - set, frozenset = set, frozenset -except NameError: - from sets import set, frozenset - -# pass through -enumerate = enumerate - -try: - BaseException = BaseException -except NameError: - BaseException = Exception - -try: - GeneratorExit = GeneratorExit -except NameError: - class GeneratorExit(Exception): - """ This exception is never raised, it is there to make it possible to - write code compatible with CPython 2.5 even in lower CPython - versions.""" - pass - GeneratorExit.__module__ = 'exceptions' +# Passthrough for builtins supported with py27. +BaseException = BaseException +GeneratorExit = GeneratorExit _sysex = (KeyboardInterrupt, SystemExit, MemoryError, GeneratorExit) +all = all +any = any +callable = callable +enumerate = enumerate +reversed = reversed +set, frozenset = set, frozenset +sorted = sorted -try: - callable = callable -except NameError: - def callable(obj): - return hasattr(obj, "__call__") if sys.version_info >= (3, 0): - exec ("print_ = print ; exec_=exec") + exec("print_ = print ; exec_=exec") import builtins # some backward compatibility helpers @@ -131,13 +32,13 @@ def _totext(obj, encoding=None, errors=None): def _isbytes(x): return isinstance(x, bytes) + def _istext(x): return isinstance(x, str) text = str bytes = bytes - def _getimself(function): return getattr(function, '__self__', None) diff --git a/py/_io/capture.py b/py/_io/capture.py index bc157ed9..cacf2fa7 100644 --- a/py/_io/capture.py +++ b/py/_io/capture.py @@ -13,7 +13,7 @@ class TextIO(StringIO): def write(self, data): if not isinstance(data, unicode): data = unicode(data, getattr(self, '_encoding', 'UTF-8'), 'replace') - StringIO.write(self, data) + return StringIO.write(self, data) else: TextIO = StringIO @@ -24,7 +24,7 @@ class BytesIO(StringIO): def write(self, data): if isinstance(data, unicode): raise TypeError("not a byte value: %r" %(data,)) - StringIO.write(self, data) + return StringIO.write(self, data) patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'} @@ -266,7 +266,7 @@ def readouterr(self): err = self._readsnapshot(self.err.tmpfile) else: err = "" - return [out, err] + return out, err def _readsnapshot(self, f): f.seek(0) diff --git a/py/_io/terminalwriter.py b/py/_io/terminalwriter.py index 74d31259..442ca239 100644 --- a/py/_io/terminalwriter.py +++ b/py/_io/terminalwriter.py @@ -5,9 +5,10 @@ """ -import sys, os +import sys, os, unicodedata import py py3k = sys.version_info[0] >= 3 +py33 = sys.version_info >= (3, 3) from py.builtin import text, bytes win32_and_ctypes = False @@ -24,10 +25,15 @@ def _getdimensions(): - import termios,fcntl,struct - call = fcntl.ioctl(1,termios.TIOCGWINSZ,"\000"*8) - height,width = struct.unpack( "hhhh", call ) [:2] - return height, width + if py33: + import shutil + size = shutil.get_terminal_size() + return size.lines, size.columns + else: + import termios, fcntl, struct + call = fcntl.ioctl(1, termios.TIOCGWINSZ, "\000" * 8) + height, width = struct.unpack("hhhh", call)[:2] + return height, width def get_terminal_width(): @@ -53,6 +59,21 @@ def get_terminal_width(): terminal_width = get_terminal_width() +char_width = { + 'A': 1, # "Ambiguous" + 'F': 2, # Fullwidth + 'H': 1, # Halfwidth + 'N': 1, # Neutral + 'Na': 1, # Narrow + 'W': 2, # Wide +} + + +def get_line_width(text): + text = unicodedata.normalize('NFC', text) + return sum(char_width.get(unicodedata.east_asian_width(c), 1) for c in text) + + # XXX unify with _escaped func below def ansi_print(text, esc, file=None, newline=True, flush=False): if file is None: @@ -112,6 +133,8 @@ def should_do_markup(file): return True if os.environ.get('PY_COLORS') == '0': return False + if 'NO_COLOR' in os.environ: + return False return hasattr(file, 'isatty') and file.isatty() \ and os.environ.get('TERM') != 'dumb' \ and not (sys.platform.startswith('java') and os._name == 'nt') @@ -140,6 +163,7 @@ def __init__(self, file=None, stringio=False, encoding=None): self.hasmarkup = should_do_markup(file) self._lastlen = 0 self._chars_on_current_line = 0 + self._width_of_current_line = 0 @property def fullwidth(self): @@ -164,6 +188,16 @@ def chars_on_current_line(self): """ return self._chars_on_current_line + @property + def width_of_current_line(self): + """Return an estimate of the width so far in the current line. + + .. versionadded:: 1.6.0 + + :rtype: int + """ + return self._width_of_current_line + def _escaped(self, text, esc): if esc and self.hasmarkup: text = (''.join(['\x1b[%sm' % cod for cod in esc]) + @@ -195,7 +229,7 @@ def sep(self, sepchar, title=None, fullwidth=None, **kw): # i.e. 2 + 2*len(sepchar)*N + len(title) <= fullwidth # 2*len(sepchar)*N <= fullwidth - len(title) - 2 # N <= (fullwidth - len(title) - 2) // (2*len(sepchar)) - N = (fullwidth - len(title) - 2) // (2*len(sepchar)) + N = max((fullwidth - len(title) - 2) // (2*len(sepchar)), 1) fill = sepchar * N line = "%s %s %s" % (fill, title, fill) else: @@ -223,12 +257,17 @@ def write(self, msg, **kw): markupmsg = msg write_out(self._file, markupmsg) - def _update_chars_on_current_line(self, text): - fields = text.rsplit('\n', 1) - if '\n' in text: - self._chars_on_current_line = len(fields[-1]) + def _update_chars_on_current_line(self, text_or_bytes): + newline = b'\n' if isinstance(text_or_bytes, bytes) else '\n' + current_line = text_or_bytes.rsplit(newline, 1)[-1] + if isinstance(current_line, bytes): + current_line = current_line.decode('utf-8', errors='replace') + if newline in text_or_bytes: + self._chars_on_current_line = len(current_line) + self._width_of_current_line = get_line_width(current_line) else: - self._chars_on_current_line += len(fields[-1]) + self._chars_on_current_line += len(current_line) + self._width_of_current_line += get_line_width(current_line) def line(self, s='', **kw): self.write(s, **kw) diff --git a/py/_path/common.py b/py/_path/common.py index 2d490b56..2364e5fe 100644 --- a/py/_path/common.py +++ b/py/_path/common.py @@ -10,6 +10,12 @@ # Moved from local.py. iswin32 = sys.platform == "win32" or (getattr(os, '_name', False) == 'nt') +try: + # FileNotFoundError might happen in py34, and is not available with py27. + import_errors = (ImportError, FileNotFoundError) +except NameError: + import_errors = (ImportError,) + try: from os import fspath except ImportError: @@ -35,7 +41,7 @@ def fspath(path): raise try: import pathlib - except ImportError: + except import_errors: pass else: if isinstance(path, pathlib.PurePath): diff --git a/py/_path/local.py b/py/_path/local.py index 79dc6284..1385a039 100644 --- a/py/_path/local.py +++ b/py/_path/local.py @@ -18,6 +18,11 @@ def map_as_list(func, iter): else: map_as_list = map +ALLOW_IMPORTLIB_MODE = sys.version_info > (3,5) +if ALLOW_IMPORTLIB_MODE: + import importlib + + class Stat(object): def __getattr__(self, name): return getattr(self._osstatresult, "st_" + name) @@ -158,7 +163,10 @@ def __init__(self, path=None, expanduser=False): self.strpath = abspath(path) def __hash__(self): - return hash(self.strpath) + s = self.strpath + if iswin32: + s = s.lower() + return hash(s) def __eq__(self, other): s1 = fspath(self) @@ -191,8 +199,8 @@ def samefile(self, other): other = abspath(other) if self == other: return True - if iswin32: - return False # there is no samefile + if not hasattr(os.path, "samefile"): + return False return py.error.checked_call( os.path.samefile, self.strpath, other) @@ -574,14 +582,17 @@ def chdir(self): @contextmanager def as_cwd(self): - """ return context manager which changes to current dir during the - managed "with" context. On __enter__ it returns the old dir. + """ + Return a context manager, which changes to the path's dir during the + managed "with" context. + On __enter__ it returns the old dir, which might be ``None``. """ old = self.chdir() try: yield old finally: - old.chdir() + if old is not None: + old.chdir() def realpath(self): """ return a new path which contains no symbolic links.""" @@ -647,10 +658,35 @@ def pyimport(self, modname=None, ensuresyspath=True): If ensuresyspath=="append" the root dir will be appended if it isn't already contained in sys.path. if ensuresyspath is False no modification of syspath happens. + + Special value of ensuresyspath=="importlib" is intended + purely for using in pytest, it is capable only of importing + separate .py files outside packages, e.g. for test suite + without any __init__.py file. It effectively allows having + same-named test modules in different places and offers + mild opt-in via this option. Note that it works only in + recent versions of python. """ if not self.check(): raise py.error.ENOENT(self) + if ensuresyspath == 'importlib': + if modname is None: + modname = self.purebasename + if not ALLOW_IMPORTLIB_MODE: + raise ImportError( + "Can't use importlib due to old version of Python") + spec = importlib.util.spec_from_file_location( + modname, str(self)) + if spec is None: + raise ImportError( + "Can't find module %s at location %s" % + (modname, str(self)) + ) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + pkgpath = None if modname is None: pkgpath = self.pypkgpath() @@ -683,7 +719,9 @@ def pyimport(self, modname=None, ensuresyspath=True): except py.error.ENOENT: issame = False if not issame: - raise self.ImportMismatchError(modname, modfile, self) + ignore = os.getenv('PY_IGNORE_IMPORTMISMATCH') + if ignore != '1': + raise self.ImportMismatchError(modname, modfile, self) return mod else: try: diff --git a/py/_path/svnwc.py b/py/_path/svnwc.py index 3138dd85..b5b9d8d5 100644 --- a/py/_path/svnwc.py +++ b/py/_path/svnwc.py @@ -396,7 +396,7 @@ def makecmdoptions(self): def __str__(self): return "" %(self.username,) -rex_blame = re.compile(r'\s*(\d+)\s*(\S+) (.*)') +rex_blame = re.compile(r'\s*(\d+)\s+(\S+) (.*)') class SvnWCCommandPath(common.PathBase): """ path implementation offering access/modification to svn working copies. diff --git a/py/_std.py b/py/_std.py index 74d43672..66adb7b0 100644 --- a/py/_std.py +++ b/py/_std.py @@ -15,8 +15,9 @@ def __init__(self): self.__dict__ = sys.modules def __getattr__(self, name): - warnings.warn("py.std is deprecated, plase import %s directly" % name, - category=PyStdIsDeprecatedWarning) + warnings.warn("py.std is deprecated, please import %s directly" % name, + category=PyStdIsDeprecatedWarning, + stacklevel=2) try: m = __import__(name) except ImportError: diff --git a/py/_vendored_packages/apipkg-1.4.dist-info/DESCRIPTION.rst b/py/_vendored_packages/apipkg-1.4.dist-info/DESCRIPTION.rst deleted file mode 100644 index 54822200..00000000 --- a/py/_vendored_packages/apipkg-1.4.dist-info/DESCRIPTION.rst +++ /dev/null @@ -1,87 +0,0 @@ -Welcome to apipkg! ------------------------- - -With apipkg you can control the exported namespace of a -python package and greatly reduce the number of imports for your users. -It is a `small pure python module`_ that works on virtually all Python -versions, including CPython2.3 to Python3.1, Jython and PyPy. It co-operates -well with Python's ``help()`` system, custom importers (PEP302) and common -command line completion tools. - -Usage is very simple: you can require 'apipkg' as a dependency or you -can copy paste the <200 Lines of code into your project. - - -Tutorial example -------------------- - -Here is a simple ``mypkg`` package that specifies one namespace -and exports two objects imported from different modules:: - - # mypkg/__init__.py - import apipkg - apipkg.initpkg(__name__, { - 'path': { - 'Class1': "_mypkg.somemodule:Class1", - 'clsattr': "_mypkg.othermodule:Class2.attr", - } - } - -The package is initialized with a dictionary as namespace. - -You need to create a ``_mypkg`` package with a ``somemodule.py`` -and ``othermodule.py`` containing the respective classes. -The ``_mypkg`` is not special - it's a completely -regular python package. - -Namespace dictionaries contain ``name: value`` mappings -where the value may be another namespace dictionary or -a string specifying an import location. On accessing -an namespace attribute an import will be performed:: - - >>> import mypkg - >>> mypkg.path - - >>> mypkg.path.Class1 # '_mypkg.somemodule' gets imported now - - >>> mypkg.path.clsattr # '_mypkg.othermodule' gets imported now - 4 # the value of _mypkg.othermodule.Class2.attr - -The ``mypkg.path`` namespace and its two entries are -loaded when they are accessed. This means: - -* lazy loading - only what is actually needed is ever loaded - -* only the root "mypkg" ever needs to be imported to get - access to the complete functionality. - -* the underlying modules are also accessible, for example:: - - from mypkg.sub import Class1 - - -Including apipkg in your package --------------------------------------- - -If you don't want to add an ``apipkg`` dependency to your package you -can copy the `apipkg.py`_ file somewhere to your own package, -for example ``_mypkg/apipkg.py`` in the above example. You -then import the ``initpkg`` function from that new place and -are good to go. - -.. _`small pure python module`: -.. _`apipkg.py`: http://bitbucket.org/hpk42/apipkg/src/tip/apipkg.py - -Feedback? ------------------------ - -If you have questions you are welcome to - -* join the #pylib channel on irc.freenode.net -* subscribe to the http://codespeak.net/mailman/listinfo/py-dev list. -* create an issue on http://bitbucket.org/hpk42/apipkg/issues - -have fun, -holger krekel - - diff --git a/py/_vendored_packages/apipkg-1.4.dist-info/RECORD b/py/_vendored_packages/apipkg-1.4.dist-info/RECORD deleted file mode 100644 index dc72959d..00000000 --- a/py/_vendored_packages/apipkg-1.4.dist-info/RECORD +++ /dev/null @@ -1,9 +0,0 @@ -apipkg.py,sha256=BNnv_qvq8zZvku-uudoqgp3XTNFbwsNUmtzOKrVI7X0,6420 -apipkg-1.4.dist-info/top_level.txt,sha256=3TGS6nmN7kjxhUK4LpPCB3QkQI34QYGrT0ZQGWajoZ8,7 -apipkg-1.4.dist-info/METADATA,sha256=Fk_8BrHyXE--kvB3_ZBKgwvPaKusAZUjchH-kpB63Hs,3491 -apipkg-1.4.dist-info/DESCRIPTION.rst,sha256=RkMQqk5ljhGy0DiZkR_nbpjqvwCIhuIEHsyvkn3O96k,2803 -apipkg-1.4.dist-info/metadata.json,sha256=GdshYrA_7gAII3E3EQMH-31BHzU-klTZ6bPQzlDmuy4,779 -apipkg-1.4.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110 -apipkg-1.4.dist-info/RECORD,, -apipkg-1.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -__pycache__/apipkg.cpython-35.pyc,, diff --git a/py/_vendored_packages/apipkg-1.4.dist-info/metadata.json b/py/_vendored_packages/apipkg-1.4.dist-info/metadata.json deleted file mode 100644 index 05609b99..00000000 --- a/py/_vendored_packages/apipkg-1.4.dist-info/metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"license": "MIT License", "name": "apipkg", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "apipkg: namespace control and lazy-import mechanism", "platform": "unix", "version": "1.4", "extensions": {"python.details": {"project_urls": {"Home": "http://bitbucket.org/hpk42/apipkg"}, "document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "holger at merlinux.eu", "name": "holger krekel"}]}}, "classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Topic :: Software Development :: Libraries", "Programming Language :: Python"]} \ No newline at end of file diff --git a/py/_vendored_packages/apipkg-1.4.dist-info/INSTALLER b/py/_vendored_packages/apipkg-2.0.0.dist-info/INSTALLER similarity index 100% rename from py/_vendored_packages/apipkg-1.4.dist-info/INSTALLER rename to py/_vendored_packages/apipkg-2.0.0.dist-info/INSTALLER diff --git a/py/_vendored_packages/apipkg-2.0.0.dist-info/LICENSE b/py/_vendored_packages/apipkg-2.0.0.dist-info/LICENSE new file mode 100644 index 00000000..ff33b8f7 --- /dev/null +++ b/py/_vendored_packages/apipkg-2.0.0.dist-info/LICENSE @@ -0,0 +1,18 @@ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/py/_vendored_packages/apipkg-1.4.dist-info/METADATA b/py/_vendored_packages/apipkg-2.0.0.dist-info/METADATA similarity index 58% rename from py/_vendored_packages/apipkg-1.4.dist-info/METADATA rename to py/_vendored_packages/apipkg-2.0.0.dist-info/METADATA index eb7e60ac..7eea770a 100644 --- a/py/_vendored_packages/apipkg-1.4.dist-info/METADATA +++ b/py/_vendored_packages/apipkg-2.0.0.dist-info/METADATA @@ -1,11 +1,12 @@ -Metadata-Version: 2.0 +Metadata-Version: 2.1 Name: apipkg -Version: 1.4 +Version: 2.0.0 Summary: apipkg: namespace control and lazy-import mechanism -Home-page: http://bitbucket.org/hpk42/apipkg +Home-page: https://github.com/pytest-dev/apipkg Author: holger krekel -Author-email: holger at merlinux.eu -License: MIT License +Maintainer: Ronny Pfannschmidt +Maintainer-email: opensource@ronnypfannschmidt.de +License: MIT Platform: unix Platform: linux Platform: osx @@ -14,24 +15,36 @@ Platform: win32 Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: POSIX -Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X -Classifier: Topic :: Software Development :: Libraries +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Topic :: Software Development :: Libraries +Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7 +Description-Content-Type: text/x-rst +License-File: LICENSE -Welcome to apipkg! ------------------------- +Welcome to apipkg ! +------------------- -With apipkg you can control the exported namespace of a -python package and greatly reduce the number of imports for your users. -It is a `small pure python module`_ that works on virtually all Python -versions, including CPython2.3 to Python3.1, Jython and PyPy. It co-operates -well with Python's ``help()`` system, custom importers (PEP302) and common -command line completion tools. +With apipkg you can control the exported namespace of a Python package and +greatly reduce the number of imports for your users. +It is a `small pure Python module`_ that works on CPython 2.7 and 3.4+, +Jython and PyPy. It cooperates well with Python's ``help()`` system, +custom importers (PEP302) and common command-line completion tools. Usage is very simple: you can require 'apipkg' as a dependency or you -can copy paste the <200 Lines of code into your project. +can copy paste the ~200 lines of code into your project. Tutorial example @@ -40,6 +53,7 @@ Tutorial example Here is a simple ``mypkg`` package that specifies one namespace and exports two objects imported from different modules:: + # mypkg/__init__.py import apipkg apipkg.initpkg(__name__, { @@ -54,7 +68,7 @@ The package is initialized with a dictionary as namespace. You need to create a ``_mypkg`` package with a ``somemodule.py`` and ``othermodule.py`` containing the respective classes. The ``_mypkg`` is not special - it's a completely -regular python package. +regular Python package. Namespace dictionaries contain ``name: value`` mappings where the value may be another namespace dictionary or @@ -75,7 +89,7 @@ loaded when they are accessed. This means: * lazy loading - only what is actually needed is ever loaded * only the root "mypkg" ever needs to be imported to get - access to the complete functionality. + access to the complete functionality * the underlying modules are also accessible, for example:: @@ -91,19 +105,21 @@ for example ``_mypkg/apipkg.py`` in the above example. You then import the ``initpkg`` function from that new place and are good to go. -.. _`small pure python module`: -.. _`apipkg.py`: http://bitbucket.org/hpk42/apipkg/src/tip/apipkg.py +.. _`small pure Python module`: +.. _`apipkg.py`: https://github.com/pytest-dev/apipkg/blob/master/src/apipkg/__init__.py Feedback? ----------------------- If you have questions you are welcome to -* join the #pylib channel on irc.freenode.net -* subscribe to the http://codespeak.net/mailman/listinfo/py-dev list. -* create an issue on http://bitbucket.org/hpk42/apipkg/issues +* join the **#pytest** channel on irc.libera.chat_ + (using an IRC client, via webchat_, or via Matrix_). +* create an issue on the bugtracker_ -have fun, -holger krekel +.. _irc.libera.chat: ircs://irc.libera.chat:6697/#pytest +.. _webchat: https://web.libera.chat/#pytest +.. _matrix: https://matrix.to/#/%23pytest:libera.chat +.. _bugtracker: https://github.com/pytest-dev/apipkg/issues diff --git a/py/_vendored_packages/apipkg-2.0.0.dist-info/RECORD b/py/_vendored_packages/apipkg-2.0.0.dist-info/RECORD new file mode 100644 index 00000000..357b8b9c --- /dev/null +++ b/py/_vendored_packages/apipkg-2.0.0.dist-info/RECORD @@ -0,0 +1,11 @@ +apipkg-2.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +apipkg-2.0.0.dist-info/LICENSE,sha256=6J7tEHTTqUMZi6E5uAhE9bRFuGC7p0qK6twGEFZhZOo,1054 +apipkg-2.0.0.dist-info/METADATA,sha256=GqNwkxraK5UTxObLVXTLc2UqktOPwZnKqdk2ThzHX0A,4292 +apipkg-2.0.0.dist-info/RECORD,, +apipkg-2.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +apipkg-2.0.0.dist-info/WHEEL,sha256=WzZ8cwjh8l0jtULNjYq1Hpr-WCqCRgPr--TX4P5I1Wo,110 +apipkg-2.0.0.dist-info/top_level.txt,sha256=3TGS6nmN7kjxhUK4LpPCB3QkQI34QYGrT0ZQGWajoZ8,7 +apipkg/__init__.py,sha256=gpbD3O57S9f-LsO2e-XwI6IGISayicfnCq3B5y_8frg,6978 +apipkg/__pycache__/__init__.cpython-39.pyc,, +apipkg/__pycache__/version.cpython-39.pyc,, +apipkg/version.py,sha256=bgZFg-f3UKhgE-z2w8RoFrwqRBzJBZkM4_jKFiYB9eU,142 diff --git a/py/_vendored_packages/apipkg-2.0.0.dist-info/REQUESTED b/py/_vendored_packages/apipkg-2.0.0.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/py/_vendored_packages/apipkg-1.4.dist-info/WHEEL b/py/_vendored_packages/apipkg-2.0.0.dist-info/WHEEL similarity index 70% rename from py/_vendored_packages/apipkg-1.4.dist-info/WHEEL rename to py/_vendored_packages/apipkg-2.0.0.dist-info/WHEEL index 9dff69d8..b733a60d 100644 --- a/py/_vendored_packages/apipkg-1.4.dist-info/WHEEL +++ b/py/_vendored_packages/apipkg-2.0.0.dist-info/WHEEL @@ -1,5 +1,5 @@ Wheel-Version: 1.0 -Generator: bdist_wheel (0.24.0) +Generator: bdist_wheel (0.37.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any diff --git a/py/_vendored_packages/apipkg-1.4.dist-info/top_level.txt b/py/_vendored_packages/apipkg-2.0.0.dist-info/top_level.txt similarity index 100% rename from py/_vendored_packages/apipkg-1.4.dist-info/top_level.txt rename to py/_vendored_packages/apipkg-2.0.0.dist-info/top_level.txt diff --git a/py/_vendored_packages/apipkg.py b/py/_vendored_packages/apipkg/__init__.py similarity index 64% rename from py/_vendored_packages/apipkg.py rename to py/_vendored_packages/apipkg/__init__.py index 9d56e0bc..350d8c4b 100644 --- a/py/_vendored_packages/apipkg.py +++ b/py/_vendored_packages/apipkg/__init__.py @@ -1,7 +1,7 @@ """ -apipkg: control the exported namespace of a python package. +apipkg: control the exported namespace of a Python package. -see http://pypi.python.org/pypi/apipkg +see https://pypi.python.org/pypi/apipkg (c) holger krekel, 2009 - MIT license """ @@ -9,8 +9,7 @@ import sys from types import ModuleType - -__version__ = '1.4' +from .version import version as __version__ # NOQA:F401 def _py_abspath(path): @@ -18,7 +17,7 @@ def _py_abspath(path): special version of abspath that will leave paths from jython jars alone """ - if path.startswith('__pyclasspath__'): + if path.startswith("__pyclasspath__"): return path else: @@ -29,6 +28,7 @@ def distribution_version(name): """try to get the version of the named distribution, returs None on failure""" from pkg_resources import get_distribution, DistributionNotFound + try: dist = get_distribution(name) except DistributionNotFound: @@ -37,36 +37,42 @@ def distribution_version(name): return dist.version -def initpkg(pkgname, exportdefs, attr=dict(), eager=False): +def initpkg(pkgname, exportdefs, attr=None, eager=False): """ initialize given package from the export definitions. """ + attr = attr or {} oldmod = sys.modules.get(pkgname) d = {} - f = getattr(oldmod, '__file__', None) + f = getattr(oldmod, "__file__", None) if f: f = _py_abspath(f) - d['__file__'] = f - if hasattr(oldmod, '__version__'): - d['__version__'] = oldmod.__version__ - if hasattr(oldmod, '__loader__'): - d['__loader__'] = oldmod.__loader__ - if hasattr(oldmod, '__path__'): - d['__path__'] = [_py_abspath(p) for p in oldmod.__path__] - if '__doc__' not in exportdefs and getattr(oldmod, '__doc__', None): - d['__doc__'] = oldmod.__doc__ + d["__file__"] = f + if hasattr(oldmod, "__version__"): + d["__version__"] = oldmod.__version__ + if hasattr(oldmod, "__loader__"): + d["__loader__"] = oldmod.__loader__ + if hasattr(oldmod, "__path__"): + d["__path__"] = [_py_abspath(p) for p in oldmod.__path__] + if hasattr(oldmod, "__package__"): + d["__package__"] = oldmod.__package__ + if "__doc__" not in exportdefs and getattr(oldmod, "__doc__", None): + d["__doc__"] = oldmod.__doc__ + d["__spec__"] = getattr(oldmod, "__spec__", None) d.update(attr) if hasattr(oldmod, "__dict__"): oldmod.__dict__.update(d) mod = ApiModule(pkgname, exportdefs, implprefix=pkgname, attr=d) sys.modules[pkgname] = mod # eagerload in bypthon to avoid their monkeypatching breaking packages - if 'bpython' in sys.modules or eager: - for module in sys.modules.values(): + if "bpython" in sys.modules or eager: + for module in list(sys.modules.values()): if isinstance(module, ApiModule): module.__dict__ + return mod def importobj(modpath, attrname): - module = __import__(modpath, None, None, ['__doc__']) + """imports a module, then resolves the attrname on it""" + module = __import__(modpath, None, None, ["__doc__"]) if not attrname: return module @@ -78,20 +84,23 @@ def importobj(modpath, attrname): class ApiModule(ModuleType): + """the magical lazy-loading module standing""" + def __docget(self): try: return self.__doc except AttributeError: - if '__doc__' in self.__map__: - return self.__makeattr('__doc__') + if "__doc__" in self.__map__: + return self.__makeattr("__doc__") def __docset(self, value): self.__doc = value + __doc__ = property(__docget, __docset) def __init__(self, name, importspec, implprefix=None, attr=None): self.__name__ = name - self.__all__ = [x for x in importspec if x != '__onfirstaccess__'] + self.__all__ = [x for x in importspec if x != "__onfirstaccess__"] self.__map__ = {} self.__implprefix__ = implprefix or name if attr: @@ -100,47 +109,47 @@ def __init__(self, name, importspec, implprefix=None, attr=None): setattr(self, name, val) for name, importspec in importspec.items(): if isinstance(importspec, dict): - subname = '%s.%s' % (self.__name__, name) + subname = "{}.{}".format(self.__name__, name) apimod = ApiModule(subname, importspec, implprefix) sys.modules[subname] = apimod setattr(self, name, apimod) else: - parts = importspec.split(':') + parts = importspec.split(":") modpath = parts.pop(0) attrname = parts and parts[0] or "" - if modpath[0] == '.': + if modpath[0] == ".": modpath = implprefix + modpath if not attrname: - subname = '%s.%s' % (self.__name__, name) + subname = "{}.{}".format(self.__name__, name) apimod = AliasModule(subname, modpath) sys.modules[subname] = apimod - if '.' not in name: + if "." not in name: setattr(self, name, apimod) else: self.__map__[name] = (modpath, attrname) def __repr__(self): - l = [] - if hasattr(self, '__version__'): - l.append("version=" + repr(self.__version__)) - if hasattr(self, '__file__'): - l.append('from ' + repr(self.__file__)) - if l: - return '' % (self.__name__, " ".join(l)) - return '' % (self.__name__,) + repr_list = [] + if hasattr(self, "__version__"): + repr_list.append("version=" + repr(self.__version__)) + if hasattr(self, "__file__"): + repr_list.append("from " + repr(self.__file__)) + if repr_list: + return "".format(self.__name__, " ".join(repr_list)) + return "".format(self.__name__) def __makeattr(self, name): """lazily compute value for name or raise AttributeError if unknown.""" # print "makeattr", self.__name__, name target = None - if '__onfirstaccess__' in self.__map__: - target = self.__map__.pop('__onfirstaccess__') + if "__onfirstaccess__" in self.__map__: + target = self.__map__.pop("__onfirstaccess__") importobj(*target)() try: modpath, attrname = self.__map__[name] except KeyError: - if target is not None and name != '__onfirstaccess__': + if target is not None and name != "__onfirstaccess__": # retry, onfirstaccess might have set attrs return getattr(self, name) raise AttributeError(name) @@ -159,10 +168,10 @@ def __makeattr(self, name): def __dict__(self): # force all the content of the module # to be loaded when __dict__ is read - dictdescr = ModuleType.__dict__['__dict__'] + dictdescr = ModuleType.__dict__["__dict__"] dict = dictdescr.__get__(self) if dict is not None: - hasattr(self, 'some') + hasattr(self, "some") for name in self.__all__: try: self.__makeattr(name) @@ -182,19 +191,22 @@ def getmod(): mod.append(x) return mod[0] - class AliasModule(ModuleType): + x = modpath + ("." + attrname if attrname else "") + repr_result = "".format(modname, x) + class AliasModule(ModuleType): def __repr__(self): - x = modpath - if attrname: - x += "." + attrname - return '' % (modname, x) + return repr_result def __getattribute__(self, name): try: return getattr(getmod(), name) except ImportError: - return None + if modpath == "pytest" and attrname is None: + # hack for pylibs py.test + return None + else: + raise def __setattr__(self, name, value): setattr(getmod(), name, value) diff --git a/py/_vendored_packages/apipkg/version.py b/py/_vendored_packages/apipkg/version.py new file mode 100644 index 00000000..c5b4e0e7 --- /dev/null +++ b/py/_vendored_packages/apipkg/version.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# file generated by setuptools_scm +# don't change, don't track in version control +version = '2.0.0' +version_tuple = (2, 0, 0) diff --git a/py/_vendored_packages/iniconfig-1.0.0.dist-info/DESCRIPTION.rst b/py/_vendored_packages/iniconfig-1.0.0.dist-info/DESCRIPTION.rst deleted file mode 100644 index 6d59bc22..00000000 --- a/py/_vendored_packages/iniconfig-1.0.0.dist-info/DESCRIPTION.rst +++ /dev/null @@ -1,53 +0,0 @@ -iniconfig: brain-dead simple parsing of ini files -======================================================= - -iniconfig is a small and simple INI-file parser module -having a unique set of features: - -* tested against Python2.4 across to Python3.2, Jython, PyPy -* maintains order of sections and entries -* supports multi-line values with or without line-continuations -* supports "#" comments everywhere -* raises errors with proper line-numbers -* no bells and whistles like automatic substitutions -* iniconfig raises an Error if two sections have the same name. - -If you encounter issues or have feature wishes please report them to: - - http://github.org/RonnyPfannschmidt/iniconfig/issues - -Basic Example -=================================== - -If you have an ini file like this:: - - # content of example.ini - [section1] # comment - name1=value1 # comment - name1b=value1,value2 # comment - - [section2] - name2= - line1 - line2 - -then you can do:: - - >>> import iniconfig - >>> ini = iniconfig.IniConfig("example.ini") - >>> ini['section1']['name1'] # raises KeyError if not exists - 'value1' - >>> ini.get('section1', 'name1b', [], lambda x: x.split(",")) - ['value1', 'value2'] - >>> ini.get('section1', 'notexist', [], lambda x: x.split(",")) - [] - >>> [x.name for x in list(ini)] - ['section1', 'section2'] - >>> list(list(ini)[0].items()) - [('name1', 'value1'), ('name1b', 'value1,value2')] - >>> 'section1' in ini - True - >>> 'inexistendsection' in ini - False - - diff --git a/py/_vendored_packages/iniconfig-1.0.0.dist-info/RECORD b/py/_vendored_packages/iniconfig-1.0.0.dist-info/RECORD deleted file mode 100644 index ec2f5e17..00000000 --- a/py/_vendored_packages/iniconfig-1.0.0.dist-info/RECORD +++ /dev/null @@ -1,9 +0,0 @@ -iniconfig.py,sha256=-pBe5AF_6aAwo1CxJQ8i_zJq6ejc6IxHta7qk2tNJhY,5208 -iniconfig-1.0.0.dist-info/DESCRIPTION.rst,sha256=BDLMwWqfjpwZ5yqXRvz1x6bf8Dnt_pZhElekAwtL19o,1522 -iniconfig-1.0.0.dist-info/METADATA,sha256=bb2T8WUSDXXiUVxZ4WXhbffq6stikMTlB1jyrPbLfyU,2405 -iniconfig-1.0.0.dist-info/RECORD,, -iniconfig-1.0.0.dist-info/WHEEL,sha256=3XK1Z4AI42GuJXciCpiHMOkbehxRV8QDBW8IU41k3ZU,96 -iniconfig-1.0.0.dist-info/metadata.json,sha256=UYYwW0p815nU4qz8Iq1gGqIYaAcsCyGju3jXvTOyXSI,950 -iniconfig-1.0.0.dist-info/top_level.txt,sha256=7KfM0fugdlToj9UW7enKXk2HYALQD8qHiyKtjhSzgN8,10 -iniconfig-1.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -__pycache__/iniconfig.cpython-35.pyc,, diff --git a/py/_vendored_packages/iniconfig-1.0.0.dist-info/WHEEL b/py/_vendored_packages/iniconfig-1.0.0.dist-info/WHEEL deleted file mode 100644 index 15b96c99..00000000 --- a/py/_vendored_packages/iniconfig-1.0.0.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.30.0.a0) -Root-Is-Purelib: true -Tag: cp35-none-any - diff --git a/py/_vendored_packages/iniconfig-1.0.0.dist-info/metadata.json b/py/_vendored_packages/iniconfig-1.0.0.dist-info/metadata.json deleted file mode 100644 index 084daa6c..00000000 --- a/py/_vendored_packages/iniconfig-1.0.0.dist-info/metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Topic :: Software Development :: Libraries", "Topic :: Utilities", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3"], "extensions": {"python.details": {"contacts": [{"email": "opensource@ronnypfannschmidt.de, holger.krekel@gmail.com", "name": "Ronny Pfannschmidt, Holger Krekel", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://github.com/RonnyPfannschmidt/iniconfig"}}}, "generator": "bdist_wheel (0.30.0.a0)", "license": "MIT License", "metadata_version": "2.0", "name": "iniconfig", "platform": "unix", "summary": "iniconfig: brain-dead simple config-ini parsing", "version": "1.0.0"} \ No newline at end of file diff --git a/py/_vendored_packages/iniconfig-1.0.0.dist-info/INSTALLER b/py/_vendored_packages/iniconfig-1.1.1.dist-info/INSTALLER similarity index 100% rename from py/_vendored_packages/iniconfig-1.0.0.dist-info/INSTALLER rename to py/_vendored_packages/iniconfig-1.1.1.dist-info/INSTALLER diff --git a/py/_vendored_packages/iniconfig-1.1.1.dist-info/LICENSE b/py/_vendored_packages/iniconfig-1.1.1.dist-info/LICENSE new file mode 100644 index 00000000..31ecdfb1 --- /dev/null +++ b/py/_vendored_packages/iniconfig-1.1.1.dist-info/LICENSE @@ -0,0 +1,19 @@ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + diff --git a/py/_vendored_packages/iniconfig-1.0.0.dist-info/METADATA b/py/_vendored_packages/iniconfig-1.1.1.dist-info/METADATA similarity index 96% rename from py/_vendored_packages/iniconfig-1.0.0.dist-info/METADATA rename to py/_vendored_packages/iniconfig-1.1.1.dist-info/METADATA index 79ea62dc..c078a753 100644 --- a/py/_vendored_packages/iniconfig-1.0.0.dist-info/METADATA +++ b/py/_vendored_packages/iniconfig-1.1.1.dist-info/METADATA @@ -1,6 +1,6 @@ -Metadata-Version: 2.0 +Metadata-Version: 2.1 Name: iniconfig -Version: 1.0.0 +Version: 1.1.1 Summary: iniconfig: brain-dead simple config-ini parsing Home-page: http://github.com/RonnyPfannschmidt/iniconfig Author: Ronny Pfannschmidt, Holger Krekel @@ -39,7 +39,7 @@ having a unique set of features: If you encounter issues or have feature wishes please report them to: - http://github.org/RonnyPfannschmidt/iniconfig/issues + http://github.com/RonnyPfannschmidt/iniconfig/issues Basic Example =================================== diff --git a/py/_vendored_packages/iniconfig-1.1.1.dist-info/RECORD b/py/_vendored_packages/iniconfig-1.1.1.dist-info/RECORD new file mode 100644 index 00000000..16823333 --- /dev/null +++ b/py/_vendored_packages/iniconfig-1.1.1.dist-info/RECORD @@ -0,0 +1,11 @@ +iniconfig-1.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +iniconfig-1.1.1.dist-info/LICENSE,sha256=KvaAw570k_uCgwNW0dPfGstaBgM8ui3sehniHKp3qGY,1061 +iniconfig-1.1.1.dist-info/METADATA,sha256=_4-oFKpRXuZv5rzepScpXRwhq6DzqsgbnA5ZpgMUMcs,2405 +iniconfig-1.1.1.dist-info/RECORD,, +iniconfig-1.1.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +iniconfig-1.1.1.dist-info/WHEEL,sha256=ADKeyaGyKF5DwBNE0sRE5pvW-bSkFMJfBuhzZ3rceP4,110 +iniconfig-1.1.1.dist-info/top_level.txt,sha256=7KfM0fugdlToj9UW7enKXk2HYALQD8qHiyKtjhSzgN8,10 +iniconfig/__init__.py,sha256=-pBe5AF_6aAwo1CxJQ8i_zJq6ejc6IxHta7qk2tNJhY,5208 +iniconfig/__init__.pyi,sha256=-4KOctzq28ohRmTZsqlH6aylyFqsNKxYqtk1dteypi4,1205 +iniconfig/__pycache__/__init__.cpython-39.pyc,, +iniconfig/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/py/_vendored_packages/iniconfig-1.1.1.dist-info/REQUESTED b/py/_vendored_packages/iniconfig-1.1.1.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/py/_vendored_packages/iniconfig-1.1.1.dist-info/WHEEL b/py/_vendored_packages/iniconfig-1.1.1.dist-info/WHEEL new file mode 100644 index 00000000..6d38aa06 --- /dev/null +++ b/py/_vendored_packages/iniconfig-1.1.1.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.35.1) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/py/_vendored_packages/iniconfig-1.0.0.dist-info/top_level.txt b/py/_vendored_packages/iniconfig-1.1.1.dist-info/top_level.txt similarity index 100% rename from py/_vendored_packages/iniconfig-1.0.0.dist-info/top_level.txt rename to py/_vendored_packages/iniconfig-1.1.1.dist-info/top_level.txt diff --git a/py/_vendored_packages/iniconfig.py b/py/_vendored_packages/iniconfig/__init__.py similarity index 100% rename from py/_vendored_packages/iniconfig.py rename to py/_vendored_packages/iniconfig/__init__.py diff --git a/py/_vendored_packages/iniconfig/__init__.pyi b/py/_vendored_packages/iniconfig/__init__.pyi new file mode 100644 index 00000000..b6284bec --- /dev/null +++ b/py/_vendored_packages/iniconfig/__init__.pyi @@ -0,0 +1,31 @@ +from typing import Callable, Iterator, Mapping, Optional, Tuple, TypeVar, Union +from typing_extensions import Final + +_D = TypeVar('_D') +_T = TypeVar('_T') + +class ParseError(Exception): + # Private __init__. + path: Final[str] + lineno: Final[int] + msg: Final[str] + +class SectionWrapper: + # Private __init__. + config: Final[IniConfig] + name: Final[str] + def __getitem__(self, key: str) -> str: ... + def __iter__(self) -> Iterator[str]: ... + def get(self, key: str, default: _D = ..., convert: Callable[[str], _T] = ...) -> Union[_T, _D]: ... + def items(self) -> Iterator[Tuple[str, str]]: ... + def lineof(self, name: str) -> Optional[int]: ... + +class IniConfig: + path: Final[str] + sections: Final[Mapping[str, Mapping[str, str]]] + def __init__(self, path: str, data: Optional[str] = None): ... + def __contains__(self, arg: str) -> bool: ... + def __getitem__(self, name: str) -> SectionWrapper: ... + def __iter__(self) -> Iterator[SectionWrapper]: ... + def get(self, section: str, name: str, default: _D = ..., convert: Callable[[str], _T] = ...) -> Union[_T, _D]: ... + def lineof(self, section: str, name: Optional[str] = ...) -> Optional[int]: ... diff --git a/py/_vendored_packages/iniconfig/py.typed b/py/_vendored_packages/iniconfig/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/py/error.pyi b/py/error.pyi new file mode 100644 index 00000000..034eba60 --- /dev/null +++ b/py/error.pyi @@ -0,0 +1,129 @@ +from typing import Any, Callable, TypeVar + +_T = TypeVar('_T') + +def checked_call(func: Callable[..., _T], *args: Any, **kwargs: Any) -> _T: ... +class Error(EnvironmentError): ... +class EPERM(Error): ... +class ENOENT(Error): ... +class ESRCH(Error): ... +class EINTR(Error): ... +class EIO(Error): ... +class ENXIO(Error): ... +class E2BIG(Error): ... +class ENOEXEC(Error): ... +class EBADF(Error): ... +class ECHILD(Error): ... +class EAGAIN(Error): ... +class ENOMEM(Error): ... +class EACCES(Error): ... +class EFAULT(Error): ... +class ENOTBLK(Error): ... +class EBUSY(Error): ... +class EEXIST(Error): ... +class EXDEV(Error): ... +class ENODEV(Error): ... +class ENOTDIR(Error): ... +class EISDIR(Error): ... +class EINVAL(Error): ... +class ENFILE(Error): ... +class EMFILE(Error): ... +class ENOTTY(Error): ... +class ETXTBSY(Error): ... +class EFBIG(Error): ... +class ENOSPC(Error): ... +class ESPIPE(Error): ... +class EROFS(Error): ... +class EMLINK(Error): ... +class EPIPE(Error): ... +class EDOM(Error): ... +class ERANGE(Error): ... +class EDEADLCK(Error): ... +class ENAMETOOLONG(Error): ... +class ENOLCK(Error): ... +class ENOSYS(Error): ... +class ENOTEMPTY(Error): ... +class ELOOP(Error): ... +class EWOULDBLOCK(Error): ... +class ENOMSG(Error): ... +class EIDRM(Error): ... +class ECHRNG(Error): ... +class EL2NSYNC(Error): ... +class EL3HLT(Error): ... +class EL3RST(Error): ... +class ELNRNG(Error): ... +class EUNATCH(Error): ... +class ENOCSI(Error): ... +class EL2HLT(Error): ... +class EBADE(Error): ... +class EBADR(Error): ... +class EXFULL(Error): ... +class ENOANO(Error): ... +class EBADRQC(Error): ... +class EBADSLT(Error): ... +class EDEADLOCK(Error): ... +class EBFONT(Error): ... +class ENOSTR(Error): ... +class ENODATA(Error): ... +class ETIME(Error): ... +class ENOSR(Error): ... +class ENONET(Error): ... +class ENOPKG(Error): ... +class EREMOTE(Error): ... +class ENOLINK(Error): ... +class EADV(Error): ... +class ESRMNT(Error): ... +class ECOMM(Error): ... +class EPROTO(Error): ... +class EMULTIHOP(Error): ... +class EDOTDOT(Error): ... +class EBADMSG(Error): ... +class EOVERFLOW(Error): ... +class ENOTUNIQ(Error): ... +class EBADFD(Error): ... +class EREMCHG(Error): ... +class ELIBACC(Error): ... +class ELIBBAD(Error): ... +class ELIBSCN(Error): ... +class ELIBMAX(Error): ... +class ELIBEXEC(Error): ... +class EILSEQ(Error): ... +class ERESTART(Error): ... +class ESTRPIPE(Error): ... +class EUSERS(Error): ... +class ENOTSOCK(Error): ... +class EDESTADDRREQ(Error): ... +class EMSGSIZE(Error): ... +class EPROTOTYPE(Error): ... +class ENOPROTOOPT(Error): ... +class EPROTONOSUPPORT(Error): ... +class ESOCKTNOSUPPORT(Error): ... +class ENOTSUP(Error): ... +class EOPNOTSUPP(Error): ... +class EPFNOSUPPORT(Error): ... +class EAFNOSUPPORT(Error): ... +class EADDRINUSE(Error): ... +class EADDRNOTAVAIL(Error): ... +class ENETDOWN(Error): ... +class ENETUNREACH(Error): ... +class ENETRESET(Error): ... +class ECONNABORTED(Error): ... +class ECONNRESET(Error): ... +class ENOBUFS(Error): ... +class EISCONN(Error): ... +class ENOTCONN(Error): ... +class ESHUTDOWN(Error): ... +class ETOOMANYREFS(Error): ... +class ETIMEDOUT(Error): ... +class ECONNREFUSED(Error): ... +class EHOSTDOWN(Error): ... +class EHOSTUNREACH(Error): ... +class EALREADY(Error): ... +class EINPROGRESS(Error): ... +class ESTALE(Error): ... +class EUCLEAN(Error): ... +class ENOTNAM(Error): ... +class ENAVAIL(Error): ... +class EISNAM(Error): ... +class EREMOTEIO(Error): ... +class EDQUOT(Error): ... diff --git a/py/iniconfig.pyi b/py/iniconfig.pyi new file mode 100644 index 00000000..b6284bec --- /dev/null +++ b/py/iniconfig.pyi @@ -0,0 +1,31 @@ +from typing import Callable, Iterator, Mapping, Optional, Tuple, TypeVar, Union +from typing_extensions import Final + +_D = TypeVar('_D') +_T = TypeVar('_T') + +class ParseError(Exception): + # Private __init__. + path: Final[str] + lineno: Final[int] + msg: Final[str] + +class SectionWrapper: + # Private __init__. + config: Final[IniConfig] + name: Final[str] + def __getitem__(self, key: str) -> str: ... + def __iter__(self) -> Iterator[str]: ... + def get(self, key: str, default: _D = ..., convert: Callable[[str], _T] = ...) -> Union[_T, _D]: ... + def items(self) -> Iterator[Tuple[str, str]]: ... + def lineof(self, name: str) -> Optional[int]: ... + +class IniConfig: + path: Final[str] + sections: Final[Mapping[str, Mapping[str, str]]] + def __init__(self, path: str, data: Optional[str] = None): ... + def __contains__(self, arg: str) -> bool: ... + def __getitem__(self, name: str) -> SectionWrapper: ... + def __iter__(self) -> Iterator[SectionWrapper]: ... + def get(self, section: str, name: str, default: _D = ..., convert: Callable[[str], _T] = ...) -> Union[_T, _D]: ... + def lineof(self, section: str, name: Optional[str] = ...) -> Optional[int]: ... diff --git a/py/io.pyi b/py/io.pyi new file mode 100644 index 00000000..d377e240 --- /dev/null +++ b/py/io.pyi @@ -0,0 +1,130 @@ +from io import StringIO as TextIO +from io import BytesIO as BytesIO +from typing import Any, AnyStr, Callable, Generic, IO, List, Optional, Text, Tuple, TypeVar, Union, overload +from typing_extensions import Final +import sys + +_T = TypeVar("_T") + +class FDCapture(Generic[AnyStr]): + def __init__(self, targetfd: int, tmpfile: Optional[IO[AnyStr]] = ..., now: bool = ..., patchsys: bool = ...) -> None: ... + def start(self) -> None: ... + def done(self) -> IO[AnyStr]: ... + def writeorg(self, data: AnyStr) -> None: ... + +class StdCaptureFD: + def __init__( + self, + out: Union[bool, IO[str]] = ..., + err: Union[bool, IO[str]] = ..., + mixed: bool = ..., + in_: bool = ..., + patchsys: bool = ..., + now: bool = ..., + ) -> None: ... + @classmethod + def call(cls, func: Callable[..., _T], *args: Any, **kwargs: Any) -> Tuple[_T, str, str]: ... + def reset(self) -> Tuple[str, str]: ... + def suspend(self) -> Tuple[str, str]: ... + def startall(self) -> None: ... + def resume(self) -> None: ... + def done(self, save: bool = ...) -> Tuple[IO[str], IO[str]]: ... + def readouterr(self) -> Tuple[str, str]: ... + +class StdCapture: + def __init__( + self, + out: Union[bool, IO[str]] = ..., + err: Union[bool, IO[str]] = ..., + in_: bool = ..., + mixed: bool = ..., + now: bool = ..., + ) -> None: ... + @classmethod + def call(cls, func: Callable[..., _T], *args: Any, **kwargs: Any) -> Tuple[_T, str, str]: ... + def reset(self) -> Tuple[str, str]: ... + def suspend(self) -> Tuple[str, str]: ... + def startall(self) -> None: ... + def resume(self) -> None: ... + def done(self, save: bool = ...) -> Tuple[IO[str], IO[str]]: ... + def readouterr(self) -> Tuple[IO[str], IO[str]]: ... + +# XXX: The type here is not exactly right. If f is IO[bytes] and +# encoding is not None, returns some weird hybrid, not exactly IO[bytes]. +def dupfile( + f: IO[AnyStr], + mode: Optional[str] = ..., + buffering: int = ..., + raising: bool = ..., + encoding: Optional[str] = ..., +) -> IO[AnyStr]: ... +def get_terminal_width() -> int: ... +def ansi_print( + text: Union[str, Text], + esc: Union[Union[str, Text], Tuple[Union[str, Text], ...]], + file: Optional[IO[Any]] = ..., + newline: bool = ..., + flush: bool = ..., +) -> None: ... +def saferepr(obj, maxsize: int = ...) -> str: ... + +class TerminalWriter: + stringio: TextIO + encoding: Final[str] + hasmarkup: bool + def __init__(self, file: Optional[IO[str]] = ..., stringio: bool = ..., encoding: Optional[str] = ...) -> None: ... + @property + def fullwidth(self) -> int: ... + @fullwidth.setter + def fullwidth(self, value: int) -> None: ... + @property + def chars_on_current_line(self) -> int: ... + @property + def width_of_current_line(self) -> int: ... + def markup( + self, + text: str, + *, + black: int = ..., red: int = ..., green: int = ..., yellow: int = ..., blue: int = ..., purple: int = ..., + cyan: int = ..., white: int = ..., Black: int = ..., Red: int = ..., Green: int = ..., Yellow: int = ..., + Blue: int = ..., Purple: int = ..., Cyan: int = ..., White: int = ..., bold: int = ..., light: int = ..., + blink: int = ..., invert: int = ..., + ) -> str: ... + def sep( + self, + sepchar: str, + title: Optional[str] = ..., + fullwidth: Optional[int] = ..., + *, + black: int = ..., red: int = ..., green: int = ..., yellow: int = ..., blue: int = ..., purple: int = ..., + cyan: int = ..., white: int = ..., Black: int = ..., Red: int = ..., Green: int = ..., Yellow: int = ..., + Blue: int = ..., Purple: int = ..., Cyan: int = ..., White: int = ..., bold: int = ..., light: int = ..., + blink: int = ..., invert: int = ..., + ) -> None: ... + def write( + self, + msg: str, + *, + black: int = ..., red: int = ..., green: int = ..., yellow: int = ..., blue: int = ..., purple: int = ..., + cyan: int = ..., white: int = ..., Black: int = ..., Red: int = ..., Green: int = ..., Yellow: int = ..., + Blue: int = ..., Purple: int = ..., Cyan: int = ..., White: int = ..., bold: int = ..., light: int = ..., + blink: int = ..., invert: int = ..., + ) -> None: ... + def line( + self, + s: str = ..., + *, + black: int = ..., red: int = ..., green: int = ..., yellow: int = ..., blue: int = ..., purple: int = ..., + cyan: int = ..., white: int = ..., Black: int = ..., Red: int = ..., Green: int = ..., Yellow: int = ..., + Blue: int = ..., Purple: int = ..., Cyan: int = ..., White: int = ..., bold: int = ..., light: int = ..., + blink: int = ..., invert: int = ..., + ) -> None: ... + def reline( + self, + line: str, + *, + black: int = ..., red: int = ..., green: int = ..., yellow: int = ..., blue: int = ..., purple: int = ..., + cyan: int = ..., white: int = ..., Black: int = ..., Red: int = ..., Green: int = ..., Yellow: int = ..., + Blue: int = ..., Purple: int = ..., Cyan: int = ..., White: int = ..., bold: int = ..., light: int = ..., + blink: int = ..., invert: int = ..., + ) -> None: ... diff --git a/py/path.pyi b/py/path.pyi new file mode 100644 index 00000000..1ddab960 --- /dev/null +++ b/py/path.pyi @@ -0,0 +1,197 @@ +from typing import Any, AnyStr, Callable, ContextManager, Generic, IO, Iterable, Iterator, List, Optional, Text, Type, Union +from typing_extensions import Final, Literal +import os +import sys + +class _FNMatcher(Generic[AnyStr]): + pattern: AnyStr = ... + def __init__(self, pattern: AnyStr) -> None: ... + def __call__(self, path: local) -> bool: ... + +class _Stat: + path: Final[local] = ... + mode: Final[int] + ino: Final[int] + dev: Final[int] + nlink: Final[int] + uid: Final[int] + gid: Final[int] + size: Final[int] + atime: Final[float] + mtime: Final[float] + ctime: Final[float] + atime_ns: Final[int] + mtime_ns: Final[int] + ctime_ns: Final[int] + if sys.version_info >= (3, 8) and sys.platform == "win32": + reparse_tag: Final[int] + blocks: Final[int] + blksize: Final[int] + rdev: Final[int] + flags: Final[int] + gen: Final[int] + birthtime: Final[int] + rsize: Final[int] + creator: Final[int] + type: Final[int] + if sys.platform != 'win32': + @property + def owner(self) -> str: ... + @property + def group(self) -> str: ... + def isdir(self) -> bool: ... + def isfile(self) -> bool: ... + def islink(self) -> bool: ... + + +if sys.version_info >= (3, 6): + _PathLike = os.PathLike +else: + class _PathLike(Generic[AnyStr]): + def __fspath__(self) -> AnyStr: ... +_PathType = Union[bytes, Text, _PathLike[str], _PathLike[bytes], local] + +class local(_PathLike[str]): + class ImportMismatchError(ImportError): ... + + sep: Final[str] + strpath: Final[str] + + def __init__(self, path: _PathType = ..., expanduser: bool = ...) -> None: ... + def __hash__(self) -> int: ... + def __eq__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __lt__(self, other: object) -> bool: ... + def __gt__(self, other: object) -> bool: ... + def __add__(self, other: object) -> local: ... + def __cmp__(self, other: object) -> int: ... + def __div__(self, other: _PathType) -> local: ... + def __truediv__(self, other: _PathType) -> local: ... + def __fspath__(self) -> str: ... + + @classmethod + def get_temproot(cls) -> local: ... + @classmethod + def make_numbered_dir( + cls, + prefix: str = ..., + rootdir: Optional[local] = ..., + keep: Optional[int] = ..., + lock_timeout: int = ..., + ) -> local: ... + @classmethod + def mkdtemp(cls, rootdir: Optional[local] = ...) -> local: ... + @classmethod + def sysfind( + cls, + name: _PathType, + checker: Optional[Callable[[local], bool]] = ..., + paths: Optional[Iterable[_PathType]] = ..., + ) -> Optional[local]: ... + + @property + def basename(self) -> str: ... + @property + def dirname(self) -> str: ... + @property + def purebasename(self) -> str: ... + @property + def ext(self) -> str: ... + + def as_cwd(self) -> ContextManager[Optional[local]]: ... + def atime(self) -> float: ... + def bestrelpath(self, dest: local) -> str: ... + def chdir(self) -> local: ... + def check( + self, + *, + basename: int = ..., notbasename: int = ..., + basestarts: int = ..., notbasestarts: int = ..., + dir: int = ..., notdir: int = ..., + dotfile: int = ..., notdotfile: int = ..., + endswith: int = ..., notendswith: int = ..., + exists: int = ..., notexists: int = ..., + ext: int = ..., notext: int = ..., + file: int = ..., notfile: int = ..., + fnmatch: int = ..., notfnmatch: int = ..., + link: int = ..., notlink: int = ..., + relto: int = ..., notrelto: int = ..., + ) -> bool: ... + def chmod(self, mode: int, rec: Union[int, str, Text, Callable[[local], bool]] = ...) -> None: ... + if sys.platform != 'win32': + def chown(self, user: Union[int, str], group: Union[int, str], rec: int = ...) -> None: ... + def common(self, other: local) -> Optional[local]: ... + def computehash(self, hashtype: str = ..., chunksize: int = ...) -> str: ... + def copy(self, target: local, mode: bool = ..., stat: bool = ...) -> None: ... + def dirpath(self, *args: _PathType, abs: int = ...) -> local: ... + def dump(self, obj: Any, bin: Optional[int] = ...) -> None: ... + def ensure(self, *args: _PathType, dir: int = ...) -> local: ... + def ensure_dir(self, *args: _PathType) -> local: ... + def exists(self) -> bool: ... + def fnmatch(self, pattern: str): _FNMatcher + def isdir(self) -> bool: ... + def isfile(self) -> bool: ... + def islink(self) -> bool: ... + def join(self, *args: _PathType, abs: int = ...) -> local: ... + def listdir( + self, + fil: Optional[Union[str, Text, Callable[[local], bool]]] = ..., + sort: Optional[bool] = ..., + ) -> List[local]: ... + def load(self) -> Any: ... + def lstat(self) -> _Stat: ... + def mkdir(self, *args: _PathType) -> local: ... + if sys.platform != 'win32': + def mklinkto(self, oldname: Union[str, local]) -> None: ... + def mksymlinkto(self, value: local, absolute: int = ...) -> None: ... + def move(self, target: local) -> None: ... + def mtime(self) -> float: ... + def new( + self, + *, + drive: str = ..., + dirname: str = ..., + basename: str = ..., + purebasename: str = ..., + ext: str = ..., + ) -> local: ... + def open(self, mode: str = ..., ensure: bool = ..., encoding: Optional[str] = ...) -> IO[Any]: ... + def parts(self, reverse: bool = ...) -> List[local]: ... + def pyimport( + self, + modname: Optional[str] = ..., + ensuresyspath: Union[bool, Literal["append", "importlib"]] = ..., + ) -> Any: ... + def pypkgpath(self) -> Optional[local]: ... + def read(self, mode: str = ...) -> Union[Text, bytes]: ... + def read_binary(self) -> bytes: ... + def read_text(self, encoding: str) -> Text: ... + def readlines(self, cr: int = ...) -> List[str]: ... + if sys.platform != 'win32': + def readlink(self) -> str: ... + def realpath(self) -> local: ... + def relto(self, relpath: Union[str, local]) -> str: ... + def remove(self, rec: int = ..., ignore_errors: bool = ...) -> None: ... + def rename(self, target: _PathType) -> None: ... + def samefile(self, other: _PathType) -> bool: ... + def setmtime(self, mtime: Optional[float] = ...) -> None: ... + def size(self) -> int: ... + def stat(self, raising: bool = ...) -> _Stat: ... + def sysexec(self, *argv: Any, **popen_opts: Any) -> Text: ... + def visit( + self, + fil: Optional[Union[str, Text, Callable[[local], bool]]] = ..., + rec: Optional[Union[Literal[1, True], str, Text, Callable[[local], bool]]] = ..., + ignore: Type[Exception] = ..., + bf: bool = ..., + sort: bool = ..., + ) -> Iterator[local]: ... + def write(self, data: Any, mode: str = ..., ensure: bool = ...) -> None: ... + def write_binary(self, data: bytes, ensure: bool = ...) -> None: ... + def write_text(self, data: Union[str, Text], encoding: str, ensure: bool = ...) -> None: ... + + +# Untyped types below here. +svnwc: Any +svnurl: Any +SvnAuth: Any diff --git a/py/py.typed b/py/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/py/xml.pyi b/py/xml.pyi new file mode 100644 index 00000000..9c44480a --- /dev/null +++ b/py/xml.pyi @@ -0,0 +1,25 @@ +from typing import ClassVar, Generic, Iterable, Text, Type, Union +from typing_extensions import Final + +class raw: + uniobj: Final[Text] + def __init__(self, uniobj: Text) -> None: ... + +class _NamespaceMetaclass(type): + def __getattr__(self, name: str) -> Type[Tag]: ... + +class Namespace(metaclass=_NamespaceMetaclass): ... + +class Tag(list): + class Attr: + def __getattr__(self, attr: str) -> Text: ... + attr: Final[Attr] + def __init__(self, *args: Union[Text, raw, Tag, Iterable[Tag]], **kwargs: Union[Text, raw]) -> None: ... + def unicode(self, indent: int = ...) -> Text: ... + +class html(Namespace): + class Style: + def __init__(self, **kw: Union[str, Text]) -> None: ... + style: ClassVar[Style] + +def escape(ustring: Union[str, Text]) -> Text: ... diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..e386ea0b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools", + "setuptools_scm[toml]", +] +build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index a0d723c3..5948ef00 100644 --- a/setup.py +++ b/setup.py @@ -7,11 +7,11 @@ def main(): description='library with cross-python path, ini-parsing, io, code, log facilities', long_description=open('README.rst').read(), use_scm_version={"write_to": "py/_version.py"}, - setup_requires=["setuptools-scm"], - url='http://py.readthedocs.io/', + setup_requires=["setuptools_scm"], + url='https://py.readthedocs.io/', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', author='holger krekel, Ronny Pfannschmidt, Benjamin Peterson and others', author_email='pytest-dev@python.org', classifiers=['Development Status :: 6 - Mature', @@ -27,14 +27,21 @@ def main(): 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], packages=find_packages(exclude=['tasks', 'testing']), + include_package_data=True, zip_safe=False, + package_data={ + "": ["py.typed"], + }, ) if __name__ == '__main__': diff --git a/tasks/__init__.py b/tasks/__init__.py index 5d74b649..e69de29b 100644 --- a/tasks/__init__.py +++ b/tasks/__init__.py @@ -1,12 +0,0 @@ -""" -Invoke tasks to help with pytest development and release process. -""" - -import invoke - -from . import vendoring - - -ns = invoke.Collection( - vendoring -) diff --git a/tasks/vendoring.py b/tasks/vendoring.py index fbc171bc..3c7d6015 100644 --- a/tasks/vendoring.py +++ b/tasks/vendoring.py @@ -1,23 +1,41 @@ from __future__ import absolute_import, print_function -import py -import invoke +import os.path +import shutil +import subprocess +import sys -VENDOR_TARGET = py.path.local("py/_vendored_packages") -GOOD_FILES = 'README.md', '__init__.py' +VENDOR_TARGET = "py/_vendored_packages" +GOOD_FILES = ('README.md', '__init__.py') -@invoke.task() -def remove_libs(ctx): + +def remove_libs(): print("removing vendored libs") - for path in VENDOR_TARGET.listdir(): - if path.basename not in GOOD_FILES: + for filename in os.listdir(VENDOR_TARGET): + if filename not in GOOD_FILES: + path = os.path.join(VENDOR_TARGET, filename) print(" ", path) - path.remove() + if os.path.isfile(path): + os.remove(path) + else: + shutil.rmtree(path) + -@invoke.task(pre=[remove_libs]) -def update_libs(ctx): +def update_libs(): print("installing libs") - ctx.run("pip install -t {target} apipkg iniconfig".format(target=VENDOR_TARGET)) - ctx.run("git add {target}".format(target=VENDOR_TARGET)) + subprocess.check_call(( + sys.executable, '-m', 'pip', 'install', + '--target', VENDOR_TARGET, 'apipkg', 'iniconfig', + )) + subprocess.check_call(('git', 'add', VENDOR_TARGET)) print("Please commit to finish the update after running the tests:") print() print(' git commit -am "Updated vendored libs"') + + +def main(): + remove_libs() + update_libs() + + +if __name__ == '__main__': + exit(main()) diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 3492761a..ca9a4227 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -103,7 +103,7 @@ def test_source_strip_multiline(): def test_syntaxerror_rerepresentation(): ex = py.test.raises(SyntaxError, py.code.compile, 'xyz xyz') assert ex.value.lineno == 1 - assert ex.value.offset in (4,7) # XXX pypy/jython versus cpython? + assert ex.value.offset in (5, 7) # pypy/cpython difference assert ex.value.text.strip(), 'x x' def test_isparseable(): @@ -456,7 +456,9 @@ class A(object): class B: pass B.__name__ = "B2" - assert getfslineno(B)[1] == -1 + # TODO: On CPython 3.9 this actually returns the line, + # should it? + # assert getfslineno(B)[1] == -1 def test_code_of_object_instance_with_call(): class A: @@ -510,11 +512,17 @@ def test_comments(): comment 4 """ ''' - for line in range(2,6): - assert str(getstatement(line, source)) == ' x = 1' - for line in range(6,10): - assert str(getstatement(line, source)) == ' assert False' - assert str(getstatement(10, source)) == '"""' + for line in range(2, 6): + assert str(getstatement(line, source)) == " x = 1" + if sys.version_info >= (3, 8) or hasattr(sys, "pypy_version_info"): + tqs_start = 8 + else: + tqs_start = 10 + assert str(getstatement(10, source)) == '"""' + for line in range(6, tqs_start): + assert str(getstatement(line, source)) == " assert False" + for line in range(tqs_start, 10): + assert str(getstatement(line, source)) == '"""\ncomment 4\n"""' def test_comment_in_statement(): source = '''test(foo=1, diff --git a/testing/io_/test_terminalwriter.py b/testing/io_/test_terminalwriter.py index 7e9ebf40..44b4f1dd 100644 --- a/testing/io_/test_terminalwriter.py +++ b/testing/io_/test_terminalwriter.py @@ -1,3 +1,4 @@ +from collections import namedtuple import py import os, sys @@ -10,16 +11,22 @@ def test_get_terminal_width(): assert x == terminalwriter.get_terminal_width def test_getdimensions(monkeypatch): - fcntl = py.test.importorskip("fcntl") - import struct - l = [] - monkeypatch.setattr(fcntl, 'ioctl', lambda *args: l.append(args)) - try: - terminalwriter._getdimensions() - except (TypeError, struct.error): - pass - assert len(l) == 1 - assert l[0][0] == 1 + if sys.version_info >= (3, 3): + import shutil + Size = namedtuple('Size', 'lines columns') + monkeypatch.setattr(shutil, 'get_terminal_size', lambda: Size(60, 100)) + assert terminalwriter._getdimensions() == (60, 100) + else: + fcntl = py.test.importorskip("fcntl") + import struct + l = [] + monkeypatch.setattr(fcntl, 'ioctl', lambda *args: l.append(args)) + try: + terminalwriter._getdimensions() + except (TypeError, struct.error): + pass + assert len(l) == 1 + assert l[0][0] == 1 def test_terminal_width_COLUMNS(monkeypatch): """ Dummy test for get_terminal_width @@ -158,6 +165,12 @@ def test_sep_with_title(self, tw): assert len(l) == 1 assert l[0] == "-" * 26 + " hello " + "-" * (27-win32) + "\n" + def test_sep_longer_than_width(self, tw): + tw.sep('-', 'a' * 10, fullwidth=5) + line, = tw.getlines() + # even though the string is wider than the line, still have a separator + assert line == '- aaaaaaaaaa -\n' + @py.test.mark.skipif("sys.platform == 'win32'") def test__escaped(self, tw): text2 = tw._escaped("hello", (31)) @@ -290,3 +303,39 @@ def test_should_do_markup_PY_COLORS_eq_0(monkeypatch): tw.line("hello", bold=True) s = f.getvalue() assert s == "hello\n" + +def test_should_do_markup(monkeypatch): + monkeypatch.delenv("PY_COLORS", raising=False) + monkeypatch.delenv("NO_COLOR", raising=False) + + should_do_markup = terminalwriter.should_do_markup + + f = py.io.TextIO() + f.isatty = lambda: True + + assert should_do_markup(f) is True + + # NO_COLOR without PY_COLORS. + monkeypatch.setenv("NO_COLOR", "0") + assert should_do_markup(f) is False + monkeypatch.setenv("NO_COLOR", "1") + assert should_do_markup(f) is False + monkeypatch.setenv("NO_COLOR", "any") + assert should_do_markup(f) is False + + # PY_COLORS overrides NO_COLOR ("0" and "1" only). + monkeypatch.setenv("PY_COLORS", "1") + assert should_do_markup(f) is True + monkeypatch.setenv("PY_COLORS", "0") + assert should_do_markup(f) is False + # Uses NO_COLOR. + monkeypatch.setenv("PY_COLORS", "any") + assert should_do_markup(f) is False + monkeypatch.delenv("NO_COLOR") + assert should_do_markup(f) is True + + # Back to defaults. + monkeypatch.delenv("PY_COLORS") + assert should_do_markup(f) is True + f.isatty = lambda: False + assert should_do_markup(f) is False diff --git a/testing/io_/test_terminalwriter_linewidth.py b/testing/io_/test_terminalwriter_linewidth.py new file mode 100644 index 00000000..e6d84fbf --- /dev/null +++ b/testing/io_/test_terminalwriter_linewidth.py @@ -0,0 +1,56 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from py._io.terminalwriter import TerminalWriter + + +def test_terminal_writer_line_width_init(): + tw = TerminalWriter() + assert tw.chars_on_current_line == 0 + assert tw.width_of_current_line == 0 + + +def test_terminal_writer_line_width_update(): + tw = TerminalWriter() + tw.write('hello world') + assert tw.chars_on_current_line == 11 + assert tw.width_of_current_line == 11 + + +def test_terminal_writer_line_width_update_with_newline(): + tw = TerminalWriter() + tw.write('hello\nworld') + assert tw.chars_on_current_line == 5 + assert tw.width_of_current_line == 5 + + +def test_terminal_writer_line_width_update_with_wide_text(): + tw = TerminalWriter() + tw.write('乇乂ㄒ尺卂 ㄒ卄丨匚匚') + assert tw.chars_on_current_line == 11 + assert tw.width_of_current_line == 21 # 5*2 + 1 + 5*2 + + +def test_terminal_writer_line_width_update_with_wide_bytes(): + tw = TerminalWriter() + tw.write('乇乂ㄒ尺卂 ㄒ卄丨匚匚'.encode('utf-8')) + assert tw.chars_on_current_line == 11 + assert tw.width_of_current_line == 21 + + +def test_terminal_writer_line_width_composed(): + tw = TerminalWriter() + text = 'café food' + assert len(text) == 9 + tw.write(text) + assert tw.chars_on_current_line == 9 + assert tw.width_of_current_line == 9 + + +def test_terminal_writer_line_width_combining(): + tw = TerminalWriter() + text = 'café food' + assert len(text) == 10 + tw.write(text) + assert tw.chars_on_current_line == 10 + assert tw.width_of_current_line == 9 diff --git a/testing/log/test_warning.py b/testing/log/test_warning.py index a460c319..36efec91 100644 --- a/testing/log/test_warning.py +++ b/testing/log/test_warning.py @@ -8,8 +8,7 @@ mypath = py.path.local(__file__).new(ext=".py") -win = sys.platform.startswith('win') -pytestmark = pytest.mark.skipif(win and LooseVersion(pytest.__version__) >= LooseVersion('3.1'), +pytestmark = pytest.mark.skipif(LooseVersion(pytest.__version__) >= LooseVersion('3.1'), reason='apiwarn is not compatible with pytest >= 3.1 (#162)') diff --git a/testing/path/test_local.py b/testing/path/test_local.py index ee4b9bde..1b9a7923 100644 --- a/testing/path/test_local.py +++ b/testing/path/test_local.py @@ -125,6 +125,19 @@ def test_chdir_gone(self, path1): assert path1.chdir() is None assert os.getcwd() == str(path1) + with pytest.raises(py.error.ENOENT): + with p.as_cwd(): + raise NotImplementedError + + @skiponwin32 + def test_chdir_gone_in_as_cwd(self, path1): + p = path1.ensure("dir_to_be_removed", dir=1) + p.chdir() + p.remove() + + with path1.as_cwd() as old: + assert old is None + def test_as_cwd(self, path1): dir = path1.ensure("subdir", dir=1) old = py.path.local() @@ -151,6 +164,17 @@ def test_tilde_expansion(self, monkeypatch, tmpdir): p = py.path.local("~", expanduser=True) assert p == os.path.expanduser("~") + @pytest.mark.skipif( + not sys.platform.startswith("win32"), reason="case insensitive only on windows" + ) + def test_eq_hash_are_case_insensitive_on_windows(self): + a = py.path.local("/some/path") + b = py.path.local("/some/PATH") + assert a == b + assert hash(a) == hash(b) + assert a in {b} + assert a in {b: 'b'} + def test_eq_with_strings(self, path1): path1 = path1.join('sampledir') path2 = str(path1) @@ -161,7 +185,31 @@ def test_eq_with_strings(self, path1): assert path2 != path3 def test_eq_with_none(self, path1): - assert path1 != None # noqa + assert path1 != None # noqa: E711 + + @pytest.mark.skipif( + sys.platform.startswith("win32"), reason="cannot remove cwd on Windows" + ) + @pytest.mark.skipif( + sys.version_info < (3, 0) or sys.version_info >= (3, 5), + reason="only with Python 3 before 3.5" + ) + def test_eq_with_none_and_custom_fspath(self, monkeypatch, path1): + import os + import shutil + import tempfile + + d = tempfile.mkdtemp() + monkeypatch.chdir(d) + shutil.rmtree(d) + + monkeypatch.delitem(sys.modules, 'pathlib', raising=False) + monkeypatch.setattr(sys, 'path', [''] + sys.path) + + with pytest.raises(FileNotFoundError): + import pathlib # noqa: F401 + + assert path1 != None # noqa: E711 def test_eq_non_ascii_unicode(self, path1): path2 = path1.join(u'temp') @@ -475,13 +523,22 @@ def test_pyimport(self, path1): assert obj.x == 42 assert obj.__name__ == 'execfile' - def test_pyimport_renamed_dir_creates_mismatch(self, tmpdir): + def test_pyimport_renamed_dir_creates_mismatch(self, tmpdir, monkeypatch): p = tmpdir.ensure("a", "test_x123.py") p.pyimport() tmpdir.join("a").move(tmpdir.join("b")) with pytest.raises(tmpdir.ImportMismatchError): tmpdir.join("b", "test_x123.py").pyimport() + # Errors can be ignored. + monkeypatch.setenv('PY_IGNORE_IMPORTMISMATCH', '1') + tmpdir.join("b", "test_x123.py").pyimport() + + # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error. + monkeypatch.setenv('PY_IGNORE_IMPORTMISMATCH', '0') + with pytest.raises(tmpdir.ImportMismatchError): + tmpdir.join("b", "test_x123.py").pyimport() + def test_pyimport_messy_name(self, tmpdir): # http://bitbucket.org/hpk42/py-trunk/issue/129 path = tmpdir.ensure('foo__init__.py') @@ -572,6 +629,39 @@ def test_ensuresyspath_append(self, tmpdir): assert str(root1) not in sys.path[:-1] +class TestImportlibImport: + pytestmark = py.test.mark.skipif("sys.version_info < (3, 5)") + + OPTS = {'ensuresyspath': 'importlib'} + + def test_pyimport(self, path1): + obj = path1.join('execfile.py').pyimport(**self.OPTS) + assert obj.x == 42 + assert obj.__name__ == 'execfile' + + def test_pyimport_dir_fails(self, tmpdir): + p = tmpdir.join("hello_123") + p.ensure("__init__.py") + with pytest.raises(ImportError): + p.pyimport(**self.OPTS) + + def test_pyimport_execfile_different_name(self, path1): + obj = path1.join('execfile.py').pyimport(modname="0x.y.z", **self.OPTS) + assert obj.x == 42 + assert obj.__name__ == '0x.y.z' + + def test_pyimport_relative_import_fails(self, path1): + otherdir = path1.join('otherdir') + with pytest.raises(ImportError): + otherdir.join('a.py').pyimport(**self.OPTS) + + def test_pyimport_doesnt_use_sys_modules(self, tmpdir): + p = tmpdir.ensure('file738jsk.py') + mod = p.pyimport(**self.OPTS) + assert mod.__name__ == 'file738jsk' + assert 'file738jsk' not in sys.modules + + def test_pypkgdir(tmpdir): pkg = tmpdir.ensure('pkg1', dir=1) pkg.ensure("__init__.py") @@ -625,6 +715,18 @@ def test_samefile(tmpdir): p2 = p.__class__(str(p).upper()) assert p1.samefile(p2) +@pytest.mark.skipif(not hasattr(os, "symlink"), reason="os.symlink not available") +def test_samefile_symlink(tmpdir): + p1 = tmpdir.ensure("foo.txt") + p2 = tmpdir.join("linked.txt") + try: + os.symlink(str(p1), str(p2)) + except (OSError, NotImplementedError) as e: + # on Windows this might fail if the user doesn't have special symlink permissions + # pypy3 on Windows doesn't implement os.symlink and raises NotImplementedError + pytest.skip(str(e.args[0])) + + assert p1.samefile(p2) def test_listdir_single_arg(tmpdir): tmpdir.ensure("hello") diff --git a/testing/root/test_builtin.py b/testing/root/test_builtin.py index a6f1a3c7..287c60d5 100644 --- a/testing/root/test_builtin.py +++ b/testing/root/test_builtin.py @@ -1,7 +1,7 @@ import sys import types import py -from py.builtin import set, frozenset, reversed, sorted +from py.builtin import set, frozenset def test_enumerate(): l = [0,1,2] @@ -53,29 +53,6 @@ def test_frozenset(): s = set([frozenset([0, 1]), frozenset([1, 0])]) assert len(s) == 1 -def test_sorted(): - if sorted == py.builtin.sorted: - return # don't test a real builtin - for s in [py.builtin.sorted]: - def test(): - assert s([3, 2, 1]) == [1, 2, 3] - assert s([1, 2, 3], reverse=True) == [3, 2, 1] - l = s([1, 2, 3, 4, 5, 6], key=lambda x: x % 2) - assert l == [2, 4, 6, 1, 3, 5] - l = s([1, 2, 3, 4], cmp=lambda x, y: -cmp(x, y)) - assert l == [4, 3, 2, 1] - l = s([1, 2, 3, 4], cmp=lambda x, y: -cmp(x, y), - key=lambda x: x % 2) - assert l == [1, 3, 2, 4] - - def compare(x, y): - assert type(x) == str - assert type(y) == str - return cmp(x, y) - data = 'The quick Brown fox Jumped over The lazy Dog'.split() - s(data, cmp=compare, key=str.lower) - yield test - def test_print_simple(): from py.builtin import print_ @@ -116,7 +93,7 @@ class A: def test_getfuncdict(): def f(): - pass + raise NotImplementedError f.x = 4 assert py.builtin._getfuncdict(f)["x"] == 4 assert py.builtin._getfuncdict(2) is None diff --git a/tox.ini b/tox.ini index d5f362ae..f3203507 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,11 @@ [tox] -envlist=py{27,34,35,36}-pytest{29,30,31} +# Skip py37-pytest29 as such a combination does not work (#192) +envlist=py{27,35,36}-pytest{29,30,31},py37-pytest{30,31} [testenv] -changedir=testing commands= - pip install -U .. # hande the install order fallout since pytest depends on pip - py.test --confcutdir=.. -rfsxX --junitxml={envlogdir}/junit-{envname}.xml [] + pip install -U . # hande the install order fallout since pytest depends on pip + py.test --confcutdir=. --junitxml={envlogdir}/junit-{envname}.xml [] deps= attrs pytest29: pytest~=2.9.0 @@ -19,15 +19,26 @@ deps= pytest-xdist<=1.16.0 commands= pip install -U .. # hande the install order fallout since pytest depends on pip - py.test -n3 -rfsxX --confcutdir=.. --runslowtests \ + py.test -n3 --confcutdir=.. --runslowtests \ --junitxml={envlogdir}/junit-{envname}.xml [] [testenv:jython] changedir=testing commands= {envpython} -m pip install -U .. # hande the install order fallout since pytest depends on pip - {envpython} -m pytest --confcutdir=.. -rfsxX --junitxml={envlogdir}/junit-{envname}0.xml {posargs:io_ code} + {envpython} -m pytest --confcutdir=.. --junitxml={envlogdir}/junit-{envname}0.xml {posargs:io_ code} [pytest] rsyncdirs = conftest.py py doc testing addopts = -ra +testpaths = testing + +[coverage:run] +branch = 1 +source = . +parallel = 1 +[coverage:report] +include = py/*,testing/* +exclude_lines = + #\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(cover|COVER) + ^\s*raise NotImplementedError\b