flask-postgresql-audit 1.0.4__tar.gz → 1.0.5__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.
Files changed (40) hide show
  1. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/PKG-INFO +1 -1
  2. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/__init__.py +1 -1
  3. flask_postgresql_audit-1.0.5/flask_postgresql_audit/extensions/alembic_utils/__init__.py +9 -0
  4. flask_postgresql_audit-1.0.5/flask_postgresql_audit/extensions/alembic_utils/pg_aggregate.py +119 -0
  5. flask_postgresql_audit-1.0.5/tests/__init__.py +0 -0
  6. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/.gitignore +0 -0
  7. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/CHANGES.md +0 -0
  8. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/LICENSE +0 -0
  9. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/README.md +0 -0
  10. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/alembic/__init__.py +0 -0
  11. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/alembic/entities.py +0 -0
  12. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/alembic/schema.py +0 -0
  13. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/alembic/setup.py +0 -0
  14. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/base.py +0 -0
  15. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/core.py +0 -0
  16. {flask_postgresql_audit-1.0.4/tests → flask_postgresql_audit-1.0.5/flask_postgresql_audit/extensions}/__init__.py +0 -0
  17. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/models.py +0 -0
  18. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/templates/audit_trigger_delete.sql +0 -0
  19. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/templates/audit_trigger_insert.sql +0 -0
  20. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/templates/audit_trigger_update.sql +0 -0
  21. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/templates/create_activity.sql +0 -0
  22. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/templates/create_schema.sql +0 -0
  23. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/templates/drop_schema.sql +0 -0
  24. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/templates/get_pk_values.sql +0 -0
  25. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/templates/get_setting.sql +0 -0
  26. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/templates/jsonb_subtract.sql +0 -0
  27. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/templates/row_audit_trigger.sql +0 -0
  28. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/templates/row_create_activity.sql +0 -0
  29. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/typing.py +0 -0
  30. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/flask_postgresql_audit/utils.py +0 -0
  31. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/pyproject.toml +0 -0
  32. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/tests/app.py +0 -0
  33. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/tests/conftest.py +0 -0
  34. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/tests/migrations/README +0 -0
  35. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/tests/migrations/alembic.ini +0 -0
  36. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/tests/migrations/env.py +0 -0
  37. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/tests/migrations/script.py.mako +0 -0
  38. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/tests/test_defaults.py +0 -0
  39. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/tests/test_operators.py +0 -0
  40. {flask_postgresql_audit-1.0.4 → flask_postgresql_audit-1.0.5}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flask-postgresql_audit
3
- Version: 1.0.4
3
+ Version: 1.0.5
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>
@@ -1,4 +1,4 @@
1
1
  # ruff: noqa: F401
2
2
  from .core import Audit, PostgreSQLAudit
3
3
 
4
- __version__ = "1.0.4"
4
+ __version__ = "1.0.5"
@@ -0,0 +1,9 @@
1
+ import alembic_utils
2
+ from alembic_utils.experimental import collect_subclasses
3
+ from alembic_utils.replaceable_entity import ReplaceableEntity
4
+
5
+ from .pg_aggregate import PGAggregate
6
+
7
+ original_types = set(collect_subclasses(alembic_utils, ReplaceableEntity))
8
+ extension_types = {PGAggregate}
9
+ allowed_types = original_types | extension_types
@@ -0,0 +1,119 @@
1
+ import re
2
+ import typing as t
3
+
4
+ import sqlalchemy as sa
5
+ from alembic_utils.exceptions import SQLParseFailure
6
+ from alembic_utils.replaceable_entity import ReplaceableEntity
7
+ from alembic_utils.statement import strip_terminating_semicolon
8
+ from sqlalchemy.orm import Session
9
+
10
+ T = t.TypeVar("T", bound="ReplaceableEntity")
11
+
12
+
13
+ class PGAggregate(ReplaceableEntity):
14
+ def __init__(self, schema: str, signature: str, definition: str):
15
+ super().__init__(schema, signature, definition)
16
+ self.definition: str = strip_terminating_semicolon(definition)
17
+
18
+ @property
19
+ def type_(self):
20
+ return "aggregate"
21
+
22
+ @property
23
+ def literal_signature(self):
24
+ name, remainder = self.signature.split("(", 1)
25
+ return '"' + name.strip() + '"(' + remainder
26
+
27
+ @classmethod
28
+ def from_sql(cls, sql: str) -> "PGAggregate":
29
+ pattern = r"^CREATE\s+AGGREGATE\s+(?:(?P<schema>\w+)\.)?(?P<signature>.+)\s+(?P<definition>\(.*\))$"
30
+ result = re.search(pattern, sql, re.IGNORECASE)
31
+ if result is not None and isinstance(result, re.Match):
32
+ raw_signature: str = result["signature"]
33
+ signature = (
34
+ "".join(raw_signature.split('"', 2))
35
+ if raw_signature.startswith('"')
36
+ else raw_signature
37
+ )
38
+ return cls(
39
+ schema=result["schema"],
40
+ signature=signature,
41
+ definition=result["definition"],
42
+ )
43
+ raise SQLParseFailure(f'Failed to parse SQL into PGAggregate """{sql}"""')
44
+
45
+ def to_sql_statement_create(self, *, replace: bool = False):
46
+ return sa.text(
47
+ f"{'CREATE OR REPLACE' if replace else 'CREATE'} AGGREGATE "
48
+ f"{self.literal_schema}.{self.literal_signature} {self.definition}"
49
+ )
50
+
51
+ def to_sql_statement_create_or_replace(self):
52
+ return self.to_sql_statement_create(replace=True)
53
+
54
+ def to_sql_statement_drop(self, cascade=False):
55
+ return sa.text(
56
+ f"DROP AGGREGATE {self.literal_schema}.{self.literal_signature}"
57
+ + (" CASCADE" if cascade else "")
58
+ )
59
+
60
+ @classmethod
61
+ def from_database(cls, sess: Session, schema: str) -> "list[PGAggregate]":
62
+ PG_GTE_11 = """
63
+ and p.prokind = 'a'
64
+ """
65
+
66
+ # NOTE: not tested
67
+ # PG_LT_11 = """
68
+ # and p.proisagg
69
+ # and not p.proiswindow
70
+ # """
71
+
72
+ # Retrieve the postgres server version e.g. 90603 for 9.6.3 or 120003 for 12.3
73
+ pg_version_str = sess.execute(sa.text("show server_version_num")).scalar_one()
74
+ pg_version = int(pg_version_str)
75
+
76
+ sql = sa.text(
77
+ """
78
+ WITH extension_functions AS (
79
+ SELECT objid AS extension_function_oid, classid
80
+ FROM pg_depend
81
+ WHERE deptype='e' -- depends on an extension
82
+ AND classid = 'pg_proc'::regclass -- is a proc/function
83
+ )
84
+
85
+ SELECT n.nspname AS function_schema
86
+ , p.proname AS function_name
87
+ , pg_get_function_arguments(p.oid) AS function_arguments
88
+ , format('CREATE AGGREGATE %s (SFUNC = %s, STYPE = %s%s%s%s%s)'
89
+ , format('%s.%s(%s)', n.nspname, p.proname, pg_get_function_arguments(p.oid))
90
+ , r.aggtransfn
91
+ , r.aggtranstype::regtype
92
+ , ', SORTOP = ' || NULLIF(r.aggsortop, 0)::regoper
93
+ , ', INITCOND = ' || quote_nullable(r.agginitval)
94
+ , ', FINALFUNC = ' || NULLIF(r.aggfinalfn, 0)
95
+ , CASE WHEN r.aggfinalextra THEN ', FINALFUNC_EXTRA' END
96
+ -- add more to cover special cases like moving-aggregate etc.
97
+ ) AS create_statement
98
+ , t.typname AS return_type
99
+ , l.lanname AS function_language
100
+ FROM pg_aggregate r
101
+ LEFT JOIN pg_proc p ON p.oid = aggfnoid
102
+ LEFT JOIN pg_namespace n ON p.pronamespace = n.oid
103
+ LEFT JOIN pg_language l ON p.prolang = l.oid
104
+ LEFT JOIN pg_type t ON t.oid = p.prorettype
105
+ LEFT JOIN extension_functions ef ON p.oid = ef.extension_function_oid
106
+ WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
107
+ AND ef.extension_function_oid IS NULL -- Filter out functions from extensions
108
+ AND n.nspname = :schema
109
+ """
110
+ + (PG_GTE_11 if pg_version >= 110000 else "")
111
+ ).bindparams(schema=schema)
112
+
113
+ rows = sess.execute(sql)
114
+ db_aggregates = [cls.from_sql(x[3]) for x in rows]
115
+
116
+ for agg in db_aggregates:
117
+ assert agg is not None
118
+
119
+ return db_aggregates
File without changes