gridparse 1.5.0__tar.gz → 1.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: gridparse
3
- Version: 1.5.0
3
+ Version: 1.5.2
4
4
  Summary: Grid search directly from argparse
5
5
  Home-page: https://github.com/gchochla/gridparse
6
6
  Author: Georgios Chochlakis
@@ -8,6 +8,328 @@ from omegaconf import OmegaConf
8
8
  from gridparse.utils import list_as_dashed_str, strbool
9
9
 
10
10
 
11
+ class AuxArgumentParser(argparse.ArgumentParser):
12
+ """Overwritten only to collect the argument names that
13
+ are specified in the command line."""
14
+
15
+ def parse_known_args(
16
+ self, args=None, namespace=None
17
+ ) -> Tuple[argparse.Namespace, List[str]]:
18
+ """Overwritten to collect the argument names that
19
+ are specified in the command line."""
20
+ if args is None:
21
+ # args default to the system args
22
+ args = argparse._sys.argv[1:]
23
+ else:
24
+ # make sure that args are mutable
25
+ args = list(args)
26
+
27
+ # default Namespace built from parser defaults
28
+ if namespace is None:
29
+ namespace = argparse.Namespace()
30
+
31
+ if not hasattr(namespace, "___specified_args___"):
32
+ setattr(namespace, "___specified_args___", set())
33
+
34
+ # add any action defaults that aren't present
35
+ for action in self._actions:
36
+ if action.dest is not argparse.SUPPRESS:
37
+ if not hasattr(namespace, action.dest):
38
+ if action.default is not argparse.SUPPRESS:
39
+ setattr(namespace, action.dest, action.default)
40
+
41
+ # add any parser defaults that aren't present
42
+ for dest in self._defaults:
43
+ if not hasattr(namespace, dest):
44
+ setattr(namespace, dest, self._defaults[dest])
45
+
46
+ # parse the arguments and exit if there are any errors
47
+ if self.exit_on_error:
48
+ try:
49
+ namespace, args = self._parse_known_args(args, namespace)
50
+ except argparse.ArgumentError:
51
+ err = argparse._sys.exc_info()[1]
52
+ self.error(str(err))
53
+ else:
54
+ namespace, args = self._parse_known_args(args, namespace)
55
+
56
+ if hasattr(namespace, argparse._UNRECOGNIZED_ARGS_ATTR):
57
+ args.extend(getattr(namespace, argparse._UNRECOGNIZED_ARGS_ATTR))
58
+ delattr(namespace, argparse._UNRECOGNIZED_ARGS_ATTR)
59
+
60
+ return namespace, args
61
+
62
+ def _parse_known_args(
63
+ self, arg_strings, namespace
64
+ ) -> Tuple[List[argparse.Namespace], List[str]]:
65
+ """Overwritten to collect the argument names that
66
+ are specified in the command line."""
67
+
68
+ # replace arg strings that are file references
69
+ if self.fromfile_prefix_chars is not None:
70
+ arg_strings = self._read_args_from_files(arg_strings)
71
+
72
+ # map all mutually exclusive arguments to the other arguments
73
+ # they can't occur with
74
+ action_conflicts = {}
75
+ for mutex_group in self._mutually_exclusive_groups:
76
+ group_actions = mutex_group._group_actions
77
+ for i, mutex_action in enumerate(mutex_group._group_actions):
78
+ conflicts = action_conflicts.setdefault(mutex_action, [])
79
+ conflicts.extend(group_actions[:i])
80
+ conflicts.extend(group_actions[i + 1 :])
81
+
82
+ # find all option indices, and determine the arg_string_pattern
83
+ # which has an 'O' if there is an option at an index,
84
+ # an 'A' if there is an argument, or a '-' if there is a '--'
85
+ option_string_indices = {}
86
+ arg_string_pattern_parts = []
87
+ arg_strings_iter = iter(arg_strings)
88
+ for i, arg_string in enumerate(arg_strings_iter):
89
+
90
+ # all args after -- are non-options
91
+ if arg_string == '--':
92
+ arg_string_pattern_parts.append('-')
93
+ for arg_string in arg_strings_iter:
94
+ arg_string_pattern_parts.append('A')
95
+
96
+ # otherwise, add the arg to the arg strings
97
+ # and note the index if it was an option
98
+ else:
99
+ option_tuple = self._parse_optional(arg_string)
100
+ if option_tuple is None:
101
+ pattern = 'A'
102
+ else:
103
+ option_string_indices[i] = option_tuple
104
+ pattern = 'O'
105
+ arg_string_pattern_parts.append(pattern)
106
+
107
+ # join the pieces together to form the pattern
108
+ arg_strings_pattern = ''.join(arg_string_pattern_parts)
109
+
110
+ # converts arg strings to the appropriate and then takes the action
111
+ seen_actions = set()
112
+ seen_non_default_actions = set()
113
+
114
+ def take_action(action, argument_strings, option_string=None):
115
+ seen_actions.add(action)
116
+ argument_values = self._get_values(action, argument_strings)
117
+
118
+ # error if this argument is not allowed with other previously
119
+ # seen arguments, assuming that actions that use the default
120
+ # value don't really count as "present"
121
+ if argument_values is not action.default:
122
+ seen_non_default_actions.add(action)
123
+ for conflict_action in action_conflicts.get(action, []):
124
+ if conflict_action in seen_non_default_actions:
125
+ msg = argparse._('not allowed with argument %s')
126
+ action_name = argparse._get_action_name(conflict_action)
127
+ raise argparse.ArgumentError(action, msg % action_name)
128
+
129
+ # take the action if we didn't receive a SUPPRESS value
130
+ # (e.g. from a default)
131
+ if argument_values is not argparse.SUPPRESS:
132
+ action(self, namespace, argument_values, option_string)
133
+ namespace.___specified_args___.add(action.dest)
134
+
135
+ # function to convert arg_strings into an optional action
136
+ def consume_optional(start_index):
137
+
138
+ # get the optional identified at this index
139
+ option_tuple = option_string_indices[start_index]
140
+ action, option_string, explicit_arg = option_tuple
141
+
142
+ # identify additional optionals in the same arg string
143
+ # (e.g. -xyz is the same as -x -y -z if no args are required)
144
+ match_argument = self._match_argument
145
+ action_tuples = []
146
+ while True:
147
+
148
+ # if we found no optional action, skip it
149
+ if action is None:
150
+ extras.append(arg_strings[start_index])
151
+ return start_index + 1
152
+
153
+ # if there is an explicit argument, try to match the
154
+ # optional's string arguments to only this
155
+ if explicit_arg is not None:
156
+ arg_count = match_argument(action, 'A')
157
+
158
+ # if the action is a single-dash option and takes no
159
+ # arguments, try to parse more single-dash options out
160
+ # of the tail of the option string
161
+ chars = self.prefix_chars
162
+ if (
163
+ arg_count == 0
164
+ and option_string[1] not in chars
165
+ and explicit_arg != ''
166
+ ):
167
+ action_tuples.append((action, [], option_string))
168
+ char = option_string[0]
169
+ option_string = char + explicit_arg[0]
170
+ new_explicit_arg = explicit_arg[1:] or None
171
+ optionals_map = self._option_string_actions
172
+ if option_string in optionals_map:
173
+ action = optionals_map[option_string]
174
+ explicit_arg = new_explicit_arg
175
+ else:
176
+ msg = argparse._('ignored explicit argument %r')
177
+ raise argparse.ArgumentError(
178
+ action, msg % explicit_arg
179
+ )
180
+
181
+ # if the action expect exactly one argument, we've
182
+ # successfully matched the option; exit the loop
183
+ elif arg_count == 1:
184
+ stop = start_index + 1
185
+ args = [explicit_arg]
186
+ action_tuples.append((action, args, option_string))
187
+ break
188
+
189
+ # error if a double-dash option did not use the
190
+ # explicit argument
191
+ else:
192
+ msg = argparse._('ignored explicit argument %r')
193
+ raise argparse.ArgumentError(action, msg % explicit_arg)
194
+
195
+ # if there is no explicit argument, try to match the
196
+ # optional's string arguments with the following strings
197
+ # if successful, exit the loop
198
+ else:
199
+ start = start_index + 1
200
+ selected_patterns = arg_strings_pattern[start:]
201
+ arg_count = match_argument(action, selected_patterns)
202
+ stop = start + arg_count
203
+ args = arg_strings[start:stop]
204
+ action_tuples.append((action, args, option_string))
205
+ break
206
+
207
+ # add the Optional to the list and return the index at which
208
+ # the Optional's string args stopped
209
+ assert action_tuples
210
+ for action, args, option_string in action_tuples:
211
+ take_action(action, args, option_string)
212
+ return stop
213
+
214
+ # the list of Positionals left to be parsed; this is modified
215
+ # by consume_positionals()
216
+ positionals = self._get_positional_actions()
217
+
218
+ # function to convert arg_strings into positional actions
219
+ def consume_positionals(start_index):
220
+ # match as many Positionals as possible
221
+ match_partial = self._match_arguments_partial
222
+ selected_pattern = arg_strings_pattern[start_index:]
223
+ arg_counts = match_partial(positionals, selected_pattern)
224
+
225
+ # slice off the appropriate arg strings for each Positional
226
+ # and add the Positional and its args to the list
227
+ for action, arg_count in zip(positionals, arg_counts):
228
+ args = arg_strings[start_index : start_index + arg_count]
229
+ start_index += arg_count
230
+ take_action(action, args)
231
+
232
+ # slice off the Positionals that we just parsed and return the
233
+ # index at which the Positionals' string args stopped
234
+ positionals[:] = positionals[len(arg_counts) :]
235
+ return start_index
236
+
237
+ # consume Positionals and Optionals alternately, until we have
238
+ # passed the last option string
239
+ extras = []
240
+ start_index = 0
241
+ if option_string_indices:
242
+ max_option_string_index = max(option_string_indices)
243
+ else:
244
+ max_option_string_index = -1
245
+ while start_index <= max_option_string_index:
246
+
247
+ # consume any Positionals preceding the next option
248
+ next_option_string_index = min(
249
+ [
250
+ index
251
+ for index in option_string_indices
252
+ if index >= start_index
253
+ ]
254
+ )
255
+ if start_index != next_option_string_index:
256
+ positionals_end_index = consume_positionals(start_index)
257
+
258
+ # only try to parse the next optional if we didn't consume
259
+ # the option string during the positionals parsing
260
+ if positionals_end_index > start_index:
261
+ start_index = positionals_end_index
262
+ continue
263
+ else:
264
+ start_index = positionals_end_index
265
+
266
+ # if we consumed all the positionals we could and we're not
267
+ # at the index of an option string, there were extra arguments
268
+ if start_index not in option_string_indices:
269
+ strings = arg_strings[start_index:next_option_string_index]
270
+ extras.extend(strings)
271
+ start_index = next_option_string_index
272
+
273
+ # consume the next optional and any arguments for it
274
+ start_index = consume_optional(start_index)
275
+
276
+ # consume any positionals following the last Optional
277
+ stop_index = consume_positionals(start_index)
278
+
279
+ # if we didn't consume all the argument strings, there were extras
280
+ extras.extend(arg_strings[stop_index:])
281
+
282
+ # make sure all required actions were present and also convert
283
+ # action defaults which were not given as arguments
284
+ required_actions = []
285
+ for action in self._actions:
286
+ if action not in seen_actions:
287
+ if action.required:
288
+ required_actions.append(argparse._get_action_name(action))
289
+ else:
290
+ # Convert action default now instead of doing it before
291
+ # parsing arguments to avoid calling convert functions
292
+ # twice (which may fail) if the argument was given, but
293
+ # only if it was defined already in the namespace
294
+ if (
295
+ action.default is not None
296
+ and isinstance(action.default, str)
297
+ and hasattr(namespace, action.dest)
298
+ and action.default is getattr(namespace, action.dest)
299
+ ):
300
+ setattr(
301
+ namespace,
302
+ action.dest,
303
+ self._get_value(action, action.default),
304
+ )
305
+
306
+ if required_actions:
307
+ self.error(
308
+ argparse._('the following arguments are required: %s')
309
+ % ', '.join(required_actions)
310
+ )
311
+
312
+ # make sure all required groups had one option present
313
+ for group in self._mutually_exclusive_groups:
314
+ if group.required:
315
+ for action in group._group_actions:
316
+ if action in seen_non_default_actions:
317
+ break
318
+
319
+ # if no actions were used, report the error
320
+ else:
321
+ names = [
322
+ argparse._get_action_name(action)
323
+ for action in group._group_actions
324
+ if action.help is not argparse.SUPPRESS
325
+ ]
326
+ msg = argparse._('one of the arguments %s is required')
327
+ self.error(msg % ' '.join(names))
328
+
329
+ # return the updated namespace, the extra arguments, and the epxlicitly specified args
330
+ return namespace, extras
331
+
332
+
11
333
  # overwritten to fix issue in __call__
