The CLI Flag System¤
devapps
uses absl flags to get configured via the CLI or
environ.
Application modules and packages define the flags they require or support within their source code themselves.
Dependent on which of these modules are imported at a certain time after startup (before the call to app.run
),
then these are the flags presented to the user when he calls -h|--help|--helpful|-hf
.
Set flag values are then globals throughout the application.
This makes a lot of sense when a package has a lot of varying use cases, with certain modules sometimes needed or not.
Note
It is allowed to do FLG.foo=bar
after startup - but considered bad practice.
Flag Definitions via Nested Class Trees¤
In devapps, while fully supporting the standard absl mechanics (flags.DEFINE_string
) we also allow to defined them in
class name spaces:
These are e.g. the flags of the project
module:
from devapp.app import FLG, app, run_app, do, system
class Flags:
# short codes built dynamically (conflict resolved) for all flags:
autoshort = '' # You could give a short code prefix here
# flag name:
class force:
# CLI help string:
n = 'Assume y on all questions. Required when started w/o a tty'
d = False # default
class init_at:
n = 'Set up project in given directory. env vars / relative dirs supported.'
d = ''
class init_create_unit_files:
n = 'List service unit files you want to have created for systemctl --user'
d = []
class init_resource_match:
n = 'Install only matching resources. Example: -irm "redis, hub"'
d = []
class list_resources_files:
n = 'Show available definition files.'
d = False
class edit_matching_resource_file:
n = 'Open resource files in $EDITOR, matching given string in their content'
d = ''
(...)
def run():
if FLG.init_at:
# structlog call:
app.info('Re-initializing project', location=FLG.init_at)
(...)
# if flags argument to run_app is given it will implicitly call devapp.tools.define_flags:
main = lambda: run_app(run, flags=Flags)
Full Control
module import does not cause flags already to be defined, the parent class is just a namespace without any magic.
Instead there is special call devapp.tools.define_flags(my_flags_class)
, which calls
absl.define_<type>
for any of the inner classes.
Handing the flags via the flags argument into run_app
will issue that call.
A call to help then lists the flags on the CLI:
-h
: Module Help
This lists the supported flags for the module whose main method is called:
$ ops project -h
[1;38;5;11mCreating A Project With Resources
[1;38;5;11m────────────────────────────────────────────────────────────────────────────────
[0m
This plugin is helper for creating a project directory, incl. required local
resources. Your system remains unchanged, except <project_dir> and
<conda_prefix>.
[0m
It provides an [0;38;5;5minstall[0m action (implicitely by providing the
--init_resource_match or --init_at switch)
[0m
Default action is: [0;38;5;5mlist[0m (show installable resources, -m <match> filters).
[0m
At install we will (re-)initialize a "project_dir", at location given with
--init_at (default: '.'), incl:
[0m
[0;38;5;5m❖ [0mInstalling available resources, like databases and tools within a given
directory (conda_prefix)
[0m
[0;38;5;5m❖ [0mCreating resource start wrappers in <project_dir>/bin
[0m
[0;38;5;5m❖ [0mGenerating default config when required
[0m
[0;38;5;5m❖ [0mOptionally generating systemd unit files (e.g. via: --init_create_all_units)
[0m
[0;38;5;5m❖ [0mInstances support: export <name>_instances=x before running and you'll get x
systemd units created, for startable commands.
[0;38;5;5m
[0;38;5;5m [0;38;5;5mExample: export client_instances=10; ops p -irm client -icau
[0;38;5;5m [0;38;5;5m(Name of a resource: ops p [-m <match>])[0m
[0m
[0;38;5;5m❖ [0mAny other parametrization: Via environ variables Check key environ vars in
list output and also doc text.
[0m
Privilege escalation is not required for any of these steps.
[0m
Main command line flags [matching [1;32mops_devapp.project[0m]:
[0;32mappc [1;33madd_post_process_cmd [0;31m''[0;38;5;245m
Add this to all commands which have systemd service units. Intended for output redirection. Not applied when stdout is a tty.
Example: -appc='2>&1 | rotatelogs -e -n1 "$logfile" 1M' ($logfile defined in wrapper -> use single quotes).
Tip: Use rotatelogs only on powers of 10 - spotted problems with 200M. Use 100M or 1G in that case.
[0;32mcp [1;33mconda_prefix [0;31m/home/runner/miniconda3[0;38;5;245m
Resources install location, except filesystem based ones. Env vars resolved.
Aliases:
- local|l: <project_dir>/conda
- default|d: $HOME/miniconda3 (default path of conda)
- current|c: Any current conda_prefix set when running.
Note: Installing resources outside the project keeps the project relocatable and resources reusable for other products.
[0;32mdamsu [1;33mdelete_all_matching_service_unit_files [0;31m''[0;38;5;245m
This removes all matching unit files calling devapp service wrappers. Say "service" to match all
[0;32mdi [1;33mdev_install [0;31mFalse[0;38;5;245m
Set the project up in developer mode - incl. make and poetry file machinery
[0;32memrf [1;33medit_matching_resource_file [0;31m''[0;38;5;245m
Open resource files in $EDITOR, matching given string in their content
[0;32mf [1;33mforce [0;31mFalse[0;38;5;245m
Assume y on all questions. Required when started w/o a tty
[0;32mfr [1;33mforce_reinstall [0;31mFalse[0;38;5;245m
Do not only install resources detected uninstalled but reinstall all
[0;32mfd [1;33mfs_dir [0;31mdefault[0;38;5;245m
Filesystem based resource location. Env vars resolved.
Aliases:
- local|l: <project_dir>/fs
- default|d: $HOME/miniconda3/fs (default path of conda)
- conda|c: Within conda_prefix/fs
[0;32mia [1;33minit_at [0;31m''[0;38;5;245m
Set up project in given directory. env vars / relative dirs supported. Sets install action implicitly
[0;32micau [1;33minit_create_all_units [0;31mFalse[0;38;5;245m
If set we create unit files for ALL service type resources
[0;32micuf [1;33minit_create_unit_files [0;31m''[0;38;5;245m
List service unit files you want to have created for systemctl --user. [0;38;5;241m
Valid: Entries in rsc.provides, rsc.cmd or rsc.exe (i.e. the filename of the wrapper in bin dir). Not: rsc.name.
[0;32mirm [1;33minit_resource_match [0;31m''[0;38;5;245m
Like resource match but implies install action
[0;32mi [1;33minstall [0;33mACTION[0;38;5;245m
Install
[0;32mis [1;33minstall_state [0;31mFalse[0;38;5;245m
show install state infos
[0;32ml [1;33mlist [1;33mACTION*[0;38;5;245m
Show available definition files.
[0;32mlrf [1;33mlist_resources_files [0;33mACTION[0;38;5;245m
Alias for list action
[0;32m [1;33mlog_resources_fully [0;31mFalse[0;38;5;245m
Always output all settings of resources when logging
[0;32mm [1;33mresource_match [0;31m''[0;38;5;245m
Provide a match string for actions. Examples: -irm "redis, hub" or -irm '!mysql, !redis' (! negates).
[0;32ms [1;33msystem [0;31m''[0;38;5;245m
Set to a server for system mode API (e.g. lc hub(s))
[36m-hf [match string][0m: List [36;1mALL[0m (matching) flags. E.g. -hf or -hf log.
[0m
-hf
: All Flags
-hf <match>
gives help for ALL flags imported, here with a match:
$ ops project -hf log | grep -A 100 'All supported'
All supported command line flags [matching [1;32mlog[0m]:
[1;34mabsl.logging
[0;32m [1;33malsologtostderr [0;31mFalse[0;38;5;245m
also log to stderr?
[0;32m [1;33mlog_dir [0;31m''[0;38;5;245m
directory to write logfiles into
[0;32m [1;33mlogger_levels [0;31m''[0;38;5;245m
Specify log level of loggers. The format is a CSV list of `name:level`. Where `name` is the logger name used with `logging.getLogger()`, and `level` is a level name (INFO, DEBUG, etc). e.g. `myapp.foo:INFO,other.logger:DEBUG`
[0;32m [1;33mlogtostderr [0;31mFalse[0;38;5;245m
Should only log to stderr?
[0;32m [1;33mshowprefixforinfo [0;31mTrue[0;38;5;245m
If False, do not prepend prefix to info messages when it's logged to stderr, --verbosity is set to INFO level, and python logging is used.
[0;32m [1;33mstderrthreshold [0;31mfatal[0;38;5;245m
log messages at this level, or more severe, to stderr in addition to the logfile. Possible values are 'debug', 'info', 'warning', 'error', and 'fatal'. Obsoletes --alsologtostderr. Using --alsologtostderr cancels the effect of this flag. Please also note that this flag is subject to --verbosity and requires logfile not be stderr.
[0;32mv [1;33mverbosity [0;31m-1[0;38;5;245m
Logging verbosity level. Messages logged at this level or lower will be included. Set to 1 for debug logging. If the flag was not set or supplied, the value will be changed from the default of -1 (warning) to 0 (info) after flags are parsed.
[1;34mdevapp.tools
[0;32mdasdi [1;33msensitive_data_identifiers [0;31mpass.*|.*secret.*[0;38;5;245m
Regexp which helps identify keys carrying sensitive information (for filtering out of logs). Case insensitive matching.
[1;34mstructlogging.sl
[0;32mlatn [1;33mlog_add_thread_name [0;31mFalse[0;38;5;245m
Add name of thread
[0;32mldcs [1;33mlog_dev_coljson_style [0;31mdark[0;38;5;245m
Pygments style for colorized json. To use the 16 base colors and leave it to the terminal palette how to render choose light or dark <abap|algol|algol_nu|arduino|autumn|borland|bw|colorful|dark|default|dracula|emacs|friendly|friendly_grayscale|fruity|gruvbox-dark|gruvbox-light|igor|inkpot|light|lilypond|lovelace|manni|material|monokai|murphy|native|one-dark|paraiso-dark|paraiso-light|pastie|perldoc|rainbow_dash|rrt|sas|solarized-dark|solarized-light|stata|stata-dark|stata-light|tango|trac|vim|vs|xcode|zenburn>
[0;32mldfc [1;33mlog_dev_fmt_coljson [0;31mjson,payload[0;38;5;245m
List of keys to log as json.
[0;32mlf [1;33mlog_fmt [0;31mauto[0;38;5;245m
Force a log format. 0: off, 1: auto, 2: plain, 3: plain_no_colors, 4: json. Note: This value can be set away from auto via export log_fmt as well.
[0;32mll [1;33mlog_level [0;31m20[0;38;5;245m
Log level (10: debug, 20: info, ...). You may also say log_level=error
[0;32mlsf [1;33mlog_stack_filter [0;31mfn not contains frozen and fn not contains /rx/[0;38;5;245m
When logging error tracebacks this is an optional filter. Keywords:fn: filename, frame: frame nr, line: line nr, name: name of callable [0;38;5;241m
Example: fn contains project and frame lt 1 (axiros/pycond expression)
[0;32mlsmf [1;33mlog_stack_max_frames [0;31m3[0;38;5;245m
Maximum Frames Shown in Terminal Stack Traces
[0;32mltln [1;33mlog_thread_local_names [0;31mFalse[0;38;5;245m
Prefer thread local logger_name, when set
[0;32mltf [1;33mlog_time_fmt [0;31m%m-%d %H:%M:%S[0;38;5;245m
Log time format. Shortcuts: "ISO", "dt"
[1;34mops_devapp.project
[0;32mappc [1;33madd_post_process_cmd [0;31m''[0;38;5;245m
Add this to all commands which have systemd service units. Intended for output redirection. Not applied when stdout is a tty.
Example: -appc='2>&1 | rotatelogs -e -n1 "$logfile" 1M' ($logfile defined in wrapper -> use single quotes).
Tip: Use rotatelogs only on powers of 10 - spotted problems with 200M. Use 100M or 1G in that case.
[0;32m [1;33mlog_resources_fully [0;31mFalse[0;38;5;245m
Always output all settings of resources when logging
[0m
Flag Types¤
$ cat /tmp/flagtest.py
#!/usr/bin/env python
from devapp.app import app, run_app, FLG
class Flags:
autoshort = '' # enabling short forms, prefixed with '', i.e. not prefixed
class my_bool:
d = False
class my_float:
d = 1.1
class my_int:
d = 1
class my_multi_str:
t = 'multi_string' # can supply more than once, any value
class my_list:
s = 'x' # non auto short form
t = list # comma sepped values into a list
d = 'foo, bar'
class my_opt:
t = ['foo', 'bar'] # can pick exactly one within the list (enum)
d = 'foo'
class my_opt_multi:
t = ('a', 'b', 'c') # now we can select more than one within the tuple (multi_enum)
d = 'a'
class my_str:
d = 'foo' # most easy way
class my_str_detailed:
'''Detailed help'''
n = '''Options (multiline help)
- opt1: foo
- opt2: bar
'''
s = False # disable short
d = 'opt1'
class my_condition:
# will be parsed into an axiros/pycond filter, incl. the condition (list).
t = 'pycond'
d = 'fn not contains frozen and fn not contains /rx/'
# Print out all FLG vals.
# Normal (global) app access e.g. like : if FLG.my_int > 42:
flg = lambda: [(k, getattr(FLG, k)) for k in FLG if k.startswith('my_')]
run = lambda: app.info('Flag values (CLI over defaults):', json=flg())
if __name__ == '__main__':
# supplying the flags keyword implicitly calls devapp.tools.define_flags on them:
run_app(run, flags=Flags)
With this
$ /tmp/flagtest.py -h
[1;34m__main__
[0;32mmb [1;33mmy_bool [0;31mFalse [0;38;5;245mMy_bool
[0;32mmc [1;33mmy_condition [0;31mfn not.. [0;38;5;245mMy_condition (axiros/pycond expression)
[0;32mmf [1;33mmy_float [0;31m1.1 [0;38;5;245mMy_float
[0;32mmi [1;33mmy_int [0;31m1 [0;38;5;245mMy_int
[0;32mx [1;33mmy_list [0;31mfoo,bar [0;38;5;245mMy_list
[0;32mmms [1;33mmy_multi_str [0;31m[''] [0;38;5;245mMy_multi_str; repeat this option to specify..
[0;32mmo [1;33mmy_opt [0;31mfoo [0;38;5;245m My_opt<foo|bar>
[0;32mmom [1;33mmy_opt_multi [0;31m['a'] [0;38;5;245mMy_opt_multi; repeat this option to specify..
[0;32mms [1;33mmy_str [0;31mfoo [0;38;5;245mMy_str
[0;32m [1;33mmy_str_detailed [0;31mopt1 [0;38;5;245mOptions (multiline help) - opt1: foo - opt2:..
[36m-hf [match string][0m: List [36;1mALL[0m (matching) flags. E.g. -hf or -hf log.
[0m
$ /tmp/flagtest.py -mo baz || true
FATAL Flags parsing error: flag --my_opt=baz: value should be one of <foo|bar>
Pass --helpshort or --helpfull to see help on flags.
$ /tmp/flagtest.py -ms a -mb -mf 42.1 -mi 42 -mms a -mms b -mo bar -mom b -mom c -x a,b -ms b -lf plain
[2;38;5;5m07-25 16:06:03[0m [[38;5;2minfo [0m] Flag values (CLI over defaults):[0m [[38;5;5mflagtest[0m] [1;32mjson[0m=[1;38;5;11m[0m[[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"my_list"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"a"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[33m"b"[39;49;00m[90m[39;49;00m
[90m [39;49;00m][90m[39;49;00m
[90m [39;49;00m],[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"my_bool"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[94mtrue[39;49;00m[90m[39;49;00m
[90m [39;49;00m],[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"my_condition"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"<function build_pycond_flag_expr.<locals>.<lambda> at 0x7fcc09a830e0>"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[33m"fn not contains frozen and fn not contains /rx/"[39;49;00m[90m[39;49;00m
[90m [39;49;00m][90m[39;49;00m
[90m [39;49;00m],[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"my_float"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[94m42.1[39;49;00m[90m[39;49;00m
[90m [39;49;00m],[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"my_int"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[94m42[39;49;00m[90m[39;49;00m
[90m [39;49;00m],[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"my_multi_str"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"a"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[33m"b"[39;49;00m[90m[39;49;00m
[90m [39;49;00m][90m[39;49;00m
[90m [39;49;00m],[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"my_opt"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[33m"bar"[39;49;00m[90m[39;49;00m
[90m [39;49;00m],[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"my_opt_multi"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"b"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[33m"c"[39;49;00m[90m[39;49;00m
[90m [39;49;00m][90m[39;49;00m
[90m [39;49;00m],[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"my_str"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[33m"b"[39;49;00m[90m[39;49;00m
[90m [39;49;00m],[90m[39;49;00m
[90m [39;49;00m[[90m[39;49;00m
[90m [39;49;00m[33m"my_str_detailed"[39;49;00m,[90m[39;49;00m
[90m [39;49;00m[33m"opt1"[39;49;00m[90m[39;49;00m
[90m [39;49;00m][90m[39;49;00m
][90m[39;49;00m
[0m
Hint
Note my_str
defined twice in the example - last wins (except when defined multi_string
) -> you can preparametrize apps in wrappers
and still overwrite flags when calling the wrapper.
E.g. in the wrapper you have -ll 20 while in the call you say -ll 10 to have debug logging for a certain run.
Environ Flags¤
Adding --environ_flags
causes the app to check the process environ first(!), for any flag value.
Setting project directory and log level via environ
$ export init_at="$HOME/foo"; export log_level=30
$
$ ops project -ia /tmp --environ_flags
[31m╭───────────────────── [1mTraceback [2m(most recent call last)[0m[31m[49m ──────────────────────╮
│[39m [31m│
│[39m [2m[33m/home/runner/work/devapps/devapps/src/devapp/[0;1m[33m[49mapp.py[0m[39m[49m:[94m509[39m in [92mrun_phase_2[39m [31m│
│[39m [31m│
│[39m [2m506 │ │ │ # main = lambda: run_app(Action, flags=Flags, wrapper=clea[0m[39m[49m [31m│
│[39m [2m507 │ │ │ [0m[94m[49mif[39m FLG.dirwatch: [31m│
│[39m [2m508 │ │ │ │ [0m[39m[49msignal.signal(reload_signal, reload_handler) [31m│
│[39m [31m❱ [39m509 [2m│ │ │ [0m[39m[49mres = wrapper(main) [94mif[39m wrapper [94melse[39m main() [31m│
│[39m [2m510 │ │ │ [0m[94m[49mif[39m FLG.dirwatch: [31m│
│[39m [2m511 │ │ │ │ [0m[39m[49mapp.info([33m'Keep running, dirwatch is set'[39m) [31m│
│[39m [2m512 │ │ │ │ [0m[94m[49mwhile[39m [94m1[39m: [31m│
│[39m [31m│
│[39m [33m╭───────────────────────────────── locals ─────────────────────────────────╮[39m [31m│
│[39m [33m│[39m args = [1m[[0m[39m[49m [33m│[39m [31m│
│[39m [33m│[39m [2m│ [0m[39m[49m [33m│[39m [31m│
│[39m [33m│[39m [33m'/home/runner/miniconda3/envs/devapps_py3.7/bin/ops'[39m, [33m│[39m [31m│
│[39m [33m│[39m [2m│ [0m[33m[49m'project'[39m [33m│[39m [31m│
│[39m [33m│[39m [1m][0m[39m[49m [33m│[39m [31m│
│[39m [33m│[39m ex = [1m[35mDieNow[39m([0m[33m[49m'Not exists'[39m, [1m{[0m[33m[49m'dir'[39m: [33m'/home/runner/foo'[1m[39m})[0m[39m[49m [33m│[39m [31m│
│[39m [33m│[39m flags_validator = [94mNone[39m [33m│[39m [31m│
│[39m [33m│[39m kw_log = [1m{}[0m[39m[49m [33m│[39m [31m│
│[39m [33m│[39m log = [1m<[35mAXLogger[39m([0m[33m[49mcontext[39m=[1m{}[0m[39m[49m, [33mprocessors[39m=[1m[[0m[39m[49m<function [33m│[39m [31m│
│[39m [33m│[39m filter_by_level at [94m0x7fa9487c0290[1m[39m>[0m[39m[49m, [33m│[39m [31m│
│[39m [33m│[39m [1m<[95mstructlog.processors.TimeStamper[0m[39m[49m object at [33m│[39m [31m│
│[39m [33m│[39m [94m0x7fa948329cd0[1m[39m>[0m[39m[49m, [1m<[95mfunction[0m[39m[49m add_log_level at [33m│[39m [31m│
│[39m [33m│[39m [94m0x7fa9489599e0[1m[39m>[0m[39m[49m, [1m<[95mfunction[0m[39m[49m censor_passwords at [33m│[39m [31m│
│[39m [33m│[39m [94m0x7fa9487c0320[1m[39m>[0m[39m[49m, [1m<[95mfunction[0m[39m[49m add_logger_name at [33m│[39m [31m│
│[39m [33m│[39m [94m0x7fa9487c03b0[1m[39m>[0m[39m[49m, [1m<[95mfunction[0m[39m[49m [33m│[39m [31m│
│[39m [33m│[39m stack_info.<locals[1m>[0m[39m[49m._stack_info at [94m0x7fa949f2a440[39m>, [33m│[39m [31m│
│[39m [33m│[39m [1m<[95mstructlogging.renderers.ThemeableConsoleRenderer[0m[39m[49m [33m│[39m [31m│
│[39m [33m│[39m object at [94m0x7fa94869b9d0[1m[39m>])[0m[39m[49m> [33m│[39m [31m│
│[39m [33m│[39m main = [1m<[95mfunction[0m[39m[49m run at [94m0x7fa9486888c0[1m[39m>[0m[39m[49m [33m│[39m [31m│
│[39m [33m│[39m name = [33m'project'[39m [33m│[39m [31m│
│[39m [33m│[39m post = [94mNone[39m [33m│[39m [31m│
│[39m [33m│[39m res = [94mNone[39m [33m│[39m [31m│
│[39m [33m│[39m watcher_pid = [94mNone[39m [33m│[39m [31m│
│[39m [33m│[39m wrapper = [94mNone[39m [33m│[39m [31m│
│[39m [33m╰──────────────────────────────────────────────────────────────────────────╯[39m [31m│
│[39m [2m[33m/home/runner/work/devapps/devapps/src/devapp/plugins/ops_devapp/project/[0;1m[33m[49m__in[0m[39m[49m [31m│
│[39m [1m[33mit__.py[0m[39m[49m:[94m301[39m in [92mrun[39m [31m│
│[39m [31m│
│[39m [2m298 │ [0m[39m[49md = os.path.abspath(d) [31m│
│[39m [2m299 │ [0m[39m[49md = d[:-[94m1[39m] [94mif[39m d.endswith([33m'/'[39m) [94melse[39m d [31m│
│[39m [2m300 │ [0m[94m[49mif[39m [95mnot[39m exists(d): [31m│
│[39m [31m❱ [39m301 [2m│ │ [0m[39m[49mapp.die([33m'Not exists'[39m, [96mdir[39m=d) [31m│
│[39m [2m302 │ [0m[39m[49mdo(os.chdir, d) [31m│
│[39m [2m303 │ [0m[39m[49md = FLG.init_at = os.path.abspath([33m'.'[39m) [31m│
│[39m [2m304 │ [0m[39m[49mproject.set_project_dir([96mdir[39m=d) [31m│
│[39m [31m│
│[39m [33m╭──────── locals ────────╮[39m [31m│
│[39m [33m│[39m d = [33m'/home/runner/foo'[39m [33m│[39m [31m│
│[39m [33m│[39m m = [33m''[39m [33m│[39m [31m│
│[39m [33m╰────────────────────────╯[39m [31m│
│[39m [31m│
│[39m [2m[33m/home/runner/work/devapps/devapps/src/devapp/[0;1m[33m[49mapp.py[0m[39m[49m:[94m166[39m in [92mdie[39m [31m│
│[39m [31m│
│[39m [2m163 │ [0m[39m[49m [31m│
│[39m [2m164 │ [0m[94m[49mdef[39m [92mdie[39m(msg, **kw): [31m│
│[39m [2m165 │ │ [0m[33m[49m"""Application decided to bail out"""[39m [31m│
│[39m [31m❱ [39m166 [2m│ │ [0m[94m[49mraise[39m DieNow(msg, kw) [31m│
│[39m [2m167 │ [0m[39m[49m [31m│
│[39m [2m168 │ [0m[39m[49mapp.die = die [31m│
│[39m [2m169 │ [0m[39m[49mapp.name = name [31m│
│[39m [31m│
│[39m [33m╭───────────── locals ──────────────╮[39m [31m│
│[39m [33m│[39m kw = [1m{[0m[33m[49m'dir'[39m: [33m'/home/runner/foo'[1m[39m}[0m[39m[49m [33m│[39m [31m│
│[39m [33m│[39m msg = [33m'Not exists'[39m [33m│[39m [31m│
│[39m [33m╰───────────────────────────────────╯[39m [31m│
╰──────────────────────────────────────────────────────────────────────────────╯
[1m[91mDieNow: [39m([0m[32m[49m'Not exists'[39m, [1m{[0m[32m[49m'dir'[39m: [32m'/home/runner/foo'[1m[39m})
[0;2m[38;5;5m[49m07-25 16:06:09[0m[39m[49m [[1m[38;5;124merror [0m[39m[49m] Not exists [[38;5;5mproject[39m] [1m[32mdir[0m[39m[49m=[1m[38;5;11m/home/runner/foo
[0m[39m[49m$
Environ over CLI
Please note again that the environ value does overwrite the CLI value, when environ_flags
is
explicitly set. On the cli, you'd have to use the unset
command first.
Flagsets¤
You can store full sets of flags in files and refer to them via the absl standard --flagfile=....
flag.
Using Flags in pytest
¤
When the started process is pytest, then the environ_flags
flag is set to true. Means you can export non default flag
values before starting pytest like so:
export my_flag=myval && pytest -xs test/test_my_test.py
Action Flags¤
Example:
$ cat /tmp/action_flagtest.py
#!/usr/bin/env python
from devapp.app import app, run_app, FLG
class Flags:
autoshort=''
class force:
d = False
class Actions:
class install:
d = False
class verbose:
s = 'iv' # no auto for nested flags
d = False
class run:
d = True # default
class ActionNS:
def _pre():
print('pre')
def _post():
print('post')
def run():
print('running', FLG.force)
def install():
print('installing', FLG.force, FLG.install_verbose)
if __name__ == '__main__':
run_app(ActionNS, flags=Flags)
Test it:
$ /tmp/action_flagtest.py -h
[1;34m__main__
[0;32mf [1;33mforce [0;31mFalse [0;38;5;245mForce
[0;32mi [1;33minstall [0;33mACTION [0;38;5;245mInstall
[0;32mr [1;33mrun [1;33mACTION* [0;38;5;245mRun
[36m-hf [match string][0m: List [36;1mALL[0m (matching) flags. E.g. -hf or -hf log.
[0m
$ /tmp/action_flagtest.py
pre
running False
post
$ /tmp/action_flagtest.py install -iv
pre
installing False True
post
$ /tmp/action_flagtest.py -f install --install_verbose
pre
installing True True
post
$ /tmp/action_flagtest.py run --install_verbose=True || true
FATAL Flags parsing error: Unknown command line flag 'install_verbose'
Pass --helpshort or --helpfull to see help on flags.
Mind the concatenation of action and flag name for the nested property verbose within def install()
action.