Skip to content

Commit e11ac63

Browse files
committed
fixes #137
1 parent 7ed8d0c commit e11ac63

File tree

3 files changed

+111
-66
lines changed

3 files changed

+111
-66
lines changed

fastcore/_nbdev.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,10 @@
178178
"delegates": "06_meta.ipynb",
179179
"method": "06_meta.ipynb",
180180
"funcs_kwargs": "06_meta.ipynb",
181-
"Param": "07_script.ipynb",
181+
"store_true": "07_script.ipynb",
182+
"store_false": "07_script.ipynb",
182183
"bool_arg": "07_script.ipynb",
184+
"Param": "07_script.ipynb",
183185
"anno_parser": "07_script.ipynb",
184186
"args_from_prog": "07_script.ipynb",
185187
"call_parse": "07_script.ipynb"}

fastcore/script.py

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/07_script.ipynb (unless otherwise specified).
22

3-
__all__ = ['Param', 'bool_arg', 'anno_parser', 'args_from_prog', 'call_parse']
3+
__all__ = ['store_true', 'store_false', 'bool_arg', 'Param', 'anno_parser', 'args_from_prog', 'call_parse']
44

55
# Cell
66
import inspect,functools
@@ -9,23 +9,14 @@
99
from .utils import *
1010

1111
# Cell
12-
class Param:
13-
"A parameter in a function used in `anno_parser` or `call_parse`"
14-
def __init__(self, help=None, type=None, opt=True, action=None, nargs=None, const=None,
15-
choices=None, required=None):
16-
self.help,self.type,self.opt,self.action,self.nargs = help,type,opt,action,nargs
17-
self.const,self.choices,self.required = const,choices,required
18-
19-
def set_default(self, d):
20-
if d==inspect.Parameter.empty: self.opt = False
21-
else:
22-
self.default = d
23-
self.help += f" (default: {d})"
12+
def store_true():
13+
"Placeholder to pass to `Param` for `store_true` action"
14+
pass
2415

25-
@property
26-
def pre(self): return '--' if self.opt else ''
27-
@property
28-
def kwargs(self): return {k:v for k,v in self.__dict__.items() if v is not None and k!='opt'}
16+
# Cell
17+
def store_false():
18+
"Placeholder to pass to `Param` for `store_false` action"
19+
pass
2920

3021
# Cell
3122
def bool_arg(v):
@@ -35,6 +26,27 @@ def bool_arg(v):
3526
elif v.lower() in ('no', 'false', 'f', 'n', '0'): return False
3627
else: raise argparse.ArgumentTypeError('Boolean value expected.')
3728

29+
# Cell
30+
class Param:
31+
"A parameter in a function used in `anno_parser` or `call_parse`"
32+
def __init__(self, help=None, type=None, opt=True, action=None, nargs=None, const=None,
33+
choices=None, required=None, default=None):
34+
if type==store_true: type,action,default=None,'store_true' ,False
35+
if type==store_false: type,action,default=None,'store_false',True
36+
store_attr()
37+
38+
def set_default(self, d):
39+
if self.default is None:
40+
if d==inspect.Parameter.empty: self.opt = False
41+
else: self.default = d
42+
if self.default is not None: self.help += f" (default: {self.default})"
43+
44+
@property
45+
def pre(self): return '--' if self.opt else ''
46+
@property
47+
def kwargs(self): return {k:v for k,v in self.__dict__.items()
48+
if v is not None and k!='opt' and k[0]!='_'}
49+
3850
# Cell
3951
def anno_parser(func, prog=None, from_name=False):
4052
"Look at params (annotated with `Param`) in func and return an `ArgumentParser`"
@@ -43,8 +55,8 @@ def anno_parser(func, prog=None, from_name=False):
4355
param = func.__annotations__.get(k, Param())
4456
param.set_default(v.default)
4557
p.add_argument(f"{param.pre}{k}", **param.kwargs)
46-
p.add_argument(f"--pdb", help="Run in pdb debugger (default: False)", type=bool_arg)
47-
p.add_argument(f"--xtra", help="Parse for additional args", type=str)
58+
p.add_argument(f"--pdb", help="Run in pdb debugger (default: False)", action='store_true')
59+
p.add_argument(f"--xtra", help="Parse for additional args (default: '')", type=str)
4860
return p
4961

5062
# Cell

nbs/07_script.ipynb

