Module generate_setup

Generates setup.py, requirements, and environment.yml for SFPPy.

Features: - Ensures SFPPy/ (parent of patankar/) is in the Python path and saves it. - Adds the minimal version of Pillow required for PNG loading and cropping. - Generates: - setup.py - requirements.txt - environment.yml

Project Structure Assumed: SFPPy/ │ ├── utils/ │ ├── generate_requirements.py │ ├── generate_manifest_in.py │ └── generate_setup.py <- This script │ ├── patankar/ │ ├── init.py │ └── (Other modules) │ ├── example1.py ├── example2.py ├── tmp/ ├── setup.py (Generated by this script) ├── requirements.txt ├── environment.yml └── .sfppy_path (Stores detected SFPPy root path)


Author: INRAE\Olivier Vitrac Email: olivier.vitrac@agroparistech.fr Last Revised: 2025-03-07

Expand source code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Generates setup.py, requirements, and environment.yml for SFPPy.

**Features:**
- Ensures `SFPPy/` (parent of `patankar/`) is in the Python path and saves it.
- Adds the **minimal version** of `Pillow` required for PNG loading and cropping.
- Generates:
  - `setup.py`
  - `requirements.txt`
  - `environment.yml`

Project Structure Assumed:
SFPPy/
│
├── utils/
│   ├── generate_requirements.py
│   ├── generate_manifest_in.py
│   └── generate_setup.py   <- This script
│
├── patankar/
│   ├── __init__.py
│   └── (Other modules)
│
├── example1.py
├── example2.py
├── tmp/
├── setup.py  (Generated by this script)
├── requirements.txt
├── environment.yml
└── .sfppy_path (Stores detected SFPPy root path)

-------------------------------------------------------------------------------
Author: INRAE\\Olivier Vitrac
Email: olivier.vitrac@agroparistech.fr
Last Revised: 2025-03-07
"""

import os
import sys
import re
from pathlib import Path

# Define dependencies
dependencies = [
    "numpy>=1.21.0",
    "matplotlib>=3.4.0",
    "scipy>=1.7.0",
    "pandas>=1.3.0",
    "openpyxl>=3.0.10",
    "pillow>=8.0.0"  # Minimal version for PNG image loading and cropping
]

conda_channels = ["conda-forge", "defaults"]

def find_sfppy_root():
    """Find and return the SFPPy root directory (parent of patankar/)."""
    current_dir = Path.cwd()

    while current_dir != current_dir.parent:
        if (current_dir / "patankar").is_dir():
            return current_dir
        current_dir = current_dir.parent

    sys.stderr.write("Error: Could not locate SFPPy root containing 'patankar/'.\n")
    sys.exit(1)

def ensure_sfppy_in_pythonpath(sfppy_root):
    """Ensure SFPPy root is in sys.path and save the path in .sfppy_path."""
    sfppy_path_file = sfppy_root / ".sfppy_path"

    if str(sfppy_root) not in sys.path:
        sys.path.insert(0, str(sfppy_root))

    # Save the path for future reference
    with open(sfppy_path_file, "w") as f:
        f.write(str(sfppy_root) + "\n")

    print(f"✔ SFPPy root '{sfppy_root}' added to Python path and saved in '.sfppy_path'.")

def get_version(sfppy_root):
    """Extract the version number of SFPPy from VERSION.txt."""
    version_file = sfppy_root / "utils" / "VERSION.txt"

    if not version_file.exists():
        sys.stderr.write(f"Error: {version_file} not found. Please create VERSION.txt with content: version=\"X.Y.Z\"\n")
        sys.exit(1)

    with open(version_file, "r") as f:
        for line in f:
            match = re.match(r'^version\s*=\s*"(.*?)"$', line.strip())
            if match:
                return match.group(1)

    sys.stderr.write(f"Error: No valid version string found in {version_file}.\n")
    sys.exit(1)

def prompt_overwrite(file_path):
    """Prompt the user to overwrite an existing file."""
    while True:
        choice = input(f"'{file_path}' already exists. Overwrite? [Y/n]: ").strip().lower()
        if choice in ['', 'y', 'yes']:
            return True
        elif choice in ['n', 'no']:
            return False
        else:
            print("Please enter 'Y' or 'N'.")

def generate_setup_py(sfppy_root, dependencies):
    """Generate setup.py with specified dependencies."""
    setup_path = sfppy_root / "setup.py"

    if setup_path.exists() and not prompt_overwrite(setup_path):
        print("Skipping setup.py generation.")
        return

    setup_content = f"""from setuptools import setup, find_packages
