pum 1.2.2__tar.gz → 1.3.0__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 (50) hide show
  1. {pum-1.2.2 → pum-1.3.0}/PKG-INFO +6 -2
  2. {pum-1.2.2 → pum-1.3.0}/README.md +3 -1
  3. pum-1.3.0/pum/__init__.py +90 -0
  4. {pum-1.2.2 → pum-1.3.0}/pum/changelog.py +61 -1
  5. pum-1.3.0/pum/checker.py +661 -0
  6. pum-1.3.0/pum/cli.py +571 -0
  7. {pum-1.2.2 → pum-1.3.0}/pum/config_model.py +57 -34
  8. pum-1.3.0/pum/connection.py +30 -0
  9. {pum-1.2.2 → pum-1.3.0}/pum/dependency_handler.py +69 -4
  10. {pum-1.2.2 → pum-1.3.0}/pum/dumper.py +14 -4
  11. {pum-1.2.2 → pum-1.3.0}/pum/exceptions.py +9 -0
  12. pum-1.3.0/pum/feedback.py +119 -0
  13. {pum-1.2.2 → pum-1.3.0}/pum/hook.py +95 -29
  14. {pum-1.2.2 → pum-1.3.0}/pum/info.py +0 -2
  15. {pum-1.2.2 → pum-1.3.0}/pum/parameter.py +4 -0
  16. {pum-1.2.2 → pum-1.3.0}/pum/pum_config.py +103 -20
  17. pum-1.3.0/pum/report_generator.py +1043 -0
  18. {pum-1.2.2 → pum-1.3.0}/pum/role_manager.py +151 -23
  19. {pum-1.2.2 → pum-1.3.0}/pum/schema_migrations.py +173 -36
  20. {pum-1.2.2 → pum-1.3.0}/pum/sql_content.py +83 -21
  21. pum-1.3.0/pum/upgrader.py +441 -0
  22. {pum-1.2.2 → pum-1.3.0}/pum.egg-info/PKG-INFO +6 -2
  23. {pum-1.2.2 → pum-1.3.0}/pum.egg-info/SOURCES.txt +9 -0
  24. {pum-1.2.2 → pum-1.3.0}/pum.egg-info/requires.txt +3 -0
  25. {pum-1.2.2 → pum-1.3.0}/pyproject.toml +1 -0
  26. pum-1.3.0/requirements/html.txt +1 -0
  27. {pum-1.2.2 → pum-1.3.0}/test/test_changelog.py +5 -1
  28. pum-1.3.0/test/test_checker.py +577 -0
  29. pum-1.3.0/test/test_config.py +298 -0
  30. {pum-1.2.2 → pum-1.3.0}/test/test_dumper.py +2 -2
  31. pum-1.3.0/test/test_feedback.py +317 -0
  32. pum-1.3.0/test/test_hooks.py +202 -0
  33. pum-1.3.0/test/test_roles.py +309 -0
  34. {pum-1.2.2 → pum-1.3.0}/test/test_schema_migrations.py +57 -6
  35. pum-1.3.0/test/test_sql_content.py +193 -0
  36. pum-1.3.0/test/test_transaction_fix.py +100 -0
  37. {pum-1.2.2 → pum-1.3.0}/test/test_upgrader.py +162 -16
  38. pum-1.2.2/pum/__init__.py +0 -29
  39. pum-1.2.2/pum/checker.py +0 -431
  40. pum-1.2.2/pum/cli.py +0 -408
  41. pum-1.2.2/pum/upgrader.py +0 -177
  42. pum-1.2.2/test/test_config.py +0 -152
  43. pum-1.2.2/test/test_roles.py +0 -117
  44. {pum-1.2.2 → pum-1.3.0}/LICENSE +0 -0
  45. {pum-1.2.2 → pum-1.3.0}/pum.egg-info/dependency_links.txt +0 -0
  46. {pum-1.2.2 → pum-1.3.0}/pum.egg-info/entry_points.txt +0 -0
  47. {pum-1.2.2 → pum-1.3.0}/pum.egg-info/top_level.txt +0 -0
  48. {pum-1.2.2 → pum-1.3.0}/requirements/base.txt +0 -0
  49. {pum-1.2.2 → pum-1.3.0}/requirements/development.txt +0 -0
  50. {pum-1.2.2 → pum-1.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pum
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary: Pum stands for "Postgres Upgrades Manager". It is a Database migration management tool very similar to flyway-db or Liquibase, based on metadata tables.
5
5
  Author-email: Denis Rouzaud <denis@opengis.ch>
6
6
  License-Expression: GPL-2.0-or-later
@@ -32,6 +32,8 @@ Requires-Dist: flake8-isort; extra == "dev"
32
32
  Requires-Dist: flake8-print; extra == "dev"
33
33
  Requires-Dist: pre-commit; extra == "dev"
34
34
  Requires-Dist: nose2; extra == "dev"
35
+ Provides-Extra: html
36
+ Requires-Dist: Jinja2; extra == "html"
35
37
  Dynamic: license-file
36
38
 
37
39
  # PostgreSQL Upgrades Manager (PUM)
@@ -49,10 +51,12 @@ PUM (PostgreSQL Upgrades Manager) is a robust database migration management tool
49
51
 
50
52
  ## Key Features
51
53
 
54
+ - **Flexible Database Connections**: Connect using PostgreSQL service names or direct connection strings (URI or parameters).
52
55
  - **Command-line and Python Integration**: Use PUM as a standalone CLI tool or integrate it into your Python project.
53
56
  - **Database Versioning**: Automatically manage database versioning with a metadata table.
54
57
  - **Changelog Management**: Apply and track SQL delta files for database upgrades.
55
- - **Migration Hooks**: Define custom hooks to execute additional SQL or Python code before or after migrations. This feature allows you to isolate data (table) code from application code (such as views and triggers), ensuring a clear separation of concerns and more maintainable database structures.
58
+ - **Droppable & recreatable app with data isolation**: PUM supports a clean rebuild workflow where an application environment can be dropped and recreated deterministically using hooks (pre and post migration).
59
+
56
60
 
57
61
  ## Why PUM?
58
62
 
@@ -13,10 +13,12 @@ PUM (PostgreSQL Upgrades Manager) is a robust database migration management tool
13
13
 
14
14
  ## Key Features
15
15
 
16
+ - **Flexible Database Connections**: Connect using PostgreSQL service names or direct connection strings (URI or parameters).
16
17
  - **Command-line and Python Integration**: Use PUM as a standalone CLI tool or integrate it into your Python project.
17
18
  - **Database Versioning**: Automatically manage database versioning with a metadata table.
18
19
  - **Changelog Management**: Apply and track SQL delta files for database upgrades.
19
- - **Migration Hooks**: Define custom hooks to execute additional SQL or Python code before or after migrations. This feature allows you to isolate data (table) code from application code (such as views and triggers), ensuring a clear separation of concerns and more maintainable database structures.
20
+ - **Droppable & recreatable app with data isolation**: PUM supports a clean rebuild workflow where an application environment can be dropped and recreated deterministically using hooks (pre and post migration).
21
+
20
22
 
21
23
  ## Why PUM?
22
24
 
@@ -0,0 +1,90 @@
1
+ import importlib
2
+ import logging
3
+ from typing import Any, TYPE_CHECKING
4
+
5
+ # Custom SQL logging level (more verbose than DEBUG)
6
+ # Register with: logging.addLevelName(SQL, 'SQL')
7
+ SQL = 5
8
+
9
+ # Configure default logging for API usage (not CLI)
10
+ # CLI will override this with its own configuration
11
+ if not logging.getLogger().handlers:
12
+ logging.basicConfig(
13
+ level=logging.INFO,
14
+ format="%(message)s",
15
+ )
16
+
17
+ if TYPE_CHECKING:
18
+ from .changelog import Changelog
19
+ from .checker import Checker
20
+ from .dependency_handler import DependencyHandler
21
+ from .dumper import Dumper, DumpFormat
22
+ from .feedback import Feedback, LogFeedback, SilentFeedback
23
+ from .hook import HookBase, HookHandler
24
+ from .parameter import ParameterDefinition, ParameterType
25
+ from .pum_config import PumConfig
26
+ from .role_manager import Permission, PermissionType, Role, RoleManager
27
+ from .schema_migrations import SchemaMigrations
28
+ from .sql_content import SqlContent, CursorResult
29
+ from .upgrader import Upgrader
30
+
31
+ __all__ = [
32
+ "Checker",
33
+ "Changelog",
34
+ "CursorResult",
35
+ "DependencyHandler",
36
+ "Dumper",
37
+ "DumpFormat",
38
+ "Feedback",
39
+ "HookBase",
40
+ "HookHandler",
41
+ "LogFeedback",
42
+ "ParameterDefinition",
43
+ "ParameterType",
44
+ "Permission",
45
+ "PermissionType",
46
+ "PumConfig",
47
+ "Role",
48
+ "RoleManager",
49
+ "SchemaMigrations",
50
+ "SilentFeedback",
51
+ "SQL",
52
+ "SqlContent",
53
+ "Upgrader",
54
+ ]
55
+
56
+
57
+ _LAZY_IMPORTS: dict[str, tuple[str, str]] = {
58
+ "Checker": ("pum.checker", "Checker"),
59
+ "Changelog": ("pum.changelog", "Changelog"),
60
+ "CursorResult": ("pum.sql_content", "CursorResult"),
61
+ "DependencyHandler": ("pum.dependency_handler", "DependencyHandler"),
62
+ "Dumper": ("pum.dumper", "Dumper"),
63
+ "DumpFormat": ("pum.dumper", "DumpFormat"),
64
+ "HookBase": ("pum.hook", "HookBase"),
65
+ "HookHandler": ("pum.hook", "HookHandler"),
66
+ "ParameterDefinition": ("pum.parameter", "ParameterDefinition"),
67
+ "ParameterType": ("pum.parameter", "ParameterType"),
68
+ "Permission": ("pum.role_manager", "Permission"),
69
+ "PermissionType": ("pum.role_manager", "PermissionType"),
70
+ "PumConfig": ("pum.pum_config", "PumConfig"),
71
+ "Role": ("pum.role_manager", "Role"),
72
+ "RoleManager": ("pum.role_manager", "RoleManager"),
73
+ "SchemaMigrations": ("pum.schema_migrations", "SchemaMigrations"),
74
+ "SqlContent": ("pum.sql_content", "SqlContent"),
75
+ "Upgrader": ("pum.upgrader", "Upgrader"),
76
+ }
77
+
78
+
79
+ def __getattr__(name: str) -> Any:
80
+ if name not in _LAZY_IMPORTS:
81
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
82
+ module_name, symbol_name = _LAZY_IMPORTS[name]
83
+ module = importlib.import_module(module_name)
84
+ value = getattr(module, symbol_name)
85
+ globals()[name] = value
86
+ return value
87
+
88
+
89
+ def __dir__() -> list[str]:
90
+ return sorted(set(list(globals().keys()) + list(_LAZY_IMPORTS.keys())))
@@ -1,13 +1,21 @@
1
+ import logging
1
2
  from os import listdir
2
3
  from os.path import basename
3
4
  from pathlib import Path
5
+ from typing import TYPE_CHECKING
4
6
 
5
7
  from packaging.version import parse as parse_version
6
8
  import psycopg
7
9
 
10
+ from .schema_migrations import SchemaMigrations
8
11
  from .exceptions import PumInvalidChangelog, PumSqlError
9
12
  from .sql_content import SqlContent
10
13
 
14
+ if TYPE_CHECKING:
15
+ from .feedback import Feedback
16
+
17
+ logger = logging.getLogger(__name__)
18
+
11
19
 
12
20
  class Changelog:
13
21
  """This class represent a changelog directory.
@@ -82,6 +90,9 @@ class Changelog:
82
90
  connection: psycopg.Connection,
83
91
  parameters: dict | None = None,
84
92
  commit: bool = True,
93
+ schema_migrations: SchemaMigrations | None = None,
94
+ beta_testing: bool = False,
95
+ feedback: "Feedback | None" = None,
85
96
  ) -> list[Path]:
86
97
  """Apply a changelog
87
98
  This will execute all the files in the changelog directory.
@@ -94,18 +105,67 @@ class Changelog:
94
105
  The parameters to pass to the SQL files
95
106
  commit: bool
96
107
  If true, the transaction is committed. The default is true.
108
+ schema_migrations: SchemaMigrations | None
109
+ The SchemaMigrations instance to use to record the applied changelog.
110
+ If None, the changelog will not be recorded.
111
+ beta_testing: bool
112
+ If true, the changelog will be recorded as a beta testing version.
113
+ feedback: Feedback | None
114
+ Optional feedback object for progress reporting.
97
115
 
98
116
  Returns:
99
117
  list[Path]
100
118
  The list of changelogs that were executed
101
119
 
102
120
  """
121
+ logger.info(f"Applying changelog version {self.version} from {self.dir}")
122
+
123
+ parameters_literals = SqlContent.prepare_parameters(parameters)
103
124
  files = self.files()
104
125
  for file in files:
126
+ if feedback:
127
+ feedback.increment_step()
128
+ feedback.report_progress(f"Executing {file.name}")
105
129
  try:
106
130
  SqlContent(file).execute(
107
- connection=connection, commit=commit, parameters=parameters
131
+ connection=connection, commit=commit, parameters=parameters_literals
108
132
  )
109
133
  except PumSqlError as e:
110
134
  raise PumSqlError(f"Error applying changelog {file}: {e}") from e
135
+ if schema_migrations:
136
+ schema_migrations.set_baseline(
137
+ connection=connection,
138
+ version=self.version,
139
+ beta_testing=beta_testing,
140
+ commit=commit,
141
+ changelog_files=[str(f) for f in files],
142
+ parameters=parameters,
143
+ )
111
144
  return files
145
+
146
+ def is_applied(
147
+ self,
148
+ connection: psycopg.Connection,
149
+ schema_migrations: SchemaMigrations,
150
+ ) -> bool:
151
+ """Check if the changelog has been applied.
152
+
153
+ Args:
154
+ connection: The database connection to use.
155
+ Returns:
156
+ bool: True if the changelog has been applied, False otherwise.
157
+ """
158
+ query = psycopg.sql.SQL("""
159
+ SELECT EXISTS (
160
+ SELECT 1
161
+ FROM {table}
162
+ WHERE version = {version}
163
+ )
164
+ """)
165
+ parameters = {
166
+ "version": psycopg.sql.Literal(str(self.version)),
167
+ "table": schema_migrations.migration_table_identifier,
168
+ }
169
+ cursor = SqlContent(query).execute(connection, parameters=parameters)
170
+ result = cursor._pum_results[0] if cursor._pum_results else None
171
+ return result[0] if result else False