Lines changed: 77 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,38 @@
5555
"cell_type": "markdown",
5656
"metadata": {},
5757
"source": [
58-
"Here's a complete example:\n",
58+
"Here's a complete example (available in `examples/test_fastcore.py`):\n",
5959
"\n",
6060
"```python\n",
6161
"from fastcore.script import *\n",
6262
"@call_parse\n",
6363
"def main(msg:Param(\"The message\", str),\n",
64-
" upper:Param(\"Convert to uppercase?\", bool_arg)=False):\n",
64+
" upper:Param(\"Convert to uppercase?\", store_true)):\n",
65+
" \"Print `msg`, optionally converting to uppercase\"\n",
6566
" print(msg.upper() if upper else msg)\n",
66-
"````\n",
67-
"\n",
67+
"````"
68+
]
69+
},
70+
{
71+
"cell_type": "markdown",
72+
"metadata": {},
73+
"source": [
6874
"If you copy that info a file and run it, you'll see:\n",
6975
"\n",
7076
"```\n",
71-
"$ python test_fastcore.script.py\n",
72-
"usage: test_fastcore.script.py [-h] [--upper UPPER] msg\n",
73-
"test_fastcore.script.py: error: the following arguments are required: msg\n",
77+
"$ examples/test_fastcore.py --help\n",
78+
"usage: test_fastcore.py [-h] [--upper] [--pdb PDB] [--xtra XTRA] msg\n",
79+
"\n",
80+
"Print `msg`, optionally converting to uppercase\n",
81+
"\n",
82+
"positional arguments:\n",
83+
" msg The message\n",
84+
"\n",
85+
"optional arguments:\n",
86+
" -h, --help show this help message and exit\n",
87+
" --upper Convert to uppercase? (default: False)\n",
88+
" --pdb PDB Run in pdb debugger (default: False)\n",
89+
" --xtra XTRA Parse for additional args (default: '')\n",
7490
"```\n",
7591
"\n",
7692
"As you see, we didn't need any `if __name__ == \"__main__\"`, we didn't have to parse arguments, we just wrote a function, added a decorator to it, and added some annotations to our function's parameters. As a bonus, we can also use this function directly from a REPL such as Jupyter Notebook - it's not just for command line scripts!"
@@ -144,32 +160,9 @@
144160
"outputs": [],
145161
"source": [
146162
"#export\n",
147-
"class Param:\n",
148-
" \"A parameter in a function used in `anno_parser` or `call_parse`\"\n",
149-
" def __init__(self, help=None, type=None, opt=True, action=None, nargs=None, const=None,\n",
150-
" choices=None, required=None):\n",
151-
" self.help,self.type,self.opt,self.action,self.nargs = help,type,opt,action,nargs\n",
152-
" self.const,self.choices,self.required = const,choices,required\n",
153-
" \n",
154-
" def set_default(self, d):\n",
155-
" if d==inspect.Parameter.empty: self.opt = False\n",
156-
" else:\n",
157-
" self.default = d\n",
158-
" self.help += f\" (default: {d})\"\n",
159-
"\n",
160-
" @property\n",
161-
" def pre(self): return '--' if self.opt else ''\n",
162-
" @property\n",
163-
" def kwargs(self): return {k:v for k,v in self.__dict__.items() if v is not None and k!='opt'}"
164-
]
165-
},
166-
{
167-
"cell_type": "markdown",
168-
"metadata": {},
169-
"source": [
170-
"Each parameter in your function should have an annotation `Param(...)`. You can pass the following when calling `Param`: `help`,`type`,`opt`,`action`,`nargs`,`const`,`choices`,`required` (i.e. it takes the same parameters as `argparse.ArgumentParser.add_argument`, plus `opt`). Except for `opt`, all of these are just passed directly to `argparse`, so you have all the power of that module at your disposal. Generally you'll want to pass at least `help` (since this is provided as the help string for that parameter) and `type` (to ensure that you get the type of data you expect).\n",
171-
"\n",
172-
"`opt` is a bool that defines whether a param is optional or required (positional) - but you'll generally not need to set this manually, because fastcore.script will set it for you automatically based on *default* values. You should provide a default (after the `=`) for any *optional* parameters. If you don't provide a default for a parameter, then it will be a *positional* parameter."
163+
"def store_true():\n",
164+
" \"Placeholder to pass to `Param` for `store_true` action\"\n",
165+
" pass"
173166
]
174167
},
175168
{
@@ -178,9 +171,10 @@
178171
"metadata": {},
179172
"outputs": [],
180173
"source": [
181-
"p = Param(help=\"help\", type=int)\n",
182-
"p.set_default(1)\n",
183-
"test_eq(p.kwargs, {'help': 'help (default: 1)', 'type': int, 'default': 1})"
174+
"#export\n",
175+
"def store_false():\n",
176+
" \"Placeholder to pass to `Param` for `store_false` action\"\n",
177+
" pass"
184178
]
185179
},
186180
{
@@ -204,9 +198,46 @@
204198
"metadata": {},
205199
"outputs": [],
206200
"source": [
207-
"def f(from_name:Param(\"Get args from prog name instead of argparse\", bool_arg)=0,\n",
208-
" a:Param(\"param 1\", bool_arg)=1,\n",
209-
" b:Param(\"param 2\", str)=\"test\"): ..."
201+
"#export\n",
202+
"class Param:\n",
203+
" \"A parameter in a function used in `anno_parser` or `call_parse`\"\n",
204+
" def __init__(self, help=None, type=None, opt=True, action=None, nargs=None, const=None,\n",
205+
" choices=None, required=None, default=None):\n",
206+
" if type==store_true: type,action,default=None,'store_true' ,False\n",
207+
" if type==store_false: type,action,default=None,'store_false',True\n",
208+
" store_attr()\n",
209+
" \n",
210+
" def set_default(self, d):\n",
211+
" if self.default is None:\n",
212+
" if d==inspect.Parameter.empty: self.opt = False\n",
213+
" else: self.default = d\n",
214+
" if self.default is not None: self.help += f\" (default: {self.default})\"\n",
215+
"\n",
216+
" @property\n",
217+
" def pre(self): return '--' if self.opt else ''\n",
218+
" @property\n",
219+
" def kwargs(self): return {k:v for k,v in self.__dict__.items()\n",
220+
" if v is not None and k!='opt' and k[0]!='_'}"
221+
]
222+
},
223+
{
224+
"cell_type": "markdown",
225+
"metadata": {},
226+
"source": [
227+
"Each parameter in your function should have an annotation `Param(...)`. You can pass the following when calling `Param`: `help`,`type`,`opt`,`action`,`nargs`,`const`,`choices`,`required` (i.e. it takes the same parameters as `argparse.ArgumentParser.add_argument`, plus `opt`). Except for `opt`, all of these are just passed directly to `argparse`, so you have all the power of that module at your disposal. Generally you'll want to pass at least `help` (since this is provided as the help string for that parameter) and `type` (to ensure that you get the type of data you expect).\n",
228+
"\n",
229+
"`opt` is a bool that defines whether a param is optional or required (positional) - but you'll generally not need to set this manually, because fastcore.script will set it for you automatically based on *default* values. You should provide a default (after the `=`) for any *optional* parameters. If you don't provide a default for a parameter, then it will be a *positional* parameter."
230+
]
231+
},
232+
{
233+
"cell_type": "code",
234+
"execution_count": null,
235+
"metadata": {},
236+
"outputs": [],
237+
"source": [
238+
"p = Param(help=\"help\", type=int)\n",
239+
"p.set_default(1)\n",
240+
"test_eq(p.kwargs, {'help': 'help (default: 1)', 'type': int, 'default': 1})"
210241
]
211242
},
212243
{
@@ -223,8 +254,8 @@
223254
" param = func.__annotations__.get(k, Param())\n",
224255
" param.set_default(v.default)\n",
225256
" p.add_argument(f\"{param.pre}{k}\", **param.kwargs)\n",
226-
" p.add_argument(f\"--pdb\", help=\"Run in pdb debugger (default: False)\", type=bool_arg)\n",
227-
" p.add_argument(f\"--xtra\", help=\"Parse for additional args\", type=str)\n",
257+
" p.add_argument(f\"--pdb\", help=\"Run in pdb debugger (default: False)\", action='store_true')\n",
258+
" p.add_argument(f\"--xtra\", help=\"Parse for additional args (default: '')\", type=str)\n",
228259
" return p"
229260
]
230261
},
@@ -244,7 +275,7 @@
244275
"name": "stdout",
245276
"output_type": "stream",
246277
"text": [
247-
"usage: progname [-h] [--a A] [--b B] [--pdb PDB] [--xtra XTRA] required\n",
278+
"usage: progname [-h] [--a] [--b B] [--pdb] [--xtra XTRA] required\n",
248279
"\n",
249280
"my docs\n",
250281
"\n",
@@ -253,16 +284,16 @@
253284
"\n",
254285
"optional arguments:\n",
255286
" -h, --help show this help message and exit\n",
256-
" --a A param 1 (default: 1)\n",
287+
" --a param 1 (default: False)\n",
257288
" --b B param 2 (default: test)\n",
258-
" --pdb PDB Run in pdb debugger (default: False)\n",
259-
" --xtra XTRA Parse for additional args\n"
289+
" --pdb Run in pdb debugger (default: False)\n",
290+
" --xtra XTRA Parse for additional args (default: '')\n"
260291
]
261292
}
262293
],
263294
"source": [
264295
"def f(required:Param(\"Required param\", int),\n",
265-
" a:Param(\"param 1\", bool_arg)=1,\n",
296+
" a:Param(\"param 1\", store_true),\n",
266297
" b:Param(\"param 2\", str)=\"test\"):\n",
267298
" \"my docs\"\n",
268299
" ...\n",

0 commit comments

Comments
 (0)