Skip to content

Commit 8a1895b

Browse files
authored
Merge pull request #526 from sanders41/extra-package-dev
Add option to add extra dev dependencies
2 parents 2c7f02d + c0b7c67 commit 8a1895b

17 files changed

+260
-5
lines changed

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,20 @@ python-project create -s
201201
- Extra Python Dependencies
202202

203203
These are extra packages you want to include in the project that are not provided by default.
204-
For example specifying `fastapi, camel-converter` here will these two packages in the
204+
For example specifying `fastapi, camel-converter` here will inculde these two packages in the
205205
dependencies. If the project is an application the version will be pinned to the latest release
206206
of the packages, and if it is a library the latest release will be used for the minimum version.
207-
If you would like to specify a specific version instead you can by the version with an `@`.
208-
For example `[email protected], [email protected]`. When creating an appliction FastAPI will
209-
be pinned to `0.115.0` and Camel Converter will be pinned to `4.0.0`, or if creating a library
210-
these versions will be used for the minimum version.
207+
If you would like to specify a specific version instead you can by specifying the version with an
208+
`@`. For example `[email protected], [email protected]`. When creating an appliction FastAPI
209+
will be pinned to `0.115.0` and Camel Converter will be pinned to `4.0.0`, or if creating a
210+
library these versions will be used for the minimum version.
211+
212+
- Extra Python Dev Dependencies
213+
214+
These are extra packages you want to include in the project's dev dependencies that are not
215+
provided by default. For example specifying `pytest-xdist` here will include this package in the
216+
dev dependencies. If you would like to specify a specific version instead of checking for the
217+
latest you can by specifying the version with an `@`. For example `[email protected]`.
211218

212219
After running the generator a new directory will be created with the name you used for the
213220
`Project Slug`. Change to this directory then install the python packages and pre-commit hooks.

src/cli.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ pub enum Param {
165165
/// Remove the saved extra Python dependencies
166166
ResetExtraPythonPackages,
167167

168+
/// Extra Python dev dependencies to include in the project
169+
ExtraPythonDevPackages { value: Vec<String> },
170+
171+
/// Remove the saved extra Python dev dependencies
172+
ResetExtraPythonDevPackages,
173+
168174
/// Rerset the config to the default values
169175
Reset,
170176

src/config.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub struct Config {
3434
pub include_docs: Option<bool>,
3535
pub download_latest_packages: Option<bool>,
3636
pub extra_python_packages: Option<Vec<String>>,
37+
pub extra_python_dev_packages: Option<Vec<String>>,
3738

3839
#[serde(skip)]
3940
config_dir: Rc<Option<PathBuf>>,
@@ -63,6 +64,7 @@ impl Default for Config {
6364
include_docs: None,
6465
download_latest_packages: None,
6566
extra_python_packages: None,
67+
extra_python_dev_packages: None,
6668
config_dir: config_dir(),
6769
config_file_path: config_file_path(),
6870
}
@@ -96,6 +98,7 @@ impl Config {
9698
include_docs: config.include_docs,
9799
download_latest_packages: config.download_latest_packages,
98100
extra_python_packages: config.extra_python_packages,
101+
extra_python_dev_packages: config.extra_python_dev_packages,
99102
config_dir: self.config_dir.clone(),
100103
config_file_path: self.config_file_path.clone(),
101104
};
@@ -355,6 +358,16 @@ impl Config {
355358
Ok(())
356359
}
357360

361+
pub fn save_extra_python_dev_packages(&self, value: Vec<String>) -> Result<()> {
362+
self.handle_save_config(|config| &mut config.extra_python_packages, Some(value))?;
363+
Ok(())
364+
}
365+
366+
pub fn reset_extra_python_dev_packages(&self) -> Result<()> {
367+
self.handle_save_config(|config| &mut config.extra_python_packages, None)?;
368+
Ok(())
369+
}
370+
358371
fn handle_save_config<F, T>(&self, func: F, value: Option<T>) -> Result<()>
359372
where
360373
F: FnOnce(&mut Self) -> &mut Option<T>,
@@ -418,6 +431,16 @@ impl Config {
418431
} else {
419432
println!("{}: null", extra_python_packages_label.blue());
420433
}
434+
let extra_python_dev_packages_label = "Extra Python Dev Packages";
435+
if let Some(extra_python_dev_packages) = config.extra_python_dev_packages {
436+
let extra_python_dev_packages_str = extra_python_dev_packages.join(", ");
437+
println!(
438+
"{}: {extra_python_dev_packages_str}",
439+
extra_python_dev_packages_label.blue()
440+
);
441+
} else {
442+
println!("{}: null", extra_python_dev_packages_label.blue());
443+
}
421444
}
422445
}
423446

