openmodule 13.0.2__tar.gz → 13.1.0rc0__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 (222) hide show
  1. {openmodule-13.0.2 → openmodule-13.1.0rc0}/AUTHORS +0 -1
  2. {openmodule-13.0.2 → openmodule-13.1.0rc0}/ChangeLog +14 -27
  3. {openmodule-13.0.2 → openmodule-13.1.0rc0}/PKG-INFO +1 -1
  4. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/access_service.md +25 -1
  5. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/config.py +5 -4
  6. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/models/access_service.py +8 -10
  7. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/models/presence.py +19 -1
  8. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/models/validation.py +1 -1
  9. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/models/vehicle.py +7 -1
  10. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/rpc/client.py +3 -0
  11. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/sentry.py +0 -1
  12. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/threading.py +0 -1
  13. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/eventlog.py +5 -1
  14. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/kv_store.py +77 -51
  15. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/matching.py +0 -1
  16. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/presence.py +67 -54
  17. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule.egg-info/PKG-INFO +1 -1
  18. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule.egg-info/SOURCES.txt +6 -0
  19. openmodule-13.1.0rc0/openmodule.egg-info/pbr.json +1 -0
  20. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule.egg-info/requires.txt +1 -1
  21. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/database.py +1 -1
  22. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/presence.py +24 -6
  23. {openmodule-13.0.2 → openmodule-13.1.0rc0}/requirements.txt +1 -1
  24. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/config.py +2 -0
  25. openmodule-13.1.0rc0/tests/resources/translation/locale/de/LC_MESSAGES/translation.mo +0 -0
  26. openmodule-13.1.0rc0/tests/resources/translation/locale/de/LC_MESSAGES/translation.po +21 -0
  27. openmodule-13.1.0rc0/tests/resources/translation/locale/en/LC_MESSAGES/translation.mo +0 -0
  28. openmodule-13.1.0rc0/tests/resources/translation/locale/en/LC_MESSAGES/translation.po +21 -0
  29. openmodule-13.1.0rc0/tests/resources/translation/locale/translation.pot +8 -0
  30. openmodule-13.1.0rc0/tests/resources/translation/translate.sh +3 -0
  31. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_rpc.py +3 -3
  32. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_access_service.py +22 -1
  33. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_eventlog.py +8 -4
  34. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_kv_store.py +91 -30
  35. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_presence.py +384 -3
  36. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_vehicle.py +5 -2
  37. openmodule-13.0.2/openmodule.egg-info/pbr.json +0 -1
  38. {openmodule-13.0.2 → openmodule-13.1.0rc0}/.gitlab-ci.yml +0 -0
  39. {openmodule-13.0.2 → openmodule-13.1.0rc0}/LICENSE +0 -0
  40. {openmodule-13.0.2 → openmodule-13.1.0rc0}/README.md +0 -0
  41. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/coding_standard.md +0 -0
  42. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/commands.md +0 -0
  43. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/connection_status_listener.md +0 -0
  44. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/core.md +0 -0
  45. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/csv_export.md +0 -0
  46. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/database.md +0 -0
  47. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/deprecated_code/README.md +0 -0
  48. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/deprecated_code/access_service/openmodule/models/access_service.py +0 -0
  49. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/deprecated_code/access_service/openmodule/utils/access_service.py +0 -0
  50. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/deprecated_code/access_service/openmodule_test/access_service.py +0 -0
  51. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/deprecated_code/access_service/tests/test_utils_access_service.py +0 -0
  52. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/deprecated_code/api/openmodule/utils/api.py +0 -0
  53. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/deprecated_code/api/openmodule_test/api.py +0 -0
  54. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/deprecated_code/api/tests/test_utils_api.py +0 -0
  55. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/deprecated_code/package_reader/openmodule/utils/package_reader.py +0 -0
  56. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/deprecated_code/package_reader/openmodule_test/fake_package_creator.py +0 -0
  57. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/deprecated_code/package_reader/tests/test_package_reader.py +0 -0
  58. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/event_sending.md +0 -0
  59. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/health.md +0 -0
  60. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/known_issues.md +0 -0
  61. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/migrations.md +0 -0
  62. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/package_reader.md +0 -0
  63. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/qc.md +0 -0
  64. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/rpc.md +0 -0
  65. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/settings.md +0 -0
  66. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/testing.md +0 -0
  67. {openmodule-13.0.2 → openmodule-13.1.0rc0}/docs/translation.md +0 -0
  68. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/__init__.py +0 -0
  69. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/alert.py +0 -0
  70. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/checks.py +0 -0
  71. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/connection_status.py +0 -0
  72. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/core.py +0 -0
  73. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/database/custom_types.py +0 -0
  74. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/database/database.py +0 -0
  75. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/database/env.py +0 -0
  76. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/dispatcher.py +0 -0
  77. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/health.py +0 -0
  78. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/logging.py +0 -0
  79. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/messaging.py +0 -0
  80. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/models/__init__.py +0 -0
  81. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/models/alert.py +0 -0
  82. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/models/base.py +0 -0
  83. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/models/io.py +0 -0
  84. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/models/kv_store.py +0 -0
  85. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/models/privacy.py +0 -0
  86. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/models/rpc.py +0 -0
  87. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/models/settings.py +0 -0
  88. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/rpc/__init__.py +0 -0
  89. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/rpc/common.py +0 -0
  90. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/rpc/server.py +0 -0
  91. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/__init__.py +0 -0
  92. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/access_service.py +0 -0
  93. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/charset.py +0 -0
  94. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/csv_export.py +0 -0
  95. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/databox.py +0 -0
  96. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/db_helper.py +0 -0
  97. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/io.py +0 -0
  98. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/misc_functions.py +0 -0
  99. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/package_reader.py +0 -0
  100. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/schema.py +0 -0
  101. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/settings.py +0 -0
  102. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/translation.py +0 -0
  103. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule/utils/validation.py +0 -0
  104. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule.egg-info/dependency_links.txt +0 -0
  105. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule.egg-info/not-zip-safe +0 -0
  106. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule.egg-info/top_level.txt +0 -0
  107. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_commands/__init__.py +0 -0
  108. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_commands/setup.cfg +0 -0
  109. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_commands/setup.py +0 -0
  110. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_commands/translate.py +0 -0
  111. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/__init__.py +0 -0
  112. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/alert.py +0 -0
  113. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/connection_status.py +0 -0
  114. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/core.py +0 -0
  115. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/eventlistener.py +0 -0
  116. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/files.py +0 -0
  117. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/gate.py +0 -0
  118. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/health.py +0 -0
  119. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/interrupt.py +0 -0
  120. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/io_simulator.py +0 -0
  121. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/package_reader.py +0 -0
  122. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/requirements.txt +0 -0
  123. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/rpc.py +0 -0
  124. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/settings.py +0 -0
  125. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/setup.cfg +0 -0
  126. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/setup.py +0 -0
  127. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/utils.py +0 -0
  128. {openmodule-13.0.2 → openmodule-13.1.0rc0}/openmodule_test/zeromq.py +0 -0
  129. {openmodule-13.0.2 → openmodule-13.1.0rc0}/setup.cfg +0 -0
  130. {openmodule-13.0.2 → openmodule-13.1.0rc0}/setup.py +0 -0
  131. {openmodule-13.0.2 → openmodule-13.1.0rc0}/test-requirements.txt +0 -0
  132. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/__init__.py +0 -0
  133. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/database_models_migration.py +0 -0
  134. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/database_models_test.py +0 -0
  135. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/invalid_database/alembic/README +0 -0
  136. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/invalid_database/alembic/__init__.py +0 -0
  137. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/invalid_database/alembic/env.py +0 -0
  138. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/invalid_database/alembic/script.py.mako +0 -0
  139. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/invalid_database/alembic/versions/ff26e54332f9_datetime_models.py +0 -0
  140. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/invalid_database/alembic.ini +0 -0
  141. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/invalid_database/makemigration.sh +0 -0
  142. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/migration_test_database/__init__.py +0 -0
  143. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/migration_test_database/alembic/__init__.py +0 -0
  144. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/migration_test_database/alembic/env.py +0 -0
  145. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/migration_test_database/alembic/script.py.mako +0 -0
  146. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/migration_test_database/alembic/versions/19789aa5361c_initial.py +0 -0
  147. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/migration_test_database/alembic/versions/19d887929ae7_alter.py +0 -0
  148. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/migration_test_database/alembic/versions/__init__.py +0 -0
  149. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/migration_test_database/alembic.ini +0 -0
  150. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/migration_test_database/makemigration.sh +0 -0
  151. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/configs/config.py +0 -0
  152. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/configs/test_config.py +0 -0
  153. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/configs/test_config_1.py +0 -0
  154. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/standard_schemes/DEFAULT-10.yml +0 -0
  155. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/standard_schemes/DEFAULT-20.yml +0 -0
  156. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/standard_schemes/LEGACY-0.yml +0 -0
  157. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/utils_matching/A-10.yml +0 -0
  158. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/utils_matching/A-20.yml +0 -0
  159. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/utils_matching/DEFAULT-10.yml +0 -0
  160. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/utils_matching/DEFAULT-20.yml +0 -0
  161. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/utils_matching/DEFAULT-30.yml +0 -0
  162. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/utils_matching/LEGACY-0.yml +0 -0
  163. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/utils_matching/TEST-10.yml +0 -0
  164. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/utils_matching/TEST-20.yml +0 -0
  165. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/utils_matching/TEST-30.yml +0 -0
  166. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/resources/utils_matching/TEST-40.yml +0 -0
  167. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_access_service_database/alembic/__init__.py +0 -0
  168. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_access_service_database/alembic/env.py +0 -0
  169. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_access_service_database/alembic/script.py.mako +0 -0
  170. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_access_service_database/alembic/versions/7bd4fcd38fde_removed_nfc_and_pin.py +0 -0
  171. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_access_service_database/alembic/versions/9ca98a2e5674_added_parksettings_id.py +0 -0
  172. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_access_service_database/alembic/versions/c821971f9230_initial.py +0 -0
  173. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_access_service_database/alembic.ini +0 -0
  174. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_access_service_database/makemigration.sh +0 -0
  175. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_alert.py +0 -0
  176. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_checks.py +0 -0
  177. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_config.py +0 -0
  178. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_connection_status.py +0 -0
  179. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_core.py +0 -0
  180. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_database/alembic/__init__.py +0 -0
  181. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_database/alembic/env.py +0 -0
  182. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_database/alembic/script.py.mako +0 -0
  183. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_database/alembic/versions/32b8c728abbf_initial.py +0 -0
  184. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_database/alembic.ini +0 -0
  185. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_database/makemigration.sh +0 -0
  186. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_database.py +0 -0
  187. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_dispatcher.py +0 -0
  188. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_health.py +0 -0
  189. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_interrupt.py +0 -0
  190. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_io_listen.py +0 -0
  191. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_database/alembic/__init__.py +0 -0
  192. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_database/alembic/env.py +0 -0
  193. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_database/alembic/script.py.mako +0 -0
  194. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_database/alembic/versions/9c5c944221f4_deprecated_kv_entry_example.py +0 -0
  195. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_database/alembic/versions/c55a69026a25_initial.py +0 -0
  196. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_database/alembic.ini +0 -0
  197. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_database/makemigration.sh +0 -0
  198. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_multiple_database/alembic/README +0 -0
  199. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_multiple_database/alembic/__init__.py +0 -0
  200. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_multiple_database/alembic/env.py +0 -0
  201. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_multiple_database/alembic/script.py.mako +0 -0
  202. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_multiple_database/alembic/versions/cdb3214131a9_initial.py +0 -0
  203. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_multiple_database/alembic.ini +0 -0
  204. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_kv_store_multiple_database/makemigration.sh +0 -0
  205. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_messaging.py +0 -0
  206. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_mockrpcclient.py +0 -0
  207. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_model.py +0 -0
  208. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_schema.py +0 -0
  209. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_sentry.py +0 -0
  210. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_test_alert.py +0 -0
  211. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_test_gate.py +0 -0
  212. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_test_zeromq.py +0 -0
  213. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_charset.py +0 -0
  214. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_csv_export.py +0 -0
  215. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_databox.py +0 -0
  216. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_kv_store_multiple.py +0 -0
  217. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_matching.py +0 -0
  218. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_misc_functions.py +0 -0
  219. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_package_reader.py +0 -0
  220. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_settings.py +0 -0
  221. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tests/test_utils_validation.py +0 -0
  222. {openmodule-13.0.2 → openmodule-13.1.0rc0}/tox.ini +0 -0
@@ -4,7 +4,6 @@ Matthias Mietschnig <m.mietschnig@arivo.co>
4
4
  Philipp Reitter <i@philipp.ninja>
5
5
  Philipp Reitter <p.reitter@accessio.at>
6
6
  Philipp Reitter <p.reitter@gmail.com>
7
- Stefan Bräuer <s.braeuer@ariv.co>
8
7
  Stefan Bräuer <s.braeuer@arivo.co>
9
8
  Thomas Senfter <t.senfter@accessio.at>
10
9
  Thomas Senfter <t.senfter@arivo.co>
@@ -1,6 +1,20 @@
1
1
  CHANGES
2
2
  =======
3
3
 
4
+ v13.1.0.rc0
5
+ -----------
6
+
7
+ * OM-61 Openmodule Anpassungen für Access Events
8
+ * [OM-56] added enter\_time and leave\_time to vehicle and implemented presence message changes
9
+ * fixes and refactoring of presencelistener
10
+ * Resolve OM-78 improved KVStore performance
11
+ * fixes INBOX-2510
12
+
13
+ v13.0.3
14
+ -------
15
+
16
+ * added garage\_settings mode "barrier\_bypass" in settings models removal of some unused imports
17
+
4
18
  v13.0.2
5
19
  -------
6
20
 
