or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli-migration.mdcore-time-travel.mdescape-hatch.mdindex.mdpytest-integration.md

cli-migration.mddocs/

0

# CLI Migration Tool

1

2

A command-line tool for automatically migrating test code from freezegun to time-machine. The tool parses Python source files and rewrites imports, decorators, and context manager usage to use time-machine's API.

3

4

## Capabilities

5

6

### Main CLI Interface

7

8

The primary entry point for the migration tool with support for batch file processing.

9

10

```python { .api }

11

def main(argv: Sequence[str] | None = None) -> int:

12

"""

13

Main entry point for the migration tool.

14

15

Parameters:

16

- argv: Command line arguments, uses sys.argv if None

17

18

Returns:

19

Exit code (0 for success, non-zero for errors)

20

"""

21

```

22

23

Usage from command line:

24

25

```bash

26

# Install with CLI dependencies

27

pip install time-machine[cli]

28

29

# Migrate single file

30

python -m time_machine migrate test_example.py

31

32

# Migrate multiple files

33

python -m time_machine migrate test_*.py

34

35

# Migrate from stdin

36

cat test_file.py | python -m time_machine migrate -

37

```

38

39

### File Processing Functions

40

41

Functions for processing individual files and batches of files during migration.

42

43

```python { .api }

44

def migrate_files(files: list[str]) -> int:

45

"""

46

Migrate multiple files from freezegun to time-machine.

47

48

Parameters:

49

- files: List of file paths to migrate

50

51

Returns:

52

Exit code (0 if no files changed, 1 if any files changed)

53

"""

54

55

def migrate_file(filename: str) -> int:

56

"""

57

Migrate a single file from freezegun to time-machine.

58

59

Parameters:

60

- filename: Path to file to migrate, or "-" for stdin

61

62

Returns:

63

1 if file was modified, 0 if no changes needed

64

"""

65

66

def migrate_contents(contents_text: str) -> str:

67

"""

68

Migrate file contents from freezegun to time-machine.

69

70

Parameters:

71

- contents_text: Source code as string

72

73

Returns:

74

Modified source code with time-machine imports and calls

75

"""

76

```

77

78

### Migration Transformations

79

80

The migration tool performs the following automatic transformations:

81

82

#### Import Statement Changes

83

84

```python

85

# Before migration

86

import freezegun

87

from freezegun import freeze_time

88

89

# After migration

90

import time_machine

91

from time_machine import travel

92

```

93

94

#### Decorator Usage Changes

95

96

```python

97

# Before migration

98

@freezegun.freeze_time("2023-01-01")

99

def test_something():

100

pass

101

102

@freeze_time("2023-01-01")

103

def test_something():

104

pass

105

106

# After migration

107

@time_machine.travel("2023-01-01", tick=False)

108

def test_something():

109

pass

110

111

@time_machine.travel("2023-01-01", tick=False)

112

def test_something():

113

pass

114

```

115

116

#### Context Manager Changes

117

118

```python

119

# Before migration

120

with freezegun.freeze_time("2023-01-01"):

121

pass

122

123

with freeze_time("2023-01-01"):

124

pass

125

126

# After migration

127

with time_machine.travel("2023-01-01", tick=False):

128

pass

129

130

with time_machine.travel("2023-01-01", tick=False):

131

pass

132

```

133

134

#### Class Decorator Changes

135

136

```python

137

# Before migration

138

@freeze_time("2023-01-01")

139

class TestSomething(unittest.TestCase):

140

pass

141

142

# After migration

143

@time_machine.travel("2023-01-01", tick=False)

144

class TestSomething(unittest.TestCase):

145

pass

146

```

147

148

### Migration Examples

149

150

#### Simple Test File Migration

151

152

Before migration (`test_old.py`):

153

```python

154

import freezegun

155

from datetime import datetime

156

157

@freezegun.freeze_time("2023-01-01")

158

def test_new_year():

159

assert datetime.now().year == 2023

160

161

def test_context_manager():

162

with freezegun.freeze_time("2023-06-15"):

163

assert datetime.now().month == 6

164

```

165

166

After migration:

167

