vantage-python 0.2.0__tar.gz → 0.3.1__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.
@@ -0,0 +1,54 @@
1
+ name: Publish Package
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions:
9
+ id-token: write
10
+ contents: write
11
+
12
+ jobs:
13
+ check-if-tag:
14
+ name: Check if Tag is Present
15
+ runs-on: ubuntu-22.04
16
+ outputs:
17
+ TAG_NOT_PRESENT: ${{ steps.get-tag.outputs.TAG_NOT_PRESENT }}
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+ - name: Get the tag from Python and check if it's present
23
+ id: get-tag
24
+ run: |
25
+ pip install toml
26
+ VERSION=$(python -c "import toml; print(toml.load('pyproject.toml')['project']['version'])")
27
+ if git tag -l "v$VERSION" | grep -q .; then
28
+ echo "Tag v$VERSION is present"
29
+ echo "TAG_NOT_PRESENT=false" >> $GITHUB_OUTPUT
30
+ else
31
+ echo "Tag v$VERSION is not present"
32
+ echo "TAG_NOT_PRESENT=true" >> $GITHUB_OUTPUT
33
+
34
+ git tag "v$VERSION"
35
+ git config user.name "github-actions[bot]"
36
+ git config user.email "github-actions[bot]@users.noreply.github.com"
37
+ git push origin "v$VERSION"
38
+ fi
39
+
40
+ publish:
41
+ name: Publish Package to PyPI
42
+ runs-on: ubuntu-22.04
43
+ needs: check-if-tag
44
+ if: needs.check-if-tag.outputs.TAG_NOT_PRESENT == 'true'
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+ - uses: actions/setup-python@v5
48
+ with:
49
+ python-version: "3.14"
50
+ - run: pip install build twine
51
+ - run: pip install -e ".[dev]"
52
+ - run: python -m build
53
+ - name: Publish release distributions to PyPI
54
+ uses: pypa/gh-action-pypi-publish@v1.13.0
@@ -14,7 +14,6 @@ jobs:
14
14
  with:
15
15
  python-version: ${{ matrix.python-version }}
16
16
  - run: pip install -e ".[dev]"
17
- - run: python autogen.py
18
17
  - run: pytest tests/test_e2e.py
19
18
  env:
20
19
  VANTAGE_API_TOKEN: ${{ secrets.VANTAGE_API_TOKEN }}
@@ -32,5 +31,15 @@ jobs:
32
31
  with:
33
32
  python-version: ${{ matrix.python-version }}
34
33
  - run: pip install -e ".[dev]"
35
- - run: python autogen.py
36
34
  - run: python -c "from vantage import Client, AsyncClient"
35
+
36
+ compare-to-api:
37
+ name: Compare to API
38
+ runs-on: ubuntu-22.04
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+ - uses: actions/setup-python@v5
42
+ with:
43
+ python-version: "3.14"
44
+ - run: pip install -e ".[dev]"
45
+ - run: make diff
@@ -0,0 +1,7 @@
1
+ # Python specific
2
+ __pycache__/
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+ .pytest_cache/
7
+ .venv/
@@ -1,10 +1,14 @@
1
- .PHONY: install publish
1
+ .PHONY: install generate diff publish
2
2
 
3
3
  install:
4
- python3 autogen.py
5
4
  python3 -m pip install -e ".[dev]"
6
5
 
7
- publish:
6
+ generate:
8
7
  python3 autogen.py
8
+
9
+ diff: generate
10
+ git diff --exit-code src/vantage
11
+
12
+ publish:
9
13
  python3 -m build
