path-link 0.2.0__py3-none-any.whl → 0.3.0__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.
- {project_paths → path_link}/__init__.py +21 -15
- {project_paths → path_link}/builder.py +63 -1
- path_link/builtin_validators/__init__.py +6 -0
- {project_paths → path_link}/builtin_validators/sandbox.py +2 -2
- {project_paths → path_link}/builtin_validators/strict.py +2 -2
- {project_paths → path_link}/cli.py +161 -5
- {project_paths → path_link}/docs/ai_guidelines.md +34 -34
- {project_paths → path_link}/docs/developer_guide.md +19 -19
- {project_paths → path_link}/docs/metadata.json +6 -6
- {project_paths → path_link}/get_paths.py +3 -3
- {project_paths → path_link}/model.py +1 -1
- {project_paths → path_link}/project_paths_static.py +2 -2
- path_link/url_factory.py +225 -0
- path_link/url_model.py +151 -0
- path_link/url_static.py +128 -0
- {path_link-0.2.0.dist-info → path_link-0.3.0.dist-info}/METADATA +224 -51
- path_link-0.3.0.dist-info/RECORD +23 -0
- path_link-0.3.0.dist-info/entry_points.txt +2 -0
- path_link-0.3.0.dist-info/top_level.txt +1 -0
- path_link-0.2.0.dist-info/RECORD +0 -20
- path_link-0.2.0.dist-info/entry_points.txt +0 -2
- path_link-0.2.0.dist-info/top_level.txt +0 -1
- project_paths/builtin_validators/__init__.py +0 -6
- {project_paths → path_link}/main.py +0 -0
- {project_paths → path_link}/validation.py +0 -0
- {path_link-0.2.0.dist-info → path_link-0.3.0.dist-info}/WHEEL +0 -0
- {path_link-0.2.0.dist-info → path_link-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,12 +1,12 @@
|
|
1
1
|
"""
|
2
|
-
|
2
|
+
path-link: Type-safe path configuration for Python projects
|
3
3
|
|
4
4
|
A powerful library for managing project paths with built-in validation,
|
5
5
|
static model generation, and comprehensive security features.
|
6
6
|
|
7
7
|
Quick Start
|
8
8
|
-----------
|
9
|
-
>>> from
|
9
|
+
>>> from path_link import ProjectPaths
|
10
10
|
>>>
|
11
11
|
>>> # Load paths from pyproject.toml
|
12
12
|
>>> paths = ProjectPaths.from_pyproject()
|
@@ -28,7 +28,7 @@ Documentation Access (Offline)
|
|
28
28
|
-------------------------------
|
29
29
|
Access comprehensive documentation programmatically, even in airgapped environments:
|
30
30
|
|
31
|
-
>>> from
|
31
|
+
>>> from path_link import get_ai_guidelines, get_developer_guide, get_metadata
|
32
32
|
>>> import json
|
33
33
|
>>>
|
34
34
|
>>> # Get AI assistant usage patterns and best practices
|
@@ -45,7 +45,7 @@ Access comprehensive documentation programmatically, even in airgapped environme
|
|
45
45
|
|
46
46
|
Validation Example
|
47
47
|
------------------
|
48
|
-
>>> from
|
48
|
+
>>> from path_link import validate_or_raise, StrictPathValidator
|
49
49
|
>>>
|
50
50
|
>>> paths = ProjectPaths.from_pyproject()
|
51
51
|
>>> validator = StrictPathValidator(
|
@@ -75,14 +75,14 @@ See Also
|
|
75
75
|
- AI guidelines: get_ai_guidelines()
|
76
76
|
- Developer guide: get_developer_guide()
|
77
77
|
- Package metadata: get_metadata()
|
78
|
-
- GitHub: https://github.com/yourusername/
|
78
|
+
- GitHub: https://github.com/yourusername/path-link
|
79
79
|
"""
|
80
80
|
|
81
81
|
from importlib.resources import files
|
82
82
|
|
83
|
-
from .model import ProjectPaths
|
84
|
-
from .get_paths import write_dataclass_file
|
85
|
-
from .validation import (
|
83
|
+
from path_link.model import ProjectPaths
|
84
|
+
from path_link.get_paths import write_dataclass_file
|
85
|
+
from path_link.validation import (
|
86
86
|
Severity,
|
87
87
|
Finding,
|
88
88
|
ValidationResult,
|
@@ -91,15 +91,18 @@ from .validation import (
|
|
91
91
|
validate_or_raise,
|
92
92
|
CompositeValidator,
|
93
93
|
)
|
94
|
-
from .builtin_validators.strict import StrictPathValidator
|
95
|
-
from .builtin_validators.sandbox import SandboxPathValidator
|
94
|
+
from path_link.builtin_validators.strict import StrictPathValidator
|
95
|
+
from path_link.builtin_validators.sandbox import SandboxPathValidator
|
96
|
+
from path_link.url_factory import ProjectUrls
|
97
|
+
from path_link.url_model import ValidationMode
|
98
|
+
from path_link.url_static import write_url_dataclass_file
|
96
99
|
|
97
100
|
|
98
101
|
def get_ai_guidelines() -> str:
|
99
102
|
"""
|
100
103
|
Return AI assistant guidelines for working with this package.
|
101
104
|
|
102
|
-
This provides comprehensive guidance for AI agents helping users with
|
105
|
+
This provides comprehensive guidance for AI agents helping users with path-link,
|
103
106
|
including usage patterns, critical rules, validation patterns, and troubleshooting.
|
104
107
|
|
105
108
|
Returns:
|
@@ -109,7 +112,7 @@ def get_ai_guidelines() -> str:
|
|
109
112
|
>>> guidelines = get_ai_guidelines()
|
110
113
|
>>> print(guidelines[:100])
|
111
114
|
"""
|
112
|
-
return files("
|
115
|
+
return files("path_link.docs").joinpath("ai_guidelines.md").read_text()
|
113
116
|
|
114
117
|
|
115
118
|
def get_developer_guide() -> str:
|
@@ -117,7 +120,7 @@ def get_developer_guide() -> str:
|
|
117
120
|
Return developer guide for contributing to this package.
|
118
121
|
|
119
122
|
This provides architecture details, development setup, testing patterns,
|
120
|
-
and contribution guidelines for developers working on
|
123
|
+
and contribution guidelines for developers working on path-link.
|
121
124
|
|
122
125
|
Returns:
|
123
126
|
str: Full content of developer guide (formerly CLAUDE.md)
|
@@ -126,7 +129,7 @@ def get_developer_guide() -> str:
|
|
126
129
|
>>> guide = get_developer_guide()
|
127
130
|
>>> print(guide[:100])
|
128
131
|
"""
|
129
|
-
return files("
|
132
|
+
return files("path_link.docs").joinpath("developer_guide.md").read_text()
|
130
133
|
|
131
134
|
|
132
135
|
def get_metadata() -> str:
|
@@ -144,7 +147,7 @@ def get_metadata() -> str:
|
|
144
147
|
>>> metadata = json.loads(get_metadata())
|
145
148
|
>>> print(metadata['version'])
|
146
149
|
"""
|
147
|
-
return files("
|
150
|
+
return files("path_link.docs").joinpath("metadata.json").read_text()
|
148
151
|
|
149
152
|
|
150
153
|
__all__ = [
|
@@ -159,6 +162,9 @@ __all__ = [
|
|
159
162
|
"CompositeValidator",
|
160
163
|
"StrictPathValidator",
|
161
164
|
"SandboxPathValidator",
|
165
|
+
"ProjectUrls",
|
166
|
+
"ValidationMode",
|
167
|
+
"write_url_dataclass_file",
|
162
168
|
"get_ai_guidelines",
|
163
169
|
"get_developer_guide",
|
164
170
|
"get_metadata",
|
@@ -17,7 +17,7 @@ def get_paths_from_pyproject() -> Dict[str, str]:
|
|
17
17
|
with pyproject_path.open("rb") as f:
|
18
18
|
pyproject_data = toml_load(f)
|
19
19
|
|
20
|
-
tool_config = pyproject_data.get("tool", {}).get("
|
20
|
+
tool_config = pyproject_data.get("tool", {}).get("path_link", {})
|
21
21
|
if not tool_config:
|
22
22
|
return {}
|
23
23
|
|
@@ -82,3 +82,65 @@ def build_field_definitions(
|
|
82
82
|
)
|
83
83
|
|
84
84
|
return fields
|
85
|
+
|
86
|
+
|
87
|
+
def get_urls_from_pyproject() -> Dict[str, str]:
|
88
|
+
"""Load URL variables from pyproject.toml [tool.path_link.urls] section."""
|
89
|
+
pyproject_path = Path.cwd() / "pyproject.toml"
|
90
|
+
if not pyproject_path.is_file():
|
91
|
+
return {}
|
92
|
+
|
93
|
+
with pyproject_path.open("rb") as f:
|
94
|
+
pyproject_data = toml_load(f)
|
95
|
+
|
96
|
+
tool_config = pyproject_data.get("tool", {}).get("path_link", {})
|
97
|
+
if not tool_config:
|
98
|
+
return {}
|
99
|
+
|
100
|
+
urls = tool_config.get("urls", {})
|
101
|
+
|
102
|
+
if not isinstance(urls, dict):
|
103
|
+
raise TypeError("`urls` must be a table in pyproject.toml")
|
104
|
+
|
105
|
+
return urls
|
106
|
+
|
107
|
+
|
108
|
+
def get_urls_from_dot_urls(path_to_config: Path) -> Dict[str, str]:
|
109
|
+
"""Load URL variables from a .urls file (dotenv format)."""
|
110
|
+
if not path_to_config.is_file():
|
111
|
+
raise FileNotFoundError(f"Configuration file not found: {path_to_config}")
|
112
|
+
|
113
|
+
values = dotenv_values(path_to_config)
|
114
|
+
|
115
|
+
# Filter out None values which can occur with empty lines
|
116
|
+
return {k: v for k, v in values.items() if v is not None}
|
117
|
+
|
118
|
+
|
119
|
+
def get_urls_merged(dotenv_path: Optional[Path] = None) -> Dict[str, str]:
|
120
|
+
"""
|
121
|
+
Load URLs from both pyproject.toml and .urls file with defined precedence.
|
122
|
+
|
123
|
+
Precedence: pyproject.toml > .urls file
|
124
|
+
Missing keys are merged; duplicates resolved by pyproject.toml.
|
125
|
+
|
126
|
+
Args:
|
127
|
+
dotenv_path: Optional path to .urls file (default: ./.urls)
|
128
|
+
|
129
|
+
Returns:
|
130
|
+
Merged dictionary of URL key-value pairs
|
131
|
+
"""
|
132
|
+
# Start with dotenv values (lower priority)
|
133
|
+
urls = {}
|
134
|
+
|
135
|
+
# Load from .urls file if it exists
|
136
|
+
if dotenv_path is None:
|
137
|
+
dotenv_path = Path.cwd() / ".urls"
|
138
|
+
|
139
|
+
if dotenv_path.is_file():
|
140
|
+
urls = get_urls_from_dot_urls(dotenv_path)
|
141
|
+
|
142
|
+
# Override with pyproject.toml values (higher priority)
|
143
|
+
pyproject_urls = get_urls_from_pyproject()
|
144
|
+
urls.update(pyproject_urls)
|
145
|
+
|
146
|
+
return urls
|
@@ -3,10 +3,10 @@ from dataclasses import dataclass
|
|
3
3
|
from pathlib import Path
|
4
4
|
from typing import Iterable, TYPE_CHECKING
|
5
5
|
|
6
|
-
from
|
6
|
+
from path_link.validation import Finding, Severity, ValidationResult
|
7
7
|
|
8
8
|
if TYPE_CHECKING:
|
9
|
-
from
|
9
|
+
from path_link.model import _ProjectPathsBase
|
10
10
|
|
11
11
|
|
12
12
|
@dataclass
|
@@ -3,10 +3,10 @@ from dataclasses import dataclass
|
|
3
3
|
from pathlib import Path
|
4
4
|
from typing import Iterable, TYPE_CHECKING
|
5
5
|
|
6
|
-
from
|
6
|
+
from path_link.validation import Finding, Severity, ValidationResult
|
7
7
|
|
8
8
|
if TYPE_CHECKING:
|
9
|
-
from
|
9
|
+
from path_link.model import _ProjectPathsBase
|
10
10
|
|
11
11
|
|
12
12
|
@dataclass
|
@@ -1,4 +1,4 @@
|
|
1
|
-
"""Command-line interface for
|
1
|
+
"""Command-line interface for path-link.
|
2
2
|
|
3
3
|
Provides three commands:
|
4
4
|
1. print - Print resolved paths as JSON
|
@@ -12,9 +12,12 @@ import sys
|
|
12
12
|
from pathlib import Path
|
13
13
|
from typing import NoReturn
|
14
14
|
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from
|
15
|
+
from path_link import ProjectPaths, write_dataclass_file, validate_or_raise
|
16
|
+
from path_link.builtin_validators import StrictPathValidator
|
17
|
+
from path_link.validation import PathValidationError
|
18
|
+
from path_link.url_factory import ProjectUrls
|
19
|
+
from path_link.url_model import ValidationMode
|
20
|
+
from path_link.url_static import write_url_dataclass_file
|
18
21
|
|
19
22
|
|
20
23
|
def cmd_print(args: argparse.Namespace) -> int:
|
@@ -126,10 +129,97 @@ def cmd_gen_static(args: argparse.Namespace) -> int:
|
|
126
129
|
return 1
|
127
130
|
|
128
131
|
|
132
|
+
def cmd_print_urls(args: argparse.Namespace) -> int:
|
133
|
+
"""Print resolved URLs as JSON."""
|
134
|
+
try:
|
135
|
+
# Determine validation mode
|
136
|
+
mode = ValidationMode(args.mode.lower()) if hasattr(args, "mode") and args.mode else ValidationMode.LENIENT
|
137
|
+
|
138
|
+
# Load URLs based on source
|
139
|
+
if args.src == "pyproject":
|
140
|
+
urls = ProjectUrls.from_pyproject(mode=mode)
|
141
|
+
elif args.src == "dotenv":
|
142
|
+
config_file = args.config if hasattr(args, "config") and args.config else ".urls"
|
143
|
+
urls = ProjectUrls.from_config(config_file, mode=mode)
|
144
|
+
elif args.src == "all":
|
145
|
+
urls = ProjectUrls.from_merged(mode=mode)
|
146
|
+
else:
|
147
|
+
print(f"Unknown source: {args.src}", file=sys.stderr)
|
148
|
+
return 1
|
149
|
+
|
150
|
+
# Convert URLs to dict
|
151
|
+
urls_dict = urls.to_dict()
|
152
|
+
|
153
|
+
# Output based on format
|
154
|
+
if args.format == "json":
|
155
|
+
print(json.dumps(urls_dict, indent=2))
|
156
|
+
else: # table format
|
157
|
+
print(f"URLs (mode: {mode.value}, source: {args.src}):")
|
158
|
+
for key, value in sorted(urls_dict.items()):
|
159
|
+
print(f" {key}: {value}")
|
160
|
+
|
161
|
+
return 0
|
162
|
+
|
163
|
+
except Exception as e:
|
164
|
+
print(f"Error loading URLs: {e}", file=sys.stderr)
|
165
|
+
return 1
|
166
|
+
|
167
|
+
|
168
|
+
def cmd_validate_urls(args: argparse.Namespace) -> int:
|
169
|
+
"""Validate URLs and report results."""
|
170
|
+
try:
|
171
|
+
# Determine validation mode
|
172
|
+
mode = ValidationMode(args.mode.lower()) if hasattr(args, "mode") and args.mode else ValidationMode.LENIENT
|
173
|
+
|
174
|
+
# Load URLs based on source
|
175
|
+
if args.src == "pyproject":
|
176
|
+
urls = ProjectUrls.from_pyproject(mode=mode)
|
177
|
+
elif args.src == "dotenv":
|
178
|
+
config_file = args.config if hasattr(args, "config") and args.config else ".urls"
|
179
|
+
urls = ProjectUrls.from_config(config_file, mode=mode)
|
180
|
+
elif args.src == "all":
|
181
|
+
urls = ProjectUrls.from_merged(mode=mode)
|
182
|
+
else:
|
183
|
+
print(f"Unknown source: {args.src}", file=sys.stderr)
|
184
|
+
return 1
|
185
|
+
|
186
|
+
# If we got here, all URLs are valid
|
187
|
+
urls_dict = urls.to_dict()
|
188
|
+
print(f"✅ All {len(urls_dict)} URLs valid (mode: {mode.value})")
|
189
|
+
return 0
|
190
|
+
|
191
|
+
except Exception as e:
|
192
|
+
print(f"❌ URL validation failed: {e}", file=sys.stderr)
|
193
|
+
return 1
|
194
|
+
|
195
|
+
|
196
|
+
def cmd_gen_static_urls(args: argparse.Namespace) -> int:
|
197
|
+
"""Generate static URL dataclass file."""
|
198
|
+
try:
|
199
|
+
# Determine validation mode
|
200
|
+
mode = ValidationMode(args.mode.lower()) if hasattr(args, "mode") and args.mode else ValidationMode.LENIENT
|
201
|
+
|
202
|
+
# Determine output path
|
203
|
+
output_path = Path(args.output) if hasattr(args, "output") and args.output else None
|
204
|
+
|
205
|
+
# Generate static model
|
206
|
+
if output_path:
|
207
|
+
print(f"Generating static URL model at: {output_path} (mode: {mode.value})")
|
208
|
+
else:
|
209
|
+
print(f"Generating static URL model at default location (mode: {mode.value})")
|
210
|
+
|
211
|
+
write_url_dataclass_file(output_path=output_path, mode=mode)
|
212
|
+
return 0
|
213
|
+
|
214
|
+
except Exception as e:
|
215
|
+
print(f"Error generating static URL model: {e}", file=sys.stderr)
|
216
|
+
return 1
|
217
|
+
|
218
|
+
|
129
219
|
def main() -> NoReturn:
|
130
220
|
"""Main entry point for ptool CLI."""
|
131
221
|
parser = argparse.ArgumentParser(
|
132
|
-
prog="
|
222
|
+
prog="pathlink", description="Type-safe project path management tool"
|
133
223
|
)
|
134
224
|
|
135
225
|
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
@@ -181,6 +271,66 @@ def main() -> NoReturn:
|
|
181
271
|
help="Output path for static model (default: src/project_paths/project_paths_static.py)",
|
182
272
|
)
|
183
273
|
|
274
|
+
# print-urls command
|
275
|
+
print_urls_parser = subparsers.add_parser("print-urls", help="Print resolved URLs")
|
276
|
+
print_urls_parser.add_argument(
|
277
|
+
"--mode",
|
278
|
+
choices=["lenient", "strict"],
|
279
|
+
default="lenient",
|
280
|
+
help="Validation mode (default: lenient)",
|
281
|
+
)
|
282
|
+
print_urls_parser.add_argument(
|
283
|
+
"--format",
|
284
|
+
choices=["json", "table"],
|
285
|
+
default="json",
|
286
|
+
help="Output format (default: json)",
|
287
|
+
)
|
288
|
+
print_urls_parser.add_argument(
|
289
|
+
"--src",
|
290
|
+
choices=["pyproject", "dotenv", "all"],
|
291
|
+
default="all",
|
292
|
+
help="URL source (default: all - merged from both sources)",
|
293
|
+
)
|
294
|
+
print_urls_parser.add_argument(
|
295
|
+
"--config", type=str, help="Path to .urls config file (default: .urls)"
|
296
|
+
)
|
297
|
+
|
298
|
+
# validate-urls command
|
299
|
+
validate_urls_parser = subparsers.add_parser(
|
300
|
+
"validate-urls", help="Validate URLs and report results"
|
301
|
+
)
|
302
|
+
validate_urls_parser.add_argument(
|
303
|
+
"--mode",
|
304
|
+
choices=["lenient", "strict"],
|
305
|
+
default="lenient",
|
306
|
+
help="Validation mode (default: lenient)",
|
307
|
+
)
|
308
|
+
validate_urls_parser.add_argument(
|
309
|
+
"--src",
|
310
|
+
choices=["pyproject", "dotenv", "all"],
|
311
|
+
default="all",
|
312
|
+
help="URL source (default: all - merged from both sources)",
|
313
|
+
)
|
314
|
+
validate_urls_parser.add_argument(
|
315
|
+
"--config", type=str, help="Path to .urls config file (default: .urls)"
|
316
|
+
)
|
317
|
+
|
318
|
+
# gen-static-urls command
|
319
|
+
gen_static_urls_parser = subparsers.add_parser(
|
320
|
+
"gen-static-urls", help="Generate static URL dataclass file"
|
321
|
+
)
|
322
|
+
gen_static_urls_parser.add_argument(
|
323
|
+
"--mode",
|
324
|
+
choices=["lenient", "strict"],
|
325
|
+
default="lenient",
|
326
|
+
help="Validation mode (default: lenient)",
|
327
|
+
)
|
328
|
+
gen_static_urls_parser.add_argument(
|
329
|
+
"--output",
|
330
|
+
type=str,
|
331
|
+
help="Output path for static model (default: src/project_paths/project_urls_static.py)",
|
332
|
+
)
|
333
|
+
|
184
334
|
args = parser.parse_args()
|
185
335
|
|
186
336
|
# Dispatch to appropriate command handler
|
@@ -190,6 +340,12 @@ def main() -> NoReturn:
|
|
190
340
|
exit_code = cmd_validate(args)
|
191
341
|
elif args.command == "gen-static":
|
192
342
|
exit_code = cmd_gen_static(args)
|
343
|
+
elif args.command == "print-urls":
|
344
|
+
exit_code = cmd_print_urls(args)
|
345
|
+
elif args.command == "validate-urls":
|
346
|
+
exit_code = cmd_validate_urls(args)
|
347
|
+
elif args.command == "gen-static-urls":
|
348
|
+
exit_code = cmd_gen_static_urls(args)
|
193
349
|
else:
|
194
350
|
parser.print_help()
|
195
351
|
exit_code = 1
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Assistant Context —
|
1
|
+
# Assistant Context — path-link v0.2.0
|
2
2
|
|
3
3
|
**Last Updated:** 2025-10-10
|
4
4
|
**Status:** Active (Production)
|
@@ -8,10 +8,10 @@
|
|
8
8
|
|
9
9
|
## Overview
|
10
10
|
|
11
|
-
`
|
11
|
+
`path-link` is a Python library for type-safe project path management using Pydantic models.
|
12
12
|
|
13
13
|
**Key Facts:**
|
14
|
-
- **Package:** `
|
14
|
+
- **Package:** `path-link` → imports as `project_paths`
|
15
15
|
- **Pattern:** Dynamic Pydantic model creation via factory methods (v2 API)
|
16
16
|
- **Tool:** `uv` for dependency management
|
17
17
|
- **Python:** 3.11+
|
@@ -33,13 +33,13 @@
|
|
33
33
|
uv pip install -e ".[test]"
|
34
34
|
|
35
35
|
# One-liner smoke test (verify installation)
|
36
|
-
uv run python -c "from
|
36
|
+
uv run python -c "from path_link import ProjectPaths; p=ProjectPaths.from_pyproject(); print('✅ OK:', len(p.to_dict()), 'paths loaded')"
|
37
37
|
```
|
38
38
|
|
39
39
|
### 2. Basic Usage
|
40
40
|
|
41
41
|
```python
|
42
|
-
from
|
42
|
+
from path_link import ProjectPaths
|
43
43
|
|
44
44
|
# ✅ CORRECT: Use factory methods
|
45
45
|
paths = ProjectPaths.from_pyproject() # Load from pyproject.toml
|
@@ -69,17 +69,17 @@ uv run pytest tests/test_validators.py # Specific file
|
|
69
69
|
| Purpose | Command |
|
70
70
|
|---------|---------|
|
71
71
|
| **Install** | `uv pip install -e ".[test]"` |
|
72
|
-
| **Smoke test** | `uv run python -c "from
|
72
|
+
| **Smoke test** | `uv run python -c "from path_link import ProjectPaths; p=ProjectPaths.from_pyproject(); print('✅ OK:', len(p.to_dict()))"` |
|
73
73
|
| **Run all tests** | `uv run pytest` |
|
74
74
|
| **Coverage check** | `uv run pytest --cov=src --cov-report=term-missing:skip-covered` |
|
75
75
|
| **Type check** | `uv run mypy src/` |
|
76
76
|
| **Lint** | `uv run ruff check .` |
|
77
77
|
| **Format** | `uv run ruff format .` |
|
78
|
-
| **Regenerate static model** | `uv run python -c "from
|
78
|
+
| **Regenerate static model** | `uv run python -c "from path_link import write_dataclass_file; write_dataclass_file()"` |
|
79
79
|
| **Verify static sync** | `git diff --exit-code src/project_paths/project_paths_static.py` |
|
80
|
-
| **Access AI guidelines** | `uv run python -c "from
|
81
|
-
| **Access dev guide** | `uv run python -c "from
|
82
|
-
| **Access metadata** | `uv run python -c "import json; from
|
80
|
+
| **Access AI guidelines** | `uv run python -c "from path_link import get_ai_guidelines; print(get_ai_guidelines()[:200])"` |
|
81
|
+
| **Access dev guide** | `uv run python -c "from path_link import get_developer_guide; print(get_developer_guide()[:200])"` |
|
82
|
+
| **Access metadata** | `uv run python -c "import json; from path_link import get_metadata; print(json.loads(get_metadata())['version'])"` |
|
83
83
|
| **Run example** | `uv run python examples/minimal_project/src/main.py` |
|
84
84
|
|
85
85
|
---
|
@@ -103,8 +103,8 @@ uv run pytest tests/test_validators.py # Specific file
|
|
103
103
|
|
104
104
|
- **Validate paths:**
|
105
105
|
```python
|
106
|
-
from
|
107
|
-
from
|
106
|
+
from path_link import validate_or_raise
|
107
|
+
from path_link.builtin_validators import StrictPathValidator
|
108
108
|
|
109
109
|
validator = StrictPathValidator(
|
110
110
|
required=["config_dir", "data_dir"],
|
@@ -157,11 +157,11 @@ paths = ProjectPaths() # Raises NotImplementedError by design
|
|
157
157
|
|
158
158
|
## Static Model Sync
|
159
159
|
|
160
|
-
**When to regenerate:** After any change to `[tool.
|
160
|
+
**When to regenerate:** After any change to `[tool.path_link]` in `pyproject.toml`
|
161
161
|
|
162
162
|
```bash
|
163
163
|
# 1. Regenerate static model
|
164
|
-
uv run python -c "from
|
164
|
+
uv run python -c "from path_link import write_dataclass_file; write_dataclass_file()"
|
165
165
|
|
166
166
|
# 2. Verify no drift (CI check)
|
167
167
|
git diff --exit-code src/project_paths/project_paths_static.py || \
|
@@ -212,7 +212,7 @@ except FileExistsError:
|
|
212
212
|
**`StrictPathValidator(allow_symlinks=False)` blocks both live and dangling symlinks by default.**
|
213
213
|
|
214
214
|
```python
|
215
|
-
from
|
215
|
+
from path_link.builtin_validators import StrictPathValidator
|
216
216
|
|
217
217
|
# Blocks symlinks (default behavior)
|
218
218
|
validator = StrictPathValidator(
|
@@ -239,14 +239,14 @@ validator_permissive = StrictPathValidator(
|
|
239
239
|
### Basic Validation
|
240
240
|
|
241
241
|
```python
|
242
|
-
from
|
242
|
+
from path_link import (
|
243
243
|
ProjectPaths,
|
244
244
|
validate_or_raise,
|
245
245
|
ValidationResult,
|
246
246
|
Finding,
|
247
247
|
Severity
|
248
248
|
)
|
249
|
-
from
|
249
|
+
from path_link.builtin_validators import StrictPathValidator
|
250
250
|
|
251
251
|
# Load paths
|
252
252
|
paths = ProjectPaths.from_pyproject()
|
@@ -289,7 +289,7 @@ else:
|
|
289
289
|
|
290
290
|
```python
|
291
291
|
from dataclasses import dataclass
|
292
|
-
from
|
292
|
+
from path_link import ValidationResult, Finding, Severity
|
293
293
|
|
294
294
|
@dataclass
|
295
295
|
class MyValidator:
|
@@ -325,7 +325,7 @@ validate_or_raise(paths, validator)
|
|
325
325
|
The package includes three functions to access documentation programmatically, even in airgapped or offline environments. This is especially useful for AI assistants helping users with the package.
|
326
326
|
|
327
327
|
```python
|
328
|
-
from
|
328
|
+
from path_link import get_ai_guidelines, get_developer_guide, get_metadata
|
329
329
|
import json
|
330
330
|
|
331
331
|
# Get AI assistant guidelines (this file - comprehensive usage patterns)
|
@@ -363,13 +363,13 @@ print(f"CLI Commands: {len(metadata['cli_commands'])} available")
|
|
363
363
|
uv run python -c "import sys; print(sys.version)"
|
364
364
|
|
365
365
|
# 2. Verify editable install
|
366
|
-
uv pip list | grep
|
366
|
+
uv pip list | grep path-link # Should show editable install
|
367
367
|
|
368
368
|
# 3. Reinstall if needed
|
369
369
|
uv pip install -e ".[test]"
|
370
370
|
|
371
371
|
# 4. Test import
|
372
|
-
uv run python -c "import
|
372
|
+
uv run python -c "import path_link; print('✅ Import OK')"
|
373
373
|
```
|
374
374
|
|
375
375
|
### Test Failures
|
@@ -379,7 +379,7 @@ uv run python -c "import project_paths; print('✅ Import OK')"
|
|
379
379
|
uv run pytest tests/test_static_model_equivalence.py -v
|
380
380
|
|
381
381
|
# 2. If failing, regenerate static model
|
382
|
-
uv run python -c "from
|
382
|
+
uv run python -c "from path_link import write_dataclass_file; write_dataclass_file()"
|
383
383
|
|
384
384
|
# 3. Re-run all tests
|
385
385
|
uv run pytest -v
|
@@ -406,7 +406,7 @@ paths = ProjectPaths.from_pyproject()
|
|
406
406
|
**Fix:**
|
407
407
|
```bash
|
408
408
|
# Regenerate in current environment
|
409
|
-
uv run python -c "from
|
409
|
+
uv run python -c "from path_link import write_dataclass_file; write_dataclass_file()"
|
410
410
|
|
411
411
|
# Verify fix
|
412
412
|
uv run pytest tests/test_static_model_equivalence.py -v
|
@@ -465,7 +465,7 @@ When working with this codebase, AI assistants MUST:
|
|
465
465
|
|
466
466
|
1. **Always use factory methods** — Never call `ProjectPaths()` directly
|
467
467
|
2. **Keep static model in sync** — Regenerate after `pyproject.toml` changes
|
468
|
-
3. **Follow `src` layout** — Use absolute imports: `from
|
468
|
+
3. **Follow `src` layout** — Use absolute imports: `from path_link.model import ...`
|
469
469
|
4. **Refer to this document first** — Before any path-related task
|
470
470
|
5. **Verify changes** — Run tests after modifications: `uv run pytest`
|
471
471
|
6. **Use atomic operations** — Follow TOCTOU prevention patterns
|
@@ -474,8 +474,8 @@ When working with this codebase, AI assistants MUST:
|
|
474
474
|
### Example Pattern
|
475
475
|
|
476
476
|
```python
|
477
|
-
from
|
478
|
-
from
|
477
|
+
from path_link import ProjectPaths, validate_or_raise
|
478
|
+
from path_link.builtin_validators import StrictPathValidator
|
479
479
|
|
480
480
|
# 1. Load paths
|
481
481
|
paths = ProjectPaths.from_pyproject()
|
@@ -517,12 +517,12 @@ except FileExistsError:
|
|
517
517
|
### pyproject.toml
|
518
518
|
|
519
519
|
```toml
|
520
|
-
[tool.
|
520
|
+
[tool.path_link.paths]
|
521
521
|
config_dir = "config"
|
522
522
|
data_dir = "data"
|
523
523
|
logs_dir = "logs"
|
524
524
|
|
525
|
-
[tool.
|
525
|
+
[tool.path_link.files]
|
526
526
|
settings_file = "config/settings.json"
|
527
527
|
log_file = "logs/app.log"
|
528
528
|
```
|
@@ -569,7 +569,7 @@ user_data=~/projects/${PROJECT_NAME}/data
|
|
569
569
|
|
570
570
|
**Example pyproject.toml:**
|
571
571
|
```toml
|
572
|
-
[tool.
|
572
|
+
[tool.path_link.paths]
|
573
573
|
data_dir = "${DATA_ROOT}/app_data"
|
574
574
|
config_dir = "~/.config/myapp"
|
575
575
|
```
|
@@ -609,13 +609,13 @@ If you encounter issues, run these commands in order:
|
|
609
609
|
uv run python -c "import sys; print('Python:', sys.version)" # Should be 3.11+
|
610
610
|
|
611
611
|
# 2. Check editable install
|
612
|
-
uv pip list | grep
|
612
|
+
uv pip list | grep path-link # Should show path with "(editable)"
|
613
613
|
|
614
614
|
# 3. Test import
|
615
|
-
uv run python -c "import
|
615
|
+
uv run python -c "import path_link; print('✅ Import OK')"
|
616
616
|
|
617
617
|
# 4. Run smoke test
|
618
|
-
uv run python -c "from
|
618
|
+
uv run python -c "from path_link import ProjectPaths; p=ProjectPaths.from_pyproject(); print('✅ OK:', len(p.to_dict()), 'paths loaded')"
|
619
619
|
|
620
620
|
# 5. Check config files
|
621
621
|
ls -l pyproject.toml .paths 2>/dev/null
|
@@ -649,7 +649,7 @@ git diff src/project_paths/project_paths_static.py
|
|
649
649
|
|
650
650
|
## Summary
|
651
651
|
|
652
|
-
**
|
652
|
+
**path-link (v0.2.0)** provides type-safe, validated path management for Python projects.
|
653
653
|
|
654
654
|
**Core Principles:**
|
655
655
|
- Factory methods only (v2 API)
|
@@ -661,7 +661,7 @@ git diff src/project_paths/project_paths_static.py
|
|
661
661
|
|
662
662
|
---
|
663
663
|
|
664
|
-
**Project:**
|
664
|
+
**Project:** path-link v0.2.0
|
665
665
|
**Python:** 3.11+
|
666
666
|
**License:** MIT
|
667
667
|
**Package Manager:** uv
|