rococo 1.2.0__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.
- {rococo-1.2.0 → rococo-1.3.0}/PKG-INFO +1 -1
- {rococo-1.2.0 → rococo-1.3.0}/rococo/data/postgresql.py +4 -2
- {rococo-1.2.0 → rococo-1.3.0}/rococo/data/surrealdb.py +4 -2
- {rococo-1.2.0 → rococo-1.3.0}/rococo/migrations/common/migration_runner.py +38 -42
- rococo-1.3.0/rococo/migrations/common/sql_migration_base.py +215 -0
- rococo-1.3.0/rococo/migrations/mysql/migration.py +40 -0
- rococo-1.3.0/rococo/migrations/postgres/migration.py +44 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/postgresql/postgresql_repository.py +9 -4
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/surrealdb/surreal_db_repository.py +4 -2
- {rococo-1.2.0 → rococo-1.3.0}/rococo.egg-info/PKG-INFO +1 -1
- {rococo-1.2.0 → rococo-1.3.0}/rococo.egg-info/SOURCES.txt +3 -0
- {rococo-1.2.0 → rococo-1.3.0}/setup.py +1 -1
- rococo-1.3.0/tests/migration_cli_base_test.py +234 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/migration_runner_test.py +44 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/postgre_repository_test.py +6 -3
- rococo-1.3.0/tests/sql_migration_base_test.py +330 -0
- rococo-1.2.0/rococo/migrations/mysql/migration.py +0 -112
- rococo-1.2.0/rococo/migrations/postgres/migration.py +0 -111
- {rococo-1.2.0 → rococo-1.3.0}/LICENSE +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/README.md +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/auth/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/auth/tokens.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/config/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/config/config.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/data/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/data/base.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/data/dynamodb.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/data/mongodb.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/data/mysql.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/emailing/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/emailing/base.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/emailing/config.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/emailing/enums.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/emailing/factory.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/emailing/mailjet.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/emailing/ses.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/faxing/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/faxing/base.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/faxing/config.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/faxing/enums.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/faxing/factory.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/faxing/ifax.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/messaging/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/messaging/base.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/messaging/rabbitmq.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/messaging/sqs.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/migrations/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/migrations/common/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/migrations/common/cli_base.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/migrations/common/migration_base.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/migrations/common/migration_template.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/migrations/mongo/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/migrations/mongo/cli.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/migrations/mongo/migration.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/migrations/mysql/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/migrations/mysql/cli.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/migrations/postgres/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/migrations/postgres/cli.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/email.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/login_method.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/organization.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/otp_method.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/person.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/person_organization_role.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/recovery_code.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/surrealdb/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/surrealdb/email.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/surrealdb/login_method.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/surrealdb/organization.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/surrealdb/otp_method.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/surrealdb/person.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/surrealdb/person_organization_role.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/surrealdb/surreal_versioned_model.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/models/versioned_model.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/plugins/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/plugins/pooled_connection.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/base_repository.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/dynamodb/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/dynamodb/dynamodb_repository.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/mongodb/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/mongodb/mongodb_repository.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/mysql/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/mysql/mysql_repository.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/mysql/organization_repository.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/postgresql/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/surrealdb/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/surrealdb/organization_repository.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/repositories/surrealdb/person_organization_role_repository.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/sms/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/sms/base.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/sms/config.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/sms/enums.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/sms/factory.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo/sms/twilio.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo.egg-info/dependency_links.txt +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo.egg-info/entry_points.txt +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo.egg-info/requires.txt +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/rococo.egg-info/top_level.txt +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/setup.cfg +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/base_repository_test.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/config_test.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/database-integration/__init__.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/database-integration/conftest.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/database-integration/test_dynamodb_integration.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/database-integration/test_models.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/database-integration/test_mongodb_integration.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/database-integration/test_mysql_integration.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/database-integration/test_postgres_integration.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/database-integration/test_surrealdb_integration.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/dynamodb_repository_test.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/model_test.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/mongodb_repository_test.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/mysql_repository_test.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/surrealdb_repository_test.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/test_calculated_properties_fix.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/test_faxing.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/test_mailjet_addresses.py +0 -0
- {rococo-1.2.0 → rococo-1.3.0}/tests/test_non_versioned_model.py +0 -0
|
@@ -160,7 +160,8 @@ class PostgreSQLAdapter(DbAdapter):
|
|
|
160
160
|
conditions: Dict[str, Any],
|
|
161
161
|
sort: List[Tuple[str, str]] = None,
|
|
162
162
|
join_statements: list = None,
|
|
163
|
-
additional_fields: list = None
|
|
163
|
+
additional_fields: list = None,
|
|
164
|
+
active: bool = True
|
|
164
165
|
) -> Optional[Dict[str, Any]]:
|
|
165
166
|
fields = [f'{table}.*']
|
|
166
167
|
if additional_fields:
|
|
@@ -175,7 +176,8 @@ class PostgreSQLAdapter(DbAdapter):
|
|
|
175
176
|
if conditions:
|
|
176
177
|
condition_strs_values = [self._build_condition_string(
|
|
177
178
|
table, k, v) for k, v in conditions.items()]
|
|
178
|
-
|
|
179
|
+
if active:
|
|
180
|
+
condition_strs_values.append((f"{table}.active = %s", ['true']))
|
|
179
181
|
query += f" WHERE {' AND '.join([condition_str for condition_str, condition_value in condition_strs_values])}"
|
|
180
182
|
|
|
181
183
|
if sort:
|
|
@@ -108,7 +108,8 @@ class SurrealDbAdapter(DbAdapter):
|
|
|
108
108
|
conditions: Dict[str, Any],
|
|
109
109
|
sort: List[Tuple[str, str]] = None,
|
|
110
110
|
fetch_related: list = None,
|
|
111
|
-
additional_fields: list = None
|
|
111
|
+
additional_fields: list = None,
|
|
112
|
+
active: bool = True
|
|
112
113
|
) -> Dict[str, Any]:
|
|
113
114
|
fields = ['*']
|
|
114
115
|
if additional_fields:
|
|
@@ -120,7 +121,8 @@ class SurrealDbAdapter(DbAdapter):
|
|
|
120
121
|
if conditions:
|
|
121
122
|
condition_strs = [
|
|
122
123
|
f"{self._build_condition_string(k, v)}" for k, v in conditions.items()]
|
|
123
|
-
|
|
124
|
+
if active:
|
|
125
|
+
condition_strs.append("active=true")
|
|
124
126
|
query += f" WHERE {' AND '.join(condition_strs)}"
|
|
125
127
|
|
|
126
128
|
if sort:
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import logging
|
|
3
|
+
import importlib
|
|
3
4
|
from importlib import import_module
|
|
4
|
-
|
|
5
|
-
# For a common module, it would typically be:
|
|
6
|
-
# from rococo.migrations.common.migration_template import get_template
|
|
7
|
-
from .migration_template import get_template # This relative import is fine if it's part of a package
|
|
5
|
+
from .migration_template import get_template
|
|
8
6
|
|
|
9
7
|
class MigrationRunner:
|
|
10
8
|
def __init__(self, migrations_dir, migration):
|
|
@@ -150,42 +148,40 @@ class MigrationRunner:
|
|
|
150
148
|
print(f"Error creating migration file: {e}")
|
|
151
149
|
|
|
152
150
|
def run_forward_migration_script(self, initial_db_version_for_run):
|
|
153
|
-
#
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
# (updated by the migration script that just ran).
|
|
188
|
-
return self.run_forward_migration_script(initial_db_version_for_run)
|
|
151
|
+
# Iterates one migration at a time, re-resolving the next forward script
|
|
152
|
+
# against the DB version updated by the just-applied script. Iterative to
|
|
153
|
+
# avoid stack overflow on deep migration histories.
|
|
154
|
+
while True:
|
|
155
|
+
current_script_filename = self._get_forward_migration_script()
|
|
156
|
+
|
|
157
|
+
if current_script_filename is None:
|
|
158
|
+
latest_db_version_after_ops = self.get_db_version()
|
|
159
|
+
if initial_db_version_for_run == latest_db_version_after_ops:
|
|
160
|
+
logging.info(
|
|
161
|
+
f'No forward migration scripts found for current DB version: '
|
|
162
|
+
f'{initial_db_version_for_run}. Database is likely up to date.'
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
logging.info('All pending forward migrations complete!')
|
|
166
|
+
logging.info(f'Latest DB version: {latest_db_version_after_ops}')
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
logging.info(
|
|
170
|
+
f'Running forward migration: {os.path.basename(current_script_filename)}.py'
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
importlib.invalidate_caches()
|
|
175
|
+
migration_module = import_module(current_script_filename)
|
|
176
|
+
migration_module.upgrade(self.migration)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logging.error(
|
|
179
|
+
f"Error during forward migration {current_script_filename}.py: {e}",
|
|
180
|
+
exc_info=True,
|
|
181
|
+
)
|
|
182
|
+
print(f"Error applying migration {current_script_filename}.py. Halting.")
|
|
183
|
+
print(f"Current DB version after error: {self.get_db_version()}")
|
|
184
|
+
return
|
|
189
185
|
|
|
190
186
|
def run_backward_migration_script(self):
|
|
191
187
|
current_script_filename = self._get_backward_migration_script()
|
|
@@ -197,8 +193,8 @@ class MigrationRunner:
|
|
|
197
193
|
logging.info(f'Running backward migration: {os.path.basename(current_script_filename)}.py')
|
|
198
194
|
|
|
199
195
|
try:
|
|
200
|
-
|
|
201
|
-
migration_module = import_module(
|
|
196
|
+
importlib.invalidate_caches()
|
|
197
|
+
migration_module = import_module(current_script_filename)
|
|
202
198
|
migration_module.downgrade(self.migration)
|
|
203
199
|
except Exception as e:
|
|
204
200
|
logging.error(f"Error during backward migration {current_script_filename}.py: {e}", exc_info=True)
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from typing import Any, Iterable, Optional
|
|
4
|
+
|
|
5
|
+
from .migration_base import MigrationBase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SQLMigrationBase(MigrationBase):
|
|
9
|
+
"""Shared SQL migration helpers for relational adapters.
|
|
10
|
+
|
|
11
|
+
Concrete subclasses (e.g. PostgresMigration, MySQLMigration) provide the
|
|
12
|
+
dialect-specific SQL via class-level templates and queries. Identifiers
|
|
13
|
+
(table, column, index, constraint names) are validated against
|
|
14
|
+
``_IDENT_RE`` before being interpolated into SQL. Values (database name,
|
|
15
|
+
version strings) are bound through the adapter's parameterized query
|
|
16
|
+
interface, never concatenated into SQL strings.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Parameterized queries (use %s placeholders).
|
|
20
|
+
EXISTS_COLUMN_QUERY: str = NotImplemented # args: (database, table, column)
|
|
21
|
+
EXISTS_PK_QUERY: str = NotImplemented # args: (database, table)
|
|
22
|
+
UPDATE_VERSION_QUERY: str = NotImplemented # args: (version,)
|
|
23
|
+
INSERT_DB_VERSION_DATA_QUERY: str = NotImplemented # no args
|
|
24
|
+
|
|
25
|
+
# Identifier templates (use str.format with validated names).
|
|
26
|
+
DROP_PK_TEMPLATE: str = NotImplemented # {table}
|
|
27
|
+
ADD_PK_TEMPLATE: str = NotImplemented # {table}, {keys}
|
|
28
|
+
ADD_COLUMN_TEMPLATE: str = NotImplemented # {table}, {column}, {datatype}
|
|
29
|
+
DROP_COLUMN_TEMPLATE: str = NotImplemented # {table}, {column}
|
|
30
|
+
ADD_INDEX_TEMPLATE: str = NotImplemented # {table}, {index}, {column}
|
|
31
|
+
REMOVE_INDEX_TEMPLATE: str = NotImplemented # {table}, {index}
|
|
32
|
+
ALTER_INDEX_TEMPLATES: tuple = NotImplemented # tuple of templates run in order
|
|
33
|
+
ALTER_COLUMN_TEMPLATE: str = NotImplemented # {table}, {column}, {datatype}
|
|
34
|
+
RENAME_COLUMN_TEMPLATE: str = NotImplemented # {table}, {old}, {new}
|
|
35
|
+
RENAME_TABLE_TEMPLATE: str = NotImplemented # {old_table}, {new_table}
|
|
36
|
+
CREATE_TABLE_TEMPLATE: str = NotImplemented # {table}, {fields_with_type}
|
|
37
|
+
DROP_TABLE_IF_EXISTS_TEMPLATE: str = NotImplemented # {table}
|
|
38
|
+
DROP_TABLE_TEMPLATE: str = NotImplemented # {table}
|
|
39
|
+
|
|
40
|
+
_IDENT_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
41
|
+
|
|
42
|
+
def _validate_ident(self, name: str) -> str:
|
|
43
|
+
if not isinstance(name, str) or not self._IDENT_RE.match(name):
|
|
44
|
+
raise ValueError(f"invalid SQL identifier: {name!r}")
|
|
45
|
+
return name
|
|
46
|
+
|
|
47
|
+
def _validate_idents(self, names: Iterable[str]) -> None:
|
|
48
|
+
for n in names:
|
|
49
|
+
self._validate_ident(n)
|
|
50
|
+
|
|
51
|
+
def _validate_pk_keys(self, keys: str) -> str:
|
|
52
|
+
if not isinstance(keys, str) or not (keys.startswith("(") and keys.endswith(")")):
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"primary key keys must be wrapped in parentheses, got: {keys!r}"
|
|
55
|
+
)
|
|
56
|
+
inner = keys[1:-1]
|
|
57
|
+
for col in inner.split(","):
|
|
58
|
+
self._validate_ident(col.strip())
|
|
59
|
+
return keys
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def _extract_count(response: Any) -> int:
|
|
63
|
+
if not response:
|
|
64
|
+
return 0
|
|
65
|
+
row = response[0] if isinstance(response, list) else response
|
|
66
|
+
if not isinstance(row, dict) or not row:
|
|
67
|
+
return 0
|
|
68
|
+
value = next(iter(row.values()))
|
|
69
|
+
return int(value or 0)
|
|
70
|
+
|
|
71
|
+
def _does_column_exist(self, table_name, column_name, commit: bool = True) -> bool:
|
|
72
|
+
self._validate_idents([table_name, column_name])
|
|
73
|
+
rows = self.execute(
|
|
74
|
+
self.EXISTS_COLUMN_QUERY,
|
|
75
|
+
commit=commit,
|
|
76
|
+
args=(self.db_adapter._database, table_name, column_name),
|
|
77
|
+
)
|
|
78
|
+
return self._extract_count(rows) > 0
|
|
79
|
+
|
|
80
|
+
def _does_primary_key_constraint_exists(self, table_name, commit: bool = True) -> bool:
|
|
81
|
+
self._validate_ident(table_name)
|
|
82
|
+
rows = self.execute(
|
|
83
|
+
self.EXISTS_PK_QUERY,
|
|
84
|
+
commit=commit,
|
|
85
|
+
args=(self.db_adapter._database, table_name),
|
|
86
|
+
)
|
|
87
|
+
return self._extract_count(rows) > 0
|
|
88
|
+
|
|
89
|
+
def remove_primary_key(self, table_name, commit: bool = True):
|
|
90
|
+
self._validate_ident(table_name)
|
|
91
|
+
if self._does_primary_key_constraint_exists(table_name, commit=commit):
|
|
92
|
+
return self.execute(
|
|
93
|
+
self.DROP_PK_TEMPLATE.format(table=table_name), commit=commit
|
|
94
|
+
)
|
|
95
|
+
logging.info(
|
|
96
|
+
f"PRIMARY KEY constraint for table {table_name} does not exist. "
|
|
97
|
+
f"Skipping removal..."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def add_primary_key(self, table_name, keys: str, commit: bool = True):
|
|
101
|
+
self._validate_ident(table_name)
|
|
102
|
+
self._validate_pk_keys(keys)
|
|
103
|
+
if self._does_primary_key_constraint_exists(table_name, commit=commit):
|
|
104
|
+
self.execute(
|
|
105
|
+
self.DROP_PK_TEMPLATE.format(table=table_name), commit=commit
|
|
106
|
+
)
|
|
107
|
+
return self.execute(
|
|
108
|
+
self.ADD_PK_TEMPLATE.format(table=table_name, keys=keys), commit=commit
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def add_column(self, table_name, column_name, datatype, commit: bool = True):
|
|
112
|
+
self._validate_idents([table_name, column_name])
|
|
113
|
+
if self._does_column_exist(table_name, column_name, commit=commit):
|
|
114
|
+
logging.info(
|
|
115
|
+
f"Column {column_name} for table {table_name} already exists. "
|
|
116
|
+
f"Skipping creation..."
|
|
117
|
+
)
|
|
118
|
+
return
|
|
119
|
+
query = self.ADD_COLUMN_TEMPLATE.format(
|
|
120
|
+
table=table_name, column=column_name, datatype=datatype
|
|
121
|
+
)
|
|
122
|
+
self.execute(query, commit=commit)
|
|
123
|
+
|
|
124
|
+
def drop_column(self, table_name, column_name, commit: bool = True):
|
|
125
|
+
self._validate_idents([table_name, column_name])
|
|
126
|
+
if not self._does_column_exist(table_name, column_name, commit=commit):
|
|
127
|
+
logging.info(
|
|
128
|
+
f"Column {column_name} for table {table_name} does not exist. "
|
|
129
|
+
f"Skipping deletion..."
|
|
130
|
+
)
|
|
131
|
+
return
|
|
132
|
+
query = self.DROP_COLUMN_TEMPLATE.format(
|
|
133
|
+
table=table_name, column=column_name
|
|
134
|
+
)
|
|
135
|
+
self.execute(query, commit=commit)
|
|
136
|
+
|
|
137
|
+
def alter_index(
|
|
138
|
+
self,
|
|
139
|
+
table_name,
|
|
140
|
+
new_index_name,
|
|
141
|
+
new_indexed_column,
|
|
142
|
+
old_index_name,
|
|
143
|
+
commit: bool = True,
|
|
144
|
+
):
|
|
145
|
+
self._validate_idents(
|
|
146
|
+
[table_name, new_index_name, new_indexed_column, old_index_name]
|
|
147
|
+
)
|
|
148
|
+
for tmpl in self.ALTER_INDEX_TEMPLATES:
|
|
149
|
+
query = tmpl.format(
|
|
150
|
+
table=table_name,
|
|
151
|
+
new_index=new_index_name,
|
|
152
|
+
new_column=new_indexed_column,
|
|
153
|
+
old_index=old_index_name,
|
|
154
|
+
)
|
|
155
|
+
self.execute(query, commit=commit)
|
|
156
|
+
|
|
157
|
+
def add_index(self, table_name, index_name, indexed_column, commit: bool = True):
|
|
158
|
+
self._validate_idents([table_name, index_name, indexed_column])
|
|
159
|
+
query = self.ADD_INDEX_TEMPLATE.format(
|
|
160
|
+
table=table_name, index=index_name, column=indexed_column
|
|
161
|
+
)
|
|
162
|
+
self.execute(query, commit=commit)
|
|
163
|
+
|
|
164
|
+
def remove_index(self, table_name, index_name, commit: bool = True):
|
|
165
|
+
self._validate_idents([table_name, index_name])
|
|
166
|
+
query = self.REMOVE_INDEX_TEMPLATE.format(
|
|
167
|
+
table=table_name, index=index_name
|
|
168
|
+
)
|
|
169
|
+
self.execute(query, commit=commit)
|
|
170
|
+
|
|
171
|
+
def alter_column(self, table_name, column_name, datatype, commit: bool = True):
|
|
172
|
+
self._validate_idents([table_name, column_name])
|
|
173
|
+
query = self.ALTER_COLUMN_TEMPLATE.format(
|
|
174
|
+
table=table_name, column=column_name, datatype=datatype
|
|
175
|
+
)
|
|
176
|
+
self.execute(query, commit=commit)
|
|
177
|
+
|
|
178
|
+
def change_column_name(
|
|
179
|
+
self, table_name, old_column_name, new_column_name, commit: bool = True
|
|
180
|
+
):
|
|
181
|
+
self._validate_idents([table_name, old_column_name, new_column_name])
|
|
182
|
+
query = self.RENAME_COLUMN_TEMPLATE.format(
|
|
183
|
+
table=table_name, old=old_column_name, new=new_column_name
|
|
184
|
+
)
|
|
185
|
+
self.execute(query, commit=commit)
|
|
186
|
+
|
|
187
|
+
def change_table_name(self, old_table, new_table, commit: bool = True):
|
|
188
|
+
self._validate_idents([old_table, new_table])
|
|
189
|
+
query = self.RENAME_TABLE_TEMPLATE.format(
|
|
190
|
+
old_table=old_table, new_table=new_table
|
|
191
|
+
)
|
|
192
|
+
self.execute(query, commit=commit)
|
|
193
|
+
|
|
194
|
+
def create_table(
|
|
195
|
+
self, table_name, fields_with_type, drop_if_exists: bool = True, commit: bool = True
|
|
196
|
+
):
|
|
197
|
+
self._validate_ident(table_name)
|
|
198
|
+
if drop_if_exists:
|
|
199
|
+
drop_query = self.DROP_TABLE_IF_EXISTS_TEMPLATE.format(table=table_name)
|
|
200
|
+
self.execute(drop_query, commit=commit)
|
|
201
|
+
create_query = self.CREATE_TABLE_TEMPLATE.format(
|
|
202
|
+
table=table_name, fields_with_type=fields_with_type
|
|
203
|
+
)
|
|
204
|
+
self.execute(create_query, commit=commit)
|
|
205
|
+
|
|
206
|
+
def drop_table(self, table_name, commit: bool = True):
|
|
207
|
+
self._validate_ident(table_name)
|
|
208
|
+
query = self.DROP_TABLE_TEMPLATE.format(table=table_name)
|
|
209
|
+
self.execute(query, commit=commit)
|
|
210
|
+
|
|
211
|
+
def insert_db_version_data(self, commit: bool = True):
|
|
212
|
+
self.execute(self.INSERT_DB_VERSION_DATA_QUERY, commit=commit)
|
|
213
|
+
|
|
214
|
+
def update_version_table(self, version, commit: bool = True):
|
|
215
|
+
self.execute(self.UPDATE_VERSION_QUERY, commit=commit, args=(str(version),))
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from ..common.sql_migration_base import SQLMigrationBase
|
|
2
|
+
from rococo.data.mysql import MySqlAdapter
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MySQLMigration(SQLMigrationBase):
|
|
6
|
+
EXISTS_COLUMN_QUERY = (
|
|
7
|
+
"SELECT COUNT(*) AS count "
|
|
8
|
+
"FROM information_schema.COLUMNS "
|
|
9
|
+
"WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = %s;"
|
|
10
|
+
)
|
|
11
|
+
EXISTS_PK_QUERY = (
|
|
12
|
+
"SELECT COUNT(*) AS count "
|
|
13
|
+
"FROM information_schema.TABLE_CONSTRAINTS "
|
|
14
|
+
"WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s "
|
|
15
|
+
"AND CONSTRAINT_TYPE = 'PRIMARY KEY';"
|
|
16
|
+
)
|
|
17
|
+
UPDATE_VERSION_QUERY = "UPDATE db_version SET version = %s;"
|
|
18
|
+
INSERT_DB_VERSION_DATA_QUERY = (
|
|
19
|
+
"INSERT INTO db_version (version) VALUES ('0000000000');"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
DROP_PK_TEMPLATE = "ALTER TABLE {table} DROP PRIMARY KEY;"
|
|
23
|
+
ADD_PK_TEMPLATE = "ALTER TABLE {table} ADD PRIMARY KEY {keys};"
|
|
24
|
+
ADD_COLUMN_TEMPLATE = "ALTER TABLE {table} ADD {column} {datatype};"
|
|
25
|
+
DROP_COLUMN_TEMPLATE = "ALTER TABLE {table} DROP {column};"
|
|
26
|
+
ADD_INDEX_TEMPLATE = "ALTER TABLE {table} ADD INDEX {index} ({column});"
|
|
27
|
+
REMOVE_INDEX_TEMPLATE = "ALTER TABLE {table} DROP INDEX {index};"
|
|
28
|
+
ALTER_INDEX_TEMPLATES = (
|
|
29
|
+
"ALTER TABLE {table} ADD INDEX {new_index} ({new_column}), "
|
|
30
|
+
"DROP INDEX {old_index};",
|
|
31
|
+
)
|
|
32
|
+
ALTER_COLUMN_TEMPLATE = "ALTER TABLE {table} MODIFY COLUMN {column} {datatype};"
|
|
33
|
+
RENAME_COLUMN_TEMPLATE = "ALTER TABLE {table} RENAME COLUMN {old} TO {new};"
|
|
34
|
+
RENAME_TABLE_TEMPLATE = "ALTER TABLE {old_table} RENAME TO {new_table};"
|
|
35
|
+
CREATE_TABLE_TEMPLATE = "CREATE TABLE {table} ({fields_with_type});"
|
|
36
|
+
DROP_TABLE_IF_EXISTS_TEMPLATE = "DROP TABLE IF EXISTS {table};"
|
|
37
|
+
DROP_TABLE_TEMPLATE = "DROP TABLE {table};"
|
|
38
|
+
|
|
39
|
+
def __init__(self, db_adapter: MySqlAdapter):
|
|
40
|
+
super().__init__(db_adapter)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from ..common.sql_migration_base import SQLMigrationBase
|
|
2
|
+
from rococo.data.postgresql import PostgreSQLAdapter
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PostgresMigration(SQLMigrationBase):
|
|
6
|
+
EXISTS_COLUMN_QUERY = (
|
|
7
|
+
"SELECT COUNT(*) AS count "
|
|
8
|
+
"FROM information_schema.columns "
|
|
9
|
+
"WHERE table_catalog = %s AND table_name = %s AND column_name = %s;"
|
|
10
|
+
)
|
|
11
|
+
EXISTS_PK_QUERY = (
|
|
12
|
+
"SELECT COUNT(*) AS count "
|
|
13
|
+
"FROM information_schema.table_constraints "
|
|
14
|
+
"WHERE table_catalog = %s AND table_name = %s "
|
|
15
|
+
"AND constraint_type = 'PRIMARY KEY';"
|
|
16
|
+
)
|
|
17
|
+
UPDATE_VERSION_QUERY = "UPDATE db_version SET version = %s;"
|
|
18
|
+
INSERT_DB_VERSION_DATA_QUERY = (
|
|
19
|
+
"INSERT INTO db_version (version) VALUES ('0000000000');"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
DROP_PK_TEMPLATE = (
|
|
23
|
+
"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {table}_pkey;"
|
|
24
|
+
)
|
|
25
|
+
ADD_PK_TEMPLATE = (
|
|
26
|
+
"ALTER TABLE {table} ADD CONSTRAINT {table}_pkey PRIMARY KEY {keys};"
|
|
27
|
+
)
|
|
28
|
+
ADD_COLUMN_TEMPLATE = "ALTER TABLE {table} ADD COLUMN {column} {datatype};"
|
|
29
|
+
DROP_COLUMN_TEMPLATE = "ALTER TABLE {table} DROP COLUMN {column};"
|
|
30
|
+
ADD_INDEX_TEMPLATE = "CREATE INDEX {index} ON {table} ({column});"
|
|
31
|
+
REMOVE_INDEX_TEMPLATE = "DROP INDEX IF EXISTS {index};"
|
|
32
|
+
ALTER_INDEX_TEMPLATES = (
|
|
33
|
+
"DROP INDEX IF EXISTS {old_index};",
|
|
34
|
+
"CREATE INDEX {new_index} ON {table} ({new_column});",
|
|
35
|
+
)
|
|
36
|
+
ALTER_COLUMN_TEMPLATE = "ALTER TABLE {table} ALTER COLUMN {column} TYPE {datatype};"
|
|
37
|
+
RENAME_COLUMN_TEMPLATE = "ALTER TABLE {table} RENAME COLUMN {old} TO {new};"
|
|
38
|
+
RENAME_TABLE_TEMPLATE = "ALTER TABLE {old_table} RENAME TO {new_table};"
|
|
39
|
+
CREATE_TABLE_TEMPLATE = "CREATE TABLE {table} ({fields_with_type});"
|
|
40
|
+
DROP_TABLE_IF_EXISTS_TEMPLATE = "DROP TABLE IF EXISTS {table} CASCADE;"
|
|
41
|
+
DROP_TABLE_TEMPLATE = "DROP TABLE {table} CASCADE;"
|
|
42
|
+
|
|
43
|
+
def __init__(self, db_adapter: PostgreSQLAdapter):
|
|
44
|
+
super().__init__(db_adapter)
|
|
@@ -79,7 +79,8 @@ class PostgreSQLRepository(BaseRepository):
|
|
|
79
79
|
conditions = self._adjust_conditions(conditions)
|
|
80
80
|
|
|
81
81
|
data = self._execute_within_context(
|
|
82
|
-
self.adapter.get_one, self.table_name, conditions
|
|
82
|
+
self.adapter.get_one, self.table_name, conditions,
|
|
83
|
+
active=self._is_versioned_model()
|
|
83
84
|
)
|
|
84
85
|
|
|
85
86
|
if not data:
|
|
@@ -118,7 +119,8 @@ class PostgreSQLRepository(BaseRepository):
|
|
|
118
119
|
conditions = self._adjust_conditions(conditions)
|
|
119
120
|
# Fetch the records
|
|
120
121
|
records = self._execute_within_context(
|
|
121
|
-
self.adapter.get_many, self.table_name, conditions, sort, limit, offset
|
|
122
|
+
self.adapter.get_many, self.table_name, conditions, sort, limit, offset,
|
|
123
|
+
active=self._is_versioned_model()
|
|
122
124
|
)
|
|
123
125
|
|
|
124
126
|
# If the adapter returned a single dictionary, wrap it in a list
|
|
@@ -164,8 +166,11 @@ class PostgreSQLRepository(BaseRepository):
|
|
|
164
166
|
int: The count of matching records.
|
|
165
167
|
"""
|
|
166
168
|
# Prepare the actual conditions for the database query
|
|
167
|
-
#
|
|
168
|
-
|
|
169
|
+
# Only add versioning filters for VersionedModel
|
|
170
|
+
if self._is_versioned_model():
|
|
171
|
+
db_conditions = {'latest': True, 'active': True}
|
|
172
|
+
else:
|
|
173
|
+
db_conditions = {}
|
|
169
174
|
if query: # Ensure query is not None before updating
|
|
170
175
|
actual_query = self._adjust_conditions(
|
|
171
176
|
query.copy()) # Adjust UUIDs in query
|
|
@@ -197,7 +197,8 @@ class SurrealDbRepository(BaseRepository):
|
|
|
197
197
|
self.table_name,
|
|
198
198
|
conditions,
|
|
199
199
|
fetch_related=fetch_related,
|
|
200
|
-
additional_fields=additional_fields
|
|
200
|
+
additional_fields=additional_fields,
|
|
201
|
+
active=self._is_versioned_model()
|
|
201
202
|
)
|
|
202
203
|
# prep model context
|
|
203
204
|
self.model()
|
|
@@ -265,7 +266,8 @@ class SurrealDbRepository(BaseRepository):
|
|
|
265
266
|
sort=sort,
|
|
266
267
|
limit=limit,
|
|
267
268
|
fetch_related=fetch_related,
|
|
268
|
-
additional_fields=additional_fields
|
|
269
|
+
additional_fields=additional_fields,
|
|
270
|
+
active=self._is_versioned_model()
|
|
269
271
|
)
|
|
270
272
|
if isinstance(raw, dict):
|
|
271
273
|
raw = [raw]
|
|
@@ -42,6 +42,7 @@ rococo/migrations/common/cli_base.py
|
|
|
42
42
|
rococo/migrations/common/migration_base.py
|
|
43
43
|
rococo/migrations/common/migration_runner.py
|
|
44
44
|
rococo/migrations/common/migration_template.py
|
|
45
|
+
rococo/migrations/common/sql_migration_base.py
|
|
45
46
|
rococo/migrations/mongo/__init__.py
|
|
46
47
|
rococo/migrations/mongo/cli.py
|
|
47
48
|
rococo/migrations/mongo/migration.py
|
|
@@ -95,11 +96,13 @@ tests/__init__.py
|
|
|
95
96
|
tests/base_repository_test.py
|
|
96
97
|
tests/config_test.py
|
|
97
98
|
tests/dynamodb_repository_test.py
|
|
99
|
+
tests/migration_cli_base_test.py
|
|
98
100
|
tests/migration_runner_test.py
|
|
99
101
|
tests/model_test.py
|
|
100
102
|
tests/mongodb_repository_test.py
|
|
101
103
|
tests/mysql_repository_test.py
|
|
102
104
|
tests/postgre_repository_test.py
|
|
105
|
+
tests/sql_migration_base_test.py
|
|
103
106
|
tests/surrealdb_repository_test.py
|
|
104
107
|
tests/test_calculated_properties_fix.py
|
|
105
108
|
tests/test_faxing.py
|