@@ -128,8 +142,6 @@ v11.1.0.rc0
128
142
  * removing ="asdf" for strings
129
143
  * added decimal separator support for english
130
144
  * some testcases for csv exporter, incorrect default value + static missing [skip ci]
131
- * fixed comment
132
- * added csv\_export library (no testcases)
133
145
  * removed mock rpcs from schema
134
146
 
135
147
  v11.0.3
@@ -217,28 +229,3 @@ v8.1.2
217
229
 
218
230
  * dont register schemas for mock handlers
219
231
  * fixes a race condition in the io tests
220
- * update known issues
221
-
222
- v8.1.1
223
- ------
224
-
225
- * adds multiprocessing logging to openmodule requirements
226
-
227
- v8.1.0
228
- ------
229
-
230
- * changelog and breaking changes updated
231
- * \* Adds an IO Listener \* Adds a SigTERM interrupt handler which converts to KeyboardInterrupt so shutdown in Docker containers is easier \* The main Test Mixin was refactored to start a separate process and raise a Interrupt. The other test methods were removed due to being hacky and crashing tox/pytest from time to time. Since this only breaks test code we don't bump the major version. \* The openmodule now waits for a connection with the message broker on startup \* Fixes an issue in the RPCServerTestMixin
232
- * RPCServerTestMixin now only registers one random channel instead of all channels on the server. (Author: Philipp Reitter)
233
- * add infos to CountMessage
234
- * Added line to exception\_in\_function to call new \_signal\_in\_function if exception is a signal or KeyboardInterrupt
235
-
236
- v8.0.1
237
- ------
238
-
239
- * fixed loading empty yamls fixes rpc server not logging info in debug log
240
-
241
- v8.0.0
242
- ------
243
-
244
- * fix in ci job
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openmodule
3
- Version: 13.0.2
3
+ Version: 13.1.0rc0
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
@@ -21,6 +21,11 @@ You have to implement the `rpc_check_access` function and probably add some othe
21
21
 
22
22
  An example implementations is shown below.
23
23
 
24
+ **Important:** Every Access Service has to add their human-readable description in the `AccessCheckResponse`.
25
+ The default value of `readable_service_name` is `_("__READABLE_NAME")`, so you do not have to add
26
+ this variable explicitly.
27
+
28
+
24
29
  ```python
25
30
  def rpc_check_access(self, request: AccessCheckRequest, _) -> AccessCheckResponse:
26
31
  """
@@ -44,9 +49,28 @@ def rpc_check_access(self, request: AccessCheckRequest, _) -> AccessCheckRespons
44
49
 
45
50
  self.log.info(f"Found {len(accesses)} matching accesses "
46
51
  f"where {len([a for a in accesses if a.accepted])} are valid")
47
- return AccessCheckResponse(success=True, accesses=accesses)
52
+ return AccessCheckResponse(success=True, accesses=accesses, readable_service_name=_("Test Access Service"))
48
53
  ```
49
54
 
55
+ ### Access Service Reject Reasons
56
+
57
+ In the enum `AccessCheckRejectReason` common reject reasons can be found.
58
+
59
+ #### Reject Reason "wrong_gate"
60
+
61
+ This reject reason is automatically set if the function `check_accesses_valid_at_gate()` is used.
62
+
63
+ #### Reject Reason "wrong_time"
64
+
65
+ If your access service has some time restriction the reject reason `wrong_time` can be returned.
66
+ **Do not** use this for accesses with start and end (e.g. permanent access for a month)
67
+ as these accesses should be ignored if outside start and end.
68
+
69
+ #### Reject Reason "custom"
70
+
71
+ If the given e.g. license plate should be rejected for a specific other reason the value `custom` should
72
+ be used. If the reject reason `custom` is used then the string field `supplementary_infos` has to be set!
73
+
50
74
  ## AccessServiceWithSessions
51
75
 
52
76
  In addition to the Access Service stuff implement the session handling functions `check_in_session`, `check_out_session`
@@ -234,12 +234,13 @@ def testing():
234
234
 
235
235
 
236
236
  def database_folder() -> str:
237
- path = string("DATABASE_FOLDER", f"/data/sqlite/")
238
237
  if testing():
239
- path = "../sqlite/test/"
238
+ default_path = "../sqlite/test/"
240
239
  elif debug():
241
- path = "../sqlite/debug/"
242
- return path
240
+ default_path = "../sqlite/debug/"
241
+ else:
242
+ default_path = "/data/sqlite/"
243
+ return string("DATABASE_FOLDER", default_path)
243
244
 
244
245
 
245
246
  def run_checks() -> bool:
@@ -1,18 +1,12 @@
1
+ from datetime import datetime
1
2
  from enum import Enum
2
- from typing import List, Optional, Dict, Union
3
+ from typing import List, Optional, Dict
3
4
 
4
5
  from pydantic import Field, root_validator
5
6
 
6
7
  from openmodule.models.base import OpenModuleModel, timezone_validator
7
8
  from openmodule.models.vehicle import Medium, LPRMedium, MakeModel
8
-
9
- from dateutil import tz
10
- from dateutil.parser import parse
11
- from dateutil.rrule import rrulestr
12
- from dateutil.tz import UTC
13
- from pydantic import validator
14
- import re
15
- from datetime import datetime, tzinfo, timedelta
9
+ from openmodule.utils.translation import _
16
10
 
17
11
 
18
12
  class AccessCategory(str, Enum):
@@ -72,6 +66,7 @@ class AccessCheckAccess(OpenModuleModel):
72
66
  category: AccessCategory # category used for sorting and eventlog
73
67
  used_medium: Medium # medium of the access
74
68
  access_data: Dict # complete access data, will be used only for display and debug purposes
69
+ supplementary_infos: Optional[str] # field has to be if reject reason is "custom"
75
70
 
76
71
  accepted: bool # if access service decided access can enter
77
72
  reject_reason: Optional[AccessCheckRejectReason] # only if not accepted: reason for not accepted
@@ -88,9 +83,12 @@ class AccessCheckAccess(OpenModuleModel):
88
83
  if values["category"] in [AccessCategory.shortterm, AccessCategory.unknown] \
89
84
  and values["group_limit"] is not None:
90
85
  raise ValueError("group_limit must not be set for shortterm and unknown accesses")
86
+ if values.get("reject_reason") == AccessCheckRejectReason.custom and not values.get("supplementary_infos"):
87
+ raise ValueError("supplementary_infos must be set if reject_reason is custom")
91
88
  return values
92
89
 
93
90
 
94
91
  class AccessCheckResponse(OpenModuleModel):
95
92
  success: bool
96
- accesses: List[AccessCheckAccess] = [] # list of all matched accesses (including already rejected ones)
93
+ accesses: List[AccessCheckAccess] = [] # list of all matched accesses (including already rejected ones)
94
+ readable_service_name: str = _("__READABLE_NAME") # readable name of the service
@@ -1,7 +1,7 @@
1
1
  from datetime import datetime
2
2
  from typing import Optional, List
3
3
 
4
- from pydantic import Field
4
+ from pydantic import Field, root_validator
5
5
 
6
6
  from openmodule.models.base import ZMQMessage, OpenModuleModel, Gateway, timezone_validator
7
7
  from openmodule.models.vehicle import Medium, LPRMedium, MakeModel, PresenceAllIds, EnterDirection, QRMedium
@@ -24,14 +24,23 @@ class PresenceBaseData(OpenModuleModel):
24
24
  make_model: Optional[MakeModel]
25
25
  all_ids: PresenceAllIds
26
26
  enter_direction: EnterDirection = EnterDirection.unknown
27
+ enter_time: datetime
27
28
 
28
29
  _tz_last_update = timezone_validator("last_update")
30
+ _tz_enter_time = timezone_validator("enter_time")
29
31
 
30
32
  class Config:
31
33
  # allows setting attributes both via the alias, and the field name.
32
34
  # is used to rename old variables which are hard to understand by their name (e.g. id -> medium id)
33
35
  allow_population_by_field_name = True
34
36
 
37
+ @root_validator(pre=True)
38
+ def set_enter_time_default(cls, values):
39
+ """for backward compatibility, set enter_time based on vehicle_id"""
40
+ if values.get("enter_time") is None:
41
+ values["enter_time"] = values["vehicle_id"] / 1e9 # timestamp from vehicle_id
42
+ return values
43
+
35
44
 
36
45
  class PresenceBaseMessage(PresenceBaseData, ZMQMessage):
37
46
  pass
@@ -58,6 +67,15 @@ class PresenceForwardMessage(PresenceBaseMessage):
58
67
  class PresenceLeaveMessage(PresenceBaseMessage):
59
68
  type: str = "leave"
60
69
  num_presents: int = Field(0, alias="num-presents")
70
+ leave_time: datetime = Field(..., alias="leave-time")
71
+
72
+ _tz_leave_time = timezone_validator("leave_time")
73
+
74
+ @root_validator(pre=True)
75
+ def set_leave_time_default(cls, values):
76
+ if values.get("leave_time") is None:
77
+ values["leave_time"] = values["timestamp"] # timestamp from message timestamp
78
+ return values
61
79
 
62
80
 
