Skip to content

Instantly share code, notes, and snippets.

@hiranp
Last active June 19, 2025 01:08
Show Gist options
  • Save hiranp/8bb28e882d6884338ea50fa17d923764 to your computer and use it in GitHub Desktop.
Save hiranp/8bb28e882d6884338ea50fa17d923764 to your computer and use it in GitHub Desktop.
Convert Conda requirments.txt to uv pyproject.toml
#!/usr/bin/env python3
"""
Script to convert conda environment.yml or requirements.txt to uv-compatible pyproject.toml format
This script reads a conda environment file (environment.yml or requirements.txt with conda format)
and converts all dependencies to a format compatible with uv in pyproject.toml.
Features:
- Extracts conda and pip dependencies from environment files
- Preserves version information from the source file
- Converts version constraints to '>=' format for better compatibility with uv
- Filters out internal conda packages (starting with _)
- Adds dependencies to pyproject.toml under [project.dependencies]
REQUIRES:
- pyyaml
- toml
INSTALLATION:
pip install pyyaml toml
EXECUTION:
python convert_requirements.py [requirements.txt | environment.yml]
EXAMPLE:
python convert_requirements.py requirements.txt
python convert_requirements.py environment.yml
"""
import yaml
import toml
import os
import sys
def parse_requirements_txt(filename):
"""
Parse a requirements.txt or environment.yml file to extract dependencies
"""
conda_dependencies = []
pip_dependencies = []
with open(filename, "r") as file:
content = file.read()
if filename.endswith(".yml") or filename.endswith(".yaml"):
# Parse YAML content for environment.yml
try:
env_yaml = yaml.safe_load(content)
if "dependencies" in env_yaml:
for dep in env_yaml["dependencies"]:
if isinstance(dep, dict) and "pip" in dep:
# Handle pip dependencies inside conda env file
for pip_dep in dep["pip"]:
pip_dependencies.append(pip_dep)
elif isinstance(dep, str) and not dep.startswith("python="):
# Keep conda dependencies with their versions, but skip python version
if "=" in dep:
package_parts = dep.split("=")
if not package_parts[0].startswith(
"_"
): # Skip internal packages
conda_dependencies.append(dep)
else:
conda_dependencies.append(dep)
except yaml.YAMLError as e:
print(f"Error parsing YAML: {e}")
return [], []
else:
# Assume requirements.txt format
lines = content.split("\n")
# Check if it contains conda-style entries
if any(
line.startswith("name:") or line.startswith("channels:")
for line in lines
):
# This is a conda env file saved as .txt
in_pip_section = False
in_deps_section = False
for line in lines:
line = line.strip()
if line.startswith("dependencies:"):
in_deps_section = True
in_pip_section = False
continue
if in_deps_section and line.startswith("- pip:"):
in_pip_section = True
in_deps_section = False
continue
if in_deps_section and line.startswith("-"):
dep = line[1:].strip()
if not dep.startswith("python="): # Skip Python version
if "=" in dep:
package_parts = dep.split("=")
if not package_parts[0].startswith(
"_"
): # Skip internal packages
conda_dependencies.append(dep)
else:
if not dep.startswith("_"): # Skip internal packages
conda_dependencies.append(dep)
if in_pip_section and line.startswith("-"):
pip_dep = line[1:].strip()
pip_dependencies.append(pip_dep)
else:
# Regular pip requirements.txt
for line in lines:
line = line.strip()
if line and not line.startswith("#"):
pip_dependencies.append(line)
# Format conda dependencies to PyPI format
formatted_conda_deps = []
for dep in conda_dependencies:
if "=" in dep:
parts = dep.split("=")
name = parts[0]
if len(parts) >= 2:
version = parts[1]
# Convert conda version constraint to PyPI format
formatted_conda_deps.append(f"{name}=={version}")
else:
formatted_conda_deps.append(name)
else:
formatted_conda_deps.append(dep)
# Keep pip dependencies with their exact version constraints
formatted_pip_deps = []
for dep in pip_dependencies:
formatted_pip_deps.append(dep)
return formatted_conda_deps, formatted_pip_deps
def update_pyproject_toml(pyproject_path, conda_deps, pip_deps):
"""
Update or create a pyproject.toml file with the extracted dependencies
"""
pyproject_data = {}
# Load existing pyproject.toml if it exists
if os.path.exists(pyproject_path):
with open(pyproject_path, "r") as file:
try:
pyproject_data = toml.load(file)
except toml.TomlDecodeError as e:
print(f"Error parsing existing pyproject.toml: {e}")
pyproject_data = {}
# Initialize project section if it doesn't exist
if "project" not in pyproject_data:
pyproject_data["project"] = {}
# Update dependencies list
if "dependencies" not in pyproject_data["project"]:
pyproject_data["project"]["dependencies"] = []
# Combine and format dependencies for pyproject.toml
all_deps = []
# Process conda dependencies
for dep in conda_deps:
if dep:
# Format dependency for pyproject.toml using >= instead of == for better compatibility
if "==" in dep:
name, version = dep.split("==", 1)
all_deps.append(f"{name}>={version}")
elif "=" in dep:
# Convert conda version format to PyPI format with >=
name, version = dep.split("=", 1)
all_deps.append(f"{name}>={version}")
else:
all_deps.append(dep)
# Process pip dependencies - convert == to >= for better compatibility
for dep in pip_deps:
if dep:
if "==" in dep:
name, version = dep.split("==", 1)
all_deps.append(f"{name}>={version}")
else:
all_deps.append(dep)
# Update dependencies - We'll handle writing them in a custom way
pyproject_data["project"]["dependencies"] = all_deps
# Add tool.uv section
if "tool" not in pyproject_data:
pyproject_data["tool"] = {}
if "uv" not in pyproject_data["tool"]:
pyproject_data["tool"]["uv"] = {}
# Write updated pyproject.toml with proper multi-line formatting for dependencies
with open(pyproject_path, "w") as file:
# Get all non-dependencies sections first
temp_data = pyproject_data.copy()
dependencies = temp_data["project"].pop("dependencies")
# Write the non-dependencies parts
for section, content in temp_data.items():
if section == "project":
file.write(f"[{section}]\n")
for key, value in content.items():
if isinstance(value, str):
file.write(f'{key} = "{value}"\n')
else:
file.write(f"{key} = {value}\n")
# Write dependencies with proper formatting
file.write("dependencies = [\n")
for dep in sorted(dependencies):
file.write(f' "{dep}",\n')
file.write("]\n\n")
else:
file.write(f"[{section}]\n")
for key, value in content.items():
file.write(f"{key} = {value}\n")
file.write("\n")
print(f"Updated {pyproject_path} with {len(all_deps)} dependencies")
def generate_requirements_txt(deps, output_file):
"""
Generate a requirements.txt file from a list of dependencies
"""
# Sort dependencies alphabetically for better readability
sorted_deps = sorted(deps)
with open(output_file, "w") as f:
for dep in sorted_deps:
f.write(f"{dep}\n")
print(f"Generated {output_file} with {len(deps)} dependencies")
def main():
# Parse command line arguments
import argparse
parser = argparse.ArgumentParser(
description="Convert conda environment to uv-compatible pyproject.toml"
)
parser.add_argument(
"input_file",
nargs="?",
help="Input environment.yml or requirements.txt file (default: auto-detect)",
)
parser.add_argument(
"-o",
"--output",
default="pyproject.toml",
help="Output pyproject.toml file (default: pyproject.toml)",
)
parser.add_argument(
"--requirements",
action="store_true",
help="Also generate a requirements.txt file",
)
args = parser.parse_args()
# Determine input file
requirements_file = args.input_file
if not requirements_file:
# Default to looking for requirements.txt or environment.yml
if os.path.exists("requirements.txt"):
requirements_file = "requirements.txt"
elif os.path.exists("environment.yml"):
requirements_file = "environment.yml"
else:
print("Error: Could not find requirements.txt or environment.yml")
return 1
pyproject_path = args.output
print(f"Converting {requirements_file} to {pyproject_path}")
conda_deps, pip_deps = parse_requirements_txt(requirements_file)
# Print found dependencies for debugging
print("Conda dependencies:")
for dep in conda_deps:
print(f" - {dep}")
print("\nPip dependencies:")
for dep in pip_deps:
print(f" - {dep}")
# Update pyproject.toml
update_pyproject_toml(pyproject_path, conda_deps, pip_deps)
# Generate requirements.txt if requested
if args.requirements:
# Combine all dependencies
all_deps = []
for dep in conda_deps + pip_deps:
if "==" in dep:
# Convert to >= for requirements-uv.txt to match pyproject.toml
name, version = dep.split("==", 1)
all_deps.append(f"{name}>={version}")
elif ">=" in dep:
# Keep the >= format
all_deps.append(dep)
else:
all_deps.append(dep)
# Generate requirements.txt
generate_requirements_txt(all_deps, "requirements-uv.txt")
print(
f"\nFound {len(conda_deps)} conda dependencies and {len(pip_deps)} pip dependencies"
)
print(f"Updated {pyproject_path} successfully!")
return 0
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment