Basic infrastructure for writing scripts in Python

Overview

Base Script

Build Status PyPI version

Python is an excellent language that makes writing scripts very straightforward. Over the course of writing many scripts, we realized that we were doing some things over and over like creating a logger and accepting command line arguments. Base script is a very simple abstraction that takes care of setting up logging and other basics so you can focus on your application specific logic.

Here are some facilities that Base Script offers:

  • Logging
  • Accepting command-line arguments using argparse

Installation

pip install basescript

Usage

Here is a simple example to get started

Hello World

helloworld.py

from basescript import BaseScript

class HelloWorld(BaseScript):
    def run(self):
        print "Hello world"

if __name__ == '__main__':
    HelloWorld().start()

NOTE: all examples showcased here are available under the examples directory

Run the above by doing:

python helloworld.py run

Run script with log level set to DEBUG

python helloworld.py --log-level DEBUG run

Run script with custom log file

python helloworld.py --log-level DEBUG --log mylog run

Command line args, Using the logger

The following is a more involved example

adder.py

from basescript import BaseScript

class Adder(BaseScript):
    # The following specifies the script description so that it be used
    # as a part of the usage doc when --help option is used during running.
    DESC = 'Adds numbers'

    def __init__(self):
        super(Adder, self).__init__()
        self.a = 10
        self.b = 20

    def define_args(self, parser):
        parser.add_argument('c', type=int, help='Number to add')

    def run(self):
        self.log.info("Starting run of script ...")

        print self.a + self.b + self.args.c

        self.log.info("Script is done")

if __name__ == '__main__':
    Adder().start()

Run the script as follows and observe the usage information shown. Note how the description appears along with the c argument.

python adder.py --help
usage: adder.py [-h] [--name NAME] [--log-level LOG_LEVEL]
                [--log-format {json,pretty}] [--log-file LOG_FILE] [--quiet]
                [--metric-grouping-interval METRIC_GROUPING_INTERVAL]
                [--debug]
                {run} ...

Adds numbers

optional arguments:
  -h, --help            show this help message and exit
  --name NAME           Name to identify this instance
  --log-level LOG_LEVEL
                        Logging level as picked from the logging module
  --log-format {json,pretty}
                        Force the format of the logs. By default, if the
                        command is from a terminal, print colorful logs.
                        Otherwise print json.
  --log-file LOG_FILE   Writes logs to log file if specified, default: None
  --quiet               if true, does not print logs to stderr, default: False
  --metric-grouping-interval METRIC_GROUPING_INTERVAL
                        To group metrics based on time interval ex:10 i.e;(10
                        sec)
  --debug               To run the code in debug mode

commands:
  {run}
python adder.py run --help
usage: adder.py run [-h] c

positional arguments:
  c           Number to add

optional arguments:
  -h, --help  show this help message and exit

Run the script now to see the intended output

python adder.py run 30
60

Run the same with info and higher level logs enabled

python adder.py --log-level INFO 30
2016-04-10 13:48:27,356 INFO Starting run of script ...
60
2016-04-10 13:48:27,356 INFO Script is done

--log-level accepts all the values shown at https://docs.python.org/2/library/logging.html#logging-levels.

log is a log object created using python's standard logging module. You can read more about it at https://docs.python.org/2/library/logging.html.

Sub commands

When we have multiple functionalities then there is a way to call or execute each functionality with different sub command.

For example if we have add and subtract in the same script then we can call each functionality with different sub command.

calc.py

from basescript import BaseScript

class Calc(BaseScript):
    A = 10

    def add(self):
        print(self.A + self.args.b)

    def sub(self):
        print(self.A - self.args.b)

    def define_subcommands(self, subcommands):
        super(Calc, self).define_subcommands(subcommands)

        add_cmd = subcommands.add_parser("add", help="Adds number")
        add_cmd.set_defaults(func=self.add)
        add_cmd.add_argument('--b', type=int, help="Number to add")

        sub_cmd = subcommands.add_parser("sub", help="Subtracts number")
        sub_cmd.set_defaults(func=self.sub)
        sub_cmd.add_argument('--b', type=int, help="Number to subtract")

if __name__ == '__main__':
    Calc().start()

Run

$ python3 calc.py add --b 4
14
$ python3 calc.py sub --b 4
6

Metric-Grouping

When writing a Metric using self.log, you can specify type=metric. If this is done, a background thread will automatically group multiple metrics into one by averaging values (to prevent writing too many log lines). test.py

from basescript import BaseScript
import time
import random

class Stats(BaseScript):
    def __init__(self):
        super(Stats, self).__init__()

    def run(self):
        ts = time.time()
        while True:
            # Metric Format.
            self.log.info("stats", time_duration=(time.time()-ts), type="metric", random_number=random.randint(1, 50))

if __name__ == '__main__':
    Stats().start()

Run the command to see the output.

python test.py --metric-grouping-interval 5 run
Comments
  • #Feature: Auto json conversion using pipe(|).

    #Feature: Auto json conversion using pipe(|).

    Read-Me:

    • As per discussion with @prashanthellina while running the Basescript code intially it will write pretty format logs to the console. screen shot 2018-04-11 at 3 15 36 pm
    • If we specify pipe(|) after the command it must print json format to the console. ex: python test.py run | jq -C . | less -R
    Type: Enhancement inprogress-status meta-team moveto-inprogress-eod 
    opened by ghost 24
  • Basescript command with subcommand to produce template for writing new script

    Basescript command with subcommand to produce template for writing new script

    Additional requirement:

    We should consider generating an entire project hierarchy project tree layout (with template README.md, setup.py, travis config etc)

    This will make it easy for the user to make it easy to make the script pip installable and have command installed to standard bin location.

    medium-impact inprogress-status low-priority about-halfday-effort feature-request-type meta-team moveto-inprogress-3m 
    opened by prashanthellina 15
  • Bump pyyaml from 5.1.1 to 5.4

    Bump pyyaml from 5.1.1 to 5.4

    Bumps pyyaml from 5.1.1 to 5.4.

    Changelog

    Sourced from pyyaml's changelog.

    5.4 (2021-01-19)

    5.3.1 (2020-03-18)

    • yaml/pyyaml#386 -- Prevents arbitrary code execution during python/object/new constructor

    5.3 (2020-01-06)

    5.2 (2019-12-02)

    • Repair incompatibilities introduced with 5.1. The default Loader was changed, but several methods like add_constructor still used the old default yaml/pyyaml#279 -- A more flexible fix for custom tag constructors yaml/pyyaml#287 -- Change default loader for yaml.add_constructor yaml/pyyaml#305 -- Change default loader for add_implicit_resolver, add_path_resolver
    • Make FullLoader safer by removing python/object/apply from the default FullLoader yaml/pyyaml#347 -- Move constructor for object/apply to UnsafeConstructor
    • Fix bug introduced in 5.1 where quoting went wrong on systems with sys.maxunicode <= 0xffff yaml/pyyaml#276 -- Fix logic for quoting special characters
    • Other PRs: yaml/pyyaml#280 -- Update CHANGES for 5.1

    5.1.2 (2019-07-30)

    • Re-release of 5.1 with regenerated Cython sources to build properly for Python 3.8b2+
    Commits
    • 58d0cb7 5.4 release
    • a60f7a1 Fix compatibility with Jython
    • ee98abd Run CI on PR base branch changes
    • ddf2033 constructor.timezone: _copy & deepcopy
    • fc914d5 Avoid repeatedly appending to yaml_implicit_resolvers
    • a001f27 Fix for CVE-2020-14343
    • fe15062 Add 3.9 to appveyor file for completeness sake
    • 1e1c7fb Add a newline character to end of pyproject.toml
    • 0b6b7d6 Start sentences and phrases for capital letters
    • c976915 Shell code improvements
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    inprogress-status moveto-inprogress-1m dependencies 
    opened by dependabot[bot] 7
  • Add support for env file

    Add support for env file

    • User should be able to provide an env variable that will be appended to every logline

    refer: https://github.com/nudjur/infra/issues/307

    requirements

    • Add a command-line argument to accept the env file
    • env file will be a dictionary in YAML configuration
    • whenever a signal is received it should read the env variables from the <>.yaml file and update the log line
    inprogress-status moveto-inprogress-1h 
    opened by rajinish01 6
  • TypeError: init_logger() got an unexpected keyword argument 'pre_hooks'

    TypeError: init_logger() got an unexpected keyword argument 'pre_hooks'

    • we got the below issue when ran the sample hello_world.py which is in README
    • TypeError: init_logger() got an unexpected keyword argument 'pre_hooks' image
    • basescript version(0.3.3) image
    bug-type inprogress-status 
    opened by prasannababuAddagiri 6
  • Unittest does not run properly

    Unittest does not run properly

    Hi there,

    First, thank you for your package ;)

    I'm unfortunately running on an issue. I've written unit tests, and trying to run them, I'm getting this error :

    python -m unittest discover -s tests -p "*_tests.py"
    usage: python -m unittest [-h] [--name NAME] [--log-level LOG_LEVEL]
                              [--log-format {json,pretty}] [--log-file LOG_FILE]
                              [--quiet]
                              [--metric-grouping-interval METRIC_GROUPING_INTERVAL]
                              [--debug] [--minimal]
                              {run} ...
    python -m unittest: error: argument commands: invalid choice: 'discover' (choose from 'run')
    E
    ======================================================================
    ERROR: test_MY_SCRIPT_MY_FUNCTION (MY_SCRIPT_tests.MYSCRIPTSTests)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/miniconda/path/envs/bioinfo_sdk/lib/python3.7/argparse.py", line 1787, in parse_known_args
        namespace, args = self._parse_known_args(args, namespace)
      File "/miniconda/path/envs/bioinfo_sdk/lib/python3.7/argparse.py", line 1975, in _parse_known_args
        positionals_end_index = consume_positionals(start_index)
      File "/miniconda/path/envs/bioinfo_sdk/lib/python3.7/argparse.py", line 1952, in consume_positionals
        take_action(action, args)
      File "/miniconda/path/envs/bioinfo_sdk/lib/python3.7/argparse.py", line 1845, in take_action
        argument_values = self._get_values(action, argument_strings)
      File "/miniconda/path/envs/bioinfo_sdk/lib/python3.7/argparse.py", line 2386, in _get_values
        self._check_value(action, value[0])
      File "/miniconda/path/envs/bioinfo_sdk/lib/python3.7/argparse.py", line 2433, in _check_value
        raise ArgumentError(action, msg % args)
    argparse.ArgumentError: argument commands: invalid choice: 'discover' (choose from 'run')
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/my/home/path/tests/MY_SCRIPT_tests.py", line 13, in test_MY_SCRIPT_MY_FUNCTION
        MY_SCRIPT = StatsGtMatrix()
      File "/my/home/path/scripts/MY_SCRIPT.py", line 84, in __init__
        super(StatsGtMatrix, self).__init__()
      File "/miniconda/path/envs/bioinfo_sdk/lib/python3.7/site-packages/basescript/basescript.py", line 29, in __init__
        self.args = self.parser.parse_args(args=args)
      File "/miniconda/path/envs/bioinfo_sdk/lib/python3.7/argparse.py", line 1755, in parse_args
        args, argv = self.parse_known_args(args, namespace)
      File "/miniconda/path/envs/bioinfo_sdk/lib/python3.7/argparse.py", line 1794, in parse_known_args
        self.error(str(err))
      File "/miniconda/path/envs/bioinfo_sdk/lib/python3.7/argparse.py", line 2508, in error
        self.exit(2, _('%(prog)s: error: %(message)s\n') % args)
      File "/miniconda/path/envs/bioinfo_sdk/lib/python3.7/argparse.py", line 2495, in exit
        _sys.exit(status)
    SystemExit: 2
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.005s
    
    FAILED (errors=1)
    

    Here is a sample from my unittest file :

    import unittest
    from unittest.mock import MagicMock
    
    from scripts.my_script import MyScript, ClassA, ClassB
    
    class MyScriptTests(unittest.TestCase):
    
        _fake_A_classes = [ClassA('ped1'), ClassA('ped2')]
    
        _fake_B_class = ClassB('ped1','A','A', False, False)
    
        def test_my_script_my_function(self):
            my_script = MyScript()
            my_script.A_classes = \
            MagicMock(
                return_value=self._fake_A_classes
            )
    
            returned_ped = my_script.my_function(self._fake_B_class)
    
            self.assertEqual('ped1', returned_ped.name)
    

    and from my script :

    ...
    class MyScript(BaseScript):
        # The following specifies the script description so that it be used
        # as a part of the usage doc when --help option is used during running.
        DESC = """
        Usage
        """
    
        def __init__(self):
            #this set class attribute
            super(MyScript, self).__init__()
            self.custom_array = []
    
        def define_args(self, parser):
            parser.add_argument('arg_one', type=str, help='input file path')
    
        def my_function(self, class_a: ClassA):
        # function I'm trying to test
            return class_a
    ...
    

    It's not my real Classes, path, functions name.

    I'm wondering how to run unittest for a basescript script ?

    Any help would be appreciated.

    Best regards

    next-status 
    opened by tomraulet 5
  • Document: Logging guidelines

    Document: Logging guidelines

    Basescript supports structured logging based on structlog (https://www.structlog.org/en/stable/why.html). However, the documentation here does not list the capabilities and the best practices for logging (eg: every log is an event and the string should look like an identifier; values most not be encoded into this string but instead logged a kv pairs etc)

    ready-status meta-team moveto-inprogress-3m 
    opened by prashanthellina 5
  • #Feature: Explore click module in basescript.

    #Feature: Explore click module in basescript.

    Read-me:

    • The new click module read command line arguments with more features we have to understand more about it and then decide to explore it or not. Refer: http://click.pocoo.org/5/why/#
    ready-status meta-team moveto-inprogress-3m 
    opened by ghost 5
  • Can we have a

    Can we have a "logged_metric" type

    If we specify the type as "metric" basescript does grouping and also drops the "__fields", I want something wich will mention the type as "metric" and but not drop the "__fields".

    opened by supriyopaul 5
  • references #106, on receiveding SIGUSR1, reopening log file to enable…

    references #106, on receiveding SIGUSR1, reopening log file to enable…

    … external log rotation tools to work

    This fix ensures that when an external log rotation tool such as logrotate renames the current log file, it can inform the basescript based process by sending a SIGUSR1 signal. Upon receipt, basescript will now close the currently open log file and reopen it. This ensures that newly written log lines after the file move don't continue to go into the renamed file but instead to a file with the original file name.

    opened by prashanthellina 4
  • Multiprocessing with basescript

    Multiprocessing with basescript

    Multiprocessing is not working with basescript (method is not getting called). If I remove basescript then it is working.

    Multiprocessing with basescript

    from multiprocessing import Pool
    
    from basescript import BaseScript
    
    class VocabGen(BaseScript):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
        def process_single_file(self):
            processes = []
            pool = Pool()
            for i in range(10):
                p = pool.apply_async(self.sleep_two_secs)
                processes.append(p)
                start = i 
    
            for p in processes:
                p.wait()
    
        def sleep_two_secs(self):
            print("Came inside")
    
        def run(self):
            self.process_single_file()
    
    if __name__ == '__main__':
        VocabGen().start()
    

    RUN: python3 base.py run Output: Empty output. But exepected is "Came inside" should get print 10 times.

    Multiprocessing without basescript

    from multiprocessing import Pool
    
    class VocabGen():
        def __init__(self, *args, **kwargs):
            pass
    
        def process_single_file(self):
            processes = []
            pool = Pool()
            for i in range(10):
                p = pool.apply_async(self.sleep_two_secs)
                processes.append(p)
                start = i 
    
            for p in processes:
                p.wait()
    
        def sleep_two_secs(self):
            print("Came inside")
    
        def run(self):
            self.process_single_file()
    
    if __name__ == '__main__':
        v = VocabGen()
        v.run()
    

    RUN: python3 test.py Output: "Came inside" is getting print for 10 times.

    inprogress-status moveto-inprogress-1m 
    opened by RamanjaneyuluIdavalapati 4
Releases(0.3.9)
Owner
Deep Compute, LLC
Deep Compute, LLC
A comprensive software collection for nmea manipulation

nmeatoolkit A comprensive software collection for nmea manipulation; it includes a library and a collections of command line tools. Library pipes: con

Davide Gessa 1 Sep 14, 2022
Rock 💎 Paper 📝 Scissors ✂️ Lizard 🦎 Spock 🖖

Rock 💎 Paper 📝 Scissors ✂️ Lizard 🦎 Spock 🖖 If you’ve seen The Big Bang Theory, you’ve heard of a game called “Rock, Paper, Scissors, Lizard, Spoc

AmirHossein Mohammadi 16 Jun 19, 2022
A python script to run any executable and pass test cases to it's stdin and compare stdout with correct output.

quera_testcase_checker A python script to run any executable and pass test cases to it's stdin and compare stdout with correct output. proper way to u

k3y1 1 Nov 15, 2021
Zues Auto Claimer Leaked By bazooka#0001

Zues Auto Claimer Leaked By bazooka#0001 put proxies in prox.txt put ssid in sid.txt put all users you want to target in user.txt for the login just t

1 Jan 15, 2022
Chemical equation balancer

Chemical equation balancer Balance your chemical equations with ease! Installation $ git clone

Marijan Smetko 4 Nov 26, 2022
Izy - Python functions and classes that make python even easier than it is

izy Python functions and classes that make it even easier! You will wonder why t

5 Jul 04, 2022
Pipenv-local-deps-repro - Reproduction of a local transitive dependency on pipenv

Reproduction of the pipenv bug with transitive local dependencies. Clone this re

Lucas Duailibe 2 Jan 11, 2022
The worst and slowest programming language you have ever seen

VenumLang this is a complete joke EXAMPLE: fizzbuzz in venumlang x = 0

Venum 7 Mar 12, 2022
A joke conlang with minimal semantics

SyntaxLang Reserved Defined Words Word Function fo Terminates a noun phrase or verb phrase tu Converts an adjective block or sentence to a noun to Ter

Leo Treloar 1 Dec 07, 2021
Morth - Stack Based Programming Language

Morth WARNING! THIS LANGUAGE IS A WORKING PROGRESS. THIS IS JUST A HOBBY PROJECT

Dominik Danner 2 Mar 05, 2022
This directory gathers the tools developed by the Data Sourcing Working Group

BigScience Data Sourcing Code This directory gathers the tools developed by the Data Sourcing Working Group First Sourcing Sprint: October 2021 The co

BigScience Workshop 27 Nov 04, 2022
A performant state estimator for power system

A state estimator for power system. Turbocharged with sparse matrix support, JIT, SIMD and improved ordering.

9 Dec 12, 2022
Jannik Ramrath 1 Feb 05, 2022
This alerts you when the avalanche score a goal

This alerts you when the avalanche score a goal

Davis Burrill 1 Jan 15, 2022
Python: Wrangled and unpivoted gaming datasets. Tableau: created dashboards - Market Beacon and Player’s Shopping Guide.

Created two information products for GameStop. Using Python, wrangled and unpivoted datasets, and created Tableau dashboards.

Zinaida Dvoskina 2 Jan 29, 2022
Manipulation OpenAI Gym environments to simulate robots at the STARS lab

liegroups Python implementation of SO2, SE2, SO3, and SE3 matrix Lie groups using numpy or PyTorch. [Documentation] Installation To install, cd into t

STARS Laboratory 259 Dec 11, 2022
Certipy is a Python tool to enumerate and abuse misconfigurations in Active Directory Certificate Services (AD CS).

Certipy Certipy is a Python tool to enumerate and abuse misconfigurations in Active Directory Certificate Services (AD CS). Based on the C# variant Ce

ollypwn 1.3k Jan 01, 2023
Purge all transformation orientations addon for Blender 2.8 and newer versions

CTO Purge This add-on adds a new button to Blender's Transformation Orientation panel which empowers the user to purge all of his/her custom transform

MMMrqs 10 Dec 29, 2022
🛠️ Plugin to integrate Chuy with Poetry

Archived This is bundled with Chuy since v1.3.0. Poetry Chuy Plugin This plugin integrates Chuy with Poetry. Note: This only works in Poetry 1.2.0 or

Eliaz Bobadilla 4 Sep 24, 2021
calculadora financiera hecha en python

Calculadora financiera Calculadora de factores financieros basicos, puede calcular tanto factores como expresiones algebraicas en funcion de dichos fa

crudo 5 Nov 10, 2021