Skip to content
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

feat: preserve existing data in [project] #84

Merged
merged 2 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## Unreleased

Existing data in `[project]` section of `pyproject.toml` is now preserved by default when migrating. If you prefer that the section is fully replaced, this can be done by setting `--replace-project-section` flag, like so:

```bash
migrate-to-uv --replace-project-section
```

### Features

* Preserve existing data in `[project]` section of `pyproject.toml` when migrating ([#84](https://github.com/mkniewallner/migrate-to-uv/pull/84))

## 0.5.0 - 2025-01-18

### Features
Expand Down
11 changes: 11 additions & 0 deletions docs/usage-and-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,17 @@ constraints.
migrate-to-uv --ignore-locked-versions
```

#### `--replace-project-section`

By default, existing data in `[project]` section of `pyproject.toml` is preserved when migrating. This flag allows
completely replacing existing content.

**Example**:

```bash
migrate-to-uv --replace-project-section
```

#### `--package-manager`

By default, `migrate-to-uv` tries to auto-detect the package manager based on the files (and their content) used by the
Expand Down
6 changes: 6 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ struct Cli {
help = "Ignore current locked versions of dependencies when generating `uv.lock`"
)]
ignore_locked_versions: bool,
#[arg(
long,
help = "Replace existing data in `[project]` section of `pyproject.toml` instead of keeping existing fields"
)]
replace_project_section: bool,
#[arg(
long,
help = "Enforce a specific package manager instead of auto-detecting it"
Expand Down Expand Up @@ -72,6 +77,7 @@ pub fn cli() {
dry_run: cli.dry_run,
skip_lock: cli.skip_lock,
ignore_locked_versions: cli.ignore_locked_versions,
replace_project_section: cli.replace_project_section,
keep_old_metadata: cli.keep_current_data,
dependency_groups_strategy: cli.dependency_groups_strategy,
};
Expand Down
42 changes: 42 additions & 0 deletions src/converters/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::converters::pyproject_updater::PyprojectUpdater;
use crate::schema::pep_621::Project;
use crate::schema::pyproject::DependencyGroupSpecification;
use indexmap::IndexMap;
use log::{error, info, warn};
Expand Down Expand Up @@ -30,6 +31,7 @@ pub struct ConverterOptions {
pub dry_run: bool,
pub skip_lock: bool,
pub ignore_locked_versions: bool,
pub replace_project_section: bool,
pub keep_old_metadata: bool,
pub dependency_groups_strategy: DependencyGroupsStrategy,
}
Expand Down Expand Up @@ -74,6 +76,40 @@ pub trait Converter: Debug {
/// Build `pyproject.toml` for uv package manager based on current package manager data.
fn build_uv_pyproject(&self) -> String;

/// Build PEP 621 `[project]` section, keeping existing fields if the section is already
/// defined, unless user has chosen to replace existing section.
fn build_project(&self, current_project: Option<Project>, project: Project) -> Project {
if self.replace_project_section() {
return project;
}

let Some(current_project) = current_project else {
return project;
};

Project {
name: current_project.name.or(project.name),
version: current_project.version.or(project.version),
description: current_project.description.or(project.description),
authors: current_project.authors.or(project.authors),
requires_python: current_project.requires_python.or(project.requires_python),
readme: current_project.readme.or(project.readme),
license: current_project.license.or(project.license),
maintainers: current_project.maintainers.or(project.maintainers),
keywords: current_project.keywords.or(project.keywords),
classifiers: current_project.classifiers.or(project.classifiers),
dependencies: current_project.dependencies.or(project.dependencies),
optional_dependencies: current_project
.optional_dependencies
.or(project.optional_dependencies),
urls: current_project.urls.or(project.urls),
scripts: current_project.scripts.or(project.scripts),
gui_scripts: current_project.gui_scripts.or(project.gui_scripts),
entry_points: current_project.entry_points.or(project.entry_points),
remaining_fields: current_project.remaining_fields,
}
}

/// Name of the current package manager.
fn get_package_manager_name(&self) -> String;

Expand All @@ -97,6 +133,12 @@ pub trait Converter: Debug {
self.get_converter_options().skip_lock
}

/// Whether to replace existing `[project]` section of `pyproject.toml`, or to keep existing
/// fields.
fn replace_project_section(&self) -> bool {
self.get_converter_options().replace_project_section
}

/// Whether to keep current package manager data at the end of the migration.
fn keep_old_metadata(&self) -> bool {
self.get_converter_options().keep_old_metadata
Expand Down
11 changes: 7 additions & 4 deletions src/converters/pip/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
mod dependencies;

use crate::converters::pip::dependencies::get;
use crate::converters::pyproject_updater::PyprojectUpdater;
use crate::converters::Converter;
use crate::converters::ConverterOptions;
use crate::schema::pep_621::Project;
use crate::schema::pyproject::DependencyGroupSpecification;
use crate::schema::pyproject::{DependencyGroupSpecification, PyProject};
use crate::schema::uv::Uv;
use crate::toml::PyprojectPrettyFormatter;
use indexmap::IndexMap;
Expand All @@ -26,6 +25,10 @@ pub struct Pip {

impl Converter for Pip {
fn build_uv_pyproject(&self) -> String {
let pyproject_toml_content =
fs::read_to_string(self.get_project_path().join("pyproject.toml")).unwrap_or_default();
let pyproject: PyProject = toml::from_str(pyproject_toml_content.as_str()).unwrap();

let dev_dependencies = dependencies::get(
&self.get_project_path(),
self.dev_requirements_files.clone(),
Expand Down Expand Up @@ -73,7 +76,7 @@ impl Converter for Pip {
pyproject: &mut updated_pyproject,
};

pyproject_updater.insert_pep_621(&project);
pyproject_updater.insert_pep_621(&self.build_project(pyproject.project, project));
pyproject_updater.insert_dependency_groups(dependency_groups.as_ref());
pyproject_updater.insert_uv(&uv);

Expand Down Expand Up @@ -126,7 +129,7 @@ impl Converter for Pip {
return None;
}

if let Some(dependencies) = get(
if let Some(dependencies) = dependencies::get(
self.get_project_path().as_path(),
self.requirements_files
.clone()
Expand Down
9 changes: 8 additions & 1 deletion src/converters/pipenv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::converters::Converter;
use crate::converters::ConverterOptions;
use crate::schema::pep_621::Project;
use crate::schema::pipenv::{PipenvLock, Pipfile};
use crate::schema::pyproject::PyProject;
use crate::schema::uv::{SourceContainer, Uv};
use crate::toml::PyprojectPrettyFormatter;
use indexmap::IndexMap;
Expand All @@ -26,6 +27,10 @@ pub struct Pipenv {

impl Converter for Pipenv {
fn build_uv_pyproject(&self) -> String {
let pyproject_toml_content =
fs::read_to_string(self.get_project_path().join("pyproject.toml")).unwrap_or_default();
let pyproject: PyProject = toml::from_str(pyproject_toml_content.as_str()).unwrap();

let pipfile_content = fs::read_to_string(self.get_project_path().join("Pipfile")).unwrap();
let pipfile: Pipfile = toml::from_str(pipfile_content.as_str()).unwrap();

Expand Down Expand Up @@ -66,7 +71,7 @@ impl Converter for Pipenv {
pyproject: &mut updated_pyproject,
};

pyproject_updater.insert_pep_621(&project);
pyproject_updater.insert_pep_621(&self.build_project(pyproject.project, project));
pyproject_updater.insert_dependency_groups(dependency_groups.as_ref());
pyproject_updater.insert_uv(&uv);

Expand Down Expand Up @@ -156,6 +161,7 @@ mod tests {
dry_run: true,
skip_lock: true,
ignore_locked_versions: true,
replace_project_section: false,
keep_old_metadata: false,
dependency_groups_strategy: DependencyGroupsStrategy::SetDefaultGroups,
},
Expand Down Expand Up @@ -188,6 +194,7 @@ mod tests {
dry_run: true,
skip_lock: true,
ignore_locked_versions: true,
replace_project_section: false,
keep_old_metadata: false,
dependency_groups_strategy: DependencyGroupsStrategy::SetDefaultGroups,
},
Expand Down
4 changes: 3 additions & 1 deletion src/converters/poetry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ impl Converter for Poetry {
scripts: project::get_scripts(poetry.scripts, scripts_from_plugins),
gui_scripts,
entry_points: poetry_plugins,
..Default::default()
};

let uv = Uv {
Expand Down Expand Up @@ -111,7 +112,7 @@ impl Converter for Poetry {
pyproject_updater.insert_build_system(
build_backend::get_new_build_system(pyproject.build_system).as_ref(),
);
pyproject_updater.insert_pep_621(&project);
pyproject_updater.insert_pep_621(&self.build_project(pyproject.project, project));
pyproject_updater.insert_dependency_groups(dependency_groups.as_ref());
pyproject_updater.insert_uv(&uv);
pyproject_updater.insert_hatch(hatch.as_ref());
Expand Down Expand Up @@ -215,6 +216,7 @@ mod tests {
dry_run: true,
skip_lock: true,
ignore_locked_versions: true,
replace_project_section: false,
keep_old_metadata: false,
dependency_groups_strategy: DependencyGroupsStrategy::SetDefaultGroups,
},
Expand Down
1 change: 1 addition & 0 deletions src/detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ mod tests {
dry_run: true,
skip_lock: true,
ignore_locked_versions: false,
replace_project_section: false,
keep_old_metadata: false,
dependency_groups_strategy: DependencyGroupsStrategy::SetDefaultGroups,
}
Expand Down
4 changes: 4 additions & 0 deletions src/schema/pep_621.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use crate::schema::poetry::Poetry;
use crate::schema::uv::Uv;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;

/// <https://packaging.python.org/en/latest/specifications/pyproject-toml/#pyproject-toml-spec>
#[derive(Default, Deserialize, Serialize)]
Expand All @@ -26,6 +28,8 @@ pub struct Project {
pub gui_scripts: Option<IndexMap<String, String>>,
#[serde(rename = "entry-points")]
pub entry_points: Option<IndexMap<String, IndexMap<String, String>>>,
#[serde(flatten)]
pub remaining_fields: HashMap<String, Value>,
}

#[derive(Deserialize, Serialize)]
Expand Down
4 changes: 4 additions & 0 deletions tests/fixtures/pip/existing_project/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[project]
name = "foobar"
version = "1.0.0"
requires-python = ">=3.13"
19 changes: 19 additions & 0 deletions tests/fixtures/pip/existing_project/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# A comment
anyio==4.7.0
arrow==1.3.0
certifi==2024.12.14
click==8.1.8
h11==0.14.0
httpcore==1.0.7
httpx==0.28.1
idna==3.10
markdown-it-py==3.0.0
mdurl==0.1.2
pygments==2.18.0
python-dateutil==2.9.0.post0
rich==13.9.4
six==1.17.0
sniffio==1.3.1
types-python-dateutil==2.9.0.20241206
uvicorn @ git+https://github.com/encode/uvicorn
zstandard==0.23.0
4 changes: 4 additions & 0 deletions tests/fixtures/pip_tools/existing_project/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[project]
name = "foobar"
version = "1.0.0"
requires-python = ">=3.13"
1 change: 1 addition & 0 deletions tests/fixtures/pip_tools/existing_project/requirements.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
arrow>=1.2.3
12 changes: 12 additions & 0 deletions tests/fixtures/pip_tools/existing_project/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#
# This file is autogenerated by pip-compile with Python 3.13
# by the following command:
#
# pip-compile requirements.in
#
arrow==1.2.3
# via -r requirements.in
python-dateutil==2.7.0
# via arrow
six==1.15.0
# via python-dateutil
16 changes: 16 additions & 0 deletions tests/fixtures/pipenv/existing_project/Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
arrow = ">=1.2.3"

[dev-packages]
mypy = ">=1.13.0"

[test]
factory-boy = ">=3.2.1"

[requires]
python_version = "3.13"
4 changes: 4 additions & 0 deletions tests/fixtures/pipenv/existing_project/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[project]
name = "foobar"
version = "1.0.0"
requires-python = ">=3.13"
19 changes: 19 additions & 0 deletions tests/fixtures/poetry/existing_project/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[project]
name = "foobar"
version = "1.0.0"
requires-python = ">=3.13"

[tool.poetry]
name = "foo"
version = "0.0.1"
description = "A description"

[tool.poetry.dependencies]
python = "^3.11"
arrow = "^1.2.3"

[tool.poetry.group.dev.dependencies]
factory-boy = "^3.2.1"

[tool.poetry.group.typing.dependencies]
mypy = "^1.13.0"
Loading