├── .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 | [](https://pypi.python.org/pypi/pydu)
4 | [](https://pypi.python.org/pypi/pydu)
5 | [](https://pypi.python.org/pypi/pydu)
6 | [](https://travis-ci.org/flaggo/pydu)
7 | [](https://ci.appveyor.com/project/flaggo/pydu)
8 | [](https://codecov.io/github/flaggo/pydu)
9 | [](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 |
--------------------------------------------------------------------------------