12
334
  class _GridSubparsersAction(argparse._SubParsersAction):
13
335
  def __call__(
@@ -57,7 +379,13 @@ class _GridSubparsersAction(argparse._SubParsersAction):
57
379
  subnamespaces, arg_strings = parser.parse_known_args(arg_strings, None)
58
380
  for subnamespace in subnamespaces:
59
381
  new_namespace = deepcopy(namespace)
382
+ new_namespace.___specified_args___.update(
383
+ subnamespace.___specified_args___
384
+ )
385
+
60
386
  for key, value in vars(subnamespace).items():
387
+ if key == "___specified_args___":
388
+ continue
61
389
  setattr(new_namespace, key, value)
62
390
  namespaces.append(new_namespace)
63
391
 
@@ -81,7 +409,7 @@ class _GridActionsContainer(argparse._ActionsContainer):
81
409
  self.register("action", "parsers", _GridSubparsersAction)
82
410
 
83
411
 
84
- class GridArgumentParser(_GridActionsContainer, argparse.ArgumentParser):
412
+ class GridArgumentParser(_GridActionsContainer, AuxArgumentParser):
85
413
  """ArgumentParser that supports grid search.
86
414
 
87
415
  It transforms the following arguments in the corresponding way:
@@ -163,7 +491,7 @@ class GridArgumentParser(_GridActionsContainer, argparse.ArgumentParser):
163
491
 
164
492
  def __init__(self, retain_config_filename: bool = False, *args, **kwargs):
165
493
  """Initializes the GridArgumentParser.
166
-
494
+
167
495
  Args:
168
496
  retain_config_filename: whether to keep the `gridparse-config` argument
169
497
  in the namespace or not.
@@ -180,9 +508,9 @@ class GridArgumentParser(_GridActionsContainer, argparse.ArgumentParser):
180
508
  "Values will be used if not provided in the command line.",
181
509
  )
