openedx-learning 0.28.0__tar.gz → 0.29.1__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 (178) hide show
  1. {openedx_learning-0.28.0/openedx_learning.egg-info → openedx_learning-0.29.1}/PKG-INFO +13 -4
  2. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/README.rst +9 -0
  3. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/__init__.py +1 -1
  4. openedx_learning-0.29.1/openedx_learning/apps/authoring/backup_restore/api.py +30 -0
  5. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/backup_restore/management/commands/lp_dump.py +22 -4
  6. openedx_learning-0.29.1/openedx_learning/apps/authoring/backup_restore/management/commands/lp_load.py +57 -0
  7. openedx_learning-0.29.1/openedx_learning/apps/authoring/backup_restore/serializers.py +168 -0
  8. openedx_learning-0.29.1/openedx_learning/apps/authoring/backup_restore/toml.py +254 -0
  9. openedx_learning-0.29.1/openedx_learning/apps/authoring/backup_restore/zipper.py +1062 -0
  10. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/components/api.py +55 -0
  11. openedx_learning-0.29.1/openedx_learning/apps/authoring/components/migrations/0004_remove_componentversioncontent_uuid.py +17 -0
  12. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/components/models.py +1 -3
  13. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/api.py +36 -4
  14. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/sections/api.py +17 -0
  15. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/subsections/api.py +17 -0
  16. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/units/api.py +17 -0
  17. {openedx_learning-0.28.0 → openedx_learning-0.29.1/openedx_learning.egg-info}/PKG-INFO +13 -4
  18. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning.egg-info/SOURCES.txt +3 -0
  19. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/models/base.py +7 -5
  20. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/models/utils.py +1 -1
  21. openedx_learning-0.28.0/openedx_learning/apps/authoring/backup_restore/api.py +0 -15
  22. openedx_learning-0.28.0/openedx_learning/apps/authoring/backup_restore/toml.py +0 -76
  23. openedx_learning-0.28.0/openedx_learning/apps/authoring/backup_restore/zipper.py +0 -242
  24. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/CHANGELOG.rst +0 -0
  25. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/LICENSE.txt +0 -0
  26. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/MANIFEST.in +0 -0
  27. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/api/__init__.py +0 -0
  28. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/api/authoring.py +0 -0
  29. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/api/authoring_models.py +0 -0
  30. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/__init__.py +0 -0
  31. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/__init__.py +0 -0
  32. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/backup_restore/__init__.py +0 -0
  33. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/backup_restore/admin.py +0 -0
  34. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/backup_restore/apps.py +0 -0
  35. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/backup_restore/management/__init__.py +0 -0
  36. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/backup_restore/management/commands/__init__.py +0 -0
  37. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/backup_restore/migrations/__init__.py +0 -0
  38. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/backup_restore/models.py +0 -0
  39. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/collections/__init__.py +0 -0
  40. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/collections/admin.py +0 -0
  41. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/collections/api.py +0 -0
  42. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/collections/apps.py +0 -0
  43. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/collections/migrations/0001_initial.py +0 -0
  44. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py +0 -0
  45. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/collections/migrations/0003_collection_entities.py +0 -0
  46. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py +0 -0
  47. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py +0 -0
  48. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/collections/migrations/__init__.py +0 -0
  49. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/collections/models.py +0 -0
  50. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/components/__init__.py +0 -0
  51. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/components/admin.py +0 -0
  52. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/components/apps.py +0 -0
  53. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/components/management/__init__.py +0 -0
  54. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/components/management/commands/__init__.py +0 -0
  55. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py +0 -0
  56. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/components/migrations/0001_initial.py +0 -0
  57. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py +0 -0
  58. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py +0 -0
  59. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/components/migrations/__init__.py +0 -0
  60. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/contents/__init__.py +0 -0
  61. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/contents/admin.py +0 -0
  62. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/contents/api.py +0 -0
  63. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/contents/apps.py +0 -0
  64. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/contents/migrations/0001_initial.py +0 -0
  65. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/contents/migrations/__init__.py +0 -0
  66. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/contents/models.py +0 -0
  67. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/__init__.py +0 -0
  68. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/admin.py +0 -0
  69. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/apps.py +0 -0
  70. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/contextmanagers.py +0 -0
  71. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/migrations/0001_initial.py +0 -0
  72. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py +0 -0
  73. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/migrations/0003_containers.py +0 -0
  74. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py +0 -0
  75. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/migrations/0005_alter_entitylistrow_options.py +0 -0
  76. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/migrations/0006_draftchangelog.py +0 -0
  77. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/migrations/0007_bootstrap_draftchangelog.py +0 -0
  78. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/migrations/0008_alter_draftchangelogrecord_options_and_more.py +0 -0
  79. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/migrations/__init__.py +0 -0
  80. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/models/__init__.py +0 -0
  81. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/models/container.py +0 -0
  82. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/models/draft_log.py +0 -0
  83. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/models/entity_list.py +0 -0
  84. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/models/learning_package.py +0 -0
  85. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/models/publish_log.py +0 -0
  86. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/publishing/models/publishable_entity.py +0 -0
  87. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/sections/__init__.py +0 -0
  88. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/sections/admin.py +0 -0
  89. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/sections/apps.py +0 -0
  90. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/sections/migrations/0001_initial.py +0 -0
  91. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/sections/migrations/__init__.py +0 -0
  92. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/sections/models.py +0 -0
  93. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/subsections/__init__.py +0 -0
  94. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/subsections/admin.py +0 -0
  95. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/subsections/apps.py +0 -0
  96. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/subsections/migrations/0001_initial.py +0 -0
  97. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/subsections/migrations/__init__.py +0 -0
  98. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/subsections/models.py +0 -0
  99. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/units/__init__.py +0 -0
  100. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/units/admin.py +0 -0
  101. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/units/apps.py +0 -0
  102. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/units/migrations/0001_initial.py +0 -0
  103. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/units/migrations/__init__.py +0 -0
  104. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/apps/authoring/units/models.py +0 -0
  105. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/contrib/__init__.py +0 -0
  106. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/contrib/media_server/__init__.py +0 -0
  107. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/contrib/media_server/apps.py +0 -0
  108. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/contrib/media_server/urls.py +0 -0
  109. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/contrib/media_server/views.py +0 -0
  110. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/lib/__init__.py +0 -0
  111. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/lib/admin_utils.py +0 -0
  112. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/lib/cache.py +0 -0
  113. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/lib/collations.py +0 -0
  114. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/lib/fields.py +0 -0
  115. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/lib/managers.py +0 -0
  116. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/lib/test_utils.py +0 -0
  117. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/lib/validators.py +0 -0
  118. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning/py.typed +0 -0
  119. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning.egg-info/dependency_links.txt +0 -0
  120. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning.egg-info/not-zip-safe +0 -0
  121. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning.egg-info/requires.txt +3 -3
  122. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_learning.egg-info/top_level.txt +0 -0
  123. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/__init__.py +0 -0
  124. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/__init__.py +0 -0
  125. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/__init__.py +0 -0
  126. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/admin.py +0 -0
  127. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/api.py +0 -0
  128. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/apps.py +0 -0
  129. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/data.py +0 -0
  130. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/import_export/__init__.py +0 -0
  131. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/import_export/actions.py +0 -0
  132. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/import_export/api.py +0 -0
  133. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/import_export/exceptions.py +0 -0
  134. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/import_export/import_plan.py +0 -0
  135. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/import_export/parsers.py +0 -0
  136. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/import_export/tasks.py +0 -0
  137. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/import_export/template.csv +0 -0
  138. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/import_export/template.json +0 -0
  139. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0001_initial.py +0 -0
  140. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0001_squashed.py +0 -0
  141. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0002_auto_20230718_2026.py +0 -0
  142. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0003_auto_20230721_1238.py +0 -0
  143. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0004_auto_20230723_2001.py +0 -0
  144. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0005_language_taxonomy.py +0 -0
  145. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0006_alter_objecttag_unique_together.py +0 -0
  146. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0006_auto_20230802_1631.py +0 -0
  147. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0007_tag_import_task_log_null_fix.py +0 -0
  148. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0008_taxonomy_description_not_null.py +0 -0
  149. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0009_alter_objecttag_object_id.py +0 -0
  150. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0010_cleanups.py +0 -0
  151. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0011_remove_required.py +0 -0
  152. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0012_language_taxonomy.py +0 -0
  153. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0013_tag_parent_blank.py +0 -0
  154. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0014_minor_fixes.py +0 -0
  155. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py +0 -0
  156. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0016_object_tag_export_id.py +0 -0
  157. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0017_alter_tagimporttask_status.py +0 -0
  158. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/0018_objecttag_is_copied.py +0 -0
  159. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/migrations/__init__.py +0 -0
  160. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/models/__init__.py +0 -0
  161. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/models/import_export.py +0 -0
  162. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/models/system_defined.py +0 -0
  163. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/rest_api/__init__.py +0 -0
  164. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/rest_api/paginators.py +0 -0
  165. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/rest_api/urls.py +0 -0
  166. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/rest_api/utils.py +0 -0
  167. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/rest_api/v1/__init__.py +0 -0
  168. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/rest_api/v1/permissions.py +0 -0
  169. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/rest_api/v1/serializers.py +0 -0
  170. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/rest_api/v1/urls.py +0 -0
  171. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/rest_api/v1/views.py +0 -0
  172. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/rest_api/v1/views_import.py +0 -0
  173. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/rules.py +0 -0
  174. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/core/tagging/urls.py +0 -0
  175. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/openedx_tagging/py.typed +0 -0
  176. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/requirements/base.in +0 -0
  177. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/setup.cfg +0 -0
  178. {openedx_learning-0.28.0 → openedx_learning-0.29.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openedx-learning
3
- Version: 0.28.0
3
+ Version: 0.29.1
4
4
  Summary: Open edX Learning Core and Tagging.
5
5
  Home-page: https://github.com/openedx/openedx-learning
6
6
  Author: David Ormsbee
@@ -19,13 +19,13 @@ Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Requires-Python: >=3.11
21
21
  License-File: LICENSE.txt
22
- Requires-Dist: celery
23
22
  Requires-Dist: rules<4.0
24
- Requires-Dist: Django
25
23
  Requires-Dist: edx-drf-extensions
24
+ Requires-Dist: Django
26
25
  Requires-Dist: tomlkit
27
- Requires-Dist: attrs
28
26
  Requires-Dist: djangorestframework<4.0
27
+ Requires-Dist: attrs
28
+ Requires-Dist: celery
29
29
  Dynamic: author
30
30
  Dynamic: author-email
31
31
  Dynamic: classifier
@@ -148,6 +148,15 @@ Every time you develop something in this repo
148
148
 
149
149
  # Open a PR and ask for review.
150
150
 
151
+ Configuring Visual Studio Code
152
+ ------------------------------
153
+
154
+ If you are using VS Code as your editor, you can enable the Testing bar by copying from the example configuration provided in the ``.vscode`` directory::
155
+
156
+ cd .vscode/
157
+ cp launch.json.example launch.json
158
+ cp settings.json.example settings.json
159
+
151
160
  License
152
161
  -------
153
162
 
@@ -108,6 +108,15 @@ Every time you develop something in this repo
108
108
 
109
109
  # Open a PR and ask for review.
110
110
 
111
+ Configuring Visual Studio Code
112
+ ------------------------------
113
+
114
+ If you are using VS Code as your editor, you can enable the Testing bar by copying from the example configuration provided in the ``.vscode`` directory::
115
+
116
+ cd .vscode/
117
+ cp launch.json.example launch.json
118
+ cp settings.json.example settings.json
119
+
111
120
  License
112
121
  -------
113
122
 
@@ -2,4 +2,4 @@
2
2
  Open edX Learning ("Learning Core").
3
3
  """
4
4
 
5
- __version__ = "0.28.0"
5
+ __version__ = "0.29.1"
@@ -0,0 +1,30 @@
1
+ """
2
+ Backup Restore API
3
+ """
4
+ import zipfile
5
+
6
+ from django.contrib.auth.models import User as UserType # pylint: disable=imported-auth-user
7
+
8
+ from openedx_learning.apps.authoring.backup_restore.zipper import LearningPackageUnzipper, LearningPackageZipper
9
+ from openedx_learning.apps.authoring.publishing.api import get_learning_package_by_key
10
+
11
+
12
+ def create_zip_file(lp_key: str, path: str, user: UserType | None = None) -> None:
13
+ """
14
+ Creates a dump zip file for the given learning package key at the given path.
15
+ The zip file contains a TOML representation of the learning package and its contents.
16
+
17
+ Can throw a NotFoundError at get_learning_package_by_key
18
+ """
19
+ learning_package = get_learning_package_by_key(lp_key)
20
+ LearningPackageZipper(learning_package, user).create_zip(path)
21
+
22
+
23
+ def load_learning_package(path: str, key: str | None = None, user: UserType | None = None) -> dict:
24
+ """
25
+ Loads a learning package from a zip file at the given path.
26
+ Restores the learning package and its contents to the database.
27
+ Returns a dictionary with the status of the operation and any errors encountered.
28
+ """
29
+ with zipfile.ZipFile(path, "r") as zipf:
30
+ return LearningPackageUnzipper(zipf, key, user).load()
@@ -1,8 +1,10 @@
1
1
  """
2
- Django management commands to handle backup and restore learning packages (WIP)
2
+ Django management commands to handle backup learning packages (WIP)
3
3
  """
4
4
  import logging
5
+ import time
5
6
 
7
+ from django.contrib.auth import get_user_model
6
8
  from django.core.management import CommandError
7
9
  from django.core.management.base import BaseCommand
8
10
 
@@ -12,6 +14,9 @@ from openedx_learning.apps.authoring.publishing.api import LearningPackage
12
14
  logger = logging.getLogger(__name__)
13
15
 
14
16
 
17
+ User = get_user_model()
18
+
19
+
15
20
  class Command(BaseCommand):
16
21
  """
17
22
  Django management command to export a learning package to a zip file.
@@ -21,15 +26,28 @@ class Command(BaseCommand):
21
26
  def add_arguments(self, parser):
22
27
  parser.add_argument('lp_key', type=str, help='The key of the LearningPackage to dump')
23
28
  parser.add_argument('file_name', type=str, help='The name of the output zip file')
29
+ parser.add_argument(
30
+ '--username',
31
+ type=str,
32
+ help='The username of the user performing the backup operation.',
33
+ default=None
34
+ )
24
35
 
25
36
  def handle(self, *args, **options):
26
37
  lp_key = options['lp_key']
27
38
  file_name = options['file_name']
28
- if not file_name.endswith(".zip"):
39
+ username = options['username']
40
+ if not file_name.lower().endswith(".zip"):
29
41
  raise CommandError("Output file name must end with .zip")
30
42
  try:
31
- create_zip_file(lp_key, file_name)
32
- message = f'{lp_key} written to {file_name}'
43
+ # Get the user performing the operation
44
+ user = None
45
+ if username:
46
+ user = User.objects.get(username=username)
47
+ start_time = time.time()
48
+ create_zip_file(lp_key, file_name, user=user)
49
+ elapsed = time.time() - start_time
50
+ message = f'{lp_key} written to {file_name} (create_zip_file: {elapsed:.2f} seconds)'
33
51
  self.stdout.write(self.style.SUCCESS(message))
34
52
  except LearningPackage.DoesNotExist as exc:
35
53
  message = f"Learning package with key {lp_key} not found"
@@ -0,0 +1,57 @@
1
+ """
2
+ Django management commands to handle restore learning packages (WIP)
3
+ """
4
+ import logging
5
+ import time
6
+
7
+ from django.contrib.auth import get_user_model
8
+ from django.core.management import CommandError
9
+ from django.core.management.base import BaseCommand
10
+
11
+ from openedx_learning.apps.authoring.backup_restore.api import load_learning_package
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ User = get_user_model()
16
+
17
+
18
+ class Command(BaseCommand):
19
+ """
20
+ Django management command to load a learning package from a zip file.
21
+ """
22
+ help = 'Load a learning package from a zip file.'
23
+
24
+ def add_arguments(self, parser):
25
+ parser.add_argument('file_name', type=str, help='The path of the input zip file to load.')
26
+ parser.add_argument('username', type=str, help='The username of the user performing the load operation.')
27
+
28
+ def handle(self, *args, **options):
29
+ file_name = options['file_name']
30
+ username = options['username']
31
+ if not file_name.lower().endswith(".zip"):
32
+ raise CommandError("Input file name must end with .zip")
33
+ try:
34
+ start_time = time.time()
35
+ # Get the user performing the operation
36
+ user = User.objects.get(username=username)
37
+
38
+ result = load_learning_package(file_name, user=user)
39
+ duration = time.time() - start_time
40
+ if result["status"] == "error":
41
+ message = "Errors encountered during restore:\n"
42
+ log_buffer = result.get("log_file_error")
43
+ if log_buffer:
44
+ message += log_buffer.getvalue()
45
+ raise CommandError(message)
46
+ message = f'{file_name} loaded successfully (duration: {duration:.2f} seconds)'
47
+ self.stdout.write(self.style.SUCCESS(message))
48
+ except FileNotFoundError as exc:
49
+ message = f"Learning package file {file_name} not found: {exc}"
50
+ raise CommandError(message) from exc
51
+ except Exception as e:
52
+ message = f"Failed to load '{file_name}': {e}"
53
+ logger.exception(
54
+ "Failed to load zip file %s ",
55
+ file_name,
56
+ )
57
+ raise CommandError(message) from e
@@ -0,0 +1,168 @@
1
+ """
2
+ The serializers module for restoration of authoring data.
3
+ """
4
+ from datetime import timezone
5
+
6
+ from rest_framework import serializers
7
+
8
+ from openedx_learning.apps.authoring.components import api as components_api
9
+
10
+
11
+ class LearningPackageSerializer(serializers.Serializer): # pylint: disable=abstract-method
12
+ """
13
+ Serializer for learning packages.
14
+
15
+ Note:
16
+ The `key` field is serialized, but it is generally not trustworthy for restoration.
17
+ During restore, a new key may be generated or overridden.
18
+ """
19
+ title = serializers.CharField(required=True)
20
+ key = serializers.CharField(required=True)
21
+ description = serializers.CharField(required=True, allow_blank=True)
22
+ created = serializers.DateTimeField(required=True, default_timezone=timezone.utc)
23
+
24
+
25
+ class LearningPackageMetadataSerializer(serializers.Serializer): # pylint: disable=abstract-method
26
+ """
27
+ Serializer for learning package metadata.
28
+
29
+ Note:
30
+ This serializer handles data exported to an archive (e.g., during backup),
31
+ but the metadata is not restored to the database and is meant solely for inspection.
32
+ """
33
+ format_version = serializers.IntegerField(required=True)
34
+ created_by = serializers.CharField(required=False, allow_null=True)
35
+ created_by_email = serializers.EmailField(required=False, allow_null=True)
36
+ created_at = serializers.DateTimeField(required=True, default_timezone=timezone.utc)
37
+ origin_server = serializers.CharField(required=False, allow_null=True)
38
+
39
+
40
+ class EntitySerializer(serializers.Serializer): # pylint: disable=abstract-method
41
+ """
42
+ Serializer for publishable entities.
43
+ """
44
+ can_stand_alone = serializers.BooleanField(required=True)
45
+ key = serializers.CharField(required=True)
46
+ created = serializers.DateTimeField(required=True, default_timezone=timezone.utc)
47
+ created_by = serializers.CharField(required=True, allow_null=True)
48
+
49
+
50
+ class EntityVersionSerializer(serializers.Serializer): # pylint: disable=abstract-method
51
+ """
52
+ Serializer for publishable entity versions.
53
+ """
54
+ title = serializers.CharField(required=True)
55
+ entity_key = serializers.CharField(required=True)
56
+ created = serializers.DateTimeField(required=True, default_timezone=timezone.utc)
57
+ created_by = serializers.CharField(required=True, allow_null=True)
58
+ version_num = serializers.IntegerField(required=True)
59
+
60
+
61
+ class ComponentSerializer(EntitySerializer): # pylint: disable=abstract-method
62
+ """
63
+ Serializer for components.
64
+ Contains logic to convert entity_key to component_type and local_key.
65
+ """
66
+
67
+ def validate(self, attrs):
68
+ """
69
+ Custom validation logic:
70
+ parse the entity_key into (component_type, local_key).
71
+ """
72
+ entity_key = attrs["key"]
73
+ try:
74
+ component_type_obj, local_key = components_api.get_or_create_component_type_by_entity_key(entity_key)
75
+ attrs["component_type"] = component_type_obj
76
+ attrs["local_key"] = local_key
77
+ except ValueError as exc:
78
+ raise serializers.ValidationError({"key": str(exc)})
79
+ return attrs
80
+
81
+
82
+ class ComponentVersionSerializer(EntityVersionSerializer): # pylint: disable=abstract-method
83
+ """
84
+ Serializer for component versions.
85
+ """
86
+
87
+
88
+ class ContainerSerializer(EntitySerializer): # pylint: disable=abstract-method
89
+ """
90
+ Serializer for containers.
91
+ """
92
+ container = serializers.DictField(required=True)
93
+
94
+ def validate_container(self, value):
95
+ """
96
+ Custom validation logic for the container field.
97
+ Ensures that the container dict has exactly one key which is one of
98
+ "section", "subsection", or "unit" values.
99
+ """
100
+ errors = []
101
+ if not isinstance(value, dict) or len(value) != 1:
102
+ errors.append("Container must be a dict with exactly one key.")
103
+ if len(value) == 1: # Only check the key if there is exactly one
104
+ container_type = list(value.keys())[0]
105
+ if container_type not in ("section", "subsection", "unit"):
106
+ errors.append(f"Invalid container value: {container_type}")
107
+ if errors:
108
+ raise serializers.ValidationError(errors)
109
+ return value
110
+
111
+ def validate(self, attrs):
112
+ """
113
+ Custom validation logic:
114
+ parse the container dict to extract the container type.
115
+ """
116
+ container = attrs["container"]
117
+ container_type = list(container.keys())[0] # It is safe to do this after validate_container
118
+ attrs["container_type"] = container_type
119
+ attrs.pop("container") # Remove the container field after processing
120
+ return attrs
121
+
122
+
123
+ class ContainerVersionSerializer(EntityVersionSerializer): # pylint: disable=abstract-method
124
+ """
125
+ Serializer for container versions.
126
+ """
127
+ container = serializers.DictField(required=True)
128
+
129
+ def validate_container(self, value):
130
+ """
131
+ Custom validation logic for the container field.
132
+ Ensures that the container dict has exactly one key "children" which is a list of strings.
133
+ """
134
+ errors = []
135
+ if not isinstance(value, dict) or len(value) != 1:
136
+ errors.append("Container must be a dict with exactly one key.")
137
+ if "children" not in value:
138
+ errors.append("Container must have a 'children' key.")
139
+ if "children" in value and not isinstance(value["children"], list):
140
+ errors.append("'children' must be a list.")
141
+ if errors:
142
+ raise serializers.ValidationError(errors)
143
+ return value
144
+
145
+ def validate(self, attrs):
146
+ """
147
+ Custom validation logic:
148
+ parse the container dict to extract the children list.
149
+ """
150
+ children = attrs["container"]["children"] # It is safe to do this after validate_container
151
+ attrs["children"] = children
152
+ attrs.pop("container") # Remove the container field after processing
153
+ return attrs
154
+
155
+
156
+ class CollectionSerializer(serializers.Serializer): # pylint: disable=abstract-method
157
+ """
158
+ Serializer for collections.
159
+ """
160
+ title = serializers.CharField(required=True)
161
+ key = serializers.CharField(required=True)
162
+ description = serializers.CharField(required=True, allow_blank=True)
163
+ created_by = serializers.IntegerField(required=True, allow_null=True)
164
+ entities = serializers.ListField(
165
+ child=serializers.CharField(),
166
+ required=True,
167
+ allow_empty=True,
168
+ )
@@ -0,0 +1,254 @@
1
+ """
2
+ TOML serialization for learning packages and publishable entities.
3
+ """
4
+
5
+ from datetime import datetime
6
+ from typing import Any, Dict
7
+
8
+ import tomlkit
9
+ from django.contrib.auth.models import User as UserType # pylint: disable=imported-auth-user
10
+
11
+ from openedx_learning.apps.authoring.collections.models import Collection
12
+ from openedx_learning.apps.authoring.publishing import api as publishing_api
13
+ from openedx_learning.apps.authoring.publishing.models import PublishableEntity, PublishableEntityVersion
14
+ from openedx_learning.apps.authoring.publishing.models.learning_package import LearningPackage
15
+
16
+
17
+ def toml_learning_package(
18
+ learning_package: LearningPackage,
19
+ timestamp: datetime,
20
+ format_version: int = 1,
21
+ user: UserType | None = None,
22
+ origin_server: str | None = None
23
+ ) -> str:
24
+ """
25
+ Create a TOML representation of the learning package.
26
+
27
+ The resulting content looks like:
28
+ [meta]
29
+ format_version = 1
30
+ created_by = "dormsbee"
31
+ created_at = 2025-09-03T17:50:59.536190Z
32
+ origin_server = "cms.test"
33
+
34
+ [learning_package]
35
+ title = "Components Test Case Learning Package"
36
+ key = "ComponentTestCase-test-key"
37
+ description = "This is a test learning package for components."
38
+ created = 2025-09-03T17:50:59.536190Z
39
+ updated = 2025-09-03T17:50:59.536190Z
40
+ """
41
+ doc = tomlkit.document()
42
+
43
+ # Learning package main info
44
+ section = tomlkit.table()
45
+ section.add("title", learning_package.title)
46
+ section.add("key", learning_package.key)
47
+ section.add("description", learning_package.description)
48
+ section.add("created", learning_package.created)
49
+ section.add("updated", learning_package.updated)
50
+
51
+ # Learning package metadata
52
+ metadata = tomlkit.table()
53
+ metadata.add("format_version", format_version)
54
+ if user:
55
+ metadata.add("created_by", user.username)
56
+ metadata.add("created_by_email", user.email)
57
+ metadata.add("created_at", timestamp)
58
+ if origin_server:
59
+ metadata.add("origin_server", origin_server)
60
+
61
+ doc.add("meta", metadata)
62
+ doc.add("learning_package", section)
63
+ return tomlkit.dumps(doc)
64
+
65
+
66
+ def _get_toml_publishable_entity_table(
67
+ entity: PublishableEntity,
68
+ draft_version: PublishableEntityVersion | None,
69
+ published_version: PublishableEntityVersion | None,
70
+ include_versions: bool = True) -> tomlkit.items.Table:
71
+ """
72
+ Create a TOML representation of a publishable entity.
73
+
74
+ The resulting content looks like:
75
+ [entity]
76
+ can_stand_alone = true
77
+ key = "xblock.v1:problem:my_published_example"
78
+
79
+ [entity.draft]
80
+ version_num = 2
81
+
82
+ [entity.published]
83
+ version_num = 1
84
+
85
+ [entity.container.section]
86
+
87
+ Note: This function returns a tomlkit.items.Table, which represents
88
+ a string-like TOML fragment rather than a complete TOML document.
89
+ """
90
+ entity_table = tomlkit.table()
91
+ entity_table.add("can_stand_alone", entity.can_stand_alone)
92
+ # Add key since the toml filename doesn't show the real key
93
+ entity_table.add("key", entity.key)
94
+ entity_table.add("created", entity.created)
95
+
96
+ if not include_versions:
97
+ return entity_table
98
+
99
+ if draft_version:
100
+ draft_table = tomlkit.table()
101
+ draft_table.add("version_num", draft_version.version_num)
102
+ entity_table.add("draft", draft_table)
103
+
104
+ published_table = tomlkit.table()
105
+ if published_version:
106
+ published_table.add("version_num", published_version.version_num)
107
+ else:
108
+ published_table.add(tomlkit.comment("unpublished: no published_version_num"))
109
+ entity_table.add("published", published_table)
110
+
111
+ if hasattr(entity, "container"):
112
+ container_table = tomlkit.table()
113
+ container_types = ["section", "subsection", "unit"]
114
+
115
+ for container_type in container_types:
116
+ if hasattr(entity.container, container_type):
117
+ container_table.add(container_type, tomlkit.table())
118
+ break # stop after the first match
119
+
120
+ entity_table.add("container", container_table)
121
+
122
+ return entity_table
123
+
124
+
125
+ def toml_publishable_entity(
126
+ entity: PublishableEntity,
127
+ versions_to_write: list[PublishableEntityVersion],
128
+ draft_version: PublishableEntityVersion | None,
129
+ published_version: PublishableEntityVersion | None) -> str:
130
+ """
131
+ Create a TOML representation of a publishable entity and its versions.
132
+
133
+ The resulting content looks like:
134
+ [entity]
135
+ can_stand_alone = true
136
+ key = "xblock.v1:problem:my_published_example"
137
+
138
+ [entity.draft]
139
+ version_num = 2
140
+
141
+ [entity.published]
142
+ version_num = 1
143
+
144
+ [entity.container.section] (if applicable)
145
+
146
+ # ### Versions
147
+
148
+ [[version]]
149
+ title = "My published problem"
150
+ version_num = 1
151
+
152
+ [version.container] (if applicable)
153
+ children = []
154
+ """
155
+ # Create the TOML representation for the entity itself
156
+ entity_table = _get_toml_publishable_entity_table(entity, draft_version, published_version)
157
+ doc = tomlkit.document()
158
+ doc.add("entity", entity_table)
159
+
160
+ # Add versions as an array of tables (AoT)
161
+ doc.add(tomlkit.nl())
162
+ doc.add(tomlkit.comment("### Versions"))
163
+ for entity_version in versions_to_write:
164
+ version = tomlkit.aot()
165
+ version_table = toml_publishable_entity_version(entity_version)
166
+ version.append(version_table)
167
+ doc.add("version", version)
168
+
169
+ return tomlkit.dumps(doc)
170
+
171
+
172
+ def toml_publishable_entity_version(version: PublishableEntityVersion) -> tomlkit.items.Table:
173
+ """
174
+ Create a TOML representation of a publishable entity version.
175
+
176
+ The resulting content looks like:
177
+ [[version]]
178
+ title = "My published problem"
179
+ version_num = 1
180
+
181
+ [version.container] (if applicable)
182
+ children = []
183
+
184
+ Note: This function returns a tomlkit.items.Table, which represents
185
+ a string-like TOML fragment rather than a complete TOML document.
186
+ """
187
+ version_table = tomlkit.table()
188
+ version_table.add("title", version.title)
189
+ version_table.add("version_num", version.version_num)
190
+
191
+ if hasattr(version, 'containerversion'):
192
+ # If the version has a container version, add its children
193
+ container_table = tomlkit.table()
194
+ children = publishing_api.get_container_children_entities_keys(version.containerversion)
195
+ container_table.add("children", children)
196
+ version_table.add("container", container_table)
197
+ return version_table
198
+
199
+
200
+ def toml_collection(collection: Collection, entity_keys: list[str]) -> str:
201
+ """
202
+ Create a TOML representation of a collection.
203
+
204
+ The resulting content looks like:
205
+ [collection]
206
+ title = "Collection 1"
207
+ key = "COL1"
208
+ description = "Description of Collection 1"
209
+ created = 2025-09-03T22:28:53.839362Z
210
+ entities = [
211
+ "xblock.v1:problem:my_published_example",
212
+ "xblock.v1:html:my_draft_example",
213
+ ]
214
+ """
215
+ doc = tomlkit.document()
216
+
217
+ entities_array = tomlkit.array()
218
+ entities_array.extend(entity_keys)
219
+ entities_array.multiline(True)
220
+
221
+ collection_table = tomlkit.table()
222
+ collection_table.add("title", collection.title)
223
+ collection_table.add("key", collection.key)
224
+ collection_table.add("description", collection.description)
225
+ collection_table.add("created", collection.created)
226
+ collection_table.add("entities", entities_array)
227
+
228
+ doc.add("collection", collection_table)
229
+
230
+ return tomlkit.dumps(doc)
231
+
232
+
233
+ def parse_learning_package_toml(content: str) -> dict:
234
+ """
235
+ Parse the learning package TOML content and return a dict of its fields.
236
+ """
237
+ lp_data: Dict[str, Any] = tomlkit.parse(content)
238
+ return lp_data
239
+
240
+
241
+ def parse_publishable_entity_toml(content: str) -> dict:
242
+ """
243
+ Parse the publishable entity TOML file and return a dict of its fields.
244
+ """
245
+ pe_data: Dict[str, Any] = tomlkit.parse(content)
246
+ return pe_data
247
+
248
+
249
+ def parse_collection_toml(content: str) -> dict:
250
+ """
251
+ Parse the collection TOML content and return a dict of its fields.
252
+ """
253
+ collection_data: Dict[str, Any] = tomlkit.parse(content)
254
+ return collection_data