test_arguments.py 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329
  1. import collections
  2. import pytest
  3. from flexmock import flexmock
  4. from borgmatic.commands import arguments as module
  5. def test_get_subaction_parsers_with_no_subactions_returns_empty_result():
  6. assert module.get_subaction_parsers(flexmock(_subparsers=None)) == {}
  7. def test_get_subaction_parsers_with_subactions_returns_one_entry_per_subaction():
  8. foo_parser = flexmock()
  9. bar_parser = flexmock()
  10. baz_parser = flexmock()
  11. assert module.get_subaction_parsers(
  12. flexmock(
  13. _subparsers=flexmock(
  14. _group_actions=(
  15. flexmock(choices={'foo': foo_parser, 'bar': bar_parser}),
  16. flexmock(choices={'baz': baz_parser}),
  17. )
  18. )
  19. )
  20. ) == {'foo': foo_parser, 'bar': bar_parser, 'baz': baz_parser}
  21. def test_get_subactions_for_actions_with_no_subactions_returns_empty_result():
  22. assert module.get_subactions_for_actions({'action': flexmock(_subparsers=None)}) == {}
  23. def test_get_subactions_for_actions_with_subactions_returns_one_entry_per_action():
  24. assert module.get_subactions_for_actions(
  25. {
  26. 'action': flexmock(
  27. _subparsers=flexmock(
  28. _group_actions=(
  29. flexmock(choices={'foo': flexmock(), 'bar': flexmock()}),
  30. flexmock(choices={'baz': flexmock()}),
  31. )
  32. )
  33. ),
  34. 'other': flexmock(
  35. _subparsers=flexmock(_group_actions=(flexmock(choices={'quux': flexmock()}),))
  36. ),
  37. }
  38. ) == {'action': ('foo', 'bar', 'baz'), 'other': ('quux',)}
  39. def test_omit_values_colliding_with_action_names_drops_action_names_that_have_been_parsed_as_values():
  40. assert module.omit_values_colliding_with_action_names(
  41. ('check', '--only', 'extract', '--some-list', 'borg'),
  42. {'check': flexmock(only='extract', some_list=['borg'])},
  43. ) == ('check', '--only', '--some-list')
  44. def test_omit_values_colliding_twice_with_action_names_drops_action_names_that_have_been_parsed_as_values():
  45. assert module.omit_values_colliding_with_action_names(
  46. ('config', 'bootstrap', '--local-path', '--remote-path', 'borg'),
  47. {'bootstrap': flexmock(local_path='borg', remote_path='borg')},
  48. ) == ('config', 'bootstrap', '--local-path', '--remote-path')
  49. def test_parse_and_record_action_arguments_without_action_name_leaves_arguments_untouched():
  50. unparsed_arguments = ('--foo', '--bar')
  51. flexmock(module).should_receive('omit_values_colliding_with_action_names').and_return(
  52. unparsed_arguments
  53. )
  54. assert (
  55. module.parse_and_record_action_arguments(
  56. unparsed_arguments, flexmock(), flexmock(), 'action'
  57. )
  58. == unparsed_arguments
  59. )
  60. def test_parse_and_record_action_arguments_updates_parsed_arguments_and_returns_remaining():
  61. unparsed_arguments = ('action', '--foo', '--bar', '--verbosity', '1')
  62. other_parsed_arguments = flexmock()
  63. parsed_arguments = {'other': other_parsed_arguments}
  64. action_parsed_arguments = flexmock()
  65. flexmock(module).should_receive('omit_values_colliding_with_action_names').and_return(
  66. unparsed_arguments
  67. )
  68. action_parser = flexmock()
  69. flexmock(action_parser).should_receive('parse_known_args').and_return(
  70. action_parsed_arguments, ('action', '--verbosity', '1')
  71. )
  72. assert module.parse_and_record_action_arguments(
  73. unparsed_arguments, parsed_arguments, action_parser, 'action'
  74. ) == ('--verbosity', '1')
  75. assert parsed_arguments == {'other': other_parsed_arguments, 'action': action_parsed_arguments}
  76. def test_parse_and_record_action_arguments_with_alias_updates_canonical_parsed_arguments():
  77. unparsed_arguments = ('action', '--foo', '--bar', '--verbosity', '1')
  78. other_parsed_arguments = flexmock()
  79. parsed_arguments = {'other': other_parsed_arguments}
  80. action_parsed_arguments = flexmock()
  81. flexmock(module).should_receive('omit_values_colliding_with_action_names').and_return(
  82. unparsed_arguments
  83. )
  84. action_parser = flexmock()
  85. flexmock(action_parser).should_receive('parse_known_args').and_return(
  86. action_parsed_arguments, ('action', '--verbosity', '1')
  87. )
  88. assert module.parse_and_record_action_arguments(
  89. unparsed_arguments, parsed_arguments, action_parser, 'action', canonical_name='doit'
  90. ) == ('--verbosity', '1')
  91. assert parsed_arguments == {'other': other_parsed_arguments, 'doit': action_parsed_arguments}
  92. def test_parse_and_record_action_arguments_with_borg_action_consumes_arguments_after_action_name():
  93. unparsed_arguments = ('--verbosity', '1', 'borg', 'list')
  94. parsed_arguments = {}
  95. borg_parsed_arguments = flexmock(options=flexmock())
  96. flexmock(module).should_receive('omit_values_colliding_with_action_names').and_return(
  97. unparsed_arguments
  98. )
  99. borg_parser = flexmock()
  100. flexmock(borg_parser).should_receive('parse_known_args').and_return(
  101. borg_parsed_arguments, ('--verbosity', '1', 'borg', 'list')
  102. )
  103. assert module.parse_and_record_action_arguments(
  104. unparsed_arguments,
  105. parsed_arguments,
  106. borg_parser,
  107. 'borg',
  108. ) == ('--verbosity', '1')
  109. assert parsed_arguments == {'borg': borg_parsed_arguments}
  110. assert borg_parsed_arguments.options == ('list',)
  111. @pytest.mark.parametrize(
  112. 'argument, expected',
  113. [
  114. ('--foo', True),
  115. ('foo', False),
  116. (33, False),
  117. ],
  118. )
  119. def test_argument_is_flag_only_for_string_starting_with_double_dash(argument, expected):
  120. assert module.argument_is_flag(argument) == expected
  121. @pytest.mark.parametrize(
  122. 'arguments, expected',
  123. [
  124. # Ending with a valueless flag.
  125. (
  126. ('--foo', '--bar', 33, '--baz'),
  127. (
  128. ('--foo',),
  129. ('--bar', 33),
  130. ('--baz',),
  131. ),
  132. ),
  133. # Ending with a flag and its corresponding value.
  134. (
  135. ('--foo', '--bar', 33, '--baz', '--quux', 'thing'),
  136. (('--foo',), ('--bar', 33), ('--baz',), ('--quux', 'thing')),
  137. ),
  138. # Starting with an action name.
  139. (
  140. ('check', '--foo', '--bar', 33, '--baz'),
  141. (
  142. ('check',),
  143. ('--foo',),
  144. ('--bar', 33),
  145. ('--baz',),
  146. ),
  147. ),
  148. # Action name that one could mistake for a flag value.
  149. (('--progress', 'list'), (('--progress',), ('list',))),
  150. # No arguments.
  151. ((), ()),
  152. ],
  153. )
  154. def test_group_arguments_with_values_returns_flags_with_corresponding_values(arguments, expected):
  155. flexmock(module).should_receive('argument_is_flag').with_args('--foo').and_return(True)
  156. flexmock(module).should_receive('argument_is_flag').with_args('--bar').and_return(True)
  157. flexmock(module).should_receive('argument_is_flag').with_args('--baz').and_return(True)
  158. flexmock(module).should_receive('argument_is_flag').with_args('--quux').and_return(True)
  159. flexmock(module).should_receive('argument_is_flag').with_args('--progress').and_return(True)
  160. flexmock(module).should_receive('argument_is_flag').with_args(33).and_return(False)
  161. flexmock(module).should_receive('argument_is_flag').with_args('thing').and_return(False)
  162. flexmock(module).should_receive('argument_is_flag').with_args('check').and_return(False)
  163. flexmock(module).should_receive('argument_is_flag').with_args('list').and_return(False)
  164. assert module.group_arguments_with_values(arguments) == expected
  165. @pytest.mark.parametrize(
  166. 'arguments, grouped_arguments, expected',
  167. [
  168. # An unparsable flag remaining from each parsed action.
  169. (
  170. (
  171. ('--latest', 'archive', 'prune', 'extract', 'list', '--flag'),
  172. ('--latest', 'archive', 'check', 'extract', 'list', '--flag'),
  173. ('prune', 'check', 'list', '--flag'),
  174. ('prune', 'check', 'extract', '--flag'),
  175. ),
  176. (
  177. (
  178. ('--latest',),
  179. ('archive',),
  180. ('prune',),
  181. ('extract',),
  182. ('list',),
  183. ('--flag',),
  184. ),
  185. (
  186. ('--latest',),
  187. ('archive',),
  188. ('check',),
  189. ('extract',),
  190. ('list',),
  191. ('--flag',),
  192. ),
  193. (('prune',), ('check',), ('list',), ('--flag',)),
  194. (('prune',), ('check',), ('extract',), ('--flag',)),
  195. ),
  196. ('--flag',),
  197. ),
  198. # No unparsable flags remaining.
  199. (
  200. (
  201. ('--archive', 'archive', 'prune', 'extract', 'list'),
  202. ('--archive', 'archive', 'check', 'extract', 'list'),
  203. ('prune', 'check', 'list'),
  204. ('prune', 'check', 'extract'),
  205. ),
  206. (
  207. (
  208. (
  209. '--archive',
  210. 'archive',
  211. ),
  212. ('prune',),
  213. ('extract',),
  214. ('list',),
  215. ),
  216. (
  217. (
  218. '--archive',
  219. 'archive',
  220. ),
  221. ('check',),
  222. ('extract',),
  223. ('list',),
  224. ),
  225. (('prune',), ('check',), ('list',)),
  226. (('prune',), ('check',), ('extract',)),
  227. ),
  228. (),
  229. ),
  230. # No unparsable flags remaining, but some values in common.
  231. (
  232. (
  233. ('--verbosity', '5', 'archive', 'prune', 'extract', 'list'),
  234. ('--last', '5', 'archive', 'check', 'extract', 'list'),
  235. ('prune', 'check', 'list', '--last', '5'),
  236. ('prune', 'check', '--verbosity', '5', 'extract'),
  237. ),
  238. (
  239. (('--verbosity', '5'), ('archive',), ('prune',), ('extract',), ('list',)),
  240. (
  241. (
  242. '--last',
  243. '5',
  244. ),
  245. ('archive',),
  246. ('check',),
  247. ('extract',),
  248. ('list',),
  249. ),
  250. (('prune',), ('check',), ('list',), ('--last', '5')),
  251. (
  252. ('prune',),
  253. ('check',),
  254. (
  255. '--verbosity',
  256. '5',
  257. ),
  258. ('extract',),
  259. ),
  260. ),
  261. (),
  262. ),
  263. # No flags.
  264. ((), (), ()),
  265. ],
  266. )
  267. def test_get_unparsable_arguments_returns_remaining_arguments_that_no_action_can_parse(
  268. arguments, grouped_arguments, expected
  269. ):
  270. for action_arguments, grouped_action_arguments in zip(arguments, grouped_arguments):
  271. flexmock(module).should_receive('group_arguments_with_values').with_args(
  272. action_arguments
  273. ).and_return(grouped_action_arguments)
  274. assert module.get_unparsable_arguments(arguments) == expected
  275. def test_parse_arguments_for_actions_consumes_action_arguments_after_action_name():
  276. action_namespace = flexmock(foo=True)
  277. remaining = flexmock()
  278. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  279. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  280. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  281. {action: action_namespace}
  282. )
  283. or remaining
  284. )
  285. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  286. action_parsers = {'action': flexmock(), 'other': flexmock()}
  287. global_namespace = flexmock(config_paths=[])
  288. global_parser = flexmock()
  289. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  290. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  291. ('action', '--foo', 'true'), action_parsers, global_parser
  292. )
  293. assert arguments == {'global': global_namespace, 'action': action_namespace}
  294. assert remaining_action_arguments == (remaining, ())
  295. def test_parse_arguments_for_actions_consumes_action_arguments_with_alias():
  296. action_namespace = flexmock(foo=True)
  297. remaining = flexmock()
  298. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  299. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  300. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  301. {canonical or action: action_namespace}
  302. )
  303. or remaining
  304. )
  305. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  306. action_parsers = {
  307. 'action': flexmock(),
  308. '-a': flexmock(),
  309. 'other': flexmock(),
  310. '-o': flexmock(),
  311. }
  312. global_namespace = flexmock(config_paths=[])
  313. global_parser = flexmock()
  314. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  315. flexmock(module).ACTION_ALIASES = {'action': ['-a'], 'other': ['-o']}
  316. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  317. ('-a', '--foo', 'true'), action_parsers, global_parser
  318. )
  319. assert arguments == {'global': global_namespace, 'action': action_namespace}
  320. assert remaining_action_arguments == (remaining, ())
  321. def test_parse_arguments_for_actions_consumes_multiple_action_arguments():
  322. action_namespace = flexmock(foo=True)
  323. other_namespace = flexmock(bar=3)
  324. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  325. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  326. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  327. {action: action_namespace if action == 'action' else other_namespace}
  328. )
  329. or ()
  330. ).and_return(('other', '--bar', '3')).and_return('action', '--foo', 'true')
  331. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  332. action_parsers = {
  333. 'action': flexmock(),
  334. 'other': flexmock(),
  335. }
  336. global_namespace = flexmock(config_paths=[])
  337. global_parser = flexmock()
  338. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  339. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  340. ('action', '--foo', 'true', 'other', '--bar', '3'), action_parsers, global_parser
  341. )
  342. assert arguments == {
  343. 'global': global_namespace,
  344. 'action': action_namespace,
  345. 'other': other_namespace,
  346. }
  347. assert remaining_action_arguments == ((), (), ())
  348. def test_parse_arguments_for_actions_respects_command_line_action_ordering():
  349. other_namespace = flexmock()
  350. action_namespace = flexmock(foo=True)
  351. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  352. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  353. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  354. {action: other_namespace if action == 'other' else action_namespace}
  355. )
  356. or ()
  357. ).and_return(('action',)).and_return(('other', '--foo', 'true'))
  358. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  359. action_parsers = {
  360. 'action': flexmock(),
  361. 'other': flexmock(),
  362. }
  363. global_namespace = flexmock(config_paths=[])
  364. global_parser = flexmock()
  365. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  366. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  367. ('other', '--foo', 'true', 'action'), action_parsers, global_parser
  368. )
  369. assert arguments == collections.OrderedDict(
  370. [('other', other_namespace), ('action', action_namespace), ('global', global_namespace)]
  371. )
  372. assert remaining_action_arguments == ((), (), ())
  373. def test_parse_arguments_for_actions_applies_default_action_parsers():
  374. global_namespace = flexmock(config_paths=[])
  375. namespaces = {
  376. 'global': global_namespace,
  377. 'prune': flexmock(),
  378. 'compact': flexmock(),
  379. 'create': flexmock(progress=True),
  380. 'check': flexmock(),
  381. }
  382. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  383. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  384. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  385. {action: namespaces.get(action)}
  386. )
  387. or ()
  388. ).and_return(())
  389. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  390. action_parsers = {
  391. 'prune': flexmock(),
  392. 'compact': flexmock(),
  393. 'create': flexmock(),
  394. 'check': flexmock(),
  395. 'other': flexmock(),
  396. }
  397. global_parser = flexmock()
  398. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  399. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  400. ('--progress'), action_parsers, global_parser
  401. )
  402. assert arguments == namespaces
  403. assert remaining_action_arguments == ((), (), (), (), ())
  404. def test_parse_arguments_for_actions_consumes_global_arguments():
  405. action_namespace = flexmock()
  406. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  407. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  408. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  409. {action: action_namespace}
  410. )
  411. or ('--verbosity', 'lots')
  412. )
  413. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  414. action_parsers = {
  415. 'action': flexmock(),
  416. 'other': flexmock(),
  417. }
  418. global_namespace = flexmock(config_paths=[])
  419. global_parser = flexmock()
  420. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  421. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  422. ('action', '--verbosity', 'lots'), action_parsers, global_parser
  423. )
  424. assert arguments == {'global': global_namespace, 'action': action_namespace}
  425. assert remaining_action_arguments == (('--verbosity', 'lots'), ())
  426. def test_parse_arguments_for_actions_passes_through_unknown_arguments_before_action_name():
  427. action_namespace = flexmock()
  428. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  429. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  430. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  431. {action: action_namespace}
  432. )
  433. or ('--wtf', 'yes')
  434. )
  435. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  436. action_parsers = {
  437. 'action': flexmock(),
  438. 'other': flexmock(),
  439. }
  440. global_namespace = flexmock(config_paths=[])
  441. global_parser = flexmock()
  442. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  443. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  444. ('--wtf', 'yes', 'action'), action_parsers, global_parser
  445. )
  446. assert arguments == {'global': global_namespace, 'action': action_namespace}
  447. assert remaining_action_arguments == (('--wtf', 'yes'), ())
  448. def test_parse_arguments_for_actions_passes_through_unknown_arguments_after_action_name():
  449. action_namespace = flexmock()
  450. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  451. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  452. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  453. {action: action_namespace}
  454. )
  455. or ('--wtf', 'yes')
  456. )
  457. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  458. action_parsers = {
  459. 'action': flexmock(),
  460. 'other': flexmock(),
  461. }
  462. global_namespace = flexmock(config_paths=[])
  463. global_parser = flexmock()
  464. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  465. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  466. ('action', '--wtf', 'yes'), action_parsers, global_parser
  467. )
  468. assert arguments == {'global': global_namespace, 'action': action_namespace}
  469. assert remaining_action_arguments == (('--wtf', 'yes'), ())
  470. def test_parse_arguments_for_actions_with_borg_action_skips_other_action_parsers():
  471. action_namespace = flexmock(options=[])
  472. flexmock(module).should_receive('get_subaction_parsers').and_return({})
  473. flexmock(module).should_receive('parse_and_record_action_arguments').replace_with(
  474. lambda unparsed, parsed, parser, action, canonical=None: parsed.update(
  475. {action: action_namespace}
  476. )
  477. or ()
  478. ).and_return(())
  479. flexmock(module).should_receive('get_subactions_for_actions').and_return({})
  480. action_parsers = {
  481. 'borg': flexmock(),
  482. 'list': flexmock(),
  483. }
  484. global_namespace = flexmock(config_paths=[])
  485. global_parser = flexmock()
  486. global_parser.should_receive('parse_known_args').and_return((global_namespace, ()))
  487. arguments, remaining_action_arguments = module.parse_arguments_for_actions(
  488. ('borg', 'list'), action_parsers, global_parser
  489. )
  490. assert arguments == {'global': global_namespace, 'borg': action_namespace}
  491. assert remaining_action_arguments == ((), ())
  492. def test_parse_arguments_for_actions_raises_error_when_no_action_is_specified():
  493. flexmock(module).should_receive('get_subaction_parsers').and_return({'bootstrap': [flexmock()]})
  494. flexmock(module).should_receive('parse_and_record_action_arguments').and_return(flexmock())
  495. flexmock(module).should_receive('get_subactions_for_actions').and_return(
  496. {'config': ['bootstrap']}
  497. )
  498. action_parsers = {'config': flexmock()}
  499. global_parser = flexmock()
  500. global_parser.should_receive('parse_known_args').and_return((flexmock(), ()))
  501. with pytest.raises(ValueError):
  502. module.parse_arguments_for_actions(('config',), action_parsers, global_parser)
  503. def test_make_argument_description_with_object_adds_example():
  504. buffer = flexmock()
  505. buffer.should_receive('getvalue').and_return('{foo: example}')
  506. flexmock(module.io).should_receive('StringIO').and_return(buffer)
  507. yaml = flexmock()
  508. yaml.should_receive('dump')
  509. flexmock(module.ruamel.yaml).should_receive('YAML').and_return(yaml)
  510. assert (
  511. module.make_argument_description(
  512. schema={
  513. 'description': 'Thing.',
  514. 'type': 'object',
  515. 'example': {'foo': 'example'},
  516. },
  517. flag_name='flag',
  518. )
  519. == 'Thing. Example value: "{foo: example}"'
  520. )
  521. def test_make_argument_description_without_description_and_with_object_sets_example():
  522. buffer = flexmock()
  523. buffer.should_receive('getvalue').and_return('{foo: example}')
  524. flexmock(module.io).should_receive('StringIO').and_return(buffer)
  525. yaml = flexmock()
  526. yaml.should_receive('dump')
  527. flexmock(module.ruamel.yaml).should_receive('YAML').and_return(yaml)
  528. assert (
  529. module.make_argument_description(
  530. schema={
  531. 'type': 'object',
  532. 'example': {'foo': 'example'},
  533. },
  534. flag_name='flag',
  535. )
  536. == 'Example value: "{foo: example}"'
  537. )
  538. def test_make_argument_description_with_object_skips_missing_example():
  539. flexmock(module.ruamel.yaml).should_receive('YAML').never()
  540. assert (
  541. module.make_argument_description(
  542. schema={
  543. 'description': 'Thing.',
  544. 'type': 'object',
  545. },
  546. flag_name='flag',
  547. )
  548. == 'Thing.'
  549. )
  550. def test_make_argument_description_with_array_adds_example():
  551. buffer = flexmock()
  552. buffer.should_receive('getvalue').and_return('[example]')
  553. flexmock(module.io).should_receive('StringIO').and_return(buffer)
  554. yaml = flexmock()
  555. yaml.should_receive('dump')
  556. flexmock(module.ruamel.yaml).should_receive('YAML').and_return(yaml)
  557. assert (
  558. module.make_argument_description(
  559. schema={
  560. 'description': 'Thing.',
  561. 'type': 'array',
  562. 'example': ['example'],
  563. },
  564. flag_name='flag',
  565. )
  566. == 'Thing. Example value: "[example]"'
  567. )
  568. def test_make_argument_description_without_description_and_with_array_sets_example():
  569. buffer = flexmock()
  570. buffer.should_receive('getvalue').and_return('[example]')
  571. flexmock(module.io).should_receive('StringIO').and_return(buffer)
  572. yaml = flexmock()
  573. yaml.should_receive('dump')
  574. flexmock(module.ruamel.yaml).should_receive('YAML').and_return(yaml)
  575. assert (
  576. module.make_argument_description(
  577. schema={
  578. 'type': 'array',
  579. 'example': ['example'],
  580. },
  581. flag_name='flag',
  582. )
  583. == 'Example value: "[example]"'
  584. )
  585. def test_make_argument_description_with_array_skips_missing_example():
  586. flexmock(module.ruamel.yaml).should_receive('YAML').never()
  587. assert (
  588. module.make_argument_description(
  589. schema={
  590. 'description': 'Thing.',
  591. 'type': 'array',
  592. },
  593. flag_name='flag',
  594. )
  595. == 'Thing.'
  596. )
  597. def test_make_argument_description_with_array_index_in_flag_name_adds_to_description():
  598. assert 'list element' in module.make_argument_description(
  599. schema={
  600. 'description': 'Thing.',
  601. 'type': 'something',
  602. },
  603. flag_name='flag[0]',
  604. )
  605. def test_make_argument_description_without_description_and_with_array_index_in_flag_name_sets_description():
  606. assert 'list element' in module.make_argument_description(
  607. schema={
  608. 'type': 'something',
  609. },
  610. flag_name='flag[0]',
  611. )
  612. def test_make_argument_description_escapes_percent_character():
  613. assert (
  614. module.make_argument_description(
  615. schema={
  616. 'description': '% Thing.',
  617. 'type': 'something',
  618. },
  619. flag_name='flag',
  620. )
  621. == '%% Thing.'
  622. )
  623. def test_add_array_element_arguments_without_array_index_bails():
  624. arguments_group = flexmock()
  625. arguments_group.should_receive('add_argument').never()
  626. module.add_array_element_arguments(
  627. arguments_group=arguments_group,
  628. unparsed_arguments=(),
  629. flag_name='foo',
  630. )
  631. def test_add_array_element_arguments_with_help_flag_bails():
  632. arguments_group = flexmock()
  633. arguments_group.should_receive('add_argument').never()
  634. module.add_array_element_arguments(
  635. arguments_group=arguments_group,
  636. unparsed_arguments=('--foo', '--help', '--bar'),
  637. flag_name='foo[0]',
  638. )
  639. def test_add_array_element_arguments_without_any_flags_bails():
  640. arguments_group = flexmock()
  641. arguments_group.should_receive('add_argument').never()
  642. module.add_array_element_arguments(
  643. arguments_group=arguments_group,
  644. unparsed_arguments=(),
  645. flag_name='foo[0]',
  646. )
  647. # Use this instead of a flexmock because it's not easy to check the type() of a flexmock instance.
  648. Group_action = collections.namedtuple(
  649. 'Group_action',
  650. (
  651. 'option_strings',
  652. 'choices',
  653. 'default',
  654. 'nargs',
  655. 'required',
  656. 'type',
  657. ),
  658. defaults=(
  659. flexmock(),
  660. flexmock(),
  661. flexmock(),
  662. flexmock(),
  663. flexmock(),
  664. ),
  665. )
  666. def test_add_array_element_arguments_without_array_index_flags_bails():
  667. arguments_group = flexmock(
  668. _group_actions=(
  669. Group_action(
  670. option_strings=('--foo[0].val',),
  671. ),
  672. ),
  673. _registries={'action': {'store_stuff': Group_action}},
  674. )
  675. arguments_group.should_receive('add_argument').never()
  676. module.add_array_element_arguments(
  677. arguments_group=arguments_group,
  678. unparsed_arguments=('--foo', '--bar'),
  679. flag_name='foo[0].val',
  680. )
  681. def test_add_array_element_arguments_with_non_matching_array_index_flags_bails():
  682. arguments_group = flexmock(
  683. _group_actions=(
  684. Group_action(
  685. option_strings=('--foo[0].val',),
  686. ),
  687. ),
  688. _registries={'action': {'store_stuff': Group_action}},
  689. )
  690. arguments_group.should_receive('add_argument').never()
  691. module.add_array_element_arguments(
  692. arguments_group=arguments_group,
  693. unparsed_arguments=('--foo', '--bar[25].val', 'barval'),
  694. flag_name='foo[0].val',
  695. )
  696. def test_add_array_element_arguments_with_identical_array_index_flag_bails():
  697. arguments_group = flexmock(
  698. _group_actions=(
  699. Group_action(
  700. option_strings=('--foo[0].val',),
  701. ),
  702. ),
  703. _registries={'action': {'store_stuff': Group_action}},
  704. )
  705. arguments_group.should_receive('add_argument').never()
  706. module.add_array_element_arguments(
  707. arguments_group=arguments_group,
  708. unparsed_arguments=('--foo[0].val', 'fooval', '--bar'),
  709. flag_name='foo[0].val',
  710. )
  711. def test_add_array_element_arguments_without_action_type_in_registry_bails():
  712. arguments_group = flexmock(
  713. _group_actions=(
  714. Group_action(
  715. option_strings=('--foo[0].val',),
  716. choices=flexmock(),
  717. default=flexmock(),
  718. nargs=flexmock(),
  719. required=flexmock(),
  720. type=flexmock(),
  721. ),
  722. ),
  723. _registries={'action': {'store_stuff': bool}},
  724. )
  725. arguments_group.should_receive('add_argument').never()
  726. module.add_array_element_arguments(
  727. arguments_group=arguments_group,
  728. unparsed_arguments=('--foo[25].val', 'fooval', '--bar[1].val', 'barval'),
  729. flag_name='foo[0].val',
  730. )
  731. def test_add_array_element_arguments_adds_arguments_for_array_index_flags():
  732. arguments_group = flexmock(
  733. _group_actions=(
  734. Group_action(
  735. option_strings=('--foo[0].val',),
  736. choices=flexmock(),
  737. default=flexmock(),
  738. nargs=flexmock(),
  739. required=flexmock(),
  740. type=flexmock(),
  741. ),
  742. ),
  743. _registries={'action': {'store_stuff': Group_action}},
  744. )
  745. arguments_group.should_receive('add_argument').with_args(
  746. '--foo[25].val',
  747. action='store_stuff',
  748. choices=object,
  749. default=object,
  750. dest='foo[25].val',
  751. nargs=object,
  752. required=object,
  753. type=object,
  754. ).once()
  755. module.add_array_element_arguments(
  756. arguments_group=arguments_group,
  757. unparsed_arguments=('--foo[25].val', 'fooval', '--bar[1].val', 'barval'),
  758. flag_name='foo[0].val',
  759. )
  760. def test_add_array_element_arguments_adds_arguments_for_array_index_flags_with_equals_sign():
  761. arguments_group = flexmock(
  762. _group_actions=(
  763. Group_action(
  764. option_strings=('--foo[0].val',),
  765. choices=flexmock(),
  766. default=flexmock(),
  767. nargs=flexmock(),
  768. required=flexmock(),
  769. type=flexmock(),
  770. ),
  771. ),
  772. _registries={'action': {'store_stuff': Group_action}},
  773. )
  774. arguments_group.should_receive('add_argument').with_args(
  775. '--foo[25].val',
  776. action='store_stuff',
  777. choices=object,
  778. default=object,
  779. dest='foo[25].val',
  780. nargs=object,
  781. required=object,
  782. type=object,
  783. ).once()
  784. module.add_array_element_arguments(
  785. arguments_group=arguments_group,
  786. unparsed_arguments=('--foo[25].val=fooval', '--bar[1].val=barval'),
  787. flag_name='foo[0].val',
  788. )
  789. def test_add_array_element_arguments_adds_arguments_for_array_index_flags_with_dashes():
  790. arguments_group = flexmock(
  791. _group_actions=(
  792. Group_action(
  793. option_strings=('--foo[0].val-and-stuff',),
  794. choices=flexmock(),
  795. default=flexmock(),
  796. nargs=flexmock(),
  797. required=flexmock(),
  798. type=flexmock(),
  799. ),
  800. ),
  801. _registries={'action': {'store_stuff': Group_action}},
  802. )
  803. arguments_group.should_receive('add_argument').with_args(
  804. '--foo[25].val-and-stuff',
  805. action='store_stuff',
  806. choices=object,
  807. default=object,
  808. dest='foo[25].val_and_stuff',
  809. nargs=object,
  810. required=object,
  811. type=object,
  812. ).once()
  813. module.add_array_element_arguments(
  814. arguments_group=arguments_group,
  815. unparsed_arguments=('--foo[25].val-and-stuff', 'fooval', '--bar[1].val', 'barval'),
  816. flag_name='foo[0].val-and-stuff',
  817. )
  818. def test_add_arguments_from_schema_with_non_dict_schema_bails():
  819. arguments_group = flexmock()
  820. flexmock(module).should_receive('make_argument_description').never()
  821. flexmock(module.borgmatic.config.schema).should_receive('parse_type').never()
  822. arguments_group.should_receive('add_argument').never()
  823. module.add_arguments_from_schema(
  824. arguments_group=arguments_group, schema='foo', unparsed_arguments=()
  825. )
  826. def test_add_arguments_from_schema_with_nested_object_adds_flag_for_each_option():
  827. arguments_group = flexmock()
  828. flexmock(module).should_receive('make_argument_description').and_return('help 1').and_return(
  829. 'help 2'
  830. )
  831. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(
  832. int
  833. ).and_return(str)
  834. arguments_group.should_receive('add_argument').with_args(
  835. '--foo.bar',
  836. type=int,
  837. metavar='BAR',
  838. help='help 1',
  839. ).once()
  840. arguments_group.should_receive('add_argument').with_args(
  841. '--foo.baz',
  842. type=str,
  843. metavar='BAZ',
  844. help='help 2',
  845. ).once()
  846. flexmock(module).should_receive('add_array_element_arguments')
  847. module.add_arguments_from_schema(
  848. arguments_group=arguments_group,
  849. schema={
  850. 'type': 'object',
  851. 'properties': {
  852. 'foo': {
  853. 'type': 'object',
  854. 'properties': {
  855. 'bar': {'type': 'integer'},
  856. 'baz': {'type': 'str'},
  857. },
  858. }
  859. },
  860. },
  861. unparsed_arguments=(),
  862. )
  863. def test_add_arguments_from_schema_uses_first_non_null_type_from_multi_type_object():
  864. arguments_group = flexmock()
  865. flexmock(module).should_receive('make_argument_description').and_return('help 1')
  866. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(int)
  867. arguments_group.should_receive('add_argument').with_args(
  868. '--foo.bar',
  869. type=int,
  870. metavar='BAR',
  871. help='help 1',
  872. ).once()
  873. flexmock(module).should_receive('add_array_element_arguments')
  874. module.add_arguments_from_schema(
  875. arguments_group=arguments_group,
  876. schema={
  877. 'type': 'object',
  878. 'properties': {
  879. 'foo': {
  880. 'type': ['null', 'object', 'boolean'],
  881. 'properties': {
  882. 'bar': {'type': 'integer'},
  883. },
  884. }
  885. },
  886. },
  887. unparsed_arguments=(),
  888. )
  889. def test_add_arguments_from_schema_with_empty_multi_type_raises():
  890. arguments_group = flexmock()
  891. flexmock(module).should_receive('make_argument_description').and_return('help 1')
  892. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(int)
  893. arguments_group.should_receive('add_argument').never()
  894. flexmock(module).should_receive('add_array_element_arguments').never()
  895. with pytest.raises(ValueError):
  896. module.add_arguments_from_schema(
  897. arguments_group=arguments_group,
  898. schema={
  899. 'type': 'object',
  900. 'properties': {
  901. 'foo': {
  902. 'type': [],
  903. 'properties': {
  904. 'bar': {'type': 'integer'},
  905. },
  906. }
  907. },
  908. },
  909. unparsed_arguments=(),
  910. )
  911. def test_add_arguments_from_schema_with_propertyless_option_adds_flag():
  912. arguments_group = flexmock()
  913. flexmock(module).should_receive('make_argument_description').and_return('help')
  914. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(str)
  915. arguments_group.should_receive('add_argument').with_args(
  916. '--foo',
  917. type=str,
  918. metavar='FOO',
  919. help='help',
  920. ).once()
  921. flexmock(module).should_receive('add_array_element_arguments')
  922. module.add_arguments_from_schema(
  923. arguments_group=arguments_group,
  924. schema={
  925. 'type': 'object',
  926. 'properties': {
  927. 'foo': {
  928. 'type': 'object',
  929. }
  930. },
  931. },
  932. unparsed_arguments=(),
  933. )
  934. def test_add_arguments_from_schema_with_array_of_scalars_adds_multiple_flags():
  935. arguments_group = flexmock()
  936. flexmock(module).should_receive('make_argument_description').and_return('help')
  937. flexmock(module.borgmatic.config.schema).should_receive('parse_type').with_args(
  938. 'integer', object=str, array=str
  939. ).and_return(int)
  940. flexmock(module.borgmatic.config.schema).should_receive('parse_type').with_args(
  941. 'array', object=str, array=str
  942. ).and_return(str)
  943. arguments_group.should_receive('add_argument').with_args(
  944. '--foo[0]',
  945. type=int,
  946. metavar='FOO[0]',
  947. help='help',
  948. ).once()
  949. arguments_group.should_receive('add_argument').with_args(
  950. '--foo',
  951. type=str,
  952. metavar='FOO',
  953. help='help',
  954. ).once()
  955. flexmock(module).should_receive('add_array_element_arguments')
  956. module.add_arguments_from_schema(
  957. arguments_group=arguments_group,
  958. schema={
  959. 'type': 'object',
  960. 'properties': {
  961. 'foo': {
  962. 'type': 'array',
  963. 'items': {
  964. 'type': 'integer',
  965. },
  966. }
  967. },
  968. },
  969. unparsed_arguments=(),
  970. )
  971. def test_add_arguments_from_schema_with_array_of_objects_adds_multiple_flags():
  972. arguments_group = flexmock()
  973. flexmock(module).should_receive('make_argument_description').and_return('help 1').and_return(
  974. 'help 2'
  975. )
  976. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(
  977. int
  978. ).and_return(str)
  979. arguments_group.should_receive('add_argument').with_args(
  980. '--foo[0].bar',
  981. type=int,
  982. metavar='BAR',
  983. help='help 1',
  984. ).once()
  985. arguments_group.should_receive('add_argument').with_args(
  986. '--foo',
  987. type=str,
  988. metavar='FOO',
  989. help='help 2',
  990. ).once()
  991. flexmock(module).should_receive('add_array_element_arguments')
  992. flexmock(module).should_receive('add_array_element_arguments').with_args(
  993. arguments_group=arguments_group,
  994. unparsed_arguments=(),
  995. flag_name='foo[0].bar',
  996. ).once()
  997. module.add_arguments_from_schema(
  998. arguments_group=arguments_group,
  999. schema={
  1000. 'type': 'object',
  1001. 'properties': {
  1002. 'foo': {
  1003. 'type': 'array',
  1004. 'items': {
  1005. 'type': 'object',
  1006. 'properties': {
  1007. 'bar': {
  1008. 'type': 'integer',
  1009. }
  1010. },
  1011. },
  1012. }
  1013. },
  1014. },
  1015. unparsed_arguments=(),
  1016. )
  1017. def test_add_arguments_from_schema_with_boolean_adds_two_valueless_flags():
  1018. arguments_group = flexmock()
  1019. flexmock(module).should_receive('make_argument_description').and_return('help')
  1020. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(bool)
  1021. arguments_group.should_receive('add_argument').with_args(
  1022. '--foo',
  1023. action='store_true',
  1024. default=None,
  1025. help='help',
  1026. ).once()
  1027. arguments_group.should_receive('add_argument').with_args(
  1028. '--no-foo',
  1029. dest='foo',
  1030. action='store_false',
  1031. default=None,
  1032. help=object,
  1033. ).once()
  1034. flexmock(module).should_receive('add_array_element_arguments')
  1035. module.add_arguments_from_schema(
  1036. arguments_group=arguments_group,
  1037. schema={
  1038. 'type': 'object',
  1039. 'properties': {
  1040. 'foo': {
  1041. 'type': 'boolean',
  1042. }
  1043. },
  1044. },
  1045. unparsed_arguments=(),
  1046. )
  1047. def test_add_arguments_from_schema_with_nested_boolean_adds_two_valueless_flags():
  1048. arguments_group = flexmock()
  1049. flexmock(module).should_receive('make_argument_description').and_return('help')
  1050. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(bool)
  1051. arguments_group.should_receive('add_argument').with_args(
  1052. '--foo.bar.baz-quux',
  1053. action='store_true',
  1054. default=None,
  1055. help='help',
  1056. ).once()
  1057. arguments_group.should_receive('add_argument').with_args(
  1058. '--foo.bar.no-baz-quux',
  1059. dest='foo.bar.baz_quux',
  1060. action='store_false',
  1061. default=None,
  1062. help=object,
  1063. ).once()
  1064. flexmock(module).should_receive('add_array_element_arguments')
  1065. module.add_arguments_from_schema(
  1066. arguments_group=arguments_group,
  1067. schema={
  1068. 'type': 'object',
  1069. 'properties': {
  1070. 'baz_quux': {
  1071. 'type': 'boolean',
  1072. }
  1073. },
  1074. },
  1075. unparsed_arguments=(),
  1076. names=('foo', 'bar'),
  1077. )
  1078. def test_add_arguments_from_schema_with_boolean_with_name_prefixed_with_no_adds_two_valueless_flags_and_removes_the_no_for_one():
  1079. arguments_group = flexmock()
  1080. flexmock(module).should_receive('make_argument_description').and_return('help')
  1081. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(bool)
  1082. arguments_group.should_receive('add_argument').with_args(
  1083. '--no-foo',
  1084. action='store_true',
  1085. default=None,
  1086. help='help',
  1087. ).once()
  1088. arguments_group.should_receive('add_argument').with_args(
  1089. '--foo',
  1090. dest='no_foo',
  1091. action='store_false',
  1092. default=None,
  1093. help=object,
  1094. ).once()
  1095. flexmock(module).should_receive('add_array_element_arguments')
  1096. module.add_arguments_from_schema(
  1097. arguments_group=arguments_group,
  1098. schema={
  1099. 'type': 'object',
  1100. 'properties': {
  1101. 'no_foo': {
  1102. 'type': 'boolean',
  1103. }
  1104. },
  1105. },
  1106. unparsed_arguments=(),
  1107. )
  1108. def test_add_arguments_from_schema_skips_omitted_flag_name():
  1109. arguments_group = flexmock()
  1110. flexmock(module).should_receive('make_argument_description').and_return('help')
  1111. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(str)
  1112. arguments_group.should_receive('add_argument').with_args(
  1113. '--match-archives',
  1114. type=object,
  1115. metavar=object,
  1116. help=object,
  1117. ).never()
  1118. arguments_group.should_receive('add_argument').with_args(
  1119. '--foo',
  1120. type=str,
  1121. metavar='FOO',
  1122. help='help',
  1123. ).once()
  1124. flexmock(module).should_receive('add_array_element_arguments')
  1125. module.add_arguments_from_schema(
  1126. arguments_group=arguments_group,
  1127. schema={
  1128. 'type': 'object',
  1129. 'properties': {
  1130. 'match_archives': {
  1131. 'type': 'string',
  1132. },
  1133. 'foo': {
  1134. 'type': 'string',
  1135. },
  1136. },
  1137. },
  1138. unparsed_arguments=(),
  1139. )
  1140. def test_add_arguments_from_schema_rewrites_option_name_to_flag_name():
  1141. arguments_group = flexmock()
  1142. flexmock(module).should_receive('make_argument_description').and_return('help')
  1143. flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(str)
  1144. arguments_group.should_receive('add_argument').with_args(
  1145. '--foo-and-stuff',
  1146. type=str,
  1147. metavar='FOO_AND_STUFF',
  1148. help='help',
  1149. ).once()
  1150. flexmock(module).should_receive('add_array_element_arguments')
  1151. module.add_arguments_from_schema(
  1152. arguments_group=arguments_group,
  1153. schema={
  1154. 'type': 'object',
  1155. 'properties': {
  1156. 'foo_and_stuff': {
  1157. 'type': 'string',
  1158. },
  1159. },
  1160. },
  1161. unparsed_arguments=(),
  1162. )