renart 0.3.2__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.
renart/__init__.py ADDED
@@ -0,0 +1,28 @@
1
+ """Renart Python SDK.
2
+
3
+ Available inside renart Python assets. `query()` runs read-only SQL against
4
+ the project's connections through the renart runner — credentials never enter
5
+ the Python process — and `context` exposes the run window and metadata.
6
+
7
+ from renart import query, context
8
+
9
+ def materialize():
10
+ games = query("select * from chess_games").to_pandas()
11
+ return games.groupby("winner").size().reset_index(name="wins")
12
+ """
13
+
14
+ from ._client import (
15
+ BrokerUnavailableError,
16
+ QueryError,
17
+ RenartError,
18
+ query,
19
+ )
20
+ from .context import context
21
+
22
+ __all__ = [
23
+ "query",
24
+ "context",
25
+ "RenartError",
26
+ "QueryError",
27
+ "BrokerUnavailableError",
28
+ ]
renart/__init__.pyi ADDED
@@ -0,0 +1,7 @@
1
+ from ._client import BrokerUnavailableError as BrokerUnavailableError
2
+ from ._client import QueryError as QueryError
3
+ from ._client import RenartError as RenartError
4
+ from ._client import query as query
5
+ from .context import _Context as _Context
6
+
7
+ context: _Context
renart/_client.py ADDED
@@ -0,0 +1,129 @@
1
+ """HTTP client for the renart run broker.
2
+
3
+ The runner starts a loopback broker per task and injects RENART_API_URL and
4
+ RENART_API_TOKEN. Queries execute inside the runner on the project's
5
+ connections; results stream back as Arrow IPC. Only the token crosses the
6
+ process boundary — never credentials.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import urllib.error
14
+ import urllib.request
15
+ from typing import Any, Optional
16
+
17
+
18
+ class RenartError(Exception):
19
+ """Base class for renart SDK errors."""
20
+
21
+
22
+ class BrokerUnavailableError(RenartError):
23
+ """The run broker is not reachable (not running inside a renart run?)."""
24
+
25
+
26
+ class QueryError(RenartError):
27
+ """The broker rejected or failed to execute a query."""
28
+
29
+ def __init__(self, message: str, code: Optional[str] = None):
30
+ super().__init__(message)
31
+ self.code = code
32
+
33
+
34
+ def _broker_address() -> "tuple[str, str]":
35
+ url = os.environ.get("RENART_API_URL", "").strip()
36
+ token = os.environ.get("RENART_API_TOKEN", "").strip()
37
+ if not url or not token:
38
+ raise BrokerUnavailableError(
39
+ "renart.query() needs the run broker, which is only available while "
40
+ "the asset runs through renart (RENART_API_URL / RENART_API_TOKEN "
41
+ "are not set)."
42
+ )
43
+ return url, token
44
+
45
+
46
+ def _raise_from_response(exc: urllib.error.HTTPError) -> "None":
47
+ code = None
48
+ message = f"broker request failed with HTTP {exc.code}"
49
+ try:
50
+ payload = json.loads(exc.read().decode("utf-8"))
51
+ error = payload.get("error") or {}
52
+ message = error.get("message") or message
53
+ code = error.get("code")
54
+ except Exception: # noqa: BLE001 - keep the HTTP error as the message
55
+ pass
56
+ raise QueryError(message, code) from None
57
+
58
+
59
+ def query(sql: str, connection: Optional[str] = None, format: str = "arrow") -> Any:
60
+ """Run a read-only SQL query through the renart runner.
61
+
62
+ Args:
63
+ sql: a single SELECT statement. It may reference other assets of the
64
+ project; if a referenced asset is being materialized right now,
65
+ the runner waits for it to finish before executing the query.
66
+ connection: a project connection name; defaults to the asset's own
67
+ connection.
68
+ format: ``"arrow"`` (default) returns a pyarrow.Table,
69
+ ``"pandas"`` returns a pandas.DataFrame.
70
+
71
+ Credentials never enter the Python process; the query executes inside
72
+ the renart runner.
73
+ """
74
+ if format not in ("pandas", "arrow"):
75
+ raise ValueError(f'format must be "pandas" or "arrow", got {format!r}')
76
+
77
+ url, token = _broker_address()
78
+ body = {"sql": sql}
79
+ if connection:
80
+ body["connection"] = connection
81
+ request = urllib.request.Request(
82
+ url + "/v1/query",
83
+ data=json.dumps(body).encode("utf-8"),
84
+ headers={
85
+ "Authorization": "Bearer " + token,
86
+ "Content-Type": "application/json",
87
+ },
88
+ method="POST",
89
+ )
90
+
91
+ import pyarrow.ipc as ipc
92
+
93
+ try:
94
+ response = urllib.request.urlopen(request)
95
+ except urllib.error.HTTPError as exc:
96
+ _raise_from_response(exc)
97
+ except urllib.error.URLError as exc:
98
+ raise BrokerUnavailableError(f"cannot reach the run broker: {exc.reason}") from None
99
+
100
+ with response:
101
+ table = ipc.open_stream(response).read_all()
102
+
103
+ if format == "arrow":
104
+ return table
105
+ try:
106
+ return table.to_pandas()
107
+ except ImportError as exc:
108
+ raise RenartError(
109
+ 'query(format="pandas") needs pandas installed in the asset '
110
+ 'environment; add pandas to the asset dependencies or use '
111
+ 'format="arrow".'
112
+ ) from exc
113
+
114
+
115
+ def _fetch_context() -> "dict[str, Any]":
116
+ """Fetch the run context document from the broker (internal)."""
117
+ url, token = _broker_address()
118
+ request = urllib.request.Request(
119
+ url + "/v1/context",
120
+ headers={"Authorization": "Bearer " + token},
121
+ )
122
+ try:
123
+ with urllib.request.urlopen(request) as response:
124
+ return json.loads(response.read().decode("utf-8"))
125
+ except urllib.error.HTTPError as exc:
126
+ _raise_from_response(exc)
127
+ raise # unreachable; _raise_from_response always raises
128
+ except urllib.error.URLError as exc:
129
+ raise BrokerUnavailableError(f"cannot reach the run broker: {exc.reason}") from None
renart/_client.pyi ADDED
@@ -0,0 +1,15 @@
1
+ from typing import Any, Literal
2
+
3
+ class RenartError(Exception): ...
4
+
5
+ class BrokerUnavailableError(RenartError): ...
6
+
7
+ class QueryError(RenartError):
8
+ code: str | None
9
+ def __init__(self, message: str, code: str | None = None) -> None: ...
10
+
11
+ def query(
12
+ sql: str,
13
+ connection: str | None = None,
14
+ format: Literal["arrow", "pandas"] = "arrow",
15
+ ) -> Any: ...
renart/context.py ADDED
@@ -0,0 +1,107 @@
1
+ """Typed access to the run context.
2
+
3
+ Backed by the BRUIN_* environment variables the runner injects, so it works
4
+ in any execution mode (and stays compatible with scripts that read the env
5
+ vars directly). Every property returns None when the value is absent, except
6
+ ``is_full_refresh`` (False) and ``vars`` (``{}``).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ from datetime import date, datetime
14
+ from typing import Any, Optional
15
+
16
+
17
+ def _env(name: str) -> Optional[str]:
18
+ value = os.environ.get(name, "").strip()
19
+ return value or None
20
+
21
+
22
+ def _parse_date(value: Optional[str]) -> Optional[date]:
23
+ if not value:
24
+ return None
25
+ try:
26
+ return date.fromisoformat(value)
27
+ except ValueError:
28
+ return None
29
+
30
+
31
+ def _parse_datetime(value: Optional[str]) -> Optional[datetime]:
32
+ if not value:
33
+ return None
34
+ try:
35
+ return datetime.fromisoformat(value)
36
+ except ValueError:
37
+ return None
38
+
39
+
40
+ class _Context:
41
+ """Runtime information about the current renart run."""
42
+
43
+ @property
44
+ def start_date(self) -> Optional[date]:
45
+ """Start of the run window."""
46
+ return _parse_date(_env("BRUIN_START_DATE"))
47
+
48
+ @property
49
+ def start_datetime(self) -> Optional[datetime]:
50
+ return _parse_datetime(_env("BRUIN_START_DATETIME"))
51
+
52
+ @property
53
+ def end_date(self) -> Optional[date]:
54
+ """End of the run window."""
55
+ return _parse_date(_env("BRUIN_END_DATE"))
56
+
57
+ @property
58
+ def end_datetime(self) -> Optional[datetime]:
59
+ return _parse_datetime(_env("BRUIN_END_DATETIME"))
60
+
61
+ @property
62
+ def execution_date(self) -> Optional[datetime]:
63
+ return _parse_datetime(_env("BRUIN_EXECUTION_DATETIME"))
64
+
65
+ @property
66
+ def pipeline(self) -> Optional[str]:
67
+ return _env("BRUIN_PIPELINE")
68
+
69
+ @property
70
+ def asset_name(self) -> Optional[str]:
71
+ return _env("BRUIN_ASSET")
72
+
73
+ @property
74
+ def connection(self) -> Optional[str]:
75
+ """The asset's default connection name."""
76
+ return _env("BRUIN_CONNECTION")
77
+
78
+ @property
79
+ def run_id(self) -> Optional[str]:
80
+ return _env("BRUIN_RUN_ID")
81
+
82
+ @property
83
+ def is_full_refresh(self) -> bool:
84
+ value = (_env("BRUIN_FULL_REFRESH") or "").lower()
85
+ return value in ("1", "true", "yes")
86
+
87
+ @property
88
+ def vars(self) -> "dict[str, Any]":
89
+ """Pipeline variables, types preserved from their JSON schema."""
90
+ raw = _env("BRUIN_VARS")
91
+ if not raw:
92
+ return {}
93
+ try:
94
+ parsed = json.loads(raw)
95
+ except json.JSONDecodeError:
96
+ return {}
97
+ return parsed if isinstance(parsed, dict) else {}
98
+
99
+ def __repr__(self) -> str: # pragma: no cover - debugging nicety
100
+ return (
101
+ f"renart.context(pipeline={self.pipeline!r}, asset={self.asset_name!r}, "
102
+ f"start_date={self.start_date!r}, end_date={self.end_date!r}, "
103
+ f"run_id={self.run_id!r}, is_full_refresh={self.is_full_refresh!r})"
104
+ )
105
+
106
+
107
+ context = _Context()
renart/context.pyi ADDED
@@ -0,0 +1,28 @@
1
+ from datetime import date, datetime
2
+ from typing import Any
3
+
4
+ class _Context:
5
+ @property
6
+ def start_date(self) -> date | None: ...
7
+ @property
8
+ def start_datetime(self) -> datetime | None: ...
9
+ @property
10
+ def end_date(self) -> date | None: ...
11
+ @property
12
+ def end_datetime(self) -> datetime | None: ...
13
+ @property
14
+ def execution_date(self) -> datetime | None: ...
15
+ @property
16
+ def pipeline(self) -> str | None: ...
17
+ @property
18
+ def asset_name(self) -> str | None: ...
19
+ @property
20
+ def connection(self) -> str | None: ...
21
+ @property
22
+ def run_id(self) -> str | None: ...
23
+ @property
24
+ def is_full_refresh(self) -> bool: ...
25
+ @property
26
+ def vars(self) -> dict[str, Any]: ...
27
+
28
+ context: _Context
renart/py.typed ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.1
2
+ Name: renart
3
+ Version: 0.3.2
4
+ Summary: Renart SDK for Python assets: query project data through the renart runner.
5
+ Home-page: https://getrenart.com
6
+ Project-URL: Documentation, https://getrenart.com/docs/asset-types/python-assets/
7
+ Project-URL: Source, https://github.com/renart-data/renart
8
+ License: Apache-2.0
9
+ Requires-Python: >=3.9
10
+ Requires-Dist: pyarrow>=15.0.0
11
+ Requires-Dist: pandas>=1.5
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Renart Python SDK
15
+
16
+ renart provides the Python package used by Python assets and Python
17
+ notebook cells in Renart. Renart injects its matching SDK wheel automatically
18
+ at runtime; the PyPI distribution is available for type checking, editors, and
19
+ CI environments that inspect project code outside the Renart process.
20
+
21
+ query() connects to a token-scoped broker started by the Renart runner. It
22
+ does not contain database credentials and is not a standalone database client.
@@ -0,0 +1,11 @@
1
+ renart/__init__.py,sha256=i3gPwcZoHzzkoSaqyvlOrTrI42BcPvLGdCIS6EqZ6DQ,691
2
+ renart/__init__.pyi,sha256=1Drc_oITmRq35yYrwr3fJ1ir-aVE2PRfPEb9K5ERHKk,261
3
+ renart/_client.py,sha256=goGCSSxqJRdnIR_3vCS5r5hVOdq_6OE6Qc-av9CNaC4,4387
4
+ renart/_client.pyi,sha256=s0QhbqBlRstNPgNNfXZL3qnXK0VxRa61r5_Np_sSEVM,368
5
+ renart/context.py,sha256=lLBf_khFT2CEfWPPtZJJYlfd3lU0Q5HvslU4wzsVkUs,3027
6
+ renart/context.pyi,sha256=ZUeog2ZUES8lI2XvnZMsnNQIshHYyK4UA02waNvWnpY,749
7
+ renart/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
8
+ renart-0.3.2.dist-info/METADATA,sha256=0Zf5LfhZR-Z_TWzjFBUvWR1V6tlhUrBQlBPrsfBUGsQ,922
9
+ renart-0.3.2.dist-info/WHEEL,sha256=iAQXzXWZKPhmEBuSL_3wqpOatQ9An7mT6wPP1Py91F0,77
10
+ renart-0.3.2.dist-info/top_level.txt,sha256=AOLaNFL0wxJOm6mSttggaCfHcZecQznwf8R3AnyzxM4,7
11
+ renart-0.3.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: renart
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1 @@
1
+ renart