182
510
 
183
-
184
511
  def parse_args(self, *args, **kwargs):
185
512
  vals = super().parse_args(*args, **kwargs)
513
+
186
514
  # hacky way to return namespaces in subparser
187
515
  if "___namespaces___" in vals[0]:
188
516
  vals = [ns for subps_ns in vals for ns in subps_ns.___namespaces___]
@@ -220,21 +548,25 @@ class GridArgumentParser(_GridActionsContainer, argparse.ArgumentParser):
220
548
  cfg = {}
221
549
  if ns.gridparse_config is not None:
222
550
  # reverse for priority to originally first configs
223
- for potential_fn in reversed(getattr(ns, "gridparse_config", [])):
551
+ for potential_fn in reversed(
552
+ getattr(ns, "gridparse_config", [])
553
+ ):
224
554
  if os.path.isfile(potential_fn):
225
555
  cfg = OmegaConf.merge(cfg, OmegaConf.load(potential_fn))
226
556
 
227
557
  for arg in cfg:
228
558
  if not hasattr(ns, arg):
229
559
  continue
230
- if getattr(ns, arg) is None:
231
- setattr(ns, arg, getattr(cfg, arg))
560
+ if arg not in ns.___specified_args___:
561
+ setattr(ns, arg, cfg.get(arg))
232
562
 
