Skip to content

Commit

Permalink
feat: add picasso commands
Browse files Browse the repository at this point in the history
  • Loading branch information
MaferMazu committed Jul 31, 2024
1 parent 79e39a1 commit 35a63da
Show file tree
Hide file tree
Showing 65 changed files with 1,786 additions and 7 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def load_about():
packages=find_packages(exclude=["tests*"]),
include_package_data=True,
python_requires=">=3.8",
install_requires=["tutor>=18.0.0,<19.0.0"],
install_requires=["tutor>=18.0.0,<19.0.0", "schema"],
extras_require={
"dev": [
"tutor[dev]>=18.0.0,<19.0.0",
Expand Down
27 changes: 27 additions & 0 deletions tutorpicasso/commands/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Picasso commands group.
"""

import click

from tutorpicasso.commands.enable_private_packages import enable_private_packages
from tutorpicasso.commands.enable_themes import enable_themes
from tutorpicasso.commands.repository_validator import repository_validator
from tutorpicasso.commands.run_extra_commands import run_extra_commands
from tutorpicasso.commands.syntax_validator import syntax_validator


@click.group(help="Run picasso commands")
def picasso() -> None:
"""
Main picasso command group.
This command group provides functionality to run picasso commands.
"""


picasso.add_command(enable_themes)
picasso.add_command(enable_private_packages)
picasso.add_command(repository_validator)
picasso.add_command(syntax_validator)
picasso.add_command(run_extra_commands)
102 changes: 102 additions & 0 deletions tutorpicasso/commands/enable_private_packages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""
Picasso enable private packages command.
"""

import subprocess

import click
from tutor import config as tutor_config

from tutorpicasso.picasso.packages.application.package_cloner import PackageCloner
from tutorpicasso.picasso.packages.infrastructure.package_git_repository import PackageGitRepository
from tutorpicasso.utils.packages import get_private_picasso_packages


@click.command(name="enable-private-packages", help="Enable picasso private packages")
def enable_private_packages():
"""
Enable private packages command.
This command enables picasso private packages by cloning the packages and
defining them as private.
Raises:
Exception: If an error occurs during the cloning or defining process.
"""
directory = subprocess.check_output("tutor config printroot", shell=True).\
decode("utf-8").strip()
config = tutor_config.load(directory)

repository = PackageGitRepository()
cloner = PackageCloner(repository=repository)

private_packages = get_private_picasso_packages(config)
requirements_directory = f"{directory}/env/build/openedx/requirements/"
for package in private_packages.values():
try:
cloner(
name=package["name"],
version=package["version"],
domain=package["domain"],
extra={ # pylint:disable=duplicate-code
"repo": package["repo"],
"protocol": package["protocol"],
"path": package["path"]
},
path=requirements_directory
)

# Run tutor mounts add command for the package
subprocess.check_output(f"tutor mounts add {requirements_directory}{package['name']}", shell=True)
hook = f'hooks.Filters.MOUNTED_DIRECTORIES.add_item(("openedx", "{package["name"]}"))'

hook_writer(hook_to_append=hook)
subprocess.check_output("tutor config save", shell=True)
except Exception as error: # pylint: disable=broad-exception-caught
click.echo(error)


def get_picasso_location():
"""
Function to get the right picasso path
"""

try:
result = subprocess.run(['pip', 'show', 'tutor-contrib-picasso'],
capture_output=True, text=True, check=True)

# Check if the command was successful
if result.returncode == 0:
# Split the output into lines
lines = result.stdout.splitlines()

# Loop through each line to find the Location
for line in lines:
if line.startswith('Location:'):
# Extract the location path
location_path = line.split(':', 1)[1].strip() + "/tutorpicasso/plugin.py"
return location_path
except subprocess.CalledProcessError as e:
# Print error message if command failed
print("Error running pip show picasso:", e.stderr)

# Return a default value if the location is not found or an error occurs
return None


def hook_writer(hook_to_append):
"""
Function to write the corresponding hooks depending on the private packages.
"""
file_path = get_picasso_location()
with open(file_path, 'a+', encoding='utf-8') as my_file: # Open file in append and read mode

my_file.seek(0) # Move the cursor to the beginning of the file
existing_lines = my_file.readlines()
package_name = hook_to_append.split('"')[3] # Extract package name from hook_to_append

# Check if package name already exists in the file
if any(package_name in line for line in existing_lines):
print(f"Package '{package_name}' already present in the file.")
else:
my_file.write(hook_to_append + "\n")
31 changes: 31 additions & 0 deletions tutorpicasso/commands/enable_themes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Picasso enable theme command.
"""

import subprocess

import click
from tutor import config as tutor_config

from tutorpicasso.picasso.themes.application.theme_enabler import ThemeEnabler
from tutorpicasso.picasso.themes.infraestructure.theme_git_repository import ThemeGitRepository


@click.command(name="enable-themes", help="Enable picasso themes")
def enable_themes() -> None:
"""
Enable picasso themes.
This function enables the themes specified in the `PICASSO_THEMES` configuration
and applies them using the ThemeEnabler and ThemeGitRepository classes.
"""
directory = subprocess.check_output("tutor config printroot", shell=True).\
decode("utf-8").strip()
config = tutor_config.load(directory)

repository = ThemeGitRepository()
enabler = ThemeEnabler(repository=repository)

if config.get("PICASSO_THEMES"):
for theme in config["PICASSO_THEMES"]:
enabler(settings=theme, tutor_root=directory, tutor_config=config)
76 changes: 76 additions & 0 deletions tutorpicasso/commands/repository_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
This module provides a repository validator command-line tool.
It validates the repositories used in the project, checking for specific conditions and
requirements.
"""


import subprocess

import click
from tutor import config as tutor_config

from tutorpicasso.picasso.repository_validator.application.dpkg_url_validator import DPKGUrlValidator
from tutorpicasso.picasso.repository_validator.application.extra_pip_requirements_url_validator import (
ExtraPipRequirementsUrlValidator,
)
from tutorpicasso.picasso.repository_validator.infrastructure.git_package_repository import GitPackageRepository
from tutorpicasso.utils.packages import get_public_picasso_packages


@click.command(name="repository-validator", help="Repository validator")
def repository_validator() -> None:
"""
Validate the repositories used in the project.
This function checks for specific conditions and requirements in the repositories
defined in the project's configuration. It validates GitHub repositories that end with
'DPKG' and the repositories specified in the 'OPENEDX_EXTRA_PIP_REQUIREMENTS' configuration.
It uses the DPKGUrlValidator to validate GitHub repositories and the
ExtraPipRequirementsUrlValidator to validate 'OPENEDX_EXTRA_PIP_REQUIREMENTS' repositories.
The validation results are printed using click.echo.
Raises:
Exception: If an error occurs during validation.
Returns:
None
"""
directory = subprocess.check_output("tutor config printroot", shell=True).\
decode("utf-8").strip()
config = tutor_config.load(directory)

public_packages = get_public_picasso_packages(config)

repository = GitPackageRepository()
dpkg_controller = DPKGUrlValidator(repository=repository)

# Check github repos that end with 'DPKG'

for package in public_packages.values():
try:
dpkg_controller(
name=package["name"],
version=package["version"],
domain=package["domain"],
extra={
"repo": package["repo"],
"protocol": package["protocol"],
"path": package["path"]
}
)
except Exception as error: # pylint: disable=broad-except
click.echo(error)

# Check the openedx_extra_pip_requirements repos
openedx_extra_pip_requirements = config.get('OPENEDX_EXTRA_PIP_REQUIREMENTS', [])

epr_controller = ExtraPipRequirementsUrlValidator(repository=repository)

for git_url in openedx_extra_pip_requirements:
try:
epr_controller(url=git_url)
except Exception as error: # pylint: disable=broad-except
click.echo(error)
32 changes: 32 additions & 0 deletions tutorpicasso/commands/run_extra_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Picasso run extra commands command.
"""

import subprocess

import click
from tutor import config as tutor_config

from tutorpicasso.picasso.extra_commands.application.commands_runner import CommandsRunner
from tutorpicasso.picasso.extra_commands.infrastructure.tutor_commands import TutorCommandManager


@click.command(name="run-extra-commands", help="Run tutor commands")
def run_extra_commands():
"""
This command runs tutor commands defined in PICASSO_EXTRA_COMMANDS
"""
directory = (
subprocess.check_output("tutor config printroot", shell=True)
.decode("utf-8")
.strip()
)
config = tutor_config.load(directory)
picasso_extra_commands = config.get("PICASSO_EXTRA_COMMANDS", None)

tutor_commands_manager = TutorCommandManager()
run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager, commands=picasso_extra_commands)

if picasso_extra_commands:
for command in picasso_extra_commands:
run_tutor_command(command=command)
43 changes: 43 additions & 0 deletions tutorpicasso/commands/syntax_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
Picasso config syntax validator command.
"""

import subprocess

import click

from tutorpicasso.picasso.share.domain.config_extra_files_requirements_setting import ConfigExtraFilesRequirementsSetting
from tutorpicasso.picasso.share.domain.config_extra_pip_requirements_setting import ConfigExtraPipRequirementsSetting
from tutorpicasso.picasso.share.domain.config_extra_setting import ConfigExtraSetting
from tutorpicasso.picasso.share.domain.config_packages_setting import ConfigPackagesSetting
from tutorpicasso.picasso.share.domain.config_themes_setting import ConfigThemesSetting
from tutorpicasso.picasso.syntax_validator.application.config_syntax_validator import ConfigSyntaxValidator
from tutorpicasso.picasso.syntax_validator.infrastructure.config_repository import ConfigRepository


@click.command(name="syntax-validator", help="Syntax validator")
def syntax_validator() -> None:
"""
Command to perform syntax validation on the configuration.
This command loads the Tutor configuration, validates it using the
ConfigSyntaxValidator, and displays the validation result.
"""
file_path = subprocess.check_output("tutor config printroot", shell=True).decode("utf-8").strip()

config_settings = [
ConfigExtraFilesRequirementsSetting,
ConfigExtraPipRequirementsSetting,
ConfigExtraSetting,
ConfigPackagesSetting,
ConfigThemesSetting,
]
repository = ConfigRepository(config_settings)

syntax_validator = ConfigSyntaxValidator(repository)
is_valid = syntax_validator.execute(file_path)

if is_valid:
click.echo("Success validation")
else:
click.echo("Failed validation. Check settings")
Empty file.
Empty file.
38 changes: 38 additions & 0 deletions tutorpicasso/picasso/extra_commands/application/commands_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
Picasso command runner.
"""
# Was necessary to use this for compatibility with Python 3.8
from typing import List, Optional

from tutorpicasso.picasso.extra_commands.domain.command_manager import CommandManager


class CommandsRunner:
"""
Command runner.
This class is responsible of executing extra commands by invoking the run_command method
on a commands manager.
Attributes:
commands_manager (ThemeRepository): The command manager to use for executing the extra command.
"""

def __init__(self, commands_manager: CommandManager, commands: Optional[List[str]]):
self.commands_manager = commands_manager

if commands is not None:
commands_manager.validate_commands(commands)

def __call__(self, command: str):
"""
Run the provided command.
This method runs the provided command by invoking the run_command method
from the given command manager
Args:
command (str): Command to execute.
"""

return self.commands_manager.run_command(command=command)
Empty file.
12 changes: 12 additions & 0 deletions tutorpicasso/picasso/extra_commands/domain/command_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Command Manager"""

import abc
from abc import abstractmethod


class CommandManager(metaclass=abc.ABCMeta):
"""Command Manager"""

@abstractmethod
def run_command(self, command: str):
"""Run a command."""
Empty file.
Loading

0 comments on commit 35a63da

Please sign in to comment.