Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"python-envs.defaultEnvManager": "ms-python.python:poetry",
"python-envs.defaultPackageManager": "ms-python.python:poetry"
}
50 changes: 4 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")
```
63 changes: 63 additions & 0 deletions preprod/commons.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

"""
Expand Down Expand Up @@ -534,3 +535,65 @@ 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().

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.
- 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:
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.
"""
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)
25 changes: 24 additions & 1 deletion preprod/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=""
Expand Down Expand Up @@ -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__)
Expand Down Expand Up @@ -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)))
Expand Down
17 changes: 17 additions & 0 deletions preprod/poethepoet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,20 +31,34 @@ 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")
system("msgfmt -cv -o preprod/locale/es/LC_MESSAGES/preprod.mo preprod/locale/es.po")
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
Expand Down
33 changes: 32 additions & 1 deletion preprod/tests/test_commons.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand Down Expand Up @@ -193,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")
Expand All @@ -202,6 +214,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([])
Expand Down