cli2 4.1.4__tar.gz → 4.2.1__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.
Files changed (56) hide show
  1. {cli2-4.1.4/cli2.egg-info → cli2-4.2.1}/PKG-INFO +1 -1
  2. {cli2-4.1.4 → cli2-4.2.1}/cli2/__init__.py +2 -0
  3. {cli2-4.1.4 → cli2-4.2.1}/cli2/cli.py +14 -6
  4. {cli2-4.1.4 → cli2-4.2.1}/cli2/client.py +106 -45
  5. {cli2-4.1.4 → cli2-4.2.1/cli2.egg-info}/PKG-INFO +1 -1
  6. {cli2-4.1.4 → cli2-4.2.1}/setup.py +1 -1
  7. {cli2-4.1.4 → cli2-4.2.1}/tests/test_client.py +36 -10
  8. {cli2-4.1.4 → cli2-4.2.1}/MANIFEST.in +0 -0
  9. {cli2-4.1.4 → cli2-4.2.1}/README.rst +0 -0
  10. {cli2-4.1.4 → cli2-4.2.1}/classifiers.txt +0 -0
  11. {cli2-4.1.4 → cli2-4.2.1}/cli2/ansible/__init__.py +0 -0
  12. {cli2-4.1.4 → cli2-4.2.1}/cli2/ansible/action.py +0 -0
  13. {cli2-4.1.4 → cli2-4.2.1}/cli2/ansible/playbook.py +0 -0
  14. {cli2-4.1.4 → cli2-4.2.1}/cli2/ansible/variables.py +0 -0
  15. {cli2-4.1.4 → cli2-4.2.1}/cli2/asyncio.py +0 -0
  16. {cli2-4.1.4 → cli2-4.2.1}/cli2/cli2.py +0 -0
  17. {cli2-4.1.4 → cli2-4.2.1}/cli2/colors.py +0 -0
  18. {cli2-4.1.4 → cli2-4.2.1}/cli2/configuration.py +0 -0
  19. {cli2-4.1.4 → cli2-4.2.1}/cli2/decorators.py +0 -0
  20. {cli2-4.1.4 → cli2-4.2.1}/cli2/display.py +0 -0
  21. {cli2-4.1.4 → cli2-4.2.1}/cli2/examples/__init__.py +0 -0
  22. {cli2-4.1.4 → cli2-4.2.1}/cli2/examples/client.py +0 -0
  23. {cli2-4.1.4 → cli2-4.2.1}/cli2/examples/conf.py +0 -0
  24. {cli2-4.1.4 → cli2-4.2.1}/cli2/examples/example.py +0 -0
  25. {cli2-4.1.4 → cli2-4.2.1}/cli2/examples/example_obj.py +0 -0
  26. {cli2-4.1.4 → cli2-4.2.1}/cli2/examples/nesting.py +0 -0
  27. {cli2-4.1.4 → cli2-4.2.1}/cli2/examples/obj.py +0 -0
  28. {cli2-4.1.4 → cli2-4.2.1}/cli2/examples/obj2.py +0 -0
  29. {cli2-4.1.4 → cli2-4.2.1}/cli2/examples/test.py +0 -0
  30. {cli2-4.1.4 → cli2-4.2.1}/cli2/lock.py +0 -0
  31. {cli2-4.1.4 → cli2-4.2.1}/cli2/log.py +0 -0
  32. {cli2-4.1.4 → cli2-4.2.1}/cli2/node.py +0 -0
  33. {cli2-4.1.4 → cli2-4.2.1}/cli2/sphinx.py +0 -0
  34. {cli2-4.1.4 → cli2-4.2.1}/cli2/table.py +0 -0
  35. {cli2-4.1.4 → cli2-4.2.1}/cli2/test.py +0 -0
  36. {cli2-4.1.4 → cli2-4.2.1}/cli2.egg-info/SOURCES.txt +0 -0
  37. {cli2-4.1.4 → cli2-4.2.1}/cli2.egg-info/dependency_links.txt +0 -0
  38. {cli2-4.1.4 → cli2-4.2.1}/cli2.egg-info/entry_points.txt +0 -0
  39. {cli2-4.1.4 → cli2-4.2.1}/cli2.egg-info/requires.txt +0 -0
  40. {cli2-4.1.4 → cli2-4.2.1}/cli2.egg-info/top_level.txt +0 -0
  41. {cli2-4.1.4 → cli2-4.2.1}/setup.cfg +0 -0
  42. {cli2-4.1.4 → cli2-4.2.1}/tests/test_ansible.py +0 -0
  43. {cli2-4.1.4 → cli2-4.2.1}/tests/test_ansible_variables.py +0 -0
  44. {cli2-4.1.4 → cli2-4.2.1}/tests/test_asyncio.py +0 -0
  45. {cli2-4.1.4 → cli2-4.2.1}/tests/test_cli.py +0 -0
  46. {cli2-4.1.4 → cli2-4.2.1}/tests/test_command.py +0 -0
  47. {cli2-4.1.4 → cli2-4.2.1}/tests/test_configuration.py +0 -0
  48. {cli2-4.1.4 → cli2-4.2.1}/tests/test_decorators.py +0 -0
  49. {cli2-4.1.4 → cli2-4.2.1}/tests/test_display.py +0 -0
  50. {cli2-4.1.4 → cli2-4.2.1}/tests/test_entry_point.py +0 -0
  51. {cli2-4.1.4 → cli2-4.2.1}/tests/test_group.py +0 -0
  52. {cli2-4.1.4 → cli2-4.2.1}/tests/test_inject.py +0 -0
  53. {cli2-4.1.4 → cli2-4.2.1}/tests/test_lock.py +0 -0
  54. {cli2-4.1.4 → cli2-4.2.1}/tests/test_node.py +0 -0
  55. {cli2-4.1.4 → cli2-4.2.1}/tests/test_restful.py +0 -0
  56. {cli2-4.1.4 → cli2-4.2.1}/tests/test_table.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cli2
