├── .editorconfig ├── .gitignore ├── LICENCE ├── MANIFEST.in ├── README.rst ├── requirements.txt ├── screenshot.png ├── setup.py └── src └── devpy ├── __init__.py ├── develop └── __init__.py ├── log.py ├── tb.py └── temp.py /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.py] 11 | indent_size = 4 12 | 13 | [*.{html,js,xml,css}] 14 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #### joe made this: http://goel.io/joe 2 | 3 | #####=== Python ===##### 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg* 26 | .installed.cfg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *.sqlite 46 | public_html/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | *.pid 62 | *.bz2 63 | *.zip 64 | 65 | # unit tests 66 | twistd.hostname 67 | runtests 68 | htmlcov 69 | 70 | # editors 71 | *.sublime* 72 | .vscode 73 | 74 | # tempfile 75 | *.tmp 76 | *~ 77 | 78 | *.sqlite 79 | \.idea/ 80 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 sametmax 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include src *.html *.css *.js 2 | include *requirements.txt 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | DEVPY 2 | ----- 3 | 4 | Devpy is a set of tools to ease Python development. 5 | 6 | Install 7 | ========= 8 | 9 | :: 10 | 11 | pip install devpy 12 | 13 | Quick demo 14 | =========== 15 | 16 | Devpy provides a quick dev setup for logging that you can replace later by a more robust solution: 17 | 18 | .. code:: python 19 | 20 | import devpy.develop as log 21 | 22 | log.info('This is an info') 23 | log.warning('This is a warning') 24 | log.error('This is an error') 25 | log.critical('This is a critical error') 26 | 27 | log.info('Now let me crash the program. This stack trace is automatically in the log file:') 28 | 29 | import codecs 30 | 31 | codecs.open('/thisdoesnotexist') 32 | 33 | 34 | This gives: 35 | 36 | 37 | .. image:: screenshot.png 38 | 39 | 40 | Autolog 41 | ======== 42 | 43 | Setting up proper logging is tedious, so you may want to do it later, but you wish you could get basic logging right away: 44 | 45 | .. code:: python 46 | 47 | import devpy 48 | 49 | # Get a logger that automatically logs to console and a rotating file 50 | # The rotating file is setup in the temp directory of your system, in 51 | # a subdir named after your script name. 52 | # Logs are colored in the console according to their level. 53 | # The file path is printed at the beginning of the program. 54 | 55 | log = devpy.autolog() # log is a regular stdlib logger object 56 | 57 | # start logging: 58 | 59 | log.info('Yes') 60 | 61 | Once you have time to setup logging seriously, you can just replace the autolog with a regular custom Python logger, and all your logs will still work. 62 | 63 | Setting the environment variable DEVPY_LOG_LEVEL to an integer or a level name (debug, info, error, warning, critical...) will set the autolog log to it. 64 | 65 | Setting the environment variable DEVPY_COLOR_LOG to 0 disables log highlighting. 66 | 67 | autolog parameters: 68 | 69 | - level (default=-1): the general log level 70 | - name (defaul=name of the root module): the name of the log file 71 | - path (default=OS temp dir + name): path to the log file 72 | - log_on_crash (default=True): add a hook to log the stack trace in case of a crash 73 | - log_filename (default=True): log log file path at the program start 74 | - color_log (default=True): add colors to the log 75 | 76 | 77 | Stacktrace helper 78 | ================= 79 | 80 | Format the stack trace so that: 81 | 82 | - it separates the various logicial blocs 83 | - it emphasizes the lines of your program and not the stdlib 84 | - lines of your program are syntax highlighted 85 | 86 | Just do: 87 | 88 | .. code:: python 89 | 90 | import devpy 91 | devpy.color_traceback() 92 | 93 | 94 | All helpers at once 95 | =================== 96 | 97 | Two ways: 98 | 99 | .. code:: python 100 | 101 | import devpy 102 | log = devpy.dev_mode() # can set color_traceback=True, autolog=True 103 | 104 | # or just 105 | # import devpy.develop as log 106 | # for a one liner to activate it all 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Pygments 2 | colorama 3 | colored-traceback 4 | colorlog 5 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sametmax/devpy/66a150817644edf825d19b80f644e7d05b2a3e86/screenshot.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | import re 3 | import sys 4 | import setuptools 5 | 6 | 7 | def get_version(path="src/devpy/__init__.py"): 8 | """ Return the version of by with regex intead of importing it""" 9 | init_content = open(path, "rt").read() 10 | pattern = r"^__version__ = ['\"]([^'\"]*)['\"]" 11 | return re.search(pattern, init_content, re.M).group(1) 12 | 13 | 14 | def get_requirements(path): 15 | 16 | setuppy_format = \ 17 | 'https://github.com/{user}/{repo}/tarball/master#egg={egg}' 18 | 19 | setuppy_pattern = \ 20 | r'github.com/(?P[^/.]+)/(?P[^.]+).git#egg=(?P.+)' 21 | 22 | dep_links = [] 23 | install_requires = [] 24 | with open(path) as f: 25 | for line in f: 26 | 27 | if "sys_platform" in line: 28 | line, platform = line.split(';') 29 | 30 | match = re.search(r'sys_platform\s*==\s*(.*)', platform) 31 | 32 | if sys.platform != match.groups()[0].strip('\"\''): 33 | continue 34 | 35 | if line.startswith('-e'): 36 | url_infos = re.search(setuppy_pattern, line).groupdict() 37 | dep_links.append(setuppy_format.format(**url_infos)) 38 | line = '=='.join(url_infos['egg'].rsplit('-', 1)) 39 | 40 | install_requires.append(line.strip()) 41 | 42 | return install_requires, dep_links 43 | 44 | 45 | requirements, _ = get_requirements('requirements.txt') 46 | 47 | 48 | setuptools.setup(name='devpy', 49 | version=get_version(), 50 | description='Tools to help development and debugging', 51 | long_description=open('README.rst').read().strip(), 52 | author='Sam & Max', 53 | author_email='lesametlemax@gmail.com', 54 | url='https://github.com/sametmax/devpy/', 55 | packages=setuptools.find_packages('src'), 56 | package_dir={'': 'src'}, 57 | install_requires=requirements, 58 | include_package_data=True, 59 | license='MIT', 60 | zip_safe=False, 61 | keywords='devpy dev', 62 | classifiers=['Development Status :: 1 - Planning', 63 | 'Intended Audience :: Developers', 64 | 'Natural Language :: English', 65 | 'Programming Language :: Python :: 3.5', 66 | 'Programming Language :: Python :: 3.6', 67 | 'Programming Language :: Python :: 3 :: Only', 68 | 'Operating System :: OS Independent'],) 69 | -------------------------------------------------------------------------------- /src/devpy/__init__.py: -------------------------------------------------------------------------------- 1 | import devpy 2 | 3 | from .log import autolog # noqa 4 | from .tb import color_traceback # noqa 5 | 6 | __version__ = "0.1.8" 7 | 8 | 9 | def dev_mode(color_traceback=True, autolog=True): # noqa 10 | if color_traceback: 11 | devpy.color_traceback() 12 | if autolog: 13 | return devpy.autolog() 14 | -------------------------------------------------------------------------------- /src/devpy/develop/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from devpy import dev_mode 4 | 5 | sys.modules['devpy.develop'] = dev_mode() 6 | -------------------------------------------------------------------------------- /src/devpy/log.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | from logging.handlers import RotatingFileHandler 5 | from pathlib import Path 6 | 7 | import colorlog 8 | 9 | from .temp import temp_dir 10 | 11 | try: 12 | env_log_level = os.environ.get("DEVPY_LOG_LEVEL", -1) 13 | DEFAULT_LOG_LEVEL = int(env_log_level) 14 | except ValueError: 15 | DEFAULT_LOG_LEVEL = getattr(logging, str(env_log_level).upper(), -1) 16 | 17 | env_color_log = str(os.environ.get("DEVPY_COLOR_LOG", True)).strip().lower() 18 | DEFAULT_COLOR_LOG = env_color_log in ('true', 'yes', '1') 19 | 20 | 21 | # todo: add pretty print 22 | def autolog( 23 | level=DEFAULT_LOG_LEVEL, 24 | name=None, 25 | path=None, 26 | log_on_crash=True, 27 | log_filename=True, 28 | color_log=DEFAULT_COLOR_LOG, 29 | _cache={} 30 | ): 31 | if not name: 32 | try: 33 | name = Path(sys.argv[0]).absolute().with_suffix('').name 34 | except IndexError: 35 | pass 36 | 37 | if name in _cache: 38 | return _cache[name] 39 | 40 | logger = logging.getLogger(name) 41 | 42 | filelogger = logging.getLogger('__fileonly__') 43 | 44 | logger.setLevel(level) 45 | 46 | log_file = path or Path(temp_dir(name)) / "auto.log" 47 | 48 | formatter = logging.Formatter( 49 | '%(asctime)s :: %(levelname)s :: %(pathname)s:%(lineno)s :: %(message)s' 50 | ) 51 | file_handler = RotatingFileHandler(str(log_file), 'a', 1000000, 1) 52 | file_handler.setFormatter(formatter) 53 | logger.addHandler(file_handler) 54 | filelogger.addHandler(file_handler) 55 | 56 | if color_log: 57 | stream_handler = colorlog.StreamHandler() 58 | colored_formatter = colorlog.ColoredFormatter( 59 | '%(log_color)s%(message)s', 60 | log_colors={ 61 | 'WARNING': 'yellow', 62 | 'ERROR': 'red', 63 | 'CRITICAL': 'red,bg_white' 64 | } 65 | ) 66 | stream_handler.setFormatter(colored_formatter) 67 | else: 68 | stream_handler = logging.StreamHandler() 69 | logger.addHandler(stream_handler) 70 | 71 | previous_hook = sys.excepthook 72 | 73 | def on_crash(type, value, tb): 74 | filelogger.critical( 75 | "The program crashed on:", 76 | exc_info=(type, value, tb) 77 | ) 78 | previous_hook(type, value, tb) 79 | 80 | if log_on_crash: 81 | sys.excepthook = on_crash 82 | 83 | if log_filename: 84 | logger.info('Starting to log in "{}"'.format(log_file)) 85 | 86 | _cache[name] = logger 87 | 88 | return logger 89 | -------------------------------------------------------------------------------- /src/devpy/tb.py: -------------------------------------------------------------------------------- 1 | import re 2 | import site 3 | import sys 4 | import sysconfig 5 | import traceback 6 | 7 | import pygments.lexers 8 | from colored_traceback.colored_traceback import Colorizer 9 | 10 | LIB_DIRS = [sysconfig.get_path('stdlib'), site.USER_SITE, 'File "