tap-belvo 0.3.1__tar.gz → 0.3.2__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.

Potentially problematic release.


This version of tap-belvo might be problematic. Click here for more details.

Files changed (34) hide show
  1. tap_belvo-0.3.2/.github/workflows/update_openapi.yml +33 -0
  2. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/PKG-INFO +3 -2
  3. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/pyproject.toml +16 -11
  4. tap_belvo-0.3.2/scripts/update_openapi.py +29 -0
  5. tap_belvo-0.3.2/tap_belvo/client.py +123 -0
  6. tap_belvo-0.3.1/tap_belvo/openapi/BelvoOpenFinanceApiSpec.json → tap_belvo-0.3.2/tap_belvo/openapi.json +27522 -18666
  7. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/tap_belvo/streams/banking.py +10 -12
  8. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/tap_belvo/streams/core.py +14 -16
  9. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/tap_belvo/tap.py +11 -7
  10. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/uv.lock +62 -15
  11. tap_belvo-0.3.1/tap_belvo/client.py +0 -185
  12. tap_belvo-0.3.1/tap_belvo/openapi/__init__.py +0 -24
  13. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/.copier-answers.yml +0 -0
  14. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/.github/CODEOWNERS +0 -0
  15. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/.github/renovate.json5 +0 -0
  16. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/.github/workflows/dynamic-publish.yaml +0 -0
  17. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/.github/workflows/test.yml +0 -0
  18. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/.gitignore +0 -0
  19. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/.pre-commit-config.yaml +0 -0
  20. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/.secrets/.gitignore +0 -0
  21. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/LICENSE +0 -0
  22. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/README.md +0 -0
  23. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/meltano.yml +0 -0
  24. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/output/.gitignore +0 -0
  25. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/plugins/loaders/target-duckdb--jwills.lock +0 -0
  26. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/plugins/loaders/target-jsonl--andyh1203.lock +0 -0
  27. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/tap_belvo/__init__.py +0 -0
  28. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/tap_belvo/__main__.py +0 -0
  29. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/tap_belvo/streams/__init__.py +0 -0
  30. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/tap_belvo/streams/enrichment.py +0 -0
  31. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/tap_belvo/streams/fiscal.py +0 -0
  32. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/tests/__init__.py +0 -0
  33. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/tests/conftest.py +0 -0
  34. {tap_belvo-0.3.1 → tap_belvo-0.3.2}/tests/test_core.py +0 -0
