Skip to content

Commit ca5b435

Browse files
committed
Merge branch 'release/3.0.0'
2 parents 4197f88 + f48ab66 commit ca5b435

30 files changed

+719
-1406
lines changed

.github/workflows/main.yml

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: tox
1+
name: pytest
22

33
on:
44
push:
@@ -11,17 +11,40 @@ jobs:
1111
timeout-minutes: 2
1212
strategy:
1313
matrix:
14-
python-version: [2.7, 3.6, 3.7, 3.8, 3.9, '3.10']
14+
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
1515

16+
steps:
17+
- uses: actions/checkout@v2
18+
- name: Set up Python ${{ matrix.python-version }}
19+
uses: actions/setup-python@v2
20+
with:
21+
python-version: ${{ matrix.python-version }}
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install --upgrade pip setuptools flake8
25+
pip install -e '.[tests]'
26+
- name: Get versions
27+
run: |
28+
python -V
29+
pip freeze
30+
- name: flake8
31+
run: flake8 -v python_utils setup.py
32+
- name: pytest
33+
run: py.test
34+
35+
docs:
36+
runs-on: ubuntu-latest
37+
timeout-minutes: 2
1638
steps:
1739
- uses: actions/checkout@v2
18-
- name: Set up Python ${{ matrix.python-version }}
40+
- name: Set up Python
1941
uses: actions/setup-python@v2
2042
with:
21-
python-version: ${{ matrix.python-version }}
43+
python-version: '3.10'
2244
- name: Install dependencies
2345
run: |
24-
python -m pip install --upgrade pip
25-
pip install tox tox-gh-actions
26-
- name: Test with tox
27-
run: tox
46+
python -m pip install --upgrade pip setuptools
47+
pip install -e '.[docs]'
48+
- name: build docs
49+
run: make html
50+
working-directory: docs/

README.rst

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,31 @@ format.
5858
Examples
5959
------------------------------------------------------------------------------
6060

61+
Automatically converting a generator to a list, dict or other collections
62+
using a decorator:
63+
64+
.. code-block:: pycon
65+
66+
>>> @decorators.listify()
67+
... def generate_list():
68+
... yield 1
69+
... yield 2
70+
... yield 3
71+
...
72+
>>> generate_list()
73+
[1, 2, 3]
74+
75+
>>> @listify(collection=dict)
76+
... def dict_generator():
77+
... yield 'a', 1
78+
... yield 'b', 2
79+
80+
>>> dict_generator()
81+
{'a': 1, 'b': 2}
82+
83+
Retrying until timeout
84+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
85+
6186
To easily retry a block of code with a configurable timeout, you can use the
6287
`time.timeout_generator`:
6388

@@ -69,6 +94,9 @@ To easily retry a block of code with a configurable timeout, you can use the
6994
... except Exception as e:
7095
... # Handle the exception
7196
97+
Formatting of timestamps, dates and times
98+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
99+
72100
Easy formatting of timestamps and calculating the time since:
73101

74102
.. code-block:: pycon
@@ -98,12 +126,15 @@ Easy formatting of timestamps and calculating the time since:
98126
'1 minute ago'
99127
100128
Converting your test from camel-case to underscores:
129+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
101130

102131
.. code-block:: pycon
103132
104133
>>> camel_to_underscore('SpamEggsAndBacon')
105134
'spam_eggs_and_bacon'
106135
136+
Attribute setting decorator. Very useful for the Django admin
137+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
107138
A convenient decorator to set function attributes using a decorator:
108139

109140
.. code-block:: pycon
@@ -119,7 +150,11 @@ A convenient decorator to set function attributes using a decorator:
119150
120151
>>> upper_case_name.short_description = 'Name'
121152
122-
Or to scale numbers:
153+
This can be very useful for the Django admin as it allows you to have all
154+
metadata in one place.
155+
156+
Scaling numbers between ranges
157+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
123158

124159
.. code-block:: pycon
125160
@@ -130,7 +165,8 @@ Or to scale numbers:
130165
>>> remap(decimal.Decimal('250.0'), 0.0, 1000.0, 0.0, 100.0)
131166
Decimal('25.0')
132167
133-
To get the screen/window/terminal size in characters:
168+
Get the screen/window/terminal size in characters:
169+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
134170

135171
.. code-block:: pycon
136172
@@ -140,7 +176,8 @@ To get the screen/window/terminal size in characters:
140176
That method supports IPython and Jupyter as well as regular shells, using
141177
`blessings` and other modules depending on what is available.
142178

