flask-postgresql-audit 1.1.1__tar.gz → 1.1.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.
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/.gitignore +1 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/CHANGES.md +6 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/PKG-INFO +25 -2
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/README.md +23 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/__init__.py +1 -1
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/alembic/entities.py +9 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/alembic/setup.py +1 -0
- flask_postgresql_audit-1.1.2/flask_postgresql_audit/templates/jsonb_array_subtract.sql +29 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/tests/app.py +12 -0
- flask_postgresql_audit-1.1.2/tests/test_document_staging.py +106 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/tests/test_operators.py +32 -0
- flask_postgresql_audit-1.1.2/tests/test_pg_aggregate.py +96 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/LICENSE +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/alembic/__init__.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/alembic/schema.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/base.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/core.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/extensions/__init__.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/extensions/alembic_utils/__init__.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/extensions/alembic_utils/pg_aggregate.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/extensions/document_staging/__init__.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/extensions/document_staging/enum.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/extensions/document_staging/listener.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/extensions/document_staging/model.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/models.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/templates/audit_trigger_delete.sql +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/templates/audit_trigger_insert.sql +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/templates/audit_trigger_update.sql +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/templates/create_activity.sql +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/templates/get_pk_values.sql +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/templates/get_setting.sql +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/templates/get_table_relid.sql +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/templates/jsonb_subtract.sql +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/typing.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/utils.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/pyproject.toml +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/tests/__init__.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/tests/conftest.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/tests/migrations/README +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/tests/migrations/alembic.ini +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/tests/migrations/env.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/tests/migrations/script.py.mako +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/tests/test_defaults.py +0 -0
- {flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/tests/utils.py +0 -0
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
Here you can see the full list of changes between each Flask-PostgreSQL-Audit
|
|
4
4
|
release.
|
|
5
5
|
|
|
6
|
+
## 1.1.2 (2026-08-21)
|
|
7
|
+
|
|
8
|
+
- Add dedicated `jsonb_array_subtract()` function to support set difference operations on JSONB arrays.
|
|
9
|
+
- Add unit tests for `DocumentStaging` and `PGAggregate` extensions.
|
|
10
|
+
- Add `reorder_migration_ops` troubleshooting guide to `README.md`.
|
|
11
|
+
|
|
6
12
|
## 1.1.1 (2026-08-08)
|
|
7
13
|
|
|
8
14
|
- Fix test teardown downgrade error in migration listener.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: flask-postgresql-audit
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: Versioning and auditing extension for PostgreSQL and SQLAlchemy.
|
|
5
5
|
Project-URL: Homepage, https://github.com/higamigu/flask-postgresql-audit
|
|
6
6
|
Author-email: Higamigu <higamigu@gmail.com>, Konsta Vesterinen <konsta@fastmonkeys.com>
|
|
@@ -245,6 +245,29 @@ The `fetch_activity()` method works with:
|
|
|
245
245
|
- A single instance: `audit.fetch_activity(my_instance)`
|
|
246
246
|
- Multiple instances: `audit.fetch_activity([instance1, instance2])`
|
|
247
247
|
|
|
248
|
+
### Migration dependency or trigger ordering issues?
|
|
249
|
+
|
|
250
|
+
If Alembic migration generation creates triggers before tables or schema dependencies:
|
|
251
|
+
|
|
252
|
+
Use `reorder_migration_ops` and `chain_revision_directives` in `alembic/env.py`:
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
from flask_postgresql_audit.alembic import (
|
|
256
|
+
chain_revision_directives,
|
|
257
|
+
reorder_migration_ops,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
context.configure(
|
|
261
|
+
connection=connection,
|
|
262
|
+
target_metadata=target_metadata,
|
|
263
|
+
include_schemas=True,
|
|
264
|
+
process_revision_directives=chain_revision_directives(
|
|
265
|
+
reorder_migration_ops,
|
|
266
|
+
process_revision_directives,
|
|
267
|
+
),
|
|
268
|
+
)
|
|
269
|
+
```
|
|
270
|
+
|
|
248
271
|
## Extensions
|
|
249
272
|
|
|
250
273
|
### Document Staging
|
|
@@ -218,6 +218,29 @@ The `fetch_activity()` method works with:
|
|
|
218
218
|
- A single instance: `audit.fetch_activity(my_instance)`
|
|
219
219
|
- Multiple instances: `audit.fetch_activity([instance1, instance2])`
|
|
220
220
|
|
|
221
|
+
### Migration dependency or trigger ordering issues?
|
|
222
|
+
|
|
223
|
+
If Alembic migration generation creates triggers before tables or schema dependencies:
|
|
224
|
+
|
|
225
|
+
Use `reorder_migration_ops` and `chain_revision_directives` in `alembic/env.py`:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
from flask_postgresql_audit.alembic import (
|
|
229
|
+
chain_revision_directives,
|
|
230
|
+
reorder_migration_ops,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
context.configure(
|
|
234
|
+
connection=connection,
|
|
235
|
+
target_metadata=target_metadata,
|
|
236
|
+
include_schemas=True,
|
|
237
|
+
process_revision_directives=chain_revision_directives(
|
|
238
|
+
reorder_migration_ops,
|
|
239
|
+
process_revision_directives,
|
|
240
|
+
),
|
|
241
|
+
)
|
|
242
|
+
```
|
|
243
|
+
|
|
221
244
|
## Extensions
|
|
222
245
|
|
|
223
246
|
### Document Staging
|
|
@@ -81,3 +81,12 @@ def jsonb_subtract_factory(**context):
|
|
|
81
81
|
signature="jsonb_subtract(arg1 jsonb, arg2 jsonb)",
|
|
82
82
|
definition=sql["definition"],
|
|
83
83
|
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def jsonb_array_subtract_factory(**context):
|
|
87
|
+
sql = parse_template("jsonb_array_subtract.sql", **context)
|
|
88
|
+
return PGFunction(
|
|
89
|
+
schema=context["schema_name"],
|
|
90
|
+
signature="jsonb_array_subtract(arg1 jsonb, arg2 jsonb)",
|
|
91
|
+
definition=sql["definition"],
|
|
92
|
+
)
|
|
@@ -128,6 +128,7 @@ def register_core_entities(audit: "PostgreSQLAudit"):
|
|
|
128
128
|
audit.pg_audit_entities.add(entities.get_table_relid(**audit.context))
|
|
129
129
|
audit.pg_audit_entities.add(entities.get_setting_factory(**audit.context))
|
|
130
130
|
audit.pg_audit_entities.add(entities.jsonb_subtract_factory(**audit.context))
|
|
131
|
+
audit.pg_audit_entities.add(entities.jsonb_array_subtract_factory(**audit.context))
|
|
131
132
|
audit.pg_audit_entities.add(entities.create_activity_factory(**audit.context))
|
|
132
133
|
|
|
133
134
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
CREATE FUNCTION ${schema_prefix}jsonb_array_subtract(arg1 jsonb, arg2 jsonb) RETURNS jsonb AS $$
|
|
2
|
+
SELECT
|
|
3
|
+
CASE
|
|
4
|
+
WHEN jsonb_typeof(arg1) = 'array' THEN
|
|
5
|
+
COALESCE(
|
|
6
|
+
(
|
|
7
|
+
SELECT jsonb_agg(elem ORDER BY ord)
|
|
8
|
+
FROM jsonb_array_elements(arg1) WITH ORDINALITY AS t(elem, ord)
|
|
9
|
+
WHERE NOT (
|
|
10
|
+
CASE
|
|
11
|
+
WHEN arg2 IS NULL THEN FALSE
|
|
12
|
+
WHEN jsonb_typeof(arg2) = 'array' THEN
|
|
13
|
+
EXISTS (
|
|
14
|
+
SELECT 1
|
|
15
|
+
FROM jsonb_array_elements(arg2) AS old_elem
|
|
16
|
+
WHERE old_elem = elem
|
|
17
|
+
)
|
|
18
|
+
ELSE elem = arg2
|
|
19
|
+
END
|
|
20
|
+
)
|
|
21
|
+
),
|
|
22
|
+
'[]'::jsonb
|
|
23
|
+
)
|
|
24
|
+
WHEN arg1 IS DISTINCT FROM arg2 THEN
|
|
25
|
+
arg1
|
|
26
|
+
ELSE
|
|
27
|
+
NULL
|
|
28
|
+
END
|
|
29
|
+
$$ LANGUAGE SQL;
|
|
@@ -7,6 +7,12 @@ from sqlalchemy import Table
|
|
|
7
7
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
8
8
|
|
|
9
9
|
from flask_postgresql_audit import Audit, PostgreSQLAudit
|
|
10
|
+
from flask_postgresql_audit.extensions.document_staging import (
|
|
11
|
+
DocumentStaging,
|
|
12
|
+
attach_listener,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
attach_listener(actor_id_getter=lambda: "test_actor")
|
|
10
16
|
|
|
11
17
|
|
|
12
18
|
class DefaultConfig:
|
|
@@ -52,6 +58,12 @@ class Article(BaseModel, Audit):
|
|
|
52
58
|
name: Mapped[str]
|
|
53
59
|
|
|
54
60
|
|
|
61
|
+
class Document(BaseModel, DocumentStaging, Audit):
|
|
62
|
+
__tablename__ = "document"
|
|
63
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
64
|
+
title: Mapped[str]
|
|
65
|
+
|
|
66
|
+
|
|
55
67
|
app = Flask(__name__)
|
|
56
68
|
|
|
57
69
|
app.config.from_object(DefaultConfig)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from flask_postgresql_audit.extensions.document_staging import Docstatus
|
|
4
|
+
|
|
5
|
+
from .app import Document, db
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.usefixtures("test_client")
|
|
9
|
+
class TestDocumentStaging:
|
|
10
|
+
def test_document_creation(self):
|
|
11
|
+
doc = Document(title="Doc 1")
|
|
12
|
+
db.session.add(doc)
|
|
13
|
+
db.session.commit()
|
|
14
|
+
|
|
15
|
+
assert doc.docstatus == Docstatus.DRAFT
|
|
16
|
+
assert doc.created_by == "test_actor"
|
|
17
|
+
assert doc.created_on is not None
|
|
18
|
+
|
|
19
|
+
def test_document_bump_lifecycle(self):
|
|
20
|
+
doc = Document(title="Doc 2")
|
|
21
|
+
db.session.add(doc)
|
|
22
|
+
db.session.commit()
|
|
23
|
+
|
|
24
|
+
# DRAFT -> SUBMITTED
|
|
25
|
+
doc.bump()
|
|
26
|
+
db.session.commit()
|
|
27
|
+
assert doc.docstatus == Docstatus.SUBMITTED
|
|
28
|
+
assert doc.submitted_by == "test_actor"
|
|
29
|
+
assert doc.submitted_on is not None
|
|
30
|
+
|
|
31
|
+
# SUBMITTED -> CANCELLED
|
|
32
|
+
doc.bump()
|
|
33
|
+
db.session.commit()
|
|
34
|
+
assert doc.docstatus == Docstatus.CANCELLED
|
|
35
|
+
assert doc.cancelled_by == "test_actor"
|
|
36
|
+
assert doc.cancelled_on is not None
|
|
37
|
+
|
|
38
|
+
# Bumping CANCELLED raises ValueError
|
|
39
|
+
with pytest.raises(ValueError, match="cannot be bumped"):
|
|
40
|
+
doc.bump()
|
|
41
|
+
|
|
42
|
+
def test_document_revise(self):
|
|
43
|
+
doc1 = Document(title="Original")
|
|
44
|
+
db.session.add(doc1)
|
|
45
|
+
db.session.commit()
|
|
46
|
+
|
|
47
|
+
# Cannot revise DRAFT
|
|
48
|
+
with pytest.raises(ValueError, match="cannot be revised"):
|
|
49
|
+
doc1.revise(Document(title="V2"))
|
|
50
|
+
|
|
51
|
+
doc1.bump() # SUBMITTED
|
|
52
|
+
doc1.bump() # CANCELLED
|
|
53
|
+
db.session.commit()
|
|
54
|
+
|
|
55
|
+
doc2 = Document(title="V2")
|
|
56
|
+
revised_doc = doc1.revise(doc2)
|
|
57
|
+
db.session.add(revised_doc)
|
|
58
|
+
db.session.commit()
|
|
59
|
+
|
|
60
|
+
assert doc1.docstatus == Docstatus.REVISED
|
|
61
|
+
assert doc1.revision == doc2
|
|
62
|
+
assert doc2.revision_of == doc1
|
|
63
|
+
assert doc2.docstatus == Docstatus.DRAFT
|
|
64
|
+
|
|
65
|
+
def test_document_delete(self):
|
|
66
|
+
# Delete DRAFT
|
|
67
|
+
doc = Document(title="Draft to delete")
|
|
68
|
+
db.session.add(doc)
|
|
69
|
+
db.session.commit()
|
|
70
|
+
doc_id = doc.id
|
|
71
|
+
|
|
72
|
+
doc.delete(db.session)
|
|
73
|
+
db.session.commit()
|
|
74
|
+
|
|
75
|
+
assert db.session.get(Document, doc_id) is None
|
|
76
|
+
|
|
77
|
+
# Delete non-DRAFT raises ValueError
|
|
78
|
+
doc_submitted = Document(title="Submitted")
|
|
79
|
+
db.session.add(doc_submitted)
|
|
80
|
+
db.session.commit()
|
|
81
|
+
doc_submitted.bump()
|
|
82
|
+
db.session.commit()
|
|
83
|
+
|
|
84
|
+
with pytest.raises(ValueError, match="cannot be deleted"):
|
|
85
|
+
doc_submitted.delete(db.session)
|
|
86
|
+
|
|
87
|
+
def test_delete_revision_reverts_parent_status(self):
|
|
88
|
+
doc1 = Document(title="Original")
|
|
89
|
+
db.session.add(doc1)
|
|
90
|
+
db.session.commit()
|
|
91
|
+
doc1.bump()
|
|
92
|
+
doc1.bump() # CANCELLED
|
|
93
|
+
db.session.commit()
|
|
94
|
+
|
|
95
|
+
doc2 = Document(title="V2 Revision")
|
|
96
|
+
doc1.revise(doc2)
|
|
97
|
+
db.session.add(doc2)
|
|
98
|
+
db.session.commit()
|
|
99
|
+
|
|
100
|
+
assert doc1.docstatus == Docstatus.REVISED
|
|
101
|
+
|
|
102
|
+
# Delete revision doc2
|
|
103
|
+
doc2.delete(db.session)
|
|
104
|
+
db.session.commit()
|
|
105
|
+
|
|
106
|
+
assert doc1.docstatus == Docstatus.CANCELLED
|
|
@@ -31,3 +31,35 @@ def test_jsonb_subtract(test_client, old, new, result):
|
|
|
31
31
|
)
|
|
32
32
|
== result
|
|
33
33
|
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
JSONB_ARRAY_SUBTRACT = (
|
|
37
|
+
"SELECT {schema}jsonb_array_subtract((:arg1)::jsonb, (:arg2)::jsonb)"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.parametrize(
|
|
42
|
+
("arg1", "arg2", "result"),
|
|
43
|
+
(
|
|
44
|
+
([1, 2, 3], [2], [1, 3]),
|
|
45
|
+
([1, 2, 3], [1, 2, 3], []),
|
|
46
|
+
([1, 2, 3], [4, 5], [1, 2, 3]),
|
|
47
|
+
(["a", "b", "c"], ["b"], ["a", "c"]),
|
|
48
|
+
([], [1, 2], []),
|
|
49
|
+
([1, 2], [], [1, 2]),
|
|
50
|
+
([1, 2, 2, 3], [2], [1, 3]),
|
|
51
|
+
([{"a": 1}, {"b": 2}], [{"a": 1}], [{"b": 2}]),
|
|
52
|
+
([1, 2, 3], 2, [1, 3]),
|
|
53
|
+
([1, 2, 3], None, [1, 2, 3]),
|
|
54
|
+
([1, 2], {"a": 1}, [1, 2]),
|
|
55
|
+
({"a": 1}, [1, 2], {"a": 1}),
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
def test_jsonb_array_subtract(test_client, arg1, arg2, result):
|
|
59
|
+
assert (
|
|
60
|
+
db.session.scalar(
|
|
61
|
+
text(JSONB_ARRAY_SUBTRACT.format(schema=audit.context["schema_prefix"])),
|
|
62
|
+
{"arg1": json.dumps(arg1), "arg2": json.dumps(arg2)},
|
|
63
|
+
)
|
|
64
|
+
== result
|
|
65
|
+
)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from alembic_utils.exceptions import SQLParseFailure
|
|
3
|
+
from sqlalchemy import text
|
|
4
|
+
|
|
5
|
+
from flask_postgresql_audit.extensions.alembic_utils import PGAggregate
|
|
6
|
+
from tests.app import audit, db
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.usefixtures("test_client")
|
|
10
|
+
class TestPGAggregate:
|
|
11
|
+
def test_properties_and_signatures(self):
|
|
12
|
+
agg = PGAggregate(
|
|
13
|
+
schema="public",
|
|
14
|
+
signature="custom_sum(numeric)",
|
|
15
|
+
definition="(SFUNC = numeric_add, STYPE = numeric, INITCOND = '0')",
|
|
16
|
+
)
|
|
17
|
+
assert agg.type_ == "aggregate"
|
|
18
|
+
assert agg.literal_signature == '"custom_sum"(numeric)'
|
|
19
|
+
|
|
20
|
+
def test_from_sql_parsing(self):
|
|
21
|
+
sql = "CREATE AGGREGATE public.custom_sum(numeric) (SFUNC = numeric_add, STYPE = numeric, INITCOND = '0')"
|
|
22
|
+
agg = PGAggregate.from_sql(sql)
|
|
23
|
+
|
|
24
|
+
assert agg.schema == "public"
|
|
25
|
+
assert agg.signature == "custom_sum(numeric)"
|
|
26
|
+
assert (
|
|
27
|
+
agg.definition == "(SFUNC = numeric_add, STYPE = numeric, INITCOND = '0')"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Quoted signature in SQL
|
|
31
|
+
sql_quoted = 'CREATE AGGREGATE public."custom_sum"(numeric) (SFUNC = numeric_add, STYPE = numeric)'
|
|
32
|
+
agg_quoted = PGAggregate.from_sql(sql_quoted)
|
|
33
|
+
assert agg_quoted.signature == "custom_sum(numeric)"
|
|
34
|
+
|
|
35
|
+
# Invalid SQL raises SQLParseFailure
|
|
36
|
+
with pytest.raises(SQLParseFailure):
|
|
37
|
+
PGAggregate.from_sql("INVALID SQL STATEMENT")
|
|
38
|
+
|
|
39
|
+
def test_sql_statement_generation(self):
|
|
40
|
+
agg = PGAggregate(
|
|
41
|
+
schema="public",
|
|
42
|
+
signature="custom_sum(numeric)",
|
|
43
|
+
definition="(SFUNC = numeric_add, STYPE = numeric)",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
create_sql = str(agg.to_sql_statement_create())
|
|
47
|
+
assert (
|
|
48
|
+
create_sql
|
|
49
|
+
== 'CREATE AGGREGATE "public"."custom_sum"(numeric) (SFUNC = numeric_add, STYPE = numeric)'
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
create_or_replace = list(agg.to_sql_statement_create_or_replace())
|
|
53
|
+
assert len(create_or_replace) == 1
|
|
54
|
+
assert (
|
|
55
|
+
str(create_or_replace[0])
|
|
56
|
+
== 'CREATE OR REPLACE AGGREGATE "public"."custom_sum"(numeric) (SFUNC = numeric_add, STYPE = numeric)'
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
drop_sql = str(agg.to_sql_statement_drop())
|
|
60
|
+
assert drop_sql == 'DROP AGGREGATE "public"."custom_sum"(numeric)'
|
|
61
|
+
|
|
62
|
+
drop_cascade_sql = str(agg.to_sql_statement_drop(cascade=True))
|
|
63
|
+
assert (
|
|
64
|
+
drop_cascade_sql == 'DROP AGGREGATE "public"."custom_sum"(numeric) CASCADE'
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def test_from_database(self):
|
|
68
|
+
schema = audit.context["schema_name"]
|
|
69
|
+
prefix = audit.context["schema_prefix"]
|
|
70
|
+
|
|
71
|
+
# Create temporary custom aggregate in DB
|
|
72
|
+
db.session.execute(
|
|
73
|
+
text(
|
|
74
|
+
f"""
|
|
75
|
+
CREATE OR REPLACE AGGREGATE {prefix}test_sum(numeric) (
|
|
76
|
+
SFUNC = numeric_add,
|
|
77
|
+
STYPE = numeric,
|
|
78
|
+
INITCOND = '0'
|
|
79
|
+
);
|
|
80
|
+
"""
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
db.session.commit()
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
aggregates = PGAggregate.from_database(db.session, schema=schema) # type: ignore
|
|
87
|
+
matching = [
|
|
88
|
+
a for a in aggregates if a.identity.endswith("test_sum(numeric)")
|
|
89
|
+
]
|
|
90
|
+
assert len(matching) == 1
|
|
91
|
+
assert matching[0].schema == schema
|
|
92
|
+
finally:
|
|
93
|
+
db.session.execute(
|
|
94
|
+
text(f"DROP AGGREGATE IF EXISTS {prefix}test_sum(numeric);")
|
|
95
|
+
)
|
|
96
|
+
db.session.commit()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/base.py
RENAMED
|
File without changes
|
{flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/core.py
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
|
{flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/models.py
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
|
{flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/typing.py
RENAMED
|
File without changes
|
{flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/flask_postgresql_audit/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flask_postgresql_audit-1.1.1 → flask_postgresql_audit-1.1.2}/tests/migrations/script.py.mako
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|