pytest-httpchain-jsonref 0.2.1__tar.gz → 0.3.0__tar.gz

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.
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pytest-httpchain-jsonref
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: JSON reference ($ref) support for pytest-httpchain
5
5
  Author: Alexander Eresov
6
6
  Author-email: Alexander Eresov <aeresov@gmail.com>
7
7
  Requires-Dist: deepmerge>=2.0
8
+ Requires-Dist: pytest-httpchain-core
8
9
  Requires-Python: >=3.13
9
10
  Description-Content-Type: text/markdown
10
11
 
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "pytest-httpchain-jsonref"
3
- version = "0.2.1"
3
+ version = "0.3.0"
4
4
  description = "JSON reference ($ref) support for pytest-httpchain"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
7
7
  authors = [{ name = "Alexander Eresov", email = "aeresov@gmail.com" }]
8
- dependencies = ["deepmerge>=2.0"]
8
+ dependencies = ["deepmerge>=2.0", "pytest-httpchain-core"]
9
9
 
10
10
  [build-system]
11
11
  requires = ["uv_build>=0.7.21,<0.8.0"]
@@ -0,0 +1,5 @@
1
+ from pytest_httpchain_core import HttpChainError
2
+
3
+
4
+ class ReferenceResolverError(HttpChainError):
5
+ """Exception for reference resolution errors."""
@@ -7,15 +7,18 @@ from pytest_httpchain_jsonref.plumbing.reference import ReferenceResolver
7
7
 
8
8
 
9
9
  def load_json(path: Path, max_parent_traversal_depth: int = 3, root_path: Path | None = None) -> dict[str, Any]:
10
- """Load JSON from file and resolve all $ref statements with circular reference protection.
10
+ """Load JSON from file and resolve all $include/$merge/$ref statements with circular reference protection.
11
+
12
+ All three directives ($include, $merge, $ref) work identically. $include and $merge are preferred
13
+ as they avoid conflicts with VS Code's JSON Schema validation (which treats $ref specially).
11
14
 
12
15
  Args:
13
16
  path: Path to the JSON file to load
14
- max_parent_traversal_depth: Maximum number of parent directory traversals allowed in $ref paths
17
+ max_parent_traversal_depth: Maximum number of parent directory traversals allowed in reference paths
15
18
  root_path: Optional root directory for resolving references (e.g., pytest's rootdir)
16
19
 
17
20
  Returns:
18
- Dictionary with all $ref statements resolved
21
+ Dictionary with all $include/$ref statements resolved
19
22
 
20
23
  Raises:
21
24
  ReferenceResolverError: If the file cannot be loaded or parsed, if merge conflicts occur, or if circular references are detected
@@ -46,6 +46,11 @@ class PathValidator:
46
46
  # Try resolving from different base paths in order of preference
47
47
  paths_to_try = [base_path]
48
48
 
49
+ # Add CWD so that paths like "tests/common.json" resolve from where the user runs the tool
50
+ cwd = Path.cwd()
51
+ if cwd.resolve() not in (base_path_resolved, root_path_resolved):
52
+ paths_to_try.append(cwd)
53
+
49
54
  # Add root_path if it's different from base_path
50
55
  if root_path_resolved != base_path_resolved:
51
56
  paths_to_try.append(root_path)
@@ -14,6 +14,9 @@ from pytest_httpchain_jsonref.plumbing.path import PathValidator
14
14
 
15
15
  REF_PATTERN = re.compile(r"^(?P<file>[^#]+)?(?:#(?P<pointer>/.*))?$")
16
16
 
17
+ # Supported reference keys: $include/$merge (preferred, avoids VS Code conflicts) and $ref (legacy)
18
+ REF_KEYS = ("$include", "$merge", "$ref")
19
+
17
20
 
18
21
  class ReferenceResolver:
19
22
  """Resolves JSON references ($ref) in documents."""
@@ -54,8 +57,7 @@ class ReferenceResolver:
54
57
  ReferenceResolverError: If the file cannot be loaded or references cannot be resolved
55
58
  """
56
59
  try:
57
- with open(path, encoding="utf-8") as f:
58
- data = json.load(f)
60
+ data = json.loads(path.read_text(encoding="utf-8"))
59
61
 
60
62
  # If root_path wasn't provided, find a suitable one by going up the directory tree
61
63
  # up to max_parent_traversal_depth levels
@@ -79,7 +81,7 @@ class ReferenceResolver:
79
81
  root_data: Any,
80
82
  ) -> Any:
81
83
  match data:
82
- case dict() if "$ref" in data:
84
+ case dict() if self._get_ref_key(data):
83
85
  return self._resolve_single_ref(data, current_path, root_data)
84
86
  case dict():
85
87
  return {key: self._resolve_refs(value, current_path, root_data) for key, value in data.items()}
@@ -88,17 +90,26 @@ class ReferenceResolver:
88
90
  case _:
89
91
  return data
90
92
 
93
+ def _get_ref_key(self, data: dict[str, Any]) -> str | None:
94
+ """Get the reference key ($include or $ref) if present in data."""
95
+ for key in REF_KEYS:
96
+ if key in data:
97
+ return key
98
+ return None
99
+
91
100
  def _resolve_single_ref(
92
101
  self,
93
102
  data: dict[str, Any],
94
103
  current_path: Path,
95
104
  root_data: Any,
96
105
  ) -> Any:
97
- ref_value = data["$ref"]
106
+ ref_key = self._get_ref_key(data)
107
+ assert ref_key is not None
108
+ ref_value = data[ref_key]
98
109
  match = REF_PATTERN.match(ref_value)
99
110
 
100
111
  if not match:
101
- raise ReferenceResolverError(f"Invalid $ref format: {ref_value}")
112
+ raise ReferenceResolverError(f"Invalid {ref_key} format: {ref_value}")
102
113
 
103
114
  file_path = match.group("file")
104
115
  pointer = match.group("pointer") or ""
@@ -174,7 +185,7 @@ class ReferenceResolver:
174
185
  current_path: Path,
175
186
  root_data: Any,
176
187
  ) -> Any:
177
- siblings = {k: v for k, v in ref_dict.items() if k != "$ref"}
188
+ siblings = {k: v for k, v in ref_dict.items() if k not in REF_KEYS}
178
189
 
179
190
  if not siblings:
180
191
  return referenced_data
@@ -192,8 +203,7 @@ class ReferenceResolver:
192
203
 
193
204
  def _load_json_file(self, path: Path) -> dict[str, Any]:
194
205
  """Load JSON file content."""
195
- with open(path, encoding="utf-8") as f:
196
- return json.load(f)
206
+ return json.loads(path.read_text(encoding="utf-8"))
197
207
 
198
208
  def _create_child_resolver(self) -> Self:
199
209
  """Create a child resolver with inherited state."""
@@ -1,2 +0,0 @@
1
- class ReferenceResolverError(Exception):
2
- """Base exception for reference resolution errors."""