11Useful 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
2828Requirements 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
3334Installation:
3435------------------------------------------------------------------------------
3536
3637The 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
4651Note 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.
5764Examples
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
77200To do a global import programmatically you can use the `import_global `
78201function. 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+
87213Or 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