Skip to content

Feature/test branch #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed

Conversation

selectdimensions
Copy link
Owner

@selectdimensions selectdimensions commented Nov 17, 2024

Summary by Sourcery

Add a new script 'venv_creator.py' for managing Python virtual environments and update the CI workflow to include enhanced testing, linting, and security checks. Introduce a new test suite for the 'VenvCreator' class to ensure robust functionality.

New Features:

  • Introduce a new Python script 'venv_creator.py' for creating and managing virtual environments with functionalities like setting dependencies, finding Python installations, and installing requirements.

Enhancements:

  • Enhance the CI workflow to include separate jobs for testing, linting, and security checks, with improved matrix strategy for testing across different OS and Python versions.

CI:

  • Update the GitHub Actions workflow to trigger on both 'main' and 'develop' branches for push and pull request events.
  • Add a new linting job to the CI workflow to ensure code quality using tools like flake8, black, and isort.

Tests:

  • Add a comprehensive test suite for the 'VenvCreator' class, covering various functionalities such as setting dependencies, creating virtual environments, and installing requirements.

Copy link

sourcery-ai bot commented Nov 17, 2024

Reviewer's Guide by Sourcery

This PR implements a comprehensive Python virtual environment creator with improved CI/CD workflow. The changes include a complete restructuring of the project layout, enhanced testing infrastructure, and a robust virtual environment creation utility with cross-platform support.

Class diagram for VenvCreator

classDiagram
    class VenvCreator {
        -os_type: str
        -is_windows: bool
        -is_linux: bool
        -python_installations: List~str~
        -username: str
        -dependencies_folder: Optional~str~
        +set_dependencies_folder(folder_path: str) bool
        +find_dependencies() List~str~
        +install_local_dependencies(venv_path: str) Optional~bool~
        +get_current_username() Optional~str~
        +_get_pip_path(venv_path: str) str
        +find_python_installations() List~str~
        +_find_windows_python() List~str~
        +_find_linux_python() List~str~
        +get_python_version(python_path: str) str
        +create_venv(python_path: str, venv_path: str) bool
        +install_requirements(venv_path: str, requirements_path: Optional~str~) Optional~bool~
        +get_project_details() Dict~str, str~
        +print_activation_instructions(project_path: str, venv_name: str) void
        +run() void
    }
Loading

File-Level Changes

Change Details Files
Enhanced CI/CD workflow with separate jobs for testing, linting, and security checks
  • Added matrix testing strategy for multiple Python versions and OS combinations
  • Implemented separate linting job with flake8, black, and isort checks
  • Added dedicated security scanning job with bandit and safety tools
  • Improved test coverage reporting with Codecov integration
.github/workflows/python-ci.yml
Implemented a robust VenvCreator class for managing Python virtual environments
  • Added cross-platform Python installation detection
  • Implemented local dependency management and installation
  • Added requirements.txt handling with support for local dependencies
  • Implemented user interaction for project setup configuration
  • Added virtual environment activation instructions generation
src/venv_creator.py
Added comprehensive test suite for the VenvCreator class
  • Implemented unit tests for all major VenvCreator functionality
  • Added test fixtures and mocking for system-dependent operations
  • Included temporary directory handling for test isolation
tests/test_venv_creator.py
Added project configuration and dependency management files
  • Created mypy configuration for static type checking
  • Added requirements.txt with development and testing dependencies
  • Added gitignore file for Python project
mypy.ini
requirements.txt
.gitignore

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time. You can also use
    this command to specify where the summary should be inserted.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @selectdimensions - I've reviewed your changes - here's some feedback:

Overall Comments:

  • Several docstring summaries are missing (marked with summary). Consider adding brief descriptions to help with code documentation.
Here's what I looked at during the review
  • 🟡 General issues: 2 issues found
  • 🟡 Security: 1 issue found
  • 🟡 Testing: 1 issue found
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

except (subprocess.SubprocessError, OSError):
return "Version unknown"

def create_venv(self, python_path: str, venv_path: str) -> bool:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 suggestion (security): Add path validation before subprocess calls for security

Consider adding os.path.isfile() and os.access() checks on python_path before passing it to subprocess.run() to prevent potential command injection risks.

    def create_venv(self, python_path: str, venv_path: str) -> bool:
        if not os.path.isfile(python_path) or not os.access(python_path, os.X_OK):
            return False

"""Find Python installations based on OS"""
return self._find_windows_python() if self.is_windows else self._find_linux_python()

def _find_windows_python(self) -> List[str]:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Improve version parsing error handling

Add explicit error handling for cases where Python version string doesn't match expected format, rather than silently continuing.

    def _find_windows_python(self) -> List[str]:
        """Find Python installations on Windows and validate version strings.

        Raises:
            ValueError: If a Python installation has an invalid version string format
        """

