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,471 @@
1
+ """Path validation and manipulation utilities.
2
+
3
+ This module provides utilities for path validation, normalization,
4
+ pattern matching, and directory operations.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from pathlib import Path
11
+
12
+ from .constants import FROZEN_IGNORE_PATTERNS
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class PathValidationError(Exception):
18
+ """Raised when path validation fails."""
19
+
20
+ pass
21
+
22
+
23
+ def validate_path(
24
+ path: str | Path,
25
+ must_exist: bool = False,
26
+ must_be_file: bool = False,
27
+ must_be_dir: bool = False,
28
+ ) -> Path:
29
+ """Validate and normalize a path.
30
+
31
+ Args:
32
+ path: Path to validate
33
+ must_exist: Whether the path must exist
34
+ must_be_file: Whether the path must be a file
35
+ must_be_dir: Whether the path must be a directory
36
+
37
+ Returns:
38
+ Normalized Path object
39
+
40
+ Raises:
41
+ PathValidationError: If validation fails
42
+ """
43
+ try:
44
+ normalized_path = Path(path).resolve()
45
+ except Exception as e:
46
+ raise PathValidationError(f"Invalid path: {path}") from e
47
+
48
+ if must_exist and not normalized_path.exists():
49
+ raise PathValidationError(f"Path does not exist: {normalized_path}")
50
+
51
+ if must_be_file and normalized_path.exists() and not normalized_path.is_file():
52
+ raise PathValidationError(f"Path is not a file: {normalized_path}")
53
+
54
+ if must_be_dir and normalized_path.exists() and not normalized_path.is_dir():
55
+ raise PathValidationError(f"Path is not a directory: {normalized_path}")
56
+
57
+ return normalized_path
58
+
59
+
60
+ def normalize_path(path: str | Path) -> Path:
61
+ """Normalize a path by resolving symlinks and relative components.
62
+
63
+ Args:
64
+ path: Path to normalize
65
+
66
+ Returns:
67
+ Normalized Path object
68
+ """
69
+ return Path(path).resolve()
70
+
71
+
72
+ def is_safe_path(path: str | Path, base_path: str | Path) -> bool:
73
+ """Check if a path is safe (doesn't escape base directory).
74
+
75
+ Args:
76
+ path: Path to check
77
+ base_path: Base directory path
78
+
79
+ Returns:
80
+ True if path is safe
81
+ """
82
+ try:
83
+ resolved_path = Path(path).resolve()
84
+ resolved_base = Path(base_path).resolve()
85
+
86
+ # Check if the path is within the base directory
87
+ return resolved_path.is_relative_to(resolved_base)
88
+ except Exception:
89
+ return False
90
+
91
+
92
+ def get_relative_path(path: str | Path, base: str | Path) -> Path:
93
+ """Get relative path from base to target.
94
+
95
+ Args:
96
+ path: Target path
97
+ base: Base path
98
+
99
+ Returns:
100
+ Relative path
101
+ """
102
+ return Path(path).relative_to(base)
103
+
104
+
105
+ def join_paths(*paths: str | Path) -> Path:
106
+ """Join multiple path components safely.
107
+
108
+ Args:
109
+ paths: Path components to join
110
+
111
+ Returns:
112
+ Joined path
113
+ """
114
+ if not paths:
115
+ return Path()
116
+
117
+ result = Path(paths[0])
118
+ for path in paths[1:]:
119
+ result = result / path
120
+
121
+ return result
122
+
123
+
124
+ def list_files(
125
+ directory: str | Path,
126
+ pattern: str = "*",
127
+ recursive: bool = False,
128
+ include_hidden: bool = False,
129
+ ignore_patterns: set[str] | frozenset[str] | None = None,
130
+ ) -> list[Path]:
131
+ """List files in directory with filtering.
132
+
133
+ Args:
134
+ directory: Directory path
135
+ pattern: File pattern (glob)
136
+ recursive: Whether to search recursively
137
+ include_hidden: Whether to include hidden files
138
+ ignore_patterns: Patterns to ignore
139
+
140
+ Returns:
141
+ List of file paths
142
+ """
143
+ from .filesystem_ops import FileOperationError
144
+
145
+ dir_path = Path(directory)
146
+ if not dir_path.is_dir():
147
+ raise FileOperationError(f"Not a directory: {dir_path}")
148
+
149
+ if ignore_patterns is None:
150
+ ignore_patterns = FROZEN_IGNORE_PATTERNS
151
+
152
+ glob_method = dir_path.rglob if recursive else dir_path.glob
153
+ files = []
154
+
155
+ for path in glob_method(pattern):
156
+ if not path.is_file():
157
+ continue
158
+
159
+ # Skip hidden files if requested
160
+ if not include_hidden and any(part.startswith(".") for part in path.parts):
161
+ continue
162
+
163
+ # Skip ignored patterns
164
+ if should_ignore_path(path, ignore_patterns):
165
+ continue
166
+
167
+ files.append(path)
168
+
169
+ return sorted(files)
170
+
171
+
172
+ def list_directories(
173
+ directory: str | Path,
174
+ pattern: str = "*",
175
+ recursive: bool = False,
176
+ include_hidden: bool = False,
177
+ ignore_patterns: set[str] | frozenset[str] | None = None,
178
+ ) -> list[Path]:
179
+ """List directories in directory with filtering.
180
+
181
+ Args:
182
+ directory: Directory path
183
+ pattern: Directory pattern (glob)
184
+ recursive: Whether to search recursively
185
+ include_hidden: Whether to include hidden directories
186
+ ignore_patterns: Patterns to ignore
187
+
188
+ Returns:
189
+ List of directory paths
190
+ """
191
+ from .filesystem_ops import FileOperationError
192
+
193
+ dir_path = Path(directory)
194
+ if not dir_path.is_dir():
195
+ raise FileOperationError(f"Not a directory: {dir_path}")
196
+
197
+ if ignore_patterns is None:
198
+ ignore_patterns = FROZEN_IGNORE_PATTERNS
199
+
200
+ glob_method = dir_path.rglob if recursive else dir_path.glob
201
+ directories = []
202
+
203
+ for path in glob_method(pattern):
204
+ if not path.is_dir():
205
+ continue
206
+
207
+ # Skip hidden directories if requested
208
+ if not include_hidden and any(part.startswith(".") for part in path.parts):
209
+ continue
210
+
211
+ # Skip ignored patterns
212
+ if should_ignore_path(path, ignore_patterns):
213
+ continue
214
+
215
+ directories.append(path)
216
+
217
+ return sorted(directories)
218
+
219
+
220
+ def should_ignore_path(path: Path, ignore_patterns: set[str] | frozenset[str]) -> bool:
221
+ """Check if path should be ignored based on patterns.
222
+
223
+ Args:
224
+ path: Path to check
225
+ ignore_patterns: Set of patterns to ignore
226
+
227
+ Returns:
228
+ True if path should be ignored
229
+ """
230
+ path_str = str(path)
231
+
232
+ for pattern in ignore_patterns:
233
+ # Direct name match
234
+ if path.name == pattern:
235
+ return True
236
+
237
+ # Pattern match (e.g., *.pyc)
238
+ if pattern.startswith("*") and path.name.endswith(pattern[1:]):
239
+ return True
240
+
241
+ # Path contains pattern
242
+ if pattern in path_str:
243
+ return True
244
+
245
+ return False
246
+
247
+
248
+ def find_files_by_extension(
249
+ directory: str | Path,
250
+ extensions: str | list[str],
251
+ recursive: bool = True,
252
+ ) -> list[Path]:
253
+ """Find files by extension(s).
254
+
255
+ Args:
256
+ directory: Directory to search
257
+ extensions: File extension(s) to match (with or without dot)
258
+ recursive: Whether to search recursively
259
+
260
+ Returns:
261
+ List of matching files
262
+ """
263
+ if isinstance(extensions, str):
264
+ extensions = [extensions]
265
+
266
+ # Normalize extensions (ensure they start with dot)
267
+ normalized_exts = []
268
+ for ext in extensions:
269
+ if not ext.startswith("."):
270
+ ext = "." + ext
271
+ normalized_exts.append(ext.lower())
272
+
273
+ files = []
274
+ for ext in normalized_exts:
275
+ pattern = f"*{ext}"
276
+ files.extend(list_files(directory, pattern, recursive))
277
+
278
+ return sorted(set(files)) # Remove duplicates and sort
279
+
280
+
281
+ def find_files_by_name(
282
+ directory: str | Path,
283
+ names: str | list[str],
284
+ recursive: bool = True,
285
+ case_sensitive: bool = True,
286
+ ) -> list[Path]:
287
+ """Find files by exact name(s).
288
+
289
+ Args:
290
+ directory: Directory to search
291
+ names: File name(s) to match
292
+ recursive: Whether to search recursively
293
+ case_sensitive: Whether matching is case sensitive
294
+
295
+ Returns:
296
+ List of matching files
297
+ """
298
+ if isinstance(names, str):
299
+ names = [names]
300
+
301
+ if not case_sensitive:
302
+ names = [name.lower() for name in names]
303
+
304
+ all_files = list_files(directory, "*", recursive)
305
+ matching_files = []
306
+
307
+ for file_path in all_files:
308
+ file_name = file_path.name
309
+ if not case_sensitive:
310
+ file_name = file_name.lower()
311
+
312
+ if file_name in names:
313
+ matching_files.append(file_path)
314
+
315
+ return sorted(matching_files)
316
+
317
+
318
+ def find_files_containing(
319
+ directory: str | Path,
320
+ text: str,
321
+ file_pattern: str = "*",
322
+ recursive: bool = True,
323
+ case_sensitive: bool = True,
324
+ ) -> list[Path]:
325
+ """Find files containing specific text in their names.
326
+
327
+ Args:
328
+ directory: Directory to search
329
+ text: Text to search for in filenames
330
+ file_pattern: File pattern to match
331
+ recursive: Whether to search recursively
332
+ case_sensitive: Whether search is case sensitive
333
+
334
+ Returns:
335
+ List of matching files
336
+ """
337
+ search_text = text if case_sensitive else text.lower()
338
+ all_files = list_files(directory, file_pattern, recursive)
339
+ matching_files = []
340
+
341
+ for file_path in all_files:
342
+ file_name = file_path.name
343
+ if not case_sensitive:
344
+ file_name = file_name.lower()
345
+
346
+ if search_text in file_name:
347
+ matching_files.append(file_path)
348
+
349
+ return sorted(matching_files)
350
+
351
+
352
+ def get_common_path(*paths: str | Path) -> Path:
353
+ """Get common parent path of multiple paths.
354
+
355
+ Args:
356
+ paths: Paths to find common parent for
357
+
358
+ Returns:
359
+ Common parent path
360
+ """
361
+ if not paths:
362
+ return Path()
363
+
364
+ path_objects = [Path(p) for p in paths]
365
+
366
+ # Find common parts
367
+ common_parts = []
368
+ if path_objects:
369
+ # Get the minimum number of parts
370
+ min_parts = min(len(p.parts) for p in path_objects)
371
+
372
+ for i in range(min_parts):
373
+ part = path_objects[0].parts[i]
374
+ if all(p.parts[i] == part for p in path_objects):
375
+ common_parts.append(part)
376
+ else:
377
+ break
378
+
379
+ return Path(*common_parts) if common_parts else Path()
380
+
381
+
382
+ def get_directory_size(directory: str | Path) -> int:
383
+ """Get total size of directory and all its contents.
384
+
385
+ Args:
386
+ directory: Directory path
387
+
388
+ Returns:
389
+ Total size in bytes
390
+ """
391
+ dir_path = Path(directory)
392
+ if not dir_path.is_dir():
393
+ raise PathValidationError(f"Not a directory: {dir_path}")
394
+
395
+ total_size = 0
396
+ for file_path in dir_path.rglob("*"):
397
+ if file_path.is_file():
398
+ try:
399
+ total_size += file_path.stat().st_size
400
+ except (OSError, FileNotFoundError):
401
+ # Skip files that can't be accessed
402
+ continue
403
+
404
+ return total_size
405
+
406
+
407
+ def is_empty_directory(directory: str | Path) -> bool:
408
+ """Check if directory is empty.
409
+
410
+ Args:
411
+ directory: Directory path
412
+
413
+ Returns:
414
+ True if directory is empty
415
+ """
416
+ dir_path = Path(directory)
417
+ if not dir_path.is_dir():
418
+ raise PathValidationError(f"Not a directory: {dir_path}")
419
+
420
+ try:
421
+ next(dir_path.iterdir())
422
+ return False
423
+ except StopIteration:
424
+ return True
425
+
426
+
427
+ def create_directory_tree(base_path: str | Path, structure: dict) -> None:
428
+ """Create directory tree from nested dictionary structure.
429
+
430
+ Args:
431
+ base_path: Base directory path
432
+ structure: Nested dict representing directory structure
433
+ {'dir1': {'subdir1': {}, 'subdir2': {}}, 'dir2': {}}
434
+ """
435
+ base = Path(base_path)
436
+
437
+ def _create_dirs(current_path: Path, struct: dict):
438
+ for name, subdirs in struct.items():
439
+ dir_path = current_path / name
440
+ dir_path.mkdir(exist_ok=True)
441
+ if isinstance(subdirs, dict):
442
+ _create_dirs(dir_path, subdirs)
443
+
444
+ _create_dirs(base, structure)
445
+
446
+
447
+ # Export all public functions
448
+ __all__ = [
449
+ # Exceptions
450
+ "PathValidationError",
451
+ # Validation
452
+ "validate_path",
453
+ "normalize_path",
454
+ "is_safe_path",
455
+ # Path operations
456
+ "get_relative_path",
457
+ "join_paths",
458
+ "get_common_path",
459
+ # Directory listing
460
+ "list_files",
461
+ "list_directories",
462
+ "should_ignore_path",
463
+ # File finding
464
+ "find_files_by_extension",
465
+ "find_files_by_name",
466
+ "find_files_containing",
467
+ # Directory utilities
468
+ "get_directory_size",
469
+ "is_empty_directory",
470
+ "create_directory_tree",
471
+ ]