Skip to content

Commit 7eebf47

Browse files
committed
Merge branch 'develop' into unit_conversion
# Conflicts: # docs/conf.py # python_utils/time.py # setup.py
2 parents 8ce1f5d + dd47eff commit 7eebf47

39 files changed

Lines changed: 1583 additions & 1515 deletions

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ exclude_lines =
2121
raise NotImplementedError
2222
if 0:
2323
if __name__ == .__main__.:
24+
if typing.TYPE_CHECKING:

.github/workflows/main.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: pytest
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
timeout-minutes: 2
12+
strategy:
13+
matrix:
14+
python-version: ['3.7', '3.8', '3.9', '3.10']
15+
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
38+
steps:
39+
- uses: actions/checkout@v2
40+
- name: Set up Python
41+
uses: actions/setup-python@v2
42+
with:
43+
python-version: '3.10'
44+
- name: Install dependencies
45+
run: |
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/

.travis.yml

Lines changed: 0 additions & 79 deletions
This file was deleted.

README.rst

Lines changed: 175 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
Useful Python Utils
22
==============================================================================
33

4-
.. image:: https://travis-ci.org/WoLpH/python-utils.svg?branch=master
5-
:target: https://travis-ci.org/WoLpH/python-utils
4+
.. image:: https://github.com/WoLpH/python-utils/actions/workflows/main.yml/badge.svg?branch=master
5+
:target: https://github.com/WoLpH/python-utils/actions/workflows/main.yml
66

77
.. image:: https://coveralls.io/repos/WoLpH/python-utils/badge.svg?branch=master
88
:target: https://coveralls.io/r/WoLpH/python-utils?branch=master
@@ -28,23 +28,30 @@ Links
2828
Requirements for installing:
2929
------------------------------------------------------------------------------
3030

31-
- `six` any recent version
31+
For the Python 3+ release (i.e. v3.0.0 or higher) there are no requirements.
32+
For the Python 2 compatible version (v2.x.x) the `six` package is needed.
3233

3334
Installation:
3435
------------------------------------------------------------------------------
3536

3637
The package can be installed through `pip` (this is the recommended method):
3738

39+
.. code-block:: bash
40+
3841
pip install python-utils
3942
4043
Or if `pip` is not available, `easy_install` should work as well:
4144