3
- Version: 4.1.4
3
+ Version: 4.2.1
4
4
  Summary: image:: https://yourlabs.io/oss/cli2/badges/master/pipeline.svg
5
5
  Home-page: https://yourlabs.io/oss/cli2
6
6
  Author: James Pic
@@ -24,11 +24,13 @@ try:
24
24
  FieldValueError,
25
25
  FieldExternalizeError,
26
26
  Client,
27
+ ClientCommand,
27
28
  DateTimeField,
28
29
  Field,
29
30
  Handler,
30
31
  JSONStringField,
31
32
  Model,
33
+ ModelCommand,
32
34
  Paginator,
33
35
  Related,
34
36
  )
@@ -658,6 +658,13 @@ class Command(EntryPoint, dict):
658
658
  self.exit_code = 1
659
659
  return self.help(missing=missing)
660
660
 
661
+ await self.factories_resolve()
662
+
663
+ result = self.call(*self.bound.args, **self.bound.kwargs)
664
+ return await async_resolve(result, output=True)
665
+
666
+ async def factories_resolve(self):
667
+ """ Resolve all factories values. """
661
668
  factories = self.values(factories=True)
662
669
  if factories:
663
670
  results = await asyncio.gather(*[
@@ -667,9 +674,6 @@ class Command(EntryPoint, dict):
667
674
  for _, arg in enumerate(factories):
668
675
  arg.value = results[_]
669
676
 
670
- result = self.call(*self.bound.args, **self.bound.kwargs)
671
- return await async_resolve(result, output=True)
672
-
673
677
  def ordered(self, factories=False):
674
678
  """
675
679
  Order the parameters by priority.
@@ -1153,7 +1157,7 @@ class Argument:
1153
1157
  self.value = self.cast(value)
1154
1158
  return True
1155
1159
 
1156
- def factory_value(self):
1160
+ def factory_value(self, cmd=None):
1157
1161
  """
1158
1162
  Run the factory function and return the value.
1159
1163
 
@@ -1164,11 +1168,15 @@ class Argument:
1164
1168
 
1165
1169
  It will forward any argument to the factory function if detected in
1166
1170
  it's signature, except for ``*args`` and ``**kwargs``.
1171
+
1172
+ :param cmd: Override for :py:attr:`cmd`, useful for getting the factory
1173
+ value of an argument from another class (advanced).
1167
1174
  """
1168
1175
  kwargs = dict()
1176
+ cmd = cmd or self.cmd
1169
1177
  sig = inspect.signature(self.factory)
1170
1178
  if 'cmd' in sig.parameters:
1171
- kwargs['cmd'] = self.cmd
1179
+ kwargs['cmd'] = cmd
1172
1180
  if 'arg' in sig.parameters:
1173
1181
  kwargs['arg'] = self
1174
1182
 
@@ -1176,7 +1184,7 @@ class Argument:
1176
1184
  inspect.Parameter.VAR_KEYWORD,
1177
1185
  inspect.Parameter.VAR_POSITIONAL,
1178
1186
  )
1179
- for key, arg in self.cmd.items():
1187
+ for key, arg in cmd.items():
1180
1188
  if arg.param.kind in excluded:
1181
1189
  continue
1182
1190
  if key in sig.parameters:
@@ -23,7 +23,7 @@ except ImportError:
23
23
 
24
24
  from . import display
25
25
  from .asyncio import async_resolve
26
- from .cli import Command, Group, cmd, hide
26
+ from .cli import Argument, Command, Group, cmd, hide
27
27
  from .colors import colors
28
28
  from .log import log
29
29
 
@@ -661,36 +661,43 @@ class ModelCommand(Command):
661
661
  super().__init__(target, *args, **kwargs)
662
662
  self.overrides['self']['factory'] = self.get_object
663
663
  self.overrides['cls']['factory'] = self.get_model
664
+ self.client = None
664
665
 
665
666
  def setargs(self):
667
+ """
668
+ ModelCommand setargs which calls setargs on the client class and
669
+ defines an id argument for object commands.
670
+ """
666
671
  super().setargs()
672
+ self.group.parent.client_class.setargs(self)
667
673
  if 'self' in self:
668
674
  self.arg('id', position=0, kind='POSITIONAL_ONLY', doc='ID')
669
675
 
670
- async def get_client(self):
671
- client = self.group.parent.overrides['self']['factory']()
672
- return await async_resolve(client)
676
+ async def factories_resolve(self):
677
+ """
678
+ Return a client object from it's factory, will all args resolved.
679
+ """
680
+ # create a hidden Argument to use it's factory caller
681
+ argument = Argument(
682
+ self,
683
+ inspect.Parameter('_', kind=inspect.Parameter.POSITIONAL_ONLY),
684
+ factory=self.client_class.factory,
685
+ )
686
+ # this ensures the factory gets any kind of args
687
+ factory = await argument.factory_value(self)
688
+ self.client = await async_resolve(factory)
689
+ await super().factories_resolve()
673
690
 
674
691
  async def get_model(self):
675
- return getattr(await self.get_client(), self.model.__name__)
692
+ """ Return a client instance bound model """
693
+ return getattr(self.client, self.model.__name__)
676
694
 
677
695
  async def get_object(self):
678
696
  model = await self.get_model()
679
697
  return await model.get(id=self['id'].value)
680
698
 
681
-
682
- class ModelGroup(Group):
683
- def __init__(self, cls):
684
- super().__init__(
685
- name=cls.__name__.lower(),
686
- doc=inspect.getdoc(cls),
687
- cmdclass=type(
688
- 'ModelCommand',
689
- (cls.model_command,),
690
- dict(model=cls),
691
- )
692
- )
693
- self.load(cls)
699
+ async def post_call(self):
700
+ await self.client.post_call(self)
694
701
 
695
702
 
696
703
  class ModelMetaclass(type):
@@ -699,10 +706,14 @@ class ModelMetaclass(type):
699
706
  attributes['paginator'] = attributes['Paginator']
700
707
 
701
708
  cls = super().__new__(cls, name, bases, attributes)
702
- cls.Command = type(
709
+ client_class = getattr(cls, '_client_class', None)
710
+ cls.cmdclass = type(
703
711
  'ModelCommand',
704
- (cls.Command,),
705
- dict(model=cls),
712
+ (cls.cmdclass,),
713
+ dict(
714
+ model=cls,
715
+ client_class=client_class,
716
+ ),
706
717
  )
707
718
  client = getattr(cls, 'client', None)
708
719
  if client:
@@ -710,7 +721,6 @@ class ModelMetaclass(type):
710
721
  cls.paginator = client.paginator
711
722
  return cls
712
723
 
713
- client_class = getattr(cls, '_client_class', None)
714
724
  if client_class:
715
725
  client_class.models.append(cls)
716
726
 
@@ -744,7 +754,7 @@ class ModelMetaclass(type):
744
754
  cli_kwargs = dict(
745
755
  name=cls.__name__.lower(),
746
756
  doc=inspect.getdoc(cls),
747
- cmdclass=cls.Command,
757
+ cmdclass=cls.cmdclass,
748
758
  )
749
759
  cli_kwargs.update(cls.cli_kwargs)
750
760
  cls._cli = Group(**cli_kwargs)
@@ -789,24 +799,20 @@ class Model(metaclass=ModelMetaclass):
789
799
  Dict of kwargs to use to create the :py:class:`~cli2.cli.Group` for
790
800
  this model.
791
801
 
792
- .. py:attribute:: Command
802
+ .. py:attribute:: cmdclass
793
803
 
794
- Define a Command class inside the ModelClass to use a specific
795
- :py:class:`~cli2.cli.Command` class for a model.
796
- Don't inherit from anything: a new class will be generated for you
797
- inheriting from both :py:class:`~cls.cli2.client.ModelCommand` and
798
- :py:attr:`~cli2.cli.Client.Command`.
804
+ :py:class:`ModelCommand` subclass. You generally don't need to
805
+ define this, instead, you should do what you need in the
806
+ :py:meth:`Client.factory`, :py:meth:`Client.setargs` and
807
+ :py:meth:`Client.post_call` methods.
799
808
  """
800
809
  paginator = None
801
- model_command = ModelCommand
810
+ cmdclass = ModelCommand
802
811
  url_list = None
803
812
  url_detail = '{self.url_list}/{self.id_value}'
804
813
  id_field = 'id'
805
814
  cli_kwargs = dict()
806
815
 
807
- class Command(ModelCommand):
808
- pass
809
-
810
816
  def __init__(self, data=None, **values):
811
817
  """
812
818
  Instanciate a model.
@@ -984,10 +990,10 @@ class ClientMetaclass(type):
984
990
 
985
991
  cls = super().__new__(cls, name, bases, attributes)
986
992
 
987
- cls.Command = type(
993
+ cls.cmdclass = type(
988
994
  'ClientCommand',
989
- (cls.Command, Command),
990
- dict(),
995
+ (cls.cmdclass,),
996
+ dict(client_class=cls),
991
997
  )
992
998
 
993
999
  # bind ourself as _client_class to any inherited model
@@ -1005,13 +1011,15 @@ class ClientMetaclass(type):
1005
1011
  cls=dict(factory=lambda: cls),
1006
1012
  self=dict(factory=lambda: cls())
1007
1013
  ),
1008
- cmdclass=cls.Command,
1014
+ cmdclass=cls.cmdclass,
1009
1015
  )
1010
1016
  cli_kwargs.update(cls.cli_kwargs)
1011
1017
  cli = Group(**cli_kwargs)
1018
+ cli.client_class = cls
1012
1019
  cli.load(cls)
1013
1020
  for model in cls.models:
1014
1021
  group = model.cli
1022
+ group.client_class = cls
1015
1023
  if len(group) > 1:
1016
1024
  cli[model.__name__.lower()] = group
1017
1025
  cls._cli = cli
@@ -1248,6 +1256,37 @@ class FieldExternalizeError(FieldValueError):
1248
1256
  pass
1249
1257
 
1250
1258
 
1259
+ class ClientCommand(Command):
1260
+ """
1261
+ Client CLI command
1262
+
1263
+ .. py:attribute:: client
1264
+
1265
+ The client object that was constructed from :py:meth:`Client.factory`
1266
+ """
1267
+ def __init__(self, *args, **kwargs):
1268
+ super().__init__(*args, **kwargs)
1269
+ self.client = None
1270
+
1271
+ def setargs(self):
1272
+ """
1273
+ Set a `self` factory of :py:meth:`Client.factory` method, and call
1274
+ :py:meth:`Client.setargs`.
1275
+ """
1276
+ super().setargs()
1277
+ self['self'].factory = self.client_class.factory
1278
+ self.client_class.setargs(self)
1279
+
1280
+ async def factories_resolve(self):
1281
+ """ Set :py:attr:`client` after resolving factories. """
1282
+ await super().factories_resolve()
1283
+ self.client = self['self'].value
1284
+
1285
+ async def post_call(self):
1286
+ """ Call :py:meth:`Client.post_call`. """
1287
+ await self.client.post_call(self)
1288
+
1289
+
1251
1290
  class Client(metaclass=ClientMetaclass):
1252
1291
  """
1253
1292
  HTTPx Client Wrapper
@@ -1286,12 +1325,12 @@ class Client(metaclass=ClientMetaclass):
1286
1325
  class YourClient(cli2.Client):
1287
1326
  cli_kwargs = dict(cmdclass=YourCommandClass)
1288
1327
 
1289
- .. py:attribute:: Command
1328
+ .. py:attribute:: cmdclass
1290
1329
 
1291
- Define a Command class inside the Client class to use a specific
1292
- :py:class:`~cli2.cli.Command` class for a model.
1293
- Don't inherit from anything: a new class will be generated for you
1294
- inheriting from :py:attr:`~cli2.cli.Client.Command` for you.
1330
+ :py:class:`ClientCommand` class or subclass. You usually won't have to
1331
+ define this, instead, you should do what you need in the
1332
+ :py:meth:`factory`, :py:meth:`setargs` and :py:meth:`post_call`
1333
+ methods.
1295
1334
 
1296
1335
  .. py:attribute:: debug
1297
1336
 
@@ -1307,9 +1346,7 @@ class Client(metaclass=ClientMetaclass):
1307
1346
  semaphore = None
1308
1347
  mask = []
1309
1348
  debug = False
1310
-
1311
- class Command:
1312
- pass
1349
+ cmdclass = ClientCommand
1313
1350
 
1314
1351
  def __init__(self, *args, handler=None, semaphore=None, mask=None,
1315
1352
  debug=False, **kwargs):
@@ -1341,6 +1378,30 @@ class Client(metaclass=ClientMetaclass):
1341
1378
  model.url_list = model.url_list.format(client=self)
1342
1379
  setattr(self, model.__name__, model)
1343
1380
 
1381
+ @classmethod
1382
+ async def factory(cls):
1383
+ """
1384
+ Override this method to customize your client construction.
1385
+
1386
+ You can add custom args, if you declare them in :py:meth:`setargs()`.
1387
+ """
1388
+ return cls()
1389
+
1390
+ @classmethod
1391
+ def setargs(cls, cmd):
1392
+ """
1393
+ Override this method to declare CLI args globally for this client.
1394
+
1395
+ :param cmd: :py:class:`ClientCommand` object
1396
+ """
1397
+
1398
+ async def post_call(self, cmd):
1399
+ """
1400
+ Override this method which will run after a CLI exits.
1401
+
1402
+ :param cmd: :py:class:`ClientCommand` object
1403
+ """
1404
+
1344
1405
  @property
1345
1406
  def client(self):
1346
1407
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cli2
3
- Version: 4.1.4
3
+ Version: 4.2.1
4
4
  Summary: image:: https://yourlabs.io/oss/cli2/badges/master/pipeline.svg
5
5
  Home-page: https://yourlabs.io/oss/cli2
6
6
  Author: James Pic
@@ -44,7 +44,7 @@ from setuptools import setup
44
44
 
45
45
  setup(
46
46
  name='cli2',
47
- version='4.1.4',
47
+ version='4.2.1',
48
48
  setup_requires='setupmeta',
49
49
  packages=['cli2'],
50
50
  install_requires=[
@@ -60,7 +60,10 @@ async def test_client_cli(client_class, httpx_mock):
60
60
  assert 'testmodel' in client_class.cli
61
61
  assert client_class.cli['testmodel']['get'].overrides['cls']['factory']
62
62
  assert not inspect.ismethod(client_class.cli['testmodel']['get'].target)
63
- result = await client_class.cli['testmodel']['get']['cls'].factory_value()
63
+ command = client_class.cli['testmodel']['get']
64
+ command.parse()
65
+ await command.factories_resolve()
66
+ result = client_class.cli['testmodel']['get']['cls'].value
64
67
  assert issubclass(result, TestModel)
65
68
  assert isinstance(result.client, client_class)
66
69
 
@@ -148,7 +151,7 @@ async def test_client_cli_side_effect(client_class, httpx_mock):
148
151
 
149
152
  # Test that Client's __init_subclass__ did setup a factory for self
150
153
  assert isinstance(
151
- example_client.APIClient.cli['get']['self'].factory_value(),
154
+ await example_client.APIClient.cli['get']['self'].factory_value(),
152
155
  example_client.APIClient,
153
156
  )
154
157
 
@@ -1151,17 +1154,40 @@ async def test_client_token_apply(client_class, httpx_mock):
1151
1154
  assert client.client.token == 2
1152
1155
 
1153
1156
 
1154
- def test_client_command(client_class):
1157
+ def test_client_command(client_class, httpx_mock):
1155
1158
  class Client(client_class):
1156
- class Command(cli2.Command):
1157
- pass
1159
+ post_call_called = False
1160
+
1161
+ def __init__(self, base_url, *args, **kwargs):
1162
+ kwargs['base_url'] = base_url
1163
+ super().__init__(*args, **kwargs)
1164
+
1165
+ @classmethod
1166
+ async def factory(self, base_url):
1167
+ return Client(base_url)
1168
+
1169
+ @classmethod
1170
+ def setargs(self, cmd):
1171
+ cmd.arg('base_url', position=0, kind='POSITIONAL_ONLY')
1172
+
1173
+ async def post_call(self, cmd):
1174
+ await self.client.post('/logoff')
1158
1175
 
1159
1176
  class Model(Client.Model):
1160
1177
  url_list = '/foo'
1161
- class Command(Client.Command, cli2.Model.Command):
1162
- pass
1163
1178
 
1164
- assert issubclass(Client.cli.cmdclass, Client.Command)
1165
- assert issubclass(Client.cli['model'].cmdclass, Client.Command)
1166
- assert issubclass(Client.cli['model'].cmdclass, cli2.Model.Command)
1167
1179
  assert 'get' in Client.cli['model']
1180
+
1181
+ assert Client.cli['get'].client is None
1182
+ cmd = Client.cli['get']
1183
+ assert isinstance(cmd, Client.cmdclass)
1184
+ httpx_mock.add_response(url='http://x/foo', json=[])
1185
+ httpx_mock.add_response(url='http://x/logoff')
1186
+ result = cmd('http://x', '/foo')
1187
+ assert result.status_code == 200
1188
+
1189
+ httpx_mock.add_response(url='http://x/foo', json=[])
1190
+ httpx_mock.add_response(url='http://x/logoff')
1191
+ assert Client.cli['model']['get'].client is None
1192
+ cmd = Client.cli['model']['find']
1193
+ cmd('http://x')
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes