def instrumentGooey(parser, **kwargs) -> Tuple[wx.App, wx.Frame, RGooey]: """ Context manager used during testing for setup/tear down of the WX infrastructure during subTests. Weirdness warning: this uses a globally reused wx.App instance. """ from gooey.tests import app if app == None: raise Exception( "App instance has not been created! This is likely due to " "you forgetting to add the magical import which makes all these " "tests work. See the module doc in gooey.tests.__init__ for guidance" ) buildspec = create_from_parser(parser, "", **gooey_params(**kwargs)) app, frame = bootstrap._build_app(buildspec, app) app.SetTopWindow(frame) try: # we need to run the main loop temporarily to get it to # apply any pending updates from the initial creation. # The UI state will be stale otherwise # this works because CallLater just enqueues the message to # be processed. The MainLoop starts running, picks it up, and # then exists wx.CallLater(1, app.ExitMainLoop) app.MainLoop() yield (app, frame, frame._instance) finally: frame.Destroy() del frame
def test_valid_font_weights(self): """ Asserting that only valid font-weights are allowable. """ all_valid_weights = range(100, 1001, 100) for weight in all_valid_weights: parser = ArgumentParser(description="test parser") params = gooey_params(terminal_font_weight=weight) buildspec = create_from_parser(parser, "", **params) self.assertEqual(buildspec['terminal_font_weight'], weight)
def test_program_description(self): """ Should use `program_description` if supplied, otherwise fallback to the description on the `parser` """ parser = ArgumentParser(description="Parser Description") # when supplied explicitly, we assign it as the description params = gooey_params(program_description='Custom Description') buildspec = create_from_parser(parser, "", **params) self.assertEqual(buildspec['program_description'], 'Custom Description') # when no explicit program_definition supplied, we fallback to the parser's description buildspec = create_from_parser(parser, "", **gooey_params()) self.assertEqual(buildspec['program_description'], 'Parser Description') # if no description is provided anywhere, we just set it to be an empty string. blank_parser = ArgumentParser() buildspec = create_from_parser(blank_parser, "", **gooey_params()) self.assertEqual(buildspec['program_description'], '')
def test_ignore_gooey(self): parser = GooeyParser() subs = parser.add_subparsers() foo = subs.add_parser('foo') foo.add_argument('a') foo.add_argument('b') foo.add_argument('p') bar = subs.add_parser('bar') bar.add_argument('a') bar.add_argument('b') bar.add_argument('z') control.bypass_gooey(gooey_params())(parser)
def Gooey(f=None, **gkwargs): """ Decoration entry point for the Gooey process. See types.GooeyParams for kwargs options """ params: GooeyParams = gooey_params(**gkwargs) @wraps(f) def inner(*args, **kwargs): parser_handler = choose_hander(params, gkwargs.get('cli', sys.argv)) # monkey patch parser ArgumentParser.original_parse_args = ArgumentParser.parse_args ArgumentParser.parse_args = parser_handler # return the wrapped, now monkey-patched, user function # to be later invoked return f(*args, **kwargs) def thunk(func): """ This just handles the case where the decorator is called with arguments (i.e. @Gooey(foo=bar) rather than @Gooey). Cause python is weird, when a decorator is called (e.g. @decorator()) rather than just declared (e.g. @decorator), in complete and utter defiance of what your lying eyes see, it changes from a higher order function, to a function that takes an arbitrary argument *and then* returns a higher order function. i.e. decorate :: (a -> b) -> (a -> b) decorate() :: c -> (a -> b) -> (a -> b) wat. """ return Gooey(func, **params) return inner if callable(f) else thunk
def test_validate_form(self): """ Testing the major validation cases we support. """ writer = MagicMock() exit = MagicMock() monkey_patch = control.validate_form(gooey_params(), write=writer, exit=exit) ArgumentParser.original_parse_args = ArgumentParser.parse_args ArgumentParser.parse_args = monkey_patch parser = GooeyParser() # examples: # ERROR: mismatched builtin type parser.add_argument('a', type=int, gooey_options={'initial_value': 'not-an-int'}) # ERROR: mismatched custom type parser.add_argument('b', type=custom_type, gooey_options={'initial_value': 'not-a-float'}) # ERROR: missing required positional arg parser.add_argument('c') # ERROR: missing required 'optional' arg parser.add_argument('--oc', required=True) # VALID: This is one of the bizarre cases which are possible # but don't make much sense. It should pass through as valid # because there's no way for us to send a 'not present optional value' parser.add_argument('--bo', action='store_true', required=True) # ERROR: a required mutex group, with no args supplied. # Should flag all as missing. group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--gp1-a', type=str) group.add_argument('--gp1-b', type=str) # ERROR: required mutex group with a default option but nothing # selected will still fail group2 = parser.add_mutually_exclusive_group(required=True) group2.add_argument('--gp2-a', type=str) group2.add_argument('--gp2-b', type=str, default='Heeeeyyyyy') # VALID: now, same as above, but now the option is actually enabled via # the initial selection. No error. group3 = parser.add_mutually_exclusive_group( required=True, gooey_options={'initial_selection': 1}) group3.add_argument('--gp3-a', type=str) group3.add_argument('--gp3-b', type=str, default='Heeeeyyyyy') # VALID: optional mutex. group4 = parser.add_mutually_exclusive_group() group4.add_argument('--gp4-a', type=str) group4.add_argument('--gp4-b', type=str) # VALID: arg present and type satisfied parser.add_argument('ga', type=str, gooey_options={'initial_value': 'whatever'}) # VALID: arg present and custom type satisfied parser.add_argument('gb', type=custom_type, gooey_options={'initial_value': '1234'}) # VALID: optional parser.add_argument('--gc') # now we're adding the same with instrumentGooey(parser, target='test') as (app, frame, gapp): # we start off with no errors self.assertFalse(s.has_errors(gapp.fullState())) # now we feed our form-validation cmd = s.buildFormValidationCmd(gapp.fullState()) asdf = shlex.split(cmd)[1:] parser.parse_args(shlex.split(cmd)[1:]) assert writer.called assert exit.called result = deserialize_inbound(writer.call_args[0][0].encode('utf-8'), 'utf-8') # Host->Gooey communication is all done over the PublicGooeyState schema # as such, we coarsely validate it's shape here validate_public_state(result) # manually merging the two states back together nextState = s.mergeExternalState(gapp.fullState(), result) # and now we find that we have errors! self.assertTrue(s.has_errors(nextState)) items = s.activeFormState(nextState) self.assertIn('invalid literal', get_by_id(items, 'a')['error']) self.assertIn('KABOOM!', get_by_id(items, 'b')['error']) self.assertIn('required', get_by_id(items, 'c')['error']) self.assertIn('required', get_by_id(items, 'oc')['error']) for item in get_by_id(items, 'group_gp1_a_gp1_b')['options']: self.assertIsNotNone(item['error']) for item in get_by_id(items, 'group_gp2_a_gp2_b')['options']: self.assertIsNotNone(item['error']) for item in get_by_id(items, 'group_gp3_a_gp3_b')['options']: self.assertIsNone(item['error']) # should be None, since this one was entirely optional for item in get_by_id(items, 'group_gp4_a_gp4_b')['options']: self.assertIsNone(item['error']) self.assertIsNone(get_by_id(items, 'bo')['error']) self.assertIsNone(get_by_id(items, 'ga')['error']) self.assertIsNone(get_by_id(items, 'gb')['error']) self.assertIsNone(get_by_id(items, 'gc')['error'])
def test_subparsers(self): """ Making sure that subparsers are handled correctly and all validations still work as expected. """ writer = MagicMock() exit = MagicMock() monkey_patch = control.validate_form(gooey_params(), write=writer, exit=exit) ArgumentParser.original_parse_args = ArgumentParser.parse_args ArgumentParser.parse_args = monkey_patch def build_parser(): # we build a new parser for each subtest # since we monkey patch the hell out of it # each time parser = GooeyParser() subs = parser.add_subparsers() foo = subs.add_parser('foo') foo.add_argument('a') foo.add_argument('b') foo.add_argument('p') bar = subs.add_parser('bar') bar.add_argument('a') bar.add_argument('b') bar.add_argument('z') return parser parser = build_parser() with instrumentGooey(parser, target='test') as (app, frame, gapp): with self.subTest('first subparser'): # we start off with no errors self.assertFalse(s.has_errors(gapp.fullState())) cmd = s.buildFormValidationCmd(gapp.fullState()) parser.parse_args(shlex.split(cmd)[1:]) assert writer.called assert exit.called result = deserialize_inbound( writer.call_args[0][0].encode('utf-8'), 'utf-8') nextState = s.mergeExternalState(gapp.fullState(), result) # by default, the subparser defined first, 'foo', is selected. self.assertIn('foo', nextState['forms']) # and we should find its attributes expected = {'a', 'b', 'p'} actual = {x['id'] for x in nextState['forms']['foo']} self.assertEqual(expected, actual) parser = build_parser() with instrumentGooey(parser, target='test') as (app, frame, gapp): with self.subTest('Second subparser'): # mocking a 'selection change' event to select # the second subparser event = MagicMock() event.Selection = 1 gapp.handleSelectAction(event) # Flushing our events by running the main loop wx.CallLater(1, app.ExitMainLoop) app.MainLoop() cmd = s.buildFormValidationCmd(gapp.fullState()) parser.parse_args(shlex.split(cmd)[1:]) assert writer.called assert exit.called result = deserialize_inbound( writer.call_args[0][0].encode('utf-8'), 'utf-8') nextState = s.mergeExternalState(gapp.fullState(), result) # Now our second subparer, 'bar', should be present. self.assertIn('bar', nextState['forms']) # and we should find its attributes expected = {'a', 'b', 'z'} actual = {x['id'] for x in nextState['forms']['bar']} self.assertEqual(expected, actual)
def test_invalid_font_weights_throw_error(self): parser = ArgumentParser(description="test parser") with self.assertRaises(ValueError): invalid_weight = 9123 params = gooey_params(terminal_font_weight=invalid_weight) buildspec = create_from_parser(parser, "", **params)
def test_font_weight_defaults_to_normal(self): parser = ArgumentParser(description="test parser") # no font_weight explicitly provided buildspec = create_from_parser(parser, "", **gooey_params()) self.assertEqual(buildspec['terminal_font_weight'], constants.FONTWEIGHT_NORMAL)