openmodule 18.0.1__tar.gz → 18.1.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 (106) hide show
  1. {openmodule-18.0.1 → openmodule-18.1.0}/PKG-INFO +2 -2
  2. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/database/database.py +3 -4
  3. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/database/migration.py +48 -7
  4. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule.egg-info/PKG-INFO +2 -2
  5. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule.egg-info/requires.txt +1 -1
  6. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_database.py +37 -2
  7. {openmodule-18.0.1 → openmodule-18.1.0}/LICENSE +0 -0
  8. {openmodule-18.0.1 → openmodule-18.1.0}/README.md +0 -0
  9. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/__init__.py +0 -0
  10. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/alert.py +0 -0
  11. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/config.py +0 -0
  12. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/connection_status.py +0 -0
  13. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/core.py +0 -0
  14. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/database/__init__.py +0 -0
  15. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/database/custom_types.py +0 -0
  16. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/database/env.py +0 -0
  17. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/dispatcher.py +0 -0
  18. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/health.py +0 -0
  19. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/logging.py +0 -0
  20. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/messaging.py +0 -0
  21. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/__init__.py +0 -0
  22. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/access_service.py +0 -0
  23. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/alert.py +0 -0
  24. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/base.py +0 -0
  25. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/io.py +0 -0
  26. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/kv_store.py +0 -0
  27. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/pagination.py +0 -0
  28. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/presence.py +0 -0
  29. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/privacy.py +0 -0
  30. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/rpc.py +0 -0
  31. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/settings.py +0 -0
  32. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/signals.py +0 -0
  33. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/validation.py +0 -0
  34. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/vehicle.py +0 -0
  35. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/models/vehicle_listener.py +0 -0
  36. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/rpc/__init__.py +0 -0
  37. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/rpc/client.py +0 -0
  38. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/rpc/common.py +0 -0
  39. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/rpc/server.py +0 -0
  40. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/sentry.py +0 -0
  41. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/threading.py +0 -0
  42. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/__init__.py +0 -0
  43. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/access_service.py +0 -0
  44. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/charset.py +0 -0
  45. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/cleanup.py +0 -0
  46. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/csv_export.py +0 -0
  47. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/databox.py +0 -0
  48. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/db_helper.py +0 -0
  49. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/eventlog.py +0 -0
  50. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/io.py +0 -0
  51. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/kv_store.py +0 -0
  52. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/matching.py +0 -0
  53. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/misc_functions.py +0 -0
  54. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/package_reader.py +0 -0
  55. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/pagination.py +0 -0
  56. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/presence.py +0 -0
  57. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/schedule.py +0 -0
  58. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/settings.py +0 -0
  59. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/signal_listener.py +0 -0
  60. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/translation.py +0 -0
  61. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/validation.py +0 -0
  62. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule/utils/vehicle_listener.py +0 -0
  63. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule.egg-info/SOURCES.txt +0 -0
  64. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule.egg-info/dependency_links.txt +0 -0
  65. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule.egg-info/not-zip-safe +0 -0
  66. {openmodule-18.0.1 → openmodule-18.1.0}/openmodule.egg-info/top_level.txt +0 -0
  67. {openmodule-18.0.1 → openmodule-18.1.0}/setup.cfg +0 -0
  68. {openmodule-18.0.1 → openmodule-18.1.0}/setup.py +0 -0
  69. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_alembic_migrations.py +0 -0
  70. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_alert.py +0 -0
  71. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_checks.py +0 -0
  72. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_config.py +0 -0
  73. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_connection_status.py +0 -0
  74. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_core.py +0 -0
  75. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_dispatcher.py +0 -0
  76. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_health.py +0 -0
  77. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_interrupt.py +0 -0
  78. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_io_listen.py +0 -0
  79. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_logging.py +0 -0
  80. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_messaging.py +0 -0
  81. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_mockrpcclient.py +0 -0
  82. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_model.py +0 -0
  83. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_rpc.py +0 -0
  84. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_sentry.py +0 -0
  85. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_test_alert.py +0 -0
  86. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_test_gate.py +0 -0
  87. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_test_zeromq.py +0 -0
  88. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_access_service.py +0 -0
  89. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_charset.py +0 -0
  90. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_cleanup.py +0 -0
  91. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_csv_export.py +0 -0
  92. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_databox.py +0 -0
  93. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_eventlog.py +0 -0
  94. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_kv_store.py +0 -0
  95. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_kv_store_multiple.py +0 -0
  96. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_matching.py +0 -0
  97. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_misc_functions.py +0 -0
  98. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_package_reader.py +0 -0
  99. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_pagination.py +0 -0
  100. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_presence.py +0 -0
  101. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_schedule.py +0 -0
  102. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_settings.py +0 -0
  103. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_signal.py +0 -0
  104. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_validation.py +0 -0
  105. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_utils_vehicle.py +0 -0
  106. {openmodule-18.0.1 → openmodule-18.1.0}/tests/test_vehicle_listener.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openmodule
3
- Version: 18.0.1
3
+ Version: 18.1.0
4
4
  Summary: Libraries for developing the arivo openmodule
5
5
  Home-page: https://gitlab.com/arivo-public/device-python/openmodule.git
6
6
  Author: ARIVO
@@ -20,7 +20,7 @@ Requires-Dist: pyzmq~=26.2
20
20
  Requires-Dist: pyyaml<7,>=5.0
21
21
  Requires-Dist: editdistance~=0.8.1
22
22
  Requires-Dist: sqlalchemy~=2.0.0
23
- Requires-Dist: alembic<2,>=1.5.4
23
+ Requires-Dist: alembic~=1.18.4
24
24
  Requires-Dist: requests<3,>=2.22
25
25
  Requires-Dist: python-dateutil~=2.9
26
26
  Requires-Dist: python-dotenv~=1.2.0
@@ -1,6 +1,8 @@
1
1
  import os
2
2
  import threading
3
+ import traceback
3
4
  import types
5
+ from multiprocessing import Process, Pipe, ProcessError
4
6
  from typing import Optional, Tuple
5
7
 
6
8
  from sqlalchemy import create_engine, event
@@ -8,11 +10,8 @@ from sqlalchemy.engine import Engine
8
10
  from sqlalchemy.orm import sessionmaker, Session
9
11
  from sqlalchemy.pool import StaticPool
10
12
 
11
- from multiprocessing import Process, Pipe, ProcessError
12
- import traceback
13
-
14
13
 
15
- class MigrationError(BaseException):
14
+ class MigrationError(Exception):
16
15
  # Exception wrapper to create exception with traceback from child process
17
16
  pass
18
17
 
@@ -1,14 +1,16 @@
1
1
  import os
2
2
  import shutil
3
3
  import sys
4
+ import time
4
5
  import warnings
5
- from typing import Optional
6
+ from glob import glob
6
7
 
7
8
  from alembic import command, context
8
9
  from alembic.autogenerate import comparators, renderers
9
10
  from alembic.config import Config
10
11
  from alembic.operations import Operations, MigrateOperation
11
12
  from alembic.runtime.migration import MigrationContext
13
+ from alembic.util.langhelpers import DispatchPriority
12
14
  from sqlalchemy import MetaData, DateTime, inspect, text
13
15
  from sqlalchemy.engine import Engine
14
16
 
@@ -114,26 +116,32 @@ def post_downgrade(operations, operation):
114
116
 
115
117
 
116
118
  @renderers.dispatch_for(PreUpgradeOp)
117
- def render_create_sequence(autogen_context, op):
119
+ def render_pre_upgrade(autogen_context, op):
118
120
  return "op.pre_upgrade()"
119
121
 
120
122
 
121
123
  @renderers.dispatch_for(PreDowngradeOp)
122
- def render_drop_sequence(autogen_context, op):
124
+ def render_pre_downgrade(autogen_context, op):
123
125
  return "op.pre_downgrade()"
124
126
 
125
127
 
126
128
  @renderers.dispatch_for(PostUpgradeOp)
127
- def render_create_sequence(autogen_context, op):
129
+ def render_post_upgrade(autogen_context, op):
128
130
  return "op.post_upgrade()"
129
131
 
130
132
 
131
133
  @renderers.dispatch_for(PostDowngradeOp)
132
- def render_drop_sequence(autogen_context, op):
134
+ def render_post_downgrade(autogen_context, op):
133
135
  return "op.post_downgrade()"
134
136
 
135
137
 
136
- @comparators.dispatch_for("schema")
138
+ # Copilot found this issue:
139
+ # The default priority is MEDIUM, which means our `add_pre_upgrade_hooks`
140
+ # has the same priority as the built-in comparators.
141
+ # As a result, this routine was executed before the built-in comparators,
142
+ # at a time when `upgrade_ops.ops` was still empty.
143
+ # Therefore, no custom operations were available and no render function was called.
144
+ @comparators.dispatch_for("schema", priority=DispatchPriority.LAST)
137
145
  def add_pre_upgrade_hooks(autogen_context, upgrade_ops, schemas):
138
146
  # only add those if any operations exist, otherwise we always have changes
139
147
  if len(upgrade_ops.ops):
@@ -151,10 +159,43 @@ def alembic_config(connection: Engine, alembic_path: str):
151
159
  return alembic_cfg
152
160
 
153
161
 
154
- def migrate_database(engine: Engine, alembic_path: Optional[str] = None):
162
+ def validate_migration_files(alembic_path: str) -> bool:
163
+ """
164
+ This validation function is the final defense line against the problem from CORE-1593.
165
+ We do not apply any migration without our custom auto generated operations.
166
+ Because we need those operations to prevent data loss because of the foreign key behavior of SQLite.
167
+
168
+ This function raises a `ValueError` if a migration file is incorrect.
169
+ """
170
+ migration_folder = os.path.join(alembic_path, "alembic/versions/")
171
+ assert os.path.exists(os.path.abspath(migration_folder)), \
172
+ f"migration folder {os.path.abspath(migration_folder)} does not exist"
173
+ py_files = glob(os.path.join(migration_folder, "*.py"))
174
+ assert len(py_files) > 0, f"no python files found in {migration_folder}"
175
+ for py_file in glob(os.path.join(migration_folder, "*.py")):
176
+ if os.path.basename(py_file) == "__init__.py":
177
+ continue
178
+ with open(py_file) as f:
179
+ file_content = f.read()
180
+ if not all(op in file_content for op in ["op.pre_upgrade()", "op.pre_downgrade()",
181
+ "op.pre_downgrade()", "op.post_downgrade()"]):
182
+ # We introduce a short sleep here because all services enter a crashing state,
183
+ # when the migrations are broken.
184
+ # With Docker's "restart: always" policy, it continuously tries to restart
185
+ # failing processes as fast as possible, which can lead to 100% CPU usage.
186
+ time.sleep(3)
187
+ raise ValueError("Not applying broken migration files! Our custom migration operations "
188
+ f"are not in the migration file {os.path.basename(py_file)}. "
189
+ "Please regenerate them with the correct alembic version from the Openmodule. "
190
+ "For more information see CORE-1593.")
191
+ return True
192
+
193
+
194
+ def migrate_database(engine: Engine, alembic_path: str | None = None):
155
195
  if alembic_path is None:
156
196
  alembic_path = os.path.join(os.getcwd(), "database")
157
197
  assert os.path.exists(os.path.abspath(alembic_path)), f"alembic path {os.path.abspath(alembic_path)} does not exist"
198
+ validate_migration_files(alembic_path)
158
199
  config = alembic_config(engine, alembic_path)
159
200
  command.upgrade(config, "head")
160
201
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openmodule
3
- Version: 18.0.1
3
+ Version: 18.1.0
4
4
  Summary: Libraries for developing the arivo openmodule
5
5
  Home-page: https://gitlab.com/arivo-public/device-python/openmodule.git
6
6
  Author: ARIVO
@@ -20,7 +20,7 @@ Requires-Dist: pyzmq~=26.2
20
20
  Requires-Dist: pyyaml<7,>=5.0
21
21
  Requires-Dist: editdistance~=0.8.1
