or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

file-locking.mdindex.mdlock-classes.mdredis-locking.mdsemaphores.mdutilities.md

utilities.mddocs/

0

# Utilities

1

2

Additional utilities for atomic file operations and helper functions that complement the core locking functionality.

3

4

## Capabilities

5

6

### Atomic File Operations

7

8

Utility for atomic file writing that ensures files are written completely or not at all, preventing corruption from interrupted writes.

9

10

```python { .api }

11

def open_atomic(filename: Filename, binary: bool = True) -> typing.Iterator[typing.IO]:

12

"""

13

Context manager for atomic file writing using temporary files.

14

15

Instead of locking, this method allows you to write the entire file

16

and atomically move it to the final location using os.rename().

17

18

Parameters:

19

- filename: Target filename (str or pathlib.Path)

20

- binary: If True, open in binary mode ('wb'), else text mode ('w')

21

22

Yields:

23

- File handle for writing to temporary file

24

25

Raises:

26

- AssertionError: If target file already exists

27

28

Note:

29

- Creates parent directories if they don't exist

30

- Uses os.rename() which is atomic on most platforms

31

- Temporary file is created in same directory as target

32

- Automatic cleanup if operation fails

33

"""

34

```

35

36

### Usage Examples

37

38

Basic atomic file writing:

39

40

```python

41

import portalocker

42

43

# Write file atomically - either complete file is written or nothing

44

with portalocker.open_atomic('config.json') as fh:

45

import json

46

config_data = {'key': 'value', 'settings': {'debug': True}}

47

json.dump(config_data, fh)

48

# File only becomes visible at config.json when context exits successfully

49

50

# If anything goes wrong during writing, config.json won't be created/modified

51

```

52

53

Text mode atomic writing:

54

55

```python

56

import portalocker

57

58

# Write text file atomically

59

with portalocker.open_atomic('output.txt', binary=False) as fh:

60

fh.write('Line 1\n')

61

fh.write('Line 2\n')

62

fh.write('Final line\n')

63

# File is complete when context exits

64

```

65

66

Atomic file updates:

67

68

```python

69

import portalocker

70

import json

71

import os

72

73

def update_config_file(filename, new_settings):

74

"""Atomically update a JSON config file"""

75

76

# Read existing config

77

existing_config = {}

78

if os.path.exists(filename):

79

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

80

existing_config = json.load(fh)

81

82

# Merge with new settings

83

existing_config.update(new_settings)

84

85

# Write atomically - if this fails, original file is unchanged

86

with portalocker.open_atomic(filename, binary=False) as fh:

87

json.dump(existing_config, fh, indent=2)

88

fh.write('\n')

89

90

# Usage

91

update_config_file('app_config.json', {'debug': False, 'version': '2.0'})

92

```

93

94

Binary file atomic operations:

95

96

```python

97

import portalocker

98

import pickle

99

100

def save_data_atomically(filename, data):

101

"""Save Python object to file atomically"""

102

with portalocker.open_atomic(filename, binary=True) as fh:

103

pickle.dump(data, fh)

104

105

# Save large data structure atomically

106

large_dataset = {'users': [...], 'transactions': [...]}

107

save_data_atomically('dataset.pkl', large_dataset)

108

```

109

110

Working with pathlib.Path:

111

112

```python

113

import portalocker

114

import pathlib

115

116

# Works with pathlib.Path objects

117

data_dir = pathlib.Path('/data/exports')

118

output_file = data_dir / 'report.csv'

119

120

with portalocker.open_atomic(output_file, binary=False) as fh:

121

fh.write('column1,column2,column3\n')

122

fh.write('value1,value2,value3\n')

123

# Parent directories created automatically if needed

124

```

125

126

Error handling and cleanup:

127

128

```python

129

import portalocker

130

import json

131

132

try:

133

with portalocker.open_atomic('critical_data.json') as fh:

134

# If this raises an exception, the target file remains untouched

135

data = generate_critical_data()

136

json.dump(data, fh)

137

138

# Simulate an error during writing

139

if should_fail():

140

raise ValueError("Processing failed")

141

142

except ValueError as e:

143

print(f"Writing failed: {e}")

144

# critical_data.json was not created/modified

145

# Temporary file was automatically cleaned up

146

```

147

148

Atomic replacement of existing files:

149

150

```python

151

import portalocker

152

import shutil

153

154

def atomic_file_replacement(source_file, target_file):

155

"""Atomically replace target file with processed version of source"""

156

157

# Process source and write to target atomically

158

with open(source_file, 'r') as src:

159

with portalocker.open_atomic(target_file, binary=False) as dst:

160

# Process and write line by line

161

for line in src:

162

processed_line = process_line(line)

163

dst.write(processed_line)

164

165

# If we get here, target_file now contains the processed version

166

print(f"Successfully replaced {target_file}")

167

168

# Usage

169

atomic_file_replacement('input.log', 'processed.log')

170

```

171

172

## Comparison with File Locking

173

174

Atomic file operations vs file locking serve different purposes:

175

176

```python

177

import portalocker

178

179

# File locking: Multiple processes coordinate access to same file

180

def append_with_locking(filename, data):

181

"""Multiple processes can safely append to the same file"""

182

with portalocker.Lock(filename, 'a') as fh:

183

fh.write(data + '\n')

184

fh.flush()

185

186

# Atomic writing: Ensure file is completely written or not at all

187

def replace_with_atomic(filename, data):

188

"""Ensure file contains complete data or doesn't exist"""

189

with portalocker.open_atomic(filename, binary=False) as fh:

190

fh.write(data)

191

192

# Use locking for shared access

193

append_with_locking('shared.log', 'Process 1 data')

194

append_with_locking('shared.log', 'Process 2 data')

195

196

# Use atomic for complete replacement

197

replace_with_atomic('config.json', '{"version": "1.0"}')

198

```

199

200

## Platform Considerations

201

202

The atomic nature depends on the underlying filesystem:

203

204

```python

205

import portalocker

206

import os

207

208

# os.rename() is atomic on most POSIX systems and Windows

209

# but behavior may vary on network filesystems

210

211

def safe_atomic_write(filename, data):

212

"""Write with awareness of platform limitations"""

213

try:

214

with portalocker.open_atomic(filename, binary=False) as fh:

215

fh.write(data)

216

return True

217

except Exception as e:

218

print(f"Atomic write failed: {e}")

219

return False

220

221

# For critical applications, consider combining with locking

222

def ultra_safe_write(filename, data):

223

"""Combine atomic writing with file locking"""

224

lock_file = filename + '.lock'

225

226

with portalocker.Lock(lock_file, 'w'):

227

# Only one process can write at a time

228

with portalocker.open_atomic(filename, binary=False) as fh:

229

fh.write(data)

230

```

231

232

## Type Definitions

233

234

```python { .api }

235

from typing import Union, Iterator

236

import pathlib

237

import typing

238

239

# Filename type (same as used by Lock classes)

240

Filename = Union[str, pathlib.Path]

241

242

# Generic IO type for file handles

243

IO = Union[typing.IO[str], typing.IO[bytes]]

244

```