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'.")