@@ -0,0 +1,33 @@
1
+ name: Update OpenAPI Specification for belvo.com
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ schedule:
6
+ # Daily at noon
7
+ - cron: '0 12 * * *'
8
+
9
+ env:
10
+ # renovate: datasource=pypi depName=uv
11
+ UV_VERSION: 0.8.22
12
+
13
+ jobs:
14
+ update_openapi_specification:
15
+ runs-on: ubuntu-24.04
16
+ permissions:
17
+ contents: write
18
+ pull-requests: write
19
+ steps:
20
+ - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
21
+ id: generate-token
22
+ with:
23
+ app-id: ${{ vars.APP_ID }}
24
+ private-key: ${{ secrets.APP_PRIVATE_KEY }}
25
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
26
+ - uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
27
+ with:
28
+ version: ${{ env.UV_VERSION }}
29
+ - run: uv run --only-group=scripts scripts/update_openapi.py
30
+ - uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
31
+ with:
32
+ title: 'chore: Update OpenAPI Specification for belvo.com'
33
+ token: ${{ steps.generate-token.outputs.token }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tap-belvo
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Singer tap for Belvo, built with the Meltano SDK for Singer Taps.
5
5
  Project-URL: Documentation, https://github.com/reservoir-data/tap-belvo#readme
6
6
  Project-URL: Homepage, https://github.com/reservoir-data/tap-belvo
@@ -18,7 +18,8 @@ Classifier: Programming Language :: Python :: 3.14
18
18
  Requires-Python: >=3.11
19
19
  Requires-Dist: requests-cache==1.*
20
20
  Requires-Dist: requests~=2.32.3
21
- Requires-Dist: singer-sdk~=0.49.1
21
+ Requires-Dist: singer-sdk~=0.50.0
22
+ Requires-Dist: typing-extensions>=4.15; python_full_version < '3.12'
22
23
  Description-Content-Type: text/markdown
23
24
 
24
25
  # tap-belvo
@@ -31,7 +31,8 @@ dynamic = [
31
31
  dependencies = [
32
32
  "requests~=2.32.3",
33
33
  "requests-cache==1.*",
34
- "singer-sdk~=0.49.1",
34
+ "singer-sdk~=0.50.0",
35
+ "typing-extensions>=4.15; python_full_version<'3.12'",
35
36
  ]
36
37
  urls.Documentation = "https://github.com/reservoir-data/tap-belvo#readme"
37
38
  urls.Homepage = "https://github.com/reservoir-data/tap-belvo"
@@ -40,17 +41,22 @@ scripts."tap-belvo" = "tap_belvo.tap:TapBelvo.cli"
40
41
 
41
42
  [dependency-groups]
42
43
  dev = [
44
+ { include-group = "scripts" },
43
45
  { include-group = "testing" },
44
46
  { include-group = "typing" },
45
47
  ]
48
+ scripts = [
49
+ "requests>=2.32.5",
50
+ ]
46
51
  testing = [
47
- "deptry>=0.12",
48
- "pytest>=7.4",
52
+ "deptry>=0.23",
53
+ "pytest>=8",
49
54
  "singer-sdk[testing]",
50
55
  ]
51
56
  typing = [
52
- "mypy",
53
- "types-requests",
57
+ "mypy>=1.18",
58
+ "ty>=0.0.1a21",
59
+ "types-requests>=2.32.3",
54
60
  ]
55
61
 
56
62
  [tool.hatch.version]
@@ -113,7 +119,10 @@ commands = [ [ "deptry", "." ] ]
113
119
 
114
120
  [tool.tox.env.typing]
115
121
  dependency_groups = [ "testing", "typing" ]
116
- commands = [ [ "mypy", "--strict", { replace = "posargs", default = [ "tap_belvo", "tests" ], extend = true } ] ]
122
+ commands = [
123
+ [ "mypy", "--strict", { replace = "posargs", default = [ "tap_belvo", "tests" ], extend = true } ],
124
+ [ "ty", "check", { replace = "posargs", default = [ "tap_belvo", "tests" ], extend = true } ],
125
+ ]
117
126
 
118
127
  [tool.mypy]
119
128
  enable_error_code = [
@@ -128,11 +137,7 @@ warn_unreachable = true
128
137
  warn_unused_configs = true
129
138
  warn_unused_ignores = true
130
139
 
131
- [[tool.mypy.overrides]]
132
- ignore_missing_imports = true
133
- module = [ "backoff.*" ]
134
-
135
140
  [tool.uv]
136
141
  prerelease = "allow"
137
142
  preview = true
138
- required-version = ">=0.5.19"
143
+ required-version = ">=0.8"
@@ -0,0 +1,29 @@
1
+ """Update the OpenAPI schema from the Belvo API.
2
+
3
+ Copyright (c) 2025 Edgar Ramírez-Mondragón
4
+ """ # noqa: INP001
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import pathlib
10
+
11
+ import requests
12
+
13
+ OPENAPI_URL = "https://developers.belvo.com/_spec/apis/BelvoOpenApiSpec.json"
14
+ PATH = "tap_belvo/openapi.json"
15
+
16
+
17
+ def main() -> None:
18
+ """Update the OpenAPI schema from the Belvo API."""
19
+ with pathlib.Path(PATH).open("w", encoding="utf-8") as file:
20
+ response = requests.get(OPENAPI_URL, timeout=5)
21
+ response.raise_for_status()
22
+ spec = response.json()
23
+
24
+ content = json.dumps(spec, indent=2) + "\n"
25
+ file.write(content)
26
+
27
+
28
+ if __name__ == "__main__":
29
+ main()
@@ -0,0 +1,123 @@
1
+ """REST client handling, including BelvoStream base class."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from abc import ABCMeta, abstractmethod
7
+ from copy import deepcopy
8
+ from importlib import resources
9
+ from typing import TYPE_CHECKING, Any
10
+ from urllib.parse import ParseResult, parse_qsl
11
+
12
+ from requests.auth import HTTPBasicAuth
13
+ from requests_cache import install_cache
14
+ from singer_sdk import OpenAPISchema, RESTStream
15
+ from singer_sdk.pagination import BaseHATEOASPaginator
16
+
17
+ if sys.version_info >= (3, 12):
18
+ from typing import override
19
+ else:
20
+ from typing_extensions import override
21
+
22
+ if TYPE_CHECKING:
23
+ from requests import Response
24
+ from singer_sdk.helpers.types import Context
25
+
26
+
27
+ PAGE_SIZE = 1000
28
+ OPENAPI = OpenAPISchema(resources.files("tap_belvo") / "openapi.json")
29
+
30
+ install_cache("tap_belvo_cache", backend="sqlite", expire_after=3600)
31
+
32
+
33
+ def _handle_schema_nullable(schema: dict[str, Any]) -> dict[str, Any]:
34
+ """Resolve x-nullable properties to standard JSON Schema nullable type.
35
+
36
+ Args:
37
+ schema: A JSON Schema dictionary.
38
+
39
+ Returns:
40
+ A new JSON Schema dictionary with 'x-nullable' resolved to [<type>, "null"].
41
+ """
42
+ result = deepcopy(schema)
43
+
44
+ if "object" in result["type"]:
45
+ for prop, prop_schema in result.get("properties", {}).items():
46
+ prop_type: str | list[str] = prop_schema.get("type", [])
47
+ types = [prop_type] if isinstance(prop_type, str) else prop_type
48
+ nullable: bool = prop_schema.get("nullable", False)
49
+
50
+ if nullable:
51
+ prop_schema["type"] = [*types, "null"]
52
+
53
+ result["properties"][prop] = _handle_schema_nullable(prop_schema)
54
+
55
+ elif "array" in result["type"]:
56
+ result["items"] = _handle_schema_nullable(result["items"])
57
+
58
+ if "enum" in result and None not in result["enum"]:
59
+ result["enum"].append(None)
60
+
61
+ return result
62
+
63
+
64
+ class BelvoPaginator(BaseHATEOASPaginator):
65
+ """Belvo API paginator class."""
66
+
67
+ @override
68
+ def get_next_url(self, response: Response) -> str | None:
69
+ """Get the next URL from the response."""
70
+ return response.json().get("next") # type: ignore[no-any-return]
71
+
72
+
73
+ class BelvoStream(RESTStream[ParseResult], metaclass=ABCMeta):
74
+ """Belvo stream class."""
75
+
76
+ records_jsonpath = "$.results[*]" # Or override `parse_response`.
77
+
78
+ @override
79
+ @property
80
+ def url_base(self) -> str:
81
+ return self.config["base_url"] # type: ignore[no-any-return]
82
+
83
+ @override
84
+ @property
85
+ def authenticator(self) -> HTTPBasicAuth:
86
+ return HTTPBasicAuth(self.config["secret_id"], self.config["password"])
87
+
88
+ @override
89
+ def get_new_paginator(self) -> BelvoPaginator:
90
+ return BelvoPaginator()
91
+
92
+ @override
93
+ def get_url_params(
94
+ self,
95
+ context: Context | None,
96
+ next_page_token: ParseResult | None,
97
+ ) -> dict[str, Any]:
98
+ """Get URL query parameters."""
99
+ params: dict[str, Any] = {
100
+ "page": 1,
101
+ "page_size": PAGE_SIZE,
102
+ }
103
+
104
+ if next_page_token:
105
+ params.update(parse_qsl(next_page_token.query))
106
+
107
+ if (
108
+ self.replication_key # Only if the stream is running incrementally
109
+ and (start_date := self.get_starting_timestamp(context))
110
+ ):
111
+ params[f"{self.replication_key}__gte"] = start_date.date().isoformat()
112
+
113
+ return params
114
+
115
+ @override
116
+ @property
117
+ def schema(self) -> dict[str, Any]:
118
+ return _handle_schema_nullable(OPENAPI.fetch_schema(self.openapi_ref))
119
+
120
+ @property
121
+ @abstractmethod
122
+ def openapi_ref(self) -> str:
123
+ """OpenAPI component name for this stream."""