mmisp-lib 0.8.3__tar.gz → 0.9.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 (171) hide show
  1. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/PKG-INFO +4 -3
  2. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/pyproject.toml +2 -2
  3. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/attributes.py +24 -31
  4. mmisp_lib-0.9.0/src/mmisp/api_schemas/common.py +53 -0
  5. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/events.py +25 -24
  6. mmisp_lib-0.9.0/src/mmisp/api_schemas/roles.py +205 -0
  7. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/sharing_groups.py +42 -2
  8. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/users.py +1 -36
  9. mmisp_lib-0.9.0/src/mmisp/commandline_tool/setup.py +33 -0
  10. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/mixins.py +0 -2
  11. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/attribute.py +173 -5
  12. mmisp_lib-0.9.0/src/mmisp/db/models/event.py +269 -0
  13. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/organisation.py +18 -1
  14. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/role.py +40 -1
  15. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/sharing_group.py +1 -1
  16. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/user.py +1 -1
  17. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/lib/attributes.py +12 -5
  18. mmisp_lib-0.9.0/src/mmisp/lib/distribution.py +25 -0
  19. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/lib/permissions.py +6 -0
  20. mmisp_lib-0.9.0/src/mmisp/lib/standard_roles.py +221 -0
  21. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/compatibility_helpers.py +35 -3
  22. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/fixtures.py +71 -42
  23. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/event_generator.py +2 -0
  24. mmisp_lib-0.9.0/src/mmisp/tests/maps.py +264 -0
  25. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/util/crypto.py +1 -0
  26. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp_lib.egg-info/PKG-INFO +4 -3
  27. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp_lib.egg-info/SOURCES.txt +2 -0
  28. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp_lib.egg-info/requires.txt +1 -1
  29. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/tests/test_commandline_tool.py +12 -0
  30. mmisp_lib-0.8.3/src/mmisp/api_schemas/common.py +0 -15
  31. mmisp_lib-0.8.3/src/mmisp/api_schemas/roles.py +0 -151
  32. mmisp_lib-0.8.3/src/mmisp/commandline_tool/setup.py +0 -101
  33. mmisp_lib-0.8.3/src/mmisp/db/models/event.py +0 -113
  34. mmisp_lib-0.8.3/src/mmisp/lib/distribution.py +0 -8
  35. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/LICENSE +0 -0
  36. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/README.md +0 -0
  37. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/setup.cfg +0 -0
  38. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/__init__.py +0 -0
  39. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/auth_keys.py +0 -0
  40. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/authentication.py +0 -0
  41. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/feeds.py +0 -0
  42. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/galaxies.py +0 -0
  43. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/galaxy_clusters.py +0 -0
  44. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/galaxy_common.py +0 -0
  45. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/jobs.py +0 -0
  46. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/logs.py +0 -0
  47. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/noticelists.py +0 -0
  48. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/object_templates.py +0 -0
  49. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/objects.py +0 -0
  50. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/organisations.py +0 -0
  51. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/py.typed +0 -0
  52. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/responses/__init__.py +0 -0
  53. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/responses/check_graph_response.py +0 -0
  54. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/responses/standard_status_response.py +0 -0
  55. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/server.py +0 -0
  56. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/servers.py +0 -0
  57. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/shadow_attribute.py +0 -0
  58. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/sightings.py +0 -0
  59. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/statistics.py +0 -0
  60. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/tags.py +0 -0
  61. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/taxonomies.py +0 -0
  62. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/user_settings.py +0 -0
  63. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/warninglists.py +0 -0
  64. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/api_schemas/workflows.py +0 -0
  65. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/commandline_tool/__init__.py +0 -0
  66. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/commandline_tool/main.py +0 -0
  67. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/commandline_tool/organisation.py +0 -0
  68. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/commandline_tool/py.typed +0 -0
  69. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/commandline_tool/user.py +0 -0
  70. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/__init__.py +0 -0
  71. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/additional_properties.py +0 -0
  72. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/all_models.py +0 -0
  73. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/config.py +0 -0
  74. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/database.py +0 -0
  75. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/lib.py +0 -0
  76. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/list_json_type.py +0 -0
  77. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/__init__.py +0 -0
  78. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/admin_setting.py +0 -0
  79. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/auth_key.py +0 -0
  80. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/blocklist.py +0 -0
  81. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/correlation.py +0 -0
  82. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/feed.py +0 -0
  83. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/galaxy.py +0 -0
  84. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/galaxy_cluster.py +0 -0
  85. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/identity_provider.py +0 -0
  86. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/log.py +0 -0
  87. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/noticelist.py +0 -0
  88. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/object.py +0 -0
  89. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/post.py +0 -0
  90. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/server.py +0 -0
  91. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/shadow_attribute.py +0 -0
  92. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/sighting.py +0 -0
  93. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/tag.py +0 -0
  94. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/taxonomy.py +0 -0
  95. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/threat_level.py +0 -0
  96. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/user_setting.py +0 -0
  97. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/warninglist.py +0 -0
  98. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/workflow.py +0 -0
  99. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/models/workflow_blueprint.py +0 -0
  100. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/mypy.py +0 -0
  101. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/object_json_type.py +0 -0
  102. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/print_changes.py +0 -0
  103. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/py.typed +0 -0
  104. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/db/uuid_type.py +0 -0
  105. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/lib/__init__.py +0 -0
  106. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/lib/actions.py +0 -0
  107. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/lib/attribute_search_filter.py +0 -0
  108. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/lib/fallbacks.py +0 -0
  109. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/lib/galaxies.py +0 -0
  110. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/lib/galaxy_clusters.py +0 -0
  111. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/lib/logger.py +0 -0
  112. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/lib/py.typed +0 -0
  113. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/lib/serialisation_helper.py +0 -0
  114. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/lib/uuid.py +0 -0
  115. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/plugins/__init__.py +0 -0
  116. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/plugins/enrichment/__init__.py +0 -0
  117. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/plugins/enrichment/data.py +0 -0
  118. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/plugins/enrichment/enrichment_plugin.py +0 -0
  119. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/plugins/exceptions.py +0 -0
  120. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/plugins/models/__init__.py +0 -0
  121. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/plugins/models/attribute.py +0 -0
  122. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/plugins/plugin_info.py +0 -0
  123. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/plugins/plugin_type.py +0 -0
  124. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/plugins/py.typed +0 -0
  125. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/__init__.py +0 -0
  126. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/attribute_generator.py +0 -0
  127. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/feed_generator.py +0 -0
  128. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/attribute_generator.py +0 -0
  129. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/auth_key_generator.py +0 -0
  130. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/correlation_exclusions_generator.py +0 -0
  131. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/correlation_value_generator.py +0 -0
  132. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/default_correlation_generator.py +0 -0
  133. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/galaxy_generator.py +0 -0
  134. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/identity_provider_generator.py +0 -0
  135. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/noticelist_generator.py +0 -0
  136. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/object_generator.py +0 -0
  137. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/organisation_generator.py +0 -0
  138. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/over_correlating_value_generator.py +0 -0
  139. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/post_generator.py +0 -0
  140. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/role_generator.py +0 -0
  141. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/server_generator.py +0 -0
  142. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/shadow_attribute_generator.py +0 -0
  143. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/sharing_group_generator.py +0 -0
  144. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/sighting_generator.py +0 -0
  145. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/tag_generator.py +0 -0
  146. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/taxonomy_generator.py +0 -0
  147. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/user_generator.py +0 -0
  148. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/user_setting_generator.py +0 -0
  149. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/warninglist_generator.py +0 -0
  150. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/model_generators/workflow_generator.py +0 -0
  151. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/object_generator.py +0 -0
  152. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/sighting_generator.py +0 -0
  153. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/tag_generator.py +0 -0
  154. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/tests/generators/taxonomies_generator.py +0 -0
  155. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/util/__init__.py +0 -0
  156. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/util/models.py +0 -0
  157. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/util/partial.py +0 -0
  158. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/util/py.typed +0 -0
  159. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/util/uuid.py +0 -0
  160. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/workflows/__init__.py +0 -0
  161. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/workflows/execution.py +0 -0
  162. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/workflows/fastapi.py +0 -0
  163. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/workflows/graph.py +0 -0
  164. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/workflows/input.py +0 -0
  165. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/workflows/legacy.py +0 -0
  166. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/workflows/misp_core_format.py +0 -0
  167. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/workflows/modules.py +0 -0
  168. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp/workflows/py.typed +0 -0
  169. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp_lib.egg-info/dependency_links.txt +0 -0
  170. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp_lib.egg-info/entry_points.txt +0 -0
  171. {mmisp_lib-0.8.3 → mmisp_lib-0.9.0}/src/mmisp_lib.egg-info/top_level.txt +0 -0
@@ -1,10 +1,10 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: mmisp-lib
3
- Version: 0.8.3
3
+ Version: 0.9.0
4
4
  Requires-Python: >=3.11.0
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE
7
- Requires-Dist: fastapi==0.104.1
7
+ Requires-Dist: fastapi>=0.104.1
8
8
  Requires-Dist: SQLAlchemy[asyncio]~=1.4.46
9
9
  Requires-Dist: pydantic==1.10.13
10
10
  Requires-Dist: uvicorn==0.24.0.post1
@@ -36,6 +36,7 @@ Requires-Dist: icecream; extra == "dev"
36
36
  Requires-Dist: asyncio; extra == "dev"
37
37
  Requires-Dist: twine; extra == "dev"
38
38
  Requires-Dist: build; extra == "dev"
39
+ Dynamic: license-file
39
40
 
40
41
  # Modern MISP - API
41
42
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mmisp-lib"
3
- version = "0.8.3"
3
+ version = "0.9.0"
4
4
  description = ""
5
5
  authors = []
6
6
  readme = "README.md"
@@ -8,7 +8,7 @@ requires-python = ">=3.11.0"
8
8
 
9
9
 
10
10
  dependencies = [
11
- "fastapi==0.104.1",
11
+ "fastapi>=0.104.1",
12
12
  "SQLAlchemy[asyncio]~=1.4.46",
13
13
  "pydantic==1.10.13",
14
14
  "uvicorn==0.24.0.post1",
@@ -1,6 +1,6 @@
1
- from typing import Annotated, Any, Dict, Optional, Type
1
+ from typing import Annotated, Any, Optional, Type
2
2
 
3
- from pydantic import BaseModel, Field, root_validator, validator
3
+ from pydantic import BaseModel, Field, root_validator
4
4
 
5
5
  from mmisp.lib.attributes import (
6
6
  AttributeCategories,
@@ -10,6 +10,7 @@ from mmisp.lib.attributes import (
10
10
  mapper_val_safe_clsname,
11
11
  to_ids,
12
12
  )
13
+ from mmisp.lib.distribution import AttributeDistributionLevels
13
14
 
14
15
 
15
16
  class GetAttributeTag(BaseModel):
@@ -48,7 +49,7 @@ class SearchAttributesAttributesDetails(BaseModel):
48
49
  to_ids: bool
49
50
  uuid: str
50
51
  timestamp: str
51
- distribution: str
52
+ distribution: AttributeDistributionLevels
52
53
  sharing_group_id: int | None = None
53
54
  comment: str | None = None
54
55
  deleted: bool
@@ -155,7 +156,7 @@ class RestoreAttributeResponse(BaseModel):
155
156
  to_ids: bool
156
157
  uuid: str
157
158
  timestamp: str
158
- distribution: str
159
+ distribution: AttributeDistributionLevels
159
160
  sharing_group_id: int
160
161
  comment: str
161
162
  deleted: bool
@@ -200,7 +201,7 @@ class GetAttributeAttributes(BaseModel):
200
201
  to_ids: bool
201
202
  uuid: str
202
203
  timestamp: str
203
- distribution: str
204
+ distribution: AttributeDistributionLevels
204
205
  sharing_group_id: int
205
206
  comment: str | None = None
206
207
  deleted: bool = False
@@ -231,7 +232,7 @@ class GetAllAttributesResponse(BaseModel):
231
232
  to_ids: bool | None = None
232
233
  uuid: str | None = None
233
234
  timestamp: str | None = None
234
- distribution: str | None = None
235
+ distribution: AttributeDistributionLevels | None = None
235
236
  sharing_group_id: int | None = None
236
237
  comment: str | None = None
237
238
  deleted: bool | None = None
@@ -240,18 +241,18 @@ class GetAllAttributesResponse(BaseModel):
240
241
  last_seen: str | None = None
241
242
  value: str | None = None
242
243
 
243
- @validator("sharing_group_id", always=True, allow_reuse=True)
244
- @classmethod
245
- def check_sharing_group_id(
246
- cls: Type["GetAllAttributesResponse"], value: Any, values: Dict[str, Any]
247
- ) -> Optional[int]: # noqa: ANN101
248
- """
249
- If distribution equals 4, sharing_group_id will be shown.
250
- """
251
- distribution = values.get("distribution", None)
252
- if distribution == "4" and value is not None:
253
- return value
254
- return None
244
+ # @validator("sharing_group_id", always=True, allow_reuse=True)
245
+ # @classmethod
246
+ # def check_sharing_group_id(
247
+ # cls: Type["GetAllAttributesResponse"], value: Any, values: Dict[str, Any]
248
+ # ) -> Optional[int]: # noqa: ANN101
249
+ # """
250
+ # If distribution equals 4, sharing_group_id will be shown.
251
+ # """
252
+ # distribution = values.get("distribution", None)
253
+ # if distribution == "4" and value is not None:
254
+ # return value
255
+ # return None
255
256
 
256
257
  class Config:
257
258
  orm_mode = True
@@ -281,7 +282,7 @@ class EditAttributeAttributes(BaseModel):
281
282
  to_ids: bool
282
283
  uuid: str
283
284
  timestamp: str
284
- distribution: str
285
+ distribution: AttributeDistributionLevels
285
286
  sharing_group_id: int
286
287
  comment: str | None = None
287
288
  deleted: bool
@@ -309,7 +310,7 @@ class EditAttributeBody(BaseModel):
309
310
  to_ids: bool | None = None
310
311
  uuid: str | None = None
311
312
  timestamp: str | None = None
312
- distribution: str | None = None
313
+ distribution: AttributeDistributionLevels | None = None
313
314
  sharing_group_id: int | None = None
314
315
  comment: str | None = None
315
316
  deleted: bool | None = None
@@ -327,15 +328,7 @@ class DeleteSelectedAttributeResponse(BaseModel):
327
328
  name: str
328
329
  message: str
329
330
  url: str
330
- id: int
331
-
332
- class Config:
333
- orm_mode = True
334
-
335
-
336
- class DeleteSelectedAttributeBody(BaseModel):
337
- id: str # ids can be space separated, id = "all" deletes all attributes in the event
338
- allow_hard_delete: bool | None = None
331
+ id: str
339
332
 
340
333
  class Config:
341
334
  orm_mode = True
@@ -371,7 +364,7 @@ class AddAttributeAttributes(BaseModel):
371
364
  to_ids: bool
372
365
  uuid: str
373
366
  timestamp: str
374
- distribution: str
367
+ distribution: AttributeDistributionLevels
375
368
  sharing_group_id: int
376
369
  comment: str | None = None
377
370
  deleted: bool
@@ -400,7 +393,7 @@ class AddAttributeBody(BaseModel):
400
393
  to_ids: bool | None = None
401
394
  uuid: str | None = None
402
395
  timestamp: str | None = None
403
- distribution: str | None = None
396
+ distribution: AttributeDistributionLevels | None = None
404
397
  sharing_group_id: int | None = None
405
398
  comment: str | None = None
406
399
  deleted: bool | None = None
@@ -0,0 +1,53 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class TagAttributesResponse(BaseModel):
7
+ id: int
8
+ name: str
9
+ colour: str
10
+ exportable: bool
11
+ org_id: int | None = None
12
+ user_id: int | None = None
13
+ hide_tag: bool | None = None
14
+ numerical_value: str | None = None
15
+ is_galaxy: bool | None = None
16
+ is_custom_galaxy: bool | None = None
17
+ local_only: bool | None = None
18
+
19
+
20
+ class User(BaseModel):
21
+ id: int
22
+ org_id: int
23
+ email: str
24
+ autoalert: bool
25
+ invited_by: int
26
+ gpgkey: str | None = None
27
+ certif_public: str | None = None
28
+ termsaccepted: bool
29
+ role_id: int
30
+ change_pw: bool
31
+ contactalert: bool
32
+ disabled: bool
33
+ expiration: datetime | int | None = None
34
+ current_login: int
35
+ """time in seconds"""
36
+ last_login: int
37
+ """time in seconds"""
38
+ force_logout: bool
39
+ date_created: int
40
+ """time in seconds"""
41
+ date_modified: int
42
+ """time in seconds"""
43
+ external_auth_required: bool
44
+ external_auth_key: str | None = None
45
+ last_api_access: int
46
+ """time in seconds"""
47
+ notification_daily: bool
48
+ notification_weekly: bool
49
+ notification_monthly: bool
50
+ totp: str | None = None
51
+ hotp_counter: int | None = None
52
+ last_pw_change: int | None = None
53
+ """time in seconds"""
@@ -1,9 +1,10 @@
1
+ import uuid
1
2
  from datetime import datetime
2
- from typing import Any, Type
3
3
 
4
- from pydantic import BaseModel, PositiveInt, conint, validator
4
+ from pydantic import BaseModel, Field, PositiveInt, conint
5
5
 
6
6
  from mmisp.api_schemas.organisations import Organisation
7
+ from mmisp.api_schemas.sharing_groups import EventSharingGroupResponse, MinimalSharingGroup
7
8
  from mmisp.lib.distribution import DistributionLevels
8
9
 
9
10
 
@@ -224,6 +225,7 @@ class AddEditGetEventAttribute(BaseModel):
224
225
  first_seen: str | None = None
225
226
  last_seen: str | None = None
226
227
  Galaxy: list[AddEditGetEventGalaxy] = []
228
+ sharing_group: EventSharingGroupResponse | None = Field(alias="SharingGroup", default=None)
227
229
  ShadowAttribute: list[str] = []
228
230
  Tag: list[AddEditGetEventTag] = []
229
231
 
@@ -314,7 +316,7 @@ class AddEditGetEventDetails(BaseModel):
314
316
  disable_correlation: bool
315
317
  extends_uuid: str
316
318
  protected: bool | None = None
317
- event_creator_email: str
319
+ event_creator_email: str | None = None
318
320
  Org: AddEditGetEventOrg
319
321
  Orgc: AddEditGetEventOrg
320
322
  Attribute: list[AddEditGetEventAttribute] = []
@@ -325,7 +327,7 @@ class AddEditGetEventDetails(BaseModel):
325
327
  EventReport: list[AddEditGetEventEventReport] = []
326
328
  CryptographicKey: list[str] = []
327
329
  Tag: list[AddEditGetEventTag] = []
328
-
330
+ sharing_group: EventSharingGroupResponse | None = Field(alias="SharingGroup", default=None)
329
331
  # @validator("uuid", "extends_uuid", pre=True)
330
332
  # @classmethod
331
333
  # def uuid_empty_str(cls: Type["AddEditGetEventDetails"], value: Any) -> Any: # noqa: ANN102
@@ -343,12 +345,13 @@ class AddEditGetEventDetails(BaseModel):
343
345
  #
344
346
  # return value
345
347
 
346
- @validator("sharing_group_id", pre=True)
347
- @classmethod
348
- def zero_sharing_group_id_to_none(cls: Type["AddEditGetEventDetails"], value: Any) -> Any: # noqa: ANN102
349
- if value is not None and value == 0:
350
- return None
351
- return value
348
+
349
+ # @validator("sharing_group_id", pre=True)
350
+ # @classmethod
351
+ # def zero_sharing_group_id_to_none(cls: Type["AddEditGetEventDetails"], value: Any) -> Any: # noqa: ANN102
352
+ # if value is not None and value == 0:
353
+ # return "0"
354
+ # return value
352
355
 
353
356
 
354
357
  class AddEditGetEventResponse(BaseModel):
@@ -370,7 +373,7 @@ class UnpublishEventResponse(BaseModel):
370
373
  name: str
371
374
  message: str
372
375
  url: str
373
- id: int | None = None
376
+ id: uuid.UUID | int | None = None
374
377
 
375
378
  class Config:
376
379
  orm_mode = True
@@ -431,21 +434,21 @@ class PublishEventResponse(BaseModel):
431
434
  name: str
432
435
  message: str
433
436
  url: str
434
- id: int | None = None
437
+ id: uuid.UUID | int | None = None
435
438
 
436
439
  class Config:
437
440
  orm_mode = True
438
441
 
439
442
 
440
443
  class GetAllEventsEventTagTag(BaseModel):
441
- id: int
444
+ id: uuid.UUID | int
442
445
  name: str
443
446
  colour: str
444
447
  is_galaxy: bool
445
448
 
446
449
 
447
450
  class IndexEventsEventTag(BaseModel):
448
- id: int
451
+ id: uuid.UUID | int
449
452
  event_id: int
450
453
  tag_id: int
451
454
  local: bool
@@ -512,15 +515,15 @@ class IndexEventsBody(BaseModel):
512
515
 
513
516
 
514
517
  class ObjectEventResponse(BaseModel):
515
- id: int
518
+ id: uuid.UUID | int
516
519
  info: str
517
520
  org_id: int | None = None
518
521
  orgc_id: int | None = None
519
522
 
520
523
 
521
524
  class GetAllEventsEventTag(BaseModel):
522
- id: int
523
- event_id: int
525
+ id: uuid.UUID | int
526
+ event_id: uuid.UUID | int
524
527
  tag_id: int
525
528
  local: bool
526
529
  relationship_type: bool | str | None = None
@@ -548,7 +551,8 @@ class GetAllEventsResponse(BaseModel):
548
551
  disable_correlation: bool
549
552
  extends_uuid: str
550
553
  event_creator_email: str | None = None # omitted
551
- protected: str | None = None
554
+ protected: bool | None = None
555
+ SharingGroup: MinimalSharingGroup | None = None
552
556
  Org: GetAllEventsOrg
553
557
  Orgc: GetAllEventsOrg
554
558
  GalaxyCluster: list[GetAllEventsGalaxyCluster]
@@ -578,7 +582,7 @@ class EditEventBody(BaseModel):
578
582
  disable_correlation: bool | None = None
579
583
  extends_uuid: str | None = None
580
584
  event_creator_email: str | None = None
581
- protected: str | None = None
585
+ protected: bool | None = None
582
586
  cryptographic_key: str | None = None
583
587
 
584
588
  class Config:
@@ -591,7 +595,7 @@ class DeleteEventResponse(BaseModel):
591
595
  name: str
592
596
  message: str
593
597
  url: str
594
- id: int
598
+ id: uuid.UUID | int
595
599
  errors: str | None = None
596
600
 
597
601
  class Config:
@@ -627,10 +631,7 @@ class AddEventBody(BaseModel):
627
631
  sighting_timestamp: str | None = None
628
632
  disable_correlation: bool | None = None
629
633
  extends_uuid: str | None = None
630
- protected: str | None = None
631
-
632
- class Config:
633
- orm_mode = True
634
+ protected: bool | None = None
634
635
 
635
636
 
636
637
  class AddEventTag(BaseModel):
@@ -0,0 +1,205 @@
1
+ from datetime import datetime
2
+ from typing import Any, Self
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from mmisp.lib.permissions import Permission
7
+
8
+
9
+ class HasPermission(BaseModel):
10
+ perm_add: bool | None = None
11
+ perm_modify: bool | None = None
12
+ perm_modify_org: bool | None = None
13
+ perm_publish: bool | None = None
14
+ perm_delegate: bool | None = None
15
+ perm_sync: bool | None = None
16
+ perm_admin: bool | None = None
17
+ perm_audit: bool | None = None
18
+ perm_auth: bool | None = None
19
+ perm_site_admin: bool | None = None
20
+ perm_regexp_access: bool | None = None
21
+ perm_tagger: bool | None = None
22
+ perm_template: bool | None = None
23
+ perm_sharing_group: bool | None = None
24
+ perm_tag_editor: bool | None = None
25
+ perm_sighting: bool | None = None
26
+ perm_object_template: bool | None = None
27
+ perm_publish_zmq: bool | None = None
28
+ perm_publish_kafka: bool | None = None
29
+ perm_decaying: bool | None = None
30
+ perm_galaxy_editor: bool | None = None
31
+ perm_warninglist: bool | None = None
32
+ perm_view_feed_correlations: bool | None = None
33
+ perm_skip_otp: bool | None = None
34
+ perm_server_sign: bool | None = None
35
+ perm_analyst_data: bool | None = None
36
+ perm_sync_authoritative: bool | None = None
37
+ perm_sync_internal: bool | None = None
38
+
39
+
40
+ class Role(HasPermission):
41
+ id: int
42
+ name: str
43
+ created: datetime | str | None = None
44
+ modified: datetime | str | None = None
45
+ default_role: bool
46
+ memory_limit: str | None
47
+ max_execution_time: str | None
48
+ restricted_to_site_admin: bool
49
+ enforce_rate_limit: bool
50
+ rate_limit_count: str # number as string
51
+ permission: str | None # number as string
52
+ permission_description: str | None
53
+
54
+
55
+ class RoleUsersResponse(HasPermission):
56
+ id: int
57
+ name: str
58
+ created: datetime | str | None = None
59
+ modified: datetime | str | None = None
60
+ default_role: bool | None = None
61
+ memory_limit: str | None = None
62
+ max_execution_time: str | None = None
63
+ restricted_to_site_admin: bool | None = None
64
+ enforce_rate_limit: bool | None = None
65
+ rate_limit_count: str | None = None # number as string
66
+
67
+
68
+ class RoleAttributeResponse(HasPermission):
69
+ id: int
70
+ name: str
71
+ created: datetime | str | None = None
72
+ modified: datetime | str | None = None
73
+ default_role: bool
74
+ memory_limit: str | None
75
+ max_execution_time: str | None
76
+ restricted_to_site_admin: bool
77
+ enforce_rate_limit: bool
78
+ rate_limit_count: int
79
+ permission: int | None = None
80
+ permission_description: str | None = None
81
+ default: bool | None = False
82
+
83
+
84
+ class GetRolesResponse(BaseModel):
85
+ Role: RoleAttributeResponse
86
+
87
+ class Config:
88
+ json_encoders = {datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")}
89
+
90
+
91
+ class GetRoleResponse(BaseModel):
92
+ Role: RoleAttributeResponse | None = None
93
+
94
+ class Config:
95
+ json_encoders = {datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")}
96
+
97
+
98
+ class AddRoleBody(HasPermission):
99
+ name: str
100
+ default_role: bool
101
+ memory_limit: str | None = None
102
+ max_execution_time: str | None = None
103
+ restricted_to_site_admin: bool
104
+ enforce_rate_limit: bool
105
+ rate_limit_count: int
106
+
107
+
108
+ class AddRoleResponse(BaseModel):
109
+ Role: RoleAttributeResponse
110
+ created: bool
111
+ message: str
112
+
113
+ class Config:
114
+ json_encoders = {datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")}
115
+
116
+
117
+ class DeleteRoleResponse(BaseModel):
118
+ Role: RoleAttributeResponse | None = None
119
+ saved: bool
120
+ success: bool | None = None
121
+ name: str
122
+ message: str
123
+ url: str
124
+ id: int
125
+ errors: str | None = None
126
+
127
+ class Config:
128
+ json_encoders = {datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")}
129
+
130
+
131
+ class EditRoleBody(HasPermission):
132
+ name: str | None = None
133
+ default_role: bool | None = None
134
+ memory_limit: str | None = None
135
+ max_execution_time: str | None = None
136
+ restricted_to_site_admin: bool | None = None
137
+ enforce_rate_limit: bool | None = None
138
+
139
+
140
+ class EditRoleResponse(BaseModel):
141
+ Role: RoleAttributeResponse
142
+
143
+ class Config:
144
+ json_encoders = {datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")}
145
+
146
+ def dict(self: Self, **kwargs) -> dict[str, Any]:
147
+ data = super().dict(**kwargs)
148
+ if "Role" in data:
149
+ data["Role"].pop("default", None)
150
+ return data
151
+
152
+
153
+ class ReinstateRoleResponse(BaseModel):
154
+ Role: RoleAttributeResponse
155
+ success: bool
156
+ message: str
157
+ url: str
158
+ id: int
159
+
160
+ class Config:
161
+ json_encoders = {datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")}
162
+
163
+
164
+ class FilterRoleBody(BaseModel):
165
+ # filter can be expanded by adding more criteria to filter for
166
+ permissions: list[Permission] | None = None
167
+
168
+
169
+ class FilterRoleResponse(BaseModel):
170
+ Role: RoleAttributeResponse
171
+
172
+ class Config:
173
+ json_encoders = {datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")}
174
+
175
+
176
+ class EditUserRoleBody(BaseModel):
177
+ role_id: int
178
+
179
+
180
+ class EditUserRoleResponse(BaseModel):
181
+ saved: bool
182
+ success: bool | None = None
183
+ name: str
184
+ message: str
185
+ url: str
186
+ id: int
187
+ Role: str | None = None
188
+
189
+
190
+ class GetUserRoleResponse(BaseModel):
191
+ user_id: int
192
+
193
+
194
+ class DefaultRoleResponse(BaseModel):
195
+ Role: RoleAttributeResponse | None = None
196
+ saved: bool
197
+ success: bool | None = None
198
+ name: str
199
+ message: str
200
+ url: str
201
+ id: int
202
+ errors: str | None = None
203
+
204
+ class Config:
205
+ json_encoders = {datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")}
@@ -26,6 +26,12 @@ class SharingGroup(BaseModel):
26
26
  # org_count: int = 0
27
27
 
28
28
 
29
+ class MinimalSharingGroup(BaseModel):
30
+ id: int
31
+ name: str
32
+ uuid: str
33
+
34
+
29
35
  class ShortSharingGroup(BaseModel):
30
36
  id: int
31
37
  name: str
@@ -59,6 +65,16 @@ class SharingGroupOrg(BaseModel):
59
65
  extend: bool
60
66
 
61
67
 
68
+ class SharingGroupOrgWithOrganisation(SharingGroupOrg):
69
+ Organisation: ShortOrganisation
70
+
71
+
72
+ class EventSharingGroupResponse(SharingGroup):
73
+ Organisation: ShortOrganisation
74
+ SharingGroupOrg: list[SharingGroupOrgWithOrganisation]
75
+ SharingGroupServer: list
76
+
77
+
62
78
  class GetAllSharingGroupsResponseResponseItemSharingGroup(BaseModel):
63
79
  id: int
64
80
  uuid: str
@@ -89,10 +105,13 @@ class ViewUpdateSharingGroupLegacyResponseSharingGroupServerItem(BaseModel):
89
105
  Server: ViewUpdateSharingGroupLegacyResponseServerInfo
90
106
 
91
107
 
92
- class ViewUpdateSharingGroupLegacyResponseOrganisationInfo(BaseModel):
108
+ class SharingGroupOrgOrganisationIndexInfo(BaseModel):
93
109
  id: int
94
110
  uuid: str
95
111
  name: str
112
+
113
+
114
+ class ViewUpdateSharingGroupLegacyResponseOrganisationInfo(SharingGroupOrgOrganisationIndexInfo):
96
115
  local: bool
97
116
 
98
117
 
@@ -104,15 +123,36 @@ class ViewUpdateSharingGroupLegacyResponseSharingGroupOrgItem(BaseModel):
104
123
  Organisation: ViewUpdateSharingGroupLegacyResponseOrganisationInfo
105
124
 
106
125
 
126
+ class SharingGroupOrgIndexItem(BaseModel):
127
+ id: int
128
+ sharing_group_id: int
129
+ org_id: int
130
+ extend: bool
131
+ Organisation: SharingGroupOrgOrganisationIndexInfo
132
+
133
+
107
134
  class SharingGroupResponse(BaseModel):
135
+ editable: bool | None = None
136
+ deletable: bool | None = None
137
+
108
138
  SharingGroup: ShortSharingGroup
109
139
  Organisation: ShortOrganisation
110
140
  SharingGroupOrg: list[ViewUpdateSharingGroupLegacyResponseSharingGroupOrgItem]
111
141
  SharingGroupServer: list[ViewUpdateSharingGroupLegacyResponseSharingGroupServerItem]
112
142
 
143
+ class Config:
144
+ json_encoders = {datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")}
145
+
146
+
147
+ class SharingGroupIndexResponse(BaseModel):
113
148
  editable: bool | None = None
114
149
  deletable: bool | None = None
115
150
 
151
+ SharingGroup: ShortSharingGroup
152
+ Organisation: ShortOrganisation
153
+ SharingGroupOrg: list[SharingGroupOrgIndexItem]
154
+ SharingGroupServer: list[ViewUpdateSharingGroupLegacyResponseSharingGroupServerItem]
155
+
116
156
  class Config:
117
157
  json_encoders = {datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")}
118
158
 
@@ -132,7 +172,7 @@ class ViewUpdateSharingGroupLegacyResponse(SharingGroupResponse):
132
172
 
133
173
 
134
174
  class GetSharingGroupsIndex(BaseModel):
135
- response: list[SharingGroupResponse]
175
+ response: list[SharingGroupIndexResponse]
136
176
 
137
177
  class Config:
138
178
  json_encoders = {datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")}