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.
Files changed (109) hide show
  1. coding_client/__init__.py +1 -0
  2. coding_client/api/__init__.py +19 -0
  3. coding_client/api/exception_handlers.py +158 -0
  4. coding_client/api/logging_setup.py +44 -0
  5. coding_client/api/metrics.py +40 -0
  6. coding_client/api/openapi_config.py +124 -0
  7. coding_client/api/request_logging.py +60 -0
  8. coding_client/api/validation.py +150 -0
  9. coding_client/auth/__init__.py +78 -0
  10. coding_client/auth/_default_tenant.py +132 -0
  11. coding_client/auth/_header_trust.py +98 -0
  12. coding_client/auth/_onbehalf.py +315 -0
  13. coding_client/auth/_trusted_networks.py +94 -0
  14. coding_client/auth/dependencies.py +61 -0
  15. coding_client/auth/middleware.py +445 -0
  16. coding_client/auth/models.py +83 -0
  17. coding_client/auth/outbound.py +137 -0
  18. coding_client/auth/rbac.py +21 -0
  19. coding_client/auth/rbac_coding.py +5 -0
  20. coding_client/auth/tenant.py +650 -0
  21. coding_client/auth/websocket.py +148 -0
  22. coding_client/clients/__init__.py +24 -0
  23. coding_client/clients/base.py +131 -0
  24. coding_client/clients/coding_ai.py +33 -0
  25. coding_client/clients/coding_analysis.py +36 -0
  26. coding_client/clients/coding_containers.py +45 -0
  27. coding_client/clients/coding_github.py +127 -0
  28. coding_client/clients/coding_realtime.py +127 -0
  29. coding_client/clients/coding_security.py +51 -0
  30. coding_client/clients/coding_testing.py +31 -0
  31. coding_client/clients/coding_workspaces.py +265 -0
  32. coding_client/config/__init__.py +12 -0
  33. coding_client/config/base_settings.py +61 -0
  34. coding_client/config/yaml_config_loader.py +172 -0
  35. coding_client/db/__init__.py +42 -0
  36. coding_client/db/db_types.py +122 -0
  37. coding_client/db/exceptions.py +59 -0
  38. coding_client/db/postgres_connection.py +572 -0
  39. coding_client/db/tenant_query_helper.py +417 -0
  40. coding_client/db_init.py +49 -0
  41. coding_client/exceptions/__init__.py +67 -0
  42. coding_client/exceptions/api_exceptions.py +395 -0
  43. coding_client/exceptions/pydantic_ai_exceptions.py +533 -0
  44. coding_client/interfaces/__init__.py +22 -0
  45. coding_client/interfaces/cache_interface.py +179 -0
  46. coding_client/interfaces/container_interface.py +49 -0
  47. coding_client/interfaces/docker_interface.py +283 -0
  48. coding_client/interfaces/graph_interface.py +28 -0
  49. coding_client/interfaces/security_interface.py +72 -0
  50. coding_client/interfaces/webhook_interface.py +67 -0
  51. coding_client/llm/__init__.py +54 -0
  52. coding_client/llm/base_agent.py +195 -0
  53. coding_client/llm/context.py +121 -0
  54. coding_client/llm/responses.py +102 -0
  55. coding_client/llm/routing.py +82 -0
  56. coding_client/llm/settings.py +102 -0
  57. coding_client/models/__init__.py +62 -0
  58. coding_client/models/agent.py +38 -0
  59. coding_client/models/ai_test_failure.py +745 -0
  60. coding_client/models/analysis.py +330 -0
  61. coding_client/models/architecture.py +685 -0
  62. coding_client/models/bug.py +128 -0
  63. coding_client/models/code_graph.py +317 -0
  64. coding_client/models/container.py +241 -0
  65. coding_client/models/core.py +878 -0
  66. coding_client/models/docker_container.py +104 -0
  67. coding_client/models/github.py +293 -0
  68. coding_client/models/logging.py +29 -0
  69. coding_client/models/package_manager.py +257 -0
  70. coding_client/models/pr_validation.py +440 -0
  71. coding_client/models/pydantic_ai.py +395 -0
  72. coding_client/models/repo_scanner.py +249 -0
  73. coding_client/models/responses.py +431 -0
  74. coding_client/models/security.py +185 -0
  75. coding_client/models/static_analysis.py +508 -0
  76. coding_client/models/system.py +48 -0
  77. coding_client/models/test_executor.py +204 -0
  78. coding_client/models/testing.py +673 -0
  79. coding_client/models/testing_config.py +200 -0
  80. coding_client/models/validation.py +127 -0
  81. coding_client/models_init.py +272 -0
  82. coding_client/telemetry.py +195 -0
  83. coding_client/testing.py +180 -0
  84. coding_client/types/__init__.py +130 -0
  85. coding_client/types/constraint_types.py +276 -0
  86. coding_client/types/core_types.py +112 -0
  87. coding_client/types/state_machine.py +443 -0
  88. coding_client/utils/__init__.py +86 -0
  89. coding_client/utils/archive_utils.py +387 -0
  90. coding_client/utils/ast_debug.py +45 -0
  91. coding_client/utils/async_timeout.py +364 -0
  92. coding_client/utils/constants.py +518 -0
  93. coding_client/utils/content_processing.py +326 -0
  94. coding_client/utils/database.py +916 -0
  95. coding_client/utils/encoding_helper.py +222 -0
  96. coding_client/utils/enum_helpers.py +166 -0
  97. coding_client/utils/file_operations.py +177 -0
  98. coding_client/utils/filesystem_ops.py +528 -0
  99. coding_client/utils/http_client.py +562 -0
  100. coding_client/utils/path_utils.py +471 -0
  101. coding_client/utils/profiling.py +421 -0
  102. coding_client/utils/response_builder.py +240 -0
  103. coding_client/utils/retry_helper.py +712 -0
  104. coding_client/utils/security_validation.py +423 -0
  105. coding_client-1.21.2.dist-info/METADATA +234 -0
  106. coding_client-1.21.2.dist-info/RECORD +109 -0
  107. coding_client-1.21.2.dist-info/WHEEL +5 -0
  108. coding_client-1.21.2.dist-info/licenses/LICENSE +201 -0
  109. 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
+ ]