├── .appveyor.yml ├── .coveragerc ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.md ├── docs ├── .nojekyll ├── README.md ├── _coverpage.md ├── _navbar.md ├── _sidebar.md ├── archive.md ├── cmd.md ├── compat.md ├── console.md ├── convert.md ├── dict.md ├── dt.md ├── environ.md ├── exception.md ├── functional.md ├── index.html ├── inspect.md ├── iter.md ├── list.md ├── misc.md ├── network.md ├── path.md ├── platform.md ├── process.md ├── request.md ├── set.md ├── slot.md ├── string.md ├── system.md ├── unit.md └── zh-cn │ ├── README.md │ ├── _sidebar.md │ ├── archive.md │ ├── cmd.md │ ├── compat.md │ ├── console.md │ ├── convert.md │ ├── dict.md │ ├── dt.md │ ├── environ.md │ ├── exception.md │ ├── functional.md │ ├── inspect.md │ ├── iter.md │ ├── list.md │ ├── misc.md │ ├── network.md │ ├── path.md │ ├── platform.md │ ├── process.md │ ├── request.md │ ├── set.md │ ├── slot.md │ ├── string.md │ ├── system.md │ └── unit.md ├── pydu ├── __init__.py ├── archive.py ├── cmd.py ├── compat.py ├── console.py ├── convert.py ├── dict.py ├── dt.py ├── environ.py ├── exception.py ├── functional.py ├── inspect.py ├── iter.py ├── list.py ├── misc.py ├── network.py ├── path.py ├── platform.py ├── process.py ├── request.py ├── set.py ├── slot.py ├── string.py ├── system.py └── unit.py ├── requirements-dev.txt ├── setup.cfg ├── setup.py ├── stubs └── pydu │ ├── __init__.pyi │ ├── archive.pyi │ ├── cmd.pyi │ ├── console.pyi │ ├── convert.pyi │ ├── dict.pyi │ ├── dt.pyi │ ├── environ.pyi │ ├── exception.pyi │ ├── functional.pyi │ ├── iter.pyi │ ├── list.pyi │ ├── misc.pyi │ ├── network.pyi │ ├── path.pyi │ ├── process.pyi │ ├── request.pyi │ ├── set.pyi │ ├── string.pyi │ ├── system.pyi │ └── unit.pyi ├── tests ├── __init__.py ├── files │ ├── bad │ │ ├── absolute.tar.gz │ │ ├── relative.tar.gz │ │ └── unrecognized.txt │ ├── foobar.tar │ ├── foobar.tar.bz2 │ ├── foobar.tar.gz │ ├── foobar.zip │ ├── foobar_tar_gz │ ├── 压缩.tgz │ └── 压缩.zip ├── test_archive.py ├── test_cmd.py ├── test_compat.py ├── test_console.py ├── test_convert.py ├── test_dict.py ├── test_dt.py ├── test_environ.py ├── test_exception.py ├── test_functional.py ├── test_inspect.py ├── test_iter.py ├── test_list.py ├── test_misc.py ├── test_network.py ├── test_path.py ├── test_platform.py ├── test_request.py ├── test_set.py ├── test_slot.py ├── test_string.py ├── test_system.py ├── test_unit.py └── testing.py └── tox.ini /.appveyor.yml: -------------------------------------------------------------------------------- 1 | build: off 2 | 3 | environment: 4 | matrix: 5 | - PYTHON: "C:\\Python27-x64" 6 | PYTHON_VERSION: "2.7.x" 7 | PYTHON_ARCH: "64" 8 | TOXENV: "py27" 9 | 10 | - PYTHON: "C:\\Python35-x64" 11 | PYTHON_VERSION: "3.5.x" 12 | PYTHON_ARCH: "64" 13 | TOXENV: "py35" 14 | 15 | - PYTHON: "C:\\Python36-x64" 16 | PYTHON_VERSION: "3.6.x" 17 | PYTHON_ARCH: "64" 18 | TOXENV: "py36" 19 | 20 | - PYTHON: "C:\\Python37-x64" 21 | PYTHON_VERSION: "3.7.x" 22 | PYTHON_ARCH: "64" 23 | TOXENV: "py37" 24 | 25 | - PYTHON: "C:\\Python38-x64" 26 | PYTHON_VERSION: "3.8.x" 27 | PYTHON_ARCH: "64" 28 | TOXENV: "py38" 29 | 30 | install: 31 | - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" 32 | - "%CMD_IN_ENV% pip install tox codecov" 33 | 34 | test_script: 35 | - "%CMD_IN_ENV% tox" 36 | 37 | on_success: 38 | - "%CMD_IN_ENV% codecov" 39 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = pydu 4 | 5 | [paths] 6 | source = 7 | pydu 8 | .tox/*/lib/python*/site-packages/pydu -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # IDE 7 | .idea 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # dotenv 86 | .env 87 | 88 | # virtualenv 89 | .venv 90 | venv/ 91 | ENV/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # pytest 107 | .pytest_cache/ 108 | 109 | # Mac 110 | 111 | .DS_Store 112 | 113 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.5" 5 | - "3.6" 6 | - "3.7" 7 | - "3.8" 8 | 9 | sudo: false 10 | 11 | cache: pip 12 | 13 | install: 14 | - pip install tox codecov 15 | 16 | script: 17 | - tox -e $(echo py$TRAVIS_PYTHON_VERSION | tr -d .) 18 | 19 | after_success: 20 | - codecov 21 | 22 | notifications: 23 | email: 24 | recipients: 25 | - wangbinxin001@126.com 26 | on_success: always 27 | on_failure: always 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | v0.7.2 (2019-02-08) 2 | ------------------- 3 | 4 | **Bug fixes** 5 | 6 | * Fix collections ABCs deprecation warning 7 | 8 | 9 | v0.7.0 (2018-05-14) 10 | ------------------- 11 | 12 | **Enhancements** 13 | 14 | * Upgrade to **brand new document** powerd by docsify 15 | * Add slot.SlotBase which is the base class for class using `__slots__` 16 | * Add compat.izip 17 | 18 | 19 | v0.6.2 (2018-04-30) 20 | ------------------- 21 | 22 | **Enhancements** 23 | 24 | * Add ``exception.default_if_except`` which excepts given exceptions and return default value as decorator. 25 | 26 | 27 | v0.6.1 (2018-04-23) 28 | ------------------- 29 | 30 | **Enhancements** 31 | 32 | * Add ``dt.timer`` which can time how long does calling take as a context manager or decorator. 33 | 34 | 35 | v0.6.0 (2018-04-16) 36 | ------------------- 37 | 38 | **Enhancements** 39 | 40 | * Add ``path.filename`` which return the filename without extension. 41 | * Add ``path.fileext`` which return the file extension. 42 | * Update stub for ``requests.check_connect``. 43 | 44 | 45 | v0.5.2 (2018-04-04) 46 | ------------------- 47 | 48 | **Enhancements** 49 | 50 | * Add ``system.preferredencoding`` which gets best encoding for the system. 51 | * Add ``request.update_query_params`` which update query params of given url and return new url. 52 | * Update stub for ``requests.check_connect``. 53 | 54 | 55 | v0.5.1 (2018-03-19) 56 | ------------------- 57 | 58 | **Enhancements** 59 | 60 | * Improve ``system.remove`` when path is read-only. 61 | * Add ``path.normjoin`` which join one or more path components intelligently and normalize it. 62 | * Improve ``environ.environ`` with supporting variable_name=None which means removing the variable from environment temporarily. 63 | 64 | 65 | v0.5.0 (2018-03-08) 66 | ------------------- 67 | 68 | **Enhancements** 69 | 70 | * Add ``network.private_ipv4s`` which stores private IPV4 addresses. 71 | * Add ``functional.compose`` which composes all functions into one. 72 | * Add ``TYPE HINT`` for ALL MODULES by supplying STUB FILES! 73 | 74 | **Bug fixes** 75 | 76 | * Fix reduce error on Python 3. 77 | 78 | 79 | v0.4.2 (2018-02-05) 80 | ------------------- 81 | 82 | **Enhancements** 83 | 84 | * Add ``socket.inet_pton`` and ``socket.inetntop`` for Windows if we ``import pydu.network``. 85 | * Add ``network.ip2int`` and ``network.int2ip`` which convert ip to integer or integer to ip. 86 | * Add ``process.get_processes_by_path`` for getting processes which are running on given path or sub path of given path. 87 | * Add ``first``, ``last``, ``all``, ``any`` and ``join`` to ``pydu.iter``, which support many operations on iterable object. 88 | 89 | **Bug fixes** 90 | 91 | * Fix several convert functions return values with unnecessary value 'L' when given big number on Python 2. 92 | 93 | 94 | v0.4.1 (2018-01-20) 95 | ------------------- 96 | 97 | **Enhancements** 98 | 99 | * Add ``bin2oct``, ``bin2dec``, ``bin2hex``, ``oct2bin``, ``oct2dec``, ``oct2hex``, ``dec2bin``, ``dec2oct``, ``dec2hex``, ``hex2bin``, ``hex2oct``, ``hex2dec`` to ``convert``, which support many base conversions 100 | * Add ``path.is_super_path`` which judges whether the given ``path1`` is the super path of ``path2`` 101 | * Add ``environ.environ`` which is a context manager for updating one or more environment variables 102 | * Add ``environ.path`` which is a context manager for updating the PATH environment variable 103 | * Add ``list.tolist`` which converts obj to list 104 | * Add ``list.flatten`` which generates each element of the given ``seq`` 105 | * Add ``compat.strbytes_types`` which includes all types about string 106 | 107 | 108 | v0.4.0 (2018-01-09) 109 | ------------------- 110 | 111 | **Importance** 112 | * Remove support for Python 3.4 113 | 114 | **Enhancements** 115 | 116 | * Add ``dict.OrderedDefaultDict`` which remembers insertion order and has default value with default factory 117 | * Add ``convert.boolean`` which converts obj to a boolean value 118 | * ``console.console_size`` will use ``shutil.get_terminal_size`` if possible 119 | * ``exception.ignore`` is same to ``context.lib.suppress`` on Python 3 120 | 121 | **Bug fixes** 122 | 123 | * Fix #15 (If the ``dict.attrify``'s obj is tuple, this will raise a error) 124 | 125 | 126 | v0.3.1 (2017-12-29) 127 | ------------------- 128 | 129 | **Enhancements** 130 | 131 | * Add ``FileTracker`` which could track opening files. 132 | 133 | 134 | **Bug fixes** 135 | 136 | * Fix ``pip install`` error on Windows with Python 3. 137 | * Fix ``network.is_ipv6`` test error on Windows with Python 3. 138 | * Fix description error on ``network``, ``request`` doc. 139 | 140 | 141 | v0.3.0 (2017-12-26) 142 | ------------------- 143 | 144 | **Enhancements** 145 | 146 | * Rename ``file`` to ``system``. 147 | * Add ``system.which`` which supports find executable file. 148 | * Add ``system.chmod`` which supports chmod recursively. 149 | * Add ``unit.Bytes`` which used to deal with bytes. 150 | * Add ``preferredencoding`` to ``string``. 151 | * Add ``cmd.chcp`` for Windows which is same like ``chcp`` on Windows cmd. 152 | * Add ``cmd.run_with_en_env`` which ensure the output of cmd is in English. 153 | * Add ``cmd.terminate`` which supports terminate process by given ``pid``. 154 | * ``cmd.run`` uses timeout feature on Python 3 but not implement by self. 155 | 156 | 157 | **Bug fixes** 158 | 159 | * Fix test cases to generate right coverage. 160 | 161 | 162 | v0.2.0 (2017-12-17) 163 | ------------------- 164 | 165 | **Enhancements** 166 | 167 | * Add ``exception.ignore``. 168 | * ``network.is_ipv6`` is available on Windows. 169 | * Set logging handler to avoid "No handler found" warnings. 170 | * Add ``Makefile`` which make development easier. 171 | * Update ``readme`` which is more readable. 172 | 173 | **Bug fixes** 174 | 175 | * Fix installation error on Windows. 176 | 177 | 178 | v0.1.0 (2017-12-14) 179 | ------------------- 180 | 181 | Supply many powerful data structures and utils about archive, cmd, compat, console, dict, file, inspect, list, misc, network, path, platform, request, set and string. -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Prodesire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements-dev.txt README.md CHANGELOG.md LICENSE.txt -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Env 2 | export PYTHONDONTWRITEBYTECODE=1 3 | TEST_PATH=./tests 4 | DEFAULT_PYTHON2=`python -c "import sys;print(sys.version_info.major)" | grep 2` 5 | PY2=$(if $(DEFAULT_PYTHON2),python,python2) 6 | PY3=$(if $(DEFAULT_PYTHON2),python3,python) 7 | 8 | # Func 9 | .PHONY: docs 10 | 11 | help: 12 | @echo "\033[32minit\033[0m" 13 | @echo " Init environment for pydu." 14 | @echo "\033[32mtest\033[0m" 15 | @echo " Run pytest with Python 2 and 3." 16 | @echo "\033[32mtest-py2\033[0m" 17 | @echo " Run pytest with Python 2." 18 | @echo "\033[32mtest-py3\033[0m" 19 | @echo " Run pytest with Python 3." 20 | @echo "\033[32mcoverage\033[0m" 21 | @echo " Run pytest and report coverage." 22 | @echo "\033[32mpublish\033[0m" 23 | @echo " Publish pydu to PyPI." 24 | @echo "\033[32mdocs\033[0m" 25 | @echo " Make docs for pydu." 26 | @echo "\033[32mclean\033[0m" 27 | @echo " Remove python and build artifacts." 28 | @echo "\033[32mclean-pyc\033[0m" 29 | @echo " Remove python artifacts." 30 | @echo "\033[32mclean-build\033[0m" 31 | @echo " Remove build artifacts." 32 | 33 | init: 34 | pip install -r requirements-dev.txt 35 | npm i docsify-cli -g 36 | 37 | test: test-py2 test-py3 38 | 39 | test-py2: clean-pyc 40 | $(PY2) -m pytest --color=yes $(TEST_PATH) 41 | 42 | test-py3: clean-pyc 43 | $(PY3) -m pytest --color=yes $(TEST_PATH) 44 | 45 | coverage: 46 | coverage run --source=pydu -m pytest tests 47 | coverage report 48 | 49 | publish: 50 | pip install 'twine>=1.5.0' 51 | python setup.py sdist 52 | twine upload dist/* 53 | rm -rf build dist *.egg-info .eggs 54 | 55 | docs: 56 | docsify serve docs 57 | 58 | clean: clean-pyc clean-build 59 | 60 | clean-pyc: 61 | find . -name '*.pyc' -exec rm -f {} + 62 | find . -name '*.pyo' -exec rm -f {} + 63 | find . -name '*~' -exec rm -f {} + 64 | find . -name '__pycache__' -exec rm -rf {} + 65 | 66 | clean-build: 67 | rm -rf build dist *.egg-info .eggs 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pydu 2 | 3 | [![pydu](https://img.shields.io/pypi/v/pydu.svg)](https://pypi.python.org/pypi/pydu) 4 | [![pydu](https://img.shields.io/pypi/l/pydu.svg)](https://pypi.python.org/pypi/pydu) 5 | [![pydu](https://img.shields.io/pypi/pyversions/pydu.svg)](https://pypi.python.org/pypi/pydu) 6 | [![pydu](https://img.shields.io/travis/flaggo/pydu/master.svg?label=Linux)](https://travis-ci.org/flaggo/pydu) 7 | [![pydu](https://img.shields.io/appveyor/ci/flaggo/pydu/master.svg?label=Windows)](https://ci.appveyor.com/project/flaggo/pydu) 8 | [![pydu](https://codecov.io/github/flaggo/pydu/coverage.svg?branch=master)](https://codecov.io/github/flaggo/pydu) 9 | [![pydu](https://img.shields.io/github/contributors/flaggo/pydu.svg)](https://github.com/flaggo/pydu/graphs/contributors) 10 | 11 | **pydu** is a library of useful **d**ata structures and **u**tils 12 | for Python 2 and 3, which collected from open source projects and created by contributors. 13 | 14 | 15 | ## Installation 16 | 17 | To install pydu, simply: 18 | 19 | ```bash 20 | $ pip install pydu 21 | ``` 22 | 23 | ## Document 24 | 25 | Fantastic documentation is available at: [English](https://flaggo.github.io/pydu/) | [中文版](https://flaggo.github.io/pydu/#/zh-cn/). 26 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/pydu/e6e4055f81dbbece55dccfe29b7fb82b9bf40610/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## pydu 2 | 3 | > **pydu(Python Data structures and Utils)** is a library of useful data structures and utils 4 | for Python 2 and 3, which collected from open source projects and created by contributors. 5 | 6 | 7 | ## Installation 8 | To install **pydu**, simply: 9 | 10 | ```bash 11 | $ pip install pydu 12 | ``` 13 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | # pydu 0.7.0 2 | 3 | > Python Data structures and Utils. 4 | 5 | * Rich basic data structures 6 | * A variety of utils for handling different situations 7 | 8 | [GitHub](https://github.com/flaggo/pydu/) 9 | [Modules](#pydu) 10 | -------------------------------------------------------------------------------- /docs/_navbar.md: -------------------------------------------------------------------------------- 1 | * [En](/) 2 | * [中文](/zh-cn/) 3 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * Modules 2 | 3 | * [Archive](archive.md) 4 | * [Commad](cmd.md) 5 | * [Compat](compat.md) 6 | * [Console](console.md) 7 | * [Convert](convert.md) 8 | * [Dict](dict.md) 9 | * [Date and Time](dt.md) 10 | * [Environment](environ.md) 11 | * [Exception](exception.md) 12 | * [Functional](functional.md) 13 | * [Inspect](inspect.md) 14 | * [Iter](iter.md) 15 | * [List](list.md) 16 | * [Miscellanea](misc.md) 17 | * [Network](network.md) 18 | * [Path](path.md) 19 | * [Platform](platform.md) 20 | * [Process](process.md) 21 | * [Request](request.md) 22 | * [Set](set.md) 23 | * [Slot](slot.md) 24 | * [String](string.md) 25 | * [System](system.md) 26 | * [Unit](unit.md) 27 | 28 | * [Changelog](changelog.md) 29 | -------------------------------------------------------------------------------- /docs/archive.md: -------------------------------------------------------------------------------- 1 | # archive 2 | 3 | Utils for archiving files. 4 | 5 | ## archive.extract 6 | ```python 7 | extract(path, to_path='', ext='') 8 | ``` 9 | 10 | Unpack the tar or zip file at the specified path or file to the directory 11 | specified by ``to_path``. It supports many extensions, like ``.tar``, 12 | ``.tar.bz2``, ``.tar.gz``, ``.tgz``, ``.tz2``, ``.zip``. If the file name of 13 | given ``path`` doesn't contain file extension, the ``ext`` parameter can be 14 | specified one of supported extensions to indicate file type. 15 | 16 | ```python 17 | >>> from pydu.archive import extract 18 | >>> extract('foobar.tgz', '/tmp') 19 | >>> extract('foobar', '/tmp', ext='.tgz') 20 | >>> extract('foobar', '/tmp') 21 | Traceback (most recent call last): 22 | ... AttributeError: pydu.archive.UnrecognizedArchiveFormat: Path not a recognized archive format: foobar 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/cmd.md: -------------------------------------------------------------------------------- 1 | # cmd 2 | 3 | Utils for running command and getting command line. 4 | 5 | ## cmd.TimeoutExpired 6 | ```python 7 | TimeoutExpired(cmd, timeout, output=None, stderr=None) 8 | ``` 9 | 10 | This exception is raised when the timeout expires while waiting for a 11 | child process. 12 | 13 | Attributes: 14 | cmd, output, stdout, stderr, timeout 15 | 16 | 17 | ## cmd.run 18 | ```python 19 | run(cmd, shell=False, env=None, timeout=None, timeinterval=1) 20 | ``` 21 | 22 | Run cmd based on `subprocess.Popen` and return the tuple of ``(returncode, stdout)``. 23 | 24 | Note, `stderr` is redirected to `stdout`. `shell` is same to parameter of `Popen`. 25 | 26 | If the process does not terminate after `timeout` seconds, a `TimeoutExpired` exception will be raised. 27 | `timeinterval` is workable when timeout is given on Python 2. It means process status checking interval. 28 | 29 | The child process is not killed if the timeout expires, so in order to cleanup properly a well-behaved application should kill the child process and finish communication. 30 | 31 | ```python 32 | >>> from pydu.cmd import run 33 | >>> run('echo hello') 34 | (0, b'hello\r\n') # Python 3 35 | ``` 36 | 37 | 38 | ## cmd.run_with_en_env 39 | ```python 40 | run_with_en_env(cmd, shell=False, env=None, timeout=None, timeinterval=1) 41 | ``` 42 | 43 | Run cmd with English character sets environment, so that the output will 44 | be in English. 45 | Parameters are same with `run`. 46 | 47 | 48 | ## cmd.terminate 49 | ```python 50 | terminate(pid) 51 | ``` 52 | 53 | Terminate process by given `pid`. 54 | 55 | On Windows, using `kernel32.TerminateProcess` to kill. 56 | On other platforms, using `os.kill` with `signal.SIGTERM` to kill. 57 | 58 | 59 | ## cmd.cmdline_argv 60 | ```python 61 | cmdline_argv() 62 | ``` 63 | 64 | Get command line argv of self python process. On Windows when using Python 2, 65 | `cmdline_argv` is implemented by using `shell32.GetCommandLineArgvW` to get 66 | `sys.argv` as a list of Unicode strings. 67 | 68 | On other platforms or using Python 3, `cmdline_argv` is same to `sys.argv`. 69 | 70 | ```python 71 | >>> from pydu.cmd import cmdline_argv 72 | >>> cmdline_argv() 73 | ['/Applications/PyCharm.app/Contents/helpers/pydev/pydevconsole.py', '61253', '61254'] 74 | ``` 75 | -------------------------------------------------------------------------------- /docs/compat.md: -------------------------------------------------------------------------------- 1 | # compat 2 | 3 | compatible data structures, libs, functions for Python 2 and 3. 4 | 5 | ## compat.PY2 6 | 7 | Specify current Python interpreter is Python 2 or 3. 8 | 9 | 10 | ## compat.urlib 11 | ```python 12 | urlib(base, url, allow_fragments=True) 13 | ``` 14 | 15 | Same to ``urllib`` on PY2 or ``urllib.request`` on PY3. 16 | 17 | 18 | ## compat.urlparse 19 | ```python 20 | urlparse(base, url, allow_fragments=True) 21 | ``` 22 | 23 | Same to ``urlparse`` on PY2 or ``urllib.parse`` on PY3. 24 | 25 | 26 | ## compat.urljoin 27 | ```python 28 | urljoin(base, url, allow_fragments=True) 29 | ``` 30 | 31 | Same to ``urlparse.urljoin`` on PY2 or ``urllib.parse.urljoin`` on PY3. 32 | 33 | 34 | ## compat.iterkeys 35 | ```python 36 | iterkeys(d) 37 | ``` 38 | 39 | Return an iter object of dictionary keys. 40 | 41 | 42 | ## compat.itervalues 43 | ```python 44 | itervalues(d) 45 | ``` 46 | 47 | Return an iter object of dictionary values. 48 | 49 | 50 | ## compat.iteritems 51 | ```python 52 | iteritems(d) 53 | ``` 54 | 55 | Return an iter object of dictionary items. 56 | 57 | 58 | ## compat.text_type 59 | 60 | The text type is ``unicode`` on PY2 or ``str`` on PY3. 61 | 62 | 63 | ## compat.string_types 64 | 65 | The string types are ``(str, unicode)`` on PY2 or ``(str,)`` on PY3. 66 | 67 | ## compat.strbytes_types 68 | 69 | The strbytes(string bytes) types are ``(str, unicode, bytes)`` on PY2 or ``(str, bytes)`` on PY3. 70 | 71 | 72 | ## compat.numeric_types 73 | 74 | The numeric types are ``(int, long)`` on PY2 or ``(int,)`` on PY3. 75 | 76 | 77 | ## compat.imap 78 | ```python 79 | imap(func, *iterables) 80 | ``` 81 | 82 | Same to ``itertools.imap`` on PY2 or ``map`` on PY3. 83 | 84 | 85 | ## compat.izip 86 | ```python 87 | izip(iter1 [,iter2 [...]) 88 | ``` 89 | 90 | Same to ``itertools.izip`` on PY2 or ``zip`` on PY3. 91 | 92 | 93 | ## compat.reduce 94 | ```python 95 | reduce(function, sequence, initial=None) 96 | ``` 97 | 98 | Same to built-in ``reduce`` on PY2 or ``functools.reduce`` on PY3. 99 | 100 | 101 | ## compat.cmp 102 | ```python 103 | cmp(x, y) 104 | ``` 105 | 106 | Same to ``cmp`` on PY2, but implement on PY3. 107 | 108 | 109 | ## compat.has_next_attr 110 | ```python 111 | has_next_attr(x) 112 | ``` 113 | 114 | An implementation independent way of checking for next attribute. 115 | 116 | 117 | ## compat.is_iterable 118 | ```python 119 | is_iterable(x) 120 | ``` 121 | 122 | An implementation independent way of checking for iterables. 123 | 124 | ```python 125 | >>> from pydu.compat import is_iterable 126 | >>> is_iterable([]) 127 | True 128 | >>> is_iterable(1) 129 | False 130 | ``` 131 | -------------------------------------------------------------------------------- /docs/console.md: -------------------------------------------------------------------------------- 1 | # Console 2 | 3 | Utils for handling console. 4 | 5 | ## console.console_size 6 | ```python 7 | console_size(fallback=(80, 25)) 8 | ``` 9 | 10 | For Windows, return (width, height) of available window area, fallback 11 | if no console is allocated. 12 | For POSIX system, return (width, height) of console terminal, fallback 13 | on IOError, i.e. when no console is allocated. 14 | For other system, return fallback. 15 | Fallback defaults to (80, 25) which is the default size used by many 16 | terminal emulators. 17 | 18 | ```python 19 | >>> from pydu.console import console_size 20 | >>> console_size() 21 | (80, 25) 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/convert.md: -------------------------------------------------------------------------------- 1 | # Convert 2 | 3 | Utils for converting one type of data to another. 4 | 5 | 6 | ## convert.boolean 7 | ```python 8 | boolean(obj) 9 | ``` 10 | 11 | Convert obj to a boolean value. 12 | 13 | If obj is string, obj will converted by case-insensitive way: 14 | 15 | * convert `yes`, `y`, `on`, `true`, `t`, `1` to True 16 | * convert `no`, `n`, `off`, `false`, `f`, `0` to False 17 | * raising TypeError if other values passed 18 | 19 | If obj is non-string, obj will converted by `bool(obj)`. 20 | 21 | ```python 22 | >>> from pydu.string import boolean 23 | >>> boolean('yes') 24 | True 25 | >>> boolean('no') 26 | False 27 | ``` 28 | 29 | 30 | ## convert.bin2oct 31 | ```python 32 | bin2oct(x) 33 | ``` 34 | 35 | Convert binary string to octal string. 36 | For instance: '1001' -> '11' 37 | 38 | ```python 39 | >>> from pydu.convert import bin2oct 40 | >>> bin2oct('1001') 41 | '11' 42 | ``` 43 | 44 | 45 | ## convert.bin2dec 46 | ```python 47 | bin2dec(x) 48 | ``` 49 | 50 | Convert binary string to decimal number. 51 | For instance: '11' -> 3 52 | 53 | ```python 54 | >>> from pydu.convert import bin2dec 55 | >>> bin2dec('11') 56 | 3 57 | ``` 58 | 59 | 60 | ## convert.bin2hex 61 | ```python 62 | bin2hex(x) 63 | ``` 64 | 65 | Convert binary string to hexadecimal string. 66 | For instance: '11010' -> '1a' 67 | 68 | ```python 69 | >>> from pydu.convert import bin2hex 70 | >>> bin2hex('11010') 71 | '1a' 72 | ``` 73 | 74 | 75 | ## convert.oct2bin 76 | ```python 77 | oct2bin(x) 78 | ``` 79 | 80 | Convert octal string to binary string. 81 | For instance: '11' -> '1001' 82 | 83 | ```python 84 | >>> from pydu.convert import oct2bin 85 | >>> oct2bin('11') 86 | '1001' 87 | ``` 88 | 89 | 90 | ## convert.oct2dec 91 | ```python 92 | oct2dec(x) 93 | ``` 94 | 95 | Convert octal string to decimal number. 96 | For instance: '11' -> 9 97 | 98 | ```python 99 | >>> from pydu.convert import oct2dec 100 | >>> oct2dec('11') 101 | 9 102 | ``` 103 | 104 | 105 | ## convert.oct2hex 106 | ```python 107 | oct2hex(x) 108 | ``` 109 | 110 | Convert octal string to hexadecimal string. 111 | For instance: '32' -> '1a' 112 | 113 | ```python 114 | >>> from pydu.convert import oct2hex 115 | >>> oct2hex('32') 116 | '1a' 117 | ``` 118 | 119 | 120 | ## convert.dec2bin 121 | ```python 122 | dec2bin(x) 123 | ``` 124 | 125 | Convert decimal number to binary string. 126 | For instance: 3 -> '11' 127 | 128 | ```python 129 | >>> from pydu.convert import dec2bin 130 | >>> dec2bin(3) 131 | '11' 132 | ``` 133 | 134 | 135 | ## convert.dec2oct 136 | ```python 137 | dec2oct(x) 138 | ``` 139 | 140 | Convert decimal number to octal string. 141 | For instance: 9 -> '11' 142 | 143 | ```python 144 | >>> from pydu.convert import dec2oct 145 | >>> dec2oct(9) 146 | '11' 147 | ``` 148 | 149 | 150 | ## convert.dec2hex 151 | ```python 152 | dec2hex(x) 153 | ``` 154 | 155 | Convert decimal number to hexadecimal string. 156 | For instance: 26 -> '1a' 157 | 158 | ```python 159 | >>> from pydu.convert import dec2hex 160 | >>> dec2hex(26) 161 | '1a' 162 | ``` 163 | 164 | 165 | ## convert.hex2bin 166 | ```python 167 | hex2bin(x) 168 | ``` 169 | 170 | Convert hexadecimal string to binary string. 171 | For instance: '1a' -> '11010' 172 | 173 | ```python 174 | >>> from pydu.convert import hex2bin 175 | >>> hex2bin('1a') 176 | '11010' 177 | ``` 178 | 179 | 180 | ## convert.hex2oct 181 | ```python 182 | hex2oct(x) 183 | ``` 184 | 185 | Convert hexadecimal string to octal string. 186 | For instance: '1a' -> '32' 187 | 188 | ```python 189 | >>> from pydu.convert import hex2oct 190 | >>> hex2oct('1a') 191 | '32' 192 | ``` 193 | 194 | 195 | ## convert.hex2dec 196 | ```python 197 | hex2dec(x) 198 | ``` 199 | 200 | Convert hexadecimal string to decimal number. 201 | For instance: '1a' -> 26 202 | 203 | ```python 204 | >>> from pydu.convert import hex2dec 205 | >>> hex2dec('1a') 206 | 26 207 | ``` 208 | -------------------------------------------------------------------------------- /docs/dict.md: -------------------------------------------------------------------------------- 1 | # Dict 2 | 3 | Additional powerful dictionaries and relative functions. 4 | 5 | ## dict.AttrDict 6 | ```python 7 | AttrDict(seq=None, **kwargs) 8 | ``` 9 | 10 | A AttrDict object is like a dictionary except `obj.foo` can be used 11 | in addition to `obj['foo']`. 12 | 13 | ```python 14 | >>> from pydu.dict import AttrDict 15 | >>> o = AttrDict(a=1) 16 | o.a 17 | 1 18 | >>> o['a'] 19 | 1 20 | >>> o.a = 2 21 | >>> o['a'] 22 | 2 23 | >>> del o.a 24 | >>> o.a 25 | Traceback (most recent call last): 26 | ... AttributeError: 'a' 27 | ``` 28 | 29 | 30 | ## dict.CaseInsensitiveDict 31 | ```python 32 | CaseInsensitiveDict(data=None, **kwargs) 33 | ``` 34 | 35 | A case-insensitive `dict`-like object. 36 | Implements all methods and operations of `collections.MutableMapping` 37 | as well as dict's `copy`. Also provides `lower_items`. 38 | All keys are expected to be strings. The structure remembers the 39 | case of the last key to be set, and `iter(instance)`, `keys()`, 40 | `items()`, `iterkeys()`, and `iteritems()` will contain 41 | case-sensitive keys. 42 | 43 | ```python 44 | >>> from pydu.dict import CaseInsensitiveDict 45 | >>> cid = CaseInsensitiveDict() 46 | >>> cid['Accept'] = 'application/json' 47 | >>> cid['aCCEPT'] == 'application/json' 48 | True 49 | >>> list(cid) == ['Accept'] 50 | True 51 | ``` 52 | 53 | 54 | ## dict.LookupDict 55 | ```python 56 | LookupDict(name=None) 57 | ``` 58 | 59 | Dictionary lookup object. 60 | 61 | ```python 62 | >>> from pydu.dict import LookupDict 63 | >>> d = LookupDict() 64 | >>> d['key'] 65 | None 66 | >>> d['key'] = 1 67 | >>> d['key'] 68 | 1 69 | ``` 70 | 71 | ## dict.OrderedDefaultDict 72 | ```python 73 | OrderedDefaultDict(default_factory=None, *args, **kwds) 74 | ``` 75 | 76 | Dictionary that remembers insertion order and has default value 77 | with default factory. 78 | 79 | The default factory is called without arguments to produce 80 | a new value when a key is not present, in `__getitem__` only. 81 | An `OrderedDefaultDict` compares equal to a `collections.defaultdict` 82 | with the same items. All remaining arguments are treated the same 83 | as if they were passed to the `defaultdict` constructor, 84 | including keyword arguments. 85 | 86 | ```python 87 | >>> from pydu.dict import OrderedDefaultDict 88 | >>> d = OrderedDefaultDict(int) 89 | >>> d['b'] 90 | 0 91 | >>> d['a'] 92 | 0 93 | >>> d.keys() 94 | odict_keys(['b', 'a']) 95 | ``` 96 | 97 | 98 | ## dict.attrify 99 | ```python 100 | attrify(obj) 101 | ``` 102 | 103 | Attrify obj into `AttriDict` or `list of AttriDict` if the obj is list. 104 | If obj or the item of obj is not list or dict, will return itself. 105 | 106 | ```python 107 | >>> from pydu.dict import attrify 108 | >>> attrd = attrify({ 109 | 'a': [1, 2, {'b': 'b'}], 110 | 'c': 'c', 111 | }) 112 | >>> attrd 113 | ], 'c': 'c'}> 114 | >>> attrd.a 115 | 1 116 | >>> attrd.a[2].b 117 | b 118 | >>> attrd.c 119 | c 120 | ``` 121 | -------------------------------------------------------------------------------- /docs/dt.md: -------------------------------------------------------------------------------- 1 | # Date and Time 2 | 3 | Utils for handling date and time. 4 | 5 | ## dt.timer 6 | ```python 7 | timer(path) 8 | ``` 9 | 10 | A timer can time how long does calling take as a context manager or decorator. 11 | If assign `print_func` with `sys.stdout.write`, `logger.info` and so on, 12 | timer will print the spent time. 13 | 14 | ```python 15 | timeit = timer(print_func=sys.stdout.write) 16 | with timeit: 17 | foo() 18 | 19 | @timeit 20 | def foo(): 21 | pass 22 | ``` 23 | 24 | `timer.elapsed` contains the total amount of elapsed 25 | time of running `foo`. 26 | 27 | ```python 28 | >>> timeit = timer(print_func=sys.stdout.write) 29 | >>> with timeit: 30 | ... os.getcwd() 31 | Spent time: 1.7881393432617188e-05s 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/environ.md: -------------------------------------------------------------------------------- 1 | # Environ 2 | 3 | Utils for handling environment. 4 | 5 | 6 | ## environ.environ 7 | ```python 8 | environ(**kwargs) 9 | ``` 10 | 11 | Context manager for updating one or more environment variables. 12 | 13 | Preserves the previous environment variable (if available) and 14 | recovers when exiting the context manager. 15 | 16 | If given variable_name=None, it means removing the variable from 17 | environment temporarily. 18 | 19 | ```python 20 | >>> from pydu.environ import environ 21 | >>> with environ(a='a'): 22 | ... print(os.environ['a']) 23 | ... 24 | a 25 | ``` 26 | 27 | 28 | ## environ.path 29 | ```python 30 | path(append=None, prepend=None, replace=None) 31 | ``` 32 | 33 | Context manager for updating the PATH environment variable which 34 | appends, prepends or replaces the PATH with given string or 35 | a list of strings. 36 | 37 | ```python 38 | >>> import os 39 | >>> from pydu.environ import path 40 | >>> with path(append='/foo'): 41 | ... print(os.environ['PATH']) 42 | ... 43 | /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/foo 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/exception.md: -------------------------------------------------------------------------------- 1 | # Exception 2 | 3 | Utils for handling exceptions. 4 | 5 | ## exception.ignore 6 | ```python 7 | ignore(*exceptions) 8 | ``` 9 | 10 | A context manager which can ignore given exceptions. 11 | 12 | ```python 13 | >>> from pydu.exception import ignore 14 | >>> with ignore(ValueError, AttributeError): 15 | ... int('abc') 16 | ... int.no_exists_func() 17 | ... 18 | >>> 19 | ``` 20 | 21 | ## exception.default_if_except 22 | ```python 23 | default_if_except(exception_clses, default=None) 24 | ``` 25 | 26 | A exception decorator which excepts given exceptions and return default value. 27 | 28 | ```python 29 | >>> from pydu.exception import default_if_except 30 | >>> @default_if_except(ValueError, default=0) 31 | ... def foo(value): 32 | ... return int(value) 33 | >>> foo('abc') 34 | 0 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/functional.md: -------------------------------------------------------------------------------- 1 | # functional 2 | 3 | Utils for functional programming. 4 | 5 | ## functional.compose 6 | ```python 7 | compose(*funcs) 8 | ``` 9 | 10 | Compose all functions. The previous function must accept one argument, 11 | which is the output of the next function. The last function can accept 12 | any args and kwargs. 13 | `compose(f1, f2, f3)(*x)` is same to `f1(f2(f3(*x)))`. 14 | 15 | ```python 16 | >>> from pydu.functional import compose 17 | >>> def f1(a): 18 | ... return a+1 19 | ... 20 | >>> def f2(a, b=2): 21 | ... return a+b 22 | ... 23 | >>> compose(f1, f2)(1, b=3) 24 | 5 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | pydu - Python Data structures and Utils 6 | 7 | 8 | 9 | 10 | 11 | 12 |
Loading ...
13 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/inspect.md: -------------------------------------------------------------------------------- 1 | # inspect 2 | 3 | Utils for inspecting functions. 4 | 5 | ## inspect.getargspec 6 | ```python 7 | getargspec(func) 8 | ``` 9 | 10 | Get the names and default values of a function's arguments. 11 | 12 | A tuple of four things is returned: (args, varargs, varkw, defaults). 13 | `args` is a list of the argument names (it may contain nested lists). 14 | `varargs` and `varkw` are the names of the * and ** arguments or None. 15 | `defaults` is an n-tuple of the default values of the last n arguments. 16 | 17 | ```python 18 | >>> from pydu.inspect import getargspec 19 | >>> def f(name, address='home', age=25, *args, **kwargs): 20 | ... pass 21 | ... 22 | >>> getargspect(f) 23 | ArgSpec(args=['name', 'address', 'age'], varargs='args', keywords='kwargs', defaults=('home', 25)) 24 | ``` 25 | 26 | 27 | ## inspect.get_func_args 28 | ```python 29 | get_func_args(func) 30 | ``` 31 | 32 | Return a list of the argument names. Arguments such as 33 | `*args` and `**kwargs` are not included. 34 | 35 | ```python 36 | >>> from pydu.inspect import get_func_args 37 | >>> def f(name, address='home', age=25, *args, **kwargs): 38 | ... pass 39 | ... 40 | >>> get_func_args(f) 41 | ['name', 'address', 'age'] 42 | ``` 43 | 44 | 45 | ## inspect.get_func_full_args 46 | ```python 47 | get_func_full_args(func) 48 | ``` 49 | 50 | Return a list of (argument name, default value) tuples. If the argument 51 | does not have a default value, omit it in the tuple. Arguments such as 52 | `*args` and `**kwargs` are also included. 53 | 54 | ```python 55 | >>> from pydu.inspect import get_func_full_args 56 | >>> def f(name, address='home', age=25, *args, **kwargs): 57 | ... pass 58 | ... 59 | >>> get_func_full_args(f) 60 | [('name',), ('address', 'home'), ('age', 25), ('*args',), ('**kwargs',)] 61 | ``` 62 | 63 | 64 | ## inspect.func_accepts_kwargs 65 | ```python 66 | func_accepts_kwargs(func) 67 | ``` 68 | 69 | Check whether or not the func accepts kwargs. 70 | 71 | ```python 72 | >>> from pydu.inspect import func_accepts_kwargs 73 | >>> def f(**kwargs): 74 | ... pass 75 | ... 76 | >>> func_accepts_kwargs(f) 77 | True 78 | ``` 79 | 80 | 81 | ## inspect.func_accepts_var_args 82 | ```python 83 | func_accepts_var_args(func) 84 | ``` 85 | 86 | Check whether or not the func accepts var args. 87 | 88 | ```python 89 | >>> from pydu.inspect import func_accepts_var_args 90 | >>> def f(*vargs): 91 | ... pass 92 | ... 93 | >>> func_accepts_var_args(f) 94 | True 95 | ``` 96 | 97 | 98 | ## inspect.func_supports_parameter 99 | ```python 100 | func_supports_parameter(func) 101 | ``` 102 | 103 | Check whether or the func supports the given parameter. 104 | 105 | ```python 106 | >>> from pydu.inspect import func_supports_parameter 107 | >>> def f(name): 108 | ... pass 109 | ... 110 | >>> func_supports_parameter(f, 'name') 111 | True 112 | >>> func_supports_parameter(f, 'unkown') 113 | Fasle 114 | ``` 115 | 116 | 117 | ## inspect.func_has_no_args 118 | ```python 119 | func_has_no_args(func) 120 | ``` 121 | 122 | Check whether or not the func has any args. 123 | 124 | ```python 125 | >>> from pydu.inspect import func_has_no_args 126 | >>> def f(): 127 | ... pass 128 | ... 129 | >>> func_has_no_args(f) 130 | True 131 | ``` 132 | -------------------------------------------------------------------------------- /docs/iter.md: -------------------------------------------------------------------------------- 1 | # iter 2 | 3 | Utils for handling iterations. 4 | 5 | ## iter.first 6 | ```python 7 | first(iterable) 8 | ``` 9 | 10 | Get the first item in the iterable. 11 | 12 | ```python 13 | >>> from pydu.iter import first 14 | >>> first([1, 2]) 15 | 1 16 | ``` 17 | 18 | 19 | ## iter.last 20 | ```python 21 | last(iterable) 22 | ``` 23 | 24 | Get the last item in the iterable. 25 | Warning, this can be slow due to iter step by step to last one. 26 | 27 | ```python 28 | >>> from pydu.iter import last 29 | >>> last([1, 2]) 30 | 2 31 | ``` 32 | 33 | 34 | ## iter.all 35 | ```python 36 | all(iterable, predicate) 37 | ``` 38 | 39 | Returns True if all elements in the given iterable are True for the 40 | given predicate function. 41 | 42 | ```python 43 | >>> from pydu.iter import all 44 | >>> all([0, 1, 2], lambda x: x+1) 45 | True 46 | ``` 47 | 48 | 49 | ## iter.any 50 | ```python 51 | any(iterable) 52 | ``` 53 | 54 | Returns True if any element in the given iterable is True for the 55 | given predicate function. 56 | 57 | ```python 58 | >>> from pydu.iter import any 59 | >>> any([-1, -1, 0], lambda x: x+1) 60 | True 61 | ``` 62 | 63 | 64 | ## iter.join 65 | ```python 66 | join(iterable, separator='') 67 | ``` 68 | 69 | Join each item of iterable to string. 70 | 71 | ```python 72 | >>> from pydu.iter import join 73 | >>> join([1, '2', 3], separator=',') 74 | '1,2,3' 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/list.md: -------------------------------------------------------------------------------- 1 | # list 2 | 3 | Utils for handling list. 4 | 5 | ## list.uniq 6 | ```python 7 | uniq(seq, key=None) 8 | ``` 9 | 10 | Removes duplicate elements from a list while preserving the order of the rest. 11 | 12 | The value of the optional `key` parameter should be a function that 13 | takes a single argument and returns a key to test the uniqueness. 14 | 15 | ```python 16 | >>> from pydu.list import uniq 17 | >>> uniq([1, 4, 0, 2, 0, 3]) 18 | [1, 4, 0, 2, 3] 19 | ``` 20 | 21 | 22 | ## list.tolist 23 | ```python 24 | tolist(obj) 25 | ``` 26 | 27 | Convert given `obj` to list. 28 | 29 | If `obj` is not a list, return `[obj]`, else return `obj` itself. 30 | 31 | ```python 32 | >>> from pydu.list import tolist 33 | >>> tolist('foo') 34 | ['foo'] 35 | ``` 36 | 37 | 38 | ## list.flatten 39 | ```python 40 | flatten(seq) 41 | ``` 42 | 43 | Generate each element of the given `seq`. If the element is iterable and 44 | is not string, it yields each sub-element of the element recursively. 45 | 46 | ```python 47 | >>> from pydu.list import flatten 48 | >>> flatten([1, [2, [3, 4]]]) 49 | [1, 2, 3, 4] 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/misc.md: -------------------------------------------------------------------------------- 1 | # misc 2 | 3 | Miscellaneous utils like `timeout`, `trace` and so on. 4 | 5 | ## misc.timeout 6 | ```python 7 | timeout(seconds) 8 | ``` 9 | 10 | This func decorates any func which may be hang for a while. The param `seconds` 11 | can be either integer or float. 12 | In `test.py`, you may write like below: 13 | 14 | ```python 15 | import time 16 | from pydu.misc import unix_timeout 17 | @timeout(1) 18 | def f(): 19 | time.sleep(1.01) 20 | f() 21 | ``` 22 | 23 | And run `test.py`, will see `TimeoutError`. 24 | 25 | 26 | ## misc.trace 27 | ```python 28 | trace(obj) 29 | ``` 30 | 31 | Tracing every statement and line number for running program, like `bash -x`. 32 | In `test.py`, you may write like below: 33 | 34 | ```python 35 | from pydu.misc import trace 36 | @trace 37 | def f(): 38 | print(1) 39 | a = 1 + 5 40 | b = [a] 41 | print(2) 42 | f() 43 | ``` 44 | 45 | And run `test.py`, will see below output from console: 46 | 47 | ```console 48 | test.py(4): print(1) 49 | 1 50 | test.py(5): a = 1 + 5 51 | test.py(6): b = [a] 52 | test.py(7): print(2) 53 | 2 54 | ``` 55 | 56 | 57 | ## misc.memoize 58 | ```python 59 | memoize(obj) 60 | ``` 61 | 62 | A simple memoize decorator for functions supporting (hashable) 63 | positional arguments. 64 | It also provides a `cache_clear()` function for clearing the cache. 65 | 66 | ```python 67 | >>> @memoize 68 | ... def foo() 69 | ... return 1 70 | ... 71 | >>> foo() 72 | 1 73 | >>> foo.cache_clear() 74 | >>> 75 | ``` 76 | 77 | 78 | ## misc.memoize_when_activated 79 | ```python 80 | memoize_when_activated(obj) 81 | ``` 82 | 83 | A memoize decorator which is disabled by default. It can be 84 | activated and deactivated on request. 85 | For efficiency reasons it can be used only against class methods 86 | accepting no arguments. 87 | 88 | ```python 89 | >>> class Foo: 90 | ... @memoize 91 | ... def foo() 92 | ... print(1) 93 | ... 94 | >>> f = Foo() 95 | >>> # deactivated (default) 96 | >>> foo() 97 | 1 98 | >>> foo() 99 | 1 100 | >>> 101 | >>> # activated 102 | >>> foo.cache_activate() 103 | >>> foo() 104 | 1 105 | >>> foo() 106 | >>> foo() 107 | >>> 108 | ``` 109 | 110 | 111 | ## misc.super_len 112 | ```python 113 | super_len(obj) 114 | ``` 115 | 116 | Get length of object which has attribute named `__len__`, `len`, `fileno`, `tell`, 117 | such as `list`, `tuple`, `dict`, `file` and so on. 118 | 119 | ```python 120 | >>> from pydu.misc import super_len 121 | >>> super_len([1, 2]) 122 | 2 123 | >>> super_len(open('test', 'w')) 124 | 0 125 | ``` 126 | -------------------------------------------------------------------------------- /docs/network.md: -------------------------------------------------------------------------------- 1 | # network 2 | 3 | Utils for handling network. 4 | 5 | ## network.dotted_netmask 6 | ```python 7 | dotted_netmask(mask) 8 | ``` 9 | 10 | Converts mask from /`xx` format to `xxx.xxx.xxx.xxx`. 11 | `mask` can be either `int` or `str`. 12 | 13 | ```python 14 | >>> from pydu.network import dotted_netmask 15 | >>> dotted_netmask('24') 16 | '255.255.255.0' 17 | >>> dotted_netmask(24) 18 | '255.255.255.0' 19 | ``` 20 | 21 | 22 | ## network.private_ipv4s 23 | 24 | ```python 25 | private_ipv4s 26 | ``` 27 | 28 | A list of private ipv4 addresses. Each item is a tuple of 29 | (ipv4 address, mask). 30 | 31 | ## network.is_ipv4 32 | ```python 33 | is_ipv4(ip) 34 | ``` 35 | 36 | Judge whether the given `ip` is IPV4 address. 37 | 38 | ```python 39 | >>> from pydu.network import is_ipv4 40 | >>> is_ipv4('8.8.8.8') 41 | True 42 | >>> is_ipv4('localhost.localdomain') 43 | False 44 | ``` 45 | 46 | 47 | ## network.is_ipv6 48 | ```python 49 | is_ipv6(ip) 50 | ``` 51 | 52 | Judge whether the given `ip` is IPV6 address. 53 | 54 | ```python 55 | >>> from pydu.network import is_ipv6 56 | >>> is_ipv6('fe80::9e5b:b149:e187:1a18') 57 | True 58 | >>> is_ipv6('localhost.localdomain') 59 | False 60 | ``` 61 | 62 | 63 | ## network.get_free_port 64 | ```python 65 | get_free_port() 66 | ``` 67 | 68 | Get free port which could be bound. 69 | 70 | ```python 71 | >>> from pydu.network import get_free_port 72 | >>> get_free_port() 73 | 57118 74 | ``` 75 | 76 | 77 | ## network.ip2int 78 | ```python 79 | ip2int(ip_str) 80 | ``` 81 | 82 | Convert ip to integer. Support IPV4 and IPV6. 83 | Raise `ValueError` if convert failed. 84 | 85 | ```python 86 | >>> from pydu.network import ip2int 87 | >>> ip2int('10.1.1.1') 88 | 167837953 89 | ``` 90 | 91 | 92 | ## network.int2ip 93 | ```python 94 | int2ip(ip_int) 95 | ``` 96 | 97 | Convert integer to ip. Support IPV4 and IPV6. 98 | Raise `ValueError` if convert failed. 99 | 100 | ```python 101 | >>> from pydu.network import int2ip 102 | >>> int2ip(167837953) 103 | '10.1.1.1' 104 | ``` 105 | -------------------------------------------------------------------------------- /docs/path.md: -------------------------------------------------------------------------------- 1 | # path 2 | 3 | Utils for handling path. 4 | 5 | ## path.cd 6 | ```python 7 | cd(path) 8 | ``` 9 | 10 | Context manager for cd the given path. 11 | 12 | ```python 13 | >>> from pydu.path import cd 14 | >>> with cd('test'): 15 | ... pass 16 | ``` 17 | 18 | 19 | ## path.is_super_path 20 | ```python 21 | is_super_path(path1, path2) 22 | ``` 23 | 24 | Whether `path1` is the super path of `path2`. 25 | Note that if `path1` is same as `path2`, it's also regarded as 26 | the super path os `path2`. 27 | 28 | For instance "/", "/opt" and "/opt/test" are all the super paths of "/opt/test", 29 | while "/opt/t" is the super path of "/opt/test". 30 | 31 | ```python 32 | >>> from pydu.path import is_super_path 33 | >>> is_super_path('/aa/bb/cc', '/aa/bb/cc') 34 | True 35 | >>> is_super_path('/aa/bb', '/aa/bb/cc') 36 | True 37 | >>> is_super_path('/aa/b', '/aa/bb/cc') 38 | False 39 | ``` 40 | 41 | 42 | ## path.normjoin 43 | ```python 44 | normjoin(path) 45 | ``` 46 | 47 | Join one or more path components intelligently and normalize it. 48 | 49 | ```python 50 | >>> from pydu.path import normjoin 51 | >>> normjoin('/a', '../b') 52 | '/b' 53 | ``` 54 | 55 | 56 | ## path.filename 57 | ```python 58 | filename(path) 59 | ``` 60 | 61 | Return the filename without extension. 62 | 63 | ```python 64 | >>> from pydu.path import filename 65 | >>> filename('/foo/bar.ext') 66 | 'bar' 67 | ``` 68 | 69 | 70 | ## path.fileext 71 | ```python 72 | fileext(path) 73 | ``` 74 | 75 | Return the file extension. 76 | If file has not extension, return empty string. 77 | 78 | ```python 79 | >>> from pydu.path import fileext 80 | >>> filename('/foo/bar.ext') 81 | '.ext' 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/platform.md: -------------------------------------------------------------------------------- 1 | # platform 2 | 3 | Constants which indicates specific platform. 4 | 5 | ## platform.WINDOWS 6 | 7 | Judge whether current platform is WINDOWS or not. 8 | 9 | 10 | ## platform.LINUX 11 | 12 | Judge whether current platform is LINUX or not. 13 | 14 | 15 | ## platform.POSIX 16 | 17 | Judge whether current platform is POSIX or not. 18 | 19 | 20 | ## platform.DARWIN 21 | 22 | Judge whether current platform is DARWIN or not. 23 | 24 | 25 | ## platform.SUNOS 26 | 27 | Judge whether current platform is SUNOS or not. 28 | 29 | 30 | ## platform.SMARTOS 31 | 32 | Judge whether current platform is SMARTOS or not. 33 | 34 | 35 | ## platform.FREEBSD 36 | 37 | Judge whether current platform is FREEBSD or not. 38 | 39 | 40 | ## platform.NETBSD 41 | 42 | Judge whether current platform is NETBSD or not. 43 | 44 | 45 | ## platform.OPENBSD 46 | 47 | Judge whether current platform is OPENBSD or not. 48 | 49 | 50 | ## platform.AIX 51 | 52 | Judge whether current platform is AIX or not. 53 | -------------------------------------------------------------------------------- /docs/process.md: -------------------------------------------------------------------------------- 1 | # process 2 | 3 | Utils for handling processes. 4 | 5 | `process` is based on `psutil`. Need to `pip install psutil` first. 6 | 7 | 8 | ## process.get_processes_by_path 9 | ```python 10 | get_processes_by_path(path) 11 | ``` 12 | 13 | Get processes which are running on given path or sub path of given path. 14 | 15 | ```python 16 | >>> from pydu.process import get_processes_by_path 17 | >>> get_processes_by_path('/usr/bin/python') 18 | [{'cmdline': '/usr/bin/python2.7', 'pid': 23383, 'name': 'python'}] 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/request.md: -------------------------------------------------------------------------------- 1 | # Request 2 | 3 | Utils for handling request. 4 | 5 | ## request.Filename 6 | 7 | Supply several methods to get filename. 8 | 9 | ```python 10 | Filename.from_url(url) 11 | ``` 12 | 13 | Detected filename as unicode or None. 14 | 15 | ```python 16 | Filename.from_headers(headers) 17 | ``` 18 | 19 | Detect filename from Content-Disposition headers if present. 20 | `headers` could be a dict, list or string. 21 | 22 | ```python 23 | Filename.from_any(dst=None, headers=None, url=None) 24 | ``` 25 | 26 | Detect filename from dst or headers or url. 27 | 28 | 29 | ## request.download 30 | ```python 31 | Filename.download(url, dst=None) 32 | ``` 33 | 34 | High level function, which downloads URL into tmp file in current 35 | directory and then renames it to filename autodetected from either URL 36 | or HTTP headers. 37 | `url` indicates which url to download. 38 | `dst` is the filename or directory of destination. `None` as default, means 39 | download to current directory. 40 | 41 | 42 | ## request.check_connect 43 | ```python 44 | check_connect(ip, port, retry=1, timeout=0.5) 45 | ``` 46 | 47 | Check whether given `ip` and `port` could connect or not. 48 | It will `retry` and `timeout` on given. 49 | 50 | ```python 51 | >>> from pydu.request import check_connect 52 | >>> check_connect('http://www.baidu.com', 80) 53 | '192.168.3.8' 54 | ``` 55 | 56 | 57 | ## request.update_query_params 58 | ```python 59 | update_query_params(url, params) 60 | ``` 61 | 62 | Update query params of given url and return new url. 63 | 64 | ```python 65 | >>> from pydu.request import update_query_params 66 | >>> update_query_params('http://example.com', {'foo': 1}) 67 | 'http://example.com?foo=1' 68 | ``` 69 | 70 | 71 | ## request.cookies_str_to_dict 72 | ```python 73 | cookies_str_to_dict(cookies) 74 | ``` 75 | 76 | Convert cookies from str to dict. 77 | 78 | ```python 79 | >>> from pydu.request import cookies_str_to_dict 80 | >>> cookies_str_to_dict('a=a;b=b') 81 | {'a': 'a', 'b': 'b'} 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/set.md: -------------------------------------------------------------------------------- 1 | # Set 2 | 3 | Additional powerful sets. 4 | 5 | ## set.OrderedSet 6 | ```python 7 | OrderedSet(iterable=None) 8 | ``` 9 | 10 | A set which keeps the ordering of the inserted items. 11 | 12 | ```python 13 | >>> from pydu.set import OrderedSet 14 | >>> s = OrderedSet([1, 3, 1, 2]) 15 | >>> list(s) 16 | [1, 3, 2] 17 | >>> s.discard(3) 18 | >>> list(s) 19 | [1, 2] 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/slot.md: -------------------------------------------------------------------------------- 1 | # slot 2 | 3 | ## slot.SlotBase 4 | ```python 5 | SlotBase(*args, **kwargs) 6 | ``` 7 | 8 | Base class for class using `__slots__`. 9 | If some args or kwargs are not given when initialize class, 10 | the value of them will be set with `None`. 11 | 12 | ```python 13 | >>> from pydu.slot import SlotBase 14 | >>> class Foo(SlotBase): 15 | __slots__ = ('a', 'b', 'c') 16 | >>> foo = Foo(1, b=2) 17 | >>> foo.a 18 | 1 19 | >>> foo.b 20 | 2 21 | >>> foo.c 22 | >>> 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/string.md: -------------------------------------------------------------------------------- 1 | # String 2 | 3 | Utils for handling string. 4 | 5 | ## string.safeunicode 6 | ```python 7 | safeunicode(obj, encoding='utf-8') 8 | ``` 9 | 10 | Converts any given object to unicode string. 11 | 12 | ```python 13 | >>> from pydu.string import safeunicode 14 | >>> safeunicode('hello') 15 | u'hello' 16 | >>> safeunicode(2) 17 | u'2' 18 | >>> safeunicode('\xe4\xb8\xad\xe6\x96\x87') 19 | u'中文' 20 | ``` 21 | 22 | 23 | ## string.safeencode 24 | ```python 25 | safeencode(obj, encoding='utf-8') 26 | ``` 27 | 28 | Converts any given object to encoded string (default: utf-8). 29 | 30 | ```python 31 | >>> from pydu.string import safeencode 32 | >>> safeencode('hello') 33 | 'hello' 34 | >>> safeencode(2) 35 | '2' 36 | >>> safeencode(u'中文') 37 | '\xe4\xb8\xad\xe6\x96\x87' 38 | ``` 39 | 40 | 41 | ## string.lstrips 42 | ```python 43 | lstrips(text, remove) 44 | ``` 45 | 46 | Removes the string `remove` from the left of `text`. 47 | 48 | ```python 49 | >>> from pydu.string import lstrips 50 | >>> lstrips('foobar', 'foo') 51 | 'bar' 52 | >>> lstrips('FOOBARBAZ', ['FOO', 'BAR']) 53 | 'BAZ' 54 | >>> lstrips('FOOBARBAZ', ['BAR', 'FOO']) 55 | 'BARBAZ' 56 | ``` 57 | 58 | 59 | ## string.rstrips 60 | ```python 61 | rstrips(text, remove) 62 | ``` 63 | 64 | Removes the string `remove` from the right of `text`. 65 | 66 | ```python 67 | >>> from pydu.string import rstrips 68 | >>> rstrips('foobar', 'bar') 69 | 'foo' 70 | ``` 71 | 72 | 73 | ## string.strips 74 | ```python 75 | strips(text, remove) 76 | ``` 77 | 78 | Removes the string `remove` from the both sides of `text`. 79 | 80 | ```python 81 | >>> from pydu.string import strips 82 | >>> strips('foobarfoo', 'foo') 83 | 'bar' 84 | ``` 85 | 86 | ## string.common_prefix 87 | ```python 88 | common_prefix(l) 89 | ``` 90 | 91 | Return common prefix of the stings 92 | 93 | ```python 94 | >>> from pydu.string import common_prefix 95 | >>> common_prefix(['abcd', 'abc1']) 96 | 'abc' 97 | ``` 98 | 99 | 100 | ## string.common_suffix 101 | ```python 102 | common_suffix(l) 103 | ``` 104 | 105 | Return common suffix of the stings 106 | 107 | ```python 108 | >>> from pydu.string import common_suffix 109 | >>> common_suffix(['dabc', '1abc']) 110 | 'abc' 111 | ``` 112 | 113 | 114 | ## string.sort 115 | ```python 116 | sort(s, reversed=False) 117 | ``` 118 | 119 | Sort given string by ascending order. 120 | If `reverse` is `True`, sorting given string by descending order. 121 | 122 | ```python 123 | >>> from pydu.string import sort 124 | >>> sort('dabc') 125 | 'abcd' 126 | ``` 127 | -------------------------------------------------------------------------------- /docs/system.md: -------------------------------------------------------------------------------- 1 | # System 2 | 3 | Utils for handling system, like to track file, make directory, link and so on. 4 | 5 | 6 | ## system.FileTracker 7 | ```python 8 | FileTracker() 9 | ``` 10 | 11 | Track current opening files, started with `FileTracker.track()`. 12 | When opening several files, `FileTracker` tracks them and you can locate them by calling 13 | `FileTraker.get_openfiles()`. 14 | 15 | ```python 16 | FiltTracker.track() 17 | ``` 18 | 19 | Start tracking opening files. 20 | 21 | ```python 22 | FiltTracker.untrack() 23 | ``` 24 | 25 | Stop tracking opening files. 26 | 27 | ```python 28 | FiltTracker.get_openfiles() 29 | ``` 30 | 31 | Get current opening files. 32 | 33 | ```python 34 | >>> from pydu.system import FileTracker 35 | >>> FileTracker.track() 36 | >>> f = open('test', 'w') 37 | >>> FileTracker.get_openfiles() 38 | {<_io.TextIOWrapper name='test' mode='w' encoding='UTF-8'>} 39 | >>> f.close() 40 | >>> FileTracker.get_openfiles() 41 | set() 42 | >>> FileTracker.untrack() 43 | >>> f = open('test', 'w') 44 | >>> FileTracker.get_openfiles() 45 | set() 46 | ``` 47 | 48 | 49 | ## system.makedirs 50 | ```python 51 | makedirs(path, mode=0o755, ignore_errors=False, exist_ok=False) 52 | ``` 53 | 54 | Based on `os.makedirs`,create a leaf directory and all intermediate ones. 55 | `mode` default is `0o755`. When make an exists path, if exist_ok is false, 56 | `makedirs` will raise an `Exception`. If `ignore_errors` which will ignore 57 | all errors raised by `os.makedirs`. 58 | 59 | ```python 60 | >>> from pydu.system import makedirs 61 | >>> makedirs('test1/test2') 62 | >>> makedirs('test1',exist_ok=True) 63 | >>> makedirs('test1') 64 | Traceback (most recent call last): 65 | ... OSError: Create dir: test1 error. 66 | ``` 67 | 68 | ## system.remove 69 | ```python 70 | remove(path, mode=0o755, ignore_errors=False, onerror) 71 | ``` 72 | 73 | Remove a file or directory. 74 | 75 | If `ignore_errors` is set, errors are ignored; otherwise, if `onerror` 76 | is set, it is called to handle the error with arguments (`func` , 77 | `path` , `exc_info` ) where func is platform and implementation dependent; 78 | `path` is the argument to that function that caused it to fail; and 79 | `exc_info` is a tuple returned by `sys.exc_info()`. If `ignore_errors` 80 | is `False` and `onerror` is None, it attempts to set `path` as writeable and 81 | then proceed with deletion if `path` is read-only, or raise an exception 82 | if `path` is not read-only. 83 | 84 | ```python 85 | >>> from pydu.system import makedirs 86 | >>> from pydu.system import remove 87 | >>> from pydu.system import touch 88 | >>> makedirs('test') 89 | >>> remove('test') 90 | >>> touch('test.txt') 91 | >>> remove('test.txt') 92 | >>> remove('test.txt', ignore_errors=True) 93 | >>> remove('test.txt') 94 | Traceback (most recent call last): 95 | ... OSError: Remove path: test error. Reason: [Errno 2] No such file or directory: 'test.txt' 96 | ``` 97 | 98 | ## system.removes 99 | ```python 100 | removes(paths, mode=0o755, ignore_errors=False, onerror) 101 | ``` 102 | 103 | Remove a list of file and/or directory.Other parameters same as `remove`. 104 | 105 | ```python 106 | >>> from pydu.system import makedirs 107 | >>> from pydu.system import remove 108 | >>> from pydu.system import open_file 109 | >>> makedirs('test1') 110 | >>> makedirs('test2') 111 | >>> open_file('test.txt') 112 | >>> removes(['test.txt','test1','test2']) 113 | ``` 114 | 115 | ## system.open_file 116 | ```python 117 | open_file(path, mode='wb+', buffer_size=-1, ignore_errors=False): 118 | ``` 119 | 120 | Open a file, defualt mode `wb+`. If path not exists, it will be created 121 | automatically. If `ignore_errors` is set, errors are ignored. 122 | 123 | ```python 124 | >>> from pydu.system import open_file 125 | >>> open_file('test.txt') 126 | >>> ls 127 | test.txt 128 | >>> open_file('test1.txt',mode='r') 129 | Traceback (most recent call last): 130 | ... OSError: Open file: test1.txt error 131 | ``` 132 | 133 | ## system.copy 134 | ```python 135 | copy(src, dst, ignore_errors=False, follow_symlinks=True): 136 | ``` 137 | 138 | Copy data and mode bits (`cp src dst`).Both the source and destination 139 | may be a directory.When `copy` a directory,which contains a symlink,if 140 | the optional symlinks flag is true, symbolic links in the source tree 141 | result in symbolic links in the destination tree; if it is false, the 142 | contents of the files pointed to by symbolic links are copied.When copy 143 | a file,if follow_symlinks is false and src is a symbolic link, a new 144 | symlink will be created instead of copying the file it points to,else 145 | the contents of the file pointed to by symbolic links is copied. 146 | 147 | ```python 148 | >>> from pydu.system import copy,symlink 149 | >>> from pydu.system import makedirs,open_fle 150 | >>> open_fle('test/test.txt') 151 | >>> symlink('test/test.txt','test/test.link') 152 | >>> copy('test/test.link','test/test_copy1.link') 153 | >>> copy('test/test.link','test/test_copy2.link',follow_symlink=False) 154 | ``` 155 | 156 | ## system.touch 157 | ```python 158 | touch(path): 159 | ``` 160 | 161 | Make a new file. 162 | 163 | ```python 164 | >>> from pydu.system import touch 165 | >>> touch('test.txt') 166 | ``` 167 | 168 | ## system.symlink 169 | ```python 170 | symlink(src, dst, overwrite=False, ignore_errors=False) 171 | ``` 172 | 173 | It create a symbolic link pointing 174 | to source named link_name.If dist is exist and overwrite is true,a new 175 | symlink will be created. 176 | 177 | ```python 178 | >>> from pydu.system import symlink 179 | >>> symlink('test.txt','test.link') 180 | ``` 181 | 182 | !> `symlink` can only be used on `unix-like` system. 183 | 184 | ## system.link 185 | ```python 186 | link(src, dst, overwrite=False, ignore_errors=False): 187 | ``` 188 | 189 | It create a hard link pointing to 190 | source named link_name.If dist is exist and overwrite is true, a 191 | new link will be created. 192 | 193 | ```python 194 | >>> from pydu.system import link 195 | >>> link('test.txt','test.link') 196 | ``` 197 | 198 | !> `link` can only be used on `unix-like` system. 199 | 200 | 201 | ## system.which 202 | ```python 203 | which(cmd, mode=os.F_OK | os.X_OK, path=None): 204 | ``` 205 | 206 | Given a command, mode, and a PATH string, return the path which 207 | conforms to the given mode on the PATH, or None if there is no such 208 | file. 209 | 210 | `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result 211 | of os.environ.get("PATH"), or can be overridden with a custom search 212 | path. 213 | 214 | `which` is `shutil.which` in Python 3. 215 | 216 | ```python 217 | >>> from pydu.system import which 218 | >>> which('echo') 219 | /bin/echo 220 | ``` 221 | 222 | 223 | ## system.chmod 224 | ```python 225 | chmod(path, mode, recursive=False) 226 | ``` 227 | 228 | Change permissions to the given mode. 229 | If `recursive` is True perform recursively. 230 | 231 | ```python 232 | >>> from pydu.system import chmod 233 | >>> chmod('/opt/sometest', 0o744) 234 | >>> oct(os.stat('/opt/sometest').st_mode)[-3:] 235 | '744' 236 | ``` 237 | 238 | !> Although Windows supports `chmod`, you can only set the file’s 239 | read-only flag with it (via the stat.S_IWRITE and stat.S_IREAD constants 240 | or a corresponding integer value). All other bits are ignored. 241 | 242 | 243 | ## system.chcp 244 | ```python 245 | chcp(code) 246 | ``` 247 | 248 | Context manager which sets the active code page number. 249 | It could also be used as function. 250 | 251 | ```python 252 | >>> from pydu.system import chcp 253 | >>> chcp(437) 254 | 255 | >>> with chcp(437): 256 | ... pass 257 | >>> 258 | ``` 259 | 260 | !> `chcp` can only be used on `Windows` system. 261 | 262 | 263 | ## system.preferredencoding 264 | ```python 265 | preferredencoding(code) 266 | ``` 267 | 268 | Get best encoding for the system. 269 | -------------------------------------------------------------------------------- /docs/unit.md: -------------------------------------------------------------------------------- 1 | # Unit 2 | 3 | Utils for handling unit. 4 | 5 | ## unit.Bytes 6 | ```python 7 | Bytes(bytes) 8 | ``` 9 | 10 | Supply several methods dealing with bytes. 11 | 12 | ```python 13 | Bytes.convert(self, unit=None, multiple=1024) 14 | ``` 15 | 16 | Convert bytes with given `unit`. 17 | If `unit` is `None`, convert bytes with suitable unit. 18 | Convert `multiple` is default to be 1024. 19 | 20 | ```python 21 | >>> Bytes(1024).convert() 22 | (1, 'KB') 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/zh-cn/README.md: -------------------------------------------------------------------------------- 1 | ## pydu 2 | 3 | > **pydu(Python Data structures and Utils)** 是面向Python 2 和 3 的实用数据结构和工具库。 4 | 它收集自开源项目,也有来自开发者贡献。 5 | 6 | 7 | ## 安装 8 | 要安装**pydu**,简单执行: 9 | 10 | ```bash 11 | $ pip install pydu 12 | ``` 13 | -------------------------------------------------------------------------------- /docs/zh-cn/_sidebar.md: -------------------------------------------------------------------------------- 1 | * 模块 2 | 3 | * [Archive 归档](zh-cn/archive.md) 4 | * [Commad 命令](zh-cn/cmd.md) 5 | * [Compat 兼容性](zh-cn/compat.md) 6 | * [Console 控制台](zh-cn/console.md) 7 | * [Convert 转换](zh-cn/convert.md) 8 | * [Dict 字典](zh-cn/dict.md) 9 | * [Date and Time 时间](zh-cn/dt.md) 10 | * [Environment 环境](zh-cn/environ.md) 11 | * [Exception 异常](zh-cn/exception.md) 12 | * [Functional 函数式](zh-cn/functional.md) 13 | * [Inspect 检查](zh-cn/inspect.md) 14 | * [Iter 迭代](zh-cn/iter.md) 15 | * [List 列表](zh-cn/list.md) 16 | * [Miscellanea 综合](zh-cn/misc.md) 17 | * [Network 网络](zh-cn/network.md) 18 | * [Path 路径](zh-cn/path.md) 19 | * [Platform 平台](zh-cn/platform.md) 20 | * [Process 进程](zh-cn/process.md) 21 | * [Request 请求](zh-cn/request.md) 22 | * [Set 集合](zh-cn/set.md) 23 | * [Slot 槽](zh-cn/slot.md) 24 | * [String 字符串](zh-cn/string.md) 25 | * [System 系统](zh-cn/system.md) 26 | * [Unit 单位](zh-cn/unit.md) 27 | 28 | * [变更记录](changelog.md) 29 | -------------------------------------------------------------------------------- /docs/zh-cn/archive.md: -------------------------------------------------------------------------------- 1 | # archive 2 | 3 | 提供归档相关工具,如解压。 4 | 5 | ## archive.extract 6 | ```python 7 | extract(path, to_path='', ext='') 8 | ``` 9 | 10 | 解压tar或zip文件,可指定 ``to_path`` 解压到特定目录。它支持很多文件格式,包括 " 11 | "``.tar``、``.tar.bz2``、``.tar.gz``、``.tgz``、``.tz2``、``.zip``。如果给定的 " 12 | "``path`` 不包含文件格式,则可指定 ``ext`` 参数来说明文件格式。 13 | 14 | ```python 15 | >>> from pydu.archive import extract 16 | >>> extract('foobar.tgz', '/tmp') 17 | >>> extract('foobar', '/tmp', ext='.tgz') 18 | >>> extract('foobar', '/tmp') 19 | Traceback (most recent call last): 20 | ... AttributeError: pydu.archive.UnrecognizedArchiveFormat: Path not a recognized archive format: foobar 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/zh-cn/cmd.md: -------------------------------------------------------------------------------- 1 | # cmd 2 | 3 | 提供运行命令和获取命令行等工具。 4 | 5 | ## cmd.TimeoutExpired 6 | ```python 7 | TimeoutExpired(cmd, timeout, output=None, stderr=None) 8 | ``` 9 | 10 | 该异常在等待子进程超时时抛出。 11 | 12 | 属性: 13 | cmd, output, stdout, stderr, timeout 14 | 15 | 16 | ## cmd.run 17 | ```python 18 | run(cmd, shell=False, env=None, timeout=None, timeinterval=1) 19 | ``` 20 | 21 | Run 命令基于 `subprocess.Popen` ,并返回 `(returncode, stdout)` 的这样元组。 22 | 23 | 注意,`stderr` 被重定向到了 `stdout`。`shell` 同 `Popen` 中的参数一样。 24 | 25 | 如果在 `timeout` 秒后进程没有退出,将会抛出 `TimeoutExpired` 异常。`timeinterval` 26 | 在Python 2中给定 timeout时生效。它表示进程状态检查时间间隔。 27 | 28 | 如果超时了,子进程不会被杀掉。为了合理清除表现良好的应用,应该要杀掉子进程,并且结束通信。 29 | 30 | ```python 31 | >>> from pydu.cmd import run 32 | >>> run('echo hello') 33 | (0, b'hello\r\n') # Python 3 34 | ``` 35 | 36 | 37 | ## cmd.run_with_en_env 38 | ```python 39 | run_with_en_env(cmd, shell=False, env=None, timeout=None, timeinterval=1) 40 | ``` 41 | 42 | 在英文字符集环境下运行命令,从而得到英文输出。参数同 `run`。 43 | 44 | 45 | ## cmd.terminate 46 | ```python 47 | terminate(pid) 48 | ``` 49 | 50 | 根据给定 `pid` 终止进程。 51 | 52 | 在Windows上,使用 `kernel32.TerminateProcess` 来终止。在其他平台上,使用携带 `signal.SIGTERM` 信号的 `os.kill` 来终止。 53 | 54 | 55 | ## cmd.cmdline_argv 56 | ```python 57 | cmdline_argv() 58 | ``` 59 | 60 | 获取当前Python进程的命令行参数。在Windows上使用Python 2时, `cmdline_argv` 的 61 | 实现是基于 `shell32.GetCommandLineArgvW` 获取列表元素为Unicode字符串形式的`sys.argv`。 62 | 63 | 而在其他平台或者是使用Python 3时, `cmdline_argv` 和 `sys.argv` 相同。 64 | 65 | ```python 66 | >>> from pydu.cmd import cmdline_argv 67 | >>> cmdline_argv() 68 | ['/Applications/PyCharm.app/Contents/helpers/pydev/pydevconsole.py', '61253', '61254'] 69 | ``` 70 | -------------------------------------------------------------------------------- /docs/zh-cn/compat.md: -------------------------------------------------------------------------------- 1 | # compat 2 | 3 | 提供Python 2和3兼容的数据结构、库和函数。 4 | 5 | ## compat.PY2 6 | 7 | 判断当前Python解释器是Python 2还是3。 8 | 9 | 10 | ## compat.urlib 11 | ```python 12 | urlib(base, url, allow_fragments=True) 13 | ``` 14 | 15 | 在PY2中是 ``urllib``,在PY3中是 ``urllib.request``。 16 | 17 | 18 | ## compat.urlparse 19 | ```python 20 | urlparse(base, url, allow_fragments=True) 21 | ``` 22 | 23 | 在PY2中是 ``urlparse``,在PY3中是 ``urllib.parse``。 24 | 25 | 26 | ## compat.urljoin 27 | ```python 28 | urljoin(base, url, allow_fragments=True) 29 | ``` 30 | 31 | 在PY2中是 ``urlparse.urljoin``,在PY3中是 ``urllib.parse.urljoin``。 32 | 33 | 34 | ## compat.iterkeys 35 | ```python 36 | iterkeys(d) 37 | ``` 38 | 39 | 返回字典键的iter对象。 40 | 41 | 42 | ## compat.itervalues 43 | ```python 44 | itervalues(d) 45 | ``` 46 | 47 | 返回字典值的iter对象。 48 | 49 | 50 | ## compat.iteritems 51 | ```python 52 | iteritems(d) 53 | ``` 54 | 55 | 返回字典键值对的iter对象。 56 | 57 | 58 | ## compat.text_type 59 | 60 | text类型在PY2中是 ``unicode``,在PY3中是 ``str``。 61 | 62 | 63 | ## compat.string_types 64 | 65 | string类型在PY2中是 ``(str, unicode)``,在PY3中是 ``(str,)``。 66 | 67 | ## compat.strbytes_types 68 | 69 | strbytes(string bytes)类型在PY2中是 ``(str, unicode, bytes)``,在PY3中是 ``(str, " 70 | "bytes)``。 71 | 72 | 73 | ## compat.numeric_types 74 | 75 | 在PY2中是 ``(int, long)``,在PY3中是 ``(int,)``。 76 | 77 | 78 | ## compat.imap 79 | ```python 80 | imap(func, *iterables) 81 | ``` 82 | 83 | 在PY2中是 ``itertools.imap``,在PY3中是 ``map``。 84 | 85 | 86 | ## compat.izip 87 | ```python 88 | izip(iter1 [,iter2 [...]) 89 | ``` 90 | 91 | 在PY2中是 ``itertools.izip``,在PY3中是 ``zip``。 92 | 93 | 94 | ## compat.reduce 95 | ```python 96 | reduce(function, sequence, initial=None) 97 | ``` 98 | 99 | 在PY2中是内建 ``reduce``,在PY3中是 ``functools.reduce``。 100 | 101 | 102 | ## compat.cmp 103 | ```python 104 | cmp(x, y) 105 | ``` 106 | 107 | Same to ``cmp`` on PY2, but implement on PY3. 108 | 在PY2中是内建 ``cmp``,在PY3中则由pydu实现。 109 | 110 | 111 | ## compat.has_next_attr 112 | ```python 113 | has_next_attr(x) 114 | ``` 115 | 116 | 查看是否有next属性。 117 | 118 | 119 | ## compat.is_iterable 120 | ```python 121 | is_iterable(x) 122 | ``` 123 | 124 | 查看是否是可迭代的。 125 | 126 | ```python 127 | >>> from pydu.compat import is_iterable 128 | >>> is_iterable([]) 129 | True 130 | >>> is_iterable(1) 131 | False 132 | ``` 133 | -------------------------------------------------------------------------------- /docs/zh-cn/console.md: -------------------------------------------------------------------------------- 1 | # Console 2 | 3 | 提供处理控制台的工具。 4 | 5 | ## console.console_size 6 | ```python 7 | console_size(fallback=(80, 25)) 8 | ``` 9 | 10 | 对于Windows系统,返回可用窗口区域的(width, height)。如果没有控制台,则返回fallback。 11 | 对于POSIX系统,返回控制终端的(width, height)。如果遇到IOError,比如没有控制台,返回fallback。 12 | 对于其他系统,返回fallback。Fallback默认为(80, 25),这是大多数终端模拟器的默认大小。 13 | 14 | ```python 15 | >>> from pydu.console import console_size 16 | >>> console_size() 17 | (80, 25) 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/zh-cn/convert.md: -------------------------------------------------------------------------------- 1 | # Convert 2 | 3 | 提供将一类数据转换为另一类的工具。 4 | 5 | 6 | ## convert.boolean 7 | ```python 8 | boolean(obj) 9 | ``` 10 | 11 | 将对象转换为布尔值。 12 | 13 | 如果对象是字符串,将会以不区分大小写的形式转换: 14 | 15 | * 将 `yes`、 `y`、 `on`、 `true`、 `t`、 `1` 转换为True 16 | * 将 `no`、 `n`、 `off`、 `false`、 `f`、 `0` 转换为False 17 | * 如果传入其他值,抛出TypeError 18 | 19 | 如果对象不是字符串,将会使用 `bool(obj)` 转换。 20 | 21 | ```python 22 | >>> from pydu.string import boolean 23 | >>> boolean('yes') 24 | True 25 | >>> boolean('no') 26 | False 27 | ``` 28 | 29 | 30 | ## convert.bin2oct 31 | ```python 32 | bin2oct(x) 33 | ``` 34 | 35 | 把二进制字符串转换为八进制字符串。 36 | 比如:'1001' -> '11' 37 | 38 | ```python 39 | >>> from pydu.convert import bin2oct 40 | >>> bin2oct('1001') 41 | '11' 42 | ``` 43 | 44 | 45 | ## convert.bin2dec 46 | ```python 47 | bin2dec(x) 48 | ``` 49 | 50 | 把二进制字符串转换为十进制数字。 51 | 比如:'11' -> 3 52 | 53 | ```python 54 | >>> from pydu.convert import bin2dec 55 | >>> bin2dec('11') 56 | 3 57 | ``` 58 | 59 | 60 | ## convert.bin2hex 61 | ```python 62 | bin2hex(x) 63 | ``` 64 | 65 | 把二进制字符串转换为十六进制字符串。 66 | 比如:'11010' -> '1a' 67 | 68 | ```python 69 | >>> from pydu.convert import bin2hex 70 | >>> bin2hex('11010') 71 | '1a' 72 | ``` 73 | 74 | 75 | ## convert.oct2bin 76 | ```python 77 | oct2bin(x) 78 | ``` 79 | 80 | 把八进制字符串转换为二进制字符串。 81 | 比如:'11' -> '1001' 82 | 83 | ```python 84 | >>> from pydu.convert import oct2bin 85 | >>> oct2bin('11') 86 | '1001' 87 | ``` 88 | 89 | 90 | ## convert.oct2dec 91 | ```python 92 | oct2dec(x) 93 | ``` 94 | 95 | 把八进制字符串转换为十进制数字。 96 | 比如:'11' -> 9 97 | 98 | ```python 99 | >>> from pydu.convert import oct2dec 100 | >>> oct2dec('11') 101 | 9 102 | ``` 103 | 104 | 105 | ## convert.oct2hex 106 | ```python 107 | oct2hex(x) 108 | ``` 109 | 110 | 把八进制字符串转换为十六进制字符串。 111 | 比如:'32' -> '1a' 112 | 113 | ```python 114 | >>> from pydu.convert import oct2hex 115 | >>> oct2hex('32') 116 | '1a' 117 | ``` 118 | 119 | 120 | ## convert.dec2bin 121 | ```python 122 | dec2bin(x) 123 | ``` 124 | 125 | 把十进制数字转换为二进制字符串。 126 | 比如:3 -> '11' 127 | 128 | ```python 129 | >>> from pydu.convert import dec2bin 130 | >>> dec2bin(3) 131 | '11' 132 | ``` 133 | 134 | 135 | ## convert.dec2oct 136 | ```python 137 | dec2oct(x) 138 | ``` 139 | 140 | 把十进制数字转换为八进制字符串。 141 | 比如:9 -> '11' 142 | 143 | ```python 144 | >>> from pydu.convert import dec2oct 145 | >>> dec2oct(9) 146 | '11' 147 | ``` 148 | 149 | 150 | ## convert.dec2hex 151 | ```python 152 | dec2hex(x) 153 | ``` 154 | 155 | 把十进制数字转换为十六进制字符串。 156 | 比如:26 -> '1a' 157 | 158 | ```python 159 | >>> from pydu.convert import dec2hex 160 | >>> dec2hex(26) 161 | '1a' 162 | ``` 163 | 164 | 165 | ## convert.hex2bin 166 | ```python 167 | hex2bin(x) 168 | ``` 169 | 170 | 把十六进制字符串转换为二进制字符串。 171 | 比如:'1a' -> '11010' 172 | 173 | ```python 174 | >>> from pydu.convert import hex2bin 175 | >>> hex2bin('1a') 176 | '11010' 177 | ``` 178 | 179 | 180 | ## convert.hex2oct 181 | ```python 182 | hex2oct(x) 183 | ``` 184 | 185 | 把十六进制字符串转换为八进制字符串。 186 | 比如:'1a' -> '32' 187 | 188 | ```python 189 | >>> from pydu.convert import hex2oct 190 | >>> hex2oct('1a') 191 | '32' 192 | ``` 193 | 194 | 195 | ## convert.hex2dec 196 | ```python 197 | hex2dec(x) 198 | ``` 199 | 200 | 把十六进制字符串转换为十进制数字。 201 | 比如:'1a' -> 26 202 | 203 | ```python 204 | >>> from pydu.convert import hex2dec 205 | >>> hex2dec('1a') 206 | 26 207 | ``` 208 | -------------------------------------------------------------------------------- /docs/zh-cn/dict.md: -------------------------------------------------------------------------------- 1 | # Dict 2 | 3 | 增强的字典和相关函数。 4 | 5 | ## dict.AttrDict 6 | ```python 7 | AttrDict(seq=None, **kwargs) 8 | ``` 9 | 10 | AttrDict 对象类似于字典,除了能使用 `obj['foo']`,还能使用 `obj.foo`。 11 | 12 | ```python 13 | >>> from pydu.dict import AttrDict 14 | >>> o = AttrDict(a=1) 15 | o.a 16 | 1 17 | >>> o['a'] 18 | 1 19 | >>> o.a = 2 20 | >>> o['a'] 21 | 2 22 | >>> del o.a 23 | >>> o.a 24 | Traceback (most recent call last): 25 | ... AttributeError: 'a' 26 | ``` 27 | 28 | 29 | ## dict.CaseInsensitiveDict 30 | ```python 31 | CaseInsensitiveDict(data=None, **kwargs) 32 | ``` 33 | 34 | 大小写不敏感类 `字典` 对象。实现了 `collections.MutableMapping` 的所有方法和操作, 35 | 也实现了字典的 `copy`,此外还提供 `lower_items`。所有的键都应是字符串。 36 | 内部结构会记住最后一次被设置的键的大小写,`iter(instance)`、`keys()`、`items()`、 37 | `iterkeys()` 和 `iteritems()` 将会包含大小写敏感的键。 38 | 39 | ```python 40 | >>> from pydu.dict import CaseInsensitiveDict 41 | >>> cid = CaseInsensitiveDict() 42 | >>> cid['Accept'] = 'application/json' 43 | >>> cid['aCCEPT'] == 'application/json' 44 | True 45 | >>> list(cid) == ['Accept'] 46 | True 47 | ``` 48 | 49 | 50 | ## dict.LookupDict 51 | ```python 52 | LookupDict(name=None) 53 | ``` 54 | 55 | 字典查找对象。 56 | 57 | ```python 58 | >>> from pydu.dict import LookupDict 59 | >>> d = LookupDict() 60 | >>> d['key'] 61 | None 62 | >>> d['key'] = 1 63 | >>> d['key'] 64 | 1 65 | ``` 66 | 67 | ## dict.OrderedDefaultDict 68 | ```python 69 | OrderedDefaultDict(default_factory=None, *args, **kwds) 70 | ``` 71 | 72 | 记住插入顺序且能根据默认工厂提供默认值的字典。 73 | 74 | 当key不存在(仅限通过 `__getitem__` 中)时,无参数调用默认工厂来产生新值。 75 | `OrderedDefaultDict` 和 `collections.defaultdict` 在比较时是等同的。 76 | 所有剩余参数和传入 `defaultdict` 构造器中的相同,包括关键字参数。 77 | 78 | ```python 79 | >>> from pydu.dict import OrderedDefaultDict 80 | >>> d = OrderedDefaultDict(int) 81 | >>> d['b'] 82 | 0 83 | >>> d['a'] 84 | 0 85 | >>> d.keys() 86 | odict_keys(['b', 'a']) 87 | ``` 88 | 89 | 90 | ## dict.attrify 91 | ```python 92 | attrify(obj) 93 | ``` 94 | 95 | 将对象属性化为 `AttriDict` 或 包含 `AttriDict` 的列表(如果对象为列表)。 96 | 如果对象或对象中的元素不是列表或字典,将会返回其本身。 97 | 98 | ```python 99 | >>> from pydu.dict import attrify 100 | >>> attrd = attrify({ 101 | 'a': [1, 2, {'b': 'b'}], 102 | 'c': 'c', 103 | }) 104 | >>> attrd 105 | ], 'c': 'c'}> 106 | >>> attrd.a 107 | 1 108 | >>> attrd.a[2].b 109 | b 110 | >>> attrd.c 111 | c 112 | ``` 113 | -------------------------------------------------------------------------------- /docs/zh-cn/dt.md: -------------------------------------------------------------------------------- 1 | # Date and Time 2 | 3 | 提供处理日期和时间的工具。 4 | 5 | 6 | ## dt.timer 7 | ```python 8 | timer(path) 9 | ``` 10 | 11 | timer可以上下文管理器或装饰器的方式统计一次调用的时间。 12 | 如果将 `print_func` 赋值为 `sys.stdout.write`、 `logger.info` 或其他, 13 | timer将会打印所花时长。 14 | 15 | ```python 16 | timeit = timer(print_func=sys.stdout.write) 17 | with timeit: 18 | foo() 19 | 20 | @timeit 21 | def foo(): 22 | pass 23 | ``` 24 | 25 | `timer.elapsed` 包含了 `foo` 所花费的整个时间。 26 | 27 | ```python 28 | >>> timeit = timer(print_func=sys.stdout.write) 29 | >>> with timeit: 30 | ... os.getcwd() 31 | Spent time: 1.7881393432617188e-05s 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/zh-cn/environ.md: -------------------------------------------------------------------------------- 1 | # Environ 2 | 3 | 提供处理环境相关内容的工具。 4 | 5 | 6 | ## environ.environ 7 | ```python 8 | environ(**kwargs) 9 | ``` 10 | 11 | 更新一个或多个环境变量的上下文管理器。 12 | 13 | 保存先前的环境变量(如果有),并在退出上下文管理器时还原。 14 | 15 | 如果给定 variable_name=None,表示从环境变量中临时移除该变量。 16 | 17 | ```python 18 | >>> from pydu.environ import environ 19 | >>> with environ(a='a'): 20 | ... print(os.environ['a']) 21 | ... 22 | a 23 | ``` 24 | 25 | 26 | ## environ.path 27 | ```python 28 | path(append=None, prepend=None, replace=None) 29 | ``` 30 | 31 | 更新PATH环境变量的上下文管理器。可将给定的字符串或字符串列表, 32 | 插入在PATH的开头和末尾,也可替换PATH。 33 | 34 | ```python 35 | >>> import os 36 | >>> from pydu.environ import path 37 | >>> with path(append='/foo'): 38 | ... print(os.environ['PATH']) 39 | ... 40 | /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/foo 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/zh-cn/exception.md: -------------------------------------------------------------------------------- 1 | # Exception 2 | 3 | 提供处理异常的工具。 4 | 5 | ## exception.ignore 6 | ```python 7 | ignore(*exceptions) 8 | ``` 9 | 10 | 忽略给定异常的上下文管理器。 11 | 12 | ```python 13 | >>> from pydu.exception import ignore 14 | >>> with ignore(ValueError, AttributeError): 15 | ... int('abc') 16 | ... int.no_exists_func() 17 | ... 18 | >>> 19 | ``` 20 | 21 | ## exception.default_if_except 22 | ```python 23 | default_if_except(exception_clses, default=None) 24 | ``` 25 | 26 | 捕捉给定异常(可以是一组异常,以元组表示)并返回默认值的异常装饰器。 27 | 28 | ```python 29 | >>> from pydu.exception import default_if_except 30 | >>> @default_if_except(ValueError, default=0) 31 | ... def foo(value): 32 | ... return int(value) 33 | >>> foo('abc') 34 | 0 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/zh-cn/functional.md: -------------------------------------------------------------------------------- 1 | # functional 2 | 3 | 提供函数式编程的工具。 4 | 5 | ## functional.compose 6 | ```python 7 | compose(*funcs) 8 | ``` 9 | 10 | 组成所有函数。前一个函数必须接受一个参数,该参数为后一个函数的输出值。 11 | 最后一个函数可以接受任意位置参数和关键字参数。 12 | `compose(f1, f2, f3)(*x)` 同 `f1(f2(f3(*x)))`。 13 | 14 | ```python 15 | >>> from pydu.functional import compose 16 | >>> def f1(a): 17 | ... return a+1 18 | ... 19 | >>> def f2(a, b=2): 20 | ... return a+b 21 | ... 22 | >>> compose(f1, f2)(1, b=3) 23 | 5 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/zh-cn/inspect.md: -------------------------------------------------------------------------------- 1 | # inspect 2 | 3 | 提供函数参数检查的工具。 4 | 5 | ## inspect.getargspec 6 | ```python 7 | getargspec(func) 8 | ``` 9 | 10 | 获得函数参数的名称和默认值。 11 | 12 | 返回由四个字符串组成的元组:(args, vargs, varkw, defaults)。 13 | `args` 是参数名称的列表(可能包含嵌套列表)。 14 | `varargs` 和 `varkw` 是 * 和 ** 参数的名称,或者为 `None`。 15 | `defaults` 是最后n个参数的默认值组成的元组。 16 | 17 | ```python 18 | >>> from pydu.inspect import getargspec 19 | >>> def f(name, address='home', age=25, *args, **kwargs): 20 | ... pass 21 | ... 22 | >>> getargspect(f) 23 | ArgSpec(args=['name', 'address', 'age'], varargs='args', keywords='kwargs', defaults=('home', 25)) 24 | ``` 25 | 26 | 27 | ## inspect.get_func_args 28 | ```python 29 | get_func_args(func) 30 | ``` 31 | 32 | 返回参数名称的列表。诸如 `*args` 和 `*kwargs` 的参数不被包含。 33 | 34 | ```python 35 | >>> from pydu.inspect import get_func_args 36 | >>> def f(name, address='home', age=25, *args, **kwargs): 37 | ... pass 38 | ... 39 | >>> get_func_args(f) 40 | ['name', 'address', 'age'] 41 | ``` 42 | 43 | 44 | ## inspect.get_func_full_args 45 | ```python 46 | get_func_full_args(func) 47 | ``` 48 | 49 | 返回(参数名称, 默认值)元组的列表。如果参数没有默认值,则在元组中丢弃。 50 | 诸如 `*args` 和 `*kwargs` 的参数也被包含在内。 51 | 52 | ```python 53 | >>> from pydu.inspect import get_func_full_args 54 | >>> def f(name, address='home', age=25, *args, **kwargs): 55 | ... pass 56 | ... 57 | >>> get_func_full_args(f) 58 | [('name',), ('address', 'home'), ('age', 25), ('*args',), ('**kwargs',)] 59 | ``` 60 | 61 | 62 | ## inspect.func_accepts_kwargs 63 | ```python 64 | func_accepts_kwargs(func) 65 | ``` 66 | 67 | 检查函数是否接受关键字参数。 68 | 69 | ```python 70 | >>> from pydu.inspect import func_accepts_kwargs 71 | >>> def f(**kwargs): 72 | ... pass 73 | ... 74 | >>> func_accepts_kwargs(f) 75 | True 76 | ``` 77 | 78 | 79 | ## inspect.func_accepts_var_args 80 | ```python 81 | func_accepts_var_args(func) 82 | ``` 83 | 84 | 检查函数是否接受位置参数。 85 | 86 | ```python 87 | >>> from pydu.inspect import func_accepts_var_args 88 | >>> def f(*vargs): 89 | ... pass 90 | ... 91 | >>> func_accepts_var_args(f) 92 | True 93 | ``` 94 | 95 | 96 | ## inspect.func_supports_parameter 97 | ```python 98 | func_supports_parameter(func) 99 | ``` 100 | 101 | 检查函数是否接受给定参数。 102 | 103 | ```python 104 | >>> from pydu.inspect import func_supports_parameter 105 | >>> def f(name): 106 | ... pass 107 | ... 108 | >>> func_supports_parameter(f, 'name') 109 | True 110 | >>> func_supports_parameter(f, 'unkown') 111 | Fasle 112 | ``` 113 | 114 | 115 | ## inspect.func_has_no_args 116 | ```python 117 | func_has_no_args(func) 118 | ``` 119 | 120 | 检查函数是否接受任意参数。 121 | 122 | ```python 123 | >>> from pydu.inspect import func_has_no_args 124 | >>> def f(): 125 | ... pass 126 | ... 127 | >>> func_has_no_args(f) 128 | True 129 | ``` 130 | -------------------------------------------------------------------------------- /docs/zh-cn/iter.md: -------------------------------------------------------------------------------- 1 | # iter 2 | 3 | 提供处理迭代对象的工具。 4 | 5 | ## iter.first 6 | ```python 7 | first(iterable) 8 | ``` 9 | 10 | 获取可迭代对象的第一个项。 11 | 12 | ```python 13 | >>> from pydu.iter import first 14 | >>> first([1, 2]) 15 | 1 16 | ``` 17 | 18 | 19 | ## iter.last 20 | ```python 21 | last(iterable) 22 | ``` 23 | 24 | 获取可迭代对象的最后一个项。注意,由于逐步迭代到最后一项,这可能会较慢。 25 | 26 | ```python 27 | >>> from pydu.iter import last 28 | >>> last([1, 2]) 29 | 2 30 | ``` 31 | 32 | 33 | ## iter.all 34 | ```python 35 | all(iterable, predicate) 36 | ``` 37 | 38 | 如果给定可迭代对象的所有元素套用判定函数都是True,则返回True。 39 | 40 | ```python 41 | >>> from pydu.iter import all 42 | >>> all([0, 1, 2], lambda x: x+1) 43 | True 44 | ``` 45 | 46 | 47 | ## iter.any 48 | ```python 49 | any(iterable) 50 | ``` 51 | 52 | 如果给定可迭代对象的任一元素套用判定函数是True,则返回True。 53 | 54 | ```python 55 | >>> from pydu.iter import any 56 | >>> any([-1, -1, 0], lambda x: x+1) 57 | True 58 | ``` 59 | 60 | 61 | ## iter.join 62 | ```python 63 | join(iterable, separator='') 64 | ``` 65 | 66 | 将可迭代对象中的每一项连接为字符串。 67 | 68 | ```python 69 | >>> from pydu.iter import join 70 | >>> join([1, '2', 3], separator=',') 71 | '1,2,3' 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/zh-cn/list.md: -------------------------------------------------------------------------------- 1 | # list 2 | 3 | 提供处理列表的工具。 4 | 5 | ## list.uniq 6 | ```python 7 | uniq(seq, key=None) 8 | ``` 9 | 10 | 从列表中删除重复的元素,同时保留其余的顺序。 11 | 12 | 可选参数 `key` 的值应该是一个函数,它接受一个参数并返回一个 `key` 来测试唯一性。 13 | 14 | ```python 15 | >>> from pydu.list import uniq 16 | >>> uniq([1, 4, 0, 2, 0, 3]) 17 | [1, 4, 0, 2, 3] 18 | ``` 19 | 20 | 21 | ## list.tolist 22 | ```python 23 | tolist(obj) 24 | ``` 25 | 26 | 将给定的 `obj` 转换为列表。 27 | 28 | 如果 `obj` 不是列表,返回 `[obj]`,否则返回 `obj` 本身。 29 | 30 | ```python 31 | >>> from pydu.list import tolist 32 | >>> tolist('foo') 33 | ['foo'] 34 | ``` 35 | 36 | 37 | ## list.flatten 38 | ```python 39 | flatten(seq) 40 | ``` 41 | 42 | 生成给定 `seq` 中的每个元素。如果元素是可迭代的并且不是字符串, 43 | 就递归 `yield` 元素中的每个子元素。 44 | 45 | ```python 46 | >>> from pydu.list import flatten 47 | >>> flatten([1, [2, [3, 4]]]) 48 | [1, 2, 3, 4] 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/zh-cn/misc.md: -------------------------------------------------------------------------------- 1 | # misc 2 | 3 | 提供诸如 `timeout`、 `trace` 等综合性工具。 4 | 5 | ## misc.timeout 6 | ```python 7 | timeout(seconds) 8 | ``` 9 | 10 | 该函数装饰任何可能会hang住一段时间的函数。参数 `seconds` 应为整数。 11 | 在 `test.py` 中,你可以这么写: 12 | 13 | ```python 14 | import time 15 | from pydu.misc import unix_timeout 16 | @timeout(1) 17 | def f(): 18 | time.sleep(1.01) 19 | f() 20 | ``` 21 | 22 | 然后运行 `test.py`,将会看到 `TimeoutError`。 23 | 24 | 25 | ## misc.trace 26 | ```python 27 | trace(obj) 28 | ``` 29 | 30 | 跟踪运行中程序的每条语句和行号,就像 `bash -x` 。在 `test.py` 中,你可以这么写: 31 | 32 | ```python 33 | from pydu.misc import trace 34 | @trace 35 | def f(): 36 | print(1) 37 | a = 1 + 5 38 | b = [a] 39 | print(2) 40 | f() 41 | ``` 42 | 43 | 然后运行 `test.py`,将会看到如下控制台输出: 44 | 45 | ```console 46 | test.py(4): print(1) 47 | 1 48 | test.py(5): a = 1 + 5 49 | test.py(6): b = [a] 50 | test.py(7): print(2) 51 | 2 52 | ``` 53 | 54 | 55 | ## misc.memoize 56 | ```python 57 | memoize(obj) 58 | ``` 59 | 60 | 简单的缓存装饰器,可供支持可哈希的位置参数的函数使用。 61 | 它还提供 `cache_clear()` 方法来清除缓存。 62 | 63 | ```python 64 | >>> @memoize 65 | ... def foo() 66 | ... return 1 67 | ... 68 | >>> foo() 69 | 1 70 | >>> foo.cache_clear() 71 | >>> 72 | ``` 73 | 74 | 75 | ## misc.memoize_when_activated 76 | ```python 77 | memoize_when_activated(obj) 78 | ``` 79 | 80 | 缓存装饰器,默认禁用。它能根据需求启用和禁用。 81 | 为效率起见,它只能用于没有参数的类方法。 82 | 83 | ```python 84 | >>> class Foo: 85 | ... @memoize 86 | ... def foo() 87 | ... print(1) 88 | ... 89 | >>> f = Foo() 90 | >>> # deactivated (default) 91 | >>> foo() 92 | 1 93 | >>> foo() 94 | 1 95 | >>> 96 | >>> # activated 97 | >>> foo.cache_activate() 98 | >>> foo() 99 | 1 100 | >>> foo() 101 | >>> foo() 102 | >>> 103 | ``` 104 | 105 | 106 | ## misc.super_len 107 | ```python 108 | super_len(obj) 109 | ``` 110 | 111 | 获取具有 `__len__` , `len` , `fileno` , `tell` 等属性的对的长度, 112 | 比如: `list` , `tuple` , `dict`, `file` 等等。 113 | 114 | ```python 115 | >>> from pydu.misc import super_len 116 | >>> super_len([1, 2]) 117 | 2 118 | >>> super_len(open('test', 'w')) 119 | 0 120 | ``` 121 | -------------------------------------------------------------------------------- /docs/zh-cn/network.md: -------------------------------------------------------------------------------- 1 | # network 2 | 3 | 提供处理网络的工具。 4 | 5 | ## network.dotted_netmask 6 | ```python 7 | dotted_netmask(mask) 8 | ``` 9 | 10 | 将mask从 /`xx` 转化为 `xxx.xxx.xxx.xxx` 形式。 11 | `mask` 可以是 `int` 或者 `str`。 12 | 13 | ```python 14 | >>> from pydu.network import dotted_netmask 15 | >>> dotted_netmask('24') 16 | '255.255.255.0' 17 | >>> dotted_netmask(24) 18 | '255.255.255.0' 19 | ``` 20 | 21 | 22 | ## network.private_ipv4s 23 | 24 | ```python 25 | private_ipv4s 26 | ``` 27 | 28 | ipv4地址列表。每个项是(ipv4地址,掩码)这样的元组。 29 | 30 | ## network.is_ipv4 31 | ```python 32 | is_ipv4(ip) 33 | ``` 34 | 35 | 判断给定的 `ip` 是否为 IPV4。 36 | 37 | ```python 38 | >>> from pydu.network import is_ipv4 39 | >>> is_ipv4('8.8.8.8') 40 | True 41 | >>> is_ipv4('localhost.localdomain') 42 | False 43 | ``` 44 | 45 | 46 | ## network.is_ipv6 47 | ```python 48 | is_ipv6(ip) 49 | ``` 50 | 51 | 判断给定的 `ip` 是否为 IPV6。 52 | 53 | ```python 54 | >>> from pydu.network import is_ipv6 55 | >>> is_ipv6('fe80::9e5b:b149:e187:1a18') 56 | True 57 | >>> is_ipv6('localhost.localdomain') 58 | False 59 | ``` 60 | 61 | 62 | ## network.get_free_port 63 | ```python 64 | get_free_port() 65 | ``` 66 | 67 | 获取可以绑定的空闲端口。 68 | 69 | ```python 70 | >>> from pydu.network import get_free_port 71 | >>> get_free_port() 72 | 57118 73 | ``` 74 | 75 | 76 | ## network.ip2int 77 | ```python 78 | ip2int(ip_str) 79 | ``` 80 | 81 | 将IP转换为整数。支持IPV4和IPV6。如果转换失败,将会抛出 `ValueError`。 82 | 83 | ```python 84 | >>> from pydu.network import ip2int 85 | >>> ip2int('10.1.1.1') 86 | 167837953 87 | ``` 88 | 89 | 90 | ## network.int2ip 91 | ```python 92 | int2ip(ip_int) 93 | ``` 94 | 95 | 将整数转换为IP。支持IPV4和IPV6。如果转换失败,将会抛出 `ValueError`。 96 | 97 | ```python 98 | >>> from pydu.network import int2ip 99 | >>> int2ip(167837953) 100 | '10.1.1.1' 101 | ``` 102 | -------------------------------------------------------------------------------- /docs/zh-cn/path.md: -------------------------------------------------------------------------------- 1 | # path 2 | 3 | 提供处理路径的工具。 4 | 5 | ## path.cd 6 | ```python 7 | cd(path) 8 | ``` 9 | 10 | 进入到给定目录的上下文管理器。 11 | 12 | ```python 13 | >>> from pydu.path import cd 14 | >>> with cd('test'): 15 | ... pass 16 | ``` 17 | 18 | 19 | ## path.is_super_path 20 | ```python 21 | is_super_path(path1, path2) 22 | ``` 23 | 24 | 判断 `path1` 是否是 `path2` 的父路径(或父父路径等)。 25 | 注意如果 `path1` 和 `path2` 一样,它也被视作是 `path2` 的父路径。 26 | 27 | 比如,\"/\"、\"opt\"或者\"/opt/test\"是\"/opt/test\"的超级父路径, 28 | 而\"/opt/t\"则不是。 29 | 30 | ```python 31 | >>> from pydu.path import is_super_path 32 | >>> is_super_path('/aa/bb/cc', '/aa/bb/cc') 33 | True 34 | >>> is_super_path('/aa/bb', '/aa/bb/cc') 35 | True 36 | >>> is_super_path('/aa/b', '/aa/bb/cc') 37 | False 38 | ``` 39 | 40 | 41 | ## path.normjoin 42 | ```python 43 | normjoin(path) 44 | ``` 45 | 46 | 将一个或多个路径联接,并将之标准化。 47 | 48 | ```python 49 | >>> from pydu.path import normjoin 50 | >>> normjoin('/a', '../b') 51 | '/b' 52 | ``` 53 | 54 | 55 | ## path.filename 56 | ```python 57 | filename(path) 58 | ``` 59 | 60 | 返回没有扩展名的文件名。 61 | 62 | ```python 63 | >>> from pydu.path import filename 64 | >>> filename('/foo/bar.ext') 65 | 'bar' 66 | ``` 67 | 68 | 69 | ## path.fileext 70 | ```python 71 | fileext(path) 72 | ``` 73 | 74 | 返回文件扩展名。 75 | 如果文件没有扩展名,则返回空字符串。 76 | 77 | ```python 78 | >>> from pydu.path import fileext 79 | >>> filename('/foo/bar.ext') 80 | '.ext' 81 | ``` 82 | -------------------------------------------------------------------------------- /docs/zh-cn/platform.md: -------------------------------------------------------------------------------- 1 | # platform 2 | 3 | 表示特定平台的常量。 4 | 5 | ## platform.WINDOWS 6 | 7 | 判断当前操作系统是否为 `WINDOWS` 。 8 | 9 | 10 | ## platform.LINUX 11 | 12 | 判断当前操作系统是否为 `LINUX` 。 13 | 14 | 15 | ## platform.POSIX 16 | 17 | 判断当前操作系统是否为 `POSIX` 。 18 | 19 | 20 | ## platform.DARWIN 21 | 22 | 判断当前操作系统是否为 `DARWIN` 。 23 | 24 | 25 | ## platform.SUNOS 26 | 27 | 判断当前操作系统是否为 `SUNOS` 。 28 | 29 | 30 | ## platform.SMARTOS 31 | 32 | 判断当前操作系统是否为 `SMARTOS` 。 33 | 34 | 35 | ## platform.FREEBSD 36 | 37 | 判断当前操作系统是否为 `FREEBSD` 。 38 | 39 | 40 | ## platform.NETBSD 41 | 42 | 判断当前操作系统是否为 `NETBSD` 。 43 | 44 | 45 | ## platform.OPENBSD 46 | 47 | 判断当前操作系统是否为 `OPENBSD` 。 48 | 49 | 50 | ## platform.AIX 51 | 52 | 判断当前操作系统是否为 `AIX` 。 53 | -------------------------------------------------------------------------------- /docs/zh-cn/process.md: -------------------------------------------------------------------------------- 1 | # process 2 | 3 | 提供处理进程的工具。 4 | 5 | `process` 的实现基于 `psutil`。需要先 `pip install psutil`。 6 | 7 | 8 | ## process.get_processes_by_path 9 | ```python 10 | get_processes_by_path(path) 11 | ``` 12 | 13 | 获取占用给定路径或者其子路径的进程。 14 | 15 | ```python 16 | >>> from pydu.process import get_processes_by_path 17 | >>> get_processes_by_path('/usr/bin/python') 18 | [{'cmdline': '/usr/bin/python2.7', 'pid': 23383, 'name': 'python'}] 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/zh-cn/request.md: -------------------------------------------------------------------------------- 1 | # Request 2 | 3 | 提供处理请求的工具。 4 | 5 | ## request.Filename 6 | 7 | 提供各类获取文件名的方法。 8 | 9 | ```python 10 | Filename.from_url(url) 11 | ``` 12 | 13 | 检测文件名为 unicode 或 None。 14 | 15 | ```python 16 | Filename.from_headers(headers) 17 | ``` 18 | 19 | 从响应头的Content-Disposition(如果有)中获取文件名。 20 | `headers` 可以使字典、列表或者字符串。 21 | 22 | ```python 23 | Filename.from_any(dst=None, headers=None, url=None) 24 | ``` 25 | 26 | 从目录、响应头部或者路径获取文件名称。 27 | 28 | 29 | ## request.download 30 | ```python 31 | Filename.download(url, dst=None) 32 | ``` 33 | 34 | 将URL下载到当前目录的临时文件中,然后重命名为从URL或者HTTP头中自动检测出的文件名。 35 | `url` 是要下载的URL地址。 36 | `dst` 是文件名或目录的目标路径,默认为 `None`,表示下载到当前目录。 37 | 38 | 39 | ## request.check_connect 40 | ```python 41 | check_connect(ip, port, retry=1, timeout=0.5) 42 | ``` 43 | 44 | 在给定的 `timeout` 时间内尝试连接给定的 `ip` 和 `port` 。 45 | 46 | ```python 47 | >>> from pydu.request import check_connect 48 | >>> check_connect('http://www.baidu.com', 80) 49 | '192.168.3.8' 50 | ``` 51 | 52 | 53 | ## request.update_query_params 54 | ```python 55 | update_query_params(url, params) 56 | ``` 57 | 58 | 更新给定url的查询参数并返回新的url。 59 | 60 | ```python 61 | >>> from pydu.request import update_query_params 62 | >>> update_query_params('http://example.com', {'foo': 1}) 63 | 'http://example.com?foo=1' 64 | ``` 65 | 66 | 67 | ## request.cookies_str_to_dict 68 | ```python 69 | cookies_str_to_dict(cookies) 70 | ``` 71 | 72 | 将字符串类型的Cookies转换为字典对象。 73 | 74 | ```python 75 | >>> from pydu.request import cookies_str_to_dict 76 | >>> cookies_str_to_dict('a=a;b=b') 77 | {'a': 'a', 'b': 'b'} 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/zh-cn/set.md: -------------------------------------------------------------------------------- 1 | # Set 2 | 3 | 增强的集合。 4 | 5 | ## set.OrderedSet 6 | ```python 7 | OrderedSet(iterable=None) 8 | ``` 9 | 10 | 保持插入元素有序的集合。 11 | 12 | ```python 13 | >>> from pydu.set import OrderedSet 14 | >>> s = OrderedSet([1, 3, 1, 2]) 15 | >>> list(s) 16 | [1, 3, 2] 17 | >>> s.discard(3) 18 | >>> list(s) 19 | [1, 2] 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/zh-cn/slot.md: -------------------------------------------------------------------------------- 1 | # slot 2 | 3 | ## slot.SlotBase 4 | ```python 5 | SlotBase(*args, **kwargs) 6 | ``` 7 | 8 | 使用 `__slots__` 的类的基类。当初始化类时未给定位置参数或关键字参数。 9 | 其值会被设置为 `None`。 10 | 11 | ```python 12 | >>> from pydu.slot import SlotBase 13 | >>> class Foo(SlotBase): 14 | __slots__ = ('a', 'b', 'c') 15 | >>> foo = Foo(1, b=2) 16 | >>> foo.a 17 | 1 18 | >>> foo.b 19 | 2 20 | >>> foo.c 21 | >>> 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/zh-cn/string.md: -------------------------------------------------------------------------------- 1 | # String 2 | 3 | 提供处理字符串的工具。 4 | 5 | ## string.safeunicode 6 | ```python 7 | safeunicode(obj, encoding='utf-8') 8 | ``` 9 | 10 | 将任何对象转换为 `unicode` 字符串。 11 | 12 | ```python 13 | >>> from pydu.string import safeunicode 14 | >>> safeunicode('hello') 15 | u'hello' 16 | >>> safeunicode(2) 17 | u'2' 18 | >>> safeunicode('\xe4\xb8\xad\xe6\x96\x87') 19 | u'中文' 20 | ``` 21 | 22 | 23 | ## string.safeencode 24 | ```python 25 | safeencode(obj, encoding='utf-8') 26 | ``` 27 | 28 | 将任何对象转换为编码后字符串(默认为 `utf-8`)。 29 | 30 | ```python 31 | >>> from pydu.string import safeencode 32 | >>> safeencode('hello') 33 | 'hello' 34 | >>> safeencode(2) 35 | '2' 36 | >>> safeencode(u'中文') 37 | '\xe4\xb8\xad\xe6\x96\x87' 38 | ``` 39 | 40 | 41 | ## string.lstrips 42 | ```python 43 | lstrips(text, remove) 44 | ``` 45 | 46 | 移除字符串 `text` 左侧的 `remove`。 47 | 48 | ```python 49 | >>> from pydu.string import lstrips 50 | >>> lstrips('foobar', 'foo') 51 | 'bar' 52 | >>> lstrips('FOOBARBAZ', ['FOO', 'BAR']) 53 | 'BAZ' 54 | >>> lstrips('FOOBARBAZ', ['BAR', 'FOO']) 55 | 'BARBAZ' 56 | ``` 57 | 58 | 59 | ## string.rstrips 60 | ```python 61 | rstrips(text, remove) 62 | ``` 63 | 64 | 移除字符串 `text` 右侧的 `remove`。 65 | 66 | ```python 67 | >>> from pydu.string import rstrips 68 | >>> rstrips('foobar', 'bar') 69 | 'foo' 70 | ``` 71 | 72 | 73 | ## string.strips 74 | ```python 75 | strips(text, remove) 76 | ``` 77 | 78 | 移除字符串 `text` 两边的 `remove`。 79 | 80 | ```python 81 | >>> from pydu.string import strips 82 | >>> strips('foobarfoo', 'foo') 83 | 'bar' 84 | ``` 85 | 86 | ## string.common_prefix 87 | ```python 88 | common_prefix(l) 89 | ``` 90 | 91 | 返回字符串的共有前缀。 92 | 93 | ```python 94 | >>> from pydu.string import common_prefix 95 | >>> common_prefix(['abcd', 'abc1']) 96 | 'abc' 97 | ``` 98 | 99 | 100 | ## string.common_suffix 101 | ```python 102 | common_suffix(l) 103 | ``` 104 | 105 | 返回字符串的共有后缀 106 | 107 | ```python 108 | >>> from pydu.string import common_suffix 109 | >>> common_suffix(['dabc', '1abc']) 110 | 'abc' 111 | ``` 112 | 113 | 114 | ## string.sort 115 | ```python 116 | sort(s, reversed=False) 117 | ``` 118 | 119 | 对给定的字符串进行排序,默认是升序,如果 `reverse` 的值为 `True`,将以降序排序。 120 | 121 | ```python 122 | >>> from pydu.string import sort 123 | >>> sort('dabc') 124 | 'abcd' 125 | ``` 126 | -------------------------------------------------------------------------------- /docs/zh-cn/system.md: -------------------------------------------------------------------------------- 1 | # System 2 | 3 | 提供处理系统(如追踪文件、创建目录、链接等)的工具。 4 | 5 | 6 | ## system.FileTracker 7 | ```python 8 | FileTracker() 9 | ``` 10 | 11 | 跟踪当前打开的文件,调用 `FileTracker.track()` 开始跟踪。当打开许多文件时,`FileTracker` 能够跟踪它们,你可以通过调用 `FileTracker.get_openfiles()` 来定位得到这些文件对象。 12 | 13 | ```python 14 | FiltTracker.track() 15 | ``` 16 | 17 | 开始跟踪打开文件。 18 | 19 | ```python 20 | FiltTracker.untrack() 21 | ``` 22 | 23 | 停止跟踪打开文件。 24 | 25 | ```python 26 | FiltTracker.get_openfiles() 27 | ``` 28 | 29 | 获取当前已打开的文件。 30 | 31 | ```python 32 | >>> from pydu.system import FileTracker 33 | >>> FileTracker.track() 34 | >>> f = open('test', 'w') 35 | >>> FileTracker.get_openfiles() 36 | {<_io.TextIOWrapper name='test' mode='w' encoding='UTF-8'>} 37 | >>> f.close() 38 | >>> FileTracker.get_openfiles() 39 | set() 40 | >>> FileTracker.untrack() 41 | >>> f = open('test', 'w') 42 | >>> FileTracker.get_openfiles() 43 | set() 44 | ``` 45 | 46 | 47 | ## system.makedirs 48 | ```python 49 | makedirs(path, mode=0o755, ignore_errors=False, exist_ok=False) 50 | ``` 51 | 52 | `makedirs` 基于 `os.makedirs` ,它会创建目标文件夹,以及中间文件夹 53 | (当中间文件夹不存在的时候)。 `mode` 默认值为 `0o75`,当被创建的文件夹已经存在的时候, 54 | 如果 `eist_ok` 的值为 `False`,`makedirs` 将会抛出异常。 55 | 如果 `ignore_errors` 的值为 `True`,所有的异常将会被忽略。 56 | 57 | ```python 58 | >>> from pydu.system import makedirs 59 | >>> makedirs('test1/test2') 60 | >>> makedirs('test1',exist_ok=True) 61 | >>> makedirs('test1') 62 | Traceback (most recent call last): 63 | ... OSError: Create dir: test1 error. 64 | ``` 65 | 66 | ## system.remove 67 | ```python 68 | remove(path, mode=0o755, ignore_errors=False, onerror) 69 | ``` 70 | 71 | 删除文件或者文件夹。 72 | 73 | 如果 `ignore_errors` 的值为 `True` ,异常将会被忽略; 74 | 否者,如果 `onerror` 的值不为 `None` ,那么 `onerror` 函数将会处理这些异常。 75 | `onerror` 的参数包括 `func` , `path` 和 `exc_info` 。 76 | `path` 表示 `func` 函数处理该路径时抛出的异常; 77 | `exc_info` 是由 `sys.exc_info` 返回的元组。 78 | 如果 `ignore_errors` 为 `False`,并且 `onerror` 的值为 `None`, 79 | 则尝试将只读的 `path` 改为可写并尝试删除,若非只读则抛出异常。 80 | 81 | ```python 82 | >>> from pydu.system import makedirs 83 | >>> from pydu.system import remove 84 | >>> from pydu.system import touch 85 | >>> makedirs('test') 86 | >>> remove('test') 87 | >>> touch('test.txt') 88 | >>> remove('test.txt') 89 | >>> remove('test.txt', ignore_errors=True) 90 | >>> remove('test.txt') 91 | Traceback (most recent call last): 92 | ... OSError: Remove path: test error. Reason: [Errno 2] No such file or directory: 'test.txt' 93 | ``` 94 | 95 | ## system.removes 96 | ```python 97 | removes(paths, mode=0o755, ignore_errors=False, onerror) 98 | ``` 99 | 100 | 删除多个文件或者(和)文件夹,其他的参数见 `remove`。 101 | 102 | ```python 103 | >>> from pydu.system import makedirs 104 | >>> from pydu.system import remove 105 | >>> from pydu.system import open_file 106 | >>> makedirs('test1') 107 | >>> makedirs('test2') 108 | >>> open_file('test.txt') 109 | >>> removes(['test.txt','test1','test2']) 110 | ``` 111 | 112 | ## system.open_file 113 | ```python 114 | open_file(path, mode='wb+', buffer_size=-1, ignore_errors=False): 115 | ``` 116 | 117 | 默认以 `wb+` 的方式打开文件,如果需要被创建的文件的上级目录不存在,该目录将会被创建。如果 `ignore_errors` 为 `True` ,异常将会被忽略。 118 | 119 | ```python 120 | >>> from pydu.system import open_file 121 | >>> open_file('test.txt') 122 | >>> ls 123 | test.txt 124 | >>> open_file('test1.txt',mode='r') 125 | Traceback (most recent call last): 126 | ... OSError: Open file: test1.txt error 127 | ``` 128 | 129 | ## system.copy 130 | ```python 131 | copy(src, dst, ignore_errors=False, follow_symlinks=True): 132 | ``` 133 | 134 | 复制源文件(文件夹)到目标文件(文件夹)。当复制的文件夹包含软连接时, 135 | 如果 `symlink` 的值为 `True` ,那么在目标文件夹中会创建相应的软连接; 136 | 否者将会复制软连接所指向的文件。当复制的文件为软连接的时候, 137 | 如果 `symlink` 的值为 `False` ,那么将会创建与软连接指向相同的软连接; 138 | 否者,将会复制软连接所指向的文件。 139 | 140 | ```python 141 | >>> from pydu.system import copy,symlink 142 | >>> from pydu.system import makedirs,open_fle 143 | >>> open_fle('test/test.txt') 144 | >>> symlink('test/test.txt','test/test.link') 145 | >>> copy('test/test.link','test/test_copy1.link') 146 | >>> copy('test/test.link','test/test_copy2.link',follow_symlink=False) 147 | ``` 148 | 149 | ## system.touch 150 | ```python 151 | touch(path): 152 | ``` 153 | 154 | 生成一个新的文件。 155 | 156 | ```python 157 | >>> from pydu.system import touch 158 | >>> touch('test.txt') 159 | ``` 160 | 161 | ## system.symlink 162 | ```python 163 | symlink(src, dst, overwrite=False, ignore_errors=False) 164 | ``` 165 | 166 | 创建指向源文件的软连接。 167 | 如果 `overwrite` 的值为 `True` ,那么已存在的软连接将会被覆盖。 168 | 169 | ```python 170 | >>> from pydu.system import symlink 171 | >>> symlink('test.txt','test.link') 172 | ``` 173 | 174 | !> `symlink` 只支持 `Unix类` 的系统。 175 | 176 | ## system.link 177 | ```python 178 | link(src, dst, overwrite=False, ignore_errors=False): 179 | ``` 180 | 181 | 创建指向源文件的硬连接。 182 | 如果 `overwrite` 的值为 `True` ,那么已存在的硬连接将会被覆盖。 183 | 184 | ```python 185 | >>> from pydu.system import link 186 | >>> link('test.txt','test.link') 187 | ``` 188 | 189 | !> `link` 只支持 `Unix类` 的系统。 190 | 191 | 192 | ## system.which 193 | ```python 194 | which(cmd, mode=os.F_OK | os.X_OK, path=None): 195 | ``` 196 | 197 | 给定命令名称、模式、和环境变量PATH,返回在PATH下符合给定模式的命令的路径, 198 | 如果找不到就返回None。 199 | 200 | `mode` 默认是 os.F_OK | os.X_OK。 201 | `path` 默认是 os.environ.get("PATH")的结果,也可被被自定义的搜索路径重载。 202 | 203 | 在Python 3中,`which` 就是 `shutil.which`。 204 | 205 | ```python 206 | >>> from pydu.system import which 207 | >>> which('echo') 208 | /bin/echo 209 | ``` 210 | 211 | 212 | ## system.chmod 213 | ```python 214 | chmod(path, mode, recursive=False) 215 | ``` 216 | 217 | 将权限改成给定模式。如果 `recursive` 是True,将会递归。 218 | 219 | ```python 220 | >>> from pydu.system import chmod 221 | >>> chmod('/opt/sometest', 0o744) 222 | >>> oct(os.stat('/opt/sometest').st_mode)[-3:] 223 | '744' 224 | ``` 225 | 226 | !> 尽管Windows支持 `chmod`,但你只能使用它设置文件的只读标志 227 | (通过 tat.S_IWRITE 和 stat.S_IREAD)常量或者相关整数值。 228 | 其他所有位会被忽略。 229 | 230 | 231 | ## system.chcp 232 | ```python 233 | chcp(code) 234 | ``` 235 | 236 | 设置活动代码页号的上下文管理器。它也能够被当做函数使用。 237 | 238 | ```python 239 | >>> from pydu.system import chcp 240 | >>> chcp(437) 241 | 242 | >>> with chcp(437): 243 | ... pass 244 | >>> 245 | ``` 246 | 247 | !> `chcp` 只能用于 `Windows` 系统。 248 | 249 | 250 | ## system.preferredencoding 251 | ```python 252 | preferredencoding(code) 253 | ``` 254 | 255 | 以最佳的方式获取系统编码。 256 | -------------------------------------------------------------------------------- /docs/zh-cn/unit.md: -------------------------------------------------------------------------------- 1 | # Unit 2 | 3 | 提供处理单位的工具。 4 | 5 | ## unit.Bytes 6 | ```python 7 | Bytes(bytes) 8 | ``` 9 | 10 | 提供处理字节的各类方法。 11 | 12 | ```python 13 | Bytes.convert(self, unit=None, multiple=1024) 14 | ``` 15 | 16 | 将字节转化为给定 `unit`。如果 `unit` 是 `None`,将字节转换为合适的单位。 17 | 转换 `multiple` 的默认值是 1024。 18 | 19 | ```python 20 | >>> Bytes(1024).convert() 21 | (1, 'KB') 22 | ``` 23 | -------------------------------------------------------------------------------- /pydu/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Useful data structures, utils for Python. 3 | """ 4 | from __future__ import absolute_import 5 | 6 | __version__ = '0.7.2' 7 | 8 | 9 | # Set logging handler to avoid "No handler found" warnings. 10 | import logging 11 | from logging import NullHandler 12 | 13 | 14 | logger = logging.getLogger(__name__) 15 | logger.addHandler(NullHandler()) 16 | -------------------------------------------------------------------------------- /pydu/cmd.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | import signal 5 | import subprocess 6 | from subprocess import Popen, PIPE, STDOUT 7 | 8 | from .platform import WINDOWS 9 | from .compat import PY2 10 | 11 | if PY2: 12 | class TimeoutExpired(Exception): 13 | """ 14 | This exception is raised when the timeout expires while waiting for a 15 | child process. 16 | 17 | Attributes: 18 | cmd, output, stdout, stderr, timeout 19 | """ 20 | def __init__(self, cmd, timeout, output=None, stderr=None): 21 | self.cmd = cmd 22 | self.timeout = timeout 23 | self.output = output 24 | self.stderr = stderr 25 | 26 | def __str__(self): 27 | return ("Command '%s' timed out after %s seconds" % 28 | (self.cmd, self.timeout)) 29 | 30 | @property 31 | def stdout(self): 32 | return self.output 33 | 34 | @stdout.setter 35 | def stdout(self, value): 36 | # There's no obvious reason to set this, but allow it anyway so 37 | # .stdout is a transparent alias for .output 38 | self.output = value 39 | else: 40 | TimeoutExpired = subprocess.TimeoutExpired 41 | 42 | 43 | def run(cmd, shell=False, env=None, timeout=None, timeinterval=1): 44 | """ 45 | Run cmd based on `subprocess.Popen` and return the tuple of `(returncode, stdout)`. 46 | Note, `stderr` is redirected to `stdout`. `shell` is same to parameter of `Popen`. 47 | If the process does not terminate after `timeout` seconds, a `TimeoutExpired` 48 | exception will be raised. `timeinterval` is workable when timeout is given 49 | on Python 2. It means process status checking interval. 50 | """ 51 | p = Popen(cmd, shell=shell, stdout=PIPE, stderr=STDOUT, env=env) 52 | 53 | if PY2: 54 | if timeout: 55 | while timeout > 0 and p.poll() is None: 56 | timeout = timeout - timeinterval 57 | time.sleep(timeinterval) 58 | if p.poll() is None: 59 | raise TimeoutExpired(cmd, timeout) 60 | 61 | stdout, _ = p.communicate() 62 | return p.poll(), stdout 63 | else: 64 | stdout, _ = p.communicate(timeout=timeout) 65 | return p.poll(), stdout 66 | 67 | 68 | def run_with_en_env(cmd, shell=False, env=None, timeout=None, timeinterval=1): 69 | """ 70 | Run cmd with English character sets environment, so that the output will 71 | be in English. 72 | Parameters are same with `run`. 73 | """ 74 | if WINDOWS: 75 | from .system import chcp 76 | with chcp(437): 77 | return run(cmd, shell=shell, 78 | timeout=timeout, timeinterval=timeinterval) 79 | else: 80 | env = env if env else os.environ.copy() 81 | env.update({'LANG': 'en_US.UTF-8'}) 82 | return run(cmd, shell=shell, env=env, 83 | timeout=timeout, timeinterval=timeinterval) 84 | 85 | 86 | def terminate(pid): 87 | """ 88 | Terminate process by given pid. 89 | On Windows, using Kernel32.TerminateProcess to kill. 90 | On Other platforms, using os.kill with signal.SIGTERM to kill. 91 | """ 92 | if WINDOWS: 93 | # http://code.activestate.com/recipes/347462-terminating-a-subprocess-on-windows/ 94 | import ctypes 95 | PROCESS_TERMINATE = 1 96 | handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, pid) 97 | ctypes.windll.kernel32.TerminateProcess(handle, -1) 98 | ctypes.windll.kernel32.CloseHandle(handle) 99 | else: 100 | os.kill(pid, signal.SIGTERM) 101 | 102 | 103 | if PY2 and WINDOWS: 104 | # enable passing unicode arguments from command line in Python 2.x 105 | # https://stackoverflow.com/questions/846850/read-unicode-characters 106 | def cmdline_argv(): 107 | """ 108 | Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode 109 | strings. 110 | 111 | Versions 2.x of Python don't support Unicode in sys.argv on Windows, 112 | with the underlying Windows API instead replacing multi-byte characters 113 | with '?'. 114 | """ 115 | from ctypes import POINTER, byref, cdll, c_int, windll 116 | from ctypes.wintypes import LPCWSTR, LPWSTR 117 | 118 | GetCommandLineW = cdll.kernel32.GetCommandLineW 119 | GetCommandLineW.argtypes = [] 120 | GetCommandLineW.restype = LPCWSTR 121 | 122 | CommandLineToArgvW = windll.shell32.CommandLineToArgvW 123 | CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] 124 | CommandLineToArgvW.restype = POINTER(LPWSTR) 125 | 126 | cmd = GetCommandLineW() 127 | argc = c_int(0) 128 | raw_argv = CommandLineToArgvW(cmd, byref(argc)) 129 | argnum = argc.value 130 | sysnum = len(sys.argv) 131 | argv = [] 132 | if argnum > 0: 133 | # Remove Python executable and commands if present 134 | start = argnum - sysnum 135 | for i in range(start, argnum): 136 | argv.append(raw_argv[i]) 137 | return argv 138 | else: 139 | def cmdline_argv(): 140 | return sys.argv 141 | -------------------------------------------------------------------------------- /pydu/compat.py: -------------------------------------------------------------------------------- 1 | """Utilities for make the code run both on Python2 and Python3. 2 | """ 3 | import sys 4 | import types 5 | 6 | PY2 = sys.version_info[0] == 2 7 | 8 | 9 | # builtins 10 | if PY2: 11 | import __builtin__ as builtins 12 | else: 13 | import builtins 14 | 15 | # url* 16 | if PY2: 17 | import urllib as urlib 18 | import urlparse 19 | from urlparse import urljoin 20 | from urllib import urlencode 21 | else: 22 | import urllib.request as urlib 23 | import urllib.parse as urlparse 24 | from urllib.parse import urljoin 25 | from urllib.parse import urlencode 26 | 27 | # Dictionary iteration 28 | if PY2: 29 | iterkeys = lambda d: d.iterkeys() 30 | itervalues = lambda d: d.itervalues() 31 | iteritems = lambda d: d.iteritems() 32 | else: 33 | iterkeys = lambda d: iter(d.keys()) 34 | itervalues = lambda d: iter(d.values()) 35 | iteritems = lambda d: iter(d.items()) 36 | 37 | # string and text types 38 | if PY2: 39 | text_type = unicode 40 | string_types = (str, unicode) 41 | strbytes_types = (str, unicode, bytes) 42 | numeric_types = (int, long) 43 | else: 44 | text_type = str 45 | string_types = (str,) 46 | strbytes_types = (str, bytes) 47 | numeric_types = (int,) 48 | 49 | # imap, izip 50 | if PY2: 51 | from itertools import imap, izip 52 | else: 53 | imap = map 54 | izip = zip 55 | 56 | if PY2: 57 | reduce = reduce 58 | else: 59 | from functools import reduce 60 | 61 | # next 62 | if PY2: 63 | has_next_attr = lambda x: x and hasattr(x, 'next') 64 | else: 65 | has_next_attr = lambda x: x and hasattr(x, '__next__') 66 | 67 | # Class Type 68 | if PY2: 69 | ClassTypes = (type, types.ClassType) 70 | else: 71 | ClassTypes = (type,) 72 | 73 | # cmp 74 | if PY2: 75 | cmp = cmp 76 | else: 77 | cmp = lambda x, y: (x > y) - (x < y) 78 | 79 | 80 | # is iterable 81 | def is_iterable(x): 82 | """An implementation independent way of checking for iterables.""" 83 | try: 84 | iter(x) 85 | except TypeError: 86 | return False 87 | else: 88 | return True 89 | -------------------------------------------------------------------------------- /pydu/console.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import shutil 3 | 4 | from .platform import WINDOWS, POSIX 5 | 6 | 7 | if hasattr(shutil, 'get_terminal_size'): 8 | # Actually (80, 25) is the default size used by many terminal emulators 9 | def console_size(fallback=(80, 25)): 10 | return shutil.get_terminal_size(fallback=fallback) 11 | else: 12 | # http://bitbucket.org/techtonik/python-pager 13 | def console_size(fallback=(80, 25)): 14 | """ 15 | For Windows, return (width, height) of available window area, fallback 16 | if no console is allocated. 17 | For POSIX system, return (width, height) of console terminal, fallback 18 | on IOError, i.e. when no console is allocated. 19 | For other system, return fallback. 20 | Fallback defaults to (80, 25) which is the default size used by many 21 | terminal emulators. 22 | """ 23 | 24 | if WINDOWS: 25 | STD_OUTPUT_HANDLE = -11 26 | 27 | # get console handle 28 | from ctypes import windll, Structure, byref 29 | try: 30 | from ctypes.wintypes import SHORT, WORD, DWORD 31 | except ImportError: 32 | from ctypes import ( 33 | c_short as SHORT, c_ushort as WORD, c_ulong as DWORD) 34 | console_handle = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) 35 | 36 | # CONSOLE_SCREEN_BUFFER_INFO Structure 37 | class COORD(Structure): 38 | _fields_ = [('X', SHORT), ('Y', SHORT)] 39 | 40 | class SMALL_RECT(Structure): 41 | _fields_ = [('Left', SHORT), ('Top', SHORT), 42 | ('Right', SHORT), ('Bottom', SHORT)] 43 | 44 | class CONSOLE_SCREEN_BUFFER_INFO(Structure): 45 | _fields_ = [('dwSize', COORD), 46 | ('dwCursorPosition', COORD), 47 | ('wAttributes', WORD), 48 | ('srWindow', SMALL_RECT), 49 | ('dwMaximumWindowSize', DWORD)] 50 | 51 | sbi = CONSOLE_SCREEN_BUFFER_INFO() 52 | ret = windll.kernel32.GetConsoleScreenBufferInfo( 53 | console_handle, byref(sbi)) 54 | if ret == 0: 55 | return fallback 56 | return ((sbi.srWindow.Right - sbi.srWindow.Left + 1) or fallback[0], 57 | (sbi.srWindow.Bottom - sbi.srWindow.Top + 1) or fallback[1]) 58 | 59 | elif POSIX: 60 | # http://www.kernel.org/doc/man-pages/online/pages/man4/tty_ioctl.4.html 61 | from fcntl import ioctl 62 | from termios import TIOCGWINSZ 63 | from array import array 64 | 65 | """ 66 | struct winsize { 67 | unsigned short ws_row; 68 | unsigned short ws_col; 69 | unsigned short ws_xpixel; /* unused */ 70 | unsigned short ws_ypixel; /* unused */ 71 | }; 72 | """ 73 | winsize = array("H", [0] * 4) 74 | try: 75 | ioctl(sys.stdout.fileno(), TIOCGWINSZ, winsize) 76 | except IOError: 77 | # for example IOError: [Errno 25] Inappropriate ioctl for device 78 | # when output is redirected 79 | pass 80 | return winsize[1] or fallback[0], winsize[0] or fallback[1] 81 | 82 | return fallback 83 | -------------------------------------------------------------------------------- /pydu/convert.py: -------------------------------------------------------------------------------- 1 | import functools 2 | from pydu.compat import PY2 3 | 4 | 5 | def boolean(obj): 6 | """ 7 | Convert obj to a boolean value. 8 | If obj is string, obj will converted by case-insensitive way: 9 | * convert `yes`, `y`, `on`, `true`, `t`, `1` to True 10 | * convert `no`, `n`, `off`, `false`, `f`, `0` to False 11 | * raising TypeError if other values passed 12 | If obj is non-string, obj will converted by bool(obj). 13 | """ 14 | 15 | try: 16 | text = obj.strip().lower() 17 | except AttributeError: 18 | return bool(obj) 19 | 20 | if text in ('yes', 'y', 'on', 'true', 't', '1'): 21 | return True 22 | elif text in ('no', 'n', 'off', 'false', 'f', '0'): 23 | return False 24 | else: 25 | raise ValueError("Unable to convert {!r} to a boolean value.".format(text)) 26 | 27 | 28 | ########################################################################## 29 | # Convert number from one base(2, 8, 10, 16) to another base(2, 8, 10, 16) 30 | ########################################################################## 31 | _oct_index = 1 if PY2 else 2 32 | 33 | 34 | def _rstrip_L(func): 35 | if PY2: 36 | @functools.wraps(func) 37 | def wrapper(x): 38 | return func(x).rstrip('L') 39 | return wrapper 40 | return func 41 | 42 | 43 | # binary to octal, decimal and hexadecimal 44 | @_rstrip_L 45 | def bin2oct(x): 46 | """ 47 | Convert binary string to octal string. 48 | For instance: '1001' -> '11' 49 | """ 50 | return oct(int(x, 2))[_oct_index:] 51 | 52 | 53 | def bin2dec(x): 54 | """ 55 | Convert binary string to decimal number. 56 | For instance: '11' -> 3 57 | """ 58 | return int(x, 2) 59 | 60 | 61 | @_rstrip_L 62 | def bin2hex(x): 63 | """ 64 | Convert binary string to hexadecimal string. 65 | For instance: '11010' -> '1a' 66 | """ 67 | return hex(int(x, 2))[2:] 68 | 69 | 70 | # octal to binary, decimal and hexadecimal 71 | @_rstrip_L 72 | def oct2bin(x): 73 | """ 74 | Convert octal string to binary string. 75 | For instance: '11' -> '1001' 76 | """ 77 | return bin(int(x, 8))[2:] 78 | 79 | 80 | def oct2dec(x): 81 | """ 82 | Convert octal string to decimal number. 83 | For instance: '11' -> 9 84 | """ 85 | return int(x, 8) 86 | 87 | 88 | @_rstrip_L 89 | def oct2hex(x): 90 | """ 91 | Convert octal string to hexadecimal string. 92 | For instance: '32' -> '1a' 93 | """ 94 | return hex(int(x, 8))[2:] 95 | 96 | 97 | # decimal to binary, octal and hexadecimal 98 | @_rstrip_L 99 | def dec2bin(x): 100 | """ 101 | Convert decimal number to binary string. 102 | For instance: 3 -> '11' 103 | """ 104 | return bin(x)[2:] 105 | 106 | 107 | @_rstrip_L 108 | def dec2oct(x): 109 | """ 110 | Convert decimal number to octal string. 111 | For instance: 9 -> '11' 112 | """ 113 | return oct(x)[_oct_index:] 114 | 115 | 116 | @_rstrip_L 117 | def dec2hex(x): 118 | """ 119 | Convert decimal number to hexadecimal string. 120 | For instance: 26 -> '1a' 121 | """ 122 | return hex(x)[2:] 123 | 124 | 125 | # hexadecimal to binary, octal and decimal 126 | @_rstrip_L 127 | def hex2bin(x): 128 | """ 129 | Convert hexadecimal string to binary string. 130 | For instance: '1a' -> '11010' 131 | """ 132 | return bin(int(x, 16))[2:] 133 | 134 | 135 | @_rstrip_L 136 | def hex2oct(x): 137 | """ 138 | Convert hexadecimal string to octal string. 139 | For instance: '1a' -> '32' 140 | """ 141 | return oct(int(x, 16))[_oct_index:] 142 | 143 | 144 | def hex2dec(x): 145 | """ 146 | Convert hexadecimal string to decimal number. 147 | For instance: '1a' -> 26 148 | """ 149 | return int(x, 16) 150 | -------------------------------------------------------------------------------- /pydu/dict.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import collections 3 | 4 | try: 5 | # Python 3 6 | from collections.abc import Callable, Mapping, MutableMapping 7 | except ImportError: 8 | # Python 2.7 9 | from collections import Callable, Mapping, MutableMapping 10 | 11 | from .compat import PY2 12 | 13 | 14 | class AttrDict(dict): 15 | """ 16 | A AttrDict object is like a dictionary except `obj.foo` can be used 17 | in addition to `obj['foo']`. 18 | 19 | >>> o = AttrDict(a=1) 20 | >>> o.a 21 | 1 22 | >>> o['a'] 23 | 1 24 | >>> o.a = 2 25 | >>> o['a'] 26 | 2 27 | >>> del o.a 28 | >>> o.a 29 | Traceback (most recent call last): 30 | ... 31 | AttributeError: 'a' 32 | 33 | """ 34 | 35 | def __getattr__(self, key): 36 | try: 37 | return self[key] 38 | except KeyError as k: 39 | raise AttributeError(k) 40 | 41 | def __setattr__(self, key, value): 42 | self[key] = value 43 | 44 | def __delattr__(self, key): 45 | try: 46 | del self[key] 47 | except KeyError as k: 48 | raise AttributeError(k) 49 | 50 | def __repr__(self): 51 | return '' 52 | 53 | 54 | class CaseInsensitiveDict(MutableMapping): 55 | """ 56 | A case-insensitive ``dict``-like object. 57 | Implements all methods and operations of 58 | ``MutableMapping`` as well as dict's ``copy``. Also 59 | provides ``lower_items``. 60 | All keys are expected to be strings. The structure remembers the 61 | case of the last key to be set, and ``iter(instance)``, 62 | ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` 63 | will contain case-sensitive keys. However, querying and contains 64 | testing is case insensitive: 65 | cid = CaseInsensitiveDict() 66 | cid['Accept'] = 'application/json' 67 | cid['aCCEPT'] == 'application/json' # True 68 | list(cid) == ['Accept'] # True 69 | For example, ``headers['content-encoding']`` will return the 70 | value of a ``'Content-Encoding'`` response header, regardless 71 | of how the header name was originally stored. 72 | If the constructor, ``.update``, or equality comparison 73 | operations are given keys that have equal ``.lower()``s, the 74 | behavior is undefined. 75 | """ 76 | 77 | def __init__(self, data=None, **kwargs): 78 | self._store = {} 79 | if data is None: 80 | data = {} 81 | self.update(data, **kwargs) 82 | 83 | def __setitem__(self, key, value): 84 | # Use the lowercased key for lookups, but store the actual 85 | # key alongside the value. 86 | self._store[key.lower()] = (key, value) 87 | 88 | def __getitem__(self, key): 89 | return self._store[key.lower()][1] 90 | 91 | def __delitem__(self, key): 92 | del self._store[key.lower()] 93 | 94 | def __iter__(self): 95 | return (casedkey for casedkey, mappedvalue in self._store.values()) 96 | 97 | def __len__(self): 98 | return len(self._store) 99 | 100 | def lower_items(self): 101 | """Like iteritems(), but with all lowercase keys.""" 102 | return ( 103 | (lowerkey, keyval[1]) 104 | for (lowerkey, keyval) 105 | in self._store.items() 106 | ) 107 | 108 | def __eq__(self, other): 109 | if isinstance(other, Mapping): 110 | other = CaseInsensitiveDict(other) 111 | else: 112 | return NotImplemented 113 | # Compare insensitively 114 | return dict(self.lower_items()) == dict(other.lower_items()) 115 | 116 | # Copy is required 117 | def copy(self): 118 | return CaseInsensitiveDict(self._store.values()) 119 | 120 | def __repr__(self): 121 | return '%s(%r)' % (self.__class__.__name__, dict(self.items())) 122 | 123 | 124 | class LookupDict(dict): 125 | """ 126 | Dictionary lookup object. 127 | d = LookupDict() 128 | print(d['key']) # None 129 | d['key'] = 1 130 | print(d['key']) # 1 131 | """ 132 | 133 | def __init__(self, name=None): 134 | self.name = name 135 | super(LookupDict, self).__init__() 136 | 137 | def __getitem__(self, key): 138 | # We allow fall-through here, so values default to None 139 | return self.get(key, None) 140 | 141 | 142 | # https://stackoverflow.com/questions/6190331/can-i-do-an-ordered-default-dict-in-python 143 | class OrderedDefaultDict(collections.OrderedDict): 144 | """ 145 | Dictionary that remembers insertion order and has default value 146 | with default factory. 147 | 148 | The default factory is called without arguments to produce 149 | a new value when a key is not present, in `__getitem__` only. 150 | An `OrderedDefaultDict` compares equal to a `collections.defaultdict` 151 | with the same items. All remaining arguments are treated the same 152 | as if they were passed to the `defaultdict` constructor, 153 | including keyword arguments. 154 | """ 155 | 156 | def __init__(self, default_factory=None, *args, **kwds): 157 | if (default_factory is not None and 158 | not isinstance(default_factory, Callable)): 159 | raise TypeError('First argument must be callable') 160 | super(OrderedDefaultDict, self).__init__(*args, **kwds) 161 | self.default_factory = default_factory 162 | 163 | def __getitem__(self, key): 164 | try: 165 | return super(OrderedDefaultDict, self).__getitem__(key) 166 | except KeyError: 167 | return self.__missing__(key) 168 | 169 | def __missing__(self, key): 170 | if self.default_factory is None: 171 | raise KeyError(key) 172 | self[key] = value = self.default_factory() 173 | return value 174 | 175 | def __reduce__(self): 176 | if self.default_factory is None: 177 | args = tuple() 178 | else: 179 | args = self.default_factory, 180 | return type(self), args, None, None, self.items() 181 | 182 | def copy(self): 183 | return self.__copy__() 184 | 185 | def __copy__(self): 186 | return self.__class__(self.default_factory, self) 187 | 188 | if PY2: 189 | def __deepcopy__(self, memo): 190 | import copy 191 | return self.__class__(self.default_factory, copy.deepcopy(self.items())) 192 | else: 193 | def __deepcopy__(self, memo): 194 | import copy 195 | return self.__class__(self.default_factory, copy.deepcopy(iter(self.items()))) 196 | 197 | def __repr__(self): 198 | return 'OrderedDefaultDict({default_factory}, {repr})'.format( 199 | default_factory=self.default_factory, 200 | repr=super(OrderedDefaultDict, self).__repr__() 201 | ) 202 | 203 | 204 | def attrify(obj): 205 | if isinstance(obj, list): 206 | for i, v in enumerate(obj): 207 | obj[i] = attrify(v) 208 | return obj 209 | elif isinstance(obj, dict): 210 | attrd = AttrDict() 211 | for key, value in obj.items(): 212 | value = attrify(value) 213 | setattr(attrd, key, value) 214 | return attrd 215 | else: 216 | return obj 217 | -------------------------------------------------------------------------------- /pydu/dt.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | class timer(object): 5 | """ 6 | A timer can time how long does calling take as a context manager or decorator. 7 | If assign ``print_func`` with ``sys.stdout.write``, ``logger.info`` and so on, 8 | timer will print the spent time. 9 | """ 10 | 11 | def __init__(self, print_func=None): 12 | self.elapsed = None 13 | self.print_func = print_func 14 | 15 | def __enter__(self): 16 | self.start = time.time() 17 | 18 | def __exit__(self, *_): 19 | self.elapsed = time.time() - self.start 20 | if self.print_func: 21 | self.print_func(self.__str__()) 22 | 23 | def __call__(self, fun): 24 | def wrapper(*args, **kwargs): 25 | with self: 26 | return fun(*args, **kwargs) 27 | return wrapper 28 | 29 | def __str__(self): 30 | return 'Spent time: {}s'.format(self.elapsed) 31 | -------------------------------------------------------------------------------- /pydu/environ.py: -------------------------------------------------------------------------------- 1 | import os 2 | from contextlib import contextmanager 3 | from pydu.list import tolist 4 | from pydu.compat import iteritems 5 | 6 | 7 | @contextmanager 8 | def environ(**kwargs): 9 | """ 10 | Context manager for updating one or more environment variables. 11 | 12 | Preserves the previous environment variable (if available) and 13 | recovers when exiting the context manager. 14 | 15 | If given variable_name=None, it means removing the variable from 16 | environment temporarily. 17 | """ 18 | original_kwargs = {} 19 | 20 | for key in kwargs: 21 | original_kwargs[key] = os.environ.get(key, None) 22 | if kwargs[key] is None and original_kwargs[key] is not None: 23 | del os.environ[key] 24 | elif kwargs[key] is not None: 25 | os.environ[key] = kwargs[key] 26 | 27 | yield 28 | 29 | for key, value in iteritems(original_kwargs): 30 | if value is None: 31 | os.environ.pop(key, None) 32 | continue 33 | 34 | os.environ[key] = value 35 | 36 | 37 | @contextmanager 38 | def path(append=None, prepend=None, replace=None): 39 | """ 40 | Context manager for updating the PATH environment variable which 41 | appends, prepends or replaces the PATH with given string or 42 | a list of strings. 43 | """ 44 | original = os.environ['PATH'] 45 | 46 | if replace: 47 | replace = tolist(replace) 48 | os.environ['PATH'] = os.pathsep.join(replace) 49 | else: 50 | if append: 51 | append = tolist(append) 52 | append.insert(0, os.environ['PATH']) 53 | os.environ['PATH'] = os.pathsep.join(append) 54 | 55 | if prepend: 56 | prepend = tolist(prepend) 57 | prepend.append(os.environ['PATH']) 58 | os.environ['PATH'] = os.pathsep.join(prepend) 59 | 60 | yield 61 | 62 | os.environ['PATH'] = original 63 | -------------------------------------------------------------------------------- /pydu/exception.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from functools import wraps 3 | from .compat import PY2 4 | 5 | 6 | if PY2: 7 | @contextlib.contextmanager 8 | def ignore(*exceptions): 9 | try: 10 | yield 11 | except exceptions: 12 | pass 13 | else: 14 | ignore = contextlib.suppress 15 | 16 | 17 | def default_if_except(exception_clses, default=None): 18 | """ 19 | A exception decorator which excepts given exceptions and 20 | return default value. 21 | """ 22 | def _default_if_except(func): 23 | @wraps(func) 24 | def wrapper(*args, **kwargs): 25 | try: 26 | return func(*args, **kwargs) 27 | except exception_clses: 28 | return default 29 | return wrapper 30 | return _default_if_except 31 | -------------------------------------------------------------------------------- /pydu/functional.py: -------------------------------------------------------------------------------- 1 | from .compat import reduce 2 | 3 | 4 | def compose(*funcs): 5 | """ 6 | Compose all functions. The previous function must accept one argument, 7 | which is the output of the next function. The last function can accept 8 | any args and kwargs. 9 | 10 | compose(f1, f2, f3)(*x) is same to f1(f2(f3(*x))). 11 | """ 12 | return reduce( 13 | lambda f1, f2: (lambda *args, **kwargs: f2(f1(*args, **kwargs))), 14 | reversed(funcs)) 15 | -------------------------------------------------------------------------------- /pydu/inspect.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import inspect 4 | 5 | from .compat import PY2 6 | 7 | 8 | def getargspec(func): 9 | """ 10 | Get the names and default values of a function's parameters. 11 | 12 | A tuple of four things is returned: (args, varargs, keywords, defaults). 13 | 'args' is a list of the argument names, including keyword-only argument names. 14 | 'varargs' and 'keywords' are the names of the * and ** parameters or None. 15 | 'defaults' is an n-tuple of the default values of the last n parameters. 16 | """ 17 | if PY2: 18 | return inspect.getargspec(func) 19 | else: 20 | sig = inspect.signature(func) 21 | args = [ 22 | p.name for p in sig.parameters.values() 23 | if p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD 24 | ] 25 | varargs = [ 26 | p.name for p in sig.parameters.values() 27 | if p.kind == inspect.Parameter.VAR_POSITIONAL 28 | ] 29 | varargs = varargs[0] if varargs else None 30 | varkw = [ 31 | p.name for p in sig.parameters.values() 32 | if p.kind == inspect.Parameter.VAR_KEYWORD 33 | ] 34 | varkw = varkw[0] if varkw else None 35 | defaults = [ 36 | p.default for p in sig.parameters.values() 37 | if 38 | p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD and p.default is not p.empty 39 | ] or None 40 | return args, varargs, varkw, defaults 41 | 42 | 43 | def get_func_args(func): 44 | """ 45 | Return a list of the argument names. Arguments such as 46 | *args and **kwargs are not included. 47 | """ 48 | if PY2: 49 | argspec = inspect.getargspec(func) 50 | if inspect.ismethod(func): 51 | return argspec.args[1:] # ignore 'self' 52 | return argspec.args 53 | else: 54 | sig = inspect.signature(func) 55 | return [ 56 | name for name, param in sig.parameters.items() 57 | if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD and name != 'self' 58 | ] 59 | 60 | 61 | def get_func_full_args(func): 62 | """ 63 | Return a list of (argument name, default value) tuples. If the argument 64 | does not have a default value, omit it in the tuple. Arguments such as 65 | *args and **kwargs are also included. 66 | """ 67 | if PY2: 68 | argspec = inspect.getargspec(func) 69 | if inspect.ismethod(func): 70 | args = argspec.args[1:] # ignore 'self' 71 | else: 72 | args = argspec.args 73 | defaults = argspec.defaults or [] 74 | # Split args into two lists depending on whether they have default value 75 | no_default = args[:len(args) - len(defaults)] 76 | with_default = args[len(args) - len(defaults):] 77 | # Join the two lists and combine it with default values 78 | args = [(arg,) for arg in no_default] + zip(with_default, defaults) 79 | # Add possible *args and **kwargs and prepend them with '*' or '**' 80 | varargs = [('*' + argspec.varargs,)] if argspec.varargs else [] 81 | kwargs = [('**' + argspec.keywords,)] if argspec.keywords else [] 82 | return args + varargs + kwargs 83 | else: 84 | sig = inspect.signature(func) 85 | args = [] 86 | for arg_name, param in sig.parameters.items(): 87 | name = arg_name 88 | # Ignore 'self' 89 | if name == 'self': 90 | continue 91 | if param.kind == inspect.Parameter.VAR_POSITIONAL: 92 | name = '*' + name 93 | elif param.kind == inspect.Parameter.VAR_KEYWORD: 94 | name = '**' + name 95 | if param.default != inspect.Parameter.empty: 96 | args.append((name, param.default)) 97 | else: 98 | args.append((name,)) 99 | return args 100 | 101 | 102 | def func_accepts_kwargs(func): 103 | """ 104 | Check whether or not the func accepts kwargs. 105 | """ 106 | # Not all callables are inspectable with getargspec, so we'll 107 | # try a couple different ways but in the end fall back on assuming 108 | # it is -- we don't want to prevent registration of valid but weird 109 | # callables. 110 | if PY2: 111 | try: 112 | argspec = inspect.getargspec(func) 113 | except TypeError: 114 | try: 115 | argspec = inspect.getargspec(func.__call__) 116 | except (TypeError, AttributeError): 117 | argspec = None 118 | return not argspec or argspec[2] is not None 119 | else: 120 | return any( 121 | p for p in inspect.signature(func).parameters.values() 122 | if p.kind == p.VAR_KEYWORD 123 | ) 124 | 125 | 126 | def func_accepts_var_args(func): 127 | """ 128 | Check whether or not the func accepts var args. 129 | """ 130 | if PY2: 131 | return inspect.getargspec(func)[1] is not None 132 | else: 133 | return any( 134 | p for p in inspect.signature(func).parameters.values() 135 | if p.kind == p.VAR_POSITIONAL 136 | ) 137 | 138 | 139 | def func_supports_parameter(func, parameter): 140 | """ 141 | Check whether or the func supports the given parameter. 142 | """ 143 | if PY2: 144 | args, varargs, varkw, defaults = inspect.getargspec(func) 145 | if inspect.ismethod(func): 146 | args = args[1:] # ignore 'self' 147 | return parameter in args + [varargs, varkw] 148 | else: 149 | parameters = [name for name in inspect.signature(func).parameters if name != 'self'] 150 | return parameter in parameters 151 | 152 | 153 | def func_has_no_args(func): 154 | """ 155 | Check whether or not the func has any args. 156 | """ 157 | args = inspect.getargspec(func)[0] if PY2 else [ 158 | p for name, p in inspect.signature(func).parameters.items() 159 | if p.kind == p.POSITIONAL_OR_KEYWORD and name != 'self' 160 | ] 161 | return len(args) == 1 162 | -------------------------------------------------------------------------------- /pydu/iter.py: -------------------------------------------------------------------------------- 1 | """iteration tools""" 2 | from .compat import builtins, imap 3 | 4 | 5 | def first(iterable): 6 | """ 7 | Get the first item in the iterable. 8 | """ 9 | return next(iter(iterable)) 10 | 11 | 12 | def last(iterable): 13 | """ 14 | Get the last item in the iterable. 15 | Warning, this can be slow due to iter step by step to last one. 16 | """ 17 | item = None 18 | for item in iterable: 19 | pass 20 | return item 21 | 22 | 23 | def all(iterable, predicate): 24 | """ 25 | Returns True if all elements in the given iterable are True for the 26 | given predicate function. 27 | """ 28 | return builtins.all(predicate(x) for x in iterable) 29 | 30 | 31 | def any(iterable, predicate): 32 | """ 33 | Returns True if any element in the given iterable is True for the 34 | given predicate function. 35 | """ 36 | return builtins.any(predicate(x) for x in iterable) 37 | 38 | 39 | def join(iterable, separator=''): 40 | """ 41 | Join each item of iterable to string. 42 | """ 43 | return separator.join(imap(str, iterable)) 44 | -------------------------------------------------------------------------------- /pydu/list.py: -------------------------------------------------------------------------------- 1 | 2 | try: 3 | # Python 3 4 | from collections.abc import Iterable 5 | except ImportError: 6 | # Python 2.7 7 | from collections import Iterable 8 | 9 | from pydu.compat import strbytes_types 10 | 11 | 12 | def uniq(seq, key=None): 13 | """ 14 | Removes duplicate elements from a list while preserving the order of the rest. 15 | 16 | The value of the optional `key` parameter should be a function that 17 | takes a single argument and returns a key to test the uniqueness. 18 | """ 19 | key = key or (lambda x: x) 20 | seen = set() 21 | uniq_list = [] 22 | for value in seq: 23 | uniq_value = key(value) 24 | if uniq_value in seen: 25 | continue 26 | seen.add(uniq_value) 27 | uniq_list.append(value) 28 | return uniq_list 29 | 30 | 31 | def tolist(obj): 32 | """ 33 | Convert given `obj` to list. 34 | 35 | If `obj` is not a list, return `[obj]`, else return `obj` itself. 36 | """ 37 | if not isinstance(obj, list): 38 | return [obj] 39 | return obj 40 | 41 | 42 | # https://stackoverflow.com/questions/2158395/flatten-an-irregular-list-of-lists 43 | def flatten(seq): 44 | """ 45 | Generate each element of the given `seq`. If the element is iterable and 46 | is not string, it yields each sub-element of the element recursively. 47 | """ 48 | for element in seq: 49 | if isinstance(element, Iterable) and \ 50 | not isinstance(element, strbytes_types): 51 | for sub in flatten(element): 52 | yield sub 53 | else: 54 | yield element 55 | -------------------------------------------------------------------------------- /pydu/misc.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import linecache 4 | import functools 5 | import io 6 | from threading import Thread 7 | 8 | from . import logger 9 | 10 | 11 | class TimeoutError(Exception): 12 | pass 13 | 14 | 15 | def timeout(seconds, error_message='Time out'): 16 | def decorated(func): 17 | 18 | @functools.wraps(func) 19 | def wrapper(*args, **kwargs): 20 | share = [TimeoutError(error_message)] 21 | 22 | def func_with_except(): 23 | try: 24 | share[0] = func(*args, **kwargs) 25 | except Exception as e: 26 | share[0] = e 27 | 28 | t = Thread(target=func_with_except) 29 | t.daemon = True 30 | try: 31 | t.start() 32 | t.join(seconds) 33 | except Exception as e: 34 | logger.error('Starting timeout thread for %s error', e) 35 | raise e 36 | result = share[0] 37 | if isinstance(result, BaseException): 38 | raise result 39 | return result 40 | 41 | return wrapper 42 | 43 | return decorated 44 | 45 | 46 | def trace(func): # pragma: no cover 47 | def globaltrace(frame, why, arg): 48 | if why == 'call': 49 | return localtrace 50 | return None 51 | 52 | def localtrace(frame, why, arg): 53 | if why == 'line': 54 | # record the file name and line number of every trace 55 | filename = frame.f_code.co_filename 56 | lineno = frame.f_lineno 57 | bname = os.path.basename(filename) 58 | print('{}({}): {}\n'.format( 59 | bname, 60 | lineno, 61 | linecache.getline(filename, lineno).strip('\r\n'))) 62 | return localtrace 63 | 64 | def _func(*args, **kwds): 65 | try: 66 | sys.settrace(globaltrace) 67 | result = func(*args, **kwds) 68 | return result 69 | finally: 70 | sys.settrace(None) 71 | 72 | return _func 73 | 74 | 75 | # https://github.com/giampaolo/psutil/blob/master/psutil/_common.py 76 | def memoize(func): 77 | """ 78 | A simple memoize decorator for functions supporting (hashable) 79 | positional arguments. 80 | It also provides a cache_clear() function for clearing the cache: 81 | 82 | >>> @memoize 83 | ... def foo() 84 | ... return 1 85 | ... 86 | >>> foo() 87 | 1 88 | >>> foo.cache_clear() 89 | >>> 90 | """ 91 | @functools.wraps(func) 92 | def wrapper(*args, **kwargs): 93 | key = (args, frozenset(sorted(kwargs.items()))) 94 | try: 95 | return cache[key] 96 | except KeyError: 97 | ret = cache[key] = func(*args, **kwargs) 98 | return ret 99 | 100 | def cache_clear(): 101 | """Clear cache.""" 102 | cache.clear() 103 | 104 | cache = {} 105 | wrapper.cache_clear = cache_clear 106 | return wrapper 107 | 108 | 109 | # https://github.com/giampaolo/psutil/blob/master/psutil/_common.py 110 | def memoize_when_activated(func): 111 | """ 112 | A memoize decorator which is disabled by default. It can be 113 | activated and deactivated on request. 114 | For efficiency reasons it can be used only against class methods 115 | accepting no arguments. 116 | 117 | >>> class Foo: 118 | ... @memoize 119 | ... def foo(self) 120 | ... print(1) 121 | ... 122 | >>> f = Foo() 123 | >>> # deactivated (default) 124 | >>> foo() 125 | 1 126 | >>> foo() 127 | 1 128 | >>> 129 | >>> # activated 130 | >>> foo.cache_activate() 131 | >>> foo() 132 | 1 133 | >>> foo() 134 | >>> foo() 135 | >>> 136 | """ 137 | @functools.wraps(func) 138 | def wrapper(self): 139 | if not wrapper.cache_activated: 140 | return func(self) 141 | else: 142 | try: 143 | ret = cache[func] 144 | except KeyError: 145 | ret = cache[func] = func(self) 146 | return ret 147 | 148 | def cache_activate(): 149 | """Activate cache.""" 150 | wrapper.cache_activated = True 151 | 152 | def cache_deactivate(): 153 | """Deactivate and clear cache.""" 154 | wrapper.cache_activated = False 155 | cache.clear() 156 | 157 | cache = {} 158 | wrapper.cache_activated = False 159 | wrapper.cache_activate = cache_activate 160 | wrapper.cache_deactivate = cache_deactivate 161 | return wrapper 162 | 163 | 164 | # https://github.com/requests/requests/blob/master/requests/utils.py 165 | def super_len(obj): 166 | total_length = None 167 | current_position = 0 168 | 169 | if hasattr(obj, '__len__'): 170 | total_length = len(obj) 171 | 172 | elif hasattr(obj, 'len'): 173 | total_length = obj.len 174 | 175 | elif hasattr(obj, 'fileno'): 176 | try: 177 | fileno = obj.fileno() 178 | except io.UnsupportedOperation: 179 | pass 180 | else: 181 | total_length = os.fstat(fileno).st_size 182 | 183 | if hasattr(obj, 'tell'): 184 | try: 185 | current_position = obj.tell() 186 | except (OSError, IOError): 187 | # This can happen in some weird situations, such as when the file 188 | # is actually a special file descriptor like stdin. In this 189 | # instance, we don't know what the length is, so set it to zero and 190 | # let requests chunk it instead. 191 | if total_length is not None: 192 | current_position = total_length 193 | else: 194 | if hasattr(obj, 'seek') and total_length is None: 195 | # StringIO and BytesIO have seek but no useable fileno 196 | try: 197 | # seek to end of file 198 | obj.seek(0, 2) 199 | total_length = obj.tell() 200 | 201 | # seek back to current position to support 202 | # partially read file-like objects 203 | obj.seek(current_position or 0) 204 | except (OSError, IOError): 205 | total_length = 0 206 | 207 | if total_length is None: 208 | total_length = 0 209 | 210 | return max(0, total_length - current_position) 211 | -------------------------------------------------------------------------------- /pydu/network.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import struct 3 | import ctypes 4 | import binascii 5 | from contextlib import closing 6 | 7 | from .platform import WINDOWS 8 | from .string import safeencode, safeunicode 9 | from .convert import hex2dec, dec2hex 10 | 11 | 12 | # https://github.com/hickeroar/win_inet_pton/blob/master/win_inet_pton.py 13 | if WINDOWS: 14 | class _sockaddr(ctypes.Structure): 15 | _fields_ = [("sa_family", ctypes.c_short), 16 | ("__pad1", ctypes.c_ushort), 17 | ("ipv4_addr", ctypes.c_byte * 4), 18 | ("ipv6_addr", ctypes.c_byte * 16), 19 | ("__pad2", ctypes.c_ulong)] 20 | 21 | 22 | WSAStringToAddressA = ctypes.windll.ws2_32.WSAStringToAddressA 23 | WSAAddressToStringA = ctypes.windll.ws2_32.WSAAddressToStringA 24 | 25 | 26 | def _win_inet_pton(address_family, ip_str): 27 | ip_str = safeencode(ip_str) 28 | addr = _sockaddr() 29 | addr.sa_family = address_family 30 | addr_size = ctypes.c_int(ctypes.sizeof(addr)) 31 | 32 | if WSAStringToAddressA( 33 | ip_str, 34 | address_family, 35 | None, 36 | ctypes.byref(addr), 37 | ctypes.byref(addr_size) 38 | ) != 0: 39 | raise socket.error(ctypes.FormatError()) 40 | 41 | if address_family == socket.AF_INET: 42 | return ctypes.string_at(addr.ipv4_addr, 4) 43 | if address_family == socket.AF_INET6: 44 | return ctypes.string_at(addr.ipv6_addr, 16) 45 | 46 | raise socket.error('unknown address family') 47 | 48 | 49 | def _win_inet_ntop(address_family, packed_ip): 50 | addr = _sockaddr() 51 | addr.sa_family = address_family 52 | addr_size = ctypes.c_int(ctypes.sizeof(addr)) 53 | ip_string = ctypes.create_string_buffer(128) 54 | ip_string_size = ctypes.c_int(ctypes.sizeof(ip_string)) 55 | 56 | if address_family == socket.AF_INET: 57 | if len(packed_ip) != ctypes.sizeof(addr.ipv4_addr): 58 | raise socket.error('packed IP wrong length for inet_ntoa') 59 | ctypes.memmove(addr.ipv4_addr, packed_ip, 4) 60 | elif address_family == socket.AF_INET6: 61 | if len(packed_ip) != ctypes.sizeof(addr.ipv6_addr): 62 | raise socket.error('packed IP wrong length for inet_ntoa') 63 | ctypes.memmove(addr.ipv6_addr, packed_ip, 16) 64 | else: 65 | raise socket.error('unknown address family') 66 | 67 | if WSAAddressToStringA( 68 | ctypes.byref(addr), 69 | addr_size, 70 | None, 71 | ip_string, 72 | ctypes.byref(ip_string_size) 73 | ) != 0: 74 | raise socket.error(ctypes.FormatError()) 75 | 76 | return ip_string[:ip_string_size.value - 1] 77 | 78 | 79 | socket.inet_pton = _win_inet_pton 80 | socket.inet_ntop = _win_inet_ntop 81 | 82 | 83 | # https://github.com/kennethreitz/requests/blob/master/requests/utils.py 84 | def dotted_netmask(mask): 85 | """ 86 | Converts mask from /xx format to xxx.xxx.xxx.xxx 87 | Example: if mask is 24 function returns 255.255.255.0 88 | """ 89 | mask = int(mask) 90 | bits = 0xffffffff ^ (1 << 32 - mask) - 1 91 | return socket.inet_ntoa(struct.pack('>I', bits)) 92 | 93 | 94 | # http://en.wikipedia.org/wiki/Private_network 95 | private_ipv4s = [ 96 | ('10.0.0.0', 8), # 10.0.0.0 - 10.255.255.255 97 | ('172.16.0.0', 12), # 172.16.0.0 - 172.31.255.255 98 | ('192.168.0.0', 16), # 192.168.0.0 - 192.168.255.255 99 | ] 100 | 101 | 102 | # https://github.com/kennethreitz/requests/blob/master/requests/utils.py 103 | def is_ipv4(ip): 104 | """ 105 | Returns True if the IPv4 address ia valid, otherwise returns False. 106 | """ 107 | try: 108 | socket.inet_aton(ip) 109 | except socket.error: 110 | return False 111 | return True 112 | 113 | 114 | def is_ipv6(ip): 115 | """ 116 | Returns True if the IPv6 address ia valid, otherwise returns False. 117 | """ 118 | try: 119 | socket.inet_pton(socket.AF_INET6, ip) 120 | except socket.error: 121 | return False 122 | return True 123 | 124 | 125 | def get_free_port(): 126 | with closing(socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)) as s: 127 | s.bind(('127.0.0.1', 0)) 128 | _, port = s.getsockname() 129 | return port 130 | 131 | 132 | # https://stackoverflow.com/questions/5619685/conversion-from-ip-string-to-integer-and-backward-in-python 133 | # https://stackoverflow.com/questions/11894717/python-convert-ipv6-to-an-integer 134 | def ip2int(ip_str): 135 | """ 136 | Convert ip to integer. Support IPV4 and IPV6. 137 | Raise `ValueError` if convert failed. 138 | """ 139 | try: 140 | return struct.unpack("!I", socket.inet_aton(ip_str))[0] 141 | except socket.error: 142 | pass 143 | 144 | try: 145 | return hex2dec(binascii.hexlify(socket.inet_pton(socket.AF_INET6, ip_str))) 146 | except socket.error: 147 | pass 148 | 149 | raise ValueError('{!r} does not appear to be an IPv4 or IPv6 address'.format(ip_str)) 150 | 151 | 152 | # https://stackoverflow.com/questions/5619685/conversion-from-ip-string-to-integer-and-backward-in-python 153 | def int2ip(ip_int): 154 | """ 155 | Convert integer to ip. Support IPV4 and IPV6. 156 | Raise `ValueError` if convert failed. 157 | """ 158 | try: 159 | return socket.inet_ntoa(struct.pack("!I", ip_int)) 160 | except (socket.error, struct.error): 161 | pass 162 | 163 | try: 164 | ip_str = socket.inet_ntop(socket.AF_INET6, binascii.unhexlify(dec2hex(ip_int))) 165 | return safeunicode(ip_str, encoding='ascii') 166 | except (socket.error, struct.error): 167 | pass 168 | 169 | raise ValueError('{!r} does not appear to be an IPv4 or IPv6 address'.format(ip_int)) 170 | -------------------------------------------------------------------------------- /pydu/path.py: -------------------------------------------------------------------------------- 1 | import os 2 | from contextlib import contextmanager 3 | 4 | 5 | @contextmanager 6 | def cd(path): 7 | """ 8 | Context manager for cd the given path. 9 | """ 10 | cwd = os.getcwd() 11 | os.chdir(path) 12 | yield 13 | os.chdir(cwd) 14 | 15 | 16 | def is_super_path(path1, path2): 17 | """ 18 | Whether `path1` is the super path of `path2`. 19 | Note that if `path1` is same as `path2`, it's also regarded as 20 | the super path os `path2`. 21 | For instance "/", "/opt" and "/opt/test" are all the super paths of "/opt/test", 22 | while "/opt/t" is the super path of "/opt/test". 23 | """ 24 | path1 = os.path.normpath(path1) 25 | current_path2 = os.path.normpath(path2) 26 | parent_path2 = os.path.dirname(current_path2) 27 | if path1 == current_path2: 28 | return True 29 | 30 | while parent_path2 != current_path2: 31 | if path1 == parent_path2: 32 | return True 33 | current_path2 = parent_path2 34 | parent_path2 = os.path.dirname(parent_path2) 35 | 36 | return False 37 | 38 | 39 | def normjoin(path, *paths): 40 | """Join one or more path components intelligently and normalize it.""" 41 | return os.path.normpath(os.path.join(path, *paths)) 42 | 43 | 44 | def filename(path): 45 | """Return the filename without extension.""" 46 | return os.path.splitext(os.path.basename(path))[0] 47 | 48 | 49 | def fileext(path): 50 | """ 51 | Return the file extension. 52 | If file has not extension, return empty string. 53 | """ 54 | return os.path.splitext(os.path.basename(path))[1] 55 | -------------------------------------------------------------------------------- /pydu/platform.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | WINDOWS = os.name == 'nt' 6 | LINUX = sys.platform.startswith('linux') 7 | POSIX = os.name == 'posix' 8 | DARWIN = sys.platform.startswith('darwin') 9 | SUNOS = sys.platform.startswith('sunos') 10 | SMARTOS = os.uname()[3].startswith('joyent_') if SUNOS else False 11 | FREEBSD = sys.platform.startswith('freebsd') 12 | NETBSD = sys.platform.startswith('netbsd') 13 | OPENBSD = sys.platform.startswith('openbsd') 14 | AIX = sys.platform.startswith('aix') 15 | -------------------------------------------------------------------------------- /pydu/process.py: -------------------------------------------------------------------------------- 1 | try: 2 | import psutil 3 | except ImportError: 4 | raise ImportError('Need to pip install psutil if you use pydu.process') 5 | 6 | from .path import is_super_path 7 | 8 | 9 | def get_processes_by_path(path): 10 | """ 11 | Get processes which are running on given path or sub path of given path. 12 | """ 13 | pinfos = [] 14 | for proc in psutil.process_iter(): 15 | pinfo = proc.as_dict(attrs=['pid', 'name', 'exe', 'cwd', 'open_files']) 16 | 17 | using_paths = [] 18 | if pinfo['exe']: 19 | using_paths.append(pinfo['exe']) 20 | if pinfo['cwd']: 21 | using_paths.append(pinfo['cwd']) 22 | if pinfo['open_files']: 23 | using_paths.extend(pinfo['open_files']) 24 | 25 | for using_path in using_paths: 26 | if is_super_path(path, using_path): 27 | continue 28 | pinfos.append({ 29 | 'pid': pinfo['pid'], 30 | 'name': pinfo['name'], 31 | 'cmdline': pinfo['exe'] 32 | }) 33 | return pinfos 34 | -------------------------------------------------------------------------------- /pydu/request.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import tempfile 4 | import socket 5 | 6 | from . import logger 7 | from .string import safeunicode 8 | from .compat import PY2, string_types, urlparse, urlib, urlencode 9 | 10 | 11 | class FileName(object): 12 | @staticmethod 13 | def from_url(url): 14 | """ 15 | Detected filename as unicode or None 16 | """ 17 | filename = os.path.basename(urlparse.urlparse(url).path) 18 | if len(filename.strip(' \n\t.')) == 0: 19 | return None 20 | return safeunicode(filename) 21 | 22 | # http://greenbytes.de/tech/tc2231/ 23 | @staticmethod 24 | def from_headers(headers): 25 | """ 26 | Detect filename from Content-Disposition headers if present. 27 | 28 | headers: as dict, list or string 29 | """ 30 | if not headers: 31 | return None 32 | 33 | if isinstance(headers, string_types): 34 | headers = [line.split(':', 1) for line in headers.splitlines()] 35 | if isinstance(headers, list): 36 | headers = dict(headers) 37 | 38 | cdisp = headers.get("Content-Disposition") 39 | if not cdisp: 40 | return None 41 | 42 | cdtype = cdisp.split(';') 43 | if len(cdtype) == 1: 44 | return None 45 | if cdtype[0].strip().lower() not in ('inline', 'attachment'): 46 | return None 47 | 48 | # several filename params is illegal, but just in case 49 | fnames = [x for x in cdtype[1:] if x.strip().startswith('filename=')] 50 | if len(fnames) > 1: 51 | return None 52 | 53 | name = fnames[0].split('=')[1].strip(' \t"') 54 | name = os.path.basename(name) 55 | if not name: 56 | return None 57 | return name 58 | 59 | @classmethod 60 | def from_any(cls, dst=None, headers=None, url=None): 61 | return dst or cls.from_headers(headers) or cls.from_url(url) 62 | 63 | 64 | # http://bitbucket.org/techtonik/python-wget/ 65 | def download(url, dst=None): 66 | """ 67 | High level function, which downloads URL into tmp file in current 68 | directory and then renames it to filename autodetected from either URL 69 | or HTTP headers. 70 | 71 | url: which url to download 72 | dst: filename or directory of destination 73 | """ 74 | # detect of dst is a directory 75 | dst_ = None 76 | if dst and os.path.isdir(dst): 77 | dst_ = dst 78 | dst = None 79 | 80 | # get filename for temp file in current directory 81 | prefix = FileName.from_any(dst=dst, url=url) 82 | fd, tmpfile = tempfile.mkstemp(".tmp", prefix=prefix, dir=".") 83 | os.close(fd) 84 | os.unlink(tmpfile) 85 | 86 | if PY2: 87 | binurl = url 88 | else: 89 | # Python 3 can not quote URL as needed 90 | binurl = list(urlparse.urlsplit(url)) 91 | binurl[2] = urlparse.quote(binurl[2]) 92 | binurl = urlparse.urlunsplit(binurl) 93 | tmpfile, headers = urlib.urlretrieve(binurl, tmpfile) 94 | filename = FileName.from_any(dst=dst, headers=headers, url=url) 95 | if dst_: 96 | filename = os.path.join(dst_, filename) 97 | 98 | if os.path.exists(filename): 99 | os.unlink(filename) 100 | shutil.move(tmpfile, filename) 101 | 102 | return filename 103 | 104 | 105 | def check_connect(ip, port, retry=1, timeout=0.5): 106 | """ 107 | Check whether given ``ip`` and ``port`` could connect or not. 108 | It will ``retry`` and ``timeout`` on given. 109 | """ 110 | while retry: 111 | try: 112 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 113 | except socket.error as e: 114 | logger.exception(e) 115 | retry -= 1 116 | continue 117 | 118 | try: 119 | s.settimeout(timeout) 120 | s.connect((ip, port)) 121 | return s.getsockname()[0] 122 | except socket.error: 123 | logger.error("Connect to ip:%s port:%d fail", ip, port) 124 | s.close() 125 | finally: 126 | retry -= 1 127 | return None 128 | 129 | 130 | def update_query_params(url, params): 131 | """ 132 | Update query params of given url and return new url. 133 | """ 134 | parts = list(urlparse.urlparse(url)) 135 | query = dict(urlparse.parse_qsl(parts[4])) 136 | query.update(params) 137 | parts[4] = urlencode(query) 138 | new_url = urlparse.urlunparse(parts) 139 | return new_url 140 | 141 | 142 | def cookies_str_to_dict(cookies): 143 | """ 144 | Convert cookies from str to dict. 145 | """ 146 | if not isinstance(cookies, str): 147 | raise TypeError('Invalid type of cookies_string !') 148 | 149 | cookies_obj = {} 150 | for item in cookies.split(';'): 151 | item = item.strip().replace('\t', '').replace('\n', '') 152 | if '=' not in item: 153 | continue 154 | key, value = item.split('=', 1) 155 | cookies_obj[key] = value 156 | return cookies_obj 157 | -------------------------------------------------------------------------------- /pydu/set.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import collections 3 | 4 | 5 | class OrderedSet(object): 6 | """ 7 | A set which keeps the ordering of the inserted items. 8 | """ 9 | 10 | def __init__(self, iterable=None): 11 | self.dict = collections.OrderedDict.fromkeys(iterable or ()) 12 | 13 | def add(self, item): 14 | self.dict[item] = None 15 | 16 | def remove(self, item): 17 | del self.dict[item] 18 | 19 | def discard(self, item): 20 | try: 21 | self.remove(item) 22 | except KeyError: 23 | pass 24 | 25 | def __iter__(self): 26 | return iter(self.dict) 27 | 28 | def __contains__(self, item): 29 | return item in self.dict 30 | 31 | def __bool__(self): 32 | return bool(self.dict) 33 | 34 | def __nonzero__(self): 35 | return bool(self.dict) 36 | 37 | def __len__(self): 38 | return len(self.dict) 39 | -------------------------------------------------------------------------------- /pydu/slot.py: -------------------------------------------------------------------------------- 1 | from .compat import iteritems, izip 2 | 3 | 4 | class SlotBase(object): 5 | """ 6 | Base class for class using __slots__. 7 | If some args or kwargs are not given when initialize class, 8 | the value of them will be set with ``None``. 9 | """ 10 | def __init__(self, *args, **kwargs): 11 | setted = set() 12 | kwargs_ = dict(izip(self.__slots__, args)) 13 | kwargs_.update(kwargs) 14 | for key, value in iteritems(kwargs_): 15 | setattr(self, key, value) 16 | setted.add(key) 17 | for key in set(self.__slots__) - setted: 18 | setattr(self, key, None) 19 | -------------------------------------------------------------------------------- /pydu/string.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import locale 3 | from .compat import text_type 4 | 5 | 6 | preferredencoding = locale.getpreferredencoding() 7 | 8 | 9 | def safeunicode(obj, encoding='utf-8'): 10 | """ 11 | Converts any given object to unicode string. 12 | 13 | >>> safeunicode('hello') 14 | u'hello' 15 | >>> safeunicode(2) 16 | u'2' 17 | >>> safeunicode('\xe4\xb8\xad\xe6\x96\x87') 18 | u'中文' 19 | """ 20 | t = type(obj) 21 | if t is text_type: 22 | return obj 23 | elif t is bytes: 24 | return obj.decode(encoding) 25 | else: 26 | return text_type(obj) 27 | 28 | 29 | def safeencode(obj, encoding='utf-8'): 30 | """ 31 | Converts any given object to encoded string (default: utf-8). 32 | 33 | >>> safestr('hello') 34 | 'hello' 35 | >>> safestr(2) 36 | '2' 37 | """ 38 | t = type(obj) 39 | if t is text_type: 40 | return obj.encode(encoding) 41 | elif t is bytes: 42 | return obj 43 | else: 44 | return text_type(obj).encode(encoding) 45 | 46 | 47 | iters = [list, tuple, set, frozenset] 48 | class _hack(tuple): pass 49 | iters = _hack(iters) 50 | iters.__doc__ = """ 51 | A list of iterable items (like lists, but not strings). Includes whichever 52 | of lists, tuples, sets, and Sets are available in this version of Python. 53 | """ 54 | 55 | 56 | def _strips(direction, text, remove): 57 | if isinstance(remove, iters): 58 | for subr in remove: 59 | text = _strips(direction, text, subr) 60 | return text 61 | 62 | if direction == 'l': 63 | if text.startswith(remove): 64 | return text[len(remove):] 65 | elif direction == 'r': 66 | if text.endswith(remove): 67 | return text[:-len(remove) or None] 68 | else: 69 | raise ValueError('Direction needs to be r or l.') 70 | return text 71 | 72 | 73 | def rstrips(text, remove): 74 | """ 75 | removes the string `remove` from the right of `text` 76 | >>> rstrips('foobar', 'bar') 77 | 'foo' 78 | """ 79 | return _strips('r', text, remove) 80 | 81 | 82 | def lstrips(text, remove): 83 | """ 84 | removes the string `remove` from the left of `text` 85 | 86 | >>> lstrips('foobar', 'foo') 87 | 'bar' 88 | >>> lstrips('FOOBARBAZ', ['FOO', 'BAR']) 89 | 'BAZ' 90 | >>> lstrips('FOOBARBAZ', ['BAR', 'FOO']) 91 | 'BARBAZ' 92 | 93 | """ 94 | return _strips('l', text, remove) 95 | 96 | 97 | def strips(text, remove): 98 | """ 99 | removes the string `remove` from the both sides of `text` 100 | >>> strips('foobarfoo', 'foo') 101 | 'bar' 102 | """ 103 | return rstrips(lstrips(text, remove), remove) 104 | 105 | 106 | def common_prefix(l): 107 | """ 108 | Return common prefix of the stings 109 | >>> common_prefix(['abcd', 'abc1']) 110 | 'abc' 111 | """ 112 | commons = [] 113 | for i in range(min(len(s) for s in l)): 114 | common = l[0][i] 115 | for c in l[1:]: 116 | if c[i] != common: 117 | return ''.join(commons) 118 | commons.append(common) 119 | return ''.join(commons) 120 | 121 | 122 | def common_suffix(l): 123 | """ 124 | Return common suffix of the stings 125 | >>> common_suffix(['dabc', '1abc']) 126 | 'abc' 127 | """ 128 | commons = [] 129 | for i in range(min(len(s) for s in l)): 130 | common = l[0][-i-1] 131 | for c in l[1:]: 132 | if c[-i-1] != common: 133 | return ''.join(reversed(commons)) 134 | commons.append(common) 135 | return ''.join(reversed(commons)) 136 | 137 | 138 | def sort(s, reverse=False): 139 | """ 140 | Sort given string by ascending order. 141 | If reverse is True, sorting given string by descending order. 142 | """ 143 | return ''.join(sorted(s, reverse=reverse)) 144 | -------------------------------------------------------------------------------- /pydu/unit.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | BYTE_UNITS = ('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB') 4 | 5 | 6 | class Bytes(object): 7 | """ 8 | Supply several methods dealing with bytes. 9 | """ 10 | def __init__(self, bytes): 11 | self.bytes = bytes 12 | 13 | def convert(self, unit=None, multiple=1024): 14 | """ 15 | Convert bytes with given ``unit``. 16 | If `unit` is None, convert bytes with suitable unit. 17 | Convert `multiple` is default to be 1024. 18 | """ 19 | step = 0 20 | if not unit: 21 | while self.bytes >= multiple and step < len(BYTE_UNITS) - 1: 22 | self.bytes /= multiple 23 | step += 1 24 | unit = BYTE_UNITS[step] 25 | 26 | else: # convert to specific unit 27 | index_of_unit = BYTE_UNITS.index(unit) 28 | while len(BYTE_UNITS) - 1 > step and index_of_unit != step: 29 | self.bytes /= multiple 30 | step += 1 31 | return self.bytes, unit 32 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest>=2.8.0 2 | pytest-xdist 3 | coverage 4 | psutil 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [metadata] 5 | license_file = LICENSE.txt 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pydu import __version__ 3 | from pydu.compat import PY2 4 | from setuptools import setup, find_packages 5 | from setuptools.command.test import test as TestCommand 6 | 7 | 8 | class PyTest(TestCommand): 9 | user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")] 10 | 11 | def initialize_options(self): 12 | TestCommand.initialize_options(self) 13 | try: 14 | from multiprocessing import cpu_count 15 | self.pytest_args = ['-n', str(cpu_count())] 16 | except (ImportError, NotImplementedError): 17 | self.pytest_args = ['-n', '1'] 18 | 19 | def finalize_options(self): 20 | TestCommand.finalize_options(self) 21 | self.test_args = [] 22 | self.test_suite = True 23 | 24 | def run_tests(self): 25 | import pytest 26 | 27 | errno = pytest.main(self.pytest_args) 28 | sys.exit(errno) 29 | 30 | 31 | test_requirements = [] 32 | for line in open('requirements-dev.txt'): 33 | requirement = line.strip() 34 | if requirement: 35 | test_requirements.append(requirement) 36 | 37 | 38 | open_kwargs = {} if PY2 else {'encoding': 'utf-8'} 39 | setup( 40 | name="pydu", 41 | version=__version__, 42 | description="Useful data structures, utils for Python.", 43 | long_description=open('README.md', **open_kwargs).read(), 44 | long_description_content_type='text/markdown', 45 | author="Prodesire", 46 | author_email='wangbinxin001@126.com', 47 | license='MIT License', 48 | url="https://github.com/Prodesire/pydu", 49 | cmdclass={'test': PyTest}, 50 | tests_require=test_requirements, 51 | packages=find_packages(), 52 | classifiers=[ 53 | 'Operating System :: OS Independent', 54 | 'Intended Audience :: Developers', 55 | 'License :: OSI Approved :: MIT License', 56 | 'Programming Language :: Python', 57 | 'Programming Language :: Python :: Implementation', 58 | 'Programming Language :: Python :: 2', 59 | 'Programming Language :: Python :: 2.7', 60 | 'Programming Language :: Python :: 3', 61 | 'Programming Language :: Python :: 3.5', 62 | 'Programming Language :: Python :: 3.6', 63 | 'Programming Language :: Python :: 3.7', 64 | 'Programming Language :: Python :: 3.8', 65 | 'Topic :: Software Development :: Libraries' 66 | ], 67 | ) 68 | -------------------------------------------------------------------------------- /stubs/pydu/__init__.pyi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/pydu/e6e4055f81dbbece55dccfe29b7fb82b9bf40610/stubs/pydu/__init__.pyi -------------------------------------------------------------------------------- /stubs/pydu/archive.pyi: -------------------------------------------------------------------------------- 1 | from tarfile import TarFile 2 | from zipfile import ZipFile 3 | from typing import List 4 | 5 | 6 | class Archive(object): 7 | _archive = ... # type: BaseArchive 8 | def __init__(self, file: file, ext: str='') -> None: ... 9 | def _archive_cls(self, file: file, ext: str='') -> None: ... 10 | def extract(self, dst: str='') -> None: ... 11 | def list(self) -> None: ... 12 | def filenames(self) -> list: ... 13 | def close(self) -> None: ... 14 | 15 | class BaseArchive(object): 16 | @staticmethod 17 | def _copy_permissions(mode: int, filename: str) -> None: ... 18 | def split_leading_dir(self, path: str) -> None: ... 19 | def has_leading_dir(self, paths: List[str]) -> None: ... 20 | def extract(self, dst: str) -> None: ... 21 | def list(self) -> list: ... 22 | def filenames(self) -> List[str]: ... 23 | 24 | class TarArchive(BaseArchive): 25 | _archive = ... # type: TarFile 26 | def extract(self, dst: str) -> None: ... 27 | def list(self) -> None: ... 28 | def filenames(self) -> List[str]: ... 29 | def close(self) -> None: ... 30 | 31 | class ZipArchive(BaseArchive): 32 | _archive = ... # type: ZipFile 33 | def extract(self, dst: str) -> None: ... 34 | def list(self) -> None: ... 35 | def filenames(self) -> List[str]: ... 36 | def close(self) -> None: ... 37 | -------------------------------------------------------------------------------- /stubs/pydu/cmd.pyi: -------------------------------------------------------------------------------- 1 | """Stubs for cmd""" 2 | from typing import Tuple, List, Union 3 | 4 | 5 | class TimeoutExpired(Exception): 6 | 7 | def __init__(self, cmd: str, 8 | timeout: Union[int, float], 9 | output: dict=None, 10 | stderr: dict=None) -> None: ... 11 | 12 | def run(cmd: str, 13 | shell: bool=..., 14 | env: dict=None, 15 | timeout: Union[int, float]=..., 16 | timeinterval: Union[int, float]=...) -> Tuple[int, str]: ... 17 | def run_with_en_env(cmd: str, 18 | shell: bool=..., 19 | env: dict=None, 20 | timeout: Union[int, float]=..., 21 | timeinterval: Union[int, float]=...) -> Tuple[int, str]: ... 22 | def terminate(pid: int) -> None: ... 23 | def cmdline_argv() -> List: ... 24 | -------------------------------------------------------------------------------- /stubs/pydu/console.pyi: -------------------------------------------------------------------------------- 1 | """Stubs for console""" 2 | from typing import Tuple 3 | 4 | 5 | def console_size(fallback: Tuple[int, int]=...) -> Tuple[int, int]: ... -------------------------------------------------------------------------------- /stubs/pydu/convert.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | 4 | def boolean(obj: Any) -> bool: ... 5 | def bin2oct(x: str) -> str: ... 6 | def bin2dec(x: str) -> int: ... 7 | def bin2hex(x: str) -> str: ... 8 | def oct2bin(x: str) -> str: ... 9 | def oct2dec(x: str) -> int: ... 10 | def oct2hex(x: str) -> str: ... 11 | def dec2bin(x: int) -> str: ... 12 | def dec2oct(x: int) -> str: ... 13 | def dec2hex(x: int) -> str: ... 14 | def hex2bin(x: str) -> str: ... 15 | def hex2oct(x: str) -> str: ... 16 | def hex2dec(x: str) -> int: ... 17 | -------------------------------------------------------------------------------- /stubs/pydu/dict.pyi: -------------------------------------------------------------------------------- 1 | import collections 2 | from typing import Iterable, Tuple, Any 3 | 4 | 5 | class CaseInsensitiveDict(collections.MutableMapping): 6 | _store = ... # type: dict 7 | def __init__(self, data: dict=None, **kwargs) -> None: ... 8 | def lower_items(self) -> Iterable[Tuple[str, Any]]: ... 9 | -------------------------------------------------------------------------------- /stubs/pydu/dt.pyi: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | 4 | class timer(object): 5 | elapsed = ... # type: float 6 | print_func = ... # type: Callable 7 | -------------------------------------------------------------------------------- /stubs/pydu/environ.pyi: -------------------------------------------------------------------------------- 1 | from typing import Dict, ContextManager, Union 2 | 3 | 4 | StrList = Union[str, list] 5 | 6 | def environ(kwargs: Dict[str, str]) -> ContextManager[None]: ... 7 | def path(append: StrList, prepend: StrList, replace: StrList) -> ContextManager[None]: ... -------------------------------------------------------------------------------- /stubs/pydu/exception.pyi: -------------------------------------------------------------------------------- 1 | from typing import ContextManager, Type, List, Any, Callable 2 | from pydu.compat import PY2 3 | 4 | 5 | if PY2: 6 | def ignore(*exceptions: Type[BaseException]) -> ContextManager[None]: ... 7 | else: 8 | class ignore(ContextManager[None]): 9 | def __init__(self, *exceptions: Type[BaseException]) -> None: ... 10 | 11 | 12 | def default_if_except(exceptions_clses: List[Exception], default: Any=None) -> Callable: ... 13 | -------------------------------------------------------------------------------- /stubs/pydu/functional.pyi: -------------------------------------------------------------------------------- 1 | from typing import List, Callable, Any 2 | 3 | 4 | def compose(*funcs: List[Callable]): 5 | return Any 6 | -------------------------------------------------------------------------------- /stubs/pydu/iter.pyi: -------------------------------------------------------------------------------- 1 | from typing import Iterable, TypeVar, Optional, Callable 2 | 3 | 4 | T = TypeVar('T') 5 | 6 | def first(iterable: Iterable[T]) -> T: ... 7 | def last(iterable: Iterable[T]) -> Optional[T]: ... 8 | def all(iterable: Iterable[T], predicate: Callable[[T], bool]) -> bool: ... 9 | def any(iterable: Iterable[T], predicate: Callable[[T], bool]) -> bool: ... 10 | def join(iterable: Iterable[T], separator: str) -> str: ... 11 | -------------------------------------------------------------------------------- /stubs/pydu/list.pyi: -------------------------------------------------------------------------------- 1 | from typing import Callable, Any, Hashable, Iterable 2 | 3 | 4 | KeyFunc = Callable[[Any], Hashable] 5 | def uniq(seq: Iterable[Any], key: KeyFunc=None) -> list: ... 6 | def tolist(obj: Any) -> list: ... 7 | def flatten(seq: Iterable[Any]) -> Iterable[Any]: ... 8 | -------------------------------------------------------------------------------- /stubs/pydu/misc.pyi: -------------------------------------------------------------------------------- 1 | from typing import Callable, Any 2 | 3 | 4 | AnyCallable = Callable[..., Any] 5 | 6 | def timeout(seconds: int, error_message: str) -> Callable[[AnyCallable], AnyCallable]: ... 7 | def trace(func: AnyCallable) -> AnyCallable: ... 8 | def memoize(func: AnyCallable) -> AnyCallable: ... 9 | def memoize_when_activated(func: AnyCallable) -> AnyCallable: ... 10 | def super_len(obj: Any) -> int: ... -------------------------------------------------------------------------------- /stubs/pydu/network.pyi: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | def dotted_netmask(mask: Union[int, str]) -> str: ... 4 | def is_ipv4(ip: str) -> bool: ... 5 | def is_ipv6(ip: str) -> bool: ... 6 | def ip2int(ip_str) -> int: ... 7 | def int2ip(ip_int: int) -> int: ... 8 | -------------------------------------------------------------------------------- /stubs/pydu/path.pyi: -------------------------------------------------------------------------------- 1 | from typing import ContextManager, List 2 | 3 | 4 | def cd(path: str) -> ContextManager[None]: ... 5 | def is_super_path(path1: str, path2: str) -> bool: ... 6 | def normjoin(path: str, *paths: List(str)) -> str: ... 7 | def filename(path: str) -> str: ... 8 | def fileexe(path: str) -> str: ... 9 | -------------------------------------------------------------------------------- /stubs/pydu/process.pyi: -------------------------------------------------------------------------------- 1 | from typing import List, Dict, Union 2 | 3 | def get_processes_by_path(path: str) -> List[Dict[str, Union[int, str]]]: ... 4 | -------------------------------------------------------------------------------- /stubs/pydu/request.pyi: -------------------------------------------------------------------------------- 1 | from typing import Union, Optional 2 | 3 | 4 | Headers = Union[dict, list, str] 5 | 6 | 7 | class FileName(object): 8 | def from_url(self, url: str) -> str: None 9 | def from_headers(self, headers: Headers) -> Optional[str]: ... 10 | def from_any(cls, dst: str=None, headers: Headers=None, url: str=None) -> Optional[str]: ... 11 | 12 | def download(url: str, dst: str=None) -> str: ... 13 | def check_connect(ip: str, port: int, retry: int=1, timout: float=0.5) -> Optional[str]: ... 14 | def update_query_params(url: str, params: dict) -> str: ... 15 | def cookies_str_to_dict(cookies: str) -> dict: ... 16 | -------------------------------------------------------------------------------- /stubs/pydu/set.pyi: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Tuple, Any 2 | 3 | 4 | class OrderedSet(object): 5 | def __init__(self, iterable: Iterable[Tuple[Any, Any]]=None) -> None: ... 6 | -------------------------------------------------------------------------------- /stubs/pydu/string.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from pydu.compat import text_type 3 | 4 | 5 | def safeunicode(obj: Any, encoding: str) -> text_type: ... 6 | def safeencode(obj: Any, encoding: str) -> bytes: ... 7 | def _strips(direction: str, text: str, remove: str) -> str: ... 8 | def rstrips(text: str, remove: str) -> str: ... 9 | def lstrips(text: str, remove: str) -> str: ... 10 | def strips(text: str, remove: str) -> str: ... 11 | def common_prefix(l: list) -> str: ... 12 | def common_suffix(l: list) -> str: ... 13 | def sort(s: str, reverse: bool) -> str: ... 14 | -------------------------------------------------------------------------------- /stubs/pydu/system.pyi: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Callable, List 3 | from pydu.platform import WINDOWS 4 | 5 | 6 | def makedirs(path: str, mode: int=0o755, ignore_errors: bool=False, exist_ok: bool=False) -> None: ... 7 | def remove(path: str, ignore_errors: bool=False, onerror: Callable=None) -> None: ... 8 | def removes(paths: List[str], ignore_errors: bool=False, onerror: Callable=None) -> None: ... 9 | def open_file(path: str, mode: str='wb+', buffer_size: int=-1, ignore_errors: bool=False) -> None: ... 10 | def copy(src: str, dst: str, ignore_errors: bool=False, follow_symlinks: bool=True) -> None: ... 11 | def touch(path: str) -> None: ... 12 | def chmod(path: str, mode: int, recursive: bool=False) -> None: ... 13 | def which(cmd: str, mode: int=os.F_OK | os.X_OK, path: str=None) -> None: ... 14 | if WINDOWS: 15 | class chcp(object): 16 | def __init__(self, code: str) -> None: ... 17 | else: 18 | def symlink(src: str, dst: str, overwrite: bool=False, ignore_errors: bool=False) -> None: ... 19 | def link(src: str, dst: str, overwrite: bool=False, ignore_errors: bool=False) -> None: ... 20 | -------------------------------------------------------------------------------- /stubs/pydu/unit.pyi: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | 4 | class Bytes(object): 5 | bytes=... # type: str 6 | def __init__(self, bytes: str) -> None: ... 7 | def convert(self, unit: str=None, multiple: int=1024) -> Tuple[str, str]: ... 8 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/pydu/e6e4055f81dbbece55dccfe29b7fb82b9bf40610/tests/__init__.py -------------------------------------------------------------------------------- /tests/files/bad/absolute.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/pydu/e6e4055f81dbbece55dccfe29b7fb82b9bf40610/tests/files/bad/absolute.tar.gz -------------------------------------------------------------------------------- /tests/files/bad/relative.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/pydu/e6e4055f81dbbece55dccfe29b7fb82b9bf40610/tests/files/bad/relative.tar.gz -------------------------------------------------------------------------------- /tests/files/bad/unrecognized.txt: -------------------------------------------------------------------------------- 1 | File with unrecognized archive extension. 2 | -------------------------------------------------------------------------------- /tests/files/foobar.tar.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/pydu/e6e4055f81dbbece55dccfe29b7fb82b9bf40610/tests/files/foobar.tar.bz2 -------------------------------------------------------------------------------- /tests/files/foobar.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/pydu/e6e4055f81dbbece55dccfe29b7fb82b9bf40610/tests/files/foobar.tar.gz -------------------------------------------------------------------------------- /tests/files/foobar.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/pydu/e6e4055f81dbbece55dccfe29b7fb82b9bf40610/tests/files/foobar.zip -------------------------------------------------------------------------------- /tests/files/foobar_tar_gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/pydu/e6e4055f81dbbece55dccfe29b7fb82b9bf40610/tests/files/foobar_tar_gz -------------------------------------------------------------------------------- /tests/files/压缩.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/pydu/e6e4055f81dbbece55dccfe29b7fb82b9bf40610/tests/files/压缩.tgz -------------------------------------------------------------------------------- /tests/files/压缩.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/pydu/e6e4055f81dbbece55dccfe29b7fb82b9bf40610/tests/files/压缩.zip -------------------------------------------------------------------------------- /tests/test_archive.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import os 3 | import shutil 4 | import tempfile 5 | import unittest 6 | from os.path import isfile, join as pathjoin 7 | 8 | from pydu.archive import extract, UnrecognizedArchiveFormat 9 | 10 | 11 | TEST_DIR = os.path.dirname(os.path.realpath(__file__)) 12 | 13 | 14 | class TempDirMixin(object): 15 | """ 16 | Mixin class for TestCase subclasses to set up and tear down a temporary 17 | directory for unpacking archives during tests. 18 | """ 19 | 20 | def setUp(self): 21 | """ 22 | Create temporary directory for testing extraction. 23 | """ 24 | self.tmpdir = tempfile.mkdtemp() 25 | os.chdir(TEST_DIR) 26 | 27 | def tearDown(self): 28 | """ 29 | Clean up temporary directory. 30 | """ 31 | shutil.rmtree(self.tmpdir) 32 | 33 | def check_files(self, tmpdir): 34 | self.assertTrue(isfile(pathjoin(tmpdir, '1'))) 35 | self.assertTrue(isfile(pathjoin(tmpdir, '2'))) 36 | self.assertTrue(isfile(pathjoin(tmpdir, 'foo', '1'))) 37 | self.assertTrue(isfile(pathjoin(tmpdir, 'foo', '2'))) 38 | self.assertTrue(isfile(pathjoin(tmpdir, 'foo', 'bar', '1'))) 39 | self.assertTrue(isfile(pathjoin(tmpdir, 'foo', 'bar', '2'))) 40 | 41 | 42 | class ArchiveTester(TempDirMixin): 43 | """ 44 | A mixin class to be used for testing many Archive methods for a single 45 | archive file. 46 | """ 47 | 48 | archive = None 49 | ext = '' 50 | 51 | def setUp(self): 52 | super(ArchiveTester, self).setUp() 53 | self.archive_path = pathjoin(TEST_DIR, 'files', self.archive) 54 | 55 | def test_extract(self): 56 | extract(self.archive_path, self.tmpdir, ext=self.ext) 57 | self.check_files(self.tmpdir) 58 | 59 | def test_extract_fileobject(self): 60 | with open(self.archive_path, 'rb') as f: 61 | extract(f, self.tmpdir, ext=self.ext) 62 | self.check_files(self.tmpdir) 63 | 64 | def test_extract_no_to_path(self): 65 | cur_dir = os.getcwd() 66 | os.chdir(self.tmpdir) 67 | extract(self.archive_path, ext=self.ext) 68 | self.check_files(self.tmpdir) 69 | os.chdir(cur_dir) 70 | 71 | def test_extract_bad_fileobject(self): 72 | class File: 73 | pass 74 | f = File() 75 | self.assertRaises(UnrecognizedArchiveFormat, extract, 76 | (f, self.tmpdir), {'ext': self.ext}) 77 | 78 | 79 | class TestZip(ArchiveTester, unittest.TestCase): 80 | archive = 'foobar.zip' 81 | 82 | 83 | class TestTar(ArchiveTester, unittest.TestCase): 84 | archive = 'foobar.tar' 85 | 86 | 87 | class TestGzipTar(ArchiveTester, unittest.TestCase): 88 | archive = 'foobar.tar.gz' 89 | 90 | 91 | class TestBzip2Tar(ArchiveTester, unittest.TestCase): 92 | archive = 'foobar.tar.bz2' 93 | 94 | 95 | class TestNonAsciiNamedTar(ArchiveTester, unittest.TestCase): 96 | archive = u'压缩.tgz' 97 | 98 | 99 | class TestUnicodeNamedZip(ArchiveTester, unittest.TestCase): 100 | archive = u'压缩.zip' 101 | 102 | 103 | class TestExplicitExt(ArchiveTester, unittest.TestCase): 104 | archive = 'foobar_tar_gz' 105 | ext = '.tar.gz' 106 | -------------------------------------------------------------------------------- /tests/test_cmd.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pytest 3 | import time 4 | import subprocess 5 | from pydu.compat import string_types 6 | from pydu.string import safeunicode 7 | from pydu.cmd import TimeoutExpired, run, run_with_en_env, terminate, cmdline_argv 8 | 9 | 10 | def test_run(): 11 | retcode, output = run('echo hello', shell=True) 12 | assert retcode == 0 13 | assert safeunicode(output).rstrip('\r\n') == 'hello' 14 | 15 | with pytest.raises(TimeoutExpired) as e: 16 | cmd = '{} -c "import time; time.sleep(1)"'.format(sys.executable) 17 | timeout = 0.2 18 | run(cmd, shell=True, timeout=timeout, timeinterval=0.05) 19 | assert e.cmd == cmd 20 | assert e.timeout == timeout 21 | assert hasattr(e, 'output') 22 | assert hasattr(e, 'stderr') 23 | 24 | 25 | def test_run_with_en_env(): 26 | _, output = run_with_en_env('nocmd', shell=True) 27 | assert output.decode('ascii') 28 | 29 | _, output = run_with_en_env(['nocmd'], shell=True) 30 | assert output.decode('ascii') 31 | 32 | 33 | def test_terminate(): 34 | p = subprocess.Popen('{} -c "import time; time.sleep(1)"'.format(sys.executable), 35 | shell=True) 36 | terminate(p.pid) 37 | time.sleep(0.1) 38 | assert p.poll() is not None 39 | 40 | 41 | def test_cmdline_argv(): 42 | argv = cmdline_argv() 43 | for s in argv[1:]: 44 | assert isinstance(s, string_types) 45 | -------------------------------------------------------------------------------- /tests/test_compat.py: -------------------------------------------------------------------------------- 1 | from pydu.compat import (PY2, iterkeys, itervalues, iteritems, 2 | text_type, string_types, numeric_types, 3 | is_iterable, has_next_attr, imap, cmp) 4 | 5 | 6 | def test_itersth(): 7 | d = dict(a=1, b=2) 8 | for key in iterkeys(d): 9 | assert key in ('a', 'b') 10 | 11 | for value in itervalues(d): 12 | assert value in (1, 2) 13 | 14 | for items in iteritems(d): 15 | assert items in (('a', 1), ('b', 2)) 16 | 17 | 18 | def test_has_next_attr(): 19 | if PY2: 20 | class NextAttr: 21 | def next(self): 22 | pass 23 | else: 24 | class NextAttr: 25 | def __next__(self): 26 | pass 27 | assert has_next_attr(NextAttr()) 28 | assert not has_next_attr('') 29 | 30 | 31 | def test_is_iterable(): 32 | assert is_iterable(list()) 33 | assert is_iterable(tuple()) 34 | assert is_iterable(dict()) 35 | assert is_iterable(set()) 36 | assert not is_iterable(1) 37 | 38 | 39 | def test_types(): 40 | assert isinstance(u'a', text_type) 41 | 42 | assert isinstance(u'a', string_types) 43 | assert isinstance('a', string_types) 44 | 45 | assert isinstance(1, numeric_types) 46 | assert isinstance(2**50, numeric_types) 47 | 48 | 49 | def test_urlmisc(): 50 | from pydu.compat import urljoin, urlib, urlparse 51 | 52 | 53 | def test_imap(): 54 | assert list(imap(pow, (2, 3, 10), (5, 2, 3))) == [32, 9, 1000] 55 | assert list(imap(max, (1, 4, 7), (2, 3, 8))) == [2, 4, 8] 56 | 57 | 58 | def test_cmp(): 59 | assert cmp(1, 2) < 0 60 | assert cmp(1, 1) == 0 61 | assert cmp(2, 1) > 0 62 | -------------------------------------------------------------------------------- /tests/test_console.py: -------------------------------------------------------------------------------- 1 | from pydu.console import console_size 2 | 3 | 4 | def test_console_size(): 5 | size = console_size() 6 | assert isinstance(size, tuple) 7 | assert len(size) == 2 8 | -------------------------------------------------------------------------------- /tests/test_convert.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pydu.convert import (boolean, 3 | bin2oct, bin2dec, bin2hex, 4 | oct2bin, oct2dec, oct2hex, 5 | dec2bin, dec2oct, dec2hex, 6 | hex2bin, hex2oct, hex2dec) 7 | 8 | 9 | BIG_NUM_STR = '10'*50 10 | BIG_NUM = 10**50 11 | 12 | 13 | class TestBoolean: 14 | def test_accepted_text(self): 15 | for text in ('yes', 'y', 'on', 'true', 't', '1'): 16 | assert boolean(text) 17 | assert boolean(text.upper()) 18 | 19 | for text in ('no', 'n', 'off', 'false', 'f', '0'): 20 | assert not boolean(text) 21 | assert not boolean(text.upper()) 22 | 23 | @pytest.mark.parametrize('text', ('a', 'b')) 24 | def test_unaccepted_text(self, text): 25 | with pytest.raises(ValueError): 26 | boolean(text) 27 | 28 | def test_nonstring(self): 29 | for obj in (10, [1], {1: 1}): 30 | assert boolean(obj) 31 | 32 | for obj in (0, [], {}): 33 | assert not boolean(obj) 34 | 35 | 36 | def test_bin2oct(): 37 | assert bin2oct('1001') == '11' 38 | assert 'L' not in bin2oct(BIG_NUM_STR) 39 | 40 | 41 | def test_bin2dec(): 42 | assert bin2dec('11') == 3 43 | 44 | 45 | def test_bin2hex(): 46 | assert bin2hex('11010') == '1a' 47 | assert 'L' not in bin2hex(BIG_NUM_STR) 48 | 49 | 50 | def test_oct2bin(): 51 | assert oct2bin('11') == '1001' 52 | assert 'L' not in oct2bin(BIG_NUM_STR) 53 | 54 | 55 | def test_oct2dec(): 56 | assert oct2dec('11') == 9 57 | 58 | 59 | def test_oct2hex(): 60 | assert oct2hex('32') == '1a' 61 | assert 'L' not in oct2hex(BIG_NUM_STR) 62 | 63 | 64 | def test_dec2bin(): 65 | assert dec2bin(3) == '11' 66 | assert 'L' not in dec2bin(BIG_NUM) 67 | 68 | 69 | def test_dec2oct(): 70 | assert dec2oct(9) == '11' 71 | assert 'L' not in dec2oct(BIG_NUM) 72 | 73 | 74 | def test_dec2hex(): 75 | assert dec2hex(26) == '1a' 76 | assert 'L' not in dec2hex(BIG_NUM) 77 | 78 | 79 | def test_hex2bin(): 80 | assert hex2bin('1a') == '11010' 81 | assert 'L' not in hex2bin(BIG_NUM_STR) 82 | 83 | 84 | def test_hex2oct(): 85 | assert hex2oct('1a') == '32' 86 | assert 'L' not in hex2oct(BIG_NUM_STR) 87 | 88 | 89 | def test_hex2dec(): 90 | assert hex2dec('1a') == 26 91 | -------------------------------------------------------------------------------- /tests/test_dict.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import unittest 3 | 4 | from pydu.dict import AttrDict, LookupDict, CaseInsensitiveDict, OrderedDefaultDict, attrify 5 | 6 | 7 | class TestAttrDict: 8 | 9 | def test_attr_access_with_init(self): 10 | d = AttrDict(key=1) 11 | assert d['key'] == 1 12 | assert d.key == 1 13 | 14 | def test_attr_access_without_init(self): 15 | d = AttrDict() 16 | d['key'] = 1 17 | assert d['key'] == 1 18 | assert d.key == 1 19 | 20 | d.anotherkey = 1 21 | assert d.anotherkey == 1 22 | assert d['anotherkey'] == 1 23 | 24 | def test_attr_delete(self): 25 | d = AttrDict(key=1) 26 | del d.key 27 | with pytest.raises(AttributeError): 28 | del d.key 29 | 30 | def test_repr(self): 31 | d = AttrDict() 32 | assert repr(d) == '' 33 | 34 | 35 | class TestLooUpDict: 36 | 37 | def test_key_exist(self): 38 | d = LookupDict() 39 | d['key'] = 1 40 | assert d['key'] == 1 41 | 42 | def test_key_not_exist(self): 43 | d = LookupDict() 44 | assert d['key'] is None 45 | 46 | 47 | class TestCaseInsensitiveDict(unittest.TestCase): 48 | def setUp(self): 49 | self.d = CaseInsensitiveDict() 50 | self.d['Accept'] = 1 51 | 52 | def test_ci_dict_set(self): 53 | assert self.d['aCCept'] == 1 54 | assert list(self.d) == ['Accept'] 55 | 56 | def test_ci_dict_del(self): 57 | del self.d['accept'] 58 | assert not self.d 59 | 60 | def test_ci_dict_copy_and_equal(self): 61 | d = self.d.copy() 62 | assert d == self.d 63 | 64 | 65 | class TestOrderedDefaultDict: 66 | def test_default_normal(self): 67 | d = OrderedDefaultDict(int) 68 | assert d[1] == 0 69 | assert d['a'] == 0 70 | d[2] = 2 71 | assert d[2] == 2 72 | assert list(d.keys()) == [1, 'a', 2] 73 | 74 | d = OrderedDefaultDict(int, a=1) 75 | assert d['a'] == 1 76 | 77 | def test_default_factory_not_callable(self): 78 | with pytest.raises(TypeError): 79 | OrderedDefaultDict('notcallable') 80 | 81 | def test_default_factory_none(self): 82 | d = OrderedDefaultDict() 83 | with pytest.raises(KeyError): 84 | d[1] 85 | 86 | def test_copy(self): 87 | d1 = OrderedDefaultDict(int, a=[]) 88 | d2 = d1.copy() 89 | assert d2['a'] == [] 90 | d1['a'].append(1) 91 | assert d2['a'] == [1] 92 | 93 | def test_deepcopy(self): 94 | import copy 95 | d1 = OrderedDefaultDict(int, a=[]) 96 | d2 = copy.deepcopy(d1) 97 | assert d2['a'] == [] 98 | d1['a'].append(1) 99 | assert d2['a'] == [] 100 | 101 | def test_repr(self): 102 | d = OrderedDefaultDict(int, a=1) 103 | assert repr(d).startswith('OrderedDefaultDict') 104 | 105 | def test_attrify(): 106 | attrd = attrify({ 107 | 'a': [1, 2, {'b': 'b'}], 108 | 'c': 'c', 109 | }) 110 | assert attrd.a == [1, 2, {'b': 'b'}] 111 | assert attrd.a[2].b == 'b' 112 | assert attrd.c == 'c' 113 | 114 | attrd = attrify((1, 2)) 115 | assert attrd == (1, 2) 116 | 117 | attrd = attrify({ 118 | 'a': 1, 119 | 'b': (1, 2) 120 | }) 121 | assert attrd.a == 1 122 | assert attrd.b == (1, 2) 123 | -------------------------------------------------------------------------------- /tests/test_dt.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pydu.dt import timer 3 | 4 | 5 | class TestTimer(object): 6 | def test_context_manager(self): 7 | timeit = timer() 8 | 9 | with timeit: 10 | os.getcwd() 11 | 12 | assert timeit.elapsed is not None 13 | 14 | def test_decorator(self): 15 | timeit = timer() 16 | 17 | @timeit 18 | def foo(): 19 | os.getcwd() 20 | 21 | foo() 22 | assert timeit.elapsed is not None 23 | 24 | def test_print_func(self): 25 | import sys 26 | timeit = timer(print_func=sys.stdout.write) 27 | 28 | with timeit: 29 | os.getcwd() 30 | 31 | assert timeit.elapsed is not None 32 | -------------------------------------------------------------------------------- /tests/test_environ.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pydu.environ import environ, path 3 | 4 | 5 | def test_environ(): 6 | os.environ['c'] = 'c' 7 | with environ(a='a', b='', c=None, d=None): 8 | assert os.environ['a'] == 'a' 9 | assert os.environ['b'] == '' 10 | assert 'c' not in os.environ 11 | assert 'd' not in os.environ 12 | assert 'a' not in os.environ 13 | assert 'b' not in os.environ 14 | assert 'c' in os.environ 15 | assert 'd' not in os.environ 16 | 17 | 18 | def test_path(): 19 | with path(append='foo', prepend='boo'): 20 | assert os.environ['PATH'].endswith(os.pathsep + 'foo') 21 | assert os.environ['PATH'].startswith('boo' + os.pathsep) 22 | assert not os.environ['PATH'].endswith(os.pathsep + 'foo') 23 | assert not os.environ['PATH'].startswith('boo' + os.pathsep) 24 | 25 | with path(append='foo', prepend='boo', replace='replace'): 26 | assert os.environ['PATH'] == 'replace' 27 | assert os.environ['PATH'] != 'replace' 28 | -------------------------------------------------------------------------------- /tests/test_exception.py: -------------------------------------------------------------------------------- 1 | from pydu.exception import ignore, default_if_except 2 | 3 | 4 | def test_ignore(): 5 | with ignore(ValueError, AttributeError): 6 | int('abc') 7 | int.no_exists_func() 8 | 9 | 10 | def test_default_if_except(): 11 | @default_if_except(ValueError, default=0) 12 | def foo(value): 13 | return int(value) 14 | 15 | assert foo('abc') == 0 16 | assert foo('1') == 1 17 | -------------------------------------------------------------------------------- /tests/test_functional.py: -------------------------------------------------------------------------------- 1 | from pydu.functional import compose 2 | 3 | 4 | def test_compose(): 5 | def f1(a, b=1): 6 | return a+b 7 | 8 | def f2(a): 9 | return 2*a 10 | 11 | def f3(a, b=3): 12 | return a+b 13 | 14 | assert compose(f1, f2, f3)(1) == 9 15 | assert compose(f1, f2, f3)(1, b=5) == 13 16 | -------------------------------------------------------------------------------- /tests/test_inspect.py: -------------------------------------------------------------------------------- 1 | from pydu.inspect import (get_func_args, get_func_full_args, func_accepts_var_args, 2 | func_accepts_kwargs, func_supports_parameter) 3 | 4 | 5 | class Person: 6 | def no_arguments(self): 7 | return None 8 | 9 | def one_argument(self, something): 10 | return something 11 | 12 | def just_args(self, *args): 13 | return args 14 | 15 | def just_kwargs(self, **kwargs): 16 | return kwargs 17 | 18 | def all_kinds(self, name, address='home', age=25, *args, **kwargs): 19 | return kwargs 20 | 21 | 22 | def func_no_arguments(): 23 | pass 24 | 25 | 26 | def func_one_argument(something): 27 | pass 28 | 29 | 30 | def func_just_args(*args): 31 | pass 32 | 33 | 34 | def func_just_kwargs(**kwargs): 35 | pass 36 | 37 | 38 | def func_all_kinds(name, address='home', age=25, *args, **kwargs): 39 | pass 40 | 41 | 42 | def test_get_func_args(): 43 | arguments = ['name', 'address', 'age'] 44 | assert get_func_args(Person.all_kinds) == arguments 45 | 46 | 47 | def test_get_func_full_args(): 48 | # no arguments 49 | assert get_func_full_args(Person.no_arguments) == [] 50 | assert get_func_full_args(func_no_arguments) == [] 51 | # one argument 52 | assert get_func_full_args(Person.one_argument) == [('something',)] 53 | assert get_func_full_args(func_one_argument) == [('something',)] 54 | # all_arguments 55 | arguments = [('name',), ('address', 'home'), ('age', 25), ('*args',), ('**kwargs',)] 56 | assert get_func_full_args(Person.all_kinds) == arguments 57 | assert get_func_full_args(func_all_kinds) == arguments 58 | 59 | 60 | def test_func_accepts_var_args(): 61 | # has args 62 | assert func_accepts_var_args(Person.just_args) 63 | assert func_accepts_var_args(func_just_args) 64 | # no args 65 | assert not func_accepts_var_args(Person.one_argument) 66 | assert not func_accepts_var_args(func_one_argument) 67 | 68 | 69 | def test_func_accepts_kwargs(): 70 | # has kwargs 71 | assert func_accepts_kwargs(Person.just_kwargs) 72 | assert func_accepts_kwargs(func_just_kwargs) 73 | # no kwargs 74 | assert not func_accepts_kwargs(Person.one_argument) 75 | assert not func_accepts_kwargs(func_one_argument) 76 | 77 | 78 | def test_func_supports_parameter(): 79 | for all_kinds in Person.all_kinds, func_all_kinds: 80 | assert func_supports_parameter(all_kinds, 'name') 81 | assert func_supports_parameter(all_kinds, 'kwargs') 82 | assert not func_supports_parameter(all_kinds, 'self') -------------------------------------------------------------------------------- /tests/test_iter.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pydu.iter import first, last, all, any, join 3 | 4 | 5 | @pytest.mark.parametrize( 6 | 'iterable', ( 7 | [1, 2], 8 | (1, 2), 9 | {1, 2}, 10 | {1: 1, 2: 2}, 11 | iter([1, 2]) 12 | )) 13 | def test_first_last(iterable): 14 | assert first(iterable) == 1 15 | assert last(iterable) == 2 16 | 17 | 18 | def test_all(): 19 | assert all([0, 1, 2], lambda x: x+1) 20 | assert not all([0, 1, 2], lambda x: x) 21 | 22 | 23 | def test_any(): 24 | assert any([-1, -1, 0], lambda x: x+1) 25 | assert not any([-1, -1, -1], lambda x: x + 1) 26 | 27 | 28 | def test_join(): 29 | assert join(iter([1, '2', 3])) == '123' 30 | assert join(iter([1, '2', 3]), separator=',') == '1,2,3' 31 | -------------------------------------------------------------------------------- /tests/test_list.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pydu.list import uniq, tolist, flatten 3 | 4 | 5 | def test_uniq(): 6 | assert uniq([1, 4, 0, 2, 0, 3]) == [1, 4, 0, 2, 3] 7 | 8 | 9 | @pytest.mark.parametrize('obj', ('foo', ['foo'])) 10 | def test_tolist(obj): 11 | assert tolist(obj) == ['foo'] 12 | 13 | 14 | def test_flatten(): 15 | assert list(flatten([1, 2])) == [1, 2] 16 | assert list(flatten([1, [2, 3]])) == [1, 2, 3] 17 | assert list(flatten([1, [2, [3, 4]]])) == [1, 2, 3, 4] 18 | -------------------------------------------------------------------------------- /tests/test_misc.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import pytest 4 | from pydu.misc import (trace, TimeoutError, timeout, 5 | memoize, memoize_when_activated, 6 | super_len) 7 | 8 | try: 9 | from cStringIO import StringIO # py2 10 | except ImportError: 11 | from io import StringIO # py3 12 | 13 | 14 | def test_timeout(): 15 | @timeout(1) 16 | def f1(): 17 | time.sleep(0.01) 18 | return 1 19 | 20 | @timeout(0.01) 21 | def f2(): 22 | time.sleep(1) 23 | return 2 24 | 25 | assert f1() == 1 26 | with pytest.raises(TimeoutError): 27 | f2() 28 | 29 | 30 | def test_memoize(): 31 | @memoize 32 | def foo(*args, **kwargs): 33 | """foo docstring""" 34 | calls.append(None) 35 | return (args, kwargs) 36 | 37 | calls = [] 38 | # no args 39 | for x in range(2): 40 | ret = foo() 41 | expected = ((), {}) 42 | assert ret == expected 43 | assert len(calls) == 1 44 | 45 | # with args 46 | for x in range(2): 47 | ret = foo(1) 48 | expected = ((1,), {}) 49 | assert ret == expected 50 | assert len(calls) == 2 51 | 52 | # with args + kwargs 53 | for x in range(2): 54 | ret = foo(1, bar=2) 55 | expected = ((1,), {'bar': 2}) 56 | assert ret == expected 57 | assert len(calls) == 3 58 | 59 | # clear cache 60 | foo.cache_clear() 61 | ret = foo() 62 | expected = ((), {}) 63 | assert ret == expected 64 | assert len(calls) == 4 65 | 66 | # docstring 67 | assert foo.__doc__ == "foo docstring" 68 | 69 | 70 | def test_memoize_when_activated(): 71 | class Foo: 72 | @memoize_when_activated 73 | def foo(self): 74 | calls.append(None) 75 | 76 | f = Foo() 77 | calls = [] 78 | f.foo() 79 | f.foo() 80 | assert len(calls) == 2 81 | 82 | # activate 83 | calls = [] 84 | f.foo.cache_activate() 85 | f.foo() 86 | f.foo() 87 | assert len(calls) == 1 88 | 89 | # deactivate 90 | calls = [] 91 | f.foo.cache_deactivate() 92 | f.foo() 93 | f.foo() 94 | assert len(calls) == 2 95 | 96 | 97 | class TestSuperLen: 98 | @pytest.mark.parametrize( 99 | 'stream, value', ( 100 | (StringIO, 'Test'), 101 | )) 102 | def test_io_streams(self, stream, value): 103 | """Ensures that we properly deal with different kinds of IO streams.""" 104 | assert super_len(stream()) == 0 105 | assert super_len(stream(value)) == 4 106 | 107 | def test_super_len_correctly_calculates_len_of_partially_read_file(self): 108 | """Ensure that we handle partially consumed file like objects.""" 109 | s = StringIO() 110 | s.write('foobarbogus') 111 | assert super_len(s) == 0 112 | 113 | @pytest.mark.parametrize('error', [IOError, OSError]) 114 | def test_super_len_handles_files_raising_weird_errors_in_tell(self, error): 115 | """If tell() raises errors, assume the cursor is at position zero.""" 116 | 117 | class BoomFile(object): 118 | def __len__(self): 119 | return 5 120 | 121 | def tell(self): 122 | raise error() 123 | 124 | assert super_len(BoomFile()) == 0 125 | 126 | @pytest.mark.parametrize('error', [IOError, OSError]) 127 | def test_super_len_tell_ioerror(self, error): 128 | """Ensure that if tell gives an IOError super_len doesn't fail""" 129 | 130 | class NoLenBoomFile(object): 131 | def tell(self): 132 | raise error() 133 | 134 | def seek(self, offset, whence): 135 | pass 136 | 137 | assert super_len(NoLenBoomFile()) == 0 138 | 139 | def test_string(self): 140 | assert super_len('Test') == 4 141 | 142 | @pytest.mark.parametrize( 143 | 'mode, warnings_num', ( 144 | ('r', 0), 145 | ('rb', 0), 146 | )) 147 | def test_file(self, tmpdir, mode, warnings_num, recwarn): 148 | file_obj = tmpdir.join('test.txt') 149 | file_obj.write('Test') 150 | with file_obj.open(mode) as fd: 151 | assert super_len(fd) == 4 152 | assert len(recwarn) == warnings_num 153 | 154 | def test_super_len_with__len__(self): 155 | foo = [1, 2, 3, 4] 156 | len_foo = super_len(foo) 157 | assert len_foo == 4 158 | 159 | def test_super_len_with_no__len__(self): 160 | class LenFile(object): 161 | def __init__(self): 162 | self.len = 5 163 | 164 | assert super_len(LenFile()) == 5 165 | 166 | def test_super_len_with_tell(self): 167 | foo = StringIO('12345') 168 | assert super_len(foo) == 5 169 | foo.read(2) 170 | assert super_len(foo) == 3 171 | 172 | def test_super_len_with_fileno(self): 173 | with open(__file__, 'rb') as f: 174 | length = super_len(f) 175 | file_data = f.read() 176 | assert length == len(file_data) 177 | 178 | def test_super_len_with_no_matches(self): 179 | """Ensure that objects without any length methods default to 0""" 180 | assert super_len(object()) == 0 181 | -------------------------------------------------------------------------------- /tests/test_network.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pydu.network import (dotted_netmask, is_ipv4, is_ipv6, get_free_port, 3 | ip2int, int2ip) 4 | 5 | 6 | @pytest.mark.parametrize( 7 | 'mask, expected', ( 8 | (8, '255.0.0.0'), 9 | (24, '255.255.255.0'), 10 | (25, '255.255.255.128'), 11 | )) 12 | def test_dotted_netmask(mask, expected): 13 | assert dotted_netmask(mask) == expected 14 | 15 | 16 | class TestIsIPv4Address: 17 | 18 | def test_valid(self): 19 | assert is_ipv4('8.8.8.8') 20 | 21 | @pytest.mark.parametrize('value', ('8.8.8.8.8', 'localhost.localdomain')) 22 | def test_invalid(self, value): 23 | assert not is_ipv4(value) 24 | 25 | 26 | class TestIsIPv6Address: 27 | 28 | def test_valid(self): 29 | assert is_ipv6('fe80::9e5b:b149:e187:1a18') 30 | 31 | @pytest.mark.parametrize('value', ('fe80::9e5b:b149:e187::', 'localhost.localdomain')) 32 | def test_invalid(self, value): 33 | assert not is_ipv6(value) 34 | 35 | 36 | def test_get_free_port(): 37 | port = get_free_port() 38 | assert isinstance(port, int) 39 | assert 65536 > port > 0 40 | 41 | 42 | def test_ip2int(): 43 | assert ip2int('10.1.1.1') == 167837953 44 | with pytest.raises(ValueError): 45 | ip2int('255.255.255.256') 46 | 47 | assert ip2int('fe80::9e5b:b149:e187:1a18') == 338288524927261089665429805853095434776 48 | with pytest.raises(ValueError): 49 | ip2int('fe80::9e5b:b149:e187::') 50 | 51 | 52 | def test_int2ip(): 53 | assert int2ip(167837953) == '10.1.1.1' 54 | assert int2ip(338288524927261089665429805853095434776) == 'fe80::9e5b:b149:e187:1a18' 55 | with pytest.raises(ValueError): 56 | int2ip(10**50) 57 | -------------------------------------------------------------------------------- /tests/test_path.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from pydu.platform import WINDOWS 4 | from pydu.path import cd, is_super_path, normjoin, filename, fileext 5 | 6 | 7 | def test_cd(tmpdir): 8 | path = str(tmpdir) 9 | cwd = os.getcwd() 10 | with cd(path): 11 | assert os.getcwd() == path 12 | assert os.getcwd() == cwd 13 | 14 | 15 | class TestIsSupoerPath: 16 | def test_is_super_path_general(self): 17 | assert is_super_path('/aa/bb/cc', '/aa/bb/cc') 18 | assert is_super_path('/aa/bb', '/aa/bb/cc') 19 | assert is_super_path('/aa', '/aa/bb/cc') 20 | assert is_super_path('/', '/aa/bb/cc') 21 | assert is_super_path('/', '/') 22 | assert not is_super_path('/a', '/aa/bb/cc') 23 | 24 | @pytest.mark.skipif(not WINDOWS, reason='Not support on none-windows') 25 | def test_is_super_path_win(self): 26 | assert is_super_path('c:/aa/bb', 'c:/aa/bb\\cc') 27 | assert is_super_path('c:/aa/bb', 'c:/aa\\bb/cc') 28 | assert is_super_path('c:/aa\\bb', 'c:\\aa/bb/cc') 29 | assert is_super_path('c:/', 'c:\\') 30 | 31 | 32 | def test_normjoin(): 33 | if WINDOWS: 34 | assert normjoin('C:\\', 'b') == 'C:\\b' 35 | assert normjoin('C:\\', '\\b') == 'C:\\b' 36 | assert normjoin('C:\\a', '\\b') == 'C:\\b' 37 | assert normjoin('C:\\a', '..\\b') == 'C:\\b' 38 | else: 39 | assert normjoin('/a', 'b') == '/a/b' 40 | assert normjoin('/a', '/b') == '/b' 41 | assert normjoin('/a', '../b') == '/b' 42 | 43 | 44 | def test_filename(): 45 | assert filename('/foo/bar') == 'bar' 46 | assert filename('/foo/bar.ext') == 'bar' 47 | assert filename('/foo/bar.more.ext') == 'bar.more' 48 | 49 | 50 | def test_fileext(): 51 | assert fileext('/foo/bar') == '' 52 | assert fileext('/foo/bar.ext') == '.ext' 53 | assert fileext('/foo/bar.more.ext') == '.ext' 54 | -------------------------------------------------------------------------------- /tests/test_platform.py: -------------------------------------------------------------------------------- 1 | from pydu.platform import (WINDOWS, LINUX, POSIX, DARWIN, SUNOS, SMARTOS, 2 | FREEBSD, NETBSD, OPENBSD, AIX) 3 | 4 | 5 | def test_platform_constants(): 6 | assert any([WINDOWS, LINUX, POSIX, DARWIN, SUNOS, SMARTOS, 7 | FREEBSD, NETBSD, OPENBSD, AIX]) 8 | -------------------------------------------------------------------------------- /tests/test_request.py: -------------------------------------------------------------------------------- 1 | import socket 2 | from .testing import mockserver 3 | import pydu.request 4 | from pydu.network import get_free_port 5 | from pydu.request import FileName, check_connect, update_query_params, cookies_str_to_dict 6 | 7 | 8 | def test_filename_from_url(): 9 | url = 'http://www.example.com/test.txt' 10 | assert FileName.from_url(url) == 'test.txt' 11 | 12 | url = 'http://www.example.com/' 13 | assert FileName.from_url(url) is None 14 | 15 | 16 | def test_filename_from_headers(): 17 | headers = {'Content-Disposition': 'attachment; filename=test.txt'} 18 | assert FileName.from_headers(headers) == 'test.txt' 19 | 20 | headers = [('Content-Disposition', 'attachment; filename=test.txt')] 21 | assert FileName.from_headers(headers) == 'test.txt' 22 | 23 | headers = 'Content-Disposition: attachment; filename=test.txt' 24 | assert FileName.from_headers(headers) == 'test.txt' 25 | 26 | headers = 'Content-Disposition: attachment; filename=abc/test.txt' 27 | assert FileName.from_headers(headers) == 'test.txt' 28 | 29 | headers = '' 30 | assert FileName.from_headers(headers) is None 31 | 32 | headers = 'Content-Disposition: abc' 33 | assert FileName.from_headers(headers) is None 34 | 35 | headers = 'Content-Disposition: abc;' 36 | assert FileName.from_headers(headers) is None 37 | 38 | headers = 'Content-Disposition: attachment; filename=test.txt; filename=test2.txt' 39 | assert FileName.from_headers(headers) is None 40 | 41 | headers = 'Content-Disposition: attachment; filename=' 42 | assert FileName.from_headers(headers) is None 43 | 44 | 45 | @mockserver 46 | def test_check_connect(port=None): 47 | assert check_connect('127.0.0.1', port=port, timeout=0.01) 48 | assert not check_connect('127.0.0.1', port=get_free_port(), timeout=0.01) 49 | 50 | def mock_socket(*args): 51 | raise socket.error 52 | 53 | old_socket = pydu.request.socket.socket 54 | pydu.request.socket.socket = mock_socket 55 | assert not check_connect('127.0.0.1', port=port, timeout=0.01) 56 | pydu.request.socket.socket = old_socket 57 | 58 | 59 | def test_update_query_params(): 60 | base = 'http://example.com/' 61 | assert update_query_params(base, {'foo': 1}) == base + '?foo=1' 62 | assert update_query_params(base + '?foo=1', {'foo': 2}) == base + '?foo=2' 63 | assert update_query_params(base + '?foo=1', {'foo': 2, 'bar': 3}) in \ 64 | (base + '?foo=2&bar=3', base + '?bar=3&foo=2') 65 | 66 | 67 | def test_cookies_str_to_dict(): 68 | cookies = cookies_str_to_dict('a=a; \tb=b;\nc=c;d;e=') 69 | assert cookies['a'] == 'a' 70 | assert cookies['b'] == 'b' 71 | assert cookies['c'] == 'c' 72 | assert cookies['e'] == '' 73 | -------------------------------------------------------------------------------- /tests/test_set.py: -------------------------------------------------------------------------------- 1 | from pydu.set import OrderedSet 2 | 3 | 4 | def test_ordered_set(): 5 | ordered_set = OrderedSet([1, 3, 1, 2]) 6 | assert list(ordered_set) == [1, 3, 2] 7 | assert 1 in ordered_set 8 | assert bool(ordered_set) 9 | 10 | ordered_set.add(1) 11 | assert 1 in ordered_set 12 | 13 | ordered_set.remove(1) 14 | assert 1 not in ordered_set 15 | 16 | for i in range(4): 17 | ordered_set.discard(i) 18 | assert not bool(ordered_set) 19 | -------------------------------------------------------------------------------- /tests/test_slot.py: -------------------------------------------------------------------------------- 1 | from pydu.slot import SlotBase 2 | 3 | 4 | class Foo(SlotBase): 5 | __slots__ = ('a', 'b', 'c') 6 | 7 | 8 | class TestSlotBase(object): 9 | def test_args(self): 10 | foo = Foo(1) 11 | assert foo.a == 1 12 | assert foo.b is None 13 | assert foo.c is None 14 | 15 | def test_kwargs(self): 16 | foo = Foo(b=2) 17 | assert foo.a is None 18 | assert foo.b == 2 19 | assert foo.c is None 20 | 21 | def test_args_kwargs(self): 22 | foo = Foo(1, b=2) 23 | assert foo.a == 1 24 | assert foo.b == 2 25 | assert foo.c is None 26 | -------------------------------------------------------------------------------- /tests/test_string.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from pydu.string import (safeencode, safeunicode, strips, lstrips, rstrips, 3 | common_prefix, common_suffix, sort) 4 | 5 | 6 | def test_safeencode(): 7 | assert safeencode('hello') == b'hello' 8 | assert safeencode(1) == b'1' 9 | assert safeencode(u'中文') == b'\xe4\xb8\xad\xe6\x96\x87' 10 | 11 | 12 | def test_safeunicode(): 13 | assert safeunicode('hello') == u'hello' 14 | assert safeunicode(1) == u'1' 15 | assert safeunicode('中文') == u'中文' 16 | 17 | assert safeunicode(u'hello') == u'hello' 18 | assert safeunicode(u'中文') == u'中文' 19 | 20 | 21 | def test_lstrips(): 22 | assert lstrips('foobbar', '') == 'foobbar' 23 | assert lstrips('foobar', 'fo') == 'obar' 24 | assert lstrips('foofoobar', 'foo') == 'foobar' 25 | assert lstrips('foobarbaz', ('foo', 'bar')) == 'baz' 26 | assert lstrips('foobarbaz', ('bar', 'foo')) == 'barbaz' 27 | 28 | 29 | def test_rstrips(): 30 | assert rstrips('foobbar', '') == 'foobbar' 31 | assert rstrips('foobbar', 'bar') == 'foob' 32 | assert rstrips('foobarbar', 'bar') == 'foobar' 33 | assert rstrips('fozfoobar', ('bar', 'foo')) == 'foz' 34 | assert rstrips('fozfoobar', ('foo', 'bar')) == 'fozfoo' 35 | 36 | 37 | def test_strips(): 38 | assert strips('foobarfoo', '') == 'foobarfoo' 39 | assert strips('foobarfoo', 'foo') == 'bar' 40 | assert strips('foobarfoo', ('foo', 'bar')) == '' 41 | 42 | 43 | def test_common_prefix(): 44 | l = ['abcd', 'abc1'] 45 | assert common_prefix(l) == 'abc' 46 | 47 | 48 | def test_common_suffix(): 49 | l = ['dabc', '1abc'] 50 | assert common_suffix(l) == 'abc' 51 | 52 | 53 | def test_sort(): 54 | assert sort('acb21') == '12abc' 55 | assert sort('abc21', reverse=True) == 'cba21' 56 | -------------------------------------------------------------------------------- /tests/test_unit.py: -------------------------------------------------------------------------------- 1 | from pydu.unit import Bytes 2 | 3 | 4 | class TestBytes: 5 | def test_convert(self): 6 | assert Bytes(1024*1024).convert() == (1, 'MB') 7 | assert Bytes(1024*1024).convert(unit='KB') == (1024, 'KB') 8 | assert Bytes(1000).convert(multiple=1000) == (1, 'KB') 9 | -------------------------------------------------------------------------------- /tests/testing.py: -------------------------------------------------------------------------------- 1 | import functools 2 | from threading import Thread 3 | from pydu.network import get_free_port 4 | from pydu.inspect import func_supports_parameter 5 | from pydu.compat import PY2, ClassTypes 6 | 7 | if PY2: 8 | from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler 9 | else: 10 | from http.server import HTTPServer as HTTPServer 11 | from http.server import BaseHTTPRequestHandler 12 | 13 | 14 | class mockserverfy(object): 15 | 16 | def __init__(self, RequestHandler=BaseHTTPRequestHandler): 17 | self.RequestHandler = RequestHandler 18 | self.server = None 19 | self.port = None 20 | 21 | def __enter__(self): 22 | self.port = get_free_port() 23 | self.server = HTTPServer(('127.0.0.1', self.port), self.RequestHandler) 24 | 25 | t = Thread(target=self.server.serve_forever) 26 | t.setDaemon(True) 27 | t.start() 28 | return self 29 | 30 | def __exit__(self, exc_type, exc_value, traceback): 31 | self.server.shutdown() 32 | 33 | 34 | def mockserver(test): 35 | """A decorator tests that use mock server""" 36 | def decorate_class(klass): 37 | for attr in dir(klass): 38 | if not attr.startswith('test_'): 39 | continue 40 | 41 | attr_value = getattr(klass, attr) 42 | if not hasattr(attr_value, "__call__"): 43 | continue 44 | 45 | setattr(klass, attr, decorate_callable(attr_value)) 46 | return klass 47 | 48 | def decorate_callable(test): 49 | @functools.wraps(test) 50 | def wrapper(*args, **kwargs): 51 | with mockserverfy() as server: 52 | if func_supports_parameter(test, 'port'): 53 | return test(*args, port=server.port, **kwargs) 54 | else: 55 | return test(*args, **kwargs) 56 | return wrapper 57 | 58 | if isinstance(test, ClassTypes): 59 | return decorate_class(test) 60 | return decorate_callable(test) 61 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py35,py36,py37,py38 3 | 4 | [testenv] 5 | deps=-rrequirements-dev.txt 6 | commands= 7 | coverage run -m pytest 8 | --------------------------------------------------------------------------------