10
14
  twine upload dist/*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vantage-python
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: Python SDK for the Vantage API
5
5
  Project-URL: Homepage, https://github.com/vantage-sh/vantage-python
6
6
  Project-URL: Repository, https://github.com/vantage-sh/vantage-python
@@ -96,7 +96,7 @@ except VantageAPIError as e:
96
96
  make install
97
97
  ```
98
98
 
99
- This fetches the latest OpenAPI schema from the Vantage API and generates Pydantic models, sync client, and async client. Your pip version will need to be up to date for this.
99
+ This generates Pydantic models, sync client, and the async client. Your pip version will need to be up to date for this. If you wish to generate the client first, you should use `make generate`.
100
100
 
101
101
  ### Testing
102
102
 
@@ -75,7 +75,7 @@ except VantageAPIError as e:
75
75
  make install
76
76
  ```
77
77
 
78
- This fetches the latest OpenAPI schema from the Vantage API and generates Pydantic models, sync client, and async client. Your pip version will need to be up to date for this.
78
+ This generates Pydantic models, sync client, and the async client. Your pip version will need to be up to date for this. If you wish to generate the client first, you should use `make generate`.
79
79
 
80
80
  ### Testing
81
81
 
@@ -248,7 +248,7 @@ def extract_response_type(
248
248
  responses: dict[str, Any], schemas: dict[str, Any]
249
249
  ) -> str | None:
250
250
  """Extract successful response type."""
251
- for code in ["200", "201", "202", "204"]:
251
+ for code in ["200", "201", "202", "203"]:
252
252
  if code not in responses:
253
253
  continue
254
254
  response = responses[code]
@@ -373,6 +373,70 @@ def generate_method_name(endpoint: Endpoint, resource_name: str) -> str:
373
373
  return to_snake_case(op_id)
374
374
 
375
375
 
376
+ def _extract_inner_type(type_hint: str, generic_prefix: str) -> str | None:
377
+ """Extract inner type from simple generic forms like Prefix[Inner]."""
378
+ if not type_hint.startswith(generic_prefix) or not type_hint.endswith("]"):
379
+ return None
380
+ return type_hint[len(generic_prefix):-1].strip()
381
+
382
+
383
+ def _extract_dict_value_type(type_hint: str) -> str | None:
384
+ """Extract value type from Dict[str, ValueType]."""
385
+ if not type_hint.startswith("Dict[") or not type_hint.endswith("]"):
386
+ return None
387
+ inner = type_hint[len("Dict["):-1].strip()
388
+ key_and_value = inner.split(",", 1)
389
+ if len(key_and_value) != 2:
390
+ return None
391
+ key_type = key_and_value[0].strip()
392
+ value_type = key_and_value[1].strip()
393
+ if key_type not in {"str", "Optional[str]"}:
394
+ return None
395
+ return value_type
396
+
397
+
398
+ def _is_model_type(type_hint: str) -> bool:
399
+ """Return True for generated Pydantic model type names."""
400
+ return bool(re.match(r"^[A-Z][A-Za-z0-9_]*$", type_hint))
401
+
402
+
403
+ def _append_response_mapping(lines: list[str], return_type: str, data_var: str) -> None:
404
+ """Append generated code that coerces dict payloads into typed models."""
405
+ type_hint = return_type.strip()
406
+
407
+ optional_inner = _extract_inner_type(type_hint, "Optional[")
408
+ if optional_inner:
409
+ type_hint = optional_inner
410
+
411
+ list_inner = _extract_inner_type(type_hint, "List[")
412
+ if list_inner and _is_model_type(list_inner):
413
+ lines.extend(
414
+ [
415
+ f" if isinstance({data_var}, list):",
416
+ f" return [{list_inner}.model_validate(item) if isinstance(item, dict) else item for item in {data_var}]",
417
+ ]
418
+ )
419
+ return
420
+
421
+ dict_inner = _extract_dict_value_type(type_hint)
422
+ if dict_inner and _is_model_type(dict_inner):
423
+ lines.extend(
424
+ [
425
+ f" if isinstance({data_var}, dict):",
426
+ f" return {{k: {dict_inner}.model_validate(v) if isinstance(v, dict) else v for k, v in {data_var}.items()}}",
427
+ ]
428
+ )
429
+ return
430
+
431
+ if _is_model_type(type_hint):
432
+ lines.extend(
433
+ [
434
+ f" if isinstance({data_var}, dict):",
435
+ f" return {type_hint}.model_validate({data_var})",
436
+ ]
437
+ )
438
+
439
+
376
440
  def generate_pydantic_models(schema: dict[str, Any]) -> str:
377
441
  """Generate Pydantic models from OpenAPI schemas."""
378
442
  schemas = schema.get("components", {}).get("schemas", {})
@@ -675,10 +739,12 @@ def generate_sync_method(endpoint: Endpoint, method_name: str) -> list[str]:
675
739
  else:
676
740
  lines.append(" body_data = None")
677
741
 
678
- # Make request
742
+ # Make request and coerce response payload into typed models where possible
679
743
  lines.append(
680
- f' return self._client.request("{endpoint.method}", path, params=params, body=body_data)'
744
+ f' data = self._client.request("{endpoint.method}", path, params=params, body=body_data)'
681
745
  )
746
+ _append_response_mapping(lines, return_type, "data")
747
+ lines.append(" return data")
682
748
 
683
749
  return lines
684
750
 
@@ -899,10 +965,12 @@ def generate_async_method(endpoint: Endpoint, method_name: str) -> list[str]:
899
965
  else:
900
966
  lines.append(" body_data = None")
901
967
 
902
- # Make request
968
+ # Make request and coerce response payload into typed models where possible
903
969
  lines.append(
904
- f' return await self._client.request("{endpoint.method}", path, params=params, body=body_data)'
970
+ f' data = await self._client.request("{endpoint.method}", path, params=params, body=body_data)'
905
971
  )
972
+ _append_response_mapping(lines, return_type, "data")
973
+ lines.append(" return data")
906
974
 
907
975
  return lines
908
976
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vantage-python"
7
- version = "0.2.0"
7
+ version = "0.3.1"
8
8
  description = "Python SDK for the Vantage API"
9
9
  readme = "README.md"
10
10
  license = "MIT"