pythia-plsql 0.11.0__tar.gz → 0.11.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.
- {pythia_plsql-0.11.0/scripts/pythia_plsql.egg-info → pythia_plsql-0.11.2}/PKG-INFO +1 -1
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/pyproject.toml +1 -1
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/scripts/pythia.py +47 -11
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2/scripts/pythia_plsql.egg-info}/PKG-INFO +1 -1
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/tests/test_phase3.py +102 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/LICENSE +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/README.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/compile-errors.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/current-schema.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/dependencies.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/impact.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/invalid-objects.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/name-occupants.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/object-names.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/object-source.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/plscope-enabled.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/plscope-statements.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/plscope-usages.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/session-privileges.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/similar-candidates.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/queries/source.sql +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/scripts/pythia_plsql.egg-info/SOURCES.txt +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/scripts/pythia_plsql.egg-info/dependency_links.txt +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/scripts/pythia_plsql.egg-info/entry_points.txt +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/scripts/pythia_plsql.egg-info/requires.txt +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/scripts/pythia_plsql.egg-info/top_level.txt +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/setup.cfg +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/pythia-apply/SKILL.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/pythia-conventions/SKILL.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/pythia-explore/SKILL.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/pythia-explore/reference/data-dictionary.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/pythia-impact/SKILL.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/pythia-review/SKILL.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/pythia-review/reference/antipatterns.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/pythia-setup/SKILL.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/pythia-skill-author/SKILL.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/pythia-spec/SKILL.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/pythia-write/SKILL.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/pythia-write/reference/patterns.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/using-pythia/SKILL.md +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/tests/test_install.py +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/tests/test_phase1.py +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/tests/test_phase2.py +0 -0
- {pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/tests/test_phase5.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythia-plsql
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.2
|
|
4
4
|
Summary: PL/SQL development for AI agents on Oracle Database - expert data-dictionary queries, impact analysis, and a snapshot-verified write path with honest rollback.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/thaildhe172591/pythia
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pythia-plsql"
|
|
7
|
-
version = "0.11.
|
|
7
|
+
version = "0.11.2"
|
|
8
8
|
description = "PL/SQL development for AI agents on Oracle Database - expert data-dictionary queries, impact analysis, and a snapshot-verified write path with honest rollback."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -713,7 +713,12 @@ def plscope_message(name, has_any_data):
|
|
|
713
713
|
GROUPS = ("plsql_source", "data_dml", "structural", "grants", "session")
|
|
714
714
|
|
|
715
715
|
PLSQL_SOURCE_RE = re.compile(
|
|
716
|
-
|
|
716
|
+
# NONEDITIONABLE, not "noeditionable": Oracle's keyword has the middle N,
|
|
717
|
+
# and missing it dropped every NONEDITIONABLE object into structural —
|
|
718
|
+
# deny by default, and counted by semicolons, so a PL/SQL body read as
|
|
719
|
+
# "more than one statement". Both spellings are accepted here; only the
|
|
720
|
+
# database decides whether the statement is valid.
|
|
721
|
+
r"^create\s+(?:or\s+replace\s+)?(?:(?:non?)?editionable\s+|(?:no)?force\s+)*"
|
|
717
722
|
r"(procedure|function|package\s+body|package|trigger|view|type\s+body|type)\b",
|
|
718
723
|
re.I | re.S)
|
|
719
724
|
|
|
@@ -1231,12 +1236,29 @@ def prune_expired_grants(root, now=None):
|
|
|
1231
1236
|
return gone
|
|
1232
1237
|
|
|
1233
1238
|
|
|
1234
|
-
|
|
1239
|
+
EDITIONABLE_KW_RE = re.compile(
|
|
1240
|
+
r"^create\s+(?:or\s+replace\s+)?((?:non?)?editionable)\b", re.I)
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
def create_prefix(after_text):
|
|
1244
|
+
"""The CREATE header ALL_SOURCE does not store, rebuilt for the version
|
|
1245
|
+
held in the database. The editionable keyword is read off the file being
|
|
1246
|
+
applied, because the database's own answer is not in ALL_SOURCE and
|
|
1247
|
+
Oracle refuses to *change* the property through CREATE OR REPLACE
|
|
1248
|
+
(ORA-38824) — so a file that applies cleanly already spells the property
|
|
1249
|
+
the object has. Getting this wrong costs twice: a restore that fails
|
|
1250
|
+
exactly when it is needed, and a phantom line in the diff the developer
|
|
1251
|
+
approves."""
|
|
1252
|
+
m = EDITIONABLE_KW_RE.match(skip_leading_noise(after_text))
|
|
1253
|
+
return "CREATE OR REPLACE " + (f"{m.group(1).upper()} " if m else "")
|
|
1254
|
+
|
|
1255
|
+
|
|
1256
|
+
def render_restore(obj_type, name, before_text, after_text=""):
|
|
1235
1257
|
"""The statement that puts things back. For an object that did not exist,
|
|
1236
1258
|
undo means DROP — a genuinely different promise than restoring source, so
|
|
1237
1259
|
the caller records created=True and the report says it plainly."""
|
|
1238
1260
|
if before_text.strip():
|
|
1239
|
-
return
|
|
1261
|
+
return create_prefix(after_text) + before_text.rstrip() + "\n"
|
|
1240
1262
|
return f"DROP {obj_type} {name}\n"
|
|
1241
1263
|
|
|
1242
1264
|
|
|
@@ -1256,7 +1278,7 @@ def write_journal_entry(root, obj_type, name, before, after, meta, now=None):
|
|
|
1256
1278
|
d.mkdir(parents=True)
|
|
1257
1279
|
(d / "before.sql").write_text(before, encoding="utf-8")
|
|
1258
1280
|
(d / "after.sql").write_text(after, encoding="utf-8")
|
|
1259
|
-
(d / "restore.sql").write_text(render_restore(obj_type, name, before),
|
|
1281
|
+
(d / "restore.sql").write_text(render_restore(obj_type, name, before, after),
|
|
1260
1282
|
encoding="utf-8")
|
|
1261
1283
|
full = {"object": name, "type": obj_type, "created": not before.strip(),
|
|
1262
1284
|
"entry": eid, **meta}
|
|
@@ -2105,8 +2127,10 @@ def run_apply(conn, schema, ns, file_text, origin=None):
|
|
|
2105
2127
|
|
|
2106
2128
|
# 3. PREVIEW — diff like against like: ALL_SOURCE never stores the
|
|
2107
2129
|
# CREATE OR REPLACE header, so prepend it before comparing, or an
|
|
2108
|
-
# unchanged object would show a phantom two-line change forever.
|
|
2109
|
-
|
|
2130
|
+
# unchanged object would show a phantom two-line change forever. The
|
|
2131
|
+
# editionable keyword is part of that header, or a NONEDITIONABLE file
|
|
2132
|
+
# shows its own first line as a change that is not one.
|
|
2133
|
+
base = (create_prefix(stmt) + db_source) if db_source.strip() else ""
|
|
2110
2134
|
diff_text, changed = render_diff(base, stmt)
|
|
2111
2135
|
warn = privilege_warning(conn, schema, ns.conn_user)
|
|
2112
2136
|
style = (naming_violation(otype, name, load_conventions(ns.project_root))
|
|
@@ -2307,10 +2331,17 @@ def token_mismatch_message(ns, row_set):
|
|
|
2307
2331
|
base = ("The confirmation token does not match: the file or the "
|
|
2308
2332
|
"database object changed since that preview. Preview again:\n"
|
|
2309
2333
|
f" {invocation()} apply {ns.file}")
|
|
2310
|
-
if not row_set:
|
|
2311
|
-
return base
|
|
2312
2334
|
_, meta = find_preview_by_token(ns.project_root,
|
|
2313
2335
|
str(ns.confirm).strip().lower())
|
|
2336
|
+
if (meta or {}).get("applied"):
|
|
2337
|
+
# the token names a preview that already ran. "the file changed" is
|
|
2338
|
+
# the wrong story, and sends the agent hunting a difference that is
|
|
2339
|
+
# not there
|
|
2340
|
+
return (f"Token {str(ns.confirm).strip().lower()} was already applied "
|
|
2341
|
+
"— one approval, one write.\nPreview again, then get the new "
|
|
2342
|
+
f"token approved:\n {invocation()} apply {ns.file}")
|
|
2343
|
+
if not row_set:
|
|
2344
|
+
return base
|
|
2314
2345
|
then = (meta or {}).get("row_set") or {}
|
|
2315
2346
|
if not then or fingerprint_text(then) == fingerprint_text(row_set):
|
|
2316
2347
|
return base # the file moved, not the rows
|
|
@@ -3443,10 +3474,15 @@ def build_parser():
|
|
|
3443
3474
|
def main(argv=None):
|
|
3444
3475
|
argv = sys.argv[1:] if argv is None else argv
|
|
3445
3476
|
forbid_write_flag(argv)
|
|
3446
|
-
|
|
3477
|
+
# stdin too, and before anything reads it: the AskUserQuestion hook
|
|
3478
|
+
# payload is UTF-8, and Windows would decode it as cp1252 — an em dash
|
|
3479
|
+
# arrives as mojibake and no approval card ever matches again.
|
|
3480
|
+
# line_buffering keeps stdout in order with stderr when both are pipes,
|
|
3481
|
+
# so a driver error lands after the preview it belongs to, not above it.
|
|
3482
|
+
for stream in (sys.stdout, sys.stderr, sys.stdin):
|
|
3447
3483
|
try:
|
|
3448
|
-
stream.reconfigure(encoding="utf-8")
|
|
3449
|
-
except AttributeError:
|
|
3484
|
+
stream.reconfigure(encoding="utf-8", line_buffering=True)
|
|
3485
|
+
except (AttributeError, ValueError):
|
|
3450
3486
|
pass
|
|
3451
3487
|
ns = build_parser().parse_args(argv)
|
|
3452
3488
|
# refusals that need no database come first — an unreachable or locked
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythia-plsql
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.2
|
|
4
4
|
Summary: PL/SQL development for AI agents on Oracle Database - expert data-dictionary queries, impact analysis, and a snapshot-verified write path with honest rollback.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/thaildhe172591/pythia
|
|
@@ -32,6 +32,11 @@ def test_classify_groups():
|
|
|
32
32
|
assert c("alter table t_order add (x number)") == "structural" # session tested first
|
|
33
33
|
assert c("CREATE OR REPLACE PROCEDURE p AS BEGIN NULL; END;") == "plsql_source"
|
|
34
34
|
assert c("create or replace editionable package body pkg_order as end;") == "plsql_source"
|
|
35
|
+
# Oracle's keyword is NONEDITIONABLE, with the middle N. Missing it sent
|
|
36
|
+
# every such object to structural — deny by default, and counted by
|
|
37
|
+
# semicolons, so a PL/SQL body came back as "more than one statement".
|
|
38
|
+
assert c("create or replace NONEDITIONABLE procedure p as begin null; end;") == "plsql_source"
|
|
39
|
+
assert c("CREATE OR REPLACE NONEDITIONABLE PACKAGE BODY pkg AS END;") == "plsql_source"
|
|
35
40
|
assert c("CREATE OR REPLACE FORCE VIEW v AS SELECT 1 FROM dual") == "plsql_source"
|
|
36
41
|
assert c("CREATE TABLE t (x number)") == "structural" # other CREATE
|
|
37
42
|
assert c("insert into t values (1)") == "data_dml"
|
|
@@ -54,6 +59,27 @@ def test_parse_object():
|
|
|
54
59
|
assert (t, n, s) == ("TYPE BODY", "T_THING", None)
|
|
55
60
|
t, n, s = pythia.parse_object("CREATE OR REPLACE TRIGGER trg_x BEFORE INSERT ON t BEGIN NULL; END;")
|
|
56
61
|
assert (t, n, s) == ("TRIGGER", "TRG_X", None)
|
|
62
|
+
# the identity drives the snapshot, so the editionable keyword must not
|
|
63
|
+
# shift what is parsed out of the statement
|
|
64
|
+
t, n, s = pythia.parse_object(
|
|
65
|
+
"CREATE OR REPLACE NONEDITIONABLE PROCEDURE core_bh.pbh_file_nh AS BEGIN NULL; END;")
|
|
66
|
+
assert (t, n, s) == ("PROCEDURE", "PBH_FILE_NH", "CORE_BH")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_restore_keeps_the_editionable_property():
|
|
70
|
+
"""ALL_SOURCE holds no CREATE prefix, so the restore builds one. On an
|
|
71
|
+
editions-enabled schema a bare CREATE OR REPLACE over a NONEDITIONABLE
|
|
72
|
+
object is ORA-38824 — an undo that fails exactly when it is needed."""
|
|
73
|
+
body = "PROCEDURE p AS BEGIN NULL; END;"
|
|
74
|
+
assert pythia.render_restore("PROCEDURE", "P", body, "CREATE OR REPLACE " + body) \
|
|
75
|
+
== "CREATE OR REPLACE " + body + "\n"
|
|
76
|
+
assert pythia.render_restore(
|
|
77
|
+
"PROCEDURE", "P", body,
|
|
78
|
+
"create or replace noneditionable procedure p as begin null; end;") \
|
|
79
|
+
== "CREATE OR REPLACE NONEDITIONABLE " + body + "\n"
|
|
80
|
+
# a new object still has no source to put back: undo is a DROP
|
|
81
|
+
assert pythia.render_restore("PROCEDURE", "P", "", "CREATE OR REPLACE NONEDITIONABLE PROCEDURE p") \
|
|
82
|
+
== "DROP PROCEDURE P\n"
|
|
57
83
|
|
|
58
84
|
|
|
59
85
|
def test_prepare_statement_terminators():
|
|
@@ -1475,6 +1501,38 @@ def test_apply_reads_a_bom_file_as_the_statement_it_is():
|
|
|
1475
1501
|
assert "approve --card" in buf.getvalue() # it previewed
|
|
1476
1502
|
|
|
1477
1503
|
|
|
1504
|
+
def test_apply_previews_a_noneditionable_object_end_to_end():
|
|
1505
|
+
"""The reported bug, from the outside: NONEDITIONABLE missed the
|
|
1506
|
+
classifier, fell through to structural — which counts statements by
|
|
1507
|
+
semicolon — and a PL/SQL body came back as 'more than one statement'.
|
|
1508
|
+
The only escape was dropping the keyword, which Oracle then met with
|
|
1509
|
+
ORA-38824. Preview must produce a token, and the restore must keep the
|
|
1510
|
+
property."""
|
|
1511
|
+
import contextlib
|
|
1512
|
+
import io
|
|
1513
|
+
import pathlib
|
|
1514
|
+
src = ("CREATE OR REPLACE NONEDITIONABLE PACKAGE BODY pkg_order AS\n"
|
|
1515
|
+
" new line;\nEND;\n/\n")
|
|
1516
|
+
with tempfile.TemporaryDirectory() as td:
|
|
1517
|
+
f = pathlib.Path(td) / "f.sql"
|
|
1518
|
+
f.write_text(src, encoding="utf-8")
|
|
1519
|
+
buf = io.StringIO()
|
|
1520
|
+
with contextlib.redirect_stdout(buf):
|
|
1521
|
+
pythia.cmd_apply(FakeConn(base_script()), "APP",
|
|
1522
|
+
apply_ns(td, file=str(f)))
|
|
1523
|
+
out = buf.getvalue()
|
|
1524
|
+
assert "more than one statement" not in out
|
|
1525
|
+
assert "approve --card" in out # it previewed
|
|
1526
|
+
entry = pythia.list_journal_entries(td)[0]
|
|
1527
|
+
restore = (pythia.journal_root(td) / entry / "restore.sql").read_text(
|
|
1528
|
+
encoding="utf-8")
|
|
1529
|
+
assert restore.startswith("CREATE OR REPLACE NONEDITIONABLE ")
|
|
1530
|
+
# and the keyword is not shown as a change it is not: ALL_SOURCE holds
|
|
1531
|
+
# no header, so the diff's base has to carry the same one
|
|
1532
|
+
assert "-CREATE OR REPLACE" not in out and "+CREATE OR REPLACE" not in out
|
|
1533
|
+
assert "2 lines changed" in out # the body line, not the header
|
|
1534
|
+
|
|
1535
|
+
|
|
1478
1536
|
def test_apply_refuses_when_the_session_schema_is_not_the_configured_one():
|
|
1479
1537
|
"""A proxy into another user, or a stale `schema` in connections.json:
|
|
1480
1538
|
an unqualified CREATE would land in one schema while pythia snapshots
|
|
@@ -1507,5 +1565,49 @@ def main():
|
|
|
1507
1565
|
print("OK")
|
|
1508
1566
|
|
|
1509
1567
|
|
|
1568
|
+
|
|
1569
|
+
|
|
1570
|
+
def test_hook_matches_a_card_with_a_non_ascii_dash_through_real_stdin():
|
|
1571
|
+
"""The whole hook, end to end, the way Claude Code runs it: UTF-8 bytes on
|
|
1572
|
+
a pipe. Windows decodes stdin as cp1252 unless told otherwise, and the em
|
|
1573
|
+
dash in the structural card would arrive as mojibake — no card would ever
|
|
1574
|
+
match again. Subprocess on purpose: an in-process StringIO is already
|
|
1575
|
+
decoded and cannot fail this way."""
|
|
1576
|
+
import subprocess
|
|
1577
|
+
with tempfile.TemporaryDirectory() as td:
|
|
1578
|
+
stmt = "ALTER PROCEDURE PKG_ORDER COMPILE;"
|
|
1579
|
+
pythia.write_journal_entry(
|
|
1580
|
+
td, "STRUCTURAL", "STATEMENT", "", stmt,
|
|
1581
|
+
{"token": "dc8d27", "connection": "DEV", "schema": "APP",
|
|
1582
|
+
"group": "structural", "applied": False})
|
|
1583
|
+
_, _, body = pythia.approval_card(td, "dc8d27")
|
|
1584
|
+
card = "python -m pythia approve dc8d27\n" + "\n".join(body)
|
|
1585
|
+
assert "—" in card, "the structural card is the non-ASCII one"
|
|
1586
|
+
payload = json.dumps(_hook_payload(card, "Approve"), ensure_ascii=False)
|
|
1587
|
+
p = subprocess.run(
|
|
1588
|
+
[sys.executable,
|
|
1589
|
+
str(pathlib.Path(pythia.__file__).resolve()), "approve", "--hook"],
|
|
1590
|
+
input=payload.encode("utf-8"), cwd=td, capture_output=True)
|
|
1591
|
+
assert p.returncode == 0, p.stderr.decode("utf-8", "replace")
|
|
1592
|
+
g = pythia.read_grant(td, "dc8d27")
|
|
1593
|
+
assert g and g["approver"] == "chat", p.stdout.decode("utf-8", "replace")
|
|
1594
|
+
|
|
1595
|
+
|
|
1596
|
+
def test_token_mismatch_names_an_already_applied_preview():
|
|
1597
|
+
"""A spent token is not a moved file. Saying 'the file or the object
|
|
1598
|
+
changed' sends the agent looking for a difference that is not there."""
|
|
1599
|
+
import argparse
|
|
1600
|
+
with tempfile.TemporaryDirectory() as td:
|
|
1601
|
+
pythia.write_journal_entry(
|
|
1602
|
+
td, "PACKAGE BODY", "PKG_ORDER", OLD_SRC, NEW_FILE,
|
|
1603
|
+
{"token": "7f3a91", "connection": "DEV", "schema": "APP",
|
|
1604
|
+
"group": "plsql_source", "applied": True})
|
|
1605
|
+
ns = argparse.Namespace(project_root=td, confirm="7f3a91",
|
|
1606
|
+
file="pkg.sql")
|
|
1607
|
+
msg = pythia.token_mismatch_message(ns, None)
|
|
1608
|
+
assert "already applied" in msg and "changed since that preview" not in msg
|
|
1609
|
+
|
|
1610
|
+
|
|
1611
|
+
|
|
1510
1612
|
if __name__ == "__main__":
|
|
1511
1613
|
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/scripts/pythia_plsql.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythia_plsql-0.11.0 → pythia_plsql-0.11.2}/skills/pythia-explore/reference/data-dictionary.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|