0
# Utilities and Exceptions
1
2
Helper functions, logging configuration, replay functionality, and comprehensive exception hierarchy. This module provides supporting utilities and error handling for all cookiecutter operations.
3
4
## Capabilities
5
6
### File System Utilities
7
8
File and directory management functions.
9
10
```python { .api }
11
def rmtree(path):
12
"""
13
Remove directory and contents like rm -rf.
14
15
Parameters:
16
- path: str - Directory path to remove
17
"""
18
19
def force_delete(func, path, exc_info):
20
"""
21
Error handler for shutil.rmtree equivalent to rm -rf.
22
23
Parameters:
24
- func: callable - Function that failed
25
- path: str - Path that caused the error
26
- exc_info: tuple - Exception information
27
"""
28
29
def make_sure_path_exists(path):
30
"""
31
Ensure directory exists.
32
33
Parameters:
34
- path: str - Directory path to create if needed
35
"""
36
37
def make_executable(script_path):
38
"""
39
Make script executable.
40
41
Parameters:
42
- script_path: str - Path to script file
43
"""
44
```
45
46
### Context Management
47
48
Utilities for working directory and environment management.
49
50
```python { .api }
51
def work_in(dirname=None):
52
"""
53
Context manager for changing working directory.
54
55
Parameters:
56
- dirname: str, optional - Directory to change to
57
58
Returns:
59
ContextManager - Context manager for directory change
60
"""
61
62
def create_tmp_repo_dir(repo_dir):
63
"""
64
Create temporary directory with repo copy.
65
66
Parameters:
67
- repo_dir: str - Source repository directory
68
69
Returns:
70
str - Path to temporary directory copy
71
"""
72
73
def create_env_with_context(context):
74
"""
75
Create Jinja2 environment with context.
76
77
Parameters:
78
- context: dict - Template context
79
80
Returns:
81
Environment - Configured Jinja2 environment
82
"""
83
```
84
85
### Template Utilities
86
87
Utilities for template processing and Jinja2 integration.
88
89
```python { .api }
90
def simple_filter(filter_function):
91
"""
92
Decorator to wrap function in Jinja2 extension.
93
94
Parameters:
95
- filter_function: callable - Function to wrap as Jinja2 filter
96
97
Returns:
98
callable - Decorated function
99
"""
100
```
101
102
### Logging Configuration
103
104
Logging setup and configuration for cookiecutter operations.
105
106
```python { .api }
107
def configure_logger(stream_level='DEBUG', debug_file=None):
108
"""
109
Configure logging for cookiecutter.
110
111
Parameters:
112
- stream_level: str - Logging level for console output
113
- debug_file: str, optional - File path for debug logging
114
"""
115
```
116
117
### Replay System
118
119
Session replay functionality for reusing previous configurations.
120
121
```python { .api }
122
def get_file_name(replay_dir, template_name):
123
"""
124
Get replay file name.
125
126
Parameters:
127
- replay_dir: str - Directory containing replay files
128
- template_name: str - Name of template
129
130
Returns:
131
str - Full path to replay file
132
"""
133
134
def dump(replay_dir, template_name, context):
135
"""
136
Write context data to replay file.
137
138
Parameters:
139
- replay_dir: str - Directory to store replay file
140
- template_name: str - Name of template
141
- context: dict - Context data to save
142
"""
143
144
def load(replay_dir, template_name):
145
"""
146
Read context data from replay file.
147
148
Parameters:
149
- replay_dir: str - Directory containing replay files
150
- template_name: str - Name of template
151
152
Returns:
153
dict - Loaded context data
154
"""
155
```
156
157
## Logging Constants
158
159
```python { .api }
160
LOG_LEVELS: dict # Dictionary mapping level names to logging constants
161
# Contains: {'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR}
162
163
LOG_FORMATS: dict # Dictionary of log format strings
164
# Contains format strings for different logging levels
165
```
166
167
## Exception Hierarchy
168
169
Comprehensive exception classes for error handling throughout cookiecutter.
170
171
### Base Exception
172
173
```python { .api }
174
class CookiecutterException(Exception):
175
"""Base exception class for all cookiecutter errors."""
176
```
177
178
### Configuration Exceptions
179
180
```python { .api }
181
class ConfigDoesNotExistException(CookiecutterException):
182
"""Missing config file."""
183
184
class InvalidConfiguration(CookiecutterException):
185
"""Invalid configuration file."""
186
```
187
188
### Template Exceptions
189
190
```python { .api }
191
class NonTemplatedInputDirException(CookiecutterException):
192
"""Input directory is not templated."""
193
194
class UnknownTemplateDirException(CookiecutterException):
195
"""Ambiguous project template directory."""
196
197
class MissingProjectDir(CookiecutterException):
198
"""Missing generated project directory."""
199
200
class ContextDecodingException(CookiecutterException):
201
"""Failed JSON context decoding."""
202
203
class UndefinedVariableInTemplate(CookiecutterException):
204
"""Template uses undefined variables."""
205
```
206
207
### Repository Exceptions
208
209
```python { .api }
210
class UnknownRepoType(CookiecutterException):
211
"""Unknown repository type."""
212
213
class RepositoryNotFound(CookiecutterException):
214
"""Repository doesn't exist."""
215
216
class RepositoryCloneFailed(CookiecutterException):
217
"""Repository can't be cloned."""
218
219
class InvalidZipRepository(CookiecutterException):
220
"""Invalid zip repository."""
221
```
222
223
### Operational Exceptions
224
225
```python { .api }
226
class VCSNotInstalled(CookiecutterException):
227
"""Version control system not available."""
228
229
class OutputDirExistsException(CookiecutterException):
230
"""Output directory already exists."""
231
232
class InvalidModeException(CookiecutterException):
233
"""Incompatible modes (no_input + replay)."""
234
235
class FailedHookException(CookiecutterException):
236
"""Hook script failures."""
237
238
class UnknownExtension(CookiecutterException):
239
"""Un-importable Jinja2 extension."""
240
```
241
242
## Usage Examples
243
244
### File System Operations
245
246
```python
247
from cookiecutter.utils import rmtree, make_sure_path_exists, work_in
248
import os
249
250
# Ensure directory exists
251
make_sure_path_exists('./my-project/src')
252
253
# Work in different directory
254
with work_in('./my-project'):
255
# Operations here happen in ./my-project
256
print(os.getcwd()) # Shows full path to ./my-project
257
# Back to original directory
258
259
# Clean up directory
260
if os.path.exists('./temp-dir'):
261
rmtree('./temp-dir')
262
```
263
264
### Logging Configuration
265
266
```python
267
from cookiecutter.log import configure_logger, LOG_LEVELS
268
269
# Basic logging setup
270
configure_logger(stream_level='INFO')
271
272
# Debug logging with file output
273
configure_logger(
274
stream_level='DEBUG',
275
debug_file='./cookiecutter-debug.log'
276
)
277
278
# Available log levels
279
print("Available log levels:", list(LOG_LEVELS.keys()))
280
```
281
282
### Replay System
283
284
```python
285
from cookiecutter.replay import dump, load, get_file_name
286
287
# Save context for replay
288
context = {
289
'cookiecutter': {
290
'project_name': 'my-awesome-project',
291
'author': 'Jane Developer',
292
'version': '1.0.0'
293
}
294
}
295
296
dump(
297
replay_dir='~/.cookiecutter_replay',
298
template_name='python-package',
299
context=context
300
)
301
302
# Load previous context
303
loaded_context = load(
304
replay_dir='~/.cookiecutter_replay',
305
template_name='python-package'
306
)
307
308
print("Loaded context:", loaded_context)
309
```
310
311
### Error Handling
312
313
```python
314
from cookiecutter.main import cookiecutter
315
from cookiecutter.exceptions import (
316
CookiecutterException,
317
OutputDirExistsException,
318
RepositoryNotFound,
319
RepositoryCloneFailed,
320
ContextDecodingException,
321
FailedHookException
322
)
323
324
def safe_cookiecutter(template, **kwargs):
325
"""Wrapper for cookiecutter with comprehensive error handling."""
326
try:
327
return cookiecutter(template, **kwargs)
328
329
except OutputDirExistsException as e:
330
print(f"Output directory exists: {e}")
331
# Optionally retry with overwrite
332
return cookiecutter(template, overwrite_if_exists=True, **kwargs)
333
334
except RepositoryNotFound as e:
335
print(f"Template repository not found: {e}")
336
return None
337
338
except RepositoryCloneFailed as e:
339
print(f"Failed to clone repository: {e}")
340
return None
341
342
except ContextDecodingException as e:
343
print(f"Invalid cookiecutter.json format: {e}")
344
return None
345
346
except FailedHookException as e:
347
print(f"Hook execution failed: {e}")
348
return None
349
350
except CookiecutterException as e:
351
print(f"Cookiecutter error: {e}")
352
return None
353
354
except Exception as e:
355
print(f"Unexpected error: {e}")
356
return None
357
358
# Usage
359
result = safe_cookiecutter('gh:audreyfeldroy/cookiecutter-pypackage')
360
```
361
362
### Advanced Utilities
363
364
```python
365
from cookiecutter.utils import (
366
create_tmp_repo_dir,
367
create_env_with_context,
368
simple_filter,
369
make_executable
370
)
371
import tempfile
372
import os
373
374
# Create temporary copy of repository
375
temp_dir = create_tmp_repo_dir('./my-template')
376
print(f"Temporary copy created at: {temp_dir}")
377
378
# Create Jinja2 environment with context
379
context = {'project_name': 'test-project', 'version': '1.0'}
380
env = create_env_with_context(context)
381
382
# Create custom Jinja2 filter
383
@simple_filter
384
def uppercase_filter(text):
385
return text.upper()
386
387
# Apply filter in template
388
template = env.from_string('{{ project_name | uppercase }}')
389
result = template.render(project_name='hello world')
390
# Returns: 'HELLO WORLD'
391
392
# Make script executable
393
script_path = './setup-script.sh'
394
if os.path.exists(script_path):
395
make_executable(script_path)
396
```
397
398
### Context Manager Usage
399
400
```python
401
from cookiecutter.utils import work_in
402
import os
403
import subprocess
404
405
# Complex operation in different directory
406
original_dir = os.getcwd()
407
print(f"Starting in: {original_dir}")
408
409
with work_in('./my-project'):
410
print(f"Working in: {os.getcwd()}")
411
412
# Run commands in the project directory
413
subprocess.run(['pip', 'install', '-e', '.'], check=True)
414
subprocess.run(['python', '-m', 'pytest'], check=True)
415
416
# Create subdirectory
417
os.makedirs('build', exist_ok=True)
418
419
print(f"Back to: {os.getcwd()}") # Back to original directory
420
```
421
422
### Exception Information
423
424
```python
425
from cookiecutter.exceptions import UndefinedVariableInTemplate
426
427
# Exception with additional context
428
try:
429
# Some template rendering operation
430
pass
431
except UndefinedVariableInTemplate as e:
432
print(f"Undefined variable error: {e.message}")
433
print(f"Error details: {e.error}")
434
print(f"Context: {e.context}")
435
```
436
437
### Replay File Management
438
439
```python
440
from cookiecutter.replay import get_file_name, load
441
import os
442
import json
443
444
# Check if replay file exists
445
replay_dir = '~/.cookiecutter_replay'
446
template_name = 'my-template'
447
448
replay_file = get_file_name(replay_dir, template_name)
449
if os.path.exists(replay_file):
450
# Load and examine replay data
451
replay_data = load(replay_dir, template_name)
452
print(f"Previous configuration:")
453
print(json.dumps(replay_data, indent=2))
454
else:
455
print("No replay file found for this template")
456
```