Skip to content

Commit 9011169

Browse files
committed
Update and install pre-commit hooks at creation
1 parent e0eb62a commit 9011169

File tree

5 files changed

+208
-165
lines changed

5 files changed

+208
-165
lines changed

README.md

Lines changed: 2 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -226,76 +226,8 @@ generating the project.
226226
`https://github.com/sanders41/python-project-generator`
227227

228228
After running the generator a new directory will be created with the name you used for the
229-
`Project Slug`. Change to this directory then install the python packages and pre-commit hooks.
230-
231-
### Pure Python Projects
232-
233-
#### Install the Python dependencies when using Poetry.
234-
235-
```sh
236-
poetry install
237-
```
238-
239-
#### Install the Python dependencies when using setuptools.
240-
241-
First create a virtual environment and activate it.
242-
243-
```sh
244-
python -m venv .venv
245-
. .venv/bin/activate
246-
```
247-
248-
```sh
249-
python -m pip install -r requirements-dev.txt
250-
```
251-
252-
#### Install the Python dependencies when using uv.
253-
254-
First create a virtual environment and activate it.
255-
256-
```sh
257-
uv venv
258-
. .venv/bin/activate
259-
```
260-
261-
Next create a lock file
262-
263-
```sh
264-
uv lock
265-
```
266-
267-
Then install the dependencies
268-
269-
```sh
270-
uv sync --frozen
271-
```
272-
273-
Install the pre-commit hooks.
274-
275-
```sh
276-
pre-commit install
277-
```
278-
279-
### PyO3 projects
280-
281-
First create a virtual environment and activate it.
282-
283-
```sh
284-
python -m venv .venv
285-
. .venv/bin/activate
286-
```
287-
288-
Install the dependencies and build the rust module.
289-
290-
```sh
291-
just install
292-
```
293-
294-
Install the pre-commit hooks.
295-
296-
```sh
297-
pre-commit install
298-
```
229+
`Project Slug`. Change to this directory and your project is ready to use. If the project is a
230+
PyO3 project the rust code will need to be compiled. Running `just install` will do this.
299231

300232
### FastAPI projects
301233

src/dev_dependency_installer.rs

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,187 @@ fn maturin_dev_dependency_installer(project_info: &ProjectInfo) -> Result<()> {
144144
bail!("No Python project manager provided for PyO3 project");
145145
}
146146
}
147+
148+
pub fn update_precommit_hooks(project_info: &ProjectInfo) -> Result<()> {
149+
match project_info.project_manager {
150+
ProjectManager::Uv => uv_precommit_autoupdate(project_info)?,
151+
ProjectManager::Poetry => poetry_precommit_autoupdate(project_info)?,
152+
ProjectManager::Setuptools => setuptools_precommit_autoupdate(project_info)?,
153+
ProjectManager::Pixi => pixi_precommit_autoupdate(project_info)?,
154+
ProjectManager::Maturin => maturin_precommit_autoupdate(project_info)?,
155+
};
156+
157+
Ok(())
158+
}
159+
160+
fn uv_precommit_autoupdate(project_info: &ProjectInfo) -> Result<()> {
161+
let output = std::process::Command::new("uv")
162+
.args(["run", "pre-commit", "autoupdate"])
163+
.current_dir(project_info.base_dir())
164+
.output()?;
165+
166+
if !output.status.success() {
167+
let stderr = String::from_utf8_lossy(&output.stderr);
168+
bail!("Failed to update pre-commit hooks: {stderr}");
169+
}
170+
171+
Ok(())
172+
}
173+
174+
fn poetry_precommit_autoupdate(project_info: &ProjectInfo) -> Result<()> {
175+
let output = std::process::Command::new("poetry")
176+
.args(["run", "pre-commit", "autoupdate"])
177+
.current_dir(project_info.base_dir())
178+
.output()?;
179+
180+
if !output.status.success() {
181+
let stderr = String::from_utf8_lossy(&output.stderr);
182+
bail!("Failed to update pre-commit hooks: {stderr}");
183+
}
184+
185+
Ok(())
186+
}
187+
188+
fn setuptools_precommit_autoupdate(project_info: &ProjectInfo) -> Result<()> {
189+
let base_dir = project_info.base_dir();
190+
let venv_path = base_dir.join(".venv");
191+
192+
if !venv_path.exists() {
193+
bail!("Virtual environment not found at {}", venv_path.display());
194+
}
195+
196+
let precommit_bin = if cfg!(windows) {
197+
venv_path.join("Scripts").join("pre-commit.exe")
198+
} else {
199+
venv_path.join("bin").join("pre-commit")
200+
};
201+
202+
let output = std::process::Command::new(&precommit_bin)
203+
.args(["autoupdate"])
204+
.current_dir(base_dir)
205+
.output()?;
206+
207+
if !output.status.success() {
208+
let stderr = String::from_utf8_lossy(&output.stderr);
209+
bail!("Failed to update pre-commit hooks: {stderr}");
210+
}
211+
212+
Ok(())
213+
}
214+
215+
fn pixi_precommit_autoupdate(project_info: &ProjectInfo) -> Result<()> {
216+
let output = std::process::Command::new("pixi")
217+
.args(["run", "pre-commit", "autoupdate"])
218+
.current_dir(project_info.base_dir())
219+
.output()?;
220+
221+
if !output.status.success() {
222+
let stderr = String::from_utf8_lossy(&output.stderr);
223+
bail!("Failed to update pre-commit hooks: {stderr}");
224+
}
225+
226+
Ok(())
227+
}
228+
229+
fn maturin_precommit_autoupdate(project_info: &ProjectInfo) -> Result<()> {
230+
if let Some(pyo3_python_manager) = &project_info.pyo3_python_manager {
231+
match pyo3_python_manager {
232+
Pyo3PythonManager::Uv => uv_precommit_autoupdate(project_info),
233+
Pyo3PythonManager::Setuptools => setuptools_precommit_autoupdate(project_info),
234+
}
235+
} else {
236+
bail!("No Python project manager provided for PyO3 project");
237+
}
238+
}
239+
240+
pub fn install_precommit_hooks(project_info: &ProjectInfo) -> Result<()> {
241+
match project_info.project_manager {
242+
ProjectManager::Uv => uv_precommit_install(project_info)?,
243+
ProjectManager::Poetry => poetry_precommit_install(project_info)?,
244+
ProjectManager::Setuptools => setuptools_precommit_install(project_info)?,
245+
ProjectManager::Pixi => pixi_precommit_install(project_info)?,
246+
ProjectManager::Maturin => maturin_precommit_install(project_info)?,
247+
};
248+
249+
Ok(())
250+
}
251+
252+
fn uv_precommit_install(project_info: &ProjectInfo) -> Result<()> {
253+
let output = std::process::Command::new("uv")
254+
.args(["run", "pre-commit", "install"])
255+
.current_dir(project_info.base_dir())
256+
.output()?;
257+
258+
if !output.status.success() {
259+
let stderr = String::from_utf8_lossy(&output.stderr);
260+
bail!("Failed to install pre-commit hooks: {stderr}");
261+
}
262+
263+
Ok(())
264+
}
265+
266+
fn poetry_precommit_install(project_info: &ProjectInfo) -> Result<()> {
267+
let output = std::process::Command::new("poetry")
268+
.args(["run", "pre-commit", "install"])
269+
.current_dir(project_info.base_dir())
270+
.output()?;
271+
272+
if !output.status.success() {
273+
let stderr = String::from_utf8_lossy(&output.stderr);
274+
bail!("Failed to install pre-commit hooks: {stderr}");
275+
}
276+
277+
Ok(())
278+
}
279+
280+
fn setuptools_precommit_install(project_info: &ProjectInfo) -> Result<()> {
281+
let base_dir = project_info.base_dir();
282+
let venv_path = base_dir.join(".venv");
283+
284+
if !venv_path.exists() {
285+
bail!("Virtual environment not found at {}", venv_path.display());
286+
}
287+
288+
let precommit_bin = if cfg!(windows) {
289+
venv_path.join("Scripts").join("pre-commit.exe")
290+
} else {
291+
venv_path.join("bin").join("pre-commit")
292+
};
293+
294+
let output = std::process::Command::new(&precommit_bin)
295+
.args(["install"])
296+
.current_dir(base_dir)
297+
.output()?;
298+
299+
if !output.status.success() {
300+
let stderr = String::from_utf8_lossy(&output.stderr);
301+
bail!("Failed to install pre-commit hooks: {stderr}");
302+
}
303+
304+
Ok(())
305+
}
306+
307+
fn pixi_precommit_install(project_info: &ProjectInfo) -> Result<()> {
308+
let output = std::process::Command::new("pixi")
309+
.args(["run", "pre-commit", "install"])
310+
.current_dir(project_info.base_dir())
311+
.output()?;
312+
313+
if !output.status.success() {
314+
let stderr = String::from_utf8_lossy(&output.stderr);
315+
bail!("Failed to install pre-commit hooks: {stderr}");
316+
}
317+
318+
Ok(())
319+
}
320+
321+
fn maturin_precommit_install(project_info: &ProjectInfo) -> Result<()> {
322+
if let Some(pyo3_python_manager) = &project_info.pyo3_python_manager {
323+
match pyo3_python_manager {
324+
Pyo3PythonManager::Uv => uv_precommit_install(project_info),
325+
Pyo3PythonManager::Setuptools => setuptools_precommit_install(project_info),
326+
}
327+
} else {
328+
bail!("No Python project manager provided for PyO3 project");
329+
}
330+
}

