coding-client 1.21.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coding_client/__init__.py +1 -0
- coding_client/api/__init__.py +19 -0
- coding_client/api/exception_handlers.py +158 -0
- coding_client/api/logging_setup.py +44 -0
- coding_client/api/metrics.py +40 -0
- coding_client/api/openapi_config.py +124 -0
- coding_client/api/request_logging.py +60 -0
- coding_client/api/validation.py +150 -0
- coding_client/auth/__init__.py +78 -0
- coding_client/auth/_default_tenant.py +132 -0
- coding_client/auth/_header_trust.py +98 -0
- coding_client/auth/_onbehalf.py +315 -0
- coding_client/auth/_trusted_networks.py +94 -0
- coding_client/auth/dependencies.py +61 -0
- coding_client/auth/middleware.py +445 -0
- coding_client/auth/models.py +83 -0
- coding_client/auth/outbound.py +137 -0
- coding_client/auth/rbac.py +21 -0
- coding_client/auth/rbac_coding.py +5 -0
- coding_client/auth/tenant.py +650 -0
- coding_client/auth/websocket.py +148 -0
- coding_client/clients/__init__.py +24 -0
- coding_client/clients/base.py +131 -0
- coding_client/clients/coding_ai.py +33 -0
- coding_client/clients/coding_analysis.py +36 -0
- coding_client/clients/coding_containers.py +45 -0
- coding_client/clients/coding_github.py +127 -0
- coding_client/clients/coding_realtime.py +127 -0
- coding_client/clients/coding_security.py +51 -0
- coding_client/clients/coding_testing.py +31 -0
- coding_client/clients/coding_workspaces.py +265 -0
- coding_client/config/__init__.py +12 -0
- coding_client/config/base_settings.py +61 -0
- coding_client/config/yaml_config_loader.py +172 -0
- coding_client/db/__init__.py +42 -0
- coding_client/db/db_types.py +122 -0
- coding_client/db/exceptions.py +59 -0
- coding_client/db/postgres_connection.py +572 -0
- coding_client/db/tenant_query_helper.py +417 -0
- coding_client/db_init.py +49 -0
- coding_client/exceptions/__init__.py +67 -0
- coding_client/exceptions/api_exceptions.py +395 -0
- coding_client/exceptions/pydantic_ai_exceptions.py +533 -0
- coding_client/interfaces/__init__.py +22 -0
- coding_client/interfaces/cache_interface.py +179 -0
- coding_client/interfaces/container_interface.py +49 -0
- coding_client/interfaces/docker_interface.py +283 -0
- coding_client/interfaces/graph_interface.py +28 -0
- coding_client/interfaces/security_interface.py +72 -0
- coding_client/interfaces/webhook_interface.py +67 -0
- coding_client/llm/__init__.py +54 -0
- coding_client/llm/base_agent.py +195 -0
- coding_client/llm/context.py +121 -0
- coding_client/llm/responses.py +102 -0
- coding_client/llm/routing.py +82 -0
- coding_client/llm/settings.py +102 -0
- coding_client/models/__init__.py +62 -0
- coding_client/models/agent.py +38 -0
- coding_client/models/ai_test_failure.py +745 -0
- coding_client/models/analysis.py +330 -0
- coding_client/models/architecture.py +685 -0
- coding_client/models/bug.py +128 -0
- coding_client/models/code_graph.py +317 -0
- coding_client/models/container.py +241 -0
- coding_client/models/core.py +878 -0
- coding_client/models/docker_container.py +104 -0
- coding_client/models/github.py +293 -0
- coding_client/models/logging.py +29 -0
- coding_client/models/package_manager.py +257 -0
- coding_client/models/pr_validation.py +440 -0
- coding_client/models/pydantic_ai.py +395 -0
- coding_client/models/repo_scanner.py +249 -0
- coding_client/models/responses.py +431 -0
- coding_client/models/security.py +185 -0
- coding_client/models/static_analysis.py +508 -0
- coding_client/models/system.py +48 -0
- coding_client/models/test_executor.py +204 -0
- coding_client/models/testing.py +673 -0
- coding_client/models/testing_config.py +200 -0
- coding_client/models/validation.py +127 -0
- coding_client/models_init.py +272 -0
- coding_client/telemetry.py +195 -0
- coding_client/testing.py +180 -0
- coding_client/types/__init__.py +130 -0
- coding_client/types/constraint_types.py +276 -0
- coding_client/types/core_types.py +112 -0
- coding_client/types/state_machine.py +443 -0
- coding_client/utils/__init__.py +86 -0
- coding_client/utils/archive_utils.py +387 -0
- coding_client/utils/ast_debug.py +45 -0
- coding_client/utils/async_timeout.py +364 -0
- coding_client/utils/constants.py +518 -0
- coding_client/utils/content_processing.py +326 -0
- coding_client/utils/database.py +916 -0
- coding_client/utils/encoding_helper.py +222 -0
- coding_client/utils/enum_helpers.py +166 -0
- coding_client/utils/file_operations.py +177 -0
- coding_client/utils/filesystem_ops.py +528 -0
- coding_client/utils/http_client.py +562 -0
- coding_client/utils/path_utils.py +471 -0
- coding_client/utils/profiling.py +421 -0
- coding_client/utils/response_builder.py +240 -0
- coding_client/utils/retry_helper.py +712 -0
- coding_client/utils/security_validation.py +423 -0
- coding_client-1.21.2.dist-info/METADATA +234 -0
- coding_client-1.21.2.dist-info/RECORD +109 -0
- coding_client-1.21.2.dist-info/WHEEL +5 -0
- coding_client-1.21.2.dist-info/licenses/LICENSE +201 -0
- coding_client-1.21.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
"""File system operations and management utilities.
|
|
2
|
+
|
|
3
|
+
This module provides core file system operations including reading, writing,
|
|
4
|
+
copying, moving, and deleting files and directories.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
import shutil
|
|
12
|
+
from collections.abc import AsyncIterator, Iterator
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import aiofiles
|
|
16
|
+
|
|
17
|
+
from .constants import CHUNK_SIZE, DEFAULT_ENCODING, MAX_FILE_SIZE
|
|
18
|
+
|
|
19
|
+
HAS_AIOFILES = True
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FileOperationError(Exception):
|
|
25
|
+
"""Base exception for file operations."""
|
|
26
|
+
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FileSizeError(FileOperationError):
|
|
31
|
+
"""Raised when file size exceeds limits."""
|
|
32
|
+
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FileNotFoundError(FileOperationError):
|
|
37
|
+
"""Raised when file is not found."""
|
|
38
|
+
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class FilePermissionError(FileOperationError):
|
|
43
|
+
"""Raised when file permissions prevent operation."""
|
|
44
|
+
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def read_file(
|
|
49
|
+
file_path: str | Path,
|
|
50
|
+
encoding: str | None = None,
|
|
51
|
+
max_size: int | None = MAX_FILE_SIZE,
|
|
52
|
+
) -> str:
|
|
53
|
+
"""Read entire file contents with size validation.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
file_path: Path to the file
|
|
57
|
+
encoding: File encoding (auto-detected if None)
|
|
58
|
+
max_size: Maximum allowed file size in bytes
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
File contents as string
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
FileNotFoundError: If file doesn't exist
|
|
65
|
+
FileSizeError: If file exceeds max_size
|
|
66
|
+
FileOperationError: For other errors
|
|
67
|
+
"""
|
|
68
|
+
from .content_processing import detect_encoding
|
|
69
|
+
from .security_validation import validate_file_access
|
|
70
|
+
|
|
71
|
+
path = Path(file_path)
|
|
72
|
+
validate_file_access(path, max_size)
|
|
73
|
+
|
|
74
|
+
# Auto-detect encoding if not provided
|
|
75
|
+
if encoding is None:
|
|
76
|
+
encoding = detect_encoding(path)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
with open(path, encoding=encoding) as f:
|
|
80
|
+
return f.read()
|
|
81
|
+
except PermissionError as e:
|
|
82
|
+
raise FilePermissionError(f"Permission denied: {path}") from e
|
|
83
|
+
except Exception as e:
|
|
84
|
+
raise FileOperationError(f"Error reading file {path}: {e}") from e
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def async_read_file(
|
|
88
|
+
file_path: str | Path,
|
|
89
|
+
encoding: str | None = None,
|
|
90
|
+
max_size: int | None = MAX_FILE_SIZE,
|
|
91
|
+
) -> str:
|
|
92
|
+
"""Asynchronously read entire file contents with size validation.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
file_path: Path to the file
|
|
96
|
+
encoding: File encoding (auto-detected if None)
|
|
97
|
+
max_size: Maximum allowed file size in bytes
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
File contents as string
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
FileNotFoundError: If file doesn't exist
|
|
104
|
+
FileSizeError: If file exceeds max_size
|
|
105
|
+
FileOperationError: For other errors
|
|
106
|
+
"""
|
|
107
|
+
from .content_processing import detect_encoding
|
|
108
|
+
from .security_validation import validate_file_access
|
|
109
|
+
|
|
110
|
+
path = Path(file_path)
|
|
111
|
+
await asyncio.to_thread(validate_file_access, path, max_size)
|
|
112
|
+
|
|
113
|
+
# Auto-detect encoding if not provided
|
|
114
|
+
if encoding is None:
|
|
115
|
+
encoding = await asyncio.to_thread(detect_encoding, path)
|
|
116
|
+
|
|
117
|
+
if not HAS_AIOFILES:
|
|
118
|
+
# Fallback to sync read in thread
|
|
119
|
+
return await asyncio.to_thread(read_file, path, encoding, max_size)
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
async with aiofiles.open(path, encoding=encoding) as f:
|
|
123
|
+
return await f.read()
|
|
124
|
+
except PermissionError as e:
|
|
125
|
+
raise FilePermissionError(f"Permission denied: {path}") from e
|
|
126
|
+
except Exception as e:
|
|
127
|
+
raise FileOperationError(f"Error reading file {path}: {e}") from e
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def read_file_lines(
|
|
131
|
+
file_path: str | Path,
|
|
132
|
+
encoding: str | None = None,
|
|
133
|
+
skip_empty: bool = False,
|
|
134
|
+
strip_lines: bool = True,
|
|
135
|
+
) -> list[str]:
|
|
136
|
+
"""Read file and return lines.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
file_path: Path to the file
|
|
140
|
+
encoding: File encoding (auto-detected if None)
|
|
141
|
+
skip_empty: Whether to skip empty lines
|
|
142
|
+
strip_lines: Whether to strip whitespace from lines
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
List of lines
|
|
146
|
+
"""
|
|
147
|
+
content = read_file(file_path, encoding)
|
|
148
|
+
lines = content.splitlines()
|
|
149
|
+
|
|
150
|
+
if strip_lines:
|
|
151
|
+
lines = [line.strip() for line in lines]
|
|
152
|
+
|
|
153
|
+
if skip_empty:
|
|
154
|
+
lines = [line for line in lines if line]
|
|
155
|
+
|
|
156
|
+
return lines
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def read_file_chunks(
|
|
160
|
+
file_path: str | Path,
|
|
161
|
+
chunk_size: int = CHUNK_SIZE,
|
|
162
|
+
encoding: str | None = None,
|
|
163
|
+
) -> Iterator[str]:
|
|
164
|
+
"""Read file in chunks for memory efficiency.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
file_path: Path to the file
|
|
168
|
+
chunk_size: Size of each chunk in bytes
|
|
169
|
+
encoding: File encoding (auto-detected if None)
|
|
170
|
+
|
|
171
|
+
Yields:
|
|
172
|
+
File chunks
|
|
173
|
+
"""
|
|
174
|
+
from .content_processing import detect_encoding
|
|
175
|
+
|
|
176
|
+
path = Path(file_path)
|
|
177
|
+
|
|
178
|
+
if not path.exists():
|
|
179
|
+
raise FileNotFoundError(f"File not found: {path}")
|
|
180
|
+
|
|
181
|
+
if encoding is None:
|
|
182
|
+
encoding = detect_encoding(path)
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
with open(path, encoding=encoding) as f:
|
|
186
|
+
while True:
|
|
187
|
+
chunk = f.read(chunk_size)
|
|
188
|
+
if not chunk:
|
|
189
|
+
break
|
|
190
|
+
yield chunk
|
|
191
|
+
except Exception as e:
|
|
192
|
+
raise FileOperationError(f"Error reading file {path}: {e}") from e
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
async def async_read_file_chunks(
|
|
196
|
+
file_path: str | Path,
|
|
197
|
+
chunk_size: int = CHUNK_SIZE,
|
|
198
|
+
encoding: str | None = None,
|
|
199
|
+
) -> AsyncIterator[str]:
|
|
200
|
+
"""Asynchronously read file in chunks for memory efficiency.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
file_path: Path to the file
|
|
204
|
+
chunk_size: Size of each chunk in bytes
|
|
205
|
+
encoding: File encoding (auto-detected if None)
|
|
206
|
+
|
|
207
|
+
Yields:
|
|
208
|
+
File chunks
|
|
209
|
+
"""
|
|
210
|
+
from .content_processing import detect_encoding
|
|
211
|
+
|
|
212
|
+
path = Path(file_path)
|
|
213
|
+
|
|
214
|
+
if not path.exists():
|
|
215
|
+
raise FileNotFoundError(f"File not found: {path}")
|
|
216
|
+
|
|
217
|
+
if encoding is None:
|
|
218
|
+
encoding = await asyncio.to_thread(detect_encoding, path)
|
|
219
|
+
|
|
220
|
+
if not HAS_AIOFILES:
|
|
221
|
+
# Fallback to sync read in thread
|
|
222
|
+
def sync_generator() -> list[str]:
|
|
223
|
+
chunks: list[str] = []
|
|
224
|
+
with open(path, encoding=encoding) as f:
|
|
225
|
+
while True:
|
|
226
|
+
c = f.read(chunk_size)
|
|
227
|
+
if not c:
|
|
228
|
+
break
|
|
229
|
+
chunks.append(c)
|
|
230
|
+
return chunks
|
|
231
|
+
|
|
232
|
+
for chunk in await asyncio.to_thread(sync_generator):
|
|
233
|
+
yield chunk
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
async with aiofiles.open(path, encoding=encoding) as f:
|
|
238
|
+
while True:
|
|
239
|
+
chunk = await f.read(chunk_size)
|
|
240
|
+
if not chunk:
|
|
241
|
+
break
|
|
242
|
+
yield chunk
|
|
243
|
+
except Exception as e:
|
|
244
|
+
raise FileOperationError(f"Error reading file {path}: {e}") from e
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def write_file(
|
|
248
|
+
file_path: str | Path,
|
|
249
|
+
content: str,
|
|
250
|
+
encoding: str = DEFAULT_ENCODING,
|
|
251
|
+
create_dirs: bool = True,
|
|
252
|
+
backup: bool = False,
|
|
253
|
+
) -> Path:
|
|
254
|
+
"""Write content to file with optional backup.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
file_path: Path to the file
|
|
258
|
+
content: Content to write
|
|
259
|
+
encoding: File encoding
|
|
260
|
+
create_dirs: Whether to create parent directories
|
|
261
|
+
backup: Whether to create backup of existing file
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Path to the written file
|
|
265
|
+
"""
|
|
266
|
+
from .archive_utils import create_backup
|
|
267
|
+
|
|
268
|
+
path = Path(file_path)
|
|
269
|
+
|
|
270
|
+
# Create parent directories if needed
|
|
271
|
+
if create_dirs:
|
|
272
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
273
|
+
|
|
274
|
+
# Create backup if requested and file exists
|
|
275
|
+
if backup and path.exists():
|
|
276
|
+
backup_path = create_backup(path)
|
|
277
|
+
logger.info(f"Created backup: {backup_path}")
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
with open(path, "w", encoding=encoding) as f:
|
|
281
|
+
f.write(content)
|
|
282
|
+
return path
|
|
283
|
+
except PermissionError as e:
|
|
284
|
+
raise FilePermissionError(f"Permission denied: {path}") from e
|
|
285
|
+
except Exception as e:
|
|
286
|
+
raise FileOperationError(f"Error writing file {path}: {e}") from e
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
async def async_write_file(
|
|
290
|
+
file_path: str | Path,
|
|
291
|
+
content: str,
|
|
292
|
+
encoding: str = DEFAULT_ENCODING,
|
|
293
|
+
create_dirs: bool = True,
|
|
294
|
+
backup: bool = False,
|
|
295
|
+
) -> Path:
|
|
296
|
+
"""Asynchronously write content to file with optional backup.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
file_path: Path to the file
|
|
300
|
+
content: Content to write
|
|
301
|
+
encoding: File encoding
|
|
302
|
+
create_dirs: Whether to create parent directories
|
|
303
|
+
backup: Whether to create backup of existing file
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
Path to the written file
|
|
307
|
+
"""
|
|
308
|
+
from .archive_utils import create_backup
|
|
309
|
+
|
|
310
|
+
path = Path(file_path)
|
|
311
|
+
|
|
312
|
+
# Create parent directories if needed
|
|
313
|
+
if create_dirs:
|
|
314
|
+
await asyncio.to_thread(path.parent.mkdir, parents=True, exist_ok=True)
|
|
315
|
+
|
|
316
|
+
# Create backup if requested and file exists
|
|
317
|
+
if backup and path.exists():
|
|
318
|
+
backup_path = await asyncio.to_thread(create_backup, path)
|
|
319
|
+
logger.info(f"Created backup: {backup_path}")
|
|
320
|
+
|
|
321
|
+
if not HAS_AIOFILES:
|
|
322
|
+
# Fallback to sync write in thread
|
|
323
|
+
return await asyncio.to_thread(write_file, path, content, encoding, create_dirs, backup)
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
async with aiofiles.open(path, "w", encoding=encoding) as f:
|
|
327
|
+
await f.write(content)
|
|
328
|
+
return path
|
|
329
|
+
except PermissionError as e:
|
|
330
|
+
raise FilePermissionError(f"Permission denied: {path}") from e
|
|
331
|
+
except Exception as e:
|
|
332
|
+
raise FileOperationError(f"Error writing file {path}: {e}") from e
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def append_to_file(
|
|
336
|
+
file_path: str | Path,
|
|
337
|
+
content: str,
|
|
338
|
+
encoding: str = DEFAULT_ENCODING,
|
|
339
|
+
create_if_missing: bool = True,
|
|
340
|
+
) -> Path:
|
|
341
|
+
"""Append content to file.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
file_path: Path to the file
|
|
345
|
+
content: Content to append
|
|
346
|
+
encoding: File encoding
|
|
347
|
+
create_if_missing: Whether to create file if it doesn't exist
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
Path to the file
|
|
351
|
+
"""
|
|
352
|
+
path = Path(file_path)
|
|
353
|
+
|
|
354
|
+
if not path.exists() and create_if_missing:
|
|
355
|
+
return write_file(path, content, encoding)
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
with open(path, "a", encoding=encoding) as f:
|
|
359
|
+
f.write(content)
|
|
360
|
+
return path
|
|
361
|
+
except Exception as e:
|
|
362
|
+
raise FileOperationError(f"Error appending to file {path}: {e}") from e
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def copy_file(
|
|
366
|
+
source: str | Path,
|
|
367
|
+
destination: str | Path,
|
|
368
|
+
create_dirs: bool = True,
|
|
369
|
+
overwrite: bool = True,
|
|
370
|
+
) -> Path:
|
|
371
|
+
"""Copy file to destination.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
source: Source file path
|
|
375
|
+
destination: Destination path
|
|
376
|
+
create_dirs: Whether to create parent directories
|
|
377
|
+
overwrite: Whether to overwrite existing file
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Path to the copied file
|
|
381
|
+
"""
|
|
382
|
+
src_path = Path(source)
|
|
383
|
+
dst_path = Path(destination)
|
|
384
|
+
|
|
385
|
+
if not src_path.is_file():
|
|
386
|
+
raise FileNotFoundError(f"Source file not found: {src_path}")
|
|
387
|
+
|
|
388
|
+
if dst_path.exists() and not overwrite:
|
|
389
|
+
raise FileOperationError(f"Destination already exists: {dst_path}")
|
|
390
|
+
|
|
391
|
+
if create_dirs:
|
|
392
|
+
dst_path.parent.mkdir(parents=True, exist_ok=True)
|
|
393
|
+
|
|
394
|
+
try:
|
|
395
|
+
shutil.copy2(src_path, dst_path)
|
|
396
|
+
return dst_path
|
|
397
|
+
except Exception as e:
|
|
398
|
+
raise FileOperationError(f"Error copying file: {e}") from e
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def move_file(
|
|
402
|
+
source: str | Path,
|
|
403
|
+
destination: str | Path,
|
|
404
|
+
create_dirs: bool = True,
|
|
405
|
+
) -> Path:
|
|
406
|
+
"""Move file to destination.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
source: Source file path
|
|
410
|
+
destination: Destination path
|
|
411
|
+
create_dirs: Whether to create parent directories
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
Path to the moved file
|
|
415
|
+
"""
|
|
416
|
+
src_path = Path(source)
|
|
417
|
+
dst_path = Path(destination)
|
|
418
|
+
|
|
419
|
+
if not src_path.exists():
|
|
420
|
+
raise FileNotFoundError(f"Source file not found: {src_path}")
|
|
421
|
+
|
|
422
|
+
if create_dirs:
|
|
423
|
+
dst_path.parent.mkdir(parents=True, exist_ok=True)
|
|
424
|
+
|
|
425
|
+
try:
|
|
426
|
+
shutil.move(str(src_path), str(dst_path))
|
|
427
|
+
return dst_path
|
|
428
|
+
except Exception as e:
|
|
429
|
+
raise FileOperationError(f"Error moving file: {e}") from e
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def delete_file(
|
|
433
|
+
file_path: str | Path,
|
|
434
|
+
missing_ok: bool = True,
|
|
435
|
+
) -> bool:
|
|
436
|
+
"""Delete file safely.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
file_path: Path to the file
|
|
440
|
+
missing_ok: Whether to ignore if file doesn't exist
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
True if file was deleted, False if it didn't exist
|
|
444
|
+
"""
|
|
445
|
+
path = Path(file_path)
|
|
446
|
+
|
|
447
|
+
try:
|
|
448
|
+
path.unlink()
|
|
449
|
+
return True
|
|
450
|
+
except FileNotFoundError:
|
|
451
|
+
if missing_ok:
|
|
452
|
+
return False
|
|
453
|
+
raise
|
|
454
|
+
except Exception as e:
|
|
455
|
+
raise FileOperationError(f"Error deleting file: {e}") from e
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def delete_directory(
|
|
459
|
+
dir_path: str | Path,
|
|
460
|
+
recursive: bool = True,
|
|
461
|
+
missing_ok: bool = True,
|
|
462
|
+
) -> bool:
|
|
463
|
+
"""Delete directory safely.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
dir_path: Path to the directory
|
|
467
|
+
recursive: Whether to delete recursively
|
|
468
|
+
missing_ok: Whether to ignore if directory doesn't exist
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
True if directory was deleted, False if it didn't exist
|
|
472
|
+
"""
|
|
473
|
+
path = Path(dir_path)
|
|
474
|
+
|
|
475
|
+
if not path.exists():
|
|
476
|
+
if missing_ok:
|
|
477
|
+
return False
|
|
478
|
+
else:
|
|
479
|
+
raise FileNotFoundError(f"Directory not found: {path}")
|
|
480
|
+
|
|
481
|
+
try:
|
|
482
|
+
if recursive:
|
|
483
|
+
shutil.rmtree(path)
|
|
484
|
+
else:
|
|
485
|
+
path.rmdir()
|
|
486
|
+
return True
|
|
487
|
+
except Exception as e:
|
|
488
|
+
raise FileOperationError(f"Error deleting directory: {e}") from e
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def ensure_directory(path: str | Path) -> Path:
|
|
492
|
+
"""Ensure directory exists, creating if necessary.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
path: Directory path
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
Path object
|
|
499
|
+
"""
|
|
500
|
+
dir_path = Path(path)
|
|
501
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
502
|
+
return dir_path
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
# Export all public functions
|
|
506
|
+
__all__ = [
|
|
507
|
+
# Exceptions
|
|
508
|
+
"FileOperationError",
|
|
509
|
+
"FileSizeError",
|
|
510
|
+
"FileNotFoundError",
|
|
511
|
+
"FilePermissionError",
|
|
512
|
+
# Reading
|
|
513
|
+
"read_file",
|
|
514
|
+
"async_read_file",
|
|
515
|
+
"read_file_lines",
|
|
516
|
+
"read_file_chunks",
|
|
517
|
+
"async_read_file_chunks",
|
|
518
|
+
# Writing
|
|
519
|
+
"write_file",
|
|
520
|
+
"async_write_file",
|
|
521
|
+
"append_to_file",
|
|
522
|
+
# File operations
|
|
523
|
+
"copy_file",
|
|
524
|
+
"move_file",
|
|
525
|
+
"delete_file",
|
|
526
|
+
"delete_directory",
|
|
527
|
+
"ensure_directory",
|
|
528
|
+
]
|