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
Telegram bot to upload media to telegra.ph

Telegraph @StarkTelegraphBot A star ⭐ from you means a lot to us ! Telegram bot to upload media to telegra.ph Usage Deploy to Heroku Tap on above butt

Stark Bots 24 Dec 29, 2022
A bunch of codes for procedurally modeling and texturing futuristic cities.

Procedural Futuristic City This is our final project for CPSC 479. We created a procedural futuristic city complete with Worley noise procedural textu

1 Dec 22, 2021
En este repositorio realizaré la tarea del laberinto.

Laberinto Perfil de GitHub del autor de este proyecto: @jmedina28 En este repositorio queda resuelta la composición de un laberinto 5x5 con sus muros

Juan Medina 1 Dec 11, 2021
A comparison of mesh generators.

This repository creates meshes of the same domains with multiple mesh generators and compares the results.

Nico Schlömer 29 Dec 12, 2022
A simple solution for water overflow problem in Python

Water Overflow problem There is a stack of water glasses in a form of triangle as illustrated. Each glass has a 250ml capacity. When a liquid is poure

Kris 2 Oct 22, 2021
Download and archive entire usenet newsgroups over NNTP.

Usenet Archiving Tool This code is for archiving Usenet discussions, not downloading files. Newsgroup posts are saved under the authors name and email

Corey White 2 Dec 23, 2021
This program is meant to take the pain out of generating nice bash PS1 prompts.

TOC PS1 Installation / Quickstart License Other Docs Examples PS1 Command Help PS1 ↑ This program is meant to take the pain out of generating nice bas

Steven Hollingsworth 6 Jun 19, 2022
プレヤフHackUチーム「キャット・タン」が作成したアプリ「illustection」

cat_tongue_illustection プレヤフHackUチーム「キャット・タン」が作成した, プライバシー保護アプリ「illustection」です! デモ動画 https://youtu.be/z3I7LuB_i58 機能 アップロードされた画像をいい感じのイラストやの素材に置き換える(

4 Jul 03, 2021
A Bot Which Can generate Random Account Based On Your Hits.

AccountGenBot This Bot Can Generate Account With Hits You Save (Randomly) Keyfeatures Join To Use Support Limit Account Generation Using Sql Customiza

DevsExpo 30 Oct 21, 2022
A general purpose low level programming language written in Python.

A general purpose low level programming language written in Python. Basal is an easy mid level programming language compiling to C. It has an easy syntax, similar to Python, Rust etc.

Snm Logic 6 Mar 30, 2022
Sample python script for monitoring Rocketchat database and get statistics of users.

rocketchat-DB-monitoring Sample python script for monitoring Rocketchat database and get statistics of users. 1. Update python: yum check-update && yu

Mojtaba Taleghani 1 Apr 12, 2022
Commodore 64 OS running on Atari 8-bit hardware

This is the Commodre 64 KERNAL, modified to run on the Atari 8-bit line of computers. They're practically the same machine; why didn't someone try this 30 years ago?

Nick Bensema 133 Nov 12, 2022
Painel simples com consulta de cep,CNPJ,placa e ip

Painel mpm Um painel simples com consultas de IP, CNPJ, CEP e PLACA Início 🌐 apt update && apt upgrade -y pkg i python git pip install requests Insta

8 Feb 27, 2022
→ Plantilla de registro para Python

🔧 Pasos Necesarios CMD 🖥️ SOCKETS pip install sockets 🎨 COLORAMA pip install colorama 💻 Código register-by-inputs from turtle import color # Impor

Panda.xyz 4 Mar 12, 2022
Online HackerRank problem solving challenges

LinkedListHackerRank Online HackerRank problem solving challenges This challenge is part of a tutorial track by MyCodeSchool You are given the pointer

Sefineh Tesfa 1 Nov 21, 2021
sfgp is a package that aggregates individual scripts and notebooks, primarily written for the basic analysis tasks of genetics and pharmacogenomics data.

sfgp is a package that aggregates individual scripts and notebooks, primarily written for the basic analysis tasks of genetics and pharmacogenomics data.

Vishal Sarsani 1 Mar 31, 2022
Semester long, web application project for CSCI 4370/6370 (Database Management)

Database_Project Prototype ideas for website: Computer Science library (Sells books, products, etc.) Code editor Graph visualizer / creator (can save

Jordan Harman 4 Feb 17, 2022
Edorado93 - Unraveling a Rockstar! -- Too much? Fine, Unraveling a humble programmer then?

Hi, I'm Sachin Malhotra ( ⛄ 💻 🎃 🍺 ) Let me set the records straight. Roger Federer is the GOAT and I will not hear otherwise! Now that we have that

Sachin Malhotra 7 Dec 25, 2022
Hopefully it'll become a very annoying desktop pet

AnnoyingPet Basic Tutorial: https://seebass22.github.io/python-desktop-pet-tutorial/ Handling Mouse Input: https://pythonhosted.org/pynput/mouse.html

1 Jun 08, 2022
An Notifier Program that Notifies you to relax your eyes Every 15 Minutes👀

Every 15 Minutes ⌛ Every 15 Minutes is an application that is used to Notify you to Relax your eyes Every 15 Minutes, This is fully made with Python a

FSP Gang s' YT 2 Oct 18, 2021