src/main.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ use indicatif::{ProgressBar, ProgressStyle};
2525
use crate::{
2626
cli::{Args, BooleanChoice, Command, Param},
2727
config::Config,
28-
dev_dependency_installer::install_dev_dependencies,
28+
dev_dependency_installer::{
29+
install_dev_dependencies, install_precommit_hooks, update_precommit_hooks,
30+
},
2931
project_generator::generate_project,
3032
project_info::{get_project_info, ProjectInfo, ProjectManager},
3133
rust_files::cargo_add_pyo3,
@@ -48,6 +50,8 @@ fn create(project_info: &ProjectInfo) -> Result<()> {
4850
}
4951

5052
install_dev_dependencies(project_info)?;
53+
update_precommit_hooks(project_info)?;
54+
install_precommit_hooks(project_info)?;
5155

5256
#[cfg(feature = "fastapi")]
5357
if project_info.is_fastapi_project {

src/package_version.rs

Lines changed: 1 addition & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
use std::{fmt, thread, time::Duration};
2-
3-
use anyhow::{bail, Result};
4-
use exponential_backoff::Backoff;
1+
use std::fmt;
52

63
#[derive(Debug, PartialEq, Eq)]
74
pub enum PythonPackage {
@@ -53,68 +50,6 @@ impl fmt::Display for PreCommitHook {
5350
}
5451
}
5552

56-
pub trait LatestVersion {
57-
fn get_latest_version(&mut self) -> Result<()>;
58-
}
59-
60-
#[derive(Debug)]
61-
pub struct PreCommitHookVersion {
62-
pub hook: PreCommitHook,
63-
pub repo: String,
64-
pub rev: String,
65-
}
66-
67-
impl LatestVersion for PreCommitHookVersion {
68-
fn get_latest_version(&mut self) -> Result<()> {
69-
let client = reqwest::blocking::Client::new();
70-
let attempts = 3;
71-
let min = Duration::from_millis(100); // 10ms
72-
let max = Duration::from_secs(1);
73-
let backoff = Backoff::new(attempts, min, max);
74-
let api_url = format!(
75-
"{}/releases",
76-
self.repo
77-
.replace("https://github.com", "https://api.github.com/repos")
78-
);
79-
80-
for duration in backoff {
81-
let response = client
82-
.get(&api_url)
83-
.header(reqwest::header::USER_AGENT, "python-project-generator")
84-
.timeout(Duration::new(5, 0))
85-
.send();
86-
87-
match response {
88-
Ok(r) => {
89-
let result = r.text()?;
90-
let info: Vec<serde_json::Value> = serde_json::from_str(&result)?;
91-
for i in info {
92-
if i["draft"] == false && i["prerelease"] == false {
93-
self.rev = i["tag_name"].to_string().replace('"', "");
94-
break;
95-
}
96-
}
97-
98-
return Ok(());
99-
}
100-
Err(e) => match duration {
101-
Some(duration) => thread::sleep(duration),
102-
None => bail!("{e}"),
103-
},
104-
}
105-
}
106-
bail!("Error retrieving latest version");
107-
}
108-
}
109-
110-
impl PreCommitHookVersion {
111-
pub fn new(hook: PreCommitHook) -> Self {
112-
let rev = default_pre_commit_rev(&hook);
113-
let repo = pre_commit_repo(&hook);
114-
PreCommitHookVersion { hook, repo, rev }
115-
}
116-
}
117-
11853
pub fn default_pre_commit_rev(hook: &PreCommitHook) -> String {
11954
match hook {
12055
PreCommitHook::MyPy => "v1.18.2".to_string(),

0 commit comments

Comments
 (0)