22
22
  Requires-Dist: sqlalchemy~=2.0.0
23
- Requires-Dist: alembic<2,>=1.5.4
23
+ Requires-Dist: alembic~=1.18.4
24
24
  Requires-Dist: requests<3,>=2.22
25
25
  Requires-Dist: python-dateutil~=2.9
26
26
  Requires-Dist: python-dotenv~=1.2.0
@@ -5,7 +5,7 @@ pyzmq~=26.2
5
5
  pyyaml<7,>=5.0
6
6
  editdistance~=0.8.1
7
7
  sqlalchemy~=2.0.0
8
- alembic<2,>=1.5.4
8
+ alembic~=1.18.4
9
9
  requests<3,>=2.22
10
10
  python-dateutil~=2.9
11
11
  python-dotenv~=1.2.0
@@ -1,7 +1,10 @@
1
1
  import os
2
+ import shutil
2
3
  import time
4
+ from glob import glob
5
+ from tempfile import TemporaryDirectory
3
6
  from threading import Thread
4
- from unittest import TestCase
7
+ from unittest import TestCase, mock
5
8
  from unittest.mock import patch
6
9
 
7
10
  import freezegun
@@ -13,7 +16,7 @@ from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound, DetachedInst
13
16
 
14
17
  from openmodule.config import settings
15
18
  from openmodule.database.database import Database, active_databases, database_path, MigrationError
16
- from openmodule.database.migration import alembic_config
19
+ from openmodule.database.migration import alembic_config, validate_migration_files
17
20
  from openmodule.utils.db_helper import update_query, delete_query
18
21
  from openmodule_test.database import SQLiteTestMixin
19
22
  from tests.database_models_migration import DatabaseCascadeDeleteParentModel, DatabaseCascadeDeleteChildModel
@@ -458,3 +461,35 @@ class DatabaseMigrationProcessFailTest(TestCase):
458
461
  with patch("openmodule.database.database.MigrationProcess.join", side_effect=TimeoutError), \
459
462
  self.assertRaises(TimeoutError):
460
463
  _ = Database(self.get_database_folder(), alembic_path=alembic_path)
464
+
465
+
466
+ class MigrationFileValidationTestCase(TestCase):
467
+ def test_alembic_version_folder_does_not_exist(self):
468
+ with self.assertRaises(AssertionError) as cm:
469
+ validate_migration_files("/does/not/exist")
470
+ self.assertEqual("migration folder /does/not/exist/alembic/versions does not exist", str(cm.exception))
471
+
472
+ def test_no_python_file_are_available(self):
473
+ td = TemporaryDirectory(prefix="__openmodule_test_case__")
474
+ shutil.copytree("../tests/migration_file_broken", td.name, dirs_exist_ok=True)
475
+ migration_folder = os.path.join(td.name, "alembic/versions/")
476
+ py_files = glob(os.path.join(migration_folder, "*.py"))
477
+ for py_file in py_files:
478
+ os.unlink(py_file)
479
+ with self.assertRaises(AssertionError) as cm:
480
+ validate_migration_files(td.name)
481
+ self.assertIn("no python files found in /tmp/__openmodule_test_case__", str(cm.exception))
482
+
483
+ @mock.patch("openmodule.database.migration.time.sleep")
484
+ def test_validation_throw_value_error_for_broken_migration_file(self, time_sleep_mock: mock.MagicMock):
485
+ with self.assertRaises(ValueError) as cm:
486
+ validate_migration_files("../tests/migration_file_broken")
487
+ time_sleep_mock.assert_called_once()
488
+ self.assertEqual("Not applying broken migration files! "
489
+ "Our custom migration operations are not in the migration file 46b275099a0d_initial.py. "
490
+ "Please regenerate them with the correct alembic version from the Openmodule. "
491
+ "For more information see CORE-1593.", str(cm.exception))
492
+
493
+ def test_validation_does_not_throw_value_error_for_correct_migration_file(self):
494
+ result = validate_migration_files("../tests/migration_test_database")
495
+ self.assertTrue(result)
File without changes
File without changes
File without changes
File without changes