143-
To extract a number from nearly every string:
179+
Extracting numbers from nearly every string:
180+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
144181

145182
.. code-block:: pycon
146183
@@ -151,6 +188,9 @@ To extract a number from nearly every string:
151188
>>> number = converters.to_int('spam', default=1)
152189
1
153190
191+
Doing a global import of all the modules in a package programmatically:
192+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
193+
154194
To do a global import programmatically you can use the `import_global`
155195
function. This effectively emulates a `from ... import *`
156196

@@ -161,6 +201,9 @@ function. This effectively emulates a `from ... import *`
161201
# The following is the equivalent of `from some_module import *`
162202
import_global('some_module')
163203
204+
Automatically named logger for classes:
205+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
206+
164207
Or add a correclty named logger to your classes which can be easily accessed:
165208

166209
.. code-block:: python
@@ -183,3 +226,23 @@ Or add a correclty named logger to your classes which can be easily accessed:
183226
import logging
184227
my_class.log(logging.ERROR, 'log')
185228
229+
230+
Convenient type aliases and some commonly used types:
231+
232+
.. code-block:: python
233+
234+
# For type hinting scopes such as locals/globals/vars
235+
Scope = Dict[str, Any]
236+
OptionalScope = O[Scope]
237+
238+
# Note that Number is only useful for extra clarity since float
239+
# will work for both int and float in practice.
240+
Number = U[int, float]
241+
DecimalNumber = U[Number, decimal.Decimal]
242+
243+
# To accept an exception or list of exceptions
244+
ExceptionType = Type[Exception]
245+
ExceptionsType = U[Tuple[ExceptionType, ...], ExceptionType]
246+
247+
# Matching string/bytes types:
248+
StringTypes = U[str, bytes]

_python_utils_tests/test_import.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ def relative_import(level):
1010
locals_ = {}
1111
globals_ = {'__name__': 'python_utils.import_'}
1212
import_.import_global('.formatters', locals_=locals_, globals_=globals_)
13-
import pprint
14-
pprint.pprint(globals_)
1513
assert 'camel_to_underscore' in globals_
1614

1715

_python_utils_tests/test_time.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import itertools
2+
from datetime import timedelta
3+
4+
import pytest
5+
6+
from python_utils import aio
7+
from python_utils import time
8+
9+
10+
@pytest.mark.parametrize(
11+
'timeout,interval,interval_multiplier,maximum_interval,iterable,result', [
12+
(0.1, 0.06, 0.5, 0.1, aio.acount, 2),
13+
(0.2, 0.06, 0.5, 0.1, aio.acount(), 4),
14+
(0.3, 0.06, 1.0, None, aio.acount, 5),
15+
(timedelta(seconds=0.1), timedelta(seconds=0.06),
16+
2.0, timedelta(seconds=0.1), aio.acount, 2),
17+
])
18+
@pytest.mark.asyncio
19+
async def test_aio_timeout_generator(timeout, interval, interval_multiplier,
20+
maximum_interval, iterable, result):
21+
i = None
22+
async for i in time.aio_timeout_generator(
23+
timeout, interval, iterable,
24+
maximum_interval=maximum_interval):
25+
pass
26+
27+
assert i == result
28+
29+
30+
@pytest.mark.parametrize(
31+
'timeout,interval,interval_multiplier,maximum_interval,iterable,result', [
32+
(0.01, 0.006, 0.5, 0.01, 'abc', 'c'),
33+
(0.01, 0.006, 0.5, 0.01, itertools.count, 2),
34+
(0.01, 0.006, 0.5, 0.01, itertools.count(), 2),
35+
(0.01, 0.006, 1.0, None, 'abc', 'c'),
36+
(timedelta(seconds=0.01),
37+
timedelta(seconds=0.006),
38+
2.0, timedelta(seconds=0.01),
39+
itertools.count, 2),
40+
])
41+
def test_timeout_generator(timeout, interval, interval_multiplier,
42+
maximum_interval, iterable, result):
43+
i = None
44+
for i in time.timeout_generator(
45+
timeout=timeout,
46+
interval=interval,
47+
interval_multiplier=interval_multiplier,
48+
iterable=iterable,
49+
maximum_interval=maximum_interval,
50+
):
51+
pass
52+
53+
assert i == result

0 commit comments

Comments
 (0)