perspective-cli 0.1.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.
- perspective/__init__.py +1 -0
- perspective/config.py +240 -0
- perspective/exceptions.py +15 -0
- perspective/ingest/dbt.py +150 -0
- perspective/ingest/ingest.py +164 -0
- perspective/ingest/postgres.py +388 -0
- perspective/ingest/sources/bi/powerbi/extract.py +184 -0
- perspective/ingest/sources/bi/powerbi/models.py +137 -0
- perspective/ingest/sources/bi/powerbi/pipeline.py +29 -0
- perspective/ingest/sources/bi/powerbi/transform.py +478 -0
- perspective/ingest/sources/bi/qlik_sense/extract.py +297 -0
- perspective/ingest/sources/bi/qlik_sense/models.py +22 -0
- perspective/ingest/sources/bi/qlik_sense/pipeline.py +19 -0
- perspective/ingest/sources/bi/qlik_sense/transform.py +76 -0
- perspective/ingest/sources/database/sap/extract.py +253 -0
- perspective/ingest/sources/database/sap/pipeline.py +23 -0
- perspective/ingest/sources/database/sap/transform.py +85 -0
- perspective/main.py +74 -0
- perspective/models/configs.py +422 -0
- perspective/models/dashboards.py +44 -0
- perspective/models/databases.py +26 -0
- perspective/utils/__init__.py +3 -0
- perspective/utils/options.py +77 -0
- perspective/utils/utils.py +274 -0
- perspective_cli-0.1.0.dist-info/METADATA +49 -0
- perspective_cli-0.1.0.dist-info/RECORD +29 -0
- perspective_cli-0.1.0.dist-info/WHEEL +5 -0
- perspective_cli-0.1.0.dist-info/entry_points.txt +2 -0
- perspective_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Common Typer options."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
MetadataDir: Path = typer.Option(
|
|
11
|
+
Path(os.getenv("DBT_PROFILES_DIR", ".")) / "target",
|
|
12
|
+
"--metadata-dir",
|
|
13
|
+
"-m",
|
|
14
|
+
help="Specify the directory with dbt metadata files. Defaults to $DBT_PROFILES_DIR/target or ./target if not set.",
|
|
15
|
+
exists=True,
|
|
16
|
+
dir_okay=True,
|
|
17
|
+
resolve_path=True,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
ConfigDir: Path = typer.Option(
|
|
21
|
+
"./.perspective",
|
|
22
|
+
"--config-dir",
|
|
23
|
+
"-c",
|
|
24
|
+
help="Specify the directory with the config files. Defaults to ./.perspective",
|
|
25
|
+
envvar="PERSPECTIVE_CONFIG_DIR",
|
|
26
|
+
dir_okay=True,
|
|
27
|
+
resolve_path=True,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
Force: bool = typer.Option(
|
|
31
|
+
False,
|
|
32
|
+
"--force",
|
|
33
|
+
"-f",
|
|
34
|
+
help="Force the operation.",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
DryRun: bool = typer.Option(
|
|
38
|
+
False,
|
|
39
|
+
"--dry-run",
|
|
40
|
+
"-D",
|
|
41
|
+
help="Perform a dry run. Print the payload but do not send it.",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
NoConfig: bool = typer.Option(
|
|
45
|
+
False,
|
|
46
|
+
"--no-config",
|
|
47
|
+
"-n",
|
|
48
|
+
help="Set this flag to prevent sending configuration data along with the request.",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
Follow: bool = typer.Option(
|
|
52
|
+
False, "--follow", help="Follow the ingestion process until it's completed."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
IngestionId = Annotated[str, typer.Argument(help="Ingestion ID.")]
|
|
56
|
+
|
|
57
|
+
PerspectiveURL: str = typer.Option(
|
|
58
|
+
"http://localhost:8000/api/v1/",
|
|
59
|
+
"--url",
|
|
60
|
+
"-u",
|
|
61
|
+
help="URL of the Perspective API.",
|
|
62
|
+
envvar="PERSPECTIVE_API_URL",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
FollowTimeout: int = typer.Option(
|
|
66
|
+
30,
|
|
67
|
+
"--follow-timeout",
|
|
68
|
+
"-t",
|
|
69
|
+
help="How many seconds to wait for the ingestion process to complete.",
|
|
70
|
+
envvar="PERSPECTIVE_TIMEOUT",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
IsDbtTestResultsIngestion: bool = typer.Option(
|
|
74
|
+
False,
|
|
75
|
+
"--test-results",
|
|
76
|
+
help="Set this flag to only ingest dbt test results.",
|
|
77
|
+
)
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""Utility functions."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import subprocess # noqa: S404
|
|
7
|
+
from traceback import format_exc
|
|
8
|
+
from urllib.parse import urljoin
|
|
9
|
+
|
|
10
|
+
from requests import Response, request
|
|
11
|
+
from requests.exceptions import ConnectionError as RequestsConnectionError
|
|
12
|
+
from requests.exceptions import Timeout
|
|
13
|
+
from rich import print as rprint
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
import typer
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class IngestionStatus(Enum):
|
|
23
|
+
"""Ingestion status values."""
|
|
24
|
+
|
|
25
|
+
successful = 0
|
|
26
|
+
failed = 1
|
|
27
|
+
pending = 2
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class HttpMethod(str, Enum):
|
|
31
|
+
"""HTTP methods."""
|
|
32
|
+
|
|
33
|
+
GET = "GET"
|
|
34
|
+
POST = "POST"
|
|
35
|
+
PUT = "PUT"
|
|
36
|
+
DELETE = "DELETE"
|
|
37
|
+
HEAD = "HEAD"
|
|
38
|
+
OPTIONS = "OPTIONS"
|
|
39
|
+
PATCH = "PATCH"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def safe_json_load(path: str | Path) -> dict | None:
|
|
43
|
+
"""Reads a JSON file into a Python dictionary.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
path (str | Path): The path to the JSON file.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
dict | None: A dictionary representation of the JSON file, or None if an error
|
|
50
|
+
occurs.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
with Path(path).open("r", encoding="utf-8") as f:
|
|
54
|
+
data = json.load(f)
|
|
55
|
+
except Exception:
|
|
56
|
+
data = None
|
|
57
|
+
return data
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def run_command(command: str, capture_output: bool = False) -> str | None:
|
|
61
|
+
"""Execute a shell command and optionally capture its output.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
command (str): The shell command to be executed.
|
|
65
|
+
capture_output (bool, optional): Flag to determine if the command's output
|
|
66
|
+
should be captured. Defaults to False.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
str | None: The standard output of the command if `capture_output` is True,
|
|
70
|
+
otherwise None.
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
typer.Exit: Exits the script if the command execution fails.
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
result = subprocess.run( # noqa: S602
|
|
77
|
+
command,
|
|
78
|
+
shell=True,
|
|
79
|
+
check=True,
|
|
80
|
+
capture_output=True,
|
|
81
|
+
text=True,
|
|
82
|
+
)
|
|
83
|
+
except subprocess.CalledProcessError as e:
|
|
84
|
+
console.print(
|
|
85
|
+
Panel.fit(
|
|
86
|
+
f"[bold red]ERROR[/bold red]: An error occurred while running the command: [bold yellow]{e}[/bold yellow]",
|
|
87
|
+
title="Error",
|
|
88
|
+
border_style="red",
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
if e.output:
|
|
92
|
+
console.print(f"[bold cyan]Output[/bold cyan]: {e.output}")
|
|
93
|
+
if e.stderr:
|
|
94
|
+
console.print(f"[bold red]Error[/bold red]: {e.stderr}")
|
|
95
|
+
raise typer.Exit(1) from e
|
|
96
|
+
|
|
97
|
+
if capture_output:
|
|
98
|
+
return result.stdout.strip()
|
|
99
|
+
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def print_response(response: Response) -> None:
|
|
104
|
+
"""Print an API response.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
response (requests.Response): The response to print.
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
parsed_response = response.json()
|
|
111
|
+
except Exception:
|
|
112
|
+
parsed_response = None
|
|
113
|
+
|
|
114
|
+
if response.ok:
|
|
115
|
+
msg = "[green]The request was successful.[/green]"
|
|
116
|
+
if parsed_response:
|
|
117
|
+
msg += f"\n[yellow]Response:\n{parsed_response}[/yellow]"
|
|
118
|
+
else:
|
|
119
|
+
msg += f"\n[green]Raw response:\n{response.text}[/green]"
|
|
120
|
+
else:
|
|
121
|
+
if parsed_response:
|
|
122
|
+
error_msg = f"\n\nError message: {parsed_response['message']}"
|
|
123
|
+
else:
|
|
124
|
+
error_msg = f"Raw response:\n{response.text}"
|
|
125
|
+
msg = f"[red]The request failed with status code {response.status_code}.{error_msg}[/red]"
|
|
126
|
+
|
|
127
|
+
rprint(msg)
|
|
128
|
+
|
|
129
|
+
if not response.ok or parsed_response is None:
|
|
130
|
+
raise typer.Exit(1)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def send_request(
|
|
134
|
+
url: str,
|
|
135
|
+
payload: dict | list | None = None,
|
|
136
|
+
verify: bool = True,
|
|
137
|
+
method: HttpMethod = HttpMethod.POST,
|
|
138
|
+
timeout: (float | tuple[float, float] | tuple[float, None]) = (21.05, 60 * 30),
|
|
139
|
+
headers: dict[str, str] | None = None,
|
|
140
|
+
params: dict[str, str | int | float] | None = None,
|
|
141
|
+
return_response_key: str | None = None,
|
|
142
|
+
) -> Response | tuple[Response, str | None]:
|
|
143
|
+
"""Send an HTTP request.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
url (str): The URL for the request.
|
|
147
|
+
payload (dict | list | None): The payload for the request, if any.
|
|
148
|
+
verify (bool, optional): Whether to verify the server's TLS certificate.
|
|
149
|
+
Defaults to True.
|
|
150
|
+
method (HttpMethod, optional): _description_. Defaults to HttpMethod.POST.
|
|
151
|
+
timeout (optional[float | tuple[float, float] | tuple[float, None]]): The
|
|
152
|
+
timeout for the request. Defaults to (20, 60 * 30).
|
|
153
|
+
headers (optional[dict[str, str]], optional): Headers to be sent with the
|
|
154
|
+
request. Defaults to None.
|
|
155
|
+
params (optional[dict[str, str | int | float]]): URL parameters for the request.
|
|
156
|
+
Defaults to None.
|
|
157
|
+
return_response_key (str | None, optional): Key to extract from the response.
|
|
158
|
+
Defaults to None.
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
typer.Exit: In case of timeout or connection error.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
[requests.Response | tuple[requests.Response, str | None]]: The HTTP response
|
|
165
|
+
and a value extracted from the response (if `return_response_key` is
|
|
166
|
+
provided).
|
|
167
|
+
"""
|
|
168
|
+
rprint("[yellow]Sending request to Perspective...[/yellow]")
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
response = request(
|
|
172
|
+
method=method,
|
|
173
|
+
url=url,
|
|
174
|
+
headers=headers,
|
|
175
|
+
params=params,
|
|
176
|
+
json=payload,
|
|
177
|
+
verify=verify,
|
|
178
|
+
timeout=timeout,
|
|
179
|
+
)
|
|
180
|
+
except (Timeout, RequestsConnectionError) as e:
|
|
181
|
+
error_message = (
|
|
182
|
+
"The request has failed. Please check your connection and try again."
|
|
183
|
+
)
|
|
184
|
+
if isinstance(e, Timeout):
|
|
185
|
+
error_message += " If you're using a VPN, ensure it's properly connected or try disabling it temporarily."
|
|
186
|
+
else:
|
|
187
|
+
error_message += " This could be due to maximum retries being exceeded or failure to establish a new connection. Please check your network configuration."
|
|
188
|
+
|
|
189
|
+
rprint(Panel(f"[red]{error_message}[/red]"))
|
|
190
|
+
|
|
191
|
+
# Print the traceback
|
|
192
|
+
traceback_info = format_exc()
|
|
193
|
+
rprint(Panel(f"[red]{traceback_info}[/red]"))
|
|
194
|
+
|
|
195
|
+
raise typer.Exit(1) from e
|
|
196
|
+
|
|
197
|
+
if not response.ok:
|
|
198
|
+
rprint(
|
|
199
|
+
Panel(
|
|
200
|
+
f"[red]The request to {url} FAILED with status: {response.status_code} ({response.reason})[/red]"
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
raise typer.Exit(1)
|
|
204
|
+
|
|
205
|
+
print_response(response)
|
|
206
|
+
|
|
207
|
+
if return_response_key:
|
|
208
|
+
extracted_value = response.json().get(return_response_key)
|
|
209
|
+
return response, extracted_value
|
|
210
|
+
return response
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def check_ingestion_status(
|
|
214
|
+
perspective_url: str, ingestion_uuid: str, verify: bool = True
|
|
215
|
+
) -> str:
|
|
216
|
+
"""Fetches the status for a specific ingestion ID from Perspective.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
perspective_url (str): The URL of the Perspective instance.
|
|
220
|
+
ingestion_uuid (str): The ingestion ID to fetch the status for.
|
|
221
|
+
verify (bool): Whether to verify the server's TLS certificate. Defaults to True.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
str: The status of the ingestion process.
|
|
225
|
+
"""
|
|
226
|
+
status_endpoint = urljoin(perspective_url, "catalog/ingestions/")
|
|
227
|
+
response = send_request(
|
|
228
|
+
method="GET",
|
|
229
|
+
url=status_endpoint,
|
|
230
|
+
params={"uuid": ingestion_uuid},
|
|
231
|
+
verify=verify,
|
|
232
|
+
)
|
|
233
|
+
ingestion = response.json().get("data")
|
|
234
|
+
return ingestion.get("status")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def check_ingestion_results(
|
|
238
|
+
perspective_url: str, ingestion_uuid: str, verify: bool = True
|
|
239
|
+
) -> str | dict:
|
|
240
|
+
"""Fetche and interpret the results for a specific ingestion ID.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
perspective_url (str): The URL of the Perspective instance.
|
|
244
|
+
ingestion_uuid (str): The ingestion ID to check the results for.
|
|
245
|
+
verify (bool): Whether to verify the server's TLS certificate. Defaults to True.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
str | dict: A message describing the status of the ingestion process or
|
|
249
|
+
the JSON response for successful completions.
|
|
250
|
+
"""
|
|
251
|
+
status_endpoint = urljoin(perspective_url, "catalog/ingestions/")
|
|
252
|
+
response = send_request(
|
|
253
|
+
method="GET",
|
|
254
|
+
url=status_endpoint,
|
|
255
|
+
params={"uuid": ingestion_uuid},
|
|
256
|
+
verify=verify,
|
|
257
|
+
)
|
|
258
|
+
ingestion = response.json().get("data")
|
|
259
|
+
status = ingestion.get("status")
|
|
260
|
+
|
|
261
|
+
if status == IngestionStatus.pending.value:
|
|
262
|
+
return f"Ingestion ID {ingestion_uuid} is still pending."
|
|
263
|
+
|
|
264
|
+
if status == IngestionStatus.failed.value:
|
|
265
|
+
error_details = ingestion.get("error", "No additional error details provided.")
|
|
266
|
+
return (
|
|
267
|
+
f"Ingestion ID {ingestion_uuid} has failed. Error details: {error_details}"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if status == IngestionStatus.successful.value:
|
|
271
|
+
# Return the entire JSON response for successful completions
|
|
272
|
+
return ingestion.get("summary")
|
|
273
|
+
|
|
274
|
+
return f"Unrecognized status for ingestion ID {ingestion_uuid}: {status}"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: perspective-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A CLI tool for managing the Perspective AI platform.
|
|
5
|
+
Author-email: Michal Zawadzki <mzawadzki@dyvenia.com>
|
|
6
|
+
Keywords: cli,dbt,perspective,data,catalog,ai
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: typer[all]<1.0,>=0.9
|
|
15
|
+
Requires-Dist: psycopg[binary]>=3.2.3
|
|
16
|
+
Requires-Dist: requests<3.0,>=2.20
|
|
17
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
18
|
+
Requires-Dist: rich<13.8,>=13.7
|
|
19
|
+
Requires-Dist: loguru>=0.7.2
|
|
20
|
+
Requires-Dist: websocket-client>=1.8.0
|
|
21
|
+
Requires-Dist: requests-ntlm>=1.3.0
|
|
22
|
+
Requires-Dist: websockets>=10.4
|
|
23
|
+
Requires-Dist: pydantic[email]==2.11.7
|
|
24
|
+
Requires-Dist: azure-identity>=1.17.1
|
|
25
|
+
Requires-Dist: dlt[duckdb]>=1.11.0
|
|
26
|
+
Requires-Dist: duckdb>1.1.3
|
|
27
|
+
Requires-Dist: dbt-artifacts-parser<1.0,>=0.12.0
|
|
28
|
+
Provides-Extra: sap
|
|
29
|
+
Requires-Dist: pyrfc==2.5.0; extra == "sap"
|
|
30
|
+
|
|
31
|
+
# perspective-cli
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### `pip`
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install perspective-cli
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `uv`
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv add perspective-cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Next Steps
|
|
48
|
+
|
|
49
|
+
Proceed to the [official documentation](dev.meetperspective.com/docs/catalog/) for next steps and detailed guides on how to utilize `perspective-cli` effectively.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
perspective/__init__.py,sha256=RMqGNAtJDIHYnlTfMKR_Zljb9KV_ZAT714yhQ1jdRFM,23
|
|
2
|
+
perspective/config.py,sha256=0oBT522vbSLxyyGiJLTf5Qrhx8Yi5DbwPZwnT2ZBo4Q,7595
|
|
3
|
+
perspective/exceptions.py,sha256=awwIcnVIAqXPX5W8eMM0zREW8FJOdlqj29mJ7lkDNM0,426
|
|
4
|
+
perspective/main.py,sha256=1muq6dEnJt0h5loUxCPCNpkEfAiajTaSSOwcwEIo9gU,1765
|
|
5
|
+
perspective/ingest/dbt.py,sha256=Bn-vMJnOicENd5jAYr-tRjtJ9jWdUo6Ddu49gV-cA3c,5188
|
|
6
|
+
perspective/ingest/ingest.py,sha256=3n7ffsQBnskOoRhvHK5Iy06dlvKbDVrt5mIIPRplhfk,5034
|
|
7
|
+
perspective/ingest/postgres.py,sha256=m9QStEBbgSsEBmn7gEJQ5-Oc1ydstj3rG_lF1Xiu908,12248
|
|
8
|
+
perspective/ingest/sources/bi/powerbi/extract.py,sha256=m8eLA9fY7UT4xbdge0s8i1kXM3hjuj67XWaGNuWl2qo,6705
|
|
9
|
+
perspective/ingest/sources/bi/powerbi/models.py,sha256=v3Gcd0SjHvYif91ACUeCIpLz3_KUOFHyAKcq7zdS41A,2854
|
|
10
|
+
perspective/ingest/sources/bi/powerbi/pipeline.py,sha256=46lCTKAXtrcemIMfeG_1ul97jnetgSOcnyTjSvYwbbk,1002
|
|
11
|
+
perspective/ingest/sources/bi/powerbi/transform.py,sha256=kytJakFbCjZWz_qfG8_pGbRptvv3PetmbB2W-8FZCoI,17817
|
|
12
|
+
perspective/ingest/sources/bi/qlik_sense/extract.py,sha256=2FvQ9HGJZO1gMkBIFvBTOld4N9AXLfKjczIeakS5qls,10676
|
|
13
|
+
perspective/ingest/sources/bi/qlik_sense/models.py,sha256=dxhjAFzWhkx5vG3MQWhu0oaQAU9BlPTx89vmWiFpvb8,412
|
|
14
|
+
perspective/ingest/sources/bi/qlik_sense/pipeline.py,sha256=fa_aClpLbeOnh-z0YlO9rs8bvo0rqzrOjQ8N7UDedNw,522
|
|
15
|
+
perspective/ingest/sources/bi/qlik_sense/transform.py,sha256=oU8NbJUrnAZjSX1Vej1aRknm82FIc8eac60F9IfrHMw,2392
|
|
16
|
+
perspective/ingest/sources/database/sap/extract.py,sha256=oQGoB-I_fxa4Mbs3Fk-PTF65NwYZg9vr3l2-XwBGa1U,9063
|
|
17
|
+
perspective/ingest/sources/database/sap/pipeline.py,sha256=w-ExBhMAcXuySdEFs_1Z3pNBxmAGfrU_FYM15wo8F5A,810
|
|
18
|
+
perspective/ingest/sources/database/sap/transform.py,sha256=4KCurK-NrlPCqK4Wv52eSfTkSBN3lWlf_9cGqB_0kao,2745
|
|
19
|
+
perspective/models/configs.py,sha256=t5GJ0QORG6wlKyF2saGJyzE3H9E6psDXJ_6UwsDFSTw,9420
|
|
20
|
+
perspective/models/dashboards.py,sha256=8wcg-YfymPP_ErSbjvUgKWPju2PktjvZMShdmkL6APU,1084
|
|
21
|
+
perspective/models/databases.py,sha256=XLDZ29hLhrCVUMmnbodv1quNcuRcLeexiR_tZkxnCeM,536
|
|
22
|
+
perspective/utils/__init__.py,sha256=8PP1VNUc3DkZBgadUEvImVpuYUrAigkD7aYRCmSZ1l4,85
|
|
23
|
+
perspective/utils/options.py,sha256=a8eVilistizMOxFFOPpW8TKNvi5kaZoxUw67nzFbs8w,1751
|
|
24
|
+
perspective/utils/utils.py,sha256=CWUsV8Zp9dZJEiKSS1Hbu_Gxf6B_kCMT_NWlQ6L3jrU,8760
|
|
25
|
+
perspective_cli-0.1.0.dist-info/METADATA,sha256=TiO7EaOAWV3Qt2mZGUFpe1A6J2ep5Aha3bMQQombYho,1398
|
|
26
|
+
perspective_cli-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
27
|
+
perspective_cli-0.1.0.dist-info/entry_points.txt,sha256=FyAeBznbZy1x_I_ZMg3pB7TPOJ1oZRT3RHKho7Mr7mk,53
|
|
28
|
+
perspective_cli-0.1.0.dist-info/top_level.txt,sha256=-W1-t6tcSPy7mH-EhKzt_SJAICr8eZmyBwC3Eu5kqkU,12
|
|
29
|
+
perspective_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
perspective
|