cli2 5.0.0rc9__tar.gz → 5.0.0rc12__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 (54) hide show
  1. {cli2-5.0.0rc9/cli2.egg-info → cli2-5.0.0rc12}/PKG-INFO +1 -4
  2. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/__init__.py +1 -1
  3. cli2-5.0.0rc12/cli2/asyncio.py +130 -0
  4. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/display.py +1 -6
  5. {cli2-5.0.0rc9 → cli2-5.0.0rc12/cli2.egg-info}/PKG-INFO +1 -4
  6. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2.egg-info/requires.txt +0 -4
  7. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/setup.py +1 -5
  8. cli2-5.0.0rc12/tests/test_asyncio.py +68 -0
  9. cli2-5.0.0rc9/cli2/asyncio.py +0 -42
  10. cli2-5.0.0rc9/tests/test_asyncio.py +0 -18
  11. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/MANIFEST.in +0 -0
  12. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/README.rst +0 -0
  13. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/classifiers.txt +0 -0
  14. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/cli.py +0 -0
  15. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/cli2.py +0 -0
  16. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/colors.py +0 -0
  17. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/configuration.py +0 -0
  18. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/decorators.py +0 -0
  19. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/examples/__init__.py +0 -0
  20. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/examples/conf.py +0 -0
  21. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/examples/example.py +0 -0
  22. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/examples/example_obj.py +0 -0
  23. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/examples/nesting.py +0 -0
  24. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/examples/obj.py +0 -0
  25. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/examples/obj2.py +0 -0
  26. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/examples/test.py +0 -0
  27. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/lock.py +0 -0
  28. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/log.py +0 -0
  29. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/mask.py +0 -0
  30. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/node.py +0 -0
  31. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/sphinx.py +0 -0
  32. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/table.py +0 -0
  33. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2/test.py +0 -0
  34. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2.egg-info/SOURCES.txt +0 -0
  35. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2.egg-info/dependency_links.txt +0 -0
  36. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2.egg-info/entry_points.txt +0 -0
  37. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/cli2.egg-info/top_level.txt +0 -0
  38. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/setup.cfg +0 -0
  39. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_ansible.py +0 -0
  40. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_ansible_variables.py +0 -0
  41. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_cli.py +0 -0
  42. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_client.py +0 -0
  43. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_command.py +0 -0
  44. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_configuration.py +0 -0
  45. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_decorators.py +0 -0
  46. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_display.py +0 -0
  47. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_entry_point.py +0 -0
  48. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_group.py +0 -0
  49. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_inject.py +0 -0
  50. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_lock.py +0 -0
  51. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_mask.py +0 -0
  52. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_node.py +0 -0
  53. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_restful.py +0 -0
  54. {cli2-5.0.0rc9 → cli2-5.0.0rc12}/tests/test_table.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cli2
3
- Version: 5.0.0rc9
3
+ Version: 5.0.0rc12
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
@@ -13,9 +13,6 @@ Requires-Dist: docstring_parser
13
13
  Requires-Dist: pyyaml
14
14
  Requires-Dist: pygments
15
15
  Requires-Dist: structlog
16
- Provides-Extra: client
17
- Requires-Dist: httpx; extra == "client"
18
- Requires-Dist: truststore; extra == "client"
19
16
  Provides-Extra: test
20
17
  Requires-Dist: freezegun; extra == "test"
21
18
  Requires-Dist: pytest; extra == "test"
@@ -9,7 +9,7 @@ from .cli import (
9
9
  Group,
10
10
  EntryPoint,
11
11
  )
12
- from .asyncio import async_resolve, async_run
12
+ from .asyncio import async_resolve, async_run, Queue
13
13
  from .colors import colors as c
14
14
 
15
15
  from .configuration import Configuration, cfg
@@ -0,0 +1,130 @@
1
+ import asyncio
2
+ import inspect
3
+ from . import display
4
+ from .log import log
5
+
6
+
7
+ def async_iter(obj):
8
+ """ Check if an object is an async iterable. """
9
+ return inspect.isasyncgen(obj) or hasattr(obj, '__aiter__')
10
+
11
+
12
+ async def async_resolve(result, output=False):
13
+ """
14
+ Recursively resolve awaitables and async iterables.
15
+
16
+ :param result: The awaitable or async iterable to resolve
17
+ :param output: If True, print results as they are resolved. If False,
18
+ collect results.
19
+
20
+ :return: The resolved value(s). If output is True, returns None. If output
21
+ is False, returns a list of resolved values from async iterables.
22
+ """
23
+ while inspect.iscoroutine(result):
24
+ result = await result
25
+ if async_iter(result):
26
+ results = []
27
+ async for _ in result:
28
+ if output:
29
+ if (
30
+ not inspect.iscoroutine(_)
31
+ and not inspect.isasyncgen(_)
32
+ ):
33
+ display.print(_)
34
+ else:
35
+ await async_resolve(_, output=output)
36
+ else:
37
+ results.append(await async_resolve(_))
38
+ return None if output else results
39
+ return result
40
+
41
+
42
+ def async_run(coroutine):
43
+ """Run an async coroutine in the current event loop or create a new one.
44
+
45
+ If an event loop is already running, creates a task in that loop.
46
+ If no event loop is running, creates a new one and runs the coroutine.
47
+
48
+ :param coroutine: The coroutine to run (return value of an async function)
49
+ :return: The result of the coroutine execution
50
+ """
51
+ try:
52
+ loop = asyncio.get_running_loop()
53
+ except RuntimeError:
54
+ return asyncio.run(coroutine)
55
+ else:
56
+ return loop.create_task(coroutine)
57
+
58
+
59
+ class Queue(asyncio.Queue):
60
+ """
61
+ An async queue with worker pool for concurrent task processing.
62
+
63
+ Extends asyncio.Queue to manage a pool of worker tasks that process items
64
+ from the queue concurrently.
65
+
66
+ .. code-block:: python
67
+
68
+ # will run 2 at the time
69
+ queue = cli2.Queue(num_workers=2)
70
+ # call like asyncio.run
71
+ await queue.run(foo(), bar(), other())
72
+
73
+ .. py:attribute:: num_workers
74
+
75
+ Number of concurrent workers (default: 12)
76
+
77
+ .. py:attribute:: results
78
+
79
+ List of results from completed tasks, order of results not garanteed
80
+ due to concurrency.
81
+ """
82
+
83
+ def __init__(self, *args, num_workers=12, **kwargs):
84
+ """Initialize the queue with worker pool.
85
+
86
+ :param num_workers: Number of concurrent workers (default: 12)
87
+ :paarm *args: Positional arguments for asyncio.Queue
88
+ :param **kwargs: Keyword arguments for asyncio.Queue
89
+ """
90
+ self.num_workers = num_workers or 12
91
+ self.results = []
92
+ super().__init__(*args, **kwargs)
93
+
94
+ async def run(self, *tasks):
95
+ """
96
+ Run tasks through the worker pool.
97
+
98
+ :param tasks: Coroutines
99
+ """
100
+ self.results = []
101
+
102
+ for task in tasks:
103
+ await self.put(task)
104
+
105
+ workers = [
106
+ asyncio.create_task(self.worker())
107
+ for i in range(self.num_workers)
108
+ ]
109
+
110
+ await self.join()
111
+ for worker in workers:
112
+ worker.cancel()
113
+
114
+ async def worker(self):
115
+ """Worker task that processes items from the queue.
116
+
117
+ Continuously gets tasks from the queue, executes them, and stores
118
+ results.
119
+ Handles exceptions by logging them.
120
+ """
121
+ while True:
122
+ task = await self.get()
123
+ try:
124
+ result = await task
125
+ except: # noqa
126
+ log.exception()
127
+ else:
128
+ self.results.append(result)
129
+ finally:
130
+ self.task_done()
@@ -7,16 +7,11 @@ Generic pretty display utils.
7
7
  force it with :envvar:`FORCE_COLOR`, ie. gitlab-ci etc
8
8
  """
9
9
  import difflib
10
+ import json
10
11
  import os
11
12
  import sys
12
13
  import yaml
13
14
 
14
- try:
15
- import jsonlight as json
16
- except ImportError:
17
- import json
18
-
19
-
20
15
  _print = print
21
16
 
22
17
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cli2
3
- Version: 5.0.0rc9
3
+ Version: 5.0.0rc12
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
@@ -13,9 +13,6 @@ Requires-Dist: docstring_parser
13
13
  Requires-Dist: pyyaml
14
14
  Requires-Dist: pygments
15
15
  Requires-Dist: structlog
16
- Provides-Extra: client
17
- Requires-Dist: httpx; extra == "client"
18
- Requires-Dist: truststore; extra == "client"
19
16
  Provides-Extra: test
20
17
  Requires-Dist: freezegun; extra == "test"
21
18
  Requires-Dist: pytest; extra == "test"
@@ -3,10 +3,6 @@ pyyaml
3
3
  pygments
4
4
  structlog
5
5
 
6
- [client]
7
- httpx
8
- truststore
9
-
10
6
  [test]
11
7
  freezegun
12
8
  pytest
@@ -44,7 +44,7 @@ from setuptools import setup
44
44
 
45
45
  setup(
46
46
  name='cli2',
47
- version='5.0.0rc9',
47
+ version='5.0.0rc12',
48
48
  setup_requires='setupmeta',
49
49
  packages=['cli2'],
50
50
  install_requires=[
@@ -54,10 +54,6 @@ setup(
54
54
  'structlog',
55
55
  ],
56
56
  extras_require=dict(
57
- client=[
58
- 'httpx',
59
- 'truststore',
60
- ],
61
57
  test=[
62
58
  'freezegun',
63
59
  'pytest',
@@ -0,0 +1,68 @@
1
+ import asyncio
2
+ import cli2
3
+ import pytest
4
+ from unittest import mock
5
+
6
+
7
+ def test_async_run_noloop():
8
+ async def task():
9
+ await asyncio.sleep(.01)
10
+
11
+ cli2.async_run(task())
12
+
13
+
14
+ @pytest.mark.asyncio
15
+ async def test_async_run_loop():
16
+ async def task():
17
+ await asyncio.sleep(.01)
18
+
19
+ cli2.async_run(task())
20
+
21
+
22
+ @pytest.mark.asyncio
23
+ async def test_queue_basic():
24
+ async def task(i):
25
+ return i
26
+
27
+ queue = cli2.Queue()
28
+ await queue.run(task(1), task(2), task(3))
29
+
30
+ assert sorted(queue.results) == [1, 2, 3]
31
+
32
+
33
+ @pytest.mark.asyncio
34
+ async def test_queue_concurrency():
35
+ start = asyncio.get_event_loop().time()
36
+
37
+ async def worker(i):
38
+ await asyncio.sleep(0.1)
39
+ return i
40
+
41
+ queue = cli2.Queue(num_workers=3)
42
+ await queue.run(worker(1), worker(2), worker(3))
43
+
44
+ duration = asyncio.get_event_loop().time() - start
45
+ assert duration < 0.15, 'Should run in parallel'
46
+ assert sorted(queue.results) == [1, 2, 3]
47
+
48
+
49
+ @pytest.mark.asyncio
50
+ async def test_queue_error_handling(monkeypatch):
51
+ import cli2.asyncio
52
+ logger = mock.Mock()
53
+ monkeypatch.setattr(cli2.asyncio, 'log', logger)
54
+
55
+ async def worker(i):
56
+ if i == 2:
57
+ raise ValueError('test error')
58
+ await asyncio.sleep(0.01)
59
+ return i
60
+
61
+ queue = cli2.Queue()
62
+ await queue.run(worker(1), worker(2), worker(3))
63
+
64
+ # Error should not stop other tasks
65
+ assert queue.results == [1, 3]
66
+
67
+ # Exceptions should be printed though, not swallowed
68
+ logger.exception.assert_called_once()
@@ -1,42 +0,0 @@
1
- import asyncio
2
- import inspect
3
- from . import display
4
-
5
-
6
- def async_iter(obj):
7
- return inspect.isasyncgen(obj) or hasattr(obj, '__aiter__')
8
-
9
-
10
- async def async_resolve(result, output=False):
11
- """ Recursively resolve awaitables. """
12
- while inspect.iscoroutine(result):
13
- result = await result
14
- if async_iter(result):
15
- results = []
16
- async for _ in result:
17
- if output:
18
- if (
19
- not inspect.iscoroutine(_)
20
- and not inspect.isasyncgen(_)
21
- ):
22
- display.print(_)
23
- else:
24
- await async_resolve(_, output=output)
25
- else:
26
- results.append(await async_resolve(_))
27
- return None if output else results
28
- return result
29
-
30
-
31
- def async_run(coroutine):
32
- """
33
- Run an async coroutineutine, in running loop or new loop.
34
-
35
- :param coroutine: The return value of an async function call.
36
- """
37
- try:
38
- loop = asyncio.get_running_loop()
39
- except RuntimeError:
40
- return asyncio.run(coroutine)
41
- else:
42
- return loop.create_task(coroutine)
@@ -1,18 +0,0 @@
1
- import asyncio
2
- import cli2
3
- import pytest
4
-
5
-
6
- def test_async_run_noloop():
7
- async def task():
8
- await asyncio.sleep(.01)
9
-
10
- cli2.async_run(task())
11
-
12
-
13
- @pytest.mark.asyncio
14
- async def test_async_run_loop():
15
- async def task():
16
- await asyncio.sleep(.01)
17
-
18
- cli2.async_run(task())
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