From 5553a68128b28855e4fcb94e6a6fe8f8426a9d11 Mon Sep 17 00:00:00 2001 From: romainprignon Date: Wed, 7 Aug 2019 14:36:09 +0200 Subject: [PATCH 1/3] fix: better type structure --- MANIFEST.in | 2 ++ README.md | 6 +++--- makefile | 4 ++-- setup.py | 5 ++++- src/cli.py | 6 +++--- src/{unshell.py => core.py} | 2 +- src/py.typed | 1 + src/test_cli.py | 7 ++----- src/{test_unshell.py => test_core.py} | 4 ++-- type/__init__.py => src/type.py | 0 10 files changed, 20 insertions(+), 17 deletions(-) rename src/{unshell.py => core.py} (97%) create mode 100644 src/py.typed rename src/{test_unshell.py => test_core.py} (99%) rename type/__init__.py => src/type.py (100%) diff --git a/MANIFEST.in b/MANIFEST.in index c28ab72..87ddf51 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,4 @@ include README.md include LICENSE.md + +global-exclude test_*.py diff --git a/README.md b/README.md index 6213f51..637d1d0 100644 --- a/README.md +++ b/README.md @@ -64,10 +64,10 @@ def main(): script = resolve('./scripts/pause.js') # resolve your python module try: - Unshell({env: os.environ})(script) + Unshell({"env": os.environ})(script) except Exception as err: print(err) -} + ``` @@ -84,7 +84,7 @@ The code is available under the [MIT license](LICENSE.md). ## TODO - release script (semver and publish script) - fix core import -- fix typings +- fix typings (better rules places) - install dev dependencies - spec - example diff --git a/makefile b/makefile index d710280..d962616 100644 --- a/makefile +++ b/makefile @@ -12,8 +12,8 @@ freeze: # make freeze pip freeze > requirements.txt lint: ## make lint - flake8 src/ type/ - mypy src/ type/ + flake8 src/ + mypy src/ test: ## make test python setup.py test diff --git a/setup.py b/setup.py index 659cc06..34c1323 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,10 @@ name="Unshell", version_command="git describe --tags", python_requires=">=3.7", - packages=find_packages(exclude=["spec"]), + package_dir={"": "src"}, + packages=[".", "utils"], + # packages=find_packages(where="src"), + package_data={"unshell": ["py.typed"]}, entry_points={ "console_scripts": ['unshell = src.cli:main'] }, diff --git a/src/cli.py b/src/cli.py index 68aab48..2162715 100644 --- a/src/cli.py +++ b/src/cli.py @@ -1,13 +1,13 @@ #!/usr/bin/env python from typing import Any -from type import Args, Script +from .type import Args, Script import os import sys import importlib.util -from src.unshell import Unshell -from src.utils import colors +from .core import Unshell +from .utils import colors def help(argv: Args, env: os._Environ) -> Any: diff --git a/src/unshell.py b/src/core.py similarity index 97% rename from src/unshell.py rename to src/core.py index e582252..51e7682 100644 --- a/src/unshell.py +++ b/src/core.py @@ -1,5 +1,5 @@ from typing import Any, Callable, Union, cast -from type import Script, Args, Commands, Command, Engine, \ +from .type import Script, Args, Commands, Command, Engine, \ AsyncScript, AsyncCommands, Options import inspect diff --git a/src/py.typed b/src/py.typed new file mode 100644 index 0000000..17af580 --- /dev/null +++ b/src/py.typed @@ -0,0 +1 @@ +# PEP 561 diff --git a/src/test_cli.py b/src/test_cli.py index 7456cfc..42a0aad 100644 --- a/src/test_cli.py +++ b/src/test_cli.py @@ -3,13 +3,10 @@ from unittest.mock import patch, call # test -from src.cli import cli -from src.utils import colors +from .cli import cli +from .utils import colors -# mock -# loop = asyncio.get_event_loop() - class TestCli(unittest.TestCase): @patch('builtins.print') diff --git a/src/test_unshell.py b/src/test_core.py similarity index 99% rename from src/test_unshell.py rename to src/test_core.py index 68edbb6..1c02875 100644 --- a/src/test_unshell.py +++ b/src/test_core.py @@ -1,13 +1,13 @@ from dataclasses import dataclass from typing import Callable, List -from type import Options +from .type import Options import unittest import asyncio from unittest.mock import patch, call # test -from src.unshell import Unshell +from .core import Unshell # mock diff --git a/type/__init__.py b/src/type.py similarity index 100% rename from type/__init__.py rename to src/type.py From dbc871c5cfe25b0718fe58f8ccf4367dfa6f55bd Mon Sep 17 00:00:00 2001 From: romainprignon Date: Thu, 8 Aug 2019 12:07:31 +0200 Subject: [PATCH 2/3] fix: better module structure --- README.md | 2 -- makefile | 2 +- setup.py | 6 +++--- src/unshell/__init__.py | 3 +++ src/{ => unshell}/cli.py | 0 src/{ => unshell}/core.py | 0 src/{ => unshell}/py.typed | 0 src/{ => unshell}/test_cli.py | 4 ++-- src/{ => unshell}/test_core.py | 2 +- src/{ => unshell}/type.py | 0 src/{ => unshell/utils}/__init__.py | 0 src/{ => unshell}/utils/colors.py | 0 src/{ => unshell}/utils/pipe.py | 2 +- src/{ => unshell}/utils/test_colors.py | 6 +++--- src/{ => unshell}/utils/test_pipe.py | 2 +- src/utils/__init__.py | 0 16 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 src/unshell/__init__.py rename src/{ => unshell}/cli.py (100%) rename src/{ => unshell}/core.py (100%) rename src/{ => unshell}/py.typed (100%) rename src/{ => unshell}/test_cli.py (94%) rename src/{ => unshell}/test_core.py (99%) rename src/{ => unshell}/type.py (100%) rename src/{ => unshell/utils}/__init__.py (100%) rename src/{ => unshell}/utils/colors.py (100%) rename src/{ => unshell}/utils/pipe.py (81%) rename src/{ => unshell}/utils/test_colors.py (78%) rename src/{ => unshell}/utils/test_pipe.py (96%) delete mode 100644 src/utils/__init__.py diff --git a/README.md b/README.md index 637d1d0..c478587 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,6 @@ The code is available under the [MIT license](LICENSE.md). ## TODO -- release script (semver and publish script) -- fix core import - fix typings (better rules places) - install dev dependencies - spec diff --git a/makefile b/makefile index d962616..c4ec3ca 100644 --- a/makefile +++ b/makefile @@ -18,7 +18,7 @@ lint: ## make lint test: ## make test python setup.py test -test-one: ## make test test=src.test_unshell.TestUnshell.test_unshell_should_return_function +test-one: ## make test-one test=src.unshell.test_core or make test-one test=src.unshell.test_core.testCore.test_unshell_should_return_function python setup.py test --test-suite $(test) coverage: ## make coverage diff --git a/setup.py b/setup.py index 34c1323..9580f5e 100644 --- a/setup.py +++ b/setup.py @@ -8,12 +8,12 @@ version_command="git describe --tags", python_requires=">=3.7", package_dir={"": "src"}, - packages=[".", "utils"], - # packages=find_packages(where="src"), + packages=find_packages(where="src"), package_data={"unshell": ["py.typed"]}, entry_points={ - "console_scripts": ['unshell = src.cli:main'] + "console_scripts": ['unshell = src.unshell.cli:main'] }, + test_suite="src.unshell", author="Romain Prignon", author_email="pro.rprignon@gmail.com", diff --git a/src/unshell/__init__.py b/src/unshell/__init__.py new file mode 100644 index 0000000..fca4552 --- /dev/null +++ b/src/unshell/__init__.py @@ -0,0 +1,3 @@ +from .core import Unshell as UnshellCore + +Unshell = UnshellCore diff --git a/src/cli.py b/src/unshell/cli.py similarity index 100% rename from src/cli.py rename to src/unshell/cli.py diff --git a/src/core.py b/src/unshell/core.py similarity index 100% rename from src/core.py rename to src/unshell/core.py diff --git a/src/py.typed b/src/unshell/py.typed similarity index 100% rename from src/py.typed rename to src/unshell/py.typed diff --git a/src/test_cli.py b/src/unshell/test_cli.py similarity index 94% rename from src/test_cli.py rename to src/unshell/test_cli.py index 42a0aad..4371e8f 100644 --- a/src/test_cli.py +++ b/src/unshell/test_cli.py @@ -85,7 +85,7 @@ def test_cli_should_display_error_on_unresolvable_script(self, print_mock): def test_cli_should_display_error_on_errored_script(self, print_mock): # given abs_test_path = os.path.dirname(os.path.abspath(__file__)) - scriptPath = f"{abs_test_path}/../fixtures/scripts/notCompatibleCmd.py" + scriptPath = f"{abs_test_path}/../../fixtures/scripts/notCompatibleCmd.py" argv = ['cli.py', 'run', scriptPath] env = os.environ @@ -102,7 +102,7 @@ def test_cli_should_display_error_on_errored_script(self, print_mock): def test_cli_should_execute_script_on_run_command(self, print_mock): # given abs_test_path = os.path.dirname(os.path.abspath(__file__)) - scriptPath = f"{abs_test_path}/../fixtures/scripts/yieldAndReturnCommand.py" + scriptPath = f"{abs_test_path}/../../fixtures/scripts/yieldAndReturnCommand.py" argv = ['cli.py', 'run', scriptPath] env = os.environ diff --git a/src/test_core.py b/src/unshell/test_core.py similarity index 99% rename from src/test_core.py rename to src/unshell/test_core.py index 1c02875..dbd4dc6 100644 --- a/src/test_core.py +++ b/src/unshell/test_core.py @@ -41,7 +41,7 @@ async def communicate(self): return future_process -class TestUnshell(unittest.TestCase): +class TestCore(unittest.TestCase): def test_unshell_should_return_function(self): # given opt: Options = {"env": {}} diff --git a/src/type.py b/src/unshell/type.py similarity index 100% rename from src/type.py rename to src/unshell/type.py diff --git a/src/__init__.py b/src/unshell/utils/__init__.py similarity index 100% rename from src/__init__.py rename to src/unshell/utils/__init__.py diff --git a/src/utils/colors.py b/src/unshell/utils/colors.py similarity index 100% rename from src/utils/colors.py rename to src/unshell/utils/colors.py diff --git a/src/utils/pipe.py b/src/unshell/utils/pipe.py similarity index 81% rename from src/utils/pipe.py rename to src/unshell/utils/pipe.py index ecf4f22..46ae450 100644 --- a/src/utils/pipe.py +++ b/src/unshell/utils/pipe.py @@ -3,7 +3,7 @@ from functools import reduce -def pipe(f1: Callable, *fns: List[Callable]): +def pipe(f1: Callable, *fns: List[Callable]) -> Callable: def args(*args: List[Any]) -> str: return reduce( lambda res, fn: f"{res} | {fn()}", diff --git a/src/utils/test_colors.py b/src/unshell/utils/test_colors.py similarity index 78% rename from src/utils/test_colors.py rename to src/unshell/utils/test_colors.py index f0b2e7e..aa5fae1 100644 --- a/src/utils/test_colors.py +++ b/src/unshell/utils/test_colors.py @@ -1,7 +1,7 @@ import unittest # test -import src.utils.colors as colors +from .colors import red, green class TestColors(unittest.TestCase): @@ -10,7 +10,7 @@ def test_red(self): val = 'value' # when - output = colors.red(val) + output = red(val) # then self.assertEqual(output, f"\x1b[31m{val}\x1b[0m") @@ -20,7 +20,7 @@ def test_green(self): val = 'value' # when - output = colors.green(val) + output = green(val) # then self.assertEqual(output, f"\x1b[32m{val}\x1b[0m") diff --git a/src/utils/test_pipe.py b/src/unshell/utils/test_pipe.py similarity index 96% rename from src/utils/test_pipe.py rename to src/unshell/utils/test_pipe.py index 6b799ff..65be8f2 100644 --- a/src/utils/test_pipe.py +++ b/src/unshell/utils/test_pipe.py @@ -3,7 +3,7 @@ import unittest # test -from src.utils.pipe import pipe +from .pipe import pipe class TestPipe(unittest.TestCase): diff --git a/src/utils/__init__.py b/src/utils/__init__.py deleted file mode 100644 index e69de29..0000000 From 1784e5bec4be2edbb764876fa0150c68a61723cb Mon Sep 17 00:00:00 2001 From: romainprignon Date: Sat, 17 Aug 2019 16:01:38 +0200 Subject: [PATCH 3/3] fix: mypy --- README.md | 2 - mypy.ini | 5 + requirements.txt | 1 + setup.cfg | 6 -- src/unshell/cli.py | 10 +- src/unshell/core.py | 27 ++++-- src/unshell/test_cli.py | 18 ++-- src/unshell/test_core.py | 152 ++++++++++++++++--------------- src/unshell/type.py | 18 +++- src/unshell/utils/colors.py | 4 +- src/unshell/utils/pipe.py | 6 +- src/unshell/utils/test_colors.py | 4 +- src/unshell/utils/test_pipe.py | 16 ++-- 13 files changed, 146 insertions(+), 123 deletions(-) create mode 100644 mypy.ini diff --git a/README.md b/README.md index c478587..7a5dbfb 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,6 @@ The code is available under the [MIT license](LICENSE.md). ## TODO -- fix typings (better rules places) -- install dev dependencies - spec - example - publish on pypi diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..d927c53 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +check_untyped_defs = true +disallow_untyped_defs=true +no_implicit_optional = true +warn_unused_ignores = true diff --git a/requirements.txt b/requirements.txt index 9f4ae97..e09074b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ mypy==0.711 setuptools==41.0.1 setuptools-version-command==2.2 twine==1.13.0 +typing_extensions==3.7.4 wheel==0.33.4 diff --git a/setup.cfg b/setup.cfg index a8af20a..95f4830 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,9 +3,3 @@ license_files = LICENSE.md [bdist_wheel] universal=0 - -[mypy] -check_untyped_defs = true -disallow_incomplete_defs = true -no_implicit_optional = true -warn_unused_ignores = true diff --git a/src/unshell/cli.py b/src/unshell/cli.py index 2162715..93a61e8 100644 --- a/src/unshell/cli.py +++ b/src/unshell/cli.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from typing import Any +from typing import List, Any from .type import Args, Script import os @@ -10,7 +10,7 @@ from .utils import colors -def help(argv: Args, env: os._Environ) -> Any: +def help(argv: Args, env: os._Environ) -> None: print(""" Execute script through unshell runtime @@ -23,7 +23,7 @@ def help(argv: Args, env: os._Environ) -> Any: """) -def run(argv: Args, env: os._Environ) -> Any: +def run(argv: List[Any], env: os._Environ) -> None: [_, __, scriptPath, *args] = argv script = resolveScript(scriptPath) @@ -56,7 +56,7 @@ def resolveScript(scriptPath: str) -> Script: raise err -def cli(argv: Args, env: os._Environ) -> Any: +def cli(argv: List[Any], env: os._Environ) -> None: try: [_, unshell_command, __] = argv except Exception: @@ -73,7 +73,7 @@ def cli(argv: Args, env: os._Environ) -> Any: return help(argv, env) -def main(): # pragma: no cover +def main() -> None: # pragma: no cover argv = sys.argv env = os.environ diff --git a/src/unshell/core.py b/src/unshell/core.py index 51e7682..25dcf57 100644 --- a/src/unshell/core.py +++ b/src/unshell/core.py @@ -1,25 +1,28 @@ -from typing import Any, Callable, Union, cast -from .type import Script, Args, Commands, Command, Engine, \ - AsyncScript, AsyncCommands, Options +from typing import Any, Callable, Union, cast, Type, Optional, Awaitable +from .type import Script, Command, Engine, \ + AsyncScript, Options, Commands, AsyncCommands, Args import inspect import asyncio +AsyncSend = Callable[[Optional[str]], Awaitable[str]] +Send = Callable[[Optional[str]], str] + defaultOptions: Options = { "env": {} } -def Unshell(opt: Options = defaultOptions) -> Engine: +def Unshell(opt: Optional[Options] = defaultOptions) -> Engine: async def engine(script: Union[Script, AsyncScript], *args: Args) -> Any: if is_async_generator(script): - commands = script(*args) + commands = script(*args) # type: ignore commands = cast(AsyncCommands, commands) return await iter(commands.asend, StopAsyncIteration, True) if is_generator(script): - commands = script(*args) + commands = script(*args) # type: ignore commands = cast(Commands, commands) return await iter(commands.send, StopIteration, False) @@ -29,15 +32,21 @@ async def engine(script: Union[Script, AsyncScript], *args: Args) -> Any: return lambda script, *args: asyncio.run(engine(script, *args)) -async def iter(send, exception, is_async): +async def iter( + send: Union[Send, AsyncSend], + exception: Union[Type[StopIteration], Type[StopAsyncIteration]], + is_async: bool +) -> None: cmd_res = None command: Command = "" while True: try: if is_async: + send = cast(AsyncSend, send) command = await send(cmd_res) else: + send = cast(Send, send) command = send(cmd_res) if not isValidCmd(command): @@ -86,11 +95,11 @@ async def exec(command: Command) -> str: raise Exception("unshell: something went wrong") -def is_generator(fn: Callable[[Any], Any]) -> bool: +def is_generator(fn: Any) -> bool: return inspect.isgeneratorfunction(fn) -def is_async_generator(fn: Callable[[Any], Any]) -> bool: +def is_async_generator(fn: Any) -> bool: return inspect.isasyncgenfunction(fn) diff --git a/src/unshell/test_cli.py b/src/unshell/test_cli.py index 4371e8f..ad829af 100644 --- a/src/unshell/test_cli.py +++ b/src/unshell/test_cli.py @@ -1,6 +1,6 @@ import unittest import os -from unittest.mock import patch, call +from unittest.mock import patch, call, Mock # test from .cli import cli @@ -10,7 +10,7 @@ class TestCli(unittest.TestCase): @patch('builtins.print') - def test_cli_should_display_help_on_help_command(self, print_mock): + def test_cli_should_display_help_on_help_command(self, print_mock: Mock) -> None: # given argv = ['cli.py', 'help'] env = os.environ @@ -30,7 +30,7 @@ def test_cli_should_display_help_on_help_command(self, print_mock): """) @patch('builtins.print') - def test_cli_should_display_help_if_called_with_nothing(self, print_mock): + def test_cli_should_display_help_if_called_with_nothing(self, print_mock: Mock) -> None: # given argv = ['cli.py'] env = os.environ @@ -44,8 +44,8 @@ def test_cli_should_display_help_if_called_with_nothing(self, print_mock): @patch('builtins.print') def test_cli_should_display_help_if_called_with_no_script( self, - print_mock - ): + print_mock: Mock + ) -> None: # given argv = ['cli.py', 'run'] env = os.environ @@ -55,7 +55,7 @@ def test_cli_should_display_help_if_called_with_no_script( print_mock.assert_called_once() @patch('builtins.print') - def test_cli_should_display_help_on_invalid_command(self, print_mock): + def test_cli_should_display_help_on_invalid_command(self, print_mock: Mock) -> None: # given argv = ['cli.py', 'invalid', 'foo'] env = os.environ @@ -67,7 +67,7 @@ def test_cli_should_display_help_on_invalid_command(self, print_mock): print_mock.assert_called_once() @patch('builtins.print') - def test_cli_should_display_error_on_unresolvable_script(self, print_mock): + def test_cli_should_display_error_on_unresolvable_script(self, print_mock: Mock) -> None: # given argv = ['cli.py', 'run', 'unresolvable'] env = os.environ @@ -82,7 +82,7 @@ def test_cli_should_display_error_on_unresolvable_script(self, print_mock): ) @patch('builtins.print') - def test_cli_should_display_error_on_errored_script(self, print_mock): + def test_cli_should_display_error_on_errored_script(self, print_mock: Mock) -> None: # given abs_test_path = os.path.dirname(os.path.abspath(__file__)) scriptPath = f"{abs_test_path}/../../fixtures/scripts/notCompatibleCmd.py" @@ -99,7 +99,7 @@ def test_cli_should_display_error_on_errored_script(self, print_mock): ) @patch('builtins.print') - def test_cli_should_execute_script_on_run_command(self, print_mock): + def test_cli_should_execute_script_on_run_command(self, print_mock: Mock) -> None: # given abs_test_path = os.path.dirname(os.path.abspath(__file__)) scriptPath = f"{abs_test_path}/../../fixtures/scripts/yieldAndReturnCommand.py" diff --git a/src/unshell/test_core.py b/src/unshell/test_core.py index dbd4dc6..d255434 100644 --- a/src/unshell/test_core.py +++ b/src/unshell/test_core.py @@ -1,38 +1,46 @@ from dataclasses import dataclass -from typing import Callable, List -from .type import Options +from typing import Callable, List, Union, Optional +from typing_extensions import Literal +from .type import Options, Commands, AsyncCommands import unittest import asyncio -from unittest.mock import patch, call +from asyncio import Future +from unittest.mock import patch, call, Mock # test from .core import Unshell +# type +ReturnCode = Union[Literal[0], Literal[1]] # mock loop = asyncio.get_event_loop() -def make_future_process(return_code, stdout, stderr): +def make_future_process( + return_code: Union[None, ReturnCode], + stdout: Optional[str], + stderr: Optional[str] +) -> Future: @dataclass class Stdout: - def decode(self, format): + def decode(self, format: str) -> Optional[str]: return stdout @dataclass class Stderr: - def decode(self, format): + def decode(self, format: str) -> Optional[str]: return stderr @dataclass class Process: - returncode: int = return_code + returncode: Union[None, ReturnCode] = return_code - async def wait(self): - return + async def wait(self) -> None: + return None - async def communicate(self): + async def communicate(self) -> List[object]: return [Stdout(), Stderr()] future_process: asyncio.Future = asyncio.Future(loop=loop) @@ -42,7 +50,7 @@ async def communicate(self): class TestCore(unittest.TestCase): - def test_unshell_should_return_function(self): + def test_unshell_should_return_function(self) -> None: # given opt: Options = {"env": {}} @@ -50,37 +58,37 @@ def test_unshell_should_return_function(self): output = Unshell(opt) # then - self.assertTrue(isinstance(output, Callable)) + self.assertTrue(isinstance(output, Callable)) # type: ignore - def test_unshell_should_return_default_options(self): - self.assertTrue(isinstance(Unshell(), Callable)) + def test_unshell_should_return_default_options(self) -> None: + self.assertTrue(isinstance(Unshell(), Callable)) # type: ignore - def test_unshell_should_raise_if_script_not_generator(self): + def test_unshell_should_raise_if_script_not_generator(self) -> None: # given - def script(): + def script() -> str: return "echo OK" # then with self.assertRaises(TypeError): - Unshell()(script) + Unshell()(script) # type: ignore @patch('builtins.print') @patch('asyncio.create_subprocess_shell') - def test_unshell_should_process_command(self, shell_mock, print_mock): + def test_unshell_should_process_command(self, shell_mock: Mock, print_mock: Mock) -> None: # given - opt = {"env": {}} + opt: Options = {"env": {}} cmd: str = "echo OK" stdout: str = "result of echo OK" stderr: str = "" - def script(): + def script() -> Commands: # type: ignore yield f"{cmd}" # mock shell_mock.return_value = make_future_process(0, stdout, stderr) # when - Unshell(opt)(script) + Unshell(opt)(script) # type: ignore # then shell_mock.assert_called_once_with( @@ -96,16 +104,16 @@ def script(): @patch('builtins.print') @patch('asyncio.create_subprocess_shell') def test_unshell_should_not_process_unvalid_command( - self, shell_mock, print_mock - ): + self, shell_mock: Mock, print_mock: Mock + ) -> None: # given - opt = {"env": {}} + opt: Options = {"env": {}} - def script(): + def script() -> Commands: # type: ignore yield "" # when - Unshell(opt)(script) + Unshell(opt)(script) # type: ignore # then shell_mock.assert_not_called() @@ -114,14 +122,14 @@ def script(): @patch('builtins.print') @patch('asyncio.create_subprocess_shell') def test_unshell_should_handle_command_throwing_error( - self, shell_mock, print_mock - ): + self, shell_mock: Mock, print_mock: Mock + ) -> None: # given - opt = {"env": {}} + opt: Options = {"env": {}} cmd: str = "echo OK" stderr: str = "cmd error" - def script(): + def script() -> Commands: # type: ignore yield f"{cmd}" # mock @@ -129,7 +137,7 @@ def script(): # when try: - Unshell(opt)(script) + Unshell(opt)(script) # type: ignore except Exception as err: # then err_msg = f"{cmd}: {stderr}" @@ -142,13 +150,13 @@ def script(): @patch('builtins.print') @patch('asyncio.create_subprocess_shell') def test_unshell_should_throw_if_nothing_returns( - self, shell_mock, print_mock - ): + self, shell_mock: Mock, print_mock: Mock + ) -> None: # given - opt = {"env": {}} + opt: Options = {"env": {}} cmd: str = "echo OK" - def script(): + def script() -> Commands: # type: ignore yield f"{cmd}" # mock @@ -156,7 +164,7 @@ def script(): # when try: - Unshell(opt)(script) + Unshell(opt)(script) # type: ignore except Exception as err: # then err_msg = f"unshell: something went wrong" @@ -169,15 +177,15 @@ def script(): @patch('asyncio.create_subprocess_shell') def test_unshell_should_process_several_command( self, - shell_mock, - print_mock - ): + shell_mock: Mock, + print_mock: Mock + ) -> None: # given - opt = {"env": {}} + opt: Options = {"env": {}} cmd: str = "echo OK" stdout: str = "result of echo OK" - def script(): + def script() -> Commands: # type: ignore yield f"{cmd}" yield f"{cmd}" @@ -185,7 +193,7 @@ def script(): shell_mock.return_value = make_future_process(0, stdout, None) # when - Unshell(opt)(script) + Unshell(opt)(script) # type: ignore # then self.assertEqual(shell_mock.mock_calls, [ @@ -211,15 +219,15 @@ def script(): @patch('asyncio.create_subprocess_shell') def test_unshell_should_process_yield_and_return_command( self, - shell_mock, - print_mock - ): + shell_mock: Mock, + print_mock: Mock + ) -> None: # given - opt = {"env": {}} + opt: Options = {"env": {}} cmd: str = "echo OK" stdout: str = "result of echo OK" - def script(): + def script() -> Commands: yield f"{cmd}" return f"{cmd}" @@ -227,7 +235,7 @@ def script(): shell_mock.return_value = make_future_process(0, stdout, None) # when - Unshell(opt)(script) + Unshell(opt)(script) # type: ignore # then self.assertEqual(shell_mock.mock_calls, [ @@ -253,16 +261,16 @@ def script(): @patch('asyncio.create_subprocess_shell') def test_unshell_should_pass_cmd_res_to_next_cmd( self, - shell_mock, - print_mock - ): + shell_mock: Mock, + print_mock: Mock + ) -> None: # given - opt = {"env": {}} + opt: Options = {"env": {}} cmd1: str = "echo 1" cmd2: str = "echo 2" stdout: str = "result of echo" - def script(): + def script() -> Commands: # type: ignore cmd1_res = yield f"{cmd1}" yield f"{cmd2} {cmd1_res}" @@ -270,7 +278,7 @@ def script(): shell_mock.return_value = make_future_process(0, stdout, None) # when - Unshell(opt)(script) + Unshell(opt)(script) # type: ignore # then self.assertEqual(shell_mock.mock_calls, [ @@ -288,14 +296,14 @@ def script(): @patch('builtins.print') @patch('asyncio.create_subprocess_shell') - def test_unshell_should_pass_args_to_script(self, shell_mock, print_mock): + def test_unshell_should_pass_args_to_script(self, shell_mock: Mock, print_mock: Mock) -> None: # given - opt = {"env": {}} + opt: Options = {"env": {}} cmd: str = "echo" script_args: List[str] = ['1', '2'] stdout: str = "result of echo" - def script(*args: List[str]): + def script(*args: List[str]) -> Commands: # type: ignore for arg in args: yield f"{cmd} {arg}" @@ -329,19 +337,19 @@ def script(*args: List[str]): @patch('asyncio.create_subprocess_shell') def test_unshell_should_process_async_command( self, - shell_mock, - print_mock - ): + shell_mock: Mock, + print_mock: Mock + ) -> None: # given - opt = {"env": {}} + opt: Options = {"env": {}} cmd: str = "echo OK" stdout: str = "result of echo OK" stderr: str = "" - async def do_cmd(): + async def do_cmd() -> str: return cmd - async def script(): + async def script() -> AsyncCommands: yield f"{await do_cmd()}" yield f"{await do_cmd()}" @@ -349,7 +357,7 @@ async def script(): shell_mock.return_value = make_future_process(0, stdout, stderr) # when - Unshell(opt)(script) + Unshell(opt)(script) # type: ignore # then self.assertEqual(shell_mock.mock_calls, [ @@ -375,23 +383,23 @@ async def script(): @patch('asyncio.create_subprocess_shell') def test_unshell_should_pass_cmd_res_to_next_async_cmd( self, - shell_mock, - print_mock - ): + shell_mock: Mock, + print_mock: Mock + ) -> None: # given - opt = {"env": {}} + opt: Options = {"env": {}} cmd1: str = "echo 1" cmd2: str = "echo 2" stdout: str = "result of echo OK" stderr: str = "" - async def do_cmd1(): + async def do_cmd1() -> str: return cmd1 - async def do_cmd2(): + async def do_cmd2() -> str: return cmd2 - async def script(): + async def script() -> AsyncCommands: cmd_res = yield f"{await do_cmd1()}" yield f"{await do_cmd2()} {cmd_res}" @@ -399,7 +407,7 @@ async def script(): shell_mock.return_value = make_future_process(0, stdout, stderr) # when - Unshell(opt)(script) + Unshell(opt)(script) # type: ignore # then self.assertEqual(shell_mock.mock_calls, [ diff --git a/src/unshell/type.py b/src/unshell/type.py index 4e884c0..293b0b4 100644 --- a/src/unshell/type.py +++ b/src/unshell/type.py @@ -1,12 +1,20 @@ -from typing import Any, Callable, Generator, AsyncGenerator, List, \ +from typing import Any, Callable, Generator, AsyncGenerator, \ Union, Optional, Awaitable, Dict Options = Dict[Any, Any] -Args = List[Any] +Args = Optional[Any] + Command = str CommandResult = Optional[str] Commands = Generator[Command, CommandResult, Command] AsyncCommands = AsyncGenerator[Command, CommandResult] -Script = Callable[[Args], Commands] -AsyncScript = Callable[[Args], AsyncCommands] -Engine = Callable[[Union[Script, AsyncScript], Args], Awaitable[Any]] + +NoArgsScript = Callable[[], Commands] +ArgsScript = Callable[[Args], Commands] +Script = Union[NoArgsScript, ArgsScript] + +NoArgsAsyncScript = Callable[[], AsyncCommands] +ArgsAsynScript = Callable[[Args], AsyncCommands] +AsyncScript = Union[NoArgsAsyncScript, ArgsAsynScript] + +Engine = Callable[[Union[Script, AsyncScript], Args], Awaitable[None]] diff --git a/src/unshell/utils/colors.py b/src/unshell/utils/colors.py index 2b180fa..3c3676f 100644 --- a/src/unshell/utils/colors.py +++ b/src/unshell/utils/colors.py @@ -1,6 +1,6 @@ -def red(val): +def red(val: str) -> str: return f"\x1b[31m{val}\x1b[0m" -def green(val): +def green(val: str) -> str: return f"\x1b[32m{val}\x1b[0m" diff --git a/src/unshell/utils/pipe.py b/src/unshell/utils/pipe.py index 46ae450..d23742a 100644 --- a/src/unshell/utils/pipe.py +++ b/src/unshell/utils/pipe.py @@ -1,10 +1,10 @@ -from typing import Callable, List, Any +from typing import Callable, Any from functools import reduce -def pipe(f1: Callable, *fns: List[Callable]) -> Callable: - def args(*args: List[Any]) -> str: +def pipe(f1: Callable, *fns: Callable) -> Callable: + def args(*args: Any) -> str: return reduce( lambda res, fn: f"{res} | {fn()}", fns, diff --git a/src/unshell/utils/test_colors.py b/src/unshell/utils/test_colors.py index aa5fae1..dff4292 100644 --- a/src/unshell/utils/test_colors.py +++ b/src/unshell/utils/test_colors.py @@ -5,7 +5,7 @@ class TestColors(unittest.TestCase): - def test_red(self): + def test_red(self) -> None: # given val = 'value' @@ -15,7 +15,7 @@ def test_red(self): # then self.assertEqual(output, f"\x1b[31m{val}\x1b[0m") - def test_green(self): + def test_green(self) -> None: # given val = 'value' diff --git a/src/unshell/utils/test_pipe.py b/src/unshell/utils/test_pipe.py index 65be8f2..7913a6e 100644 --- a/src/unshell/utils/test_pipe.py +++ b/src/unshell/utils/test_pipe.py @@ -7,20 +7,20 @@ class TestPipe(unittest.TestCase): - def test_pipe_should_return_a_function(self): + def test_pipe_should_return_a_function(self) -> None: # given - def echo(x): + def echo(x: str) -> str: return f"echo {x}" # when output = pipe(echo) # then - self.assertTrue(isinstance(output, Callable)) + self.assertTrue(isinstance(output, Callable)) # type: ignore - def test_pipe_should_return_an_empty_string(self): + def test_pipe_should_return_an_empty_string(self) -> None: # given - def echo(x): + def echo(x: str) -> str: return f"echo {x}" # when @@ -30,12 +30,12 @@ def echo(x): # then self.assertEqual(output, f"echo {param}") - def test_pipe_should_pipe_two_function(self): + def test_pipe_should_pipe_two_function(self) -> None: # given - def echo(x): + def echo(x: str) -> str: return f"echo {x}" - def grep(): + def grep() -> str: return "grep world" # when