233
563
  if not self._retain_config_filename:
234
564
  delattr(ns, "gridparse_config")
235
565
 
566
+ delattr(ns, "___specified_args___")
567
+
236
568
  return vals
237
-
569
+
238
570
  def _check_value(self, action, value):
239
571
  """Overwrites `_check_value` to support grid search with `None`s."""
240
572
  # converted value must be one of the choices (if specified)
@@ -243,7 +575,7 @@ class GridArgumentParser(_GridActionsContainer, argparse.ArgumentParser):
243
575
  ): # allows value to be None without error
244
576
  args = {
245
577
  "value": value,
246
- "choices": ", ".join(map(repr, action.choices))
578
+ "choices": ", ".join(map(repr, action.choices)),
247
579
  }
248
580
  msg = argparse._(
249
581
  "invalid choice: %(value)r (choose from %(choices)s)"
@@ -263,12 +595,12 @@ class GridArgumentParser(_GridActionsContainer, argparse.ArgumentParser):
263
595
  default == arg_string or arg_string is None
264
596
  ):
265
597
  return default
266
-
598
+
267
599
  # if arg_string is "args.X" value,
268
600
  # then set up value so that X is grabbed from the same namespace later
269
601
  if arg_string.startswith("args."):
270
602
  return arg_string
271
-
603
+
272
604
  # if arg_string is "_None_", then return None
