From c0f05a731a386c6bb85c3023704a8cf02696adeb Mon Sep 17 00:00:00 2001 From: turulomio Date: Tue, 24 Feb 2026 19:18:16 +0100 Subject: [PATCH 1/5] Added code to preprod_invoke --- .vscode/settings.json | 4 ++++ preprod/commons.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6a742d9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:poetry", + "python-envs.defaultPackageManager": "ms-python.python:poetry" +} \ No newline at end of file diff --git a/preprod/commons.py b/preprod/commons.py index 623390e..02b2063 100644 --- a/preprod/commons.py +++ b/preprod/commons.py @@ -534,3 +534,44 @@ def poetry_env_info(): python_=p.stdout.decode('utf-8')[:-1] pip_=python_.replace("bin/python", "bin/pip") return python_, pip_ + +def preprod(project, action, pretend=False, description=""): + """ + Invokes another preprod action from within the current script. + Note: This function will terminate the current process if the invoked + preprod action encounters an error and calls sys.exit(). + + Parameters: + - project (str): The name of the project. + - action (str): The name of the action within the project. + - pretend (bool): If True, runs the action in pretend mode. + - description (str): Optional description for logging. + """ + # Lazy import to avoid circular dependency with preprod.core + from preprod import core + from preprod.core import concurrent_log + + log_message = _("Invoking preprod action '{0}' for project '{1}'").format(action, project) + if pretend: + log_message += _(" (pretend mode)") + + description = log_message if description == "" else description + print_before(description, description is not None) + + args_list = [project, action] + if pretend: + args_list.append('--pretend') + + try: + core.main(args_list) + print_after_ok(description is not None) + concurrent_log(log_message + _(" - Succeeded")) + except SystemExit as e: + return_code = e.code + print_after_error(description is not None) + concurrent_log(log_message + _(" - Failed with exit code {0}").format(return_code)) + raise # Re-raise the SystemExit to ensure the process terminates as core.main intended. + except Exception as e: + print_after_error(description is not None) + concurrent_log(log_message + _(" - Failed with unexpected error: {0}").format(str(e))) + raise # Re-raise any other unexpected exception From 3d7ff55153e4ab94b69f8314fccc0e465c3b051a Mon Sep 17 00:00:00 2001 From: turulomio Date: Tue, 24 Feb 2026 19:41:36 +0100 Subject: [PATCH 2/5] Added test --- preprod/tests/test_commons.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/preprod/tests/test_commons.py b/preprod/tests/test_commons.py index 6562c59..c45fc46 100644 --- a/preprod/tests/test_commons.py +++ b/preprod/tests/test_commons.py @@ -1,6 +1,6 @@ from inspect import currentframe from preprod import commons, core -from os import system, path +from os import system, path,remove from pytest import raises, fixture from shutil import which @@ -12,6 +12,7 @@ def setup_and_teardown(): # Code to run at the beginning print("Creating preprod test project!") + system(f"rm -Rf {tmp_path}") system(f"mkdir -p {project_test_path}") # Anything you need to initialize @@ -202,6 +203,25 @@ def test_commons_npm_install(): assert path.exists(f"{tmp_test_path}/calories_tracker/node_modules/") +def test_commons_preprod(): + # Create new action to test from preprod + tmp_test_path = create_and_run_action("testing_preprod_command", """ +preprod_commons.system("touch testing_preprod_command.txt") + """) + assert path.exists(f"testing_preprod_command.txt") + remove("testing_preprod_command.txt") + assert not path.exists(f"testing_preprod_command.txt") + + + # Call create_command remove command + tmp_test_path = create_and_run_action("preprod_remove_testing_preprod_command", """ +preprod_commons.preprod("test", "testing_preprod_command") + """) + assert path.exists("testing_preprod_command.txt") + remove("testing_preprod_command.txt") + assert not path.exists(f"testing_preprod_command.txt") + + def test_list(): with raises(SystemExit): core.main([]) From 649fcf33b2a01c2c7633520318990911f6a7042a Mon Sep 17 00:00:00 2001 From: turulomio Date: Tue, 24 Feb 2026 19:45:33 +0100 Subject: [PATCH 3/5] Immproving doc --- README.md | 50 ++++--------------------------------------- preprod/commons.py | 5 ++++- preprod/core.py | 25 +++++++++++++++++++++- preprod/poethepoet.py | 17 +++++++++++++++ 4 files changed, 49 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 7c67a68..82adcbf 100644 --- a/README.md +++ b/README.md @@ -55,49 +55,7 @@ I hope you like it and give me a star. You can see all preprod_commons method [here](doc/PREPROD_COMMANDS.md) - -## Changelog - -### 1.3.0 (2025-03-13) -- Migrated to poetry>2.0.0 - -### 1.2.0 (2024-07-25) -- Added branch support to git clone command -- Added number_of_sockets and socket_timeout parameters to nmcli_net_change command - -### 1.1.0 (2024-07-03) -- Running system with other user changes before to current working directory -- Added kill_from_ps_aux method - -### 1.0.1 (2024-06-29) -- run_and_check allows to add user and password parameters -- system allows to add user parameter - -### 0.6.0 (2024-06-23) -- Solved problem creating python virtual environment -- Improving tests procedure. Coverage is now 85% (#42) - -### 0.5.0 (2024-06-17) -- Added getuser, rm, create_a_file commands -- Improved preprod parameters experience -- poetry_env_info now returns a tuple with the virtual env python executable and pip executable - -### 0.4.0 (2024-06-08) -- Temporal preprod logs are now created by each user to avoid permissions problems -- Added examples and documentation -- Added poe doc command -- Improved parameters errors - -### 0.3.0 (2024-05-28) -- Improved description system -- Added makedirs and git clone in different directory -- Improved spanish translations -- Added --version to commands -- Added test with a 84% coverture - -### 0.2.0 (2024-05-26) -- Added logs in /tmp/preprod_logs/ -- Added chown_recursive, chmod_recursive, rsync, poetry_install, poetry_env_info methods to commons - -### 0.1.0 (2024-05-21) -- Basic functionality +Additionally, you can invoke other preprod actions from within your scripts using: +```python +preprod_commons.preprod("project_name", "action_name", pretend=False, description="Optional description") +``` diff --git a/preprod/commons.py b/preprod/commons.py index 02b2063..a8de3d3 100644 --- a/preprod/commons.py +++ b/preprod/commons.py @@ -540,7 +540,10 @@ def preprod(project, action, pretend=False, description=""): Invokes another preprod action from within the current script. Note: This function will terminate the current process if the invoked preprod action encounters an error and calls sys.exit(). - + + The `SystemExit` exception is caught to allow for logging and displaying + an error message, but it is then re-raised to ensure the calling process + terminates as `preprod.core.main` intends for failed actions. Parameters: - project (str): The name of the project. - action (str): The name of the action within the project. diff --git a/preprod/core.py b/preprod/core.py index f81d3ae..b796d18 100644 --- a/preprod/core.py +++ b/preprod/core.py @@ -16,9 +16,21 @@ _=str def argparse_epilog(): - return _("Developed by Mariano Muñoz 2023-{}").format(__versiondate__.year) + """ + Generates the epilog string for the argument parser, including version information. + """ + return _("Developed by Mariano Muñoz {}-{}").format(__versiondate__.year, __versiondate__.year) def concurrent_log(title, stdout=None, stderr=None): + """ + Logs messages concurrently to a project-specific log file in /tmp/. + Includes timestamps, project/action context, and optional stdout/stderr. + + Parameters: + - title (str): The main log message. + - stdout (str, optional): Standard output to log. + - stderr (str, optional): Standard error to log. + """ def parse_std(std): arr=std.split("\n") r="" @@ -106,6 +118,12 @@ def main(arguments=None): def create(): + """ + Initializes a new preprod repository. + + Creates the necessary directory structure and a sample project/action + along with a `repository_commons.py` file. + """ parser=ArgumentParser(description=_("Preprod manager"), epilog= argparse_epilog()) parser.add_argument('--version', action='version', version=__version__) @@ -135,6 +153,11 @@ def create(): """) def list_repository(): + """ + Lists all available preprod projects and their actions in the repository. + + Prints a formatted output of the discovered projects and actions. + """ commons.check_repository_path(verbose=True) rp=commons.repository_path() print(commons.yellow(_("Reading repository from {0} and listing available preprod scripts").format(rp))) diff --git a/preprod/poethepoet.py b/preprod/poethepoet.py index 81b90c2..a8d9ed6 100644 --- a/preprod/poethepoet.py +++ b/preprod/poethepoet.py @@ -2,6 +2,9 @@ from os import system def release(): + """ + Provides instructions for creating a new release of the preprod project. + """ print("""Nueva versión: * Cambiar la version en pyproject.toml * Cambiar la versión y la fecha en __init__.py @@ -28,10 +31,17 @@ def release(): def coverage(): + """ + Runs pytest with coverage and generates a coverage report and HTML output. + """ system("coverage run --omit='*repository_commons.py' -m pytest && coverage report && coverage html") def translate(): + """ + Manages translation files for the project. + Extracts translatable strings, updates PO files, and compiles MO files. + """ #es system("xgettext -L Python --no-wrap --no-location --from-code='UTF-8' -o preprod/locale/preprod.pot preprod/*.py") system("msgmerge -N --no-wrap -U preprod/locale/es.po preprod/locale/preprod.pot") @@ -39,9 +49,16 @@ def translate(): system("msgfmt -cv -o preprod/locale/en/LC_MESSAGES/preprod.mo preprod/locale/en.po") def pytest(): + """ + Runs the pytest test suite for the project. + """ system("pytest") def doc(): + """ + Generates documentation for preprod.commons module in Markdown format. + Captures the output of `help(preprod.commons)` and saves it to `doc/PREPROD_COMMANDS.md`. + """ import io import sys import preprod.commons From 8390d6e0b38d7811ae579df0619a8cdcdb26b5e7 Mon Sep 17 00:00:00 2001 From: turulomio Date: Tue, 24 Feb 2026 19:49:40 +0100 Subject: [PATCH 4/5] Added create_file method --- preprod/commons.py | 10 ++++++++++ preprod/tests/test_commons.py | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/preprod/commons.py b/preprod/commons.py index a8de3d3..1f7fa3c 100644 --- a/preprod/commons.py +++ b/preprod/commons.py @@ -578,3 +578,13 @@ def preprod(project, action, pretend=False, description=""): print_after_error(description is not None) concurrent_log(log_message + _(" - Failed with unexpected error: {0}").format(str(e))) raise # Re-raise any other unexpected exception + +def create_file(filename, content="", description=""): + """ + Creates a file with the given filename and content. + """ + description = _("Creating file '{0}'").format(filename) if description == "" else description + print_before(description, description is not None) + with open(filename, "w") as f: + f.write(content) + print_after_ok(description is not None) diff --git a/preprod/tests/test_commons.py b/preprod/tests/test_commons.py index c45fc46..acaf11a 100644 --- a/preprod/tests/test_commons.py +++ b/preprod/tests/test_commons.py @@ -194,6 +194,17 @@ def test_commons_poetry_install(): preprod_commons.poetry_install() """) +def test_commons_create_file(): + tmp_test_path = create_and_run_action(currentframe().f_code.co_name, """ +preprod_commons.create_file("test_file.txt", "Hello, world!") +preprod_commons.create_file("empty_file.txt", "") + """) + assert path.exists(f"{tmp_test_path}/test_file.txt") + assert commons.file_contains_string(f"{tmp_test_path}/test_file.txt", "Hello, world!") + assert path.exists(f"{tmp_test_path}/empty_file.txt") + with open(f"{tmp_test_path}/empty_file.txt", "r") as f: + assert f.read() == "" + def test_commons_npm_install(): tmp_test_path=create_and_run_action(currentframe().f_code.co_name, """ preprod_commons.git_clone("https://github.com/turulomio/calories_tracker") From 7d4ceb1e8c51a6d4290264cfaf8911a218d9a02f Mon Sep 17 00:00:00 2001 From: turulomio Date: Tue, 24 Feb 2026 19:50:46 +0100 Subject: [PATCH 5/5] Added sleep method --- preprod/commons.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/preprod/commons.py b/preprod/commons.py index 1f7fa3c..c1a52de 100644 --- a/preprod/commons.py +++ b/preprod/commons.py @@ -6,6 +6,7 @@ from shutil import copyfile as shutil_copyfile, rmtree as shutil_rmtree from socket import create_connection from subprocess import run, Popen, PIPE +from time import sleep as time_sleep from sys import exit, stdout """ @@ -575,10 +576,18 @@ def preprod(project, action, pretend=False, description=""): concurrent_log(log_message + _(" - Failed with exit code {0}").format(return_code)) raise # Re-raise the SystemExit to ensure the process terminates as core.main intended. except Exception as e: - print_after_error(description is not None) concurrent_log(log_message + _(" - Failed with unexpected error: {0}").format(str(e))) raise # Re-raise any other unexpected exception +def sleep(seconds, description=""): + """ + Pauses the execution for a given number of seconds. + """ + description = _("Sleeping for {0} seconds").format(seconds) if description == "" else description + print_before(description, description is not None) + time_sleep(seconds) + print_after_ok(description is not None) + def create_file(filename, content="", description=""): """ Creates a file with the given filename and content.