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
Jarvis Python BOT acts like Google-assistance

Jarvis-Python-BOT Jarvis Python BOT acts like Google-assistance Setup Add Mail ID (Gmail) in the file at line no 82.

Ishan Jogalekar 1 Jan 08, 2022
pyreports is a python library that allows you to create complex report from various sources

pyreports pyreports is a python library that allows you to create complex reports from various sources such as databases, text files, ldap, etc. and p

Matteo Guadrini aka GU 78 Dec 13, 2022
Simple project to assist in tracking/logging my working hours

Fill working hours Basic script to assist in the logging/tracking of my working hours How it works Create a file called projects.json in this director

Robin Kennedy-Reid 2 Oct 31, 2022
Would upload anything I do with/related to brainfuck

My Brainfu*k Repo Basically wanted to create something with Brainfu*k but realized that with the smol brain I have, I need to see the cell values real

Rafeed 1 Mar 22, 2022
Run CodeServer on Google Colab using Inlets in less than 60 secs using your own domain.

Inlets Colab Run CodeServer on Colab using Inlets in less than 60 secs using your own domain. Features Optimized for Inlets/InletsPro Use your own Cus

2 Dec 30, 2021
NExT-Ford-aula4 - NExT Ford aula4

Questão 1: vocês deveram fazer o passo a passo de como ficará as pilhas(Stack) e

Gerson 1 Jan 06, 2022
Possible solutions to Wordscapes, a mobile game for the android operating system, downloadable from the play store

Possible solutions to Wordscapes, a mobile game for the android operating system, downloadable from the play store

Clifford Onyonka 2 Feb 23, 2022
A jokes python module

Made with Python3 (C) @FayasNoushad Copyright permission under MIT License License - https://github.com/FayasNoushad/Jokes/blob/main/LICENSE Deploy

Fayas Noushad 3 Nov 28, 2021
Fetch PRs from GitHub and analyze which ones are unmergeable

Set up token Generate a personal access token on GitHub. Add repo permissions. export GH_TOKEN="abcdefg" Pull PR data make Usually, GitHub doesn't h

Stefan van der Walt 1 Nov 05, 2021
A streaming animation of all the edits to a given Wikipedia page.

WikiFilms! What is it? A streaming animation of all the edits to a given Wikipedia page. How it works. It works by creating a "virtual camera," which

Tal Zaken 2 Jan 18, 2022
Repo contains Python Code Reference to learn Python in a week, It also contains Machine Learning Algorithms and some examples for Practice, Also contains MySql, Tableau etc

DataScience_ML_and_Python Repo contains Python Code Reference to learn Python in a week, It also contains Machine Learning Algorithms and some example

Meerabo D Shah 1 Jan 17, 2022
Python program to start your zoom meetings

zoomstarter Python programm to start your zoom meetings More about Initially this was a bash script for starting zoom meetings, but as i started devel

Viktor Cvetanovic 2 Nov 24, 2021
PREFS is a Python library to store and manage preferences and settings.

PREFS PREFS is a Python library to store and manage preferences and settings. PREFS stores a Python dictionary in a total human-readable file, the PRE

Pat 13 May 26, 2022
tidevice can be used to communicate with iPhone device

h 该工具能够用于与iOS设备进行通信, 提供以下功能 截图 获取手机信息 ipa包的安装和卸载 根据bundleID 启动和停止应用 列出安装应用信息 模拟Xcode运行XCTest,常用的如启动WebDriverAgent测试

Alibaba 1.8k Dec 30, 2022
Hacktoberfest 2021 contribution repository✨

🎃 HacktoberFest-2021 🎃 Repository for Hacktoberfest Note: Although, We are actively focusing on Machine Learning, Data Science and Tricky Python pro

Manjunatha Sai Uppu 42 Dec 11, 2022
combs is a package used to generate all possible combinations of a given length k on a given set.

The package combs is a package used to generate all possible combinations of a given length k on a given set. The set is given as a list, and k must b

1 Dec 24, 2021
Python bindings for `ign-msgs` and `ign-transport`

Python Ignition This project aims to provide Python bindings for ignition-msgs and ignition-transport. It is a work in progress... C++ and Python libr

Rhys Mainwaring 3 Nov 08, 2022
Script for resizing MTD partitions on a QNAP device in order to be available to upgrade from buster to bullseye

QNAP partitions resize for kirkwood devices. As explained by Marin Michlmayr, Debian bullseye support on kirkwood QNAP devices was dropped due to [mai

Arnaud Mouiche 26 Jan 05, 2023
The LiberaPay archive module for the SeanPM life archive project.

By: Top README.md Read this article in a different language Sorted by: A-Z Sorting options unavailable ( af Afrikaans Afrikaans | sq Shqiptare Albania

Sean P. Myrick V19.1.7.2 1 Aug 26, 2022
A site that went kinda viral that lets you put Bernie Sanders in places

Bernie In Places An app that accidentally went viral! Read the story in WIRED here Install First, create a python virtual environment, and install all

310 Aug 22, 2022