easydev 0.13.3__tar.gz → 0.15.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- easydev-0.15.0/PKG-INFO +222 -0
- easydev-0.15.0/README.rst +184 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/appdirs.py +0 -3
- {easydev-0.13.3 → easydev-0.15.0}/easydev/browser.py +6 -10
- {easydev-0.13.3 → easydev-0.15.0}/easydev/chunks.py +0 -6
- {easydev-0.13.3 → easydev-0.15.0}/easydev/codecs.py +1 -1
- {easydev-0.13.3 → easydev-0.15.0}/easydev/config_tools.py +6 -6
- {easydev-0.13.3 → easydev-0.15.0}/easydev/console.py +1 -1
- {easydev-0.13.3 → easydev-0.15.0}/easydev/copybutton.py +1 -1
- {easydev-0.13.3 → easydev-0.15.0}/easydev/decorators.py +5 -5
- {easydev-0.13.3 → easydev-0.15.0}/easydev/easytest.py +18 -3
- {easydev-0.13.3 → easydev-0.15.0}/easydev/logging_tools.py +0 -1
- {easydev-0.13.3 → easydev-0.15.0}/easydev/misc.py +3 -9
- {easydev-0.13.3 → easydev-0.15.0}/easydev/multicore.py +8 -7
- {easydev-0.13.3 → easydev-0.15.0}/easydev/paths.py +6 -7
- {easydev-0.13.3 → easydev-0.15.0}/easydev/platform.py +12 -20
- {easydev-0.13.3 → easydev-0.15.0}/easydev/sphinx_themes.py +1 -1
- {easydev-0.13.3 → easydev-0.15.0}/easydev/tools.py +13 -14
- {easydev-0.13.3 → easydev-0.15.0}/easydev/url.py +3 -6
- {easydev-0.13.3 → easydev-0.15.0}/pyproject.toml +6 -4
- easydev-0.13.3/PKG-INFO +0 -92
- easydev-0.13.3/README.rst +0 -57
- {easydev-0.13.3 → easydev-0.15.0}/COPYING +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/__init__.py +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/dependencies.py +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/doc.py +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/md5tools.py +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/options.py +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/profiler.py +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/progressbar.py +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/__init__.py +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/copybutton.js +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/cno/google_footer.html +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/cno/google_head.html +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/cno/index.html +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/cno/indexsidebar.html +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/cno/layout.html +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/cno/static/bgfooter.png +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/cno/static/bgtop.png +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/cno/static/software.css_t +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/cno/static/warning.jpg +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/cno/theme.conf +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/standard/google_footer.html +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/standard/google_head.html +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/standard/index.html +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/standard/indexsidebar.html +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/standard/layout.html +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/standard/static/bgfooter.png +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/standard/static/bgtop.png +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/standard/static/software.css_t +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/standard/static/warning.jpg +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/share/themes/standard/theme.conf +0 -0
- {easydev-0.13.3 → easydev-0.15.0}/easydev/timer.py +0 -0
easydev-0.15.0/PKG-INFO
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: easydev
|
|
3
|
+
Version: 0.15.0
|
|
4
|
+
Summary: Common utilities to ease development of Python packages
|
|
5
|
+
License-Expression: BSD-3-Clause
|
|
6
|
+
License-File: COPYING
|
|
7
|
+
Keywords: config,decorators,development
|
|
8
|
+
Author: Thomas Cokelaer
|
|
9
|
+
Author-email: thomas.cokelaer@pasteur.fr
|
|
10
|
+
Requires-Python: >=3.9,<4.0
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
27
|
+
Requires-Dist: colorama (>=0.4.6,<0.5.0)
|
|
28
|
+
Requires-Dist: colorlog (>=6.8.2,<7.0.0)
|
|
29
|
+
Requires-Dist: line-profiler (>=5.0.0,<6.0.0)
|
|
30
|
+
Requires-Dist: pexpect (>=4.9.0,<5.0.0)
|
|
31
|
+
Requires-Dist: platformdirs (>=4.2.0,<5.0.0)
|
|
32
|
+
Project-URL: Documentation, https://easydev.readthedocs.io
|
|
33
|
+
Project-URL: Homepage, https://easydev.readthedocs.io
|
|
34
|
+
Project-URL: Issues, https://github.com/cokelaer/easydev/issues
|
|
35
|
+
Project-URL: Repository, https://github.com/cokelaer/easydev
|
|
36
|
+
Description-Content-Type: text/x-rst
|
|
37
|
+
|
|
38
|
+
easydev
|
|
39
|
+
##########
|
|
40
|
+
|
|
41
|
+
.. image:: https://badge.fury.io/py/easydev.svg
|
|
42
|
+
:target: https://pypi.python.org/pypi/easydev
|
|
43
|
+
|
|
44
|
+
.. image:: https://github.com/cokelaer/easydev/actions/workflows/main.yml/badge.svg
|
|
45
|
+
:target: https://github.com/cokelaer/easydev/actions/workflows/main.yml
|
|
46
|
+
|
|
47
|
+
.. image:: https://coveralls.io/repos/cokelaer/easydev/badge.svg?branch=main
|
|
48
|
+
:target: https://coveralls.io/r/cokelaer/easydev?branch=main
|
|
49
|
+
|
|
50
|
+
.. image:: https://static.pepy.tech/personalized-badge/easydev?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads
|
|
51
|
+
:target: https://pepy.tech/projects/easydev
|
|
52
|
+
|
|
53
|
+
:documentation: http://easydev-python.readthedocs.io/en/latest/
|
|
54
|
+
:contributions: Please join https://github.com/cokelaer/easydev
|
|
55
|
+
:source: Please use https://github.com/cokelaer/easydev
|
|
56
|
+
:issues: Please use https://github.com/cokelaer/easydev/issues
|
|
57
|
+
:Python version supported: 3.9, 3.10, 3.11, 3.12, 3.13, 3.14
|
|
58
|
+
|
|
59
|
+
Overview
|
|
60
|
+
--------
|
|
61
|
+
|
|
62
|
+
The `easydev <http://pypi.python.org/pypi/easydev/>`_ package provides
|
|
63
|
+
miscellaneous utility functions and classes that are repeatedly useful during
|
|
64
|
+
the development of Python packages. The goal is to help developers speed up
|
|
65
|
+
their work by providing ready-to-use tools for common tasks such as parameter
|
|
66
|
+
validation, logging, progress bars, configuration files, multiprocessing, and
|
|
67
|
+
more.
|
|
68
|
+
|
|
69
|
+
It has been used as an incubator for other packages (e.g.,
|
|
70
|
+
`colormap <http://pypi.python.org/pypi/colormap>`_) and is used in projects
|
|
71
|
+
such as `bioservices <https://pypi.python.org/pypi/bioservices>`_,
|
|
72
|
+
`sequana <https://sequana.readthedocs.io>`_, and
|
|
73
|
+
`GDSCTools <https://gdsctools.readthedocs.io>`_.
|
|
74
|
+
|
|
75
|
+
Installation
|
|
76
|
+
------------
|
|
77
|
+
|
|
78
|
+
Install the latest release from PyPI::
|
|
79
|
+
|
|
80
|
+
pip install easydev
|
|
81
|
+
|
|
82
|
+
Features
|
|
83
|
+
--------
|
|
84
|
+
|
|
85
|
+
- **Parameter validation** – ``check_param_in_list``, ``check_range``
|
|
86
|
+
- **Dictionary utilities** – ``swapdict``, ``AttrDict`` (access dict keys as attributes, supports nested dicts)
|
|
87
|
+
- **Logging** – coloured logging via ``Logging`` class
|
|
88
|
+
- **Progress bar** – ``Progress`` works in Python, IPython, and Jupyter notebooks
|
|
89
|
+
- **Timer** – measure elapsed time with the ``Timer`` context manager
|
|
90
|
+
- **Profiling** – ``do_profile`` decorator using ``line_profiler``
|
|
91
|
+
- **Configuration files** – ``CustomConfig`` and ``DynamicConfigParser`` for INI-style config management
|
|
92
|
+
- **Shell commands** – ``shellcmd`` and ``execute`` wrappers around subprocess/pexpect
|
|
93
|
+
- **File utilities** – ``touch``, ``mkdirs``, ``md5``
|
|
94
|
+
- **Multiprocessing** – ``MultiProcessing`` class for easy parallel job execution
|
|
95
|
+
- **Codecs** – ``to_list``, ``list2string`` and other conversion helpers
|
|
96
|
+
- **URL utilities** – ``isurl_reachable`` and related helpers
|
|
97
|
+
- **Sphinx integration** – bundled Sphinx theme and ``copybutton`` extension
|
|
98
|
+
|
|
99
|
+
Quick Start
|
|
100
|
+
-----------
|
|
101
|
+
|
|
102
|
+
Parameter validation::
|
|
103
|
+
|
|
104
|
+
from easydev import check_param_in_list, check_range
|
|
105
|
+
|
|
106
|
+
check_param_in_list("on", ["on", "off"]) # passes silently
|
|
107
|
+
check_range(0.5, 0, 1) # passes silently
|
|
108
|
+
|
|
109
|
+
AttrDict – access nested dictionary keys as attributes::
|
|
110
|
+
|
|
111
|
+
from easydev import AttrDict
|
|
112
|
+
|
|
113
|
+
d = AttrDict(**{"server": {"host": "localhost", "port": 8080}})
|
|
114
|
+
print(d.server.host) # localhost
|
|
115
|
+
d.server.port = 9090
|
|
116
|
+
|
|
117
|
+
Coloured logging::
|
|
118
|
+
|
|
119
|
+
from easydev import Logging
|
|
120
|
+
|
|
121
|
+
logger = Logging("myapp", "WARNING")
|
|
122
|
+
logger.warning("something went wrong")
|
|
123
|
+
logger.debug("not shown at WARNING level")
|
|
124
|
+
logger.level = "DEBUG"
|
|
125
|
+
logger.debug("now it is shown")
|
|
126
|
+
|
|
127
|
+
Progress bar::
|
|
128
|
+
|
|
129
|
+
from easydev import Progress
|
|
130
|
+
|
|
131
|
+
pb = Progress(100)
|
|
132
|
+
for i in range(100):
|
|
133
|
+
# do work here
|
|
134
|
+
pb.animate(i + 1)
|
|
135
|
+
|
|
136
|
+
Timer::
|
|
137
|
+
|
|
138
|
+
from easydev import Timer
|
|
139
|
+
import time
|
|
140
|
+
|
|
141
|
+
times = []
|
|
142
|
+
with Timer(times):
|
|
143
|
+
time.sleep(0.1)
|
|
144
|
+
print(f"elapsed: {times[0]:.2f}s")
|
|
145
|
+
|
|
146
|
+
Multiprocessing::
|
|
147
|
+
|
|
148
|
+
from easydev.multicore import MultiProcessing
|
|
149
|
+
|
|
150
|
+
def square(n):
|
|
151
|
+
return n * n
|
|
152
|
+
|
|
153
|
+
t = MultiProcessing(maxcpu=4)
|
|
154
|
+
for i in range(10):
|
|
155
|
+
t.add_job(square, i)
|
|
156
|
+
t.run()
|
|
157
|
+
print(t.results)
|
|
158
|
+
|
|
159
|
+
Moving away from easydev
|
|
160
|
+
------------------------
|
|
161
|
+
|
|
162
|
+
easydev dates from a time when Python lacked many small conveniences. Most of
|
|
163
|
+
its tools now have a standard-library or well-maintained third-party
|
|
164
|
+
replacement, and new projects should not depend on easydev. The full
|
|
165
|
+
module-by-module guide is in the
|
|
166
|
+
`documentation <https://easydev.readthedocs.io/en/latest/migration.html>`_
|
|
167
|
+
(``doc/source/migration.rst``). Main replacements:
|
|
168
|
+
|
|
169
|
+
========================================= ======================================================
|
|
170
|
+
easydev Use instead
|
|
171
|
+
========================================= ======================================================
|
|
172
|
+
``CustomConfig``, ``AppDirs`` ``platformdirs``
|
|
173
|
+
``DynamicConfigParser`` ``configparser``
|
|
174
|
+
``AttrDict`` ``types.SimpleNamespace`` / ``dataclasses``
|
|
175
|
+
``Logging`` ``logging`` (+ ``colorlog``)
|
|
176
|
+
``Progress`` ``tqdm``
|
|
177
|
+
``MultiProcessing`` ``concurrent.futures.ProcessPoolExecutor``
|
|
178
|
+
``shellcmd``, ``execute`` ``subprocess.run``
|
|
179
|
+
``mkdirs``, ``touch``, ``get_home`` ``pathlib`` / ``os.makedirs(exist_ok=True)``
|
|
180
|
+
``cmd_exists`` ``shutil.which``
|
|
181
|
+
``md5`` ``hashlib.file_digest`` (Python 3.11+)
|
|
182
|
+
``browse`` ``webbrowser.open``
|
|
183
|
+
``get_dependencies`` ``importlib.metadata.requires``
|
|
184
|
+
``get_share_file`` ``importlib.resources.files``
|
|
185
|
+
========================================= ======================================================
|
|
186
|
+
|
|
187
|
+
For example, replacing ``CustomConfig``::
|
|
188
|
+
|
|
189
|
+
# before
|
|
190
|
+
from easydev import CustomConfig
|
|
191
|
+
config_dir = CustomConfig("myapp").user_config_dir
|
|
192
|
+
|
|
193
|
+
# after
|
|
194
|
+
from pathlib import Path
|
|
195
|
+
from platformdirs import user_config_dir
|
|
196
|
+
|
|
197
|
+
config_dir = Path(user_config_dir("myapp"))
|
|
198
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
199
|
+
|
|
200
|
+
Changelog
|
|
201
|
+
---------
|
|
202
|
+
|
|
203
|
+
========= ==========================================================================
|
|
204
|
+
Version Description
|
|
205
|
+
========= ==========================================================================
|
|
206
|
+
0.15.0 * fix ``is_mac`` (always returned False)
|
|
207
|
+
* ``assert_list_almost_equal`` now warns (FutureWarning) when called without
|
|
208
|
+
``places``/``deltas`` (nothing is compared); raises ``ListMismatchError``
|
|
209
|
+
(both ValueError and AssertionError)
|
|
210
|
+
* add a migration guide (README and documentation) explaining how to
|
|
211
|
+
replace easydev tools with the standard library, platformdirs, etc.
|
|
212
|
+
* require Python >= 3.9
|
|
213
|
+
0.13.3 * update pyproject with contribs from @s-t-e-v-e-n-k see PR37
|
|
214
|
+
0.13.2 * replace mock with unittest.mock (fixes
|
|
215
|
+
https://github.com/cokelaer/easydev/issues/20)
|
|
216
|
+
0.13.1 * fix get_dependencies
|
|
217
|
+
0.13.0 * fix requirements (line_profiler) and CI
|
|
218
|
+
0.12.2 * For developers: move to pyproject. add precommit
|
|
219
|
+
* replace pkg_resources (deprecated) with importlib
|
|
220
|
+
* replace appdirs with more generic platformdirs
|
|
221
|
+
========= ==========================================================================
|
|
222
|
+
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
easydev
|
|
2
|
+
##########
|
|
3
|
+
|
|
4
|
+
.. image:: https://badge.fury.io/py/easydev.svg
|
|
5
|
+
:target: https://pypi.python.org/pypi/easydev
|
|
6
|
+
|
|
7
|
+
.. image:: https://github.com/cokelaer/easydev/actions/workflows/main.yml/badge.svg
|
|
8
|
+
:target: https://github.com/cokelaer/easydev/actions/workflows/main.yml
|
|
9
|
+
|
|
10
|
+
.. image:: https://coveralls.io/repos/cokelaer/easydev/badge.svg?branch=main
|
|
11
|
+
:target: https://coveralls.io/r/cokelaer/easydev?branch=main
|
|
12
|
+
|
|
13
|
+
.. image:: https://static.pepy.tech/personalized-badge/easydev?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads
|
|
14
|
+
:target: https://pepy.tech/projects/easydev
|
|
15
|
+
|
|
16
|
+
:documentation: http://easydev-python.readthedocs.io/en/latest/
|
|
17
|
+
:contributions: Please join https://github.com/cokelaer/easydev
|
|
18
|
+
:source: Please use https://github.com/cokelaer/easydev
|
|
19
|
+
:issues: Please use https://github.com/cokelaer/easydev/issues
|
|
20
|
+
:Python version supported: 3.9, 3.10, 3.11, 3.12, 3.13, 3.14
|
|
21
|
+
|
|
22
|
+
Overview
|
|
23
|
+
--------
|
|
24
|
+
|
|
25
|
+
The `easydev <http://pypi.python.org/pypi/easydev/>`_ package provides
|
|
26
|
+
miscellaneous utility functions and classes that are repeatedly useful during
|
|
27
|
+
the development of Python packages. The goal is to help developers speed up
|
|
28
|
+
their work by providing ready-to-use tools for common tasks such as parameter
|
|
29
|
+
validation, logging, progress bars, configuration files, multiprocessing, and
|
|
30
|
+
more.
|
|
31
|
+
|
|
32
|
+
It has been used as an incubator for other packages (e.g.,
|
|
33
|
+
`colormap <http://pypi.python.org/pypi/colormap>`_) and is used in projects
|
|
34
|
+
such as `bioservices <https://pypi.python.org/pypi/bioservices>`_,
|
|
35
|
+
`sequana <https://sequana.readthedocs.io>`_, and
|
|
36
|
+
`GDSCTools <https://gdsctools.readthedocs.io>`_.
|
|
37
|
+
|
|
38
|
+
Installation
|
|
39
|
+
------------
|
|
40
|
+
|
|
41
|
+
Install the latest release from PyPI::
|
|
42
|
+
|
|
43
|
+
pip install easydev
|
|
44
|
+
|
|
45
|
+
Features
|
|
46
|
+
--------
|
|
47
|
+
|
|
48
|
+
- **Parameter validation** – ``check_param_in_list``, ``check_range``
|
|
49
|
+
- **Dictionary utilities** – ``swapdict``, ``AttrDict`` (access dict keys as attributes, supports nested dicts)
|
|
50
|
+
- **Logging** – coloured logging via ``Logging`` class
|
|
51
|
+
- **Progress bar** – ``Progress`` works in Python, IPython, and Jupyter notebooks
|
|
52
|
+
- **Timer** – measure elapsed time with the ``Timer`` context manager
|
|
53
|
+
- **Profiling** – ``do_profile`` decorator using ``line_profiler``
|
|
54
|
+
- **Configuration files** – ``CustomConfig`` and ``DynamicConfigParser`` for INI-style config management
|
|
55
|
+
- **Shell commands** – ``shellcmd`` and ``execute`` wrappers around subprocess/pexpect
|
|
56
|
+
- **File utilities** – ``touch``, ``mkdirs``, ``md5``
|
|
57
|
+
- **Multiprocessing** – ``MultiProcessing`` class for easy parallel job execution
|
|
58
|
+
- **Codecs** – ``to_list``, ``list2string`` and other conversion helpers
|
|
59
|
+
- **URL utilities** – ``isurl_reachable`` and related helpers
|
|
60
|
+
- **Sphinx integration** – bundled Sphinx theme and ``copybutton`` extension
|
|
61
|
+
|
|
62
|
+
Quick Start
|
|
63
|
+
-----------
|
|
64
|
+
|
|
65
|
+
Parameter validation::
|
|
66
|
+
|
|
67
|
+
from easydev import check_param_in_list, check_range
|
|
68
|
+
|
|
69
|
+
check_param_in_list("on", ["on", "off"]) # passes silently
|
|
70
|
+
check_range(0.5, 0, 1) # passes silently
|
|
71
|
+
|
|
72
|
+
AttrDict – access nested dictionary keys as attributes::
|
|
73
|
+
|
|
74
|
+
from easydev import AttrDict
|
|
75
|
+
|
|
76
|
+
d = AttrDict(**{"server": {"host": "localhost", "port": 8080}})
|
|
77
|
+
print(d.server.host) # localhost
|
|
78
|
+
d.server.port = 9090
|
|
79
|
+
|
|
80
|
+
Coloured logging::
|
|
81
|
+
|
|
82
|
+
from easydev import Logging
|
|
83
|
+
|
|
84
|
+
logger = Logging("myapp", "WARNING")
|
|
85
|
+
logger.warning("something went wrong")
|
|
86
|
+
logger.debug("not shown at WARNING level")
|
|
87
|
+
logger.level = "DEBUG"
|
|
88
|
+
logger.debug("now it is shown")
|
|
89
|
+
|
|
90
|
+
Progress bar::
|
|
91
|
+
|
|
92
|
+
from easydev import Progress
|
|
93
|
+
|
|
94
|
+
pb = Progress(100)
|
|
95
|
+
for i in range(100):
|
|
96
|
+
# do work here
|
|
97
|
+
pb.animate(i + 1)
|
|
98
|
+
|
|
99
|
+
Timer::
|
|
100
|
+
|
|
101
|
+
from easydev import Timer
|
|
102
|
+
import time
|
|
103
|
+
|
|
104
|
+
times = []
|
|
105
|
+
with Timer(times):
|
|
106
|
+
time.sleep(0.1)
|
|
107
|
+
print(f"elapsed: {times[0]:.2f}s")
|
|
108
|
+
|
|
109
|
+
Multiprocessing::
|
|
110
|
+
|
|
111
|
+
from easydev.multicore import MultiProcessing
|
|
112
|
+
|
|
113
|
+
def square(n):
|
|
114
|
+
return n * n
|
|
115
|
+
|
|
116
|
+
t = MultiProcessing(maxcpu=4)
|
|
117
|
+
for i in range(10):
|
|
118
|
+
t.add_job(square, i)
|
|
119
|
+
t.run()
|
|
120
|
+
print(t.results)
|
|
121
|
+
|
|
122
|
+
Moving away from easydev
|
|
123
|
+
------------------------
|
|
124
|
+
|
|
125
|
+
easydev dates from a time when Python lacked many small conveniences. Most of
|
|
126
|
+
its tools now have a standard-library or well-maintained third-party
|
|
127
|
+
replacement, and new projects should not depend on easydev. The full
|
|
128
|
+
module-by-module guide is in the
|
|
129
|
+
`documentation <https://easydev.readthedocs.io/en/latest/migration.html>`_
|
|
130
|
+
(``doc/source/migration.rst``). Main replacements:
|
|
131
|
+
|
|
132
|
+
========================================= ======================================================
|
|
133
|
+
easydev Use instead
|
|
134
|
+
========================================= ======================================================
|
|
135
|
+
``CustomConfig``, ``AppDirs`` ``platformdirs``
|
|
136
|
+
``DynamicConfigParser`` ``configparser``
|
|
137
|
+
``AttrDict`` ``types.SimpleNamespace`` / ``dataclasses``
|
|
138
|
+
``Logging`` ``logging`` (+ ``colorlog``)
|
|
139
|
+
``Progress`` ``tqdm``
|
|
140
|
+
``MultiProcessing`` ``concurrent.futures.ProcessPoolExecutor``
|
|
141
|
+
``shellcmd``, ``execute`` ``subprocess.run``
|
|
142
|
+
``mkdirs``, ``touch``, ``get_home`` ``pathlib`` / ``os.makedirs(exist_ok=True)``
|
|
143
|
+
``cmd_exists`` ``shutil.which``
|
|
144
|
+
``md5`` ``hashlib.file_digest`` (Python 3.11+)
|
|
145
|
+
``browse`` ``webbrowser.open``
|
|
146
|
+
``get_dependencies`` ``importlib.metadata.requires``
|
|
147
|
+
``get_share_file`` ``importlib.resources.files``
|
|
148
|
+
========================================= ======================================================
|
|
149
|
+
|
|
150
|
+
For example, replacing ``CustomConfig``::
|
|
151
|
+
|
|
152
|
+
# before
|
|
153
|
+
from easydev import CustomConfig
|
|
154
|
+
config_dir = CustomConfig("myapp").user_config_dir
|
|
155
|
+
|
|
156
|
+
# after
|
|
157
|
+
from pathlib import Path
|
|
158
|
+
from platformdirs import user_config_dir
|
|
159
|
+
|
|
160
|
+
config_dir = Path(user_config_dir("myapp"))
|
|
161
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
|
|
163
|
+
Changelog
|
|
164
|
+
---------
|
|
165
|
+
|
|
166
|
+
========= ==========================================================================
|
|
167
|
+
Version Description
|
|
168
|
+
========= ==========================================================================
|
|
169
|
+
0.15.0 * fix ``is_mac`` (always returned False)
|
|
170
|
+
* ``assert_list_almost_equal`` now warns (FutureWarning) when called without
|
|
171
|
+
``places``/``deltas`` (nothing is compared); raises ``ListMismatchError``
|
|
172
|
+
(both ValueError and AssertionError)
|
|
173
|
+
* add a migration guide (README and documentation) explaining how to
|
|
174
|
+
replace easydev tools with the standard library, platformdirs, etc.
|
|
175
|
+
* require Python >= 3.9
|
|
176
|
+
0.13.3 * update pyproject with contribs from @s-t-e-v-e-n-k see PR37
|
|
177
|
+
0.13.2 * replace mock with unittest.mock (fixes
|
|
178
|
+
https://github.com/cokelaer/easydev/issues/20)
|
|
179
|
+
0.13.1 * fix get_dependencies
|
|
180
|
+
0.13.0 * fix requirements (line_profiler) and CI
|
|
181
|
+
0.12.2 * For developers: move to pyproject. add precommit
|
|
182
|
+
* replace pkg_resources (deprecated) with importlib
|
|
183
|
+
* replace appdirs with more generic platformdirs
|
|
184
|
+
========= ==========================================================================
|
|
@@ -61,11 +61,9 @@ There is also an alias **onweb**::
|
|
|
61
61
|
from easydev import onweb
|
|
62
62
|
|
|
63
63
|
"""
|
|
64
|
-
import argparse
|
|
65
64
|
import os
|
|
66
65
|
import sys
|
|
67
66
|
import webbrowser
|
|
68
|
-
from optparse import OptionParser
|
|
69
67
|
|
|
70
68
|
|
|
71
69
|
def browse(url, verbose=True):
|
|
@@ -93,23 +91,23 @@ def _browse_mac(url, verbose=True): # pragma: no cover
|
|
|
93
91
|
try:
|
|
94
92
|
os.system("open /Applications/Safari.app {}".format(url))
|
|
95
93
|
return
|
|
96
|
-
except:
|
|
94
|
+
except Exception:
|
|
97
95
|
pass
|
|
98
96
|
|
|
99
97
|
try:
|
|
100
98
|
os.system("open /Applications/Safari.app {}".format("http://" + url))
|
|
101
99
|
return
|
|
102
|
-
except:
|
|
100
|
+
except Exception:
|
|
103
101
|
pass
|
|
104
102
|
|
|
105
103
|
try:
|
|
106
104
|
webbrowser.open_new(url)
|
|
107
|
-
except:
|
|
105
|
+
except Exception:
|
|
108
106
|
if verbose:
|
|
109
107
|
print("Could not open %s. Trying to append http://" % url)
|
|
110
108
|
try:
|
|
111
109
|
webbrowser.open_new("open http://{}".format(url))
|
|
112
|
-
except:
|
|
110
|
+
except Exception:
|
|
113
111
|
print("Could not open http://%s" % url)
|
|
114
112
|
raise Exception
|
|
115
113
|
|
|
@@ -120,7 +118,7 @@ def _browse_linux(url, verbose=True): # pragma: no cover
|
|
|
120
118
|
try:
|
|
121
119
|
webbrowser.open(url)
|
|
122
120
|
return
|
|
123
|
-
except:
|
|
121
|
+
except Exception:
|
|
124
122
|
pass
|
|
125
123
|
|
|
126
124
|
try:
|
|
@@ -128,7 +126,7 @@ def _browse_linux(url, verbose=True): # pragma: no cover
|
|
|
128
126
|
print("Could not open %s" % url)
|
|
129
127
|
webbrowser.open("http://" + url)
|
|
130
128
|
return
|
|
131
|
-
except:
|
|
129
|
+
except Exception:
|
|
132
130
|
pass
|
|
133
131
|
|
|
134
132
|
raise Exception("Could not open http://{}".format(url))
|
|
@@ -172,6 +170,4 @@ def main(args=None): # pragma: no cover
|
|
|
172
170
|
|
|
173
171
|
|
|
174
172
|
if __name__ == "__main__": # pragma: no cover
|
|
175
|
-
import sys
|
|
176
|
-
|
|
177
173
|
main(sys.argv)
|
|
@@ -125,14 +125,14 @@ class DynamicConfigParser(ConfigParser):
|
|
|
125
125
|
self.read(self._filename)
|
|
126
126
|
elif isinstance(config_or_filename, ConfigParser):
|
|
127
127
|
self._replace_config(config_or_filename)
|
|
128
|
-
elif config_or_filename
|
|
128
|
+
elif config_or_filename is None:
|
|
129
129
|
pass
|
|
130
130
|
else:
|
|
131
131
|
raise TypeError("config_or_filename must be a valid filename or valid ConfigParser instance")
|
|
132
132
|
|
|
133
133
|
def read(self, filename):
|
|
134
134
|
"""Load a new config from a filename (remove all previous sections)"""
|
|
135
|
-
if os.path.isfile(filename)
|
|
135
|
+
if not os.path.isfile(filename):
|
|
136
136
|
raise IOError("filename {0} not found".format(filename))
|
|
137
137
|
|
|
138
138
|
config = ConfigParser()
|
|
@@ -220,9 +220,9 @@ class DynamicConfigParser(ConfigParser):
|
|
|
220
220
|
try: # numbers
|
|
221
221
|
try:
|
|
222
222
|
options[option] = self.getint(section, option)
|
|
223
|
-
except:
|
|
223
|
+
except Exception:
|
|
224
224
|
options[option] = self.getfloat(section, option)
|
|
225
|
-
except: # string
|
|
225
|
+
except Exception: # string
|
|
226
226
|
options[option] = self.get(section, option, raw=True)
|
|
227
227
|
return options
|
|
228
228
|
|
|
@@ -238,7 +238,7 @@ class DynamicConfigParser(ConfigParser):
|
|
|
238
238
|
|
|
239
239
|
"""
|
|
240
240
|
try:
|
|
241
|
-
if os.path.exists(filename)
|
|
241
|
+
if os.path.exists(filename):
|
|
242
242
|
print("Warning: over-writing %s " % filename)
|
|
243
243
|
fp = open(filename, "w")
|
|
244
244
|
except Exception as err: # pragma: no cover
|
|
@@ -311,7 +311,7 @@ class DynamicConfigParser(ConfigParser):
|
|
|
311
311
|
if str(self.get(section, option, raw=True)) != str(data.get(section, option, raw=True)):
|
|
312
312
|
print("option %s in section %s differ" % (option, section))
|
|
313
313
|
return False
|
|
314
|
-
except: # pragma: no cover
|
|
314
|
+
except Exception: # pragma: no cover
|
|
315
315
|
return False
|
|
316
316
|
return True
|
|
317
317
|
|
|
@@ -91,7 +91,7 @@ def get_copybutton_path():
|
|
|
91
91
|
try:
|
|
92
92
|
packagedir = easydev.__path__[0]
|
|
93
93
|
packagedir = os.path.realpath(pj(packagedir, "..", "share"))
|
|
94
|
-
except:
|
|
94
|
+
except Exception:
|
|
95
95
|
raise IOError("could not find data directory")
|
|
96
96
|
return pj(packagedir, "copybutton.js")
|
|
97
97
|
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
#
|
|
15
15
|
##############################################################################
|
|
16
16
|
"""Handy decorators"""
|
|
17
|
-
import threading
|
|
18
17
|
from functools import wraps
|
|
19
18
|
|
|
20
19
|
__all__ = ["ifpylab", "requires", "ifpandas"]
|
|
@@ -100,6 +99,7 @@ def _blocking(not_avail):
|
|
|
100
99
|
return blocking
|
|
101
100
|
"""
|
|
102
101
|
|
|
102
|
+
|
|
103
103
|
# same as require decorator but works with list of stirngs of
|
|
104
104
|
# single string and uses the functools utilities
|
|
105
105
|
def requires(requires, msg=""):
|
|
@@ -122,7 +122,7 @@ def requires(requires, msg=""):
|
|
|
122
122
|
"""
|
|
123
123
|
if isinstance(requires, str):
|
|
124
124
|
requires = [requires]
|
|
125
|
-
elif isinstance(requires, list)
|
|
125
|
+
elif not isinstance(requires, list):
|
|
126
126
|
raise TypeError(
|
|
127
127
|
"First argument of the /requires/ decorator must be a"
|
|
128
128
|
+ "string or list of string representing the required attributes"
|
|
@@ -134,7 +134,7 @@ def requires(requires, msg=""):
|
|
|
134
134
|
@wraps(f)
|
|
135
135
|
def wrapper(*args, **kwds):
|
|
136
136
|
for require in requires:
|
|
137
|
-
if hasattr(args[0], require)
|
|
137
|
+
if not hasattr(args[0], require):
|
|
138
138
|
raise AttributeError("{} not found in {}. ".format(require, args[0]) + msg)
|
|
139
139
|
return f(*args, **kwds)
|
|
140
140
|
|
|
@@ -157,7 +157,7 @@ def ifpandas(func):
|
|
|
157
157
|
return func(*args, **kwds)
|
|
158
158
|
|
|
159
159
|
try:
|
|
160
|
-
import pandas
|
|
160
|
+
import pandas # noqa: F401
|
|
161
161
|
|
|
162
162
|
return wrapper
|
|
163
163
|
except Exception: # pragma: no cover
|
|
@@ -179,7 +179,7 @@ def ifpylab(func):
|
|
|
179
179
|
|
|
180
180
|
# for methods
|
|
181
181
|
try:
|
|
182
|
-
import pylab
|
|
182
|
+
import pylab # noqa: F401
|
|
183
183
|
|
|
184
184
|
return wrapper
|
|
185
185
|
except Exception: # pragma: no cover
|
|
@@ -16,14 +16,22 @@
|
|
|
16
16
|
# $:Id $
|
|
17
17
|
|
|
18
18
|
import tempfile
|
|
19
|
+
import warnings
|
|
19
20
|
|
|
20
21
|
__all__ = ["assert_list_almost_equal", "trysetattr", "TempFile"]
|
|
21
22
|
|
|
22
23
|
|
|
24
|
+
class ListMismatchError(ValueError, AssertionError):
|
|
25
|
+
"""Raised by :func:`assert_list_almost_equal` (ValueError kept for backward compatibility)"""
|
|
26
|
+
|
|
27
|
+
|
|
23
28
|
def assert_list_almost_equal(first, second, places=None, deltas=None):
|
|
24
29
|
"""Combined version nose.tools.assert_almost_equal and assert_list_equal
|
|
25
30
|
|
|
26
31
|
This function checks that 2 lists contain identical items.
|
|
32
|
+
Without *places* or *deltas* nothing is compared and a FutureWarning is
|
|
33
|
+
issued. On mismatch, :class:`ListMismatchError` is raised (it derives from
|
|
34
|
+
both ValueError, for backward compatibility, and AssertionError).
|
|
27
35
|
The equality between pair of items is checked with assert_almost_equal
|
|
28
36
|
function, which means you can check for the places argument
|
|
29
37
|
|
|
@@ -44,7 +52,14 @@ def assert_list_almost_equal(first, second, places=None, deltas=None):
|
|
|
44
52
|
if deltas:
|
|
45
53
|
for x, y in zip(first, second):
|
|
46
54
|
if abs(x - y) > deltas:
|
|
47
|
-
raise
|
|
55
|
+
raise ListMismatchError("{} != {} within tolerance {}".format(x, y, deltas))
|
|
56
|
+
else:
|
|
57
|
+
warnings.warn(
|
|
58
|
+
"assert_list_almost_equal called without places or deltas: nothing is compared. "
|
|
59
|
+
"This will become a strict comparison in a future release.",
|
|
60
|
+
FutureWarning,
|
|
61
|
+
stacklevel=2,
|
|
62
|
+
)
|
|
48
63
|
|
|
49
64
|
|
|
50
65
|
def trysetattr(this, attrname, value, possible):
|
|
@@ -71,7 +86,7 @@ def trysetattr(this, attrname, value, possible):
|
|
|
71
86
|
AssertionError
|
|
72
87
|
|
|
73
88
|
"""
|
|
74
|
-
if possible
|
|
89
|
+
if possible is True:
|
|
75
90
|
a1 = True
|
|
76
91
|
a2 = False
|
|
77
92
|
else:
|
|
@@ -102,7 +117,7 @@ class TempFile(object):
|
|
|
102
117
|
def delete(self):
|
|
103
118
|
try:
|
|
104
119
|
self.temp._closer.delete = True
|
|
105
|
-
except: # pragma: no cover
|
|
120
|
+
except AttributeError: # pragma: no cover
|
|
106
121
|
self.temp.delete = True
|
|
107
122
|
self.temp.close()
|
|
108
123
|
|
|
@@ -20,16 +20,10 @@ __all__ = ["get_home", "cmd_exists"]
|
|
|
20
20
|
|
|
21
21
|
def get_home():
|
|
22
22
|
"""Return path of the HOME"""
|
|
23
|
-
# This function should be robust
|
|
24
23
|
# First, let us try with expanduser
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# This may happen.
|
|
29
|
-
pass
|
|
30
|
-
else:
|
|
31
|
-
if os.path.isdir(homedir):
|
|
32
|
-
return homedir
|
|
24
|
+
homedir = os.path.expanduser("~")
|
|
25
|
+
if os.path.isdir(homedir):
|
|
26
|
+
return homedir
|
|
33
27
|
# Then, with getenv
|
|
34
28
|
for this in ("HOME", "USERPROFILE", "TMP"): # pragma: no cover
|
|
35
29
|
# getenv is same as os.environ.get
|
|
@@ -19,6 +19,12 @@ from multiprocessing import Pool, Process, Queue, cpu_count
|
|
|
19
19
|
__all__ = ["MultiProcessing"]
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def _init_worker():
|
|
23
|
+
import signal
|
|
24
|
+
|
|
25
|
+
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
26
|
+
|
|
27
|
+
|
|
22
28
|
class MultiProcessing(object):
|
|
23
29
|
"""Class to run jobs in an asynchronous manner.
|
|
24
30
|
|
|
@@ -53,7 +59,7 @@ class MultiProcessing(object):
|
|
|
53
59
|
|
|
54
60
|
|
|
55
61
|
"""
|
|
56
|
-
if maxcpu
|
|
62
|
+
if maxcpu is None:
|
|
57
63
|
maxcpu = cpu_count()
|
|
58
64
|
|
|
59
65
|
self.maxcpu = maxcpu
|
|
@@ -102,13 +108,8 @@ class MultiProcessing(object):
|
|
|
102
108
|
self.pb = Progress(len(self.jobs), 1)
|
|
103
109
|
self.pb.animate(0)
|
|
104
110
|
|
|
105
|
-
def init_worker():
|
|
106
|
-
import signal
|
|
107
|
-
|
|
108
|
-
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
109
|
-
|
|
110
111
|
self.results = []
|
|
111
|
-
self.pool = Pool(self.maxcpu,
|
|
112
|
+
self.pool = Pool(self.maxcpu, _init_worker)
|
|
112
113
|
|
|
113
114
|
for process in self.jobs:
|
|
114
115
|
self.pool.apply_async(process._target, process._args, process._kwargs, callback=self._cb)
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
import importlib
|
|
19
19
|
import os
|
|
20
20
|
from os.path import join as pj
|
|
21
|
-
from pathlib import Path
|
|
22
21
|
|
|
23
22
|
__all__ = [
|
|
24
23
|
"get_shared_directory_path",
|
|
@@ -53,7 +52,7 @@ def get_shared_directory_path(package):
|
|
|
53
52
|
|
|
54
53
|
# print("install mode ? ")
|
|
55
54
|
sharedir = os.path.realpath(pj(location, package, "share"))
|
|
56
|
-
if os.path.isdir(sharedir)
|
|
55
|
+
if os.path.isdir(sharedir):
|
|
57
56
|
# looks like we have found the share directory so it is an install mode
|
|
58
57
|
# print ("yes")
|
|
59
58
|
return sharedir
|
|
@@ -63,13 +62,13 @@ def get_shared_directory_path(package):
|
|
|
63
62
|
# FIXME: do we need the 3 cases ??
|
|
64
63
|
# probably just 2 are required, one for develop and one for install mode
|
|
65
64
|
sharedir = os.path.realpath(pj(location, "..", "share"))
|
|
66
|
-
if os.path.isdir(sharedir)
|
|
65
|
+
if os.path.isdir(sharedir):
|
|
67
66
|
return sharedir
|
|
68
67
|
sharedir = os.path.realpath(pj(location, "..", "..", "share"))
|
|
69
|
-
if os.path.isdir(sharedir)
|
|
68
|
+
if os.path.isdir(sharedir):
|
|
70
69
|
return sharedir
|
|
71
70
|
sharedir = os.path.realpath(pj(location, "..", "..", "..", "share"))
|
|
72
|
-
if os.path.isdir(sharedir)
|
|
71
|
+
if os.path.isdir(sharedir):
|
|
73
72
|
return sharedir
|
|
74
73
|
# could not be found,
|
|
75
74
|
sharedir = []
|
|
@@ -117,10 +116,10 @@ def get_share_file(package, datadir, filename):
|
|
|
117
116
|
packagedir = get_shared_directory_path(package)
|
|
118
117
|
fullpath = os.path.join(packagedir, datadir)
|
|
119
118
|
# check that it exists
|
|
120
|
-
if os.path.isdir(fullpath)
|
|
119
|
+
if not os.path.isdir(fullpath): # pragma: no cover
|
|
121
120
|
raise ValueError("The directory %s in package %s does not seem to exist" % (packagedir, fullpath))
|
|
122
121
|
filename_path = os.path.join(fullpath, filename)
|
|
123
|
-
if os.path.isfile(filename_path)
|
|
122
|
+
if not os.path.isfile(filename_path):
|
|
124
123
|
correct_files = [x for x in os.listdir(fullpath) if os.path.isfile(x)]
|
|
125
124
|
msg = "The file %s does not exists. Correct filenames found in %s/%s are:\n" % (
|
|
126
125
|
filename_path,
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
#
|
|
15
15
|
##############################################################################
|
|
16
16
|
|
|
17
|
-
import os
|
|
18
17
|
import sys
|
|
19
18
|
|
|
20
19
|
__all__ = ["get_platform"]
|
|
@@ -36,24 +35,17 @@ def get_platform(): # pragma: no cover
|
|
|
36
35
|
|
|
37
36
|
"""
|
|
38
37
|
print("//easydev.get_platform() Will be deprecated in future version of easydev")
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
platform = "Java"
|
|
51
|
-
elif platform.startswith("win"):
|
|
52
|
-
platform = "Windows"
|
|
53
|
-
elif platform.startswith("darwin"):
|
|
54
|
-
platform = "Darwin"
|
|
55
|
-
else:
|
|
56
|
-
print("platform not parsed. Return raw value of sys.platform.")
|
|
38
|
+
platform = sys.platform
|
|
39
|
+
if platform.startswith("linux"):
|
|
40
|
+
platform = "Linux"
|
|
41
|
+
elif platform.startswith("java"):
|
|
42
|
+
platform = "Java"
|
|
43
|
+
elif platform.startswith("win"):
|
|
44
|
+
platform = "Windows"
|
|
45
|
+
elif platform.startswith("darwin"):
|
|
46
|
+
platform = "Darwin"
|
|
47
|
+
else:
|
|
48
|
+
print("platform not parsed. Return raw value of sys.platform.")
|
|
57
49
|
return platform
|
|
58
50
|
|
|
59
51
|
|
|
@@ -72,7 +64,7 @@ def is_linux():
|
|
|
72
64
|
|
|
73
65
|
|
|
74
66
|
def is_mac():
|
|
75
|
-
if get_platform()
|
|
67
|
+
if get_platform() in ("Darwin", "Mac"):
|
|
76
68
|
return True
|
|
77
69
|
else:
|
|
78
70
|
return False
|
|
@@ -51,5 +51,5 @@ def get_sphinx_themes():
|
|
|
51
51
|
|
|
52
52
|
sharedir = get_shared_directory_path("easydev")
|
|
53
53
|
sharedir = pj(sharedir, "themes")
|
|
54
|
-
themes = [x for x in os.listdir(sharedir) if x.startswith(".")
|
|
54
|
+
themes = [x for x in os.listdir(sharedir) if not x.startswith(".")]
|
|
55
55
|
return themes
|
|
@@ -33,7 +33,7 @@ __all__ = [
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def precision(data, digit=2):
|
|
36
|
-
"""
|
|
36
|
+
"""Truncate a value (not round) keeping only N digits precision
|
|
37
37
|
|
|
38
38
|
::
|
|
39
39
|
|
|
@@ -116,24 +116,26 @@ def shellcmd(cmd, show=False, verbose=False, ignore_errors=False):
|
|
|
116
116
|
:param str cmd: the command to call
|
|
117
117
|
:param bool show: print the command
|
|
118
118
|
:param bool verbose: print the output
|
|
119
|
+
:param bool ignore_errors: do not raise when the command writes to stderr
|
|
119
120
|
|
|
120
|
-
:return: the output as
|
|
121
|
+
:return: the output as bytes (stripped)
|
|
121
122
|
"""
|
|
122
123
|
if show:
|
|
123
124
|
print(cmd)
|
|
124
125
|
try:
|
|
125
126
|
ret = subprocess.Popen([cmd], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
|
126
127
|
|
|
127
|
-
output = ret.
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
output, error = ret.communicate()
|
|
129
|
+
output = output.strip()
|
|
130
|
+
error = error.strip()
|
|
130
131
|
|
|
132
|
+
# Legacy behaviour kept on purpose (many packages rely on it): any
|
|
133
|
+
# output on stderr is an error, the exit status is not checked.
|
|
131
134
|
if len(error) > 0:
|
|
132
135
|
if ignore_errors is False:
|
|
133
136
|
raise Exception(error)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
print("Errors/Warning" + str(error))
|
|
137
|
+
elif verbose is True:
|
|
138
|
+
print("Errors/Warning" + str(error))
|
|
137
139
|
|
|
138
140
|
if verbose is True:
|
|
139
141
|
print(output)
|
|
@@ -161,7 +163,7 @@ def execute(cmd, showcmd=True, verbose=True):
|
|
|
161
163
|
if verbose:
|
|
162
164
|
try:
|
|
163
165
|
sys.stdout.write(line.decode())
|
|
164
|
-
except:
|
|
166
|
+
except Exception:
|
|
165
167
|
sys.stdout.write(line)
|
|
166
168
|
|
|
167
169
|
sys.stdout.flush()
|
|
@@ -279,12 +281,9 @@ class AttrDict(dict):
|
|
|
279
281
|
|
|
280
282
|
:param dict content: a valid dictionary
|
|
281
283
|
"""
|
|
282
|
-
|
|
283
|
-
try:
|
|
284
|
-
from collections import OrderedDict
|
|
285
|
-
except:
|
|
286
|
-
OrderedDict = AttrDict
|
|
284
|
+
from collections import OrderedDict
|
|
287
285
|
|
|
286
|
+
# accepts dict and attrdict classes
|
|
288
287
|
if content.__class__ not in [dict, OrderedDict, AttrDict]:
|
|
289
288
|
raise TypeError
|
|
290
289
|
|
|
@@ -14,10 +14,7 @@
|
|
|
14
14
|
#
|
|
15
15
|
##############################################################################
|
|
16
16
|
"""Utilities related to the web"""
|
|
17
|
-
|
|
18
|
-
import httplib
|
|
19
|
-
except ImportError:
|
|
20
|
-
import http.client as httplib
|
|
17
|
+
import http.client as httplib
|
|
21
18
|
|
|
22
19
|
__all__ = ["isurl_reachable"]
|
|
23
20
|
|
|
@@ -39,13 +36,13 @@ def isurl_reachable(url, timeout=10, path="/"): # pragma: no cover
|
|
|
39
36
|
conn = httplib.HTTPConnection(url, timeout=timeout)
|
|
40
37
|
try:
|
|
41
38
|
conn.request("HEAD", path)
|
|
42
|
-
except:
|
|
39
|
+
except Exception:
|
|
43
40
|
return False
|
|
44
41
|
# 302 is a redirection
|
|
45
42
|
# 200 is okay
|
|
46
43
|
try:
|
|
47
44
|
response = conn.getresponse()
|
|
48
|
-
except:
|
|
45
|
+
except Exception:
|
|
49
46
|
return False
|
|
50
47
|
|
|
51
48
|
if response.status in [200, 302]:
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "easydev"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.15.0"
|
|
8
8
|
description = "Common utilities to ease development of Python packages"
|
|
9
9
|
authors = [
|
|
10
10
|
{ "name" = "Thomas Cokelaer", "email" = "thomas.cokelaer@pasteur.fr" },
|
|
@@ -21,10 +21,12 @@ classifiers = [
|
|
|
21
21
|
"Intended Audience :: Science/Research",
|
|
22
22
|
"License :: OSI Approved :: BSD License",
|
|
23
23
|
"Operating System :: POSIX :: Linux",
|
|
24
|
-
"Programming Language :: Python :: 3.8",
|
|
25
24
|
"Programming Language :: Python :: 3.9",
|
|
26
25
|
"Programming Language :: Python :: 3.10",
|
|
27
26
|
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Programming Language :: Python :: 3.14",
|
|
28
30
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
29
31
|
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
30
32
|
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
@@ -46,12 +48,12 @@ Documentation = "https://easydev.readthedocs.io"
|
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
[tool.poetry.dependencies]
|
|
49
|
-
python = "^3.
|
|
51
|
+
python = "^3.9"
|
|
50
52
|
colorama = "^0.4.6"
|
|
51
53
|
colorlog = "^6.8.2"
|
|
52
54
|
pexpect = "^4.9.0"
|
|
53
55
|
platformdirs = "^4.2.0"
|
|
54
|
-
line-profiler = "^
|
|
56
|
+
line-profiler = "^5.0.0"
|
|
55
57
|
|
|
56
58
|
|
|
57
59
|
|
easydev-0.13.3/PKG-INFO
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.3
|
|
2
|
-
Name: easydev
|
|
3
|
-
Version: 0.13.3
|
|
4
|
-
Summary: Common utilities to ease development of Python packages
|
|
5
|
-
Home-page: https://easydev.readthedocs.io
|
|
6
|
-
License: BSD-3-Clause
|
|
7
|
-
Keywords: config,decorators,development
|
|
8
|
-
Author: Thomas Cokelaer
|
|
9
|
-
Author-email: thomas.cokelaer@pasteur.fr
|
|
10
|
-
Requires-Python: >=3.8,<4.0
|
|
11
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
-
Classifier: Intended Audience :: Education
|
|
13
|
-
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
-
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: Intended Audience :: Science/Research
|
|
16
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
17
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
-
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
24
|
-
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
25
|
-
Requires-Dist: colorama (>=0.4.6,<0.5.0)
|
|
26
|
-
Requires-Dist: colorlog (>=6.8.2,<7.0.0)
|
|
27
|
-
Requires-Dist: line-profiler (>=4.1.2,<5.0.0)
|
|
28
|
-
Requires-Dist: pexpect (>=4.9.0,<5.0.0)
|
|
29
|
-
Requires-Dist: platformdirs (>=4.2.0,<5.0.0)
|
|
30
|
-
Project-URL: Documentation, https://easydev.readthedocs.io
|
|
31
|
-
Project-URL: Issues, https://github.com/cokelaer/easydev/issues
|
|
32
|
-
Project-URL: Repository, https://github.com/cokelaer/easydev
|
|
33
|
-
Description-Content-Type: text/x-rst
|
|
34
|
-
|
|
35
|
-
easydev
|
|
36
|
-
##########
|
|
37
|
-
|
|
38
|
-
.. image:: https://badge.fury.io/py/easydev.svg
|
|
39
|
-
:target: https://pypi.python.org/pypi/easydev
|
|
40
|
-
|
|
41
|
-
.. image:: https://github.com/cokelaer/easydev/actions/workflows/main.yml/badge.svg
|
|
42
|
-
:target: https://github.com/cokelaer/easydev/actions/workflows/main.yml
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
.. image:: https://coveralls.io/repos/cokelaer/easydev/badge.svg?branch=main
|
|
46
|
-
:target: https://coveralls.io/r/cokelaer/easydev?branch=main
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
:documentation: http://easydev-python.readthedocs.io/en/latest/
|
|
52
|
-
:contributions: Please join https://github.com/cokelaer/easydev
|
|
53
|
-
:source: Please use https://github.com/cokelaer/easydev
|
|
54
|
-
:issues: Please use https://github.com/cokelaer/easydev/issues
|
|
55
|
-
:Python version supported: 3.9, 3.10, 3.11, 3.12
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
The `easydev <http://pypi.python.org/pypi/easydev/>`_ package
|
|
59
|
-
provides miscellaneous functions that are repeatidly used during
|
|
60
|
-
the development of Python packages. The goal is to help developers on
|
|
61
|
-
speeding up their own dev. It has been used also as an incubator for other
|
|
62
|
-
packages (e.g., http://pypi.python.org/pypi/colormap) and is stable.
|
|
63
|
-
|
|
64
|
-
.. warning:: I'm not pretending to provide universal and bug-free tools. The
|
|
65
|
-
tools provided may also change. However, **easydev** is used
|
|
66
|
-
in a few other packages such as
|
|
67
|
-
`bioservices <https://pypi.python.org/pypi/bioservices>`_,
|
|
68
|
-
`sequana <https://sequana.readthedocs.io>`_ or
|
|
69
|
-
`GDSCTools <https://sequana.readthedocs.io>`_ to give a few
|
|
70
|
-
examples.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Note 10/01/2025: Although I tried to keep back-compatibility as much as possible, test can be done only on newest version. support for python 3.7 and 3.8 were droppe because the continuous integration does not support it. However, easydev may still be python3.7 and 3.8 compatible. Have a try.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Changelog
|
|
78
|
-
~~~~~~~~~
|
|
79
|
-
|
|
80
|
-
========= ==========================================================================
|
|
81
|
-
Version Description
|
|
82
|
-
========= ==========================================================================
|
|
83
|
-
0.13.3 * update pyproject with contribs from @s-t-e-v-e-n-k see PR37
|
|
84
|
-
0.13.2 * replace mock with unittest.mock (fixes
|
|
85
|
-
https://github.com/cokelaer/easydev/issues/20)
|
|
86
|
-
0.13.1 * fix get_dependencies
|
|
87
|
-
0.13.0 * fix requirements (line_profiler) and CI
|
|
88
|
-
0.12.2 * For developers: move to pyprojet. add precomit
|
|
89
|
-
* replace pkg_resources (deprecated) with importlib
|
|
90
|
-
* replace appdirs with more generic platformdirs
|
|
91
|
-
========= ==========================================================================
|
|
92
|
-
|
easydev-0.13.3/README.rst
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
easydev
|
|
2
|
-
##########
|
|
3
|
-
|
|
4
|
-
.. image:: https://badge.fury.io/py/easydev.svg
|
|
5
|
-
:target: https://pypi.python.org/pypi/easydev
|
|
6
|
-
|
|
7
|
-
.. image:: https://github.com/cokelaer/easydev/actions/workflows/main.yml/badge.svg
|
|
8
|
-
:target: https://github.com/cokelaer/easydev/actions/workflows/main.yml
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
.. image:: https://coveralls.io/repos/cokelaer/easydev/badge.svg?branch=main
|
|
12
|
-
:target: https://coveralls.io/r/cokelaer/easydev?branch=main
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
:documentation: http://easydev-python.readthedocs.io/en/latest/
|
|
18
|
-
:contributions: Please join https://github.com/cokelaer/easydev
|
|
19
|
-
:source: Please use https://github.com/cokelaer/easydev
|
|
20
|
-
:issues: Please use https://github.com/cokelaer/easydev/issues
|
|
21
|
-
:Python version supported: 3.9, 3.10, 3.11, 3.12
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
The `easydev <http://pypi.python.org/pypi/easydev/>`_ package
|
|
25
|
-
provides miscellaneous functions that are repeatidly used during
|
|
26
|
-
the development of Python packages. The goal is to help developers on
|
|
27
|
-
speeding up their own dev. It has been used also as an incubator for other
|
|
28
|
-
packages (e.g., http://pypi.python.org/pypi/colormap) and is stable.
|
|
29
|
-
|
|
30
|
-
.. warning:: I'm not pretending to provide universal and bug-free tools. The
|
|
31
|
-
tools provided may also change. However, **easydev** is used
|
|
32
|
-
in a few other packages such as
|
|
33
|
-
`bioservices <https://pypi.python.org/pypi/bioservices>`_,
|
|
34
|
-
`sequana <https://sequana.readthedocs.io>`_ or
|
|
35
|
-
`GDSCTools <https://sequana.readthedocs.io>`_ to give a few
|
|
36
|
-
examples.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Note 10/01/2025: Although I tried to keep back-compatibility as much as possible, test can be done only on newest version. support for python 3.7 and 3.8 were droppe because the continuous integration does not support it. However, easydev may still be python3.7 and 3.8 compatible. Have a try.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Changelog
|
|
44
|
-
~~~~~~~~~
|
|
45
|
-
|
|
46
|
-
========= ==========================================================================
|
|
47
|
-
Version Description
|
|
48
|
-
========= ==========================================================================
|
|
49
|
-
0.13.3 * update pyproject with contribs from @s-t-e-v-e-n-k see PR37
|
|
50
|
-
0.13.2 * replace mock with unittest.mock (fixes
|
|
51
|
-
https://github.com/cokelaer/easydev/issues/20)
|
|
52
|
-
0.13.1 * fix get_dependencies
|
|
53
|
-
0.13.0 * fix requirements (line_profiler) and CI
|
|
54
|
-
0.12.2 * For developers: move to pyprojet. add precomit
|
|
55
|
-
* replace pkg_resources (deprecated) with importlib
|
|
56
|
-
* replace appdirs with more generic platformdirs
|
|
57
|
-
========= ==========================================================================
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|