import sys
import os

# Ensure SFPPy root is in sys.path
sfppy_root = os.path.dirname(os.path.abspath(__file__))
if sfppy_root not in sys.path:
    sys.path.insert(0, sfppy_root)

setup(
    name="SFPPy",
    version="{get_version(sfppy_root)}",
    description="Software Simulating Mass Transfer from Food Packaging",
    author="Olivier Vitrac",
    author_email="olivier.vitrac@agroparistech.fr",
    url="https://github.com/ovitrac/SFPPy",
    packages=find_packages(include=['patankar', 'patankar.*']),
    install_requires=[
        {', '.join([f'"{dep}"' for dep in dependencies])}
    ],
    classifiers=[
        "Programming Language :: Python :: 3.10",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    python_requires='>=3.10',
    include_package_data=True,
    zip_safe=True,
    entry_points={{  # CLI command for running scripts from anywhere
        "console_scripts": [
            "sfppy=cli:main",
        ],
    }},
)
"""

    with open(setup_path, 'w') as f:
        f.write(setup_content)

    print(f"✔ setup.py created successfully in '{sfppy_root}'.")

def generate_environment_yml(sfppy_root, dependencies, conda_channels):
    """Generate environment.yml for Conda users, with pip-based deps."""
    env_path = sfppy_root / "environment.yml"

    if env_path.exists() and not prompt_overwrite(env_path):
        print("Skipping environment.yml generation.")
        return

    env_content = f"""name: sfppy
channels:
{chr(10).join(f"  - {channel}" for channel in conda_channels)}
dependencies:
  - python=3.10
  - pip
  - pip:
{chr(10).join(f"    - {dep}" for dep in dependencies)}
"""

    with open(env_path, 'w') as f:
        f.write(env_content)

    print(f"✔ environment.yml created successfully in '{sfppy_root}'.")


def generate_requirements_txt(sfppy_root, dependencies):
    """Generate requirements.txt for pip users."""
    req_path = sfppy_root / "requirements.txt"

    if req_path.exists() and not prompt_overwrite(req_path):
        print("Skipping requirements.txt generation.")
        return

    req_content = "\n".join(dependencies)

    with open(req_path, 'w') as f:
        f.write(req_content)

    print(f"✔ requirements.txt created successfully in '{sfppy_root}'.")

def main():
    """Main script execution."""
    current_dir = Path.cwd()

    if current_dir.name != 'utils':
        print("Error: This script must be run from the 'utils/' directory of SFPPy.")
        sys.exit(1)

    sfppy_root = find_sfppy_root()

    # Ensure SFPPy root is added to Python path
    ensure_sfppy_in_pythonpath(sfppy_root)

    generate_setup_py(sfppy_root, dependencies)
    generate_environment_yml(sfppy_root, dependencies, conda_channels)
    generate_requirements_txt(sfppy_root, dependencies)

if __name__ == '__main__':
    main()

Functions

def ensure_sfppy_in_pythonpath(sfppy_root)

Ensure SFPPy root is in sys.path and save the path in .sfppy_path.

Expand source code
def ensure_sfppy_in_pythonpath(sfppy_root):
    """Ensure SFPPy root is in sys.path and save the path in .sfppy_path."""
    sfppy_path_file = sfppy_root / ".sfppy_path"

    if str(sfppy_root) not in sys.path:
        sys.path.insert(0, str(sfppy_root))

    # Save the path for future reference
    with open(sfppy_path_file, "w") as f:
        f.write(str(sfppy_root) + "\n")

    print(f"✔ SFPPy root '{sfppy_root}' added to Python path and saved in '.sfppy_path'.")
def find_sfppy_root()

Find and return the SFPPy root directory (parent of patankar/).

Expand source code
def find_sfppy_root():
    """Find and return the SFPPy root directory (parent of patankar/)."""
    current_dir = Path.cwd()

    while current_dir != current_dir.parent:
        if (current_dir / "patankar").is_dir():
            return current_dir
        current_dir = current_dir.parent

    sys.stderr.write("Error: Could not locate SFPPy root containing 'patankar/'.\n")
    sys.exit(1)
def generate_environment_yml(sfppy_root, dependencies, conda_channels)

Generate environment.yml for Conda users, with pip-based deps.

Expand source code
def generate_environment_yml(sfppy_root, dependencies, conda_channels):
    """Generate environment.yml for Conda users, with pip-based deps."""
    env_path = sfppy_root / "environment.yml"

    if env_path.exists() and not prompt_overwrite(env_path):
        print("Skipping environment.yml generation.")
        return

    env_content = f"""name: sfppy
channels:
{chr(10).join(f"  - {channel}" for channel in conda_channels)}
dependencies:
  - python=3.10
  - pip
  - pip:
{chr(10).join(f"    - {dep}" for dep in dependencies)}
"""

    with open(env_path, 'w') as f:
        f.write(env_content)

    print(f"✔ environment.yml created successfully in '{sfppy_root}'.")
def generate_requirements_txt(sfppy_root, dependencies)

Generate requirements.txt for pip users.

Expand source code
def generate_requirements_txt(sfppy_root, dependencies):
    """Generate requirements.txt for pip users."""
    req_path = sfppy_root / "requirements.txt"

    if req_path.exists() and not prompt_overwrite(req_path):
        print("Skipping requirements.txt generation.")
        return

    req_content = "\n".join(dependencies)

    with open(req_path, 'w') as f:
        f.write(req_content)

    print(f"✔ requirements.txt created successfully in '{sfppy_root}'.")
def generate_setup_py(sfppy_root, dependencies)

Generate setup.py with specified dependencies.

Expand source code
def generate_setup_py(sfppy_root, dependencies):
    """Generate setup.py with specified dependencies."""
    setup_path = sfppy_root / "setup.py"

    if setup_path.exists() and not prompt_overwrite(setup_path):
        print("Skipping setup.py generation.")
        return

    setup_content = f"""from setuptools import setup, find_packages
import sys
import os

# Ensure SFPPy root is in sys.path
sfppy_root = os.path.dirname(os.path.abspath(__file__))
if sfppy_root not in sys.path:
    sys.path.insert(0, sfppy_root)

setup(
    name="SFPPy",
    version="{get_version(sfppy_root)}",
    description="Software Simulating Mass Transfer from Food Packaging",
    author="Olivier Vitrac",
    author_email="olivier.vitrac@agroparistech.fr",
    url="https://github.com/ovitrac/SFPPy",
    packages=find_packages(include=['patankar', 'patankar.*']),
    install_requires=[
        {', '.join([f'"{dep}"' for dep in dependencies])}
    ],
    classifiers=[
        "Programming Language :: Python :: 3.10",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    python_requires='>=3.10',
    include_package_data=True,
    zip_safe=True,
    entry_points={{  # CLI command for running scripts from anywhere
        "console_scripts": [
            "sfppy=cli:main",
        ],
    }},
)
"""

    with open(setup_path, 'w') as f:
        f.write(setup_content)

    print(f"✔ setup.py created successfully in '{sfppy_root}'.")
def get_version(sfppy_root)

Extract the version number of SFPPy from VERSION.txt.

Expand source code
def get_version(sfppy_root):
    """Extract the version number of SFPPy from VERSION.txt."""
    version_file = sfppy_root / "utils" / "VERSION.txt"

    if not version_file.exists():
        sys.stderr.write(f"Error: {version_file} not found. Please create VERSION.txt with content: version=\"X.Y.Z\"\n")
        sys.exit(1)

    with open(version_file, "r") as f:
        for line in f:
            match = re.match(r'^version\s*=\s*"(.*?)"$', line.strip())
            if match:
                return match.group(1)

    sys.stderr.write(f"Error: No valid version string found in {version_file}.\n")
    sys.exit(1)
def main()

Main script execution.

Expand source code
def main():
    """Main script execution."""
    current_dir = Path.cwd()

    if current_dir.name != 'utils':
        print("Error: This script must be run from the 'utils/' directory of SFPPy.")
        sys.exit(1)

    sfppy_root = find_sfppy_root()

    # Ensure SFPPy root is added to Python path
    ensure_sfppy_in_pythonpath(sfppy_root)

    generate_setup_py(sfppy_root, dependencies)
    generate_environment_yml(sfppy_root, dependencies, conda_channels)
    generate_requirements_txt(sfppy_root, dependencies)
def prompt_overwrite(file_path)

Prompt the user to overwrite an existing file.

Expand source code
def prompt_overwrite(file_path):
    """Prompt the user to overwrite an existing file."""
    while True:
        choice = input(f"'{file_path}' already exists. Overwrite? [Y/n]: ").strip().lower()
        if choice in ['', 'y', 'yes']:
            return True
        elif choice in ['n', 'no']:
            return False
        else:
            print("Please enter 'Y' or 'N'.")