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,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
|
+
]
|