if os.path.exists(temp_requirements):
os.remove(temp_requirements)

def get_project_details(self) -> Dict[str, str]:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Split get_project_details into smaller focused methods

Consider breaking this method into smaller methods like get_project_name(), get_project_path(), get_venv_name() etc. for better maintainability.

    def get_project_name(self) -> str:
        """Get project name from user"""
        return self._get_input("project name")

    def get_project_path(self) -> str:
        """Get project path from user"""
        return self._get_input("project path")

    def get_venv_name(self) -> str:
        """Get virtual environment name from user"""
        return self._get_input("virtual environment name")

Comment on lines +140 to +145
def test_find_python_installations(self):
"""Test finding Python installations"""
installations = self.venv_creator.find_python_installations()
self.assertIsInstance(installations, list)
if installations:
self.assertTrue(all(os.path.exists(path) for path in installations))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): Python installations test needs mocking

Instead of relying on actual system Python installations, mock the filesystem and test specific scenarios: 1) No Python installations, 2) Multiple versions, 3) Invalid Python executables, 4) Different installation paths. This will make the test more reliable and comprehensive.

print(f"Error details: {e.stderr}")
return False

def install_requirements(self, venv_path: str, requirements_path: Optional[str] = None) -> Optional[bool]:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider extracting requirements preprocessing logic into a separate method to reduce complexity.

The install_requirements() method has excessive nesting that can be simplified by extracting the requirements preprocessing logic. Here's how to reduce complexity while maintaining functionality:

def _preprocess_requirements(self, requirements_path: str, project_root: str) -> Tuple[str, bool]:
    """Preprocess requirements file and return temp file path and success status"""
    temp_requirements = os.path.join(project_root, 'temp_requirements.txt')
    missing_files = []

    with open(requirements_path, mode='r', encoding='utf-8') as original:
        with open(temp_requirements, mode='w', encoding='utf-8') as temp:
            for line in original:
                line = line.strip()
                if not line.startswith('dependencies/'):
                    temp.write(f"{line}\n")
                    continue

                absolute_path = os.path.abspath(os.path.join(project_root, line))
                if not os.path.exists(absolute_path) or os.path.getsize(absolute_path) == 0:
                    missing_files.append(line)
                    print(f"Warning: Invalid dependency file: {line}")
                else:
                    temp.write(f"{absolute_path}\n")

    if missing_files:
        print("\nMissing or invalid dependency files:")
        for file in missing_files:
            print(f"- {file}")
        return temp_requirements, False
    return temp_requirements, True

def install_requirements(self, venv_path: str, requirements_path: Optional[str] = None) -> Optional[bool]:
    """Install requirements if requirements.txt exists"""
    if not requirements_path or not os.path.exists(requirements_path):
        return None

    project_root = os.path.dirname(requirements_path)
    temp_requirements, preprocessing_success = self._preprocess_requirements(requirements_path, project_root)

    try:
        if not preprocessing_success:
            return False

        pip_path = self._get_pip_path(venv_path)
        print("\nInstalling requirements...")
        result = subprocess.run(
            [pip_path, 'install', '-r', temp_requirements],
            check=True, capture_output=True, text=True
        )
        if result.stderr:
            print(f"Warnings during installation: {result.stderr}")
        return True

    except subprocess.CalledProcessError as e:
        print(f"Error installing requirements: {e}")
        if e.stderr:
            print(f"Error details: {e.stderr}")
        return False
    except Exception as e:
        print(f"Unexpected error during requirements installation: {e}")
        return False
    finally:
        if os.path.exists(temp_requirements):
            os.remove(temp_requirements)

This refactoring:

  1. Separates requirements preprocessing into a dedicated method
  2. Reduces nesting depth by using early returns and continue statements
  3. Maintains all error handling and functionality
  4. Improves readability by grouping related operations

Comment on lines +144 to +145
if installations:
self.assertTrue(all(os.path.exists(path) for path in installations))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

temp_requirements = os.path.join(project_root, 'temp_requirements.txt')

try:
missing_files = []
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Extract code out into method (extract-method)


def get_project_details(self) -> Dict[str, str]:
"""Get project details from user"""
project_details: Dict[str, str] = {}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): We've found these issues:

Comment on lines +331 to +338
result = self.install_local_dependencies(venv_path)
if result:
print("Local dependencies installed successfully!")
elif result is False:
print("Failed to install local dependencies.")

if 'requirements_path' in project_details:
result = self.install_requirements(venv_path, project_details['requirements_path'])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Use named expression to simplify assignment and conditional [×2] (use-named-expression)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant