or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli-interface.mdenvironment-validation.mdindex.mdinteractive-selection.mdpackage-parsing.mdpackage-upgrading.mdrequirements-detection.mdstatus-detection.md

package-upgrading.mddocs/

0

# Package Upgrading

1

2

Package installation via pip and atomic updating of version numbers in requirements files, with dry-run support and error handling.

3

4

## Capabilities

5

6

### Package Installation and Requirements Updating

7

8

Executes the final upgrade process by installing packages via pip and updating version numbers in requirements files.

9

10

```python { .api }

11

class PackagesUpgrader:

12

def __init__(self, selected_packages, requirements_files, options):

13

"""

14

Initialize package upgrader.

15

16

Args:

17

selected_packages (list): List of package dictionaries from interactive selector

18

requirements_files (list): List of requirements file paths to update

19

options (dict): Command-line options including:

20

--dry-run (bool): Simulate without actual changes

21

--skip-package-installation (bool): Only update files, don't install

22

"""

23

24

def do_upgrade(self):

25

"""

26

Execute upgrade process for all selected packages.

27

28

Returns:

29

list: List of successfully upgraded package dictionaries

30

31

Process for each package:

32

1. Install package via pip (unless skipped)

33

2. Update version in all requirements files

34

3. Handle installation failures gracefully

35

"""

36

```

37

38

### Individual Package Processing

39

40

Handles the upgrade process for a single package with proper error handling.

41

42

```python { .api }

43

def _update_package(self, package):

44

"""

45

Update (install) a single package and update requirements files.

46

47

Args:

48

package (dict): Package dictionary with name, current_version, latest_version

49

50

Process:

51

1. Install exact version via pip (unless dry-run or skip-installation)

52

2. Update requirements files only if installation succeeds

53

3. Handle CalledProcessError from pip installation

54

"""

55

```

56

57

### Requirements File Updating

58

59

Atomically updates version numbers in requirements files with rollback on failure.

60

61

```python { .api }

62

def _update_requirements_package(self, package):

63

"""

64

Update package version in all requirements files.

65

66

Args:

67

package (dict): Package dictionary with version information

68

69

Atomic operation:

70

1. Read all lines from each requirements file

71

2. Update matching package lines

72

3. Write updated content to file

73

4. Rollback original content on any exception

74

"""

75

76

def _maybe_update_line_package(self, line, package):

77

"""

78

Update package version in a single requirements line if it matches.

79

80

Args:

81

line (str): Single line from requirements file

82

package (dict): Package dictionary with version information

83

84

Returns:

85

str: Updated line or original line if no match

86

87

Matching criteria:

88

- Package name matches (case-insensitive regex)

89

- Uses exact version pinning (==)

90

- Handles package extras [extra1,extra2]

91

- Preserves line formatting and comments

92

"""

93

```

94

95

## Installation Process

96

97

### Pip Installation

98

99

Packages are installed using exact version pinning:

100

101

```bash

102

pip install django==4.1.0

103

pip install requests[security]==2.28.1

104

```

105

106

**Installation modes:**

107

108

1. **Normal mode**: Installs packages via subprocess.check_call

109

2. **Dry-run mode**: Skips installation, prints simulation message

110

3. **Skip-installation mode**: Only updates requirements files

111

112

### Subprocess Execution

113

114

```python

115

import subprocess

116

117

# Install exact version

118

pinned = f"{package['name']}=={package['latest_version']}"

119

subprocess.check_call(['pip', 'install', pinned])

120

```

121

122

**Error handling:**

123

- `CalledProcessError`: Installation failure, skips requirements update

124

- Continues processing other packages on individual failures

125

126

## Requirements File Updates

127

128

### Line Matching

129

130

Uses regex pattern matching to identify package lines:

131

132

```python

133

import re

134

135

pattern = r'\b{package}(?:\[\w*\])?=={old_version}\b'.format(

136

package=re.escape(package['name']),

137

old_version=re.escape(str(package['current_version']))

138

)

139

```

140

141

**Pattern features:**

142

- Word boundaries prevent partial matches

143

- Optional extras support: `package[extra]==version`

144

- Exact version matching only

145

- Case-insensitive matching

146

147

### Atomic File Updates

148

149

Requirements files are updated atomically:

150

151

1. **Read**: Load all lines into memory

152

2. **Process**: Update matching lines

153

3. **Write**: Write all lines to file

154

4. **Rollback**: Restore original content on any exception

155

156

```python

157

# Atomic update process

158

lines = []

159

with open(filename, 'r') as frh:

160

for line in frh:

161

lines.append(line)

162

163

try:

164

with open(filename, 'w') as fwh:

165

for line in lines:

166

updated_line = self._maybe_update_line_package(line, package)

167

fwh.write(updated_line)

168

except Exception as e:

169

# Rollback on any error

170

with open(filename, 'w') as fwh:

171

for line in lines:

172

fwh.write(line)

173

raise e

174

```

175

176

## Usage Examples

177

178

### Basic Package Upgrading

179

180

```python

181

from pip_upgrader.packages_upgrader import PackagesUpgrader

182

183

# Selected packages from interactive selector

184

selected_packages = [

185

{

186

'name': 'django',

187

'current_version': Version('3.2.0'),

188

'latest_version': Version('4.1.0'),

189

'upgrade_available': True,

190

'upload_time': '2022-08-03 08:15:30'

191

}

192

]

193

194

# Requirements files to update

195

requirements_files = ['requirements.txt', 'requirements/dev.txt']

196

197

# Options

198

options = {

199

'--dry-run': False,

200

'--skip-package-installation': False

201

}

202

203

# Execute upgrades

204

upgrader = PackagesUpgrader(selected_packages, requirements_files, options)

205

upgraded = upgrader.do_upgrade()

206

207

print(f"Successfully upgraded {len(upgraded)} packages")

208

```

209

210

### Dry Run Mode

211

212

```python

213

# Simulate upgrades without making changes

214

options = {

215

'--dry-run': True,

216

'--skip-package-installation': False

217

}

218

219

upgrader = PackagesUpgrader(selected_packages, requirements_files, options)

220

upgraded = upgrader.do_upgrade()

221

222

# Prints simulation messages, no actual changes made

223

```

224

225

### Skip Package Installation

226

227

```python

228

# Only update requirements files, don't install packages

229

options = {

230

'--dry-run': False,

231

'--skip-package-installation': True

232

}

233

234

upgrader = PackagesUpgrader(selected_packages, requirements_files, options)

235

upgraded = upgrader.do_upgrade()

236

237

# Files updated, but pip install not executed

238

```

239

240

## Requirements File Format Support

241

242

### Standard Format

243

244

```

245

django==3.2.0

246

requests==2.25.1

247

flask==2.0.0

248

```

249

250

**Updated to:**

251

```

252

django==4.1.0

253

requests==2.28.1

254

flask==2.2.2

255

```

256

257

### Package Extras

258

259

```

260

django[rest]==3.2.0

261

requests[security,socks]==2.25.1

262

```

263

264

**Updated to:**

265

```

266

django[rest]==4.1.0

267

requests[security,socks]==2.28.1

268

```

269

270

### Mixed Requirements

271

272

```

273

# Web framework

274

django==3.2.0 # Core framework

275

django-rest-framework==3.12.0

276

277

# HTTP client

278

requests==2.25.1

279

urllib3>=1.26.0 # Not updated (not exact pin)

280

281

# Development

282

pytest==6.2.4

283

```

284

285

Only exact pins (`==`) are updated. Other version specifiers (`>=`, `~=`, etc.) are left unchanged.

286

287

## Error Handling

288

289

### Installation Failures

290

291

When `pip install` fails:

292

293

```python

294

try:

295

subprocess.check_call(['pip', 'install', pinned])

296

self._update_requirements_package(package)

297

except CalledProcessError:

298

print(Color('{{autored}}Failed to install package "{}"{{/autored}}'.format(package['name'])))

299

# Continue with next package

300

```

301

302

**Behavior:**

303

- Prints colored error message

304

- Skips requirements file update for failed package

305

- Continues processing remaining packages

306

- Does not add failed package to upgraded list

307

308

### File Update Failures

309

310

Requirements file update failures trigger rollback:

311

312

```python

313

try:

314

# Write updated lines

315

pass

316

except Exception as e:

317

# Restore original file content

318

with open(filename, 'w') as fwh:

319

for line in lines:

320

fwh.write(line)

321

raise e

322

```

323

324

**Common failure scenarios:**

325

- File permission errors

326

- Disk space issues

327

- File locking by other processes

328

329

### Environment Variable Override

330

331

The `PIP_UPGRADER_SKIP_PACKAGE_INSTALLATION` environment variable can override the skip installation option:

332

333

```bash

334

export PIP_UPGRADER_SKIP_PACKAGE_INSTALLATION=1

335

pip-upgrade requirements.txt

336

```

337

338

This forces skip-installation mode regardless of command-line options.

339

340

## Dry Run Output

341

342

In dry-run mode, the upgrader provides detailed simulation output:

343

344

### Package Installation Simulation

345

346

```

347

[Dry Run]: skipping package installation: django

348

[Dry Run]: skipping package installation: requests

349

```

350

351

### Requirements Update Simulation

352

353

```

354

[Dry Run]: skipping requirements replacement: django==3.2.0 / django==4.1.0

355

[Dry Run]: skipping requirements replacement: requests==2.25.1 / requests==2.28.1

356

```

357

358

**Features:**

359

- Shows original and updated versions

360

- Indicates which operations are being skipped

361

- Maintains upgrade tracking for final summary

362

363

## Return Value

364

365

The `do_upgrade()` method returns a list of successfully upgraded packages:

366

367

```python

368

[

369

{

370

'name': 'django',

371

'current_version': Version('3.2.0'),

372

'latest_version': Version('4.1.0'),

373

'upgrade_available': True,

374

'upload_time': '2022-08-03 08:15:30'

375

}

376

]

377

```

378

379

**Inclusion criteria:**

380

- Package was found and updated in at least one requirements file

381

- Installation succeeded (if not skipped)

382

- No file update errors occurred

383

384

**Exclusion criteria:**

385

- Package installation failed (CalledProcessError)

386

- Package not found in any requirements file

387

- File update exceptions occurred