From 67bd153587695cb24931e01edc0ee8d9b2b90d5e Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 6 Sep 2023 02:43:55 +0300 Subject: [PATCH 01/17] wrong classifier has been deleted --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index a8f1e35..85fceee 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,5 @@ 'Topic :: Software Development :: Interpreters', 'Topic :: Utilities', 'Topic :: System :: Archiving :: Packaging', - 'Topic :: System :: Archiving :: Installation/Setup', ], ) From 5197fa68bfec4e0a33322559e5f747dedd0b13e5 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 6 Sep 2023 22:18:21 +0300 Subject: [PATCH 02/17] comments for comments --- instld/cli/parsing_comments/get_options_from_comments.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/instld/cli/parsing_comments/get_options_from_comments.py b/instld/cli/parsing_comments/get_options_from_comments.py index b668034..af07822 100644 --- a/instld/cli/parsing_comments/get_options_from_comments.py +++ b/instld/cli/parsing_comments/get_options_from_comments.py @@ -21,4 +21,7 @@ def get_options_from_comments(frame): option_value = splitted_option[1].strip().lower() result[option_name] = option_value + result.pop('doc', None) + result.pop('comment', None) + return result From 86f8221204be15f7f1c5d42d87808776c241c616 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Wed, 6 Sep 2023 23:18:53 +0300 Subject: [PATCH 03/17] new options in special comment language --- instld/cli/main.py | 11 ++++++++++- instld/errors.py | 3 +++ instld/module/context.py | 4 ++-- tests/cli/test_cli.py | 24 ++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/instld/cli/main.py b/instld/cli/main.py index 991ae3a..5df2061 100644 --- a/instld/cli/main.py +++ b/instld/cli/main.py @@ -11,6 +11,7 @@ from instld.cli.parsing_comments.get_options_from_comments import get_options_from_comments from instld.cli.parsing_arguments.get_python_file import get_python_file from instld.cli.traceback_cutting.cutting import set_cutting_excepthook +from instld.errors import CommentFormatError def main(): @@ -56,6 +57,14 @@ def import_wrapper(name, *args, **kwargs): if 'version' in options: package_name = f'{package_name}=={options.pop("version")}' + catch_output = options.pop('catch_output', 'no').lower() + if catch_output in ('yes', 'on', 'true'): + catch_output = True + elif catch_output in ('no', 'off', 'false'): + catch_output = False + else: + raise CommentFormatError('For option "catch_output" you can use the following values: "yes", "on", "true", "no", "off", "false".') + current_context = get_current_context(options.pop('where', None)) with lock: @@ -63,7 +72,7 @@ def import_wrapper(name, *args, **kwargs): try: result = __import__(name, *args, **kwargs) except (ModuleNotFoundError, ImportError) as e: - current_context.install(package_name) + current_context.install(package_name, catch_output=catch_output, **options) result = current_context.import_here(base_name) sys.modules[base_name] = result diff --git a/instld/errors.py b/instld/errors.py index 5fa6a34..764e682 100644 --- a/instld/errors.py +++ b/instld/errors.py @@ -6,3 +6,6 @@ class RestartingCommandError(Exception): class RunningCommandError(Exception): pass + +class CommentFormatError(Exception): + pass diff --git a/instld/module/context.py b/instld/module/context.py index 16205d9..2d43b88 100644 --- a/instld/module/context.py +++ b/instld/module/context.py @@ -42,10 +42,10 @@ def new_path(self, module_name): yield sys.path = old_path - def install(self, *package_names, **options): + def install(self, *package_names, catch_output=False, **options): if not package_names: raise ValueError('You need to pass at least one package name.') options = convert_options(options) - with self.installer(package_names, options=options): + with self.installer(package_names, catch_output=catch_output, options=options): pass diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index 4546e7a..dafcf9e 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -125,3 +125,27 @@ def test_exceptions_are_similar_with_just_python_command_2(): assert result_1.stderr == result_2.stderr os.remove(script) + + +def test_install_package_from_another_repository(main_runner): + strings = [ + 'import super_test # instld: package super_test_project, version 0.0.1, index_url https://test.pypi.org/simple/, catch_output true', + 'print(super_test.function(2, 3))', + ] + + script = os.path.join('tests', 'cli', 'data', 'main.py') + with open(script, 'w') as file: + file.write('\n'.join(strings)) + + for runner in (subprocess.run, main_runner): + result = runner(['instld', script], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=100, universal_newlines=True) + + result.check_returncode() + + print(result.stderr) + print(result.stdout) + #assert result.stderr == '' + assert result.stdout == '5\n' + + + os.remove(script) From 6e1430336802122ee85b0f281a38f14ed9b520a8 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Sat, 9 Sep 2023 19:36:36 +0300 Subject: [PATCH 04/17] REPL (without comments reading) --- instld/cli/main.py | 43 +++++++++++++++---- .../cli/parsing_arguments/get_python_file.py | 7 +-- .../parsing_comments/get_comment_string.py | 2 +- .../get_options_from_comments.py | 10 +++-- instld/module/context_manager.py | 2 + tests/cli/test_cli.py | 13 +----- .../test_get_comment_string.py | 10 ++--- .../test_get_options_from_comments.py | 12 +++--- 8 files changed, 58 insertions(+), 41 deletions(-) diff --git a/instld/cli/main.py b/instld/cli/main.py index 5df2061..f63a4f9 100644 --- a/instld/cli/main.py +++ b/instld/cli/main.py @@ -1,5 +1,6 @@ import os import sys +import code import builtins import importlib import inspect @@ -8,7 +9,7 @@ from threading import RLock import instld -from instld.cli.parsing_comments.get_options_from_comments import get_options_from_comments +from instld.cli.parsing_comments.get_options_from_comments import get_options_from_comments_by_frame from instld.cli.parsing_arguments.get_python_file import get_python_file from instld.cli.traceback_cutting.cutting import set_cutting_excepthook from instld.errors import CommentFormatError @@ -50,7 +51,8 @@ def import_wrapper(name, *args, **kwargs): last_name = splitted_name[-1] current_frame = inspect.currentframe() - options = get_options_from_comments(current_frame.f_back) + options = get_options_from_comments_by_frame(current_frame.f_back) + #print('OPTIONS:', options) package_name = options.pop('package', base_name) @@ -72,6 +74,7 @@ def import_wrapper(name, *args, **kwargs): try: result = __import__(name, *args, **kwargs) except (ModuleNotFoundError, ImportError) as e: + #print('OPTIONS>', options, package_name) current_context.install(package_name, catch_output=catch_output, **options) result = current_context.import_here(base_name) sys.modules[base_name] = result @@ -87,13 +90,37 @@ def import_wrapper(name, *args, **kwargs): return result - builtins.__import__ = import_wrapper - spec = importlib.util.spec_from_file_location('kek', os.path.abspath(python_file)) - module = importlib.util.module_from_spec(spec) - sys.modules['__main__'] = module - set_cutting_excepthook(4) - spec.loader.exec_module(module) + + if python_file is None: + try: + import readline + except ImportError: + pass + + builtins.__import__ = import_wrapper + + class REPL(code.InteractiveConsole): + pass + + + banner_strings = [ + '⚡ INSTLD REPL based on\n' + 'Python %s on %s\n' % (sys.version, sys.platform), + 'Type "help", "copyright", "credits" or "license" for more information.\n', + ] + banner = ''.join(banner_strings) + + REPL().interact(banner=banner) + + + else: + builtins.__import__ = import_wrapper + spec = importlib.util.spec_from_file_location('kek', os.path.abspath(python_file)) + module = importlib.util.module_from_spec(spec) + sys.modules['__main__'] = module + set_cutting_excepthook(4) + spec.loader.exec_module(module) if __name__ == "__main__": diff --git a/instld/cli/parsing_arguments/get_python_file.py b/instld/cli/parsing_arguments/get_python_file.py index fe667dd..dc8b8c1 100644 --- a/instld/cli/parsing_arguments/get_python_file.py +++ b/instld/cli/parsing_arguments/get_python_file.py @@ -3,8 +3,5 @@ def get_python_file(): - if len(sys.argv) < 2: - print('usage: instld python_file.py [argv ...]', file=sys.stderr) - sys.exit(1) - - return sys.argv[1] + if len(sys.argv) >= 2: + return sys.argv[1] diff --git a/instld/cli/parsing_comments/get_comment_string.py b/instld/cli/parsing_comments/get_comment_string.py index 9db6f38..850ba94 100644 --- a/instld/cli/parsing_comments/get_comment_string.py +++ b/instld/cli/parsing_comments/get_comment_string.py @@ -24,7 +24,7 @@ def get_comment_string_from_file(line_number, file_name): except (FileNotFoundError, OSError): return None -def get_comment_string(frame): +def get_comment_string_by_frame(frame): line_number = frame.f_lineno code = frame.f_code file_name = code.co_filename diff --git a/instld/cli/parsing_comments/get_options_from_comments.py b/instld/cli/parsing_comments/get_options_from_comments.py index af07822..6ddb0fc 100644 --- a/instld/cli/parsing_comments/get_options_from_comments.py +++ b/instld/cli/parsing_comments/get_options_from_comments.py @@ -1,10 +1,8 @@ from instld.errors import InstallingPackageError -from instld.cli.parsing_comments.get_comment_string import get_comment_string +from instld.cli.parsing_comments.get_comment_string import get_comment_string_by_frame -def get_options_from_comments(frame): - comment_string = get_comment_string(frame) - +def get_options_from_comments(comment_string): result = {} if comment_string is not None: @@ -25,3 +23,7 @@ def get_options_from_comments(frame): result.pop('comment', None) return result + +def get_options_from_comments_by_frame(frame): + comment_string = get_comment_string_by_frame(frame) + return get_options_from_comments(comment_string) diff --git a/instld/module/context_manager.py b/instld/module/context_manager.py index fdda090..05c4e8d 100644 --- a/instld/module/context_manager.py +++ b/instld/module/context_manager.py @@ -52,6 +52,8 @@ def create_temp_directory(): new_error = InstallingPackageError(f'{str(e)} It occurred when installing one of the following packages: {", ".join(packages_names)}.') new_error.stdout = e.stdout new_error.stderr = e.stderr + #print('STDOUT', e.stdout) + #print('STDERR', e.stderr) raise new_error from e yield Context(where, logger, catch_output, options, partial(pip_context, logger=logger, runner=runner, catch_output=catch_output, where=directory)) diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index dafcf9e..538a554 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -43,15 +43,6 @@ def test_cli_where(main_runner): os.remove(script) -def test_run_command_without_arguments(main_runner): - for runner in (main_runner, subprocess.run): - result = runner(['instld'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=100, universal_newlines=True) - - assert result.returncode == 1 - assert result.stdout == '' - assert result.stderr == f'usage: instld python_file.py [argv ...]\n' - - def test_run_command_with_arguments(main_runner): strings = [ 'import json, sys', @@ -140,11 +131,9 @@ def test_install_package_from_another_repository(main_runner): for runner in (subprocess.run, main_runner): result = runner(['instld', script], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=100, universal_newlines=True) + print(result.stderr) result.check_returncode() - print(result.stderr) - print(result.stdout) - #assert result.stderr == '' assert result.stdout == '5\n' diff --git a/tests/units/cli/parsing_comments/test_get_comment_string.py b/tests/units/cli/parsing_comments/test_get_comment_string.py index a86af74..9994306 100644 --- a/tests/units/cli/parsing_comments/test_get_comment_string.py +++ b/tests/units/cli/parsing_comments/test_get_comment_string.py @@ -3,24 +3,24 @@ import pytest from instld.errors import InstallingPackageError -from instld.cli.parsing_comments.get_comment_string import get_comment_string +from instld.cli.parsing_comments.get_comment_string import get_comment_string_by_frame def test_get_comment_started_with_instld(): - comment = get_comment_string(inspect.currentframe()) # instld: lol kek cheburek + comment = get_comment_string_by_frame(inspect.currentframe()) # instld: lol kek cheburek assert comment == 'lol kek cheburek' def test_get_comment_not_started_with_instld(): - comment = get_comment_string(inspect.currentframe()) # lol kek cheburek + comment = get_comment_string_by_frame(inspect.currentframe()) # lol kek cheburek assert comment is None def test_get_comment_without_comment(): - comment = get_comment_string(inspect.currentframe()) + comment = get_comment_string_by_frame(inspect.currentframe()) assert comment is None def test_get_comment_wrong(): with pytest.raises(InstallingPackageError): - comment = get_comment_string(inspect.currentframe()) # instld: + comment = get_comment_string_by_frame(inspect.currentframe()) # instld: diff --git a/tests/units/cli/parsing_comments/test_get_options_from_comments.py b/tests/units/cli/parsing_comments/test_get_options_from_comments.py index 41ed6ac..1d00917 100644 --- a/tests/units/cli/parsing_comments/test_get_options_from_comments.py +++ b/tests/units/cli/parsing_comments/test_get_options_from_comments.py @@ -3,11 +3,11 @@ import pytest from instld.errors import InstallingPackageError -from instld.cli.parsing_comments.get_options_from_comments import get_options_from_comments +from instld.cli.parsing_comments.get_options_from_comments import get_options_from_comments_by_frame def test_get_normal_options(): - options = get_options_from_comments(inspect.currentframe()) # instld: lol kek, cheburek mek + options = get_options_from_comments_by_frame(inspect.currentframe()) # instld: lol kek, cheburek mek assert isinstance(options, dict) assert len(options) == 2 @@ -18,15 +18,15 @@ def test_get_normal_options(): def test_get_wrong_options(): with pytest.raises(InstallingPackageError): - options = get_options_from_comments(inspect.currentframe()) # instld: lol kek cheburek, cheburek mek + options = get_options_from_comments_by_frame(inspect.currentframe()) # instld: lol kek cheburek, cheburek mek with pytest.raises(InstallingPackageError): - options = get_options_from_comments(inspect.currentframe()) # instld: lol + options = get_options_from_comments_by_frame(inspect.currentframe()) # instld: lol with pytest.raises(InstallingPackageError): - options = get_options_from_comments(inspect.currentframe()) # instld: + options = get_options_from_comments_by_frame(inspect.currentframe()) # instld: def test_get_empty_options(): - options = get_options_from_comments(inspect.currentframe()) + options = get_options_from_comments_by_frame(inspect.currentframe()) assert isinstance(options, dict) assert len(options) == 0 From ac42ae9c1a22aa5126c15e202ab173628b72ccd3 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 11 Sep 2023 17:01:31 +0300 Subject: [PATCH 05/17] gitignore --- .gitignore | 1 + tests/cli/data/main.py | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 tests/cli/data/main.py diff --git a/.gitignore b/.gitignore index b2fa7cc..c212e5a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ test.py .coverage.* tests/cli/data/chpok tests/cli/data/pok +.idea diff --git a/tests/cli/data/main.py b/tests/cli/data/main.py new file mode 100644 index 0000000..e474ef6 --- /dev/null +++ b/tests/cli/data/main.py @@ -0,0 +1,2 @@ +import super_test # instld: package super_test_project, version 0.0.1, index_url https://test.pypi.org/simple/, catch_output true +print(super_test.function(2, 3)) \ No newline at end of file From e1ef76981a2fd864a722a7eca065ffce5542fa55 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Thu, 19 Oct 2023 02:06:52 +0300 Subject: [PATCH 06/17] classifiers --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 85fceee..4d028e4 100644 --- a/setup.py +++ b/setup.py @@ -36,5 +36,7 @@ 'Topic :: Software Development :: Interpreters', 'Topic :: Utilities', 'Topic :: System :: Archiving :: Packaging', + 'Intended Audience :: System Administrators', + 'Intended Audience :: Developers', ], ) From dd1405423debc43134685c8fad5e093396d2f994 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 13 Nov 2023 11:31:03 +0300 Subject: [PATCH 07/17] +1 dependency --- requirements_dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements_dev.txt b/requirements_dev.txt index 767efeb..a4b56c9 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -3,3 +3,4 @@ coverage==7.2.7 twine==4.0.2 wheel==0.40.0 pytest-timeout==2.1.0 +contextif==0.0.3 From b4953bbf33a0370907317928238043f2f864d982 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 13 Nov 2023 11:36:32 +0300 Subject: [PATCH 08/17] tests --- tests/cli/data/main.py | 2 -- tests/cli/test_cli.py | 24 +++++++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) delete mode 100644 tests/cli/data/main.py diff --git a/tests/cli/data/main.py b/tests/cli/data/main.py deleted file mode 100644 index e474ef6..0000000 --- a/tests/cli/data/main.py +++ /dev/null @@ -1,2 +0,0 @@ -import super_test # instld: package super_test_project, version 0.0.1, index_url https://test.pypi.org/simple/, catch_output true -print(super_test.function(2, 3)) \ No newline at end of file diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index 538a554..14cccb4 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -5,6 +5,7 @@ import shutil import pytest +from contextif import state @pytest.mark.timeout(180) @@ -118,6 +119,7 @@ def test_exceptions_are_similar_with_just_python_command_2(): os.remove(script) +@pytest.mark.skip(reason="Now it's not so actual.") def test_install_package_from_another_repository(main_runner): strings = [ 'import super_test # instld: package super_test_project, version 0.0.1, index_url https://test.pypi.org/simple/, catch_output true', @@ -131,7 +133,27 @@ def test_install_package_from_another_repository(main_runner): for runner in (subprocess.run, main_runner): result = runner(['instld', script], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=100, universal_newlines=True) - print(result.stderr) + result.check_returncode() + + assert result.stdout == '5\n' + + + os.remove(script) + + +def test_install_package_from_another_repository_only_command(): + strings = [ + 'import super_test # instld: package super_test_project, version 0.0.1, index_url https://test.pypi.org/simple/, catch_output true', + 'print(super_test.function(2, 3))', + ] + + script = os.path.join('tests', 'cli', 'data', 'main.py') + with open(script, 'w') as file: + file.write('\n'.join(strings)) + + for runner in (subprocess.run,): + result = runner(['instld', script], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=100, universal_newlines=True) + result.check_returncode() assert result.stdout == '5\n' From 0e33b16b17c180c7c3690b24eb56ad77deb045ac Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 13 Nov 2023 13:07:07 +0300 Subject: [PATCH 09/17] special comment language support in the REPL --- instld/cli/main.py | 11 +++--- .../parsing_comments/get_comment_string.py | 37 +++++++++++-------- instld/state_management/__init__.py | 0 instld/state_management/storage.py | 17 +++++++++ .../test_get_comment_string.py | 10 ++++- 5 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 instld/state_management/__init__.py create mode 100644 instld/state_management/storage.py diff --git a/instld/cli/main.py b/instld/cli/main.py index f63a4f9..10fcd6a 100644 --- a/instld/cli/main.py +++ b/instld/cli/main.py @@ -12,11 +12,13 @@ from instld.cli.parsing_comments.get_options_from_comments import get_options_from_comments_by_frame from instld.cli.parsing_arguments.get_python_file import get_python_file from instld.cli.traceback_cutting.cutting import set_cutting_excepthook +from instld.state_management.storage import state_storage, RunType from instld.errors import CommentFormatError def main(): python_file = get_python_file() + state_storage.run_type = RunType.script with instld() as context: lock = RLock() @@ -52,7 +54,6 @@ def import_wrapper(name, *args, **kwargs): current_frame = inspect.currentframe() options = get_options_from_comments_by_frame(current_frame.f_back) - #print('OPTIONS:', options) package_name = options.pop('package', base_name) @@ -74,7 +75,6 @@ def import_wrapper(name, *args, **kwargs): try: result = __import__(name, *args, **kwargs) except (ModuleNotFoundError, ImportError) as e: - #print('OPTIONS>', options, package_name) current_context.install(package_name, catch_output=catch_output, **options) result = current_context.import_here(base_name) sys.modules[base_name] = result @@ -90,18 +90,19 @@ def import_wrapper(name, *args, **kwargs): return result - - if python_file is None: try: import readline except ImportError: pass + state_storage.run_type = RunType.REPL builtins.__import__ = import_wrapper class REPL(code.InteractiveConsole): - pass + def push(self, line): + state_storage.last_string = line + return super().push(line) banner_strings = [ diff --git a/instld/cli/parsing_comments/get_comment_string.py b/instld/cli/parsing_comments/get_comment_string.py index 850ba94..e22b114 100644 --- a/instld/cli/parsing_comments/get_comment_string.py +++ b/instld/cli/parsing_comments/get_comment_string.py @@ -1,32 +1,39 @@ from functools import lru_cache from instld.errors import InstallingPackageError +from instld.state_management.storage import state_storage, RunType +def get_comment_substring_from_string(string): + splitted_line = string.split('#') + right_part = splitted_line[1:] + right_part = '#'.join(right_part) + right_part = right_part.strip() + if right_part.startswith('instld:'): + right_part = right_part[7:].strip() + if right_part: + return right_part + else: + raise InstallingPackageError('An empty list of options in the comment.') + @lru_cache() def get_comment_string_from_file(line_number, file_name): try: with open(file_name, 'r') as file: for index, line in enumerate(file): if index + 1 == line_number: - splitted_line = line.split('#') - right_part = splitted_line[1:] - right_part = '#'.join(right_part) - right_part = right_part.strip() - if right_part.startswith('instld:'): - right_part = right_part[7:].strip() - if right_part: - return right_part - else: - raise InstallingPackageError('An empty list of options in the comment.') - break + return get_comment_substring_from_string(line) except (FileNotFoundError, OSError): return None def get_comment_string_by_frame(frame): - line_number = frame.f_lineno - code = frame.f_code - file_name = code.co_filename + if state_storage.run_type == RunType.script: + line_number = frame.f_lineno + code = frame.f_code + file_name = code.co_filename + + return get_comment_string_from_file(line_number, file_name) - return get_comment_string_from_file(line_number, file_name) + elif state_storage.run_type == RunType.REPL: + return get_comment_substring_from_string(state_storage.last_string) diff --git a/instld/state_management/__init__.py b/instld/state_management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/instld/state_management/storage.py b/instld/state_management/storage.py new file mode 100644 index 0000000..c65cc26 --- /dev/null +++ b/instld/state_management/storage.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass +from enum import IntEnum, auto +from typing import Optional + + +class RunType(IntEnum): + script = auto() + REPL = auto() + module = auto() + +@dataclass +class StateStorage: + run_type: RunType = RunType.module + last_string: Optional[str] = None + + +state_storage = StateStorage() diff --git a/tests/units/cli/parsing_comments/test_get_comment_string.py b/tests/units/cli/parsing_comments/test_get_comment_string.py index 9994306..89ff205 100644 --- a/tests/units/cli/parsing_comments/test_get_comment_string.py +++ b/tests/units/cli/parsing_comments/test_get_comment_string.py @@ -3,7 +3,7 @@ import pytest from instld.errors import InstallingPackageError -from instld.cli.parsing_comments.get_comment_string import get_comment_string_by_frame +from instld.cli.parsing_comments.get_comment_string import get_comment_string_by_frame, get_comment_substring_from_string def test_get_comment_started_with_instld(): @@ -24,3 +24,11 @@ def test_get_comment_without_comment(): def test_get_comment_wrong(): with pytest.raises(InstallingPackageError): comment = get_comment_string_by_frame(inspect.currentframe()) # instld: + + +def test_get_comment_substring_from_string(): + assert get_comment_substring_from_string('a + b # kek') is None + assert get_comment_substring_from_string('a + b # instld: lol kek') == 'lol kek' + + with pytest.raises(InstallingPackageError): + assert get_comment_substring_from_string('a + b # instld: ') From 278086cd4b20cab85992cb1d6bfbfddc0ab2b2e0 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 13 Nov 2023 13:57:48 +0300 Subject: [PATCH 10/17] new version tag --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4d028e4..0c08f7c 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='instld', - version='0.0.21', + version='0.0.22', author='Evgeniy Blinov', author_email='zheni-b@yandex.ru', description='The simplest package management', From 9857c85d623cc112c169de9722ac745c71bd3098 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 13 Nov 2023 14:05:13 +0300 Subject: [PATCH 11/17] codecov file --- .codecov.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..236a6f1 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,5 @@ +coverage: + status: + project: + default: + threshold: 1% From 19cd3fbec6f02d2e41898f73a8fffa8d9f9786d5 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 13 Nov 2023 14:05:42 +0300 Subject: [PATCH 12/17] threshold in the codecov file --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 236a6f1..6e72539 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -2,4 +2,4 @@ coverage: status: project: default: - threshold: 1% + threshold: 8% From 3755e83890bd947fe8f0d6534ce26f0f20200bae Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 13 Nov 2023 14:13:54 +0300 Subject: [PATCH 13/17] readme --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fd9a586..1e2d575 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Thanks to this package, it is very easy to manage the lifecycle of packages. ## Table of contents - [**Quick start**](#quick-start) +- [**REPL mode**](#repl-mode) - [**Script launch mode**](#script-launch-mode) - [**Special comment language**](#special-comment-language) - [**Using multiple environments**](#using-multiple-environments) @@ -39,7 +40,7 @@ Install [it](https://pypi.org/project/instld/): pip install instld ``` -And use the library in one of two ways: by running your script through it or by importing a context manager from there. +And use the library in one of three ways: by typing commands via REPL, by running your script through it or by importing a context manager from there. If you run the script [like this](#script-launch-mode), all dependencies will be automatically installed when the application starts and deleted when it stops: @@ -59,6 +60,24 @@ with instld('some_package'): Read more about each method, its capabilities and limitations below. +## REPL mode + +REPL mode is the fastest and easiest way to try out other people's libraries for your code. Just type this in your console: + +```bash +instld +``` + +After that you will see a welcome message similar to this: + +``` +⚡ INSTLD REPL based on +Python 3.11.6 (main, Oct 2 2023, 13:45:54) [Clang 15.0.0 (clang-1500.0.40.1)] on darwin +Type "help", "copyright", "credits" or "license" for more information. + +>>> +``` + ## Script launch mode You can use `instld` to run your script. To do this, you need to run a command like this in the console: From f2e8f032bf327dc3f726993816b05356f401f1e8 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 13 Nov 2023 14:19:21 +0300 Subject: [PATCH 14/17] readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e2d575..6265ef2 100644 --- a/README.md +++ b/README.md @@ -75,9 +75,11 @@ After that you will see a welcome message similar to this: Python 3.11.6 (main, Oct 2 2023, 13:45:54) [Clang 15.0.0 (clang-1500.0.40.1)] on darwin Type "help", "copyright", "credits" or "license" for more information. ->>> +>>> ``` +Enjoy the regular Python [interactive console mode](https://docs.python.org/3/tutorial/interpreter.html#interactive-mode)! Any libraries that you ask for will be installed within the session, and after exiting it, they will be deleted without a trace. You don't need to "clean up" anything after exiting the console. + ## Script launch mode You can use `instld` to run your script. To do this, you need to run a command like this in the console: From 1033bda43263e65f5cfc7cc0aa1bf2e868b1e711 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 13 Nov 2023 14:24:39 +0300 Subject: [PATCH 15/17] readme --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6265ef2..8abd0a6 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,8 @@ If you run the script [like this](#script-launch-mode), all dependencies will be instld script.py ``` +The [REPL mode](#repl-mode) works in a similar way, you just need to type `instld` in the console to enter it. + You can also call the [context manager](#context-manager-mode) from your code: ```python @@ -80,9 +82,11 @@ Type "help", "copyright", "credits" or "license" for more information. Enjoy the regular Python [interactive console mode](https://docs.python.org/3/tutorial/interpreter.html#interactive-mode)! Any libraries that you ask for will be installed within the session, and after exiting it, they will be deleted without a trace. You don't need to "clean up" anything after exiting the console. +In this mode, a [special comment language](#special-comment-language) is fully supported. + ## Script launch mode -You can use `instld` to run your script. To do this, you need to run a command like this in the console: +You can use `instld` to run your script from a file. To do this, you need to run a command like this in the console: ```bash instld script.py @@ -93,7 +97,7 @@ The contents of the script will be executed in the same way as if you were runni ### Special comment language -When using script launch mode, you can specify additional parameters for each import inside your program. To do this, you need to write immediately after it (but always in the same line!) a comment that starts with "instld:", separating key and value pairs with commas. +When using script launch or REPL mode, you can specify additional parameters for each import inside your program. To do this, you need to write immediately after it (but always in the same line!) a comment that starts with "instld:", separating key and value pairs with commas. As example, if the name of the imported module and the package name are different, this code imports the `f` function from the [`fazy`](https://github.com/pomponchik/fazy) library version `0.0.3`: From 7fa9aef49227af77a5c33feef1ecff383d222e63 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 13 Nov 2023 14:29:37 +0300 Subject: [PATCH 16/17] readme --- README.md | 90 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 8abd0a6..5dd7852 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,13 @@ Thanks to this package, it is very easy to manage the lifecycle of packages. - [**Quick start**](#quick-start) - [**REPL mode**](#repl-mode) - [**Script launch mode**](#script-launch-mode) - - [**Special comment language**](#special-comment-language) - - [**Using multiple environments**](#using-multiple-environments) - [**Context manager mode**](#context-manager-mode) - [**Installing multiple packages**](#installing-multiple-packages) - [**Options**](#options) - [**Using an existing virtual environment**](#using-an-existing-virtual-environment) - [**Output and logging**](#output-and-logging) +- [**Special comment language**](#special-comment-language) +- [**Using multiple environments**](#using-multiple-environments) - [**How does it work?**](#how-does-it-work) @@ -94,47 +94,7 @@ instld script.py The contents of the script will be executed in the same way as if you were running it through the `python script.py` command. If necessary, you can pass additional arguments to the command line, as if you are running a regular Python script. However, if your program has imports of any packages other than the built-in ones, they will be installed automatically. Installed packages are automatically cleaned up when you exit the program, so they don't leave any garbage behind. - -### Special comment language - -When using script launch or REPL mode, you can specify additional parameters for each import inside your program. To do this, you need to write immediately after it (but always in the same line!) a comment that starts with "instld:", separating key and value pairs with commas. - -As example, if the name of the imported module and the package name are different, this code imports the `f` function from the [`fazy`](https://github.com/pomponchik/fazy) library version `0.0.3`: - -```python -import f # instld: version 0.0.3, package fazy - -print(f('some string')) -``` - -You can also specify only the version or only the package name in the comment, they do not have to be specified together. - - -### Using multiple environments - -The instld script launch mode provides a unique opportunity to use multiple virtual environments at the same time. - -Firstly, you can run scripts in the main virtual environment, and it will work exactly as you expect: - -```bash -python3 -m venv venv -source venv/bin/activate -instld script.py -``` - -When the "import" command is executed in your script, the package will first be searched in the activated virtual environment, and only then downloaded if it is not found there. Note that by default, the activated virtual environment is read-only. That is, it is assumed that you will install all the necessary libraries there before running your script. If you want to install packages in runtime in a specific virtual environment - read about the second method further. - -Secondly, you can specify the path to the virtual environment directly [in the comments](#special-comment-language) to a specific import using the `where` directive: - -```python -import something # instld: where path/to/the/venv -``` - -If the path you specified does not exist when you first run the script, it will be automatically created. Libraries installed in this way are not deleted when the script is stopped, therefore, starting from the second launch, the download is no longer required. - -Note that the path to the virtual environment in this case should not contain spaces. In addition, there is no multiplatform way to specify directory paths using a comment. Therefore, it is not recommended to use paths consisting of more than one part. - -Since script launch mode uses a context manager to install packages "under the hood", you should also read about the features of installing packages in this way in the [corresponding section](#using-an-existing-virtual-environment). +In this mode, as in [REPL](#repl-mode), a [special comment language](#special-comment-language) is fully supported. ## Context manager mode @@ -199,7 +159,7 @@ with instld('flask==2.0.2') as context_1: > ⚠️ Keep in mind that although inter-thread isolation is used inside the library, working with contexts is not completely thread-safe. You can write code in such a way that two different contexts import different modules in separate threads at the same time. In this case, you may get paradoxical results. Therefore, it is recommended to additionally isolate with mutexes all cases where you import something from contexts in different threads. -### Options +## Options You can use [any options](https://pip.pypa.io/en/stable/cli/pip_install/) available for `pip`. To do this, you need to slightly change the name of the option, replacing the hyphens with underscores, and pass it as an argument to `instld`. Here is an example of how using the `--index-url` option will look like: @@ -309,6 +269,48 @@ with instld('flask', catch_output=True): The `INFO` [level](https://docs.python.org/3/library/logging.html#logging-levels) is used by default. For errors - `ERROR`. +## Special comment language + +When using script launch or REPL mode, you can specify additional parameters for each import inside your program. To do this, you need to write immediately after it (but always in the same line!) a comment that starts with "instld:", separating key and value pairs with commas. + +As example, if the name of the imported module and the package name are different, this code imports the `f` function from the [`fazy`](https://github.com/pomponchik/fazy) library version `0.0.3`: + +```python +import f # instld: version 0.0.3, package fazy + +print(f('some string')) +``` + +You can also specify only the version or only the package name in the comment, they do not have to be specified together. + + +## Using multiple environments + +The instld script launch mode and REPL mode provides a unique opportunity to use multiple virtual environments at the same time. + +Firstly, you can run scripts in the main virtual environment, and it will work exactly as you expect: + +```bash +python3 -m venv venv +source venv/bin/activate +instld script.py +``` + +When the "import" command is executed in your script, the package will first be searched in the activated virtual environment, and only then downloaded if it is not found there. Note that by default, the activated virtual environment is read-only. That is, it is assumed that you will install all the necessary libraries there before running your script. If you want to install packages in runtime in a specific virtual environment - read about the second method further. + +Secondly, you can specify the path to the virtual environment directly [in the comments](#special-comment-language) to a specific import using the `where` directive: + +```python +import something # instld: where path/to/the/venv +``` + +If the path you specified does not exist when you first run the script, it will be automatically created. Libraries installed in this way are not deleted when the script is stopped, therefore, starting from the second launch, the download is no longer required. + +Note that the path to the virtual environment in this case should not contain spaces. In addition, there is no multiplatform way to specify directory paths using a comment. Therefore, it is not recommended to use paths consisting of more than one part. + +Since script launch mode uses a context manager to install packages "under the hood", you should also read about the features of installing packages in this way in the [corresponding section](#using-an-existing-virtual-environment). + + ## How does it work? This package is essentially a wrapper for `venv` and `pip`. From fde8f1eae803f6e89736736baa8d21d176cc3d44 Mon Sep 17 00:00:00 2001 From: Evgeniy Blinov Date: Mon, 13 Nov 2023 14:36:21 +0300 Subject: [PATCH 17/17] threshold in the codecov file --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 6e72539..6ac3b93 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -2,4 +2,4 @@ coverage: status: project: default: - threshold: 8% + threshold: 80%