tap-belvo 0.3.0__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.
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/.github/workflows/test.yml +4 -4
- tap_belvo-0.3.2/.github/workflows/update_openapi.yml +33 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/.pre-commit-config.yaml +2 -2
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/PKG-INFO +3 -2
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/pyproject.toml +16 -11
- tap_belvo-0.3.2/scripts/update_openapi.py +29 -0
- tap_belvo-0.3.2/tap_belvo/client.py +123 -0
- tap_belvo-0.3.0/tap_belvo/openapi/BelvoOpenFinanceApiSpec.json → tap_belvo-0.3.2/tap_belvo/openapi.json +27522 -18666
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/tap_belvo/streams/banking.py +10 -12
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/tap_belvo/streams/core.py +14 -16
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/tap_belvo/tap.py +11 -7
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/uv.lock +194 -127
- tap_belvo-0.3.0/tap_belvo/client.py +0 -185
- tap_belvo-0.3.0/tap_belvo/openapi/__init__.py +0 -24
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/.copier-answers.yml +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/.github/CODEOWNERS +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/.github/renovate.json5 +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/.github/workflows/dynamic-publish.yaml +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/.gitignore +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/.secrets/.gitignore +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/LICENSE +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/README.md +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/meltano.yml +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/output/.gitignore +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/plugins/loaders/target-duckdb--jwills.lock +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/plugins/loaders/target-jsonl--andyh1203.lock +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/tap_belvo/__init__.py +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/tap_belvo/__main__.py +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/tap_belvo/streams/__init__.py +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/tap_belvo/streams/enrichment.py +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/tap_belvo/streams/fiscal.py +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/tests/__init__.py +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/tests/conftest.py +0 -0
- {tap_belvo-0.3.0 → tap_belvo-0.3.2}/tests/test_core.py +0 -0
|
@@ -30,9 +30,9 @@ concurrency:
|
|
|
30
30
|
env:
|
|
31
31
|
FORCE_COLOR: 1
|
|
32
32
|
# renovate: datasource=pypi depName=prek
|
|
33
|
-
PREK_VERSION: 0.1
|
|
33
|
+
PREK_VERSION: 0.2.1
|
|
34
34
|
# renovate: datasource=pypi depName=uv
|
|
35
|
-
UV_VERSION: 0.8.
|
|
35
|
+
UV_VERSION: 0.8.22
|
|
36
36
|
# renovate: datasource=pypi depName=tox
|
|
37
37
|
TOX_VERSION: 4.30.2
|
|
38
38
|
# renovate: datasource=pypi depName=tox-uv
|
|
@@ -60,7 +60,7 @@ jobs:
|
|
|
60
60
|
with:
|
|
61
61
|
python-version: ${{ matrix.python-version }}
|
|
62
62
|
allow-prereleases: true
|
|
63
|
-
- uses: astral-sh/setup-uv@
|
|
63
|
+
- uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
|
64
64
|
with:
|
|
65
65
|
version: ${{ env.UV_VERSION }}
|
|
66
66
|
- name: Run
|
|
@@ -82,7 +82,7 @@ jobs:
|
|
|
82
82
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
83
83
|
|
|
84
84
|
- name: Install the latest version of uv
|
|
85
|
-
uses: astral-sh/setup-uv@
|
|
85
|
+
uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
|
86
86
|
with:
|
|
87
87
|
version: ${{ env.UV_VERSION }}
|
|
88
88
|
|
|
@@ -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 }}
|
|
@@ -24,7 +24,7 @@ repos:
|
|
|
24
24
|
- id: pyproject-fmt
|
|
25
25
|
|
|
26
26
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
27
|
-
rev: "v0.
|
|
27
|
+
rev: "v0.13.2"
|
|
28
28
|
hooks:
|
|
29
29
|
- id: ruff-check
|
|
30
30
|
args: [--fix, --exit-non-zero-on-fix, --show-fixes]
|
|
@@ -36,7 +36,7 @@ repos:
|
|
|
36
36
|
- id: validate_manifest
|
|
37
37
|
|
|
38
38
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
|
39
|
-
rev: "0.8.
|
|
39
|
+
rev: "0.8.22"
|
|
40
40
|
hooks:
|
|
41
41
|
- id: uv-lock
|
|
42
42
|
- id: uv-sync
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tap-belvo
|
|
3
|
-
Version: 0.3.
|
|
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.
|
|
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.
|
|
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.
|
|
48
|
-
"pytest>=
|
|
52
|
+
"deptry>=0.23",
|
|
53
|
+
"pytest>=8",
|
|
49
54
|
"singer-sdk[testing]",
|
|
50
55
|
]
|
|
51
56
|
typing = [
|
|
52
|
-
"mypy",
|
|
53
|
-
"
|
|
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 = [
|
|
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.
|
|
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."""
|