Skip to content

Commit 7aa945d

Browse files
Add missing BRUIN_* context properties (#5)
* feat: add start_timestamp to context for BRUIN_START_TIMESTAMP Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add end_timestamp to context for BRUIN_END_TIMESTAMP Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add execution_datetime to context for BRUIN_EXECUTION_DATETIME Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add execution_timestamp to context for BRUIN_EXECUTION_TIMESTAMP Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: reorder context properties by prefix and improve error message - Group start_*, end_*, execution_* properties together instead of interleaving date/datetime across prefixes - Simplify _parse_datetime error message to not hardcode a format that doesn't match timestamp variants - Add non-UTC timezone test for start_timestamp Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix ruff formatting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c0f9ba7 commit 7aa945d

3 files changed

Lines changed: 90 additions & 8 deletions

File tree

docs/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,20 @@ from bruin import context
9999
| Property | Type | Env Var | Description |
100100
|----------|------|---------|-------------|
101101
| `context.start_date` | `date \| None` | `BRUIN_START_DATE` | Pipeline run start date |
102-
| `context.end_date` | `date \| None` | `BRUIN_END_DATE` | Pipeline run end date |
103102
| `context.start_datetime` | `datetime \| None` | `BRUIN_START_DATETIME` | Start date with time |
103+
| `context.start_timestamp` | `datetime \| None` | `BRUIN_START_TIMESTAMP` | Start timestamp with timezone |
104+
| `context.end_date` | `date \| None` | `BRUIN_END_DATE` | Pipeline run end date |
104105
| `context.end_datetime` | `datetime \| None` | `BRUIN_END_DATETIME` | End date with time |
106+
| `context.end_timestamp` | `datetime \| None` | `BRUIN_END_TIMESTAMP` | End timestamp with timezone |
105107
| `context.execution_date` | `date \| None` | `BRUIN_EXECUTION_DATE` | Execution date |
108+
| `context.execution_datetime` | `datetime \| None` | `BRUIN_EXECUTION_DATETIME` | Execution date with time |
109+
| `context.execution_timestamp` | `datetime \| None` | `BRUIN_EXECUTION_TIMESTAMP` | Execution timestamp with timezone |
106110
| `context.run_id` | `str \| None` | `BRUIN_RUN_ID` | Unique run identifier |
107111
| `context.pipeline` | `str \| None` | `BRUIN_PIPELINE` | Pipeline name |
108112
| `context.asset_name` | `str \| None` | `BRUIN_ASSET` | Current asset name |
109113
| `context.connection` | `str \| None` | `BRUIN_CONNECTION` | Asset's default connection |
110114
| `context.is_full_refresh` | `bool` | `BRUIN_FULL_REFRESH` | `True` when `--full-refresh` flag is set |
115+
| `context.commit_hash` | `str \| None` | `BRUIN_COMMIT_HASH` | Git commit hash of the pipeline's repository |
111116
| `context.vars` | `dict` | `BRUIN_VARS` | Pipeline variables (types preserved from JSON Schema) |
112117

113118
All properties return `None` when the corresponding env var is missing (except `is_full_refresh` which returns `False`, and `vars` which returns `{}`).

src/bruin/_context.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ def _parse_datetime(env_var: str) -> "datetime.datetime | None":
2727
try:
2828
return datetime.datetime.fromisoformat(val)
2929
except ValueError:
30-
raise BruinError(
31-
f"Invalid {env_var} value '{val}': expected ISO-8601 datetime (YYYY-MM-DDThh:mm:ss)."
32-
)
30+
raise BruinError(f"Invalid {env_var} value '{val}': expected ISO-8601 datetime.")
3331

3432

3533
def _coerce_value(value, type_def: dict):
@@ -95,22 +93,38 @@ class _BruinContext:
9593
def start_date(self) -> "datetime.date | None":
9694
return _parse_date("BRUIN_START_DATE")
9795

98-
@property
99-
def end_date(self) -> "datetime.date | None":
100-
return _parse_date("BRUIN_END_DATE")
101-
10296
@property
10397
def start_datetime(self) -> "datetime.datetime | None":
10498
return _parse_datetime("BRUIN_START_DATETIME")
10599

100+
@property
101+
def start_timestamp(self) -> "datetime.datetime | None":
102+
return _parse_datetime("BRUIN_START_TIMESTAMP")
103+
104+
@property
105+
def end_date(self) -> "datetime.date | None":
106+
return _parse_date("BRUIN_END_DATE")
107+
106108
@property
107109
def end_datetime(self) -> "datetime.datetime | None":
108110
return _parse_datetime("BRUIN_END_DATETIME")
109111

112+
@property
113+
def end_timestamp(self) -> "datetime.datetime | None":
114+
return _parse_datetime("BRUIN_END_TIMESTAMP")
115+
110116
@property
111117
def execution_date(self) -> "datetime.date | None":
112118
return _parse_date("BRUIN_EXECUTION_DATE")
113119

120+
@property
121+
def execution_datetime(self) -> "datetime.datetime | None":
122+
return _parse_datetime("BRUIN_EXECUTION_DATETIME")
123+
124+
@property
125+
def execution_timestamp(self) -> "datetime.datetime | None":
126+
return _parse_datetime("BRUIN_EXECUTION_TIMESTAMP")
127+
114128
@property
115129
def run_id(self) -> "str | None":
116130
return os.environ.get("BRUIN_RUN_ID")
@@ -131,6 +145,10 @@ def connection(self) -> "str | None":
131145
def is_full_refresh(self) -> bool:
132146
return os.environ.get("BRUIN_FULL_REFRESH") == "1"
133147

148+
@property
149+
def commit_hash(self) -> "str | None":
150+
return os.environ.get("BRUIN_COMMIT_HASH")
151+
134152
@property
135153
def vars(self) -> dict:
136154
val = os.environ.get("BRUIN_VARS")

tests/test_context.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,23 @@ def test_returns_none_when_missing(self, monkeypatch):
3737
assert context.start_datetime is None
3838

3939

40+
class TestStartTimestamp:
41+
def test_returns_datetime_with_tz(self, monkeypatch):
42+
monkeypatch.setenv("BRUIN_START_TIMESTAMP", "2024-01-15T10:30:00.000000+00:00")
43+
result = context.start_timestamp
44+
assert result == datetime.datetime(2024, 1, 15, 10, 30, 0, tzinfo=datetime.timezone.utc)
45+
46+
def test_returns_datetime_with_non_utc_tz(self, monkeypatch):
47+
monkeypatch.setenv("BRUIN_START_TIMESTAMP", "2024-01-15T10:30:00.000000+05:30")
48+
result = context.start_timestamp
49+
tz = datetime.timezone(datetime.timedelta(hours=5, minutes=30))
50+
assert result == datetime.datetime(2024, 1, 15, 10, 30, 0, tzinfo=tz)
51+
52+
def test_returns_none_when_missing(self, monkeypatch):
53+
monkeypatch.delenv("BRUIN_START_TIMESTAMP", raising=False)
54+
assert context.start_timestamp is None
55+
56+
4057
class TestEndDatetime:
4158
def test_returns_datetime(self, monkeypatch):
4259
monkeypatch.setenv("BRUIN_END_DATETIME", "2024-06-30T23:59:59")
@@ -47,6 +64,17 @@ def test_returns_none_when_missing(self, monkeypatch):
4764
assert context.end_datetime is None
4865

4966

67+
class TestEndTimestamp:
68+
def test_returns_datetime_with_tz(self, monkeypatch):
69+
monkeypatch.setenv("BRUIN_END_TIMESTAMP", "2024-06-30T23:59:59.000000+00:00")
70+
result = context.end_timestamp
71+
assert result == datetime.datetime(2024, 6, 30, 23, 59, 59, tzinfo=datetime.timezone.utc)
72+
73+
def test_returns_none_when_missing(self, monkeypatch):
74+
monkeypatch.delenv("BRUIN_END_TIMESTAMP", raising=False)
75+
assert context.end_timestamp is None
76+
77+
5078
class TestExecutionDate:
5179
def test_returns_date(self, monkeypatch):
5280
monkeypatch.setenv("BRUIN_EXECUTION_DATE", "2024-03-01")
@@ -57,6 +85,27 @@ def test_returns_none_when_missing(self, monkeypatch):
5785
assert context.execution_date is None
5886

5987

88+
class TestExecutionDatetime:
89+
def test_returns_datetime(self, monkeypatch):
90+
monkeypatch.setenv("BRUIN_EXECUTION_DATETIME", "2024-03-01T12:00:00")
91+
assert context.execution_datetime == datetime.datetime(2024, 3, 1, 12, 0, 0)
92+
93+
def test_returns_none_when_missing(self, monkeypatch):
94+
monkeypatch.delenv("BRUIN_EXECUTION_DATETIME", raising=False)
95+
assert context.execution_datetime is None
96+
97+
98+
class TestExecutionTimestamp:
99+
def test_returns_datetime_with_tz(self, monkeypatch):
100+
monkeypatch.setenv("BRUIN_EXECUTION_TIMESTAMP", "2024-03-01T12:00:00.000000+00:00")
101+
result = context.execution_timestamp
102+
assert result == datetime.datetime(2024, 3, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)
103+
104+
def test_returns_none_when_missing(self, monkeypatch):
105+
monkeypatch.delenv("BRUIN_EXECUTION_TIMESTAMP", raising=False)
106+
assert context.execution_timestamp is None
107+
108+
60109
class TestRunId:
61110
def test_returns_string(self, monkeypatch):
62111
monkeypatch.setenv("BRUIN_RUN_ID", "abc-123")
@@ -97,6 +146,16 @@ def test_returns_none_when_missing(self, monkeypatch):
97146
assert context.connection is None
98147

99148

149+
class TestCommitHash:
150+
def test_returns_string(self, monkeypatch):
151+
monkeypatch.setenv("BRUIN_COMMIT_HASH", "abc1234def5678")
152+
assert context.commit_hash == "abc1234def5678"
153+
154+
def test_returns_none_when_missing(self, monkeypatch):
155+
monkeypatch.delenv("BRUIN_COMMIT_HASH", raising=False)
156+
assert context.commit_hash is None
157+
158+
100159
class TestIsFullRefresh:
101160
def test_true_when_set_to_1(self, monkeypatch):
102161
monkeypatch.setenv("BRUIN_FULL_REFRESH", "1")

0 commit comments

Comments
 (0)