|
55 | 55 | "cell_type": "markdown", |
56 | 56 | "metadata": {}, |
57 | 57 | "source": [ |
58 | | - "Here's a complete example:\n", |
| 58 | + "Here's a complete example (available in `examples/test_fastcore.py`):\n", |
59 | 59 | "\n", |
60 | 60 | "```python\n", |
61 | 61 | "from fastcore.script import *\n", |
62 | 62 | "@call_parse\n", |
63 | 63 | "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", |
65 | 66 | " print(msg.upper() if upper else msg)\n", |
66 | | - "````\n", |
67 | | - "\n", |
| 67 | + "````" |
| 68 | + ] |
| 69 | + }, |
| 70 | + { |
| 71 | + "cell_type": "markdown", |
| 72 | + "metadata": {}, |
| 73 | + "source": [ |
68 | 74 | "If you copy that info a file and run it, you'll see:\n", |
69 | 75 | "\n", |
70 | 76 | "```\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", |
74 | 90 | "```\n", |
75 | 91 | "\n", |
76 | 92 | "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 | 160 | "outputs": [], |
145 | 161 | "source": [ |
146 | 162 | "#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" |
173 | 166 | ] |
174 | 167 | }, |
175 | 168 | { |
|
178 | 171 | "metadata": {}, |
179 | 172 | "outputs": [], |
180 | 173 | "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" |
184 | 178 | ] |
185 | 179 | }, |
186 | 180 | { |
|
204 | 198 | "metadata": {}, |
205 | 199 | "outputs": [], |
206 | 200 | "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})" |
210 | 241 | ] |
211 | 242 | }, |
212 | 243 | { |
|
223 | 254 | " param = func.__annotations__.get(k, Param())\n", |
224 | 255 | " param.set_default(v.default)\n", |
225 | 256 | " 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", |
228 | 259 | " return p" |
229 | 260 | ] |
230 | 261 | }, |
|
244 | 275 | "name": "stdout", |
245 | 276 | "output_type": "stream", |
246 | 277 | "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", |
248 | 279 | "\n", |
249 | 280 | "my docs\n", |
250 | 281 | "\n", |
|
253 | 284 | "\n", |
254 | 285 | "optional arguments:\n", |
255 | 286 | " -h, --help show this help message and exit\n", |
256 | | - " --a A param 1 (default: 1)\n", |
| 287 | + " --a param 1 (default: False)\n", |
257 | 288 | " --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" |
260 | 291 | ] |
261 | 292 | } |
262 | 293 | ], |
263 | 294 | "source": [ |
264 | 295 | "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", |
266 | 297 | " b:Param(\"param 2\", str)=\"test\"):\n", |
267 | 298 | " \"my docs\"\n", |
268 | 299 | " ...\n", |
|
0 commit comments