Mercurial > repos > shellac > sam_consensus_v3
comparison env/lib/python3.9/site-packages/boltons/ecoutils.py @ 0:4f3585e2f14b draft default tip
"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author | shellac |
---|---|
date | Mon, 22 Mar 2021 18:12:50 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4f3585e2f14b |
---|---|
1 # -*- coding: utf-8 -*- | |
2 """As a programming ecosystem grows, so do the chances of runtime | |
3 variability. | |
4 | |
5 Python boasts one of the widest deployments for a high-level | |
6 programming environment, making it a viable target for all manner of | |
7 application. But with breadth comes variance, so it's important to | |
8 know what you're working with. | |
9 | |
10 Some basic variations that are common among development machines: | |
11 | |
12 * **Executable runtime**: CPython, PyPy, Jython, etc., plus build date and compiler | |
13 * **Language version**: 2.4, 2.5, 2.6, 2.7... 3.4, 3.5, 3.6 | |
14 * **Host operating system**: Windows, OS X, Ubuntu, Debian, CentOS, RHEL, etc. | |
15 * **Features**: 64-bit, IPv6, Unicode character support (UCS-2/UCS-4) | |
16 * **Built-in library support**: OpenSSL, threading, SQLite, zlib | |
17 * **User environment**: umask, ulimit, working directory path | |
18 * **Machine info**: CPU count, hostname, filesystem encoding | |
19 | |
20 See the full example profile below for more. | |
21 | |
22 ecoutils was created to quantify that variability. ecoutils quickly | |
23 produces an information-dense description of critical runtime factors, | |
24 with minimal side effects. In short, ecoutils is like browser and user | |
25 agent analytics, but for Python environments. | |
26 | |
27 Transmission and collection | |
28 --------------------------- | |
29 | |
30 The data is all JSON serializable, and is suitable for sending to a | |
31 central analytics server. An HTTP-backed service for this can be found | |
32 at: https://github.com/mahmoud/espymetrics/ | |
33 | |
34 Notable omissions | |
35 ----------------- | |
36 | |
37 Due to space constraints (and possibly latency constraints), the | |
38 following information is deemed not dense enough, and thus omitted: | |
39 | |
40 * :data:`sys.path` | |
41 * full :mod:`sysconfig` | |
42 * environment variables (:data:`os.environ`) | |
43 | |
44 Compatibility | |
45 ------------- | |
46 | |
47 So far ecoutils has has been tested on Python 2.4, 2.5, 2.6, 2.7, 3.4, | |
48 3.5, and PyPy. Various versions have been tested on Ubuntu, Debian, | |
49 RHEL, OS X, FreeBSD, and Windows 7. | |
50 | |
51 .. note:: Boltons typically only support back to Python 2.6, but due | |
52 to its nature, ecoutils extends backwards compatibility to Python | |
53 2.4 and 2.5. | |
54 | |
55 Profile generation | |
56 ------------------ | |
57 | |
58 Profiles are generated by :func:`ecoutils.get_profile`. | |
59 | |
60 When run as a module, ecoutils will call :func:`~ecoutils.get_profile` | |
61 and print a profile in JSON format:: | |
62 | |
63 $ python -m boltons.ecoutils | |
64 { | |
65 "_eco_version": "1.0.0", | |
66 "cpu_count": 4, | |
67 "cwd": "/home/mahmoud/projects/boltons", | |
68 "fs_encoding": "UTF-8", | |
69 "guid": "6b139e7bbf5ad4ed8d4063bf6235b4d2", | |
70 "hostfqdn": "mahmoud-host", | |
71 "hostname": "mahmoud-host", | |
72 "linux_dist_name": "Ubuntu", | |
73 "linux_dist_version": "14.04", | |
74 "python": { | |
75 "argv": "boltons/ecoutils.py", | |
76 "bin": "/usr/bin/python", | |
77 "build_date": "Jun 22 2015 17:58:13", | |
78 "compiler": "GCC 4.8.2", | |
79 "features": { | |
80 "64bit": true, | |
81 "expat": "expat_2.1.0", | |
82 "ipv6": true, | |
83 "openssl": "OpenSSL 1.0.1f 6 Jan 2014", | |
84 "readline": true, | |
85 "sqlite": "3.8.2", | |
86 "threading": true, | |
87 "tkinter": "8.6", | |
88 "unicode_wide": true, | |
89 "zlib": "1.2.8" | |
90 }, | |
91 "version": "2.7.6 (default, Jun 22 2015, 17:58:13) [GCC 4.8.2]", | |
92 "version_info": [ | |
93 2, | |
94 7, | |
95 6, | |
96 "final", | |
97 0 | |
98 ] | |
99 }, | |
100 "time_utc": "2016-05-24 07:59:40.473140", | |
101 "time_utc_offset": -8.0, | |
102 "ulimit_hard": 4096, | |
103 "ulimit_soft": 1024, | |
104 "umask": "002", | |
105 "uname": { | |
106 "machine": "x86_64", | |
107 "node": "mahmoud-host", | |
108 "processor": "x86_64", | |
109 "release": "3.13.0-85-generic", | |
110 "system": "Linux", | |
111 "version": "#129-Ubuntu SMP Thu Mar 17 20:50:15 UTC 2016" | |
112 }, | |
113 "username": "mahmoud" | |
114 } | |
115 | |
116 ``pip install boltons`` and try it yourself! | |
117 | |
118 """ | |
119 | |
120 import re | |
121 import os | |
122 import sys | |
123 import time | |
124 import pprint | |
125 import random | |
126 import socket | |
127 import struct | |
128 import getpass | |
129 import datetime | |
130 import platform | |
131 | |
132 ECO_VERSION = '1.0.1' # see version history below | |
133 | |
134 PY_GT_2 = sys.version_info[0] > 2 | |
135 | |
136 try: | |
137 getrandbits = random.SystemRandom().getrandbits | |
138 HAVE_URANDOM = True | |
139 except Exception: | |
140 HAVE_URANDOM = False | |
141 getrandbits = random.getrandbits | |
142 | |
143 | |
144 # 128-bit GUID just like a UUID, but backwards compatible to 2.4 | |
145 INSTANCE_ID = hex(getrandbits(128))[2:-1].lower() | |
146 | |
147 IS_64BIT = struct.calcsize("P") > 4 | |
148 HAVE_UCS4 = getattr(sys, 'maxunicode', 0) > 65536 | |
149 HAVE_READLINE = True | |
150 | |
151 try: | |
152 import readline | |
153 except Exception: | |
154 HAVE_READLINE = False | |
155 | |
156 try: | |
157 import sqlite3 | |
158 SQLITE_VERSION = sqlite3.sqlite_version | |
159 except Exception: | |
160 # note: 2.5 and older have sqlite, but not sqlite3 | |
161 SQLITE_VERSION = '' | |
162 | |
163 | |
164 try: | |
165 | |
166 import ssl | |
167 try: | |
168 OPENSSL_VERSION = ssl.OPENSSL_VERSION | |
169 except AttributeError: | |
170 # This is a conservative estimate for Python <2.6 | |
171 # SSL module added in 2006, when 0.9.7 was standard | |
172 OPENSSL_VERSION = 'OpenSSL >0.8.0' | |
173 except Exception: | |
174 OPENSSL_VERSION = '' | |
175 | |
176 | |
177 try: | |
178 if PY_GT_2: | |
179 import tkinter | |
180 else: | |
181 import Tkinter as tkinter | |
182 TKINTER_VERSION = str(tkinter.TkVersion) | |
183 except Exception: | |
184 TKINTER_VERSION = '' | |
185 | |
186 | |
187 try: | |
188 import zlib | |
189 ZLIB_VERSION = zlib.ZLIB_VERSION | |
190 except Exception: | |
191 ZLIB_VERSION = '' | |
192 | |
193 | |
194 try: | |
195 from xml.parsers import expat | |
196 EXPAT_VERSION = expat.EXPAT_VERSION | |
197 except Exception: | |
198 EXPAT_VERSION = '' | |
199 | |
200 | |
201 try: | |
202 from multiprocessing import cpu_count | |
203 CPU_COUNT = cpu_count() | |
204 except Exception: | |
205 CPU_COUNT = 0 | |
206 | |
207 try: | |
208 import threading | |
209 HAVE_THREADING = True | |
210 except Exception: | |
211 HAVE_THREADING = False | |
212 | |
213 | |
214 try: | |
215 HAVE_IPV6 = socket.has_ipv6 | |
216 except Exception: | |
217 HAVE_IPV6 = False | |
218 | |
219 | |
220 try: | |
221 from resource import getrlimit, RLIMIT_NOFILE | |
222 RLIMIT_FDS_SOFT, RLIMIT_FDS_HARD = getrlimit(RLIMIT_NOFILE) | |
223 except Exception: | |
224 RLIMIT_FDS_SOFT, RLIMIT_FDS_HARD = 0, 0 | |
225 | |
226 | |
227 START_TIME_INFO = {'time_utc': str(datetime.datetime.utcnow()), | |
228 'time_utc_offset': -time.timezone / 3600.0} | |
229 | |
230 | |
231 def get_python_info(): | |
232 ret = {} | |
233 ret['argv'] = _escape_shell_args(sys.argv) | |
234 ret['bin'] = sys.executable | |
235 | |
236 # Even though compiler/build_date are already here, they're | |
237 # actually parsed from the version string. So, in the rare case of | |
238 # the unparsable version string, we're still transmitting it. | |
239 ret['version'] = ' '.join(sys.version.split()) | |
240 | |
241 ret['compiler'] = platform.python_compiler() | |
242 ret['build_date'] = platform.python_build()[1] | |
243 ret['version_info'] = list(sys.version_info) | |
244 | |
245 ret['features'] = {'openssl': OPENSSL_VERSION, | |
246 'expat': EXPAT_VERSION, | |
247 'sqlite': SQLITE_VERSION, | |
248 'tkinter': TKINTER_VERSION, | |
249 'zlib': ZLIB_VERSION, | |
250 'unicode_wide': HAVE_UCS4, | |
251 'readline': HAVE_READLINE, | |
252 '64bit': IS_64BIT, | |
253 'ipv6': HAVE_IPV6, | |
254 'threading': HAVE_THREADING, | |
255 'urandom': HAVE_URANDOM} | |
256 | |
257 return ret | |
258 | |
259 | |
260 def get_profile(**kwargs): | |
261 """The main entrypoint to ecoutils. Calling this will return a | |
262 JSON-serializable dictionary of information about the current | |
263 process. | |
264 | |
265 It is very unlikely that the information returned will change | |
266 during the lifetime of the process, and in most cases the majority | |
267 of the information stays the same between runs as well. | |
268 | |
269 :func:`get_profile` takes one optional keyword argument, *scrub*, | |
270 a :class:`bool` that, if True, blanks out identifiable | |
271 information. This includes current working directory, hostname, | |
272 Python executable path, command-line arguments, and | |
273 username. Values are replaced with '-', but for compatibility keys | |
274 remain in place. | |
275 | |
276 """ | |
277 scrub = kwargs.pop('scrub', False) | |
278 if kwargs: | |
279 raise TypeError('unexpected keyword arguments: %r' % (kwargs.keys(),)) | |
280 ret = {} | |
281 try: | |
282 ret['username'] = getpass.getuser() | |
283 except Exception: | |
284 ret['username'] = '' | |
285 ret['guid'] = str(INSTANCE_ID) | |
286 ret['hostname'] = socket.gethostname() | |
287 ret['hostfqdn'] = socket.getfqdn() | |
288 uname = platform.uname() | |
289 ret['uname'] = {'system': uname[0], | |
290 'node': uname[1], | |
291 'release': uname[2], # linux: distro name | |
292 'version': uname[3], # linux: kernel version | |
293 'machine': uname[4], | |
294 'processor': uname[5]} | |
295 try: | |
296 linux_dist = platform.linux_distribution() | |
297 except Exception: | |
298 linux_dist = ('', '', '') | |
299 ret['linux_dist_name'] = linux_dist[0] | |
300 ret['linux_dist_version'] = linux_dist[1] | |
301 ret['cpu_count'] = CPU_COUNT | |
302 | |
303 ret['fs_encoding'] = sys.getfilesystemencoding() | |
304 ret['ulimit_soft'] = RLIMIT_FDS_SOFT | |
305 ret['ulimit_hard'] = RLIMIT_FDS_HARD | |
306 ret['cwd'] = os.getcwd() | |
307 ret['umask'] = oct(os.umask(os.umask(2))).rjust(3, '0') | |
308 | |
309 ret['python'] = get_python_info() | |
310 ret.update(START_TIME_INFO) | |
311 ret['_eco_version'] = ECO_VERSION | |
312 | |
313 if scrub: | |
314 # mask identifiable information | |
315 ret['cwd'] = '-' | |
316 ret['hostname'] = '-' | |
317 ret['hostfqdn'] = '-' | |
318 ret['python']['bin'] = '-' | |
319 ret['python']['argv'] = '-' | |
320 ret['uname']['node'] = '-' | |
321 ret['username'] = '-' | |
322 | |
323 return ret | |
324 | |
325 | |
326 _real_safe_repr = pprint._safe_repr | |
327 | |
328 | |
329 def _fake_json_dumps(val, indent=2): | |
330 # never do this. this is a hack for Python 2.4. Python 2.5 added | |
331 # the json module for a reason. | |
332 def _fake_safe_repr(*a, **kw): | |
333 res, is_read, is_rec = _real_safe_repr(*a, **kw) | |
334 if res == 'None': | |
335 res = 'null' | |
336 if res == 'True': | |
337 res = 'true' | |
338 if res == 'False': | |
339 res = 'false' | |
340 if not (res.startswith("'") or res.startswith("u'")): | |
341 res = res | |
342 else: | |
343 if res.startswith('u'): | |
344 res = res[1:] | |
345 | |
346 contents = res[1:-1] | |
347 contents = contents.replace('"', '').replace(r'\"', '') | |
348 res = '"' + contents + '"' | |
349 return res, is_read, is_rec | |
350 | |
351 pprint._safe_repr = _fake_safe_repr | |
352 try: | |
353 ret = pprint.pformat(val, indent=indent) | |
354 finally: | |
355 pprint._safe_repr = _real_safe_repr | |
356 | |
357 return ret | |
358 | |
359 | |
360 def get_profile_json(indent=False): | |
361 if indent: | |
362 indent = 2 | |
363 else: | |
364 indent = 0 | |
365 try: | |
366 import json | |
367 | |
368 def dumps(val, indent): | |
369 if indent: | |
370 return json.dumps(val, sort_keys=True, indent=indent) | |
371 return json.dumps(val, sort_keys=True) | |
372 | |
373 except ImportError: | |
374 def dumps(val, indent): | |
375 ret = _fake_json_dumps(val, indent=indent) | |
376 if not indent: | |
377 ret = re.sub(r'\n\s*', ' ', ret) | |
378 return ret | |
379 | |
380 data_dict = get_profile() | |
381 return dumps(data_dict, indent) | |
382 | |
383 | |
384 def main(): | |
385 print(get_profile_json(indent=True)) | |
386 | |
387 ############################################# | |
388 # The shell escaping copied in from strutils | |
389 ############################################# | |
390 | |
391 | |
392 def _escape_shell_args(args, sep=' ', style=None): | |
393 if not style: | |
394 if sys.platform == 'win32': | |
395 style = 'cmd' | |
396 else: | |
397 style = 'sh' | |
398 | |
399 if style == 'sh': | |
400 return _args2sh(args, sep=sep) | |
401 elif style == 'cmd': | |
402 return _args2cmd(args, sep=sep) | |
403 | |
404 raise ValueError("style expected one of 'cmd' or 'sh', not %r" % style) | |
405 | |
406 | |
407 _find_sh_unsafe = re.compile(r'[^a-zA-Z0-9_@%+=:,./-]').search | |
408 | |
409 | |
410 def _args2sh(args, sep=' '): | |
411 # see strutils | |
412 ret_list = [] | |
413 | |
414 for arg in args: | |
415 if not arg: | |
416 ret_list.append("''") | |
417 continue | |
418 if _find_sh_unsafe(arg) is None: | |
419 ret_list.append(arg) | |
420 continue | |
421 # use single quotes, and put single quotes into double quotes | |
422 # the string $'b is then quoted as '$'"'"'b' | |
423 ret_list.append("'" + arg.replace("'", "'\"'\"'") + "'") | |
424 | |
425 return ' '.join(ret_list) | |
426 | |
427 | |
428 def _args2cmd(args, sep=' '): | |
429 # see strutils | |
430 result = [] | |
431 needquote = False | |
432 for arg in args: | |
433 bs_buf = [] | |
434 | |
435 # Add a space to separate this argument from the others | |
436 if result: | |
437 result.append(' ') | |
438 | |
439 needquote = (" " in arg) or ("\t" in arg) or not arg | |
440 if needquote: | |
441 result.append('"') | |
442 | |
443 for c in arg: | |
444 if c == '\\': | |
445 # Don't know if we need to double yet. | |
446 bs_buf.append(c) | |
447 elif c == '"': | |
448 # Double backslashes. | |
449 result.append('\\' * len(bs_buf)*2) | |
450 bs_buf = [] | |
451 result.append('\\"') | |
452 else: | |
453 # Normal char | |
454 if bs_buf: | |
455 result.extend(bs_buf) | |
456 bs_buf = [] | |
457 result.append(c) | |
458 | |
459 # Add remaining backslashes, if any. | |
460 if bs_buf: | |
461 result.extend(bs_buf) | |
462 | |
463 if needquote: | |
464 result.extend(bs_buf) | |
465 result.append('"') | |
466 | |
467 return ''.join(result) | |
468 | |
469 | |
470 ############################ | |
471 # End shell escaping code | |
472 ############################ | |
473 | |
474 if __name__ == '__main__': | |
475 main() | |
476 | |
477 | |
478 """ | |
479 | |
480 ecoutils protocol version history | |
481 --------------------------------- | |
482 | |
483 The version is ECO_VERSION module-level constant, and _eco_version key | |
484 in the dictionary returned from ecoutils.get_profile(). | |
485 | |
486 1.0.1 - (boltons version 16.3.2+) Remove uuid dependency and add HAVE_URANDOM | |
487 1.0.0 - (boltons version 16.3.0-16.3.1) Initial release | |
488 | |
489 """ |