Inhzus

Official

Juq Technical Documentation


Some exellent technical practice in the development of Juq.

Decorators for functions in a module

def decarate_module(module, decorator: types.FunctionType):
    for name in dir(module):
        method = getattr(module, name)
        if isinstance(method, types.FunctionType):
            setattr(module, name, decorator(method))
    return module

Usage: e.g. Serialize all return values for functions in a module

dataclass

Reason

e.g.

def Example(object):
    def __init__(self, a: int, b: str, c: str, d: str, e: str, f: str, g: str, ...):
        self.a = a
        self.b = b
        self.c = c
        ...

Common method of constructor is a waste of time and code space.

Then I found

from collections import namedtuple

Example = namedtuple('Example', 'a b c d e f g')

However, namedtuple is not friendly to type hints. The method is complex and not elegent.

Finally

from dataclasses import dataclass

# @dataclass(repr=False)
@dataclass
class Example:
    a: int
    b: str
    c: str
    d: str
    e: str
    f: str
    g: str

Dataclass is excellent and supplies some basic function.

Filter function’s useless params

When using a series of functions with the same return value and similar parameters, it’s convenient to use a map to direct to different function on different demand. Ignorarable keyword arguments are useful here.

e.g.

>>> def apple(a, b, **_):
...     return '{}{}'.format(a, b)
...
>>> def blue(a, **_):
...     return a
...
>>> input_dict = {'func': 'blue', 'a': 'q', 'b': 'e'}
>>> globals()[input_dict['func']](**input_dict)
q
>>> input_dict['func'] = 'apple'
>>> globals()[input_dict['func']](**input_dict)
qe

Command line parser

Python supplies a module - argparse - which makes it easy to write user-friendly command-line interfaces.

In juq, I need to parse multiple nested sub-commands. Set sub-parser in a seprerated function is the best pratice imo. The example is as following:

import argparse


def set_user_parser(user_parser: argparse.ArgumentParser):
    _parser = user_parser.add_subparsers(dest='user', help='identifier: login/id')
    _parser.required = True

    info = _parser.add_parser('info', help='get user detailed info')
    info.add_argument('id_', help='user id, self by default', nargs='?',
                      default='', type=str, metavar='id')


def set_doc_parser(doc_parser: argparse.ArgumentParser):
    pass
    

parser = argparse.ArgumentParser()
sub_parser = parser.add_subparsers(dest='sub')
sub_parser.required = True

set_user_parser(sub_parser.add_parser('user', help='user help'))
set_doc_parser(sub_parser.add_parser('doc'))

parser.parse_args()

As the example above, some info should be noticed.

Subparser

To add sub parsers, it is a process which is similar to create a list: create an empty list firstly and fill in the list then.

parser = argparse.ArgumentParser()
sub_parsers = parser.add_subparsers()
first_sub_parser = sub_parsers.add_parser('first')

It seems that creating an empty subparser list is useless, however, it’s easy to configure parser with these steps.

Which subparser is used

dest parameter is used for this case. As the code showed above, specify dest parameter and we can get which subparser is used.

>>> parser = argparse.ArgumentParser()
>>> sub_parser = parser.add_subparsers(dest='sub')
>>> sub_parser.add_parser('user')
>>> parser.parse_args(['user'])`
Namespace(sub='user')

Argument without value

To set an argument without value, you can use:

>>> parser.add_argument('--public', '-p', action='store_const', const=1, default=0)
>>> parser.parse_args(['-p'])
Namespace(public=1)
>>> parser.parse_args([])
Namespace(public=0)

action parameter can be specified as: store_const, store_true, store_false. With specifying const parameter and default parameter, action parameter can be used as you want.