```python

168

import time_machine

169

from datetime import datetime

170

171

@time_machine.travel("2023-01-01", tick=False)

172

def test_new_year():

173

assert datetime.now().year == 2023

174

175

def test_context_manager():

176

with time_machine.travel("2023-06-15", tick=False):

177

assert datetime.now().month == 6

178

```

179

180

#### Class-based Test Migration

181

182

Before migration:

183

```python

184

from freezegun import freeze_time

185

import unittest

186

187

@freeze_time("2023-01-01")

188

class TestFeatures(unittest.TestCase):

189

def test_feature_one(self):

190

self.assertEqual(datetime.now().year, 2023)

191

192

def test_feature_two(self):

193

self.assertEqual(datetime.now().month, 1)

194

```

195

196

After migration:

197

```python

198

import time_machine

199

import unittest

200

201

@time_machine.travel("2023-01-01", tick=False)

202

class TestFeatures(unittest.TestCase):

203

def test_feature_one(self):

204

self.assertEqual(datetime.now().year, 2023)

205

206

def test_feature_two(self):

207

self.assertEqual(datetime.now().month, 1)

208

```

209

210

### Usage Patterns

211

212

#### Interactive Migration

213

214

```bash

215

# Check what would change without modifying files

216

python -m time_machine migrate test_file.py > preview.py

217

diff test_file.py preview.py

218

219

# Migrate file in place

220

python -m time_machine migrate test_file.py

221

222

# Migrate multiple files with glob pattern

223

python -m time_machine migrate tests/test_*.py

224

225

# Process stdin (useful in pipelines)

226

find . -name "test_*.py" -exec cat {} \; | python -m time_machine migrate -

227

```

228

229

#### Batch Migration Script

230

231

```python

232

import subprocess

233

import glob

234

235

def migrate_project():

236

"""Migrate entire project from freezegun to time-machine."""

237

test_files = glob.glob("tests/**/*.py", recursive=True)

238

239

for file_path in test_files:

240

result = subprocess.run([

241

"python", "-m", "time_machine", "migrate", file_path

242

], capture_output=True, text=True)

243

244

if result.returncode == 1:

245

print(f"Migrated: {file_path}")

246

elif result.returncode != 0:

247

print(f"Error migrating {file_path}: {result.stderr}")

248

249

migrate_project()

250

```

251

252

### Error Handling

253

254

The migration tool handles various edge cases and provides informative error messages:

255

256

```python

257

# Non-UTF-8 files

258

# Output: "file.py is non-utf-8 (not supported)"

259

260

# Syntax errors in source

261

# Tool silently skips files with syntax errors

262

263

# Already migrated files

264

# Tool detects existing time-machine imports and skips unnecessary changes

265

```

266

267

### Limitations

268

269

The migration tool has the following limitations:

270

271

1. **Simple patterns only**: Only migrates basic `freeze_time()` usage patterns

272

2. **No keyword arguments**: Does not migrate calls with keyword arguments beyond the destination

273

3. **Static analysis**: Cannot handle dynamic import patterns or complex decorators

274

4. **Manual review needed**: Complex usage patterns may require manual adjustment

275

276

#### Manual Migration Examples

277

278

Some patterns require manual migration:

279

280

```python

281

# Complex decorator patterns (manual migration needed)

282

@freeze_time("2023-01-01", tick=True) # Has keyword args

283

def test_something():

284

pass

285

286

# Should become:

287

@time_machine.travel("2023-01-01", tick=True) # Keep original tick value

288

def test_something():

289

pass

290

291

# Dynamic usage (manual migration needed)

292

freeze_func = freeze_time

293

with freeze_func("2023-01-01"): # Dynamic reference

294

pass

295

296

# Should become:

297

travel_func = time_machine.travel

298

with travel_func("2023-01-01", tick=False):

299

pass

300

```

301

302

### Dependencies

303

304

The CLI tool requires additional dependencies:

305

306

```python

307

# Install with CLI support

308

pip install time-machine[cli]

309

310

# Or install dependencies manually

311

pip install tokenize-rt

312

```

313

314

## Type Definitions

315

316

```python { .api }

317

from collections.abc import Sequence

318

```