-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add schedule-owned logical parameter bindings #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
18f28bb
feat(scheduler): add logical parameter bindings
badry-dev 0bfd889
feat(schedules): add binding editor and run preview
badry-dev a0edcf7
docs(scheduler): document logical parameter bindings
badry-dev 4c478d9
test(scheduler): align restacked regression fixtures
badry-dev a1daf6f
fix(schedules): protect attached endpoint contracts
badry-dev 18ef3a2
fix(schedules): clear stale run previews
badry-dev 32cd24e
fix(schedules): validate numeric literal bindings
badry-dev bd37078
merge: reconcile scheduler bindings with endpoint fixes
badry-dev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
backend/alembic/versions/e4a6c2d9f801_add_schedule_parameter_bindings.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| """Add schedule-owned parameter bindings and logical run audit context. | ||
|
|
||
| Revision ID: e4a6c2d9f801 | ||
| Revises: c7e91a4f2d60 | ||
| Create Date: 2026-08-30 | ||
| """ | ||
|
|
||
| from collections.abc import Sequence | ||
|
|
||
| import sqlalchemy as sa | ||
| from alembic import op | ||
| from sqlalchemy.dialects import postgresql | ||
|
|
||
| revision: str = "e4a6c2d9f801" | ||
| down_revision: str | None = "c7e91a4f2d60" | ||
| branch_labels: str | Sequence[str] | None = None | ||
| depends_on: str | Sequence[str] | None = None | ||
|
|
||
|
|
||
| def upgrade() -> None: | ||
| op.add_column( | ||
| "schedules", | ||
| sa.Column("timezone", sa.String(length=64), server_default="UTC", nullable=False), | ||
| ) | ||
| op.add_column( | ||
| "schedules", | ||
| sa.Column( | ||
| "parameter_bindings_json", | ||
| postgresql.JSONB(astext_type=sa.Text()), | ||
| server_default=sa.text("'{}'::jsonb"), | ||
| nullable=False, | ||
| ), | ||
| ) | ||
| op.add_column( | ||
| "schedules", | ||
| sa.Column( | ||
| "window_config_json", | ||
| postgresql.JSONB(astext_type=sa.Text()), | ||
| nullable=True, | ||
| ), | ||
| ) | ||
|
|
||
| # Preserve the behavior of existing schedules by copying endpoint defaults | ||
| # into explicit schedule-owned bindings. Any legacy parameter without a | ||
| # resolvable default remains absent and will be reported when the schedule | ||
| # is edited, previewed, or executed rather than silently inventing a value. | ||
| op.execute( | ||
| """ | ||
| UPDATE schedules AS schedule | ||
| SET parameter_bindings_json = COALESCE(migrated.bindings, '{}'::jsonb) | ||
| FROM ( | ||
| SELECT | ||
| endpoint.id AS endpoint_id, | ||
| jsonb_object_agg( | ||
| parameter.name, | ||
| CASE | ||
| WHEN parameter.descriptor->>'default_expression' = 'today' | ||
| THEN jsonb_build_object('source', 'run_date') | ||
| WHEN parameter.descriptor->>'default_expression' = 'yesterday' | ||
| THEN jsonb_build_object( | ||
| 'source', 'relative_date', 'offset_days', -1 | ||
| ) | ||
| WHEN COALESCE( | ||
| (parameter.descriptor->>'default_is_null')::boolean, | ||
| false | ||
| ) | ||
| THEN jsonb_build_object('source', 'null') | ||
| WHEN parameter.descriptor ? 'default' | ||
| AND parameter.descriptor->'default' <> 'null'::jsonb | ||
| THEN jsonb_build_object( | ||
| 'source', 'literal', | ||
| 'value', parameter.descriptor->'default' | ||
| ) | ||
| ELSE NULL | ||
| END | ||
| ) FILTER ( | ||
| WHERE parameter.descriptor->>'default_expression' IN ('today', 'yesterday') | ||
| OR COALESCE( | ||
| (parameter.descriptor->>'default_is_null')::boolean, | ||
| false | ||
| ) | ||
| OR ( | ||
| parameter.descriptor ? 'default' | ||
| AND parameter.descriptor->'default' <> 'null'::jsonb | ||
| ) | ||
| ) AS bindings | ||
| FROM endpoints AS endpoint | ||
| CROSS JOIN LATERAL jsonb_each( | ||
| COALESCE(endpoint.param_schema_json, '{}'::jsonb) | ||
| ) AS parameter(name, descriptor) | ||
| GROUP BY endpoint.id | ||
| ) AS migrated | ||
| WHERE schedule.endpoint_id = migrated.endpoint_id | ||
| """ | ||
| ) | ||
|
|
||
| op.add_column( | ||
| "job_runs", | ||
| sa.Column("scheduled_for", sa.DateTime(timezone=True), nullable=True), | ||
| ) | ||
| op.add_column("job_runs", sa.Column("logical_date", sa.Date(), nullable=True)) | ||
| op.add_column("job_runs", sa.Column("window_start", sa.Date(), nullable=True)) | ||
| op.add_column("job_runs", sa.Column("window_end", sa.Date(), nullable=True)) | ||
| op.add_column( | ||
| "job_runs", | ||
| sa.Column( | ||
| "resolved_params_json", | ||
| postgresql.JSONB(astext_type=sa.Text()), | ||
| nullable=True, | ||
| ), | ||
| ) | ||
| op.add_column("job_runs", sa.Column("trigger_source", sa.String(length=20), nullable=True)) | ||
| op.add_column("job_runs", sa.Column("binding_hash", sa.String(length=64), nullable=True)) | ||
| op.create_unique_constraint( | ||
| "uq_job_runs_schedule_scheduled_for", | ||
| "job_runs", | ||
| ["schedule_id", "scheduled_for"], | ||
| ) | ||
|
|
||
|
|
||
| def downgrade() -> None: | ||
| op.drop_constraint("uq_job_runs_schedule_scheduled_for", "job_runs", type_="unique") | ||
| op.drop_column("job_runs", "binding_hash") | ||
| op.drop_column("job_runs", "trigger_source") | ||
| op.drop_column("job_runs", "resolved_params_json") | ||
| op.drop_column("job_runs", "window_end") | ||
| op.drop_column("job_runs", "window_start") | ||
| op.drop_column("job_runs", "logical_date") | ||
| op.drop_column("job_runs", "scheduled_for") | ||
| op.drop_column("schedules", "window_config_json") | ||
| op.drop_column("schedules", "parameter_bindings_json") | ||
| op.drop_column("schedules", "timezone") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Guard the boolean cast in the backfill.
(parameter.descriptor->>'default_is_null')::booleanfails the whole migration if any stored descriptor holds a non-boolean text value fordefault_is_null. The column is application-written JSONB, so a legacy or hand-edited descriptor value such as"1","Y", or""abortsalembic upgrade. The same expression appears in both theCASEand theFILTER, so both sites need the guard.🛡️ Proposed fix using a jsonb type check
📝 Committable suggestion
🤖 Prompt for AI Agents