273
605
  if (
274
606
  arg_string == "_None_"
@@ -304,16 +636,18 @@ class GridArgumentParser(_GridActionsContainer, argparse.ArgumentParser):
304
636
  @staticmethod
305
637
  def _add_split_in_arg(arg: str, split: str) -> str:
306
638
  """Adds the `split` to the name of the argument `arg`."""
307
-
639
+
308
640
  if "_" in arg:
309
- # if the user uses "_" as a delimiter, we use that
641
+ # if the user uses "_" as a delimiter, we use that
310
642
  delim = "_"
311
643
  else:
312
- # otherwise, we use "-" (no necessary to check for it, e.g., could be CamelCase)
644
+ # otherwise, we use "-" (no necessary to check for it, e.g., could be CamelCase)
313
645
  delim = "-"
314
646
  return split + delim + arg
315
647
 
316
- def add_argument(self, *args, **kwargs) -> Union[argparse.Action, List[argparse.Action]]:
648
+ def add_argument(
649
+ self, *args, **kwargs
650
+ ) -> Union[argparse.Action, List[argparse.Action]]:
317
651
  """Augments `add_argument` to support grid search.
318
652
  For parameters that are searchable, provide specification
319
653
  for a single value, and set the new argument `searchable`
@@ -346,10 +680,14 @@ class GridArgumentParser(_GridActionsContainer, argparse.ArgumentParser):
346
680
  while cp_args[0][i] in self.prefix_chars:
347
681
  i += 1
348
682
 
349
- cp_args[0] = cp_args[0][:i] + self._add_split_in_arg(cp_args[0][i:], split)
683
+ cp_args[0] = cp_args[0][:i] + self._add_split_in_arg(
684
+ cp_args[0][i:], split
685
+ )
350
686
 
351
687
  else:
352
- cp_kwargs["dest"] = self._add_split_in_arg(cp_kwargs["dest"], split)
688
+ cp_kwargs["dest"] = self._add_split_in_arg(
689
+ cp_kwargs["dest"], split
690
+ )
353
691
 
354
692
  actions.append(self.add_argument(*cp_args, **cp_kwargs))
355
693
 
@@ -388,7 +726,7 @@ class GridArgumentParser(_GridActionsContainer, argparse.ArgumentParser):
388
726
  self.subspaces = {}
389
727
  self.cnt = 0
390
728
  self.parent = parent
391
-
729
+
392
730
  def add_arg(self, arg: str):
393
731
  if arg == "{":
394
732
  new_subspace = GridArgumentParser.Subspace(self)
@@ -401,7 +739,7 @@ class GridArgumentParser(_GridActionsContainer, argparse.ArgumentParser):
401
739
  self.args[self.cnt] = arg
402
740
  self.cnt += 1
403
741
  return self
404
-
742
+
405
743
  def parse_paths(self) -> List[List[str]]:
406
744
 
407
745
  if not self.subspaces:
@@ -419,9 +757,9 @@ class GridArgumentParser(_GridActionsContainer, argparse.ArgumentParser):
419
757
  this_subspace_args.append(self.args[i])
420
758
  for path in cumulative_args:
421
759
  path.append(self.args[i])
422
-
760
+
423
761
  return cumulative_args
424
-
762
+
425
763
  def __repr__(self) -> str:
426
764
  repr = "Subspace("
427
765
  for i in range(self.cnt):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: gridparse
3
- Version: 1.5.0
3
+ Version: 1.5.2
4
4
  Summary: Grid search directly from argparse
5
5
  Home-page: https://github.com/gchochla/gridparse
6
6
  Author: Georgios Chochlakis
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "gridparse"
3
- version = "1.5.0"
3
+ version = "1.5.2"
4
4
  description = "Grid search directly from argparse"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.7"
@@ -31,7 +31,6 @@ classifiers = [
31
31
  "Programming Language :: Python :: 3 :: Only",
32
32
  ]
33
33
 
34
-
35
34
  dependencies = [
36
35
  "omegaconf",
37
36
  ]
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="gridparse",
8
- version="1.5.0",
8
+ version="1.5.2",
9
9
  description="Grid search directly from argparse",
10
10
  author="Georgios Chochlakis",
11
11
  author_email="georgioschochlakis@gmail.com",
File without changes
File without changes
File without changes
File without changes
File without changes