Mercurial > repos > shellac > sam_consensus_v3
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/env/lib/python3.9/site-packages/boltons/ecoutils.py Mon Mar 22 18:12:50 2021 +0000 @@ -0,0 +1,489 @@ +# -*- coding: utf-8 -*- +"""As a programming ecosystem grows, so do the chances of runtime +variability. + +Python boasts one of the widest deployments for a high-level +programming environment, making it a viable target for all manner of +application. But with breadth comes variance, so it's important to +know what you're working with. + +Some basic variations that are common among development machines: + +* **Executable runtime**: CPython, PyPy, Jython, etc., plus build date and compiler +* **Language version**: 2.4, 2.5, 2.6, 2.7... 3.4, 3.5, 3.6 +* **Host operating system**: Windows, OS X, Ubuntu, Debian, CentOS, RHEL, etc. +* **Features**: 64-bit, IPv6, Unicode character support (UCS-2/UCS-4) +* **Built-in library support**: OpenSSL, threading, SQLite, zlib +* **User environment**: umask, ulimit, working directory path +* **Machine info**: CPU count, hostname, filesystem encoding + +See the full example profile below for more. + +ecoutils was created to quantify that variability. ecoutils quickly +produces an information-dense description of critical runtime factors, +with minimal side effects. In short, ecoutils is like browser and user +agent analytics, but for Python environments. + +Transmission and collection +--------------------------- + +The data is all JSON serializable, and is suitable for sending to a +central analytics server. An HTTP-backed service for this can be found +at: https://github.com/mahmoud/espymetrics/ + +Notable omissions +----------------- + +Due to space constraints (and possibly latency constraints), the +following information is deemed not dense enough, and thus omitted: + +* :data:`sys.path` +* full :mod:`sysconfig` +* environment variables (:data:`os.environ`) + +Compatibility +------------- + +So far ecoutils has has been tested on Python 2.4, 2.5, 2.6, 2.7, 3.4, +3.5, and PyPy. Various versions have been tested on Ubuntu, Debian, +RHEL, OS X, FreeBSD, and Windows 7. + +.. note:: Boltons typically only support back to Python 2.6, but due + to its nature, ecoutils extends backwards compatibility to Python + 2.4 and 2.5. + +Profile generation +------------------ + +Profiles are generated by :func:`ecoutils.get_profile`. + +When run as a module, ecoutils will call :func:`~ecoutils.get_profile` +and print a profile in JSON format:: + + $ python -m boltons.ecoutils + { + "_eco_version": "1.0.0", + "cpu_count": 4, + "cwd": "/home/mahmoud/projects/boltons", + "fs_encoding": "UTF-8", + "guid": "6b139e7bbf5ad4ed8d4063bf6235b4d2", + "hostfqdn": "mahmoud-host", + "hostname": "mahmoud-host", + "linux_dist_name": "Ubuntu", + "linux_dist_version": "14.04", + "python": { + "argv": "boltons/ecoutils.py", + "bin": "/usr/bin/python", + "build_date": "Jun 22 2015 17:58:13", + "compiler": "GCC 4.8.2", + "features": { + "64bit": true, + "expat": "expat_2.1.0", + "ipv6": true, + "openssl": "OpenSSL 1.0.1f 6 Jan 2014", + "readline": true, + "sqlite": "3.8.2", + "threading": true, + "tkinter": "8.6", + "unicode_wide": true, + "zlib": "1.2.8" + }, + "version": "2.7.6 (default, Jun 22 2015, 17:58:13) [GCC 4.8.2]", + "version_info": [ + 2, + 7, + 6, + "final", + 0 + ] + }, + "time_utc": "2016-05-24 07:59:40.473140", + "time_utc_offset": -8.0, + "ulimit_hard": 4096, + "ulimit_soft": 1024, + "umask": "002", + "uname": { + "machine": "x86_64", + "node": "mahmoud-host", + "processor": "x86_64", + "release": "3.13.0-85-generic", + "system": "Linux", + "version": "#129-Ubuntu SMP Thu Mar 17 20:50:15 UTC 2016" + }, + "username": "mahmoud" + } + +``pip install boltons`` and try it yourself! + +""" + +import re +import os +import sys +import time +import pprint +import random +import socket +import struct +import getpass +import datetime +import platform + +ECO_VERSION = '1.0.1' # see version history below + +PY_GT_2 = sys.version_info[0] > 2 + +try: + getrandbits = random.SystemRandom().getrandbits + HAVE_URANDOM = True +except Exception: + HAVE_URANDOM = False + getrandbits = random.getrandbits + + +# 128-bit GUID just like a UUID, but backwards compatible to 2.4 +INSTANCE_ID = hex(getrandbits(128))[2:-1].lower() + +IS_64BIT = struct.calcsize("P") > 4 +HAVE_UCS4 = getattr(sys, 'maxunicode', 0) > 65536 +HAVE_READLINE = True + +try: + import readline +except Exception: + HAVE_READLINE = False + +try: + import sqlite3 + SQLITE_VERSION = sqlite3.sqlite_version +except Exception: + # note: 2.5 and older have sqlite, but not sqlite3 + SQLITE_VERSION = '' + + +try: + + import ssl + try: + OPENSSL_VERSION = ssl.OPENSSL_VERSION + except AttributeError: + # This is a conservative estimate for Python <2.6 + # SSL module added in 2006, when 0.9.7 was standard + OPENSSL_VERSION = 'OpenSSL >0.8.0' +except Exception: + OPENSSL_VERSION = '' + + +try: + if PY_GT_2: + import tkinter + else: + import Tkinter as tkinter + TKINTER_VERSION = str(tkinter.TkVersion) +except Exception: + TKINTER_VERSION = '' + + +try: + import zlib + ZLIB_VERSION = zlib.ZLIB_VERSION +except Exception: + ZLIB_VERSION = '' + + +try: + from xml.parsers import expat + EXPAT_VERSION = expat.EXPAT_VERSION +except Exception: + EXPAT_VERSION = '' + + +try: + from multiprocessing import cpu_count + CPU_COUNT = cpu_count() +except Exception: + CPU_COUNT = 0 + +try: + import threading + HAVE_THREADING = True +except Exception: + HAVE_THREADING = False + + +try: + HAVE_IPV6 = socket.has_ipv6 +except Exception: + HAVE_IPV6 = False + + +try: + from resource import getrlimit, RLIMIT_NOFILE + RLIMIT_FDS_SOFT, RLIMIT_FDS_HARD = getrlimit(RLIMIT_NOFILE) +except Exception: + RLIMIT_FDS_SOFT, RLIMIT_FDS_HARD = 0, 0 + + +START_TIME_INFO = {'time_utc': str(datetime.datetime.utcnow()), + 'time_utc_offset': -time.timezone / 3600.0} + + +def get_python_info(): + ret = {} + ret['argv'] = _escape_shell_args(sys.argv) + ret['bin'] = sys.executable + + # Even though compiler/build_date are already here, they're + # actually parsed from the version string. So, in the rare case of + # the unparsable version string, we're still transmitting it. + ret['version'] = ' '.join(sys.version.split()) + + ret['compiler'] = platform.python_compiler() + ret['build_date'] = platform.python_build()[1] + ret['version_info'] = list(sys.version_info) + + ret['features'] = {'openssl': OPENSSL_VERSION, + 'expat': EXPAT_VERSION, + 'sqlite': SQLITE_VERSION, + 'tkinter': TKINTER_VERSION, + 'zlib': ZLIB_VERSION, + 'unicode_wide': HAVE_UCS4, + 'readline': HAVE_READLINE, + '64bit': IS_64BIT, + 'ipv6': HAVE_IPV6, + 'threading': HAVE_THREADING, + 'urandom': HAVE_URANDOM} + + return ret + + +def get_profile(**kwargs): + """The main entrypoint to ecoutils. Calling this will return a + JSON-serializable dictionary of information about the current + process. + + It is very unlikely that the information returned will change + during the lifetime of the process, and in most cases the majority + of the information stays the same between runs as well. + + :func:`get_profile` takes one optional keyword argument, *scrub*, + a :class:`bool` that, if True, blanks out identifiable + information. This includes current working directory, hostname, + Python executable path, command-line arguments, and + username. Values are replaced with '-', but for compatibility keys + remain in place. + + """ + scrub = kwargs.pop('scrub', False) + if kwargs: + raise TypeError('unexpected keyword arguments: %r' % (kwargs.keys(),)) + ret = {} + try: + ret['username'] = getpass.getuser() + except Exception: + ret['username'] = '' + ret['guid'] = str(INSTANCE_ID) + ret['hostname'] = socket.gethostname() + ret['hostfqdn'] = socket.getfqdn() + uname = platform.uname() + ret['uname'] = {'system': uname[0], + 'node': uname[1], + 'release': uname[2], # linux: distro name + 'version': uname[3], # linux: kernel version + 'machine': uname[4], + 'processor': uname[5]} + try: + linux_dist = platform.linux_distribution() + except Exception: + linux_dist = ('', '', '') + ret['linux_dist_name'] = linux_dist[0] + ret['linux_dist_version'] = linux_dist[1] + ret['cpu_count'] = CPU_COUNT + + ret['fs_encoding'] = sys.getfilesystemencoding() + ret['ulimit_soft'] = RLIMIT_FDS_SOFT + ret['ulimit_hard'] = RLIMIT_FDS_HARD + ret['cwd'] = os.getcwd() + ret['umask'] = oct(os.umask(os.umask(2))).rjust(3, '0') + + ret['python'] = get_python_info() + ret.update(START_TIME_INFO) + ret['_eco_version'] = ECO_VERSION + + if scrub: + # mask identifiable information + ret['cwd'] = '-' + ret['hostname'] = '-' + ret['hostfqdn'] = '-' + ret['python']['bin'] = '-' + ret['python']['argv'] = '-' + ret['uname']['node'] = '-' + ret['username'] = '-' + + return ret + + +_real_safe_repr = pprint._safe_repr + + +def _fake_json_dumps(val, indent=2): + # never do this. this is a hack for Python 2.4. Python 2.5 added + # the json module for a reason. + def _fake_safe_repr(*a, **kw): + res, is_read, is_rec = _real_safe_repr(*a, **kw) + if res == 'None': + res = 'null' + if res == 'True': + res = 'true' + if res == 'False': + res = 'false' + if not (res.startswith("'") or res.startswith("u'")): + res = res + else: + if res.startswith('u'): + res = res[1:] + + contents = res[1:-1] + contents = contents.replace('"', '').replace(r'\"', '') + res = '"' + contents + '"' + return res, is_read, is_rec + + pprint._safe_repr = _fake_safe_repr + try: + ret = pprint.pformat(val, indent=indent) + finally: + pprint._safe_repr = _real_safe_repr + + return ret + + +def get_profile_json(indent=False): + if indent: + indent = 2 + else: + indent = 0 + try: + import json + + def dumps(val, indent): + if indent: + return json.dumps(val, sort_keys=True, indent=indent) + return json.dumps(val, sort_keys=True) + + except ImportError: + def dumps(val, indent): + ret = _fake_json_dumps(val, indent=indent) + if not indent: + ret = re.sub(r'\n\s*', ' ', ret) + return ret + + data_dict = get_profile() + return dumps(data_dict, indent) + + +def main(): + print(get_profile_json(indent=True)) + +############################################# +# The shell escaping copied in from strutils +############################################# + + +def _escape_shell_args(args, sep=' ', style=None): + if not style: + if sys.platform == 'win32': + style = 'cmd' + else: + style = 'sh' + + if style == 'sh': + return _args2sh(args, sep=sep) + elif style == 'cmd': + return _args2cmd(args, sep=sep) + + raise ValueError("style expected one of 'cmd' or 'sh', not %r" % style) + + +_find_sh_unsafe = re.compile(r'[^a-zA-Z0-9_@%+=:,./-]').search + + +def _args2sh(args, sep=' '): + # see strutils + ret_list = [] + + for arg in args: + if not arg: + ret_list.append("''") + continue + if _find_sh_unsafe(arg) is None: + ret_list.append(arg) + continue + # use single quotes, and put single quotes into double quotes + # the string $'b is then quoted as '$'"'"'b' + ret_list.append("'" + arg.replace("'", "'\"'\"'") + "'") + + return ' '.join(ret_list) + + +def _args2cmd(args, sep=' '): + # see strutils + result = [] + needquote = False + for arg in args: + bs_buf = [] + + # Add a space to separate this argument from the others + if result: + result.append(' ') + + needquote = (" " in arg) or ("\t" in arg) or not arg + if needquote: + result.append('"') + + for c in arg: + if c == '\\': + # Don't know if we need to double yet. + bs_buf.append(c) + elif c == '"': + # Double backslashes. + result.append('\\' * len(bs_buf)*2) + bs_buf = [] + result.append('\\"') + else: + # Normal char + if bs_buf: + result.extend(bs_buf) + bs_buf = [] + result.append(c) + + # Add remaining backslashes, if any. + if bs_buf: + result.extend(bs_buf) + + if needquote: + result.extend(bs_buf) + result.append('"') + + return ''.join(result) + + +############################ +# End shell escaping code +############################ + +if __name__ == '__main__': + main() + + +""" + +ecoutils protocol version history +--------------------------------- + +The version is ECO_VERSION module-level constant, and _eco_version key +in the dictionary returned from ecoutils.get_profile(). + +1.0.1 - (boltons version 16.3.2+) Remove uuid dependency and add HAVE_URANDOM +1.0.0 - (boltons version 16.3.0-16.3.1) Initial release + +"""