Skip to content
This repository has been archived by the owner on Jan 30, 2023. It is now read-only.

Commit

Permalink
set up print and logging for sage_bootstrap
Browse files Browse the repository at this point in the history
  • Loading branch information
vbraun committed Jun 20, 2015
1 parent 05875d5 commit f034a44
Show file tree
Hide file tree
Showing 12 changed files with 474 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/sage_bootstrap/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.tox
/MANIFEST
9 changes: 9 additions & 0 deletions src/sage_bootstrap/sage-bootstrap
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env python

import sage_bootstrap

print('done')

from sage_bootstrap.config import Configuration

print(Configuration())
9 changes: 9 additions & 0 deletions src/sage_bootstrap/sage_bootstrap/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

from sage_bootstrap.config import Configuration
config = Configuration()

from sage_bootstrap.stdio import init_streams
init_streams(config)

from sage_bootstrap.logger import init_logger
init_logger(config)
91 changes: 91 additions & 0 deletions src/sage_bootstrap/sage_bootstrap/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
"""
Set Up Logging
Logging can be customized using the ``SAGE_BOOTSTRAP`` environment
variable. It is a comma-separated list of ``key:value`` pairs. They
are not case sensitive. Valid pairs are:
* ``log:[level]``, where ``[level]`` is one of
* ``debug``
* ``info``
* ``warning``
* ``critical``
* ``error``
* ``interactive:true`` or ``interactive:false``, to override isatty detection.
"""


#*****************************************************************************
# Copyright (C) 2015 Volker Braun <vbraun.name@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# http://www.gnu.org/licenses/
#*****************************************************************************


import sys
import os
import logging


LOG_LEVELS = (
'debug',
'info',
'warning',
'critical',
'error'
)




class Configuration(object):

_initialized = False

log = 'info'

interactive = os.isatty(sys.stdout.fileno())

def __init__(self):
if not Configuration._initialized:
Configuration._init_from_environ()
if self.log not in LOG_LEVELS:
raise ValueError('invalid log level: {0}'.format(self.log))
assert isinstance(self.interactive, bool)

@classmethod
def _init_from_environ(cls):
env = os.environ.get('SAGE_BOOTSTRAP', '').lower()
for pair in env.split(','):
if not pair.strip():
continue
key, value = pair.split(':', 1)
key = key.strip()
value = value.strip()
if key == 'log':
cls.log = value
elif key == 'interactive':
if value == 'true':
cls.interactive = True
elif value == 'false':
cls.interactive = False
else:
raise ValueError('interactive value must be "true" or "false", got "{0}"'
.format(value))
else:
raise ValueError('unknown key: "{0}"'.format(key))
cls._initialized = True

def __repr__(self):
return '\n'.join([
'Configuration:',
' * log = {0}'.format(self.log),
' * interactive = {0}'.format(self.interactive)
])
59 changes: 59 additions & 0 deletions src/sage_bootstrap/sage_bootstrap/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
"""
Set Up Logging
When using a script interactively, logging should go to stdout and be
human-readable. When using a script as part of a pipe (usually
involving tee), logging should go to stderr.
"""


#*****************************************************************************
# Copyright (C) 2015 Volker Braun <vbraun.name@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# http://www.gnu.org/licenses/
#*****************************************************************************

import sys
import os
import logging

logger = logging.getLogger()


default_formatter = logging.Formatter(
'%(levelname)s [%(module)s.%(funcName)s:%(lineno)s]: %(message)s')

plain_formatter = logging.Formatter('%(message)s')


class ExcludeInfoFilter(logging.Filter):

def filter(self, record):
return record.levelno != logging.INFO

class OnlyInfoFilter(logging.Filter):

def filter(self, record):
return record.levelno == logging.INFO


def init_logger(config):
level = getattr(logging, config.log.upper())
logger.setLevel(level)
ch_stderr = logging.StreamHandler(sys.stderr)
ch_stderr.setLevel(logging.DEBUG)
ch_stderr.setFormatter(default_formatter)
if config.interactive:
ch_stderr.addFilter(ExcludeInfoFilter())
ch_stdout = logging.StreamHandler(sys.stdout)
ch_stdout.setLevel(logging.DEBUG)
ch_stdout.setFormatter(plain_formatter)
ch_stdout.addFilter(OnlyInfoFilter())
logger.addHandler(ch_stdout)
logger.addHandler(ch_stderr)

43 changes: 43 additions & 0 deletions src/sage_bootstrap/sage_bootstrap/stdio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
"""
Set Up Input/output
Output should always be unbuffered so that it appears immediately on
the terminal.
"""

#*****************************************************************************
# Copyright (C) 2015 Volker Braun <vbraun.name@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# http://www.gnu.org/licenses/
#*****************************************************************************