src/github_actions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,7 @@ mod tests {
13361336
include_docs: false,
13371337
docs_info: None,
13381338
extra_python_packages: None,
1339+
extra_python_dev_packages: None,
13391340
download_latest_packages: false,
13401341
project_root_dir: Some(tempdir().unwrap().path().to_path_buf()),
13411342
}

src/licenses.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ mod tests {
289289
include_docs: false,
290290
docs_info: None,
291291
extra_python_packages: None,
292+
extra_python_dev_packages: None,
292293
download_latest_packages: false,
293294
project_root_dir: Some(tempdir().unwrap().path().to_path_buf()),
294295
}

src/main.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,18 @@ fn main() {
389389
exit(1);
390390
}
391391
}
392+
Param::ExtraPythonDevPackages { value } => {
393+
if let Err(e) = Config::default().save_extra_python_dev_packages(value) {
394+
print_error(e);
395+
exit(1);
396+
}
397+
}
398+
Param::ResetExtraPythonDevPackages {} => {
399+
if let Err(e) = Config::default().reset_extra_python_dev_packages() {
400+
print_error(e);
401+
exit(1);
402+
}
403+
}
392404
Param::Reset => {
393405
if Config::reset().is_err() {
394406
let message = "Error resetting config.";
@@ -444,6 +456,7 @@ mod tests {
444456
include_docs: false,
445457
docs_info: None,
446458
extra_python_packages: None,
459+
extra_python_dev_packages: None,
447460
download_latest_packages: false,
448461
project_root_dir: Some(base),
449462
};

src/project_generator.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,24 @@ fn build_latest_dev_dependencies(project_info: &ProjectInfo) -> Result<String> {
366366
}
367367
}
368368

369+
if let Some(extras) = &project_info.extra_python_dev_packages {
370+
for extra in extras {
371+
if let Ok(p) = ExtraPythonPackageVersion::new(extra.to_lowercase().clone()) {
372+
if let ProjectManager::Poetry = project_info.project_manager {
373+
version_string.push_str(&format!("{} = \"{}\"\n", p.package, p.version));
374+
} else {
375+
version_string.push_str(&format!("{}=={}\n", p.package, p.version));
376+
}
377+
} else {
378+
let error_message = format!(
379+
"Error retrieving latest python package version for {}, skipping.",
380+
extra
381+
);
382+
println!("\n{}", error_message.yellow());
383+
}
384+
}
385+
}
386+
369387
match project_info.project_manager {
370388
ProjectManager::Poetry => Ok(version_string.trim().to_string()),
371389
ProjectManager::Uv => {
@@ -1156,6 +1174,7 @@ mod tests {
11561174
include_docs: false,
11571175
docs_info: None,
11581176
extra_python_packages: None,
1177+
extra_python_dev_packages: None,
11591178
download_latest_packages: false,
11601179
project_root_dir: Some(tempdir().unwrap().path().to_path_buf()),
11611180
}
@@ -1279,6 +1298,26 @@ mod tests {
12791298
assert_yaml_snapshot!(content);
12801299
}
12811300

1301+
#[test]
1302+
fn test_save_poetry_pyproject_toml_with_python_dev_extras() {
1303+
let mut project_info = project_info_dummy();
1304+
project_info.project_manager = ProjectManager::Poetry;
1305+
project_info.extra_python_dev_packages = Some(vec![
1306+
"[email protected]".to_string(),
1307+
"[email protected]".to_string(),
1308+
]);
1309+
let base = project_info.base_dir();
1310+
create_dir_all(&base).unwrap();
1311+
let expected_file = base.join("pyproject.toml");
1312+
save_pyproject_toml_file(&project_info).unwrap();
1313+
1314+
assert!(expected_file.is_file());
1315+
1316+
let content = std::fs::read_to_string(expected_file).unwrap();
1317+
1318+
assert_yaml_snapshot!(content);
1319+
}
1320+
12821321
#[test]
12831322
fn test_save_poetry_pyproject_toml_file_apache_application() {
12841323
let mut project_info = project_info_dummy();
@@ -1393,6 +1432,26 @@ mod tests {
13931432
assert_yaml_snapshot!(content);
13941433
}
13951434

1435+
#[test]
1436+
fn test_save_pyo3_pyproject_toml_with_python_dev_extras() {
1437+
let mut project_info = project_info_dummy();
1438+
project_info.project_manager = ProjectManager::Maturin;
1439+
project_info.extra_python_dev_packages = Some(vec![
1440+
"[email protected]".to_string(),
1441+
"[email protected]".to_string(),
1442+
]);
1443+
let base = project_info.base_dir();
1444+
create_dir_all(&base).unwrap();
1445+
let expected_file = base.join("pyproject.toml");
1446+
save_pyproject_toml_file(&project_info).unwrap();
1447+
1448+
assert!(expected_file.is_file());
1449+
1450+
let content = std::fs::read_to_string(expected_file).unwrap();
1451+
1452+
assert_yaml_snapshot!(content);
1453+
}
1454+
13961455
#[test]
13971456
fn test_save_pyproject_toml_file_apache_pyo3() {
13981457
let mut project_info = project_info_dummy();
@@ -1489,6 +1548,26 @@ mod tests {
14891548
assert_yaml_snapshot!(content);
14901549
}
14911550

1551+
#[test]
1552+
fn test_save_setuptools_pyproject_toml_with_python_dev_extras() {
1553+
let mut project_info = project_info_dummy();
1554+
project_info.project_manager = ProjectManager::Setuptools;
1555+
project_info.extra_python_dev_packages = Some(vec![
1556+
"[email protected]".to_string(),
1557+
"[email protected]".to_string(),
1558+
]);
1559+
let base = project_info.base_dir();
1560+
create_dir_all(&base).unwrap();
1561+
let expected_file = base.join("pyproject.toml");
1562+
save_pyproject_toml_file(&project_info).unwrap();
1563+
1564+
assert!(expected_file.is_file());
1565+
1566+
let content = std::fs::read_to_string(expected_file).unwrap();
1567+
1568+
assert_yaml_snapshot!(content);
1569+
}
1570+
14921571
#[test]
14931572
fn test_save_setuptools_pyproject_toml_file_apache_application() {
14941573
let mut project_info = project_info_dummy();
@@ -1603,6 +1682,26 @@ mod tests {
16031682
assert_yaml_snapshot!(content);
16041683
}
16051684

1685+
#[test]
1686+
fn test_save_uv_pyproject_toml_with_python_dev_extras() {
1687+
let mut project_info = project_info_dummy();
1688+
project_info.project_manager = ProjectManager::Uv;
1689+
project_info.extra_python_dev_packages = Some(vec![
1690+
"[email protected]".to_string(),
1691+
"[email protected]".to_string(),
1692+
]);
1693+
let base = project_info.base_dir();
1694+
create_dir_all(&base).unwrap();
1695+
let expected_file = base.join("pyproject.toml");
1696+
save_pyproject_toml_file(&project_info).unwrap();
1697+
1698+
assert!(expected_file.is_file());
1699+
1700+
let content = std::fs::read_to_string(expected_file).unwrap();
1701+
1702+
assert_yaml_snapshot!(content);
1703+
}
1704+
16061705
#[test]
16071706
fn test_save_uv_pyproject_toml_file_apache_application() {
16081707
let mut project_info = project_info_dummy();
@@ -1717,6 +1816,26 @@ mod tests {
17171816
assert_yaml_snapshot!(content);
17181817
}
17191818

1819+
#[test]
1820+
fn test_save_pixi_pyproject_toml_with_python_dev_extras() {
1821+
let mut project_info = project_info_dummy();
1822+
project_info.project_manager = ProjectManager::Pixi;
1823+
project_info.extra_python_dev_packages = Some(vec![
1824+
"[email protected]".to_string(),
1825+
"[email protected]".to_string(),
1826+
]);
1827+
let base = project_info.base_dir();
1828+
create_dir_all(&base).unwrap();
1829+
let expected_file = base.join("pyproject.toml");
1830+
save_pyproject_toml_file(&project_info).unwrap();
1831+
1832+
assert!(expected_file.is_file());
1833+
1834+
let content = std::fs::read_to_string(expected_file).unwrap();
1835+
1836+
assert_yaml_snapshot!(content);
1837+
}
1838+
17201839
#[test]
17211840
fn test_save_pixi_pyproject_toml_file_apache_application() {
17221841
let mut project_info = project_info_dummy();
@@ -1821,6 +1940,26 @@ mod tests {
18211940
assert_yaml_snapshot!(content);
18221941
}
18231942

1943+
#[test]
1944+
fn test_save_pyo3_dev_requirements_extras_file() {
1945+
let mut project_info = project_info_dummy();
1946+
project_info.project_manager = ProjectManager::Maturin;
1947+
project_info.extra_python_dev_packages = Some(vec![
1948+
"[email protected]".to_string(),
1949+
"[email protected]".to_string(),
1950+
]);
1951+
let base = project_info.base_dir();
1952+
create_dir_all(&base).unwrap();
1953+
let expected_file = base.join("requirements-dev.txt");
1954+
save_dev_requirements(&project_info).unwrap();
1955+
1956+
assert!(expected_file.is_file());
1957+
1958+
let content = std::fs::read_to_string(expected_file).unwrap();
1959+
1960+
assert_yaml_snapshot!(content);
1961+
}
1962+
18241963
#[test]
18251964
fn test_save_setuptools_dev_requirements_application_file() {
18261965
let mut project_info = project_info_dummy();
@@ -1855,6 +1994,26 @@ mod tests {
18551994
assert_yaml_snapshot!(content);
18561995
}
18571996

1997+
#[test]
1998+
fn test_save_setuptools_dev_requirements_extras_file() {
1999+
let mut project_info = project_info_dummy();
2000+
project_info.project_manager = ProjectManager::Setuptools;
2001+
project_info.extra_python_dev_packages = Some(vec![
2002+
"[email protected]".to_string(),
2003+
"[email protected]".to_string(),
2004+
]);
2005+
let base = project_info.base_dir();
2006+
create_dir_all(&base).unwrap();
2007+
let expected_file = base.join("requirements-dev.txt");
2008+
save_dev_requirements(&project_info).unwrap();
2009+
2010+
assert!(expected_file.is_file());
2011+
2012+
let content = std::fs::read_to_string(expected_file).unwrap();
2013+
2014+
assert_yaml_snapshot!(content);
2015+
}
2016+
18582017
#[test]
18592018
fn test_save_mkdocs_yaml() {
18602019
let mut project_info = project_info_dummy();

src/project_info.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ pub struct ProjectInfo {
190190
pub docs_info: Option<DocsInfo>,
191191
pub download_latest_packages: bool,
192192
pub extra_python_packages: Option<Vec<String>>,
193+
pub extra_python_dev_packages: Option<Vec<String>>,
193194
pub project_root_dir: Option<PathBuf>,
194195
}
195196

@@ -678,6 +679,12 @@ pub fn get_project_info(use_defaults: bool) -> Result<ProjectInfo> {
678679
use_defaults,
679680
)?;
680681

682+
let extra_python_dev_packages = default_or_prompt_option_vec_string(
683+
"Extra Python Dev Dependencies".to_string(),
684+
config.extra_python_dev_packages,
685+
use_defaults,
686+
)?;
687+
681688
Ok(ProjectInfo {
682689
project_name,
683690
project_slug,
@@ -704,6 +711,7 @@ pub fn get_project_info(use_defaults: bool) -> Result<ProjectInfo> {
704711
include_docs,
705712
docs_info,
706713
extra_python_packages,
714+
extra_python_dev_packages,
707715
download_latest_packages: false,
708716
project_root_dir: None,
709717
})

src/python_files.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ mod tests {
401401
include_docs: false,
402402
docs_info: None,
403403
extra_python_packages: None,
404+
extra_python_dev_packages: None,
404405
download_latest_packages: false,
405406
project_root_dir: Some(tempdir().unwrap().path().to_path_buf()),
406407
}

0 commit comments

Comments
 (0)