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
Banking management project using Tkinter GUI in python.

Bank-Management Banking management project using Tkinter GUI in python. Packages required Tkinter - Tkinter is the standard GUI library for Python. sq

Anjali Kumawat 7 Jul 03, 2022
Windows symbol tables for Volatility 3

Windows Symbol Tables for Volatility 3 This repository is the Windows Symbol Table storage for Volatility 3. How to Use $ git clone https://github.com

JPCERT Coordination Center 31 Dec 25, 2022
Broken Link Finder is a Burp Extension to detect broken links for a passive scanning domains and links.

Broken Link Finder Broken Link Finder is a Burp Extension to detect broken links for a passive scanning domains and links. Inspired by InitRoot's link

Red Section 10 Sep 11, 2021
EFB Docker image with efb-telegram-master and efb-wechat-slave

efb-wechat-docker EFB Docker image with efb-telegram-master and efb-wechat-slave Features Container run by non-root user. Support add environment vari

Haukeng 1 Nov 10, 2022
LTGen provides classic algorithms used in Language Theory.

LTGen LTGen stands for Language Theory GENerator and provides tools to implement language theory. Command Line LTGen is a collection of tools to imple

Hugues Cassé 1 Jan 07, 2022
Reconhecimento de voz, em português, com python

Speech_recognizer Reconhecimento de voz, em português, com python O ato de falar nada mais é que criar vibrações no ar. Por meio de um conversor analó

Marcus Vinícius Ribeiro Andrade 1 Dec 14, 2021
Repo created for the purpose of adding any kind of programs and projects

Programs and Project Repository A repository for adding programs and projects of any kind starting from beginners level to expert ones Contributing to

Unicorn Dev Community 3 Nov 02, 2022
Practice in Oxford_AI&ML class

Practice in Oxford_AI&ML class

St3ve Lee 2 Feb 04, 2022
Senior Comprehensive Project For Python

Senior Comprehensive Project Author: Grey Hutchinson My project, which I nicknamed “Murmur”, was to create a research tool that would use neural netwo

1 May 29, 2022
An Android app that runs Elm in a webview. And a Python script to build the app or install it on the device.

Requirements You need to have installed: the Android SDK Elm Python git Starting a project Clone this repo and cd into it: $ git clone https://github.

Benjamin Le Forestier 11 Mar 17, 2022
Hello World in different languages !

Hello World And some Examples in different Programming Languages This repository contains a big list of programming languages and some examples for th

AmirHossein Mohammadi 131 Dec 26, 2022
Buggy script to play with GPOs

GPOwned /!\ This is a buggy PoC I made just to play with GPOs in my lab. Don't use it in production! /!\ The script uses impacket and ldap3 to update

45 Dec 15, 2022
Graphsignal Logger

Graphsignal Logger Overview Graphsignal is an observability platform for monitoring and troubleshooting production machine learning applications. It h

Graphsignal 143 Dec 05, 2022
Problem 5: Fermat near-misses

Problem 5: Fermat near-misses fermatnearmiss This is a script that computes fermat nearm misses when the -f option is set and requires users to input

CHRIS BYRON (Int0x80) 1 Jan 08, 2022
A program that analyzes data from inertia measurement units installeed in aircraft and generates g-exceedance curves

A program that analyzes data from inertia measurement units installeed in aircraft and generates g-exceedance curves

Pooya 1 Nov 23, 2021
A collection of resources on neural rendering.

awesome neural rendering A collection of resources on neural rendering. Contributing If you think I have missed out on something (or) have any suggest

1.8k Dec 30, 2022
An example using debezium and mysql with docker-compose

debezium-mysql An example using debezium and mysql with docker-compose The docker compose starts the Zookeeper, Kafka, Mysql and Debezium Connect. Aft

Horácio Dias Baptista Neto 4 May 21, 2022
Stori QA Automation Challenge

Stori-QA-Automation-Challenge This is the repository is created for the Stori QA Intern Automation Engineer Challenge! In this you can find the Requir

Daniel Castañeda 0 Feb 20, 2022
Cup Noodle Vending Maching Ordering Queue

Noodle-API Cup Noodle Vending Machine Ordering Queue Install dependencies in virtual environment python3 -m venv

Jonas Kazlauskas 1 Dec 09, 2021
p5 is a Python package based on the core ideas of Processing.

p5 p5 is a Python library that provides high level drawing functionality to help you quickly create simulations and interactive art using Python. It c

p5py 645 Jan 04, 2023