import sys
import os


class UnbufferedStream(object):

def __init__(self, stream):
self.stream = stream

def write(self, data):
self.stream.write(data)
self.stream.flush()

def __getattr__(self, attr):
return getattr(self.stream, attr)


REAL_STDOUT = sys.stdout
REAL_STDERR = sys.stderr


def init_streams(config):
if not config.interactive:
sys.stdout = UnbufferedStream(REAL_STDOUT)
14 changes: 14 additions & 0 deletions src/sage_bootstrap/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env python

from distutils.core import setup

setup(
name='sage_bootstrap',
description='',
author='Volker Braun',
author_email='vbraun.name@gmail.com',
packages=['sage_bootstrap'],
scripts=['sage-bootstrap'],
version='1.0',
url='https://www.sagemath.org',
)
Empty file.
83 changes: 83 additions & 0 deletions src/sage_bootstrap/test/runnable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Utility to test running with different values for ``SAGE_BOOTSTRAP``
"""

#*****************************************************************************
# Copyright (C) 2015 Volker Braun <vbraun.name@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# http://www.gnu.org/licenses/
#*****************************************************************************


# This function's line numbers are in unit tests, try to not move it
# up or down. This is why it is up here at the beginning.
def print_log():
import logging
log = logging.getLogger()
log.debug('This is the debug log level')
log.info('This is the info log level')
log.warning('This is the warning log level')
log.critical('This is the critical log level')
log.error('This is the error log level')
print('This is printed')

# From here on the line number does not matter

import sys
import os
import json
import subprocess


def run_with(command, SAGE_BOOTSTRAP):
env = dict(os.environ)
env['SAGE_BOOTSTRAP'] = SAGE_BOOTSTRAP
proc = subprocess.Popen(
[sys.executable, __file__, command],
env=env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
)
out, err = proc.communicate()
return out.decode('ascii'), err.decode('ascii')


def run_config_with(SAGE_BOOTSTRAP):
out, err = run_with('print_config', SAGE_BOOTSTRAP)
assert not err, err
return json.loads(out)


def print_config():
from sage_bootstrap.config import Configuration
from sage_bootstrap.stdio import REAL_STDOUT, REAL_STDERR
config = Configuration()
result = dict(
log=config.log,
interactive=config.interactive,
stdout='default stdout' if sys.stdout == REAL_STDOUT else str(type(sys.stdout)),
stderr='default stderr' if sys.stderr == REAL_STDERR else str(type(sys.stderr)),
)
print(json.dumps(result))


def run_log_with(SAGE_BOOTSTRAP):
return run_with('print_log', SAGE_BOOTSTRAP)


commands = dict(
print_config=print_config,
print_log=print_log,
)


if __name__ == '__main__':
sys.path.insert(0,
os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import sage_bootstrap
commands[sys.argv[1]]()
64 changes: 64 additions & 0 deletions src/sage_bootstrap/test/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-

#*****************************************************************************
# Copyright (C) 2015 Volker Braun <vbraun.name@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# http://www.gnu.org/licenses/
#*****************************************************************************


import unittest
from sage_bootstrap.config import Configuration, LOG_LEVELS
from .runnable import run_config_with


class ConfigurationTestCase(unittest.TestCase):

def test_default(self):
"""
Test the default configuration
"""
config = Configuration()
self.assertEqual(config.log, 'info')
self.assertTrue(config.interactive)

def test_example(self):
"""
Test all ``SAGE_BOOTSTRAP`` settings
"""
SAGE_BOOTSTRAP=' loG:CrItIcAl, interactive:TRUE'
result = run_config_with(SAGE_BOOTSTRAP)
self.assertEqual(len(result), 4)
self.assertEqual(result['log'], u'critical')
self.assertTrue(result['interactive'])
self.assertEqual(result['stdout'], 'default stdout')
self.assertEqual(result['stderr'], 'default stderr')


def test_logging(self):
"""
Test that the different log levels are understood
"""
for level in LOG_LEVELS:
self.assertEqual(
run_config_with('LOG:{0}'.format(level.upper()))['log'],
level)

def test_logging(self):
"""
Test that overriding the isatty detection works
"""
interactive = run_config_with('interactive:true')
self.assertTrue(interactive['interactive'])
self.assertEqual(interactive['stdout'], 'default stdout')
self.assertEqual(interactive['stderr'], 'default stderr')
in_pipe = run_config_with('interactive:false')
self.assertFalse(in_pipe['interactive'])
self.assertEqual(in_pipe['stdout'], u"<class 'sage_bootstrap.stdio.UnbufferedStream'>")
self.assertEqual(in_pipe['stderr'], 'default stderr')


0 comments on commit f034a44

Please sign in to comment.