63
81
  class PresenceEnterMessage(PresenceBaseMessage):
@@ -1,5 +1,5 @@
1
1
  from enum import Enum
2
- from typing import List, Optional, Union
2
+ from typing import List, Optional
3
3
 
4
4
  from openmodule.models.base import OpenModuleModel, base64_validator, CostEntryData
5
5
 
@@ -1,7 +1,8 @@
1
+ from datetime import datetime
1
2
  from enum import Enum
2
3
  from typing import Optional, List, Iterable, Union
3
4
 
4
- from openmodule.models.base import OpenModuleModel
5
+ from openmodule.models.base import OpenModuleModel, timezone_validator
5
6
 
6
7
 
7
8
  def _truncate(x):
@@ -115,6 +116,11 @@ class Vehicle(OpenModuleModel):
115
116
  make_model: Optional[MakeModel]
116
117
  all_ids: PresenceAllIds = []
117
118
  enter_direction: EnterDirection = EnterDirection.unknown
119
+ enter_time: datetime
120
+ leave_time: Optional[datetime]
121
+
122
+ _tz_enter_time = timezone_validator("enter_time")
123
+ _tz_leave_time = timezone_validator("leave_time")
118
124
 
119
125
  def iterate_media(self) -> Iterable[Union[Medium, LPRMedium]]:
120
126
  """
@@ -203,3 +203,6 @@ class RPCClient:
203
203
  if entry:
204
204
  entry.response = response.response
205
205
  entry.ready.set()
206
+ else:
207
+ self.log.error("Received response after timeout from %s, type: %s, id: %s",
208
+ response.name, response.type, response.rpc_id)
@@ -1,4 +1,3 @@
1
- import logging
2
1
  from socket import gethostname
3
2
  from typing import Optional, Dict, Any
4
3
  from typing import TYPE_CHECKING
@@ -4,7 +4,6 @@ import os
4
4
  import sys
5
5
  from functools import partial
6
6
 
7
- import logging
8
7
  import time
9
8
  from zmq import ContextTerminated
10
9
  from openmodule.config import settings
@@ -1,13 +1,15 @@
1
+ import logging
1
2
  from datetime import datetime
2
3
  from enum import Enum
3
4
  from typing import Optional, Dict, Union
4
5
 
5
6
  from pydantic import validator, root_validator
6
7
 
7
- from openmodule.config import testing
8
8
  from openmodule.core import core
9
9
  from openmodule.models.base import OpenModuleModel, ZMQMessage, timezone_validator
10
10
 
11
+ log = logging.getLogger("Eventlog")
12
+
11
13
 
12
14
  class AnonymizationType(str, Enum):
13
15
  lpr = "lpr" # anonymize to "***AB"
@@ -119,4 +121,6 @@ class EventlogMessage(ZMQMessage):
119
121
 
120
122
 
121
123
  def send_event(infos: EventInfo, message: str, **message_kwargs: MessageKwarg):
124
+ format_kwargs = {key: kwarg.value for key, kwarg in message_kwargs.items()}
125
+ log.info(f"Sending event: {message.format(**format_kwargs)}")
122
126
  EventlogMessage.create(infos=infos, message=message, **message_kwargs).send()
@@ -1,5 +1,4 @@
1
1
  import hashlib
2
- import json
3
2
  import logging
4
3
  import random
5
4
  import threading
@@ -7,6 +6,7 @@ import time
7
6
  import warnings
8
7
  from typing import Type, Optional, List, Union, Dict
9
8
 
9
+ import orjson
10
10
  from sqlalchemy import Column, String, Integer
11
11
  from sqlalchemy.ext.declarative import declarative_base
12
12
 
@@ -41,12 +41,12 @@ class KVEntry:
41
41
  """
42
42
  raise NotImplementedError
43
43
 
44
- def comparison_value(self) -> str:
44
+ def comparison_value(self) -> str: # pragma: no cover
45
45
  """
46
- return some hash or other string. Changed messages are only sent if this value changed for a key
47
- if you don't want changed messages, use for efficiency:
46
+ Only used for KVStoreWithChangedNotification
47
+ return some hash or string. Changed messages are only sent if this value changed for a key
48
48
  """
49
- return self.key
49
+ raise NotImplementedError
50
50
 
51
51
 
52
52
  class KVStore:
@@ -93,24 +93,24 @@ class KVStore:
93
93
  """RPCs for kv_sync are filtered by service name"""
94
94
  return request.service == self.service_name
95
95
 
96
- def _kvs_check_etag_mismatched(self, request: KVSetRequest, current_kvs: List[KVEntry]):
96
+ def _kvs_check_etag_mismatched(self, request: KVSetRequest, current_kvs: Dict[str, int]):
97
97
  for kv in request.kvs:
98
- for current in current_kvs:
99
- if kv.key == current.key and kv.previous_e_tag == current.e_tag:
100
- break
101
- if kv.key == current.key and kv.previous_e_tag is None:
102
- self.log.warning(f"Key exists but no previous ETag given for key {kv.key}")
103
- raise self.ETagMismatchException("No previous ETag")
104
- else:
105
- if kv.previous_e_tag is not None:
106
- self.log.warning(f"Key does not exists but previous ETag given for key {kv.key}")
107
- raise self.ETagMismatchException("Previous ETag given but not in database")
98
+ current_e_tag = current_kvs.get(kv.key)
99
+ if current_e_tag is None and kv.previous_e_tag is not None:
100
+ self.log.warning(f"Key does not exists but previous ETag given for key {kv.key}")
101
+ raise self.ETagMismatchException("Previous ETag given but not in database")
102
+ elif current_e_tag is not None and kv.previous_e_tag is None:
103
+ self.log.warning(f"Key exists but no previous ETag given for key {kv.key}")
104
+ raise self.ETagMismatchException("No previous ETag")
105
+ elif current_e_tag != kv.previous_e_tag:
106
+ self.log.warning(f"Previous ETag does not match for key {kv.key}")
107
+ raise self.ETagMismatchException("Previous ETag does not match")
108
108
 
109
109
  def _kvs_set_parse_kvs(self, kvs: List[KVSetRequestKV]) -> List[KVEntry]:
110
110
  to_set = []
111
111
  deprecation_warning = False
112
112
  for kv in kvs:
113
- value = json.loads(kv.value)
113
+ value = orjson.loads(kv.value)
114
114
  if value is not None:
115
115
  instances = self.database_table.parse_value(value)
116
116
  # fallback for old parse_value() implementations
@@ -134,14 +134,6 @@ class KVStore:
134
134
  to_set += instances
135
135
  return to_set
136
136
 
137
- def _get_comparison_values(self, updated_kv_entries: List[KVEntry]):
138
- # only used in KVStoreWithChangedNotification
139
- pass
140
-
141
- def _send_changed_notification(self, success_keys: List[str], old_values: Dict[str, str]):
142
- # only used in KVStoreWithChangedNotification
143
- pass
144
-
145
137
  def kvs_set(self, request: KVSetRequest, _) -> KVSetResponse:
146
138
  """
147
139
  This method is called by the server to set values in the database.
@@ -154,24 +146,23 @@ class KVStore:
154
146
  if not request.kvs:
155
147
  return KVSetResponse()
156
148
  with self.db() as session:
157
- current_kvs = \
158
- session.query(self.database_table).filter(self.database_table.key.in_([kv.key for kv in request.kvs]))
159
- comparison_values = self._get_comparison_values(current_kvs)
149
+ current_kvs: Dict[str, int] = \
150
+ {kv[0]: kv[1]
151
+ for kv in session.query(self.database_table).
152
+ filter(self.database_table.key.in_([kv.key for kv in request.kvs])).
153
+ with_entities(self.database_table.key, self.database_table.e_tag).all()}
160
154
  self._kvs_check_etag_mismatched(request, current_kvs) # raises Exception on mismatch
161
155
  to_set = self._kvs_set_parse_kvs(request.kvs)
162
156
  try:
163
157
  if current_kvs:
164
158
  delete_query(session, session.query(self.database_table).filter(
165
- self.database_table.key.in_([kv.key for kv in current_kvs])))
159
+ self.database_table.key.in_(list(current_kvs.keys()))))
166
160
  if to_set:
167
161
  session.add_all(to_set)
168
162
  session.commit()
169
163
  except Exception as e:
170
164
  session.rollback()
171
165
  raise e
172
-
173
- if request.kvs:
174
- self._send_changed_notification([s.key for s in request.kvs], comparison_values)
175
166
  return KVSetResponse()
176
167
 
177
168
  def kvs_sync(self, request: KVSyncRequest, _) -> KVSyncResponse:
@@ -183,15 +174,20 @@ class KVStore:
183
174
  """
184
175
  self.log.debug("Syncing kvs for %s", request.service)
185
176
  with self.db() as session:
186
- current_kvs = session.query(self.database_table).all()
187
- changed_kvs = {key: current.e_tag
188
- for current in current_kvs for key, e_tag in request.kvs.items()
189
- if current.key == key and current.e_tag != e_tag}
190
- missing_kvs = {key: e_tag for key, e_tag in request.kvs.items() if
191
- not any(current for current in current_kvs if current.key == key)}
192
- additional_kvs = {current.key: current.e_tag for current in current_kvs
193
- if not any(key for key, _ in request.kvs.items() if key == current.key)}
194
- return KVSyncResponse(additions=additional_kvs, changes=changed_kvs, missing=missing_kvs)
177
+ current_kvs: Dict[str, int] = \
178
+ {kv[0]: kv[1]
179
+ for kv in session.query(self.database_table).
180
+ with_entities(self.database_table.key, self.database_table.e_tag).all()}
181
+ changed_kvs = {}
182
+ missing_kvs = {}
183
+ for key, server_e_tag in request.kvs.items():
184
+ device_e_tag = current_kvs.pop(key, None)
185
+ if device_e_tag is None:
186
+ missing_kvs[key] = server_e_tag
187
+ elif device_e_tag != server_e_tag:
188
+ changed_kvs[key] = device_e_tag
189
+ additional_kvs = current_kvs
190
+ return KVSyncResponse(additions=additional_kvs, changes=changed_kvs, missing=missing_kvs)
195
191
 
196
192
  def _log_sync_rpc_response(self, rpc_entry: RPCClient.RPCEntry) -> bool:
197
193
  """
@@ -329,25 +325,55 @@ class KVStoreHandler:
329
325
 
330
326
 
331
327
  class KVStoreWithChangedNotification(KVStore):
332
- def _get_comparison_values(self, updated_kv_entries: List[KVEntry]):
328
+ def _get_comparison_values(self, kv_entries: List[KVEntry]) -> Dict[str, str]:
333
329
  """
334
330
  make sure your db model has a meaningful `comparison_value` method
335
331
  """
336
- raise NotImplementedError
332
+ return {kv.key: kv.comparison_value() for kv in kv_entries}
337
333
 
338
- def _find_changed_kvs(self, success_keys: List[str], old_values: Dict[str, str]):
334
+ def _find_changed_kvs(self, new_values: Dict[str, str], old_values: Dict[str, str]) -> List[str]:
339
335
  changed = []
340
- with self.db() as session:
341
- kvs = session.query(self.database_table).filter(self.database_table.key.in_(success_keys)).all()
342
- new_values = {kv.key: kv.comparison_value() for kv in kvs}
343
- for key in success_keys:
344
- if key not in old_values or key not in new_values or old_values[key] != new_values[key]:
345
- changed.append(key)
336
+ for key, new_value in new_values.items():
337
+ old_value = old_values.pop(key, None)
338
+ if old_value is None or old_value != new_value:
339
+ changed.append(key)
340
+ changed += list(old_values.keys())
346
341
  return changed
347
342
 
348
- def _send_changed_notification(self, success_keys: List[str], old_values: Dict[str, str]):
343
+ def _send_changed_notification(self, new_values: Dict[str, str], old_values: Dict[str, str]): # pragma: no cover
349
344
  """
350
345
  consider using self._find_changed_kvs when implementing this
351
346
  also make sure your db model has a meaningful `comparison_value` method
352
347
  """
353
348
  raise NotImplementedError
349
+
350
+ def kvs_set(self, request: KVSetRequest, _) -> KVSetResponse:
351
+ """
352
+ This method is called by the server to set values in the database.
353
+ It must never be called from the device.
354
+ Database entries must only be created/edited/deleted by this method.
355
+ Entries are never changed, only deleted and recreated with new values and e_tag.
356
+ The e_tag of the current value of a key needs to match previous_e_tag in the request, otherwise the key is
357
+ rejected.
358
+ """
359
+ if not request.kvs:
360
+ return KVSetResponse()
361
+ with self.db() as session:
362
+ full_current_kvs: List[KVEntry] = session.query(self.database_table). \
363
+ filter(self.database_table.key.in_([kv.key for kv in request.kvs]))
364
+ comparison_values = self._get_comparison_values(full_current_kvs)
365
+ current_kvs = {kv.key: kv.e_tag for kv in full_current_kvs}
366
+ self._kvs_check_etag_mismatched(request, current_kvs) # raises Exception on mismatch
367
+ to_set = self._kvs_set_parse_kvs(request.kvs)
368
+ try:
369
+ if current_kvs:
370
+ delete_query(session, session.query(self.database_table).filter(
371
+ self.database_table.key.in_(list(current_kvs.keys()))))
372
+ if to_set:
373
+ session.add_all(to_set)
374
+ session.commit()
375
+ self._send_changed_notification(self._get_comparison_values(to_set), comparison_values)
376
+ except Exception as e:
377
+ session.rollback()
378
+ raise e
379
+ return KVSetResponse()
@@ -1,5 +1,4 @@
1
1
  import glob
2
- import threading
3
2
  import unicodedata
4
3
  from io import StringIO
5
4