Code Monkey home page Code Monkey logo

declarative-parser's Introduction

Declarative Parser

Build Status Code Climate Coverage Status Documentation Status

Modern, declarative argument parser for Python 3.6+. Powerful like click, integrated like argparse, declarative as sqlalchemy. MIT licenced. Documented on RTD. Install with:

python3 -m pip install declarative_parser

As simple as argparse

It's built on top of argparse - everything you already know stays valid!

from declarative_parser import Parser, Argument

class MyParser(Parser):
    square = Argument(help='display a square of a given number')

parser = MyParser()
args = parser.parse_args()
print(args.square**2)

Nested and Parallel

Everyone knows about nested args. What about parallel groups?

supported_formats = ['png', 'jpeg', 'gif']

class InputOptions(Parser):
    path = Argument(type=argparse.FileType('rb'), optional=False)
    format = Argument(default='png', choices=supported_formats)

class OutputOptions(Parser):
    format = Argument(default='jpeg', choices=supported_formats)
    scale = Argument(type=int, default=100, help='Rescale image to %% of original size')

class ImageConverter(Parser):
    description = 'This app converts images'

    verbose = Argument(action='store_true')
    input = InputOptions()
    output = OutputOptions()

parser = ImageConverter()

commands = '--verbose input image.png output --format gif --scale 50'.split()

namespace = parser.parse_args(commands)

assert namespace.input.format == 'png'
assert namespace.output.format == 'gif'

Intelligent

Make use of Python 3 type hints to reduce tedious task of parsers writing to two or three lines. Positional, keyword arguments, type hints, docstrings - everything can be meaningfully transformed into a parser. And if you decide to take control, just overwrite the automatically deduced arguments with an Argument() defined as a class variable.

import argparse
from declarative_parser import Argument
from declarative_parser.constructor_parser import ConstructorParser

class MyProgram:

    database = Argument(
        type=argparse.FileType('r'),
        help='Path to file with the database'
    )

    def __init__(self, text: str, threshold: float=0.05, database=None):
        """My program does XYZ.

        Arguments:
          threshold: a floating-point value defining threshold, default 0.05
          database: file object to the database if any
        """
        print(text, threshold, None)

parser = ConstructorParser(MyProgram)

options = parser.parse_args()
program = parser.constructor(**vars(options))

And it works quite intuitively:

$ ./my_program.py test --threshold 0.6
test 0.6 None
$ ./my_program.py test --threshold f
usage: my_program.py [-h] [--database DATABASE] [--threshold THRESHOLD] text {} ...
my_program.py: error: argument --threshold: invalid float value: 'f'
$ ./my_program.py --threshold 0.6
usage: my_program.py [-h] [--database DATABASE] [--threshold THRESHOLD] text {} ...
my_program.py: error: the following arguments are required: text

Three docstring formats are supported: Google, NumPy and reStructuredText, with the default being Google.

PS. It works with functions too; see the documentation of FunctionParser.

Practical

What if you only want to show licence of your program? or version? Is there a need to write a separate logic? DeclarativeParser gives you utility decorator: @action which utilizes the power of argparse.Action, leaving behind the otherwise necessary boilerplate code.

__version__ = 2.0

import argparse
from declarative_parser import action
from declarative_parser.constructor_parser import ConstructorParser

class MyProgram:

    def __init__(self, threshold: float=0.05):
        """My program does XYZ.

        Arguments:
          threshold: a floating-point value, default 0.05
        """
        pass

    @action
    def version(options):
       print(__version__)

parser = ConstructorParser(MyProgram)

options = parser.parse_args()
program = parser.constructor(**vars(options))

The execution of an action will (by default) cause the program to exit immediately when finished.

See following run as example:

$ ./my_program.py --version
2.0

See more examples in the documentation.

declarative-parser's People

Contributors

krassowski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

declarative-parser's Issues

Can't get help of second sub parser

First off great project, I really like it but...
Playing around with your Nested and Parallel example I noticed that there is no way to get the help of the second sub parser:

  • If you run it like this ./example.py input image.png output -h assuming all else goes well you get the help of input, not output
  • If you run it without input i.e. ./example.py output -h it fails with the following exception: TypeError: %o format: an integer is required, not dict

Maybe I'm missing something but any help would be appreciated

Alternative ClassParsers

First of all, great project.

I would like to understand if it's possible to specify a "either this or that" for parsers.
That is, something similar to the choices argument parameter, but for parsers instead.

Here is my attempt at doing that:

from declarative_parser import Parser
from declarative_parser.constructor_parser import ClassParser


class A:
    def __init__(self, a='a'):
        self.a = a


class B:
    def __init__(self, b='b'):
        self.b = b


class MainParser(Parser):
    a = ClassParser(A)
    b = ClassParser(B)


if __name__ == '__main__':
    parser = MainParser()
    namespace = parser.parse_args()
    print(namespace)

I am aware this doesn't implement exclusion (you could specify a and b), but doing it in this way would be good enough for me (trust the user).

I expected this to happen:

$ python run.py a
Namespace(a=Namespace(a='a'), b=None)

and then I could check if a or b existed and figure out which the user chose.

However, this is the output I get:

$ python run.py a
Namespace(a=None, b=None)

So, the arguments aren't being loaded.
When an argument is mandatory, it actually works as intended, but I wanted that for some classes all the arguments were optional.

Is this something that can be done?

Make sure that Postponed Evaluation of Annotations (PEP 563) works with parser

Recently accepted PEP 563 paved the way for a more convenient way of creating self-referential annotations; it will be achieved by storing annotations as strings, so those will be only evaluated by type-checkers and other systems making actual use of those.

The declarative_parser should detect the use of postponed evaluation (enabled by future import in 3.7, default since 4.0) and evaluate the objects for type deduction for purposes of automated parser creation with FunctionParser and ClassParser.

Any way to specify short-only args?

Thanks for the project! It seems to be the best way to handle parallel commands at this time.

I haven’t found a way to specify short-only arguments, which I prefer for simple flags. This is partly for simplicity and partly because of the way argparse formats help messages (repeating metavar for both the long and short names, which is ugly!).

Consider integration with data classes (PEP 557)

As suggested by a mod of r/pythoncoding, it might be a good idea to make use of data classes as defined in recently accepted PEP 557. I would like to know if there is a demand for that before trying to implement it. It could look like:

class OutputOptions(Parser):
    format: Argument(choices=supported_formats) = 'jpeg'
    # or
    scale: Argument[int, 'Rescale image to % of original size'] = 100

NB. it would be only available with 3.7+ unless backports are provided.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.