45+
.. code-block:: bash
46+
4247
easy_install python-utils
4348
4449
Or download the latest release from Pypi (https://pypi.python.org/pypi/python-utils) or Github.
4550

4651
Note that the releases on Pypi are signed with my GPG key (https://pgp.mit.edu/pks/lookup?op=vindex&search=0xE81444E9CE1F695D) and can be checked using GPG:
4752

53+
.. code-block:: bash
54+
4855
gpg --verify python-utils-<version>.tar.gz.asc python-utils-<version>.tar.gz
4956
5057
Quickstart
@@ -57,22 +64,138 @@ format.
5764
Examples
5865
------------------------------------------------------------------------------
5966

60-
To extract a number from nearly every string:
67+
Automatically converting a generator to a list, dict or other collections
68+
using a decorator:
6169

62-
.. code-block:: python
70+
.. code-block:: pycon
71+
72+
>>> @decorators.listify()
73+
... def generate_list():
74+
... yield 1
75+
... yield 2
76+
... yield 3
77+
...
78+
>>> generate_list()
79+
[1, 2, 3]
80+
81+
>>> @listify(collection=dict)
82+
... def dict_generator():
83+
... yield 'a', 1
84+
... yield 'b', 2
85+
86+
>>> dict_generator()
87+
{'a': 1, 'b': 2}
88+
89+
Retrying until timeout
90+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
91+
92+
To easily retry a block of code with a configurable timeout, you can use the
93+
`time.timeout_generator`:
94+
95+
.. code-block:: pycon
96+
97+
>>> for i in time.timeout_generator(10):
98+
... try:
99+
... # Run your code here
100+
... except Exception as e:
101+
... # Handle the exception
102+
103+
Formatting of timestamps, dates and times
104+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
105+
106+
Easy formatting of timestamps and calculating the time since:
107+
108+
.. code-block:: pycon
109+
110+
>>> time.format_time('1')
111+
'0:00:01'
112+
>>> time.format_time(1.234)
113+
'0:00:01'
114+
>>> time.format_time(1)
115+
'0:00:01'
116+
>>> time.format_time(datetime.datetime(2000, 1, 2, 3, 4, 5, 6))
117+
'2000-01-02 03:04:05'
118+
>>> time.format_time(datetime.date(2000, 1, 2))
119+
'2000-01-02'
120+
>>> time.format_time(datetime.timedelta(seconds=3661))
121+
'1:01:01'
122+
>>> time.format_time(None)
123+
'--:--:--'
124+
125+
>>> formatters.timesince(now)
126+
'just now'
127+
>>> formatters.timesince(now - datetime.timedelta(seconds=1))
128+
'1 second ago'
129+
>>> formatters.timesince(now - datetime.timedelta(seconds=2))
130+
'2 seconds ago'
131+
>>> formatters.timesince(now - datetime.timedelta(seconds=60))
132+
'1 minute ago'
133+
134+
Converting your test from camel-case to underscores:
135+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
136+
137+
.. code-block:: pycon
138+
139+
>>> camel_to_underscore('SpamEggsAndBacon')
140+
'spam_eggs_and_bacon'
141+
142+
Attribute setting decorator. Very useful for the Django admin
143+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
144+
A convenient decorator to set function attributes using a decorator:
145+
146+
.. code-block:: pycon
147+
148+
You can use:
149+
>>> @decorators.set_attributes(short_description='Name')
150+
... def upper_case_name(self, obj):
151+
... return ("%s %s" % (obj.first_name, obj.last_name)).upper()
152+
153+
Instead of:
154+
>>> def upper_case_name(obj):
155+
... return ("%s %s" % (obj.first_name, obj.last_name)).upper()
156+
157+
>>> upper_case_name.short_description = 'Name'
63158
64-
from python_utils import converters
159+
This can be very useful for the Django admin as it allows you to have all
160+
metadata in one place.
65161

66-
number = converters.to_int('spam15eggs')
67-
assert number == 15
162+
Scaling numbers between ranges
163+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68164

69-
number = converters.to_int('spam')
70-
assert number == 0
165+
.. code-block:: pycon
71166
72-
number = converters.to_int('spam', default=1)
73-
assert number == 1
167+
>>> converters.remap(500, old_min=0, old_max=1000, new_min=0, new_max=100)
168+
50
74169
75-
number = converters.to_float('spam1.234')
170+
# Or with decimals:
171+
>>> remap(decimal.Decimal('250.0'), 0.0, 1000.0, 0.0, 100.0)
172+
Decimal('25.0')
173+
174+
Get the screen/window/terminal size in characters:
175+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
176+
177+
.. code-block:: pycon
178+
179+
>>> terminal.get_terminal_size()
180+
(80, 24)
181+
182+
That method supports IPython and Jupyter as well as regular shells, using
183+
`blessings` and other modules depending on what is available.
184+
185+
Extracting numbers from nearly every string:
186+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
187+
188+
.. code-block:: pycon
189+
190+
>>> converters.to_int('spam15eggs')
191+
15
192+
>>> converters.to_int('spam')
193+
0
194+
>>> number = converters.to_int('spam', default=1)
195+
1
196+
197+
Doing a global import of all the modules in a package programmatically:
198+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
76199

77200
To do a global import programmatically you can use the `import_global`
78201
function. This effectively emulates a `from ... import *`
@@ -84,6 +207,9 @@ function. This effectively emulates a `from ... import *`
84207
# The following is the equivalent of `from some_module import *`
85208
import_global('some_module')
86209
210+
Automatically named logger for classes:
211+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
212+
87213
Or add a correclty named logger to your classes which can be easily accessed:
88214

89215
.. code-block:: python
@@ -106,3 +232,39 @@ Or add a correclty named logger to your classes which can be easily accessed:
106232
import logging
107233
my_class.log(logging.ERROR, 'log')
108234
235+
Alternatively loguru is also supported. It is largely a drop-in replacement for the logging module which is a bit more convenient to configure:
236+
237+
First install the extra loguru package:
238+
239+
.. code-block:: bash
240+
241+
pip install 'python-utils[loguru]'
242+
243+
.. code-block:: python
244+
245+
class MyClass(Logurud):
246+
...
247+
248+
Now you can use the `Logurud` class to make functions such as `self.info()`
249+
available. The benefit of this approach is that you can add extra context or
250+
options to you specific loguru instance (i.e. `self.logger`):
251+
252+
Convenient type aliases and some commonly used types:
253+
254+
.. code-block:: python
255+
256+
# For type hinting scopes such as locals/globals/vars
257+
Scope = Dict[str, Any]
258+
OptionalScope = O[Scope]
259+
260+
# Note that Number is only useful for extra clarity since float
261+
# will work for both int and float in practice.
262+
Number = U[int, float]
263+
DecimalNumber = U[Number, decimal.Decimal]
264+
265+
# To accept an exception or list of exceptions
266+
ExceptionType = Type[Exception]
267+
ExceptionsType = U[Tuple[ExceptionType, ...], ExceptionType]
268+
269+
# Matching string/bytes types:
270+
StringTypes = U[str, bytes]

0 commit comments

Comments
 (0)