django-pfx 1.7.6.dev35__tar.gz → 1.7.6.dev39__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 (331) hide show
  1. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/PKG-INFO +3 -1
  2. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/django_pfx.egg-info/PKG-INFO +3 -1
  3. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/django_pfx.egg-info/SOURCES.txt +2 -0
  4. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/django_pfx.egg-info/requires.txt +3 -0
  5. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/django_pfx.egg-info/scm_file_list.json +2 -0
  6. django_pfx-1.7.6.dev39/django_pfx.egg-info/scm_version.json +8 -0
  7. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/source/testing.md +36 -0
  8. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/test.py +203 -2
  9. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/requirements.txt +3 -0
  10. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/setup.cfg +1 -0
  11. django_pfx-1.7.6.dev39/tests/tests/parallel_scenarios.py +89 -0
  12. django_pfx-1.7.6.dev39/tests/tests/test_table_file_helpers.py +144 -0
  13. django_pfx-1.7.6.dev39/tests/tests/test_test_runner.py +234 -0
  14. django_pfx-1.7.6.dev35/django_pfx.egg-info/scm_version.json +0 -8
  15. django_pfx-1.7.6.dev35/tests/tests/test_test_runner.py +0 -117
  16. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/.gitignore +0 -0
  17. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/.gitlab-ci.yml +0 -0
  18. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/.pre-commit-config.yaml +0 -0
  19. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/LICENSE +0 -0
  20. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/MANIFEST.in +0 -0
  21. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/README.md +0 -0
  22. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/django_pfx.egg-info/dependency_links.txt +0 -0
  23. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/django_pfx.egg-info/top_level.txt +0 -0
  24. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/Makefile +0 -0
  25. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/conf.py +0 -0
  26. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/index.rst +0 -0
  27. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/source/api.views.rst +0 -0
  28. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/source/authentication.md +0 -0
  29. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/source/decorator.md +0 -0
  30. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/source/generate_openapi.md +0 -0
  31. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/source/getting_started.md +0 -0
  32. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/source/internationalisation.md +0 -0
  33. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/source/model.md +0 -0
  34. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/source/pfx_views.md +0 -0
  35. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/source/profiling.md +0 -0
  36. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/source/settings.md +0 -0
  37. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/doc/source/user_jobs.md +0 -0
  38. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/img/pfx.png +0 -0
  39. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/img/pfx.svg +0 -0
  40. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/make_messages +0 -0
  41. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/manage.py +0 -0
  42. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/__init__.py +0 -0
  43. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/__init__.py +0 -0
  44. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/apps.py +0 -0
  45. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/email_backend/__init__.py +0 -0
  46. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/email_backend/memory_email_backend.py +0 -0
  47. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/email_backend/q_mailjet_email_backend.py +0 -0
  48. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/exceptions.py +0 -0
  49. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/locale/fr/LC_MESSAGES/django.mo +0 -0
  50. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/locale/fr/LC_MESSAGES/django.po +0 -0
  51. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/mailjet/__init__.py +0 -0
  52. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/mailjet/mailjet.py +0 -0
  53. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/mailjet/message_result.py +0 -0
  54. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/migrations/0001_initial.py +0 -0
  55. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/migrations/0002_alter_email_headers.py +0 -0
  56. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/migrations/0003_alter_email_options.py +0 -0
  57. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/migrations/0004_alter_email_mail_from.py +0 -0
  58. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/migrations/0005_lowercase_verbose_names.py +0 -0
  59. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/migrations/0006_alter_email_attachments.py +0 -0
  60. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/migrations/__init__.py +0 -0
  61. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/models/__init__.py +0 -0
  62. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/models/email.py +0 -0
  63. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/providers/__init__.py +0 -0
  64. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/providers/email_django_q_executor.py +0 -0
  65. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/shortcuts.py +0 -0
  66. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/tasks.py +0 -0
  67. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/templates/emails/base_template.html +0 -0
  68. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/templates/emails/base_template.txt +0 -0
  69. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/templates/emails/base_template_subject.txt +0 -0
  70. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/test.py +0 -0
  71. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/urls.py +0 -0
  72. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/views/__init__.py +0 -0
  73. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/emails/views/email_rest_view.py +0 -0
  74. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/__init__.py +0 -0
  75. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/apidoc/__init__.py +0 -0
  76. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/apidoc/parameters.py +0 -0
  77. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/apidoc/schema.py +0 -0
  78. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/apidoc/tags.py +0 -0
  79. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/apps.py +0 -0
  80. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/decorator/__init__.py +0 -0
  81. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/decorator/rest.py +0 -0
  82. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/default_settings.py +0 -0
  83. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/exceptions.py +0 -0
  84. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/fields/__init__.py +0 -0
  85. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/fields/decimal_field.py +0 -0
  86. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/fields/media_field.py +0 -0
  87. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/fields/minutes_duration_field.py +0 -0
  88. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/fields/rich_text_field.py +0 -0
  89. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/http/__init__.py +0 -0
  90. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/http/json_response.py +0 -0
  91. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  92. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +0 -0
  93. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/management/__init__.py +0 -0
  94. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/management/commands/__init__.py +0 -0
  95. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
  96. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/management/commands/profile.py +0 -0
  97. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/mfa/__init__.py +0 -0
  98. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/mfa/authenticator.py +0 -0
  99. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/mfa/base.py +0 -0
  100. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/mfa/email.py +0 -0
  101. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/mfa/legacy.py +0 -0
  102. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/mfa/sms.py +0 -0
  103. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/middleware/__init__.py +0 -0
  104. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/middleware/authentication.py +0 -0
  105. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/middleware/locale.py +0 -0
  106. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/middleware/profiling.py +0 -0
  107. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/middleware/request_too_big_middleware.py +0 -0
  108. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/middleware/ws_cookie_authentication_middleware.py +0 -0
  109. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/migrations/0001_initial.py +0 -0
  110. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
  111. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
  112. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/migrations/0004_alter_loginban_failed_counter_and_more.py +0 -0
  113. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/migrations/0005_mfadevice_mfadevice_mfa_device_unique_backend.py +0 -0
  114. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/migrations/0006_alter_pfxuser_managers.py +0 -0
  115. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/migrations/0007_alter_pfxuser_options.py +0 -0
  116. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/migrations/0008_lowercase_verbose_names.py +0 -0
  117. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/migrations/__init__.py +0 -0
  118. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/migrations/operations/__init__.py +0 -0
  119. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/migrations/operations/permissions.py +0 -0
  120. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/models/__init__.py +0 -0
  121. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
  122. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/models/attachment_mixin.py +0 -0
  123. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/models/cache_mixins.py +0 -0
  124. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/models/login_ban.py +0 -0
  125. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/models/mfa_device.py +0 -0
  126. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/models/mfa_user_mixin.py +0 -0
  127. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/models/not_null_fields.py +0 -0
  128. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/models/ordered_model_mixin.py +0 -0
  129. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/models/pfx_models.py +0 -0
  130. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/models/pfx_user.py +0 -0
  131. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  132. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/serializers/__init__.py +0 -0
  133. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/serializers/json.py +0 -0
  134. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/settings.py +0 -0
  135. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/shortcuts.py +0 -0
  136. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/sms/__init__.py +0 -0
  137. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/sms/backends/__init__.py +0 -0
  138. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/sms/backends/base.py +0 -0
  139. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/sms/backends/console.py +0 -0
  140. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/sms/backends/memory.py +0 -0
  141. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/storage/__init__.py +0 -0
  142. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/storage/exceptions.py +0 -0
  143. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/storage/local_storage.py +0 -0
  144. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/storage/s3_storage.py +0 -0
  145. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
  146. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
  147. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  148. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  149. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  150. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  151. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/urls.py +0 -0
  152. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/version_check.py +0 -0
  153. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/__init__.py +0 -0
  154. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/authentication_views.py +0 -0
  155. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/fields.py +0 -0
  156. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/filters_views.py +0 -0
  157. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/locale_views.py +0 -0
  158. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/media_rest_view_mixin.py +0 -0
  159. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/mfa_authentication_views.py +0 -0
  160. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/ordered_rest_view_mixin.py +0 -0
  161. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  162. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  163. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/groups.py +0 -0
  164. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  165. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  166. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  167. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  168. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  169. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  170. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  171. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  172. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  173. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/subset.py +0 -0
  174. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  175. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  176. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  177. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  178. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  179. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/views/rest_views.py +0 -0
  180. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/pfxcore/xml_test_runner.py +0 -0
  181. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/settings/__init__.py +0 -0
  182. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/settings/dev.py +0 -0
  183. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/__init__.py +0 -0
  184. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/apps.py +0 -0
  185. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/locale/fr/LC_MESSAGES/django.mo +0 -0
  186. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/locale/fr/LC_MESSAGES/django.po +0 -0
  187. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/migrations/0001_initial.py +0 -0
  188. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/migrations/0002_alter_userjob_options_alter_userjob_auto_queued_and_more.py +0 -0
  189. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/migrations/0003_clean_scheduled_tasks.py +0 -0
  190. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/migrations/0004_userjob_summary_url.py +0 -0
  191. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/migrations/0005_swappable_user_job.py +0 -0
  192. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/migrations/0006_lowercase_verbose_names.py +0 -0
  193. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/migrations/__init__.py +0 -0
  194. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/models/__init__.py +0 -0
  195. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/models/user_job.py +0 -0
  196. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/models/user_job_attachment.py +0 -0
  197. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/providers/__init__.py +0 -0
  198. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/providers/user_job_django_q_executor.py +0 -0
  199. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/providers/user_job_websocket_channel_provider.py +0 -0
  200. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/tasks.py +0 -0
  201. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/urls.py +0 -0
  202. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/views/__init__.py +0 -0
  203. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/views/user_job_attachment_view.py +0 -0
  204. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/views/user_job_view.py +0 -0
  205. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pfx/userjobs/views/user_websocket_consumer.py +0 -0
  206. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/pyproject.toml +0 -0
  207. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/serve-doc +0 -0
  208. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/setup.py +0 -0
  209. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/__init__.py +0 -0
  210. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/apps.py +0 -0
  211. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/channel_group_send_calls.py +0 -0
  212. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
  213. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/mfa_urls.py +0 -0
  214. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/migrations/0001_initial.py +0 -0
  215. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/migrations/__init__.py +0 -0
  216. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/models.py +0 -0
  217. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/settings/__init__.py +0 -0
  218. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/settings/ci.py +0 -0
  219. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/settings/common.py +0 -0
  220. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/settings/dev.py +0 -0
  221. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/settings/dev_custom_example.py +0 -0
  222. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/settings/dev_default.py +0 -0
  223. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/__init__.py +0 -0
  224. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/basic_api_errors.py +0 -0
  225. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/basic_api_test.py +0 -0
  226. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/mfa_test_utils.py +0 -0
  227. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_api_doc.py +0 -0
  228. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_api_doc_search.py +0 -0
  229. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_auth_api.py +0 -0
  230. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_body_mixin.py +0 -0
  231. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_cache.py +0 -0
  232. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_client.py +0 -0
  233. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_emails_email_api.py +0 -0
  234. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_emails_email_model.py +0 -0
  235. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_emails_q_mailjet_email_backend.py +0 -0
  236. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_emails_send_email_message.py +0 -0
  237. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_emails_test_tools.py +0 -0
  238. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_fields_choices.py +0 -0
  239. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_fields_date.py +0 -0
  240. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_fields_decimal.py +0 -0
  241. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_fields_help_text.py +0 -0
  242. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_fields_minutes_duration.py +0 -0
  243. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_fields_one2many.py +0 -0
  244. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_fields_rich_text.py +0 -0
  245. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_fields_time.py +0 -0
  246. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_filters.py +0 -0
  247. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_locale_api.py +0 -0
  248. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_mfa_legacy.py +0 -0
  249. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_mfa_login_api.py +0 -0
  250. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_mfa_setup_api.py +0 -0
  251. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_ordered_rest_view_mixin.py +0 -0
  252. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_perm_tests.py +0 -0
  253. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_permissions.py +0 -0
  254. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_perms_api.py +0 -0
  255. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_post_migrate_groups_update.py +0 -0
  256. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_profiling_middleware.py +0 -0
  257. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_settings.py +0 -0
  258. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_shortcuts.py +0 -0
  259. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_timezone_middleware.py +0 -0
  260. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_tools.py +0 -0
  261. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_user_job_api.py +0 -0
  262. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_user_job_messages.py +0 -0
  263. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_user_job_model.py +0 -0
  264. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_user_queryset.py +0 -0
  265. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_user_websocket.py +0 -0
  266. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_verbose_names.py +0 -0
  267. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_version_check.py +0 -0
  268. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_view_decorators.py +0 -0
  269. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_view_fields.py +0 -0
  270. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/tests/test_ws_cookie_middleware.py +0 -0
  271. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/urls.py +0 -0
  272. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests/views.py +0 -0
  273. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_base_user/__init__.py +0 -0
  274. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_base_user/migrations/__init__.py +0 -0
  275. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_base_user/settings/__init__.py +0 -0
  276. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_base_user/settings/ci.py +0 -0
  277. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_base_user/settings/common.py +0 -0
  278. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_base_user/settings/dev.py +0 -0
  279. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_base_user/settings/dev_custom_example.py +0 -0
  280. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_base_user/settings/dev_default.py +0 -0
  281. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_base_user/tests/__init__.py +0 -0
  282. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_base_user/tests/test_api.py +0 -0
  283. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_base_user/tests/test_auth_api.py +0 -0
  284. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_base_user/urls.py +0 -0
  285. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_base_user/views.py +0 -0
  286. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/__init__.py +0 -0
  287. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/migrations/0001_initial.py +0 -0
  288. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/migrations/__init__.py +0 -0
  289. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/models.py +0 -0
  290. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/settings/__init__.py +0 -0
  291. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/settings/ci.py +0 -0
  292. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/settings/common.py +0 -0
  293. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/settings/dev.py +0 -0
  294. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/settings/dev_custom_example.py +0 -0
  295. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/settings/dev_default.py +0 -0
  296. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/tests/__init__.py +0 -0
  297. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/tests/test_api.py +0 -0
  298. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/tests/test_auth_api.py +0 -0
  299. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/urls.py +0 -0
  300. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user/views.py +0 -0
  301. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/__init__.py +0 -0
  302. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/migrations/0001_initial.py +0 -0
  303. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/migrations/0002_lowercase_verbose_names.py +0 -0
  304. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/migrations/__init__.py +0 -0
  305. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/models.py +0 -0
  306. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/providers.py +0 -0
  307. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/scope.py +0 -0
  308. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/settings/__init__.py +0 -0
  309. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/settings/ci.py +0 -0
  310. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/settings/common.py +0 -0
  311. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/settings/dev.py +0 -0
  312. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/settings/dev_custom_example.py +0 -0
  313. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/settings/dev_default.py +0 -0
  314. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/tests/__init__.py +0 -0
  315. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/tests/test_user_job.py +0 -0
  316. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_custom_user_job/urls.py +0 -0
  317. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/__init__.py +0 -0
  318. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/migrations/0001_initial.py +0 -0
  319. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/migrations/__init__.py +0 -0
  320. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/models.py +0 -0
  321. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/settings/__init__.py +0 -0
  322. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/settings/ci.py +0 -0
  323. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/settings/common.py +0 -0
  324. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/settings/dev.py +0 -0
  325. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/settings/dev_custom_example.py +0 -0
  326. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/settings/dev_default.py +0 -0
  327. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/tests/__init__.py +0 -0
  328. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/tests/test_api.py +0 -0
  329. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/tests/test_auth_api.py +0 -0
  330. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/urls.py +0 -0
  331. {django_pfx-1.7.6.dev35 → django_pfx-1.7.6.dev39}/tests_user_email_login/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-pfx
3
- Version: 1.7.6.dev35
3
+ Version: 1.7.6.dev39
4
4
  Summary: Django PFX is a toolkit designed to streamline the development of RESTful APIs using the Django framework.
5
5
  Author: Hervé Martinet
6
6
  Author-email: herve.martinet@gmail.com
@@ -41,6 +41,8 @@ Requires-Dist: pyotp>=2.10.0; extra == "otp"
41
41
  Provides-Extra: heic-converter
42
42
  Requires-Dist: python-magic; extra == "heic-converter"
43
43
  Requires-Dist: pillow-heif; extra == "heic-converter"
44
+ Provides-Extra: xlsx
45
+ Requires-Dist: openpyxl; extra == "xlsx"
44
46
  Dynamic: license-file
45
47
 
46
48
  # Django PFX
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-pfx
3
- Version: 1.7.6.dev35
3
+ Version: 1.7.6.dev39
4
4
  Summary: Django PFX is a toolkit designed to streamline the development of RESTful APIs using the Django framework.
5
5
  Author: Hervé Martinet
6
6
  Author-email: herve.martinet@gmail.com
@@ -41,6 +41,8 @@ Requires-Dist: pyotp>=2.10.0; extra == "otp"
41
41
  Provides-Extra: heic-converter
42
42
  Requires-Dist: python-magic; extra == "heic-converter"
43
43
  Requires-Dist: pillow-heif; extra == "heic-converter"
44
+ Provides-Extra: xlsx
45
+ Requires-Dist: openpyxl; extra == "xlsx"
44
46
  Dynamic: license-file
45
47
 
46
48
  # Django PFX
@@ -220,6 +220,7 @@ tests/tests/__init__.py
220
220
  tests/tests/basic_api_errors.py
221
221
  tests/tests/basic_api_test.py
222
222
  tests/tests/mfa_test_utils.py
223
+ tests/tests/parallel_scenarios.py
223
224
  tests/tests/test_api_doc.py
224
225
  tests/tests/test_api_doc_search.py
225
226
  tests/tests/test_auth_api.py
@@ -252,6 +253,7 @@ tests/tests/test_post_migrate_groups_update.py
252
253
  tests/tests/test_profiling_middleware.py
253
254
  tests/tests/test_settings.py
254
255
  tests/tests/test_shortcuts.py
256
+ tests/tests/test_table_file_helpers.py
255
257
  tests/tests/test_test_runner.py
256
258
  tests/tests/test_timezone_middleware.py
257
259
  tests/tests/test_tools.py
@@ -16,3 +16,6 @@ pillow-heif
16
16
 
17
17
  [otp]
18
18
  pyotp>=2.10.0
19
+
20
+ [xlsx]
21
+ openpyxl
@@ -210,6 +210,7 @@
210
210
  "tests/tests/basic_api_errors.py",
211
211
  "tests/tests/basic_api_test.py",
212
212
  "tests/tests/mfa_test_utils.py",
213
+ "tests/tests/parallel_scenarios.py",
213
214
  "tests/tests/test_api_doc.py",
214
215
  "tests/tests/test_api_doc_search.py",
215
216
  "tests/tests/test_auth_api.py",
@@ -242,6 +243,7 @@
242
243
  "tests/tests/test_profiling_middleware.py",
243
244
  "tests/tests/test_settings.py",
244
245
  "tests/tests/test_shortcuts.py",
246
+ "tests/tests/test_table_file_helpers.py",
245
247
  "tests/tests/test_test_runner.py",
246
248
  "tests/tests/test_timezone_middleware.py",
247
249
  "tests/tests/test_tools.py",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "1.7.5",
3
+ "distance": 39,
4
+ "node": "g471828c66bafecdb4544e8e23075df5d7b128c66",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-21"
8
+ }
@@ -430,3 +430,39 @@ class ATestClass(EmailTestMixin, SMSTestMixin, TestCase):
430
430
  [(to, message)] = self.sms_outbox()
431
431
  self.assertEqual(to, '+41791234567')
432
432
  ```
433
+
434
+ ## CSV and Excel files
435
+
436
+ `CSVFileHelper` and `XLSXFileHelper` read back a table file of a test: an
437
+ export, a file attached to a user job, a file to import.
438
+
439
+ ```python
440
+ from pfx.pfxcore.test import CSVFileHelper, XLSXFileHelper
441
+
442
+ f = XLSXFileHelper(response) # or CSVFileHelper(response)
443
+ f.header() # ['Name', 'Birth']
444
+ f.dicts() # [{'Name': 'Alice', 'Birth': datetime(…)}, …]
445
+ f.lines()[0][f.column('Name')] # 'Alice'
446
+ ```
447
+
448
+ The file can be given as bytes, a string (its content), a `Path`, an HTTP
449
+ response (streamed or not),
450
+ an iterable of chunks, or the value of a `MediaField` on the local storage
451
+ (`XLSXFileHelper(job.attachments.get().file)`).
452
+
453
+ - `header=2`: the number of the line of the headers, for a file with a title
454
+ above them. The lines after it are the data.
455
+ - `header()`, `lines()`, `dicts()`: the headers, the data as lists, the data
456
+ as dicts by header. `line(n)` and `line_dict(n)` count from 0, the first
457
+ line of the file; `len()` counts every line.
458
+ - `column(title)`: the index of a column.
459
+ - `write_file(path)` and `pretty_print()`: to look at the file while writing a
460
+ test.
461
+
462
+ `CSVFileHelper(data, delimiter=';', encoding='utf-8-sig')` reads the CSV of a
463
+ French Excel; its values are strings.
464
+
465
+ `XLSXFileHelper(data, sheet='Totals')` reads a sheet by name or by index, the
466
+ active one by default. Its values keep their type (numbers, dates), and
467
+ `workbook` and `sheet` give the formats and the styles. It needs
468
+ [openpyxl](https://openpyxl.readthedocs.io/): install `django-pfx[xlsx]`.
@@ -1,15 +1,25 @@
1
+ import copy
2
+ import csv
3
+ import io
1
4
  import json
2
5
  import logging
3
6
  import re
7
+ import traceback
4
8
  from functools import reduce
5
9
  from http.cookies import SimpleCookie
6
10
  from json import JSONDecodeError
11
+ from pathlib import Path
7
12
 
8
13
  from django.apps import apps
9
14
  from django.db import transaction
10
15
  from django.test import override_settings
11
16
  from django.test.client import Client as DjangoClient
12
- from django.test.runner import DiscoverRunner, ParallelTestSuite
17
+ from django.test.runner import (
18
+ DiscoverRunner,
19
+ ParallelTestSuite,
20
+ RemoteTestResult,
21
+ RemoteTestRunner,
22
+ )
13
23
  from django.test.runner import _init_worker as django_init_worker
14
24
  from django.utils.module_loading import import_string
15
25
 
@@ -320,6 +330,130 @@ class TestPermsAssertMixin(TestAssertMixin):
320
330
  self.assertPerms(**perms)
321
331
 
322
332
 
333
+ def file_content(data):
334
+ """The bytes of a file given to a test.
335
+
336
+ ``data`` can be bytes, a string, a ``Path``, an HTTP response (streamed
337
+ or not), an iterable of chunks, or the value of a ``MediaField`` on the
338
+ local storage, such as the attachment of a user job.
339
+ """
340
+ if isinstance(data, bytes):
341
+ return data
342
+ if isinstance(data, Path):
343
+ return data.read_bytes()
344
+ if isinstance(data, str):
345
+ return data.encode()
346
+ if isinstance(data, dict):
347
+ return Path(settings.STORAGE_LOCAL_ROOT, data['key']).read_bytes()
348
+ if hasattr(data, 'streaming_content'):
349
+ data = data.streaming_content
350
+ elif hasattr(data, 'content'):
351
+ return data.content
352
+ return b"".join(
353
+ chunk.encode() if isinstance(chunk, str) else chunk
354
+ for chunk in data)
355
+
356
+
357
+ class TableFileResponseHelper:
358
+ """A table file of a test, read back as rows of values.
359
+
360
+ ``header`` is the number of the line of the headers, the first by
361
+ default: the lines after it are the data. :meth:`line` counts from 0,
362
+ the first line of the file.
363
+ """
364
+
365
+ def __init__(self, data, header=1):
366
+ self.content = file_content(data)
367
+ self.header_index = header - 1
368
+ self.rows = self.read_rows()
369
+
370
+ def read_rows(self):
371
+ raise NotImplementedError # pragma: no cover
372
+
373
+ def write_file(self, path):
374
+ """Write the file, to open it while writing a test."""
375
+ Path(path).write_bytes(self.content)
376
+
377
+ def pretty_print(self):
378
+ """Print the rows as a table, to read them while writing a test."""
379
+ cells = [
380
+ ['' if v is None else str(v) for v in row] for row in self.rows]
381
+ widths = [
382
+ max(len(row[i]) for row in cells if i < len(row))
383
+ for i in range(max((len(row) for row in cells), default=0))]
384
+ print()
385
+ for row in cells:
386
+ print(' '.join(v.ljust(w) for v, w in zip(row, widths)).rstrip())
387
+
388
+ def header(self):
389
+ return self.rows[self.header_index]
390
+
391
+ def line(self, n):
392
+ return self.rows[n]
393
+
394
+ def len(self):
395
+ return len(self.rows)
396
+
397
+ def lines(self):
398
+ """The rows after the headers."""
399
+ return self.rows[self.header_index + 1:]
400
+
401
+ def column(self, title):
402
+ """The index of the column ``title``."""
403
+ return self.header().index(title)
404
+
405
+ def line_dict(self, n):
406
+ """The line ``n`` by header."""
407
+ return dict(zip(self.header(), self.line(n)))
408
+
409
+ def dicts(self):
410
+ """The rows after the headers, by header."""
411
+ return [dict(zip(self.header(), row)) for row in self.lines()]
412
+
413
+
414
+ class CSVFileHelper(TableFileResponseHelper):
415
+ """A CSV file of a test; the values are strings."""
416
+
417
+ def __init__(self, data, header=1, delimiter=',', encoding='utf-8-sig'):
418
+ self.delimiter = delimiter
419
+ self.encoding = encoding
420
+ super().__init__(data, header)
421
+
422
+ def read_rows(self):
423
+ return list(csv.reader(
424
+ io.StringIO(self.content.decode(self.encoding)),
425
+ delimiter=self.delimiter))
426
+
427
+
428
+ class XLSXFileHelper(TableFileResponseHelper):
429
+ """An Excel file of a test, read with openpyxl.
430
+
431
+ ``sheet`` is the name or the index of the sheet read, the active one
432
+ by default. The values keep their type (numbers, dates); ``workbook``
433
+ and ``sheet`` give the formats and the styles.
434
+ """
435
+
436
+ def __init__(self, data, header=1, sheet=None):
437
+ self.sheet_name = sheet
438
+ super().__init__(data, header)
439
+
440
+ def read_rows(self):
441
+ try:
442
+ import openpyxl
443
+ except ImportError: # pragma: no cover
444
+ raise ImportError(
445
+ "XLSXFileHelper needs openpyxl: install django-pfx[xlsx].")
446
+
447
+ self.workbook = openpyxl.load_workbook(io.BytesIO(self.content))
448
+ if self.sheet_name is None:
449
+ self.sheet = self.workbook.active
450
+ elif isinstance(self.sheet_name, int):
451
+ self.sheet = self.workbook.worksheets[self.sheet_name]
452
+ else:
453
+ self.sheet = self.workbook[self.sheet_name]
454
+ return [list(row) for row in self.sheet.iter_rows(values_only=True)]
455
+
456
+
323
457
  class MockBoto3Client:
324
458
  def generate_presigned_url(self, *args, **kwargs):
325
459
  params = kwargs.get('Params', {})
@@ -351,8 +485,72 @@ def _init_worker(*args, **kwargs):
351
485
  override_settings(**get_test_backends()).enable()
352
486
 
353
487
 
488
+ class UnpicklableTestError(Exception):
489
+ """An error a parallel worker could not send back, as text."""
490
+
491
+
492
+ class UnpicklableTestFailure(UnpicklableTestError, AssertionError):
493
+ """A failure a parallel worker could not send back, as text."""
494
+
495
+
496
+ class _Repr(str):
497
+ def __repr__(self):
498
+ return str(self)
499
+
500
+
501
+ class PicklableRemoteTestResult(RemoteTestResult):
502
+ """Send every outcome of a parallel worker back to the parent.
503
+
504
+ Django aborts the whole run, without the traceback, on an exception it
505
+ cannot pickle (one holding a request, a lock, a local class…). Such an
506
+ exception is sent as its formatted traceback instead, and the run goes
507
+ on."""
508
+
509
+ def _picklable(self, err):
510
+ try:
511
+ self._confirm_picklable(err)
512
+ except Exception:
513
+ cls = (
514
+ UnpicklableTestFailure if issubclass(err[0], AssertionError)
515
+ else UnpicklableTestError)
516
+ return cls, cls(''.join(traceback.format_exception(*err))), None
517
+ return err
518
+
519
+ def _picklable_subtest(self, test, subtest):
520
+ # The subtest holds the running test case, which can become
521
+ # unpicklable after the failure: a fresh one is always sent.
522
+ subtest = copy.copy(subtest)
523
+ subtest.test_case = type(test)(test._testMethodName)
524
+ try:
525
+ self._confirm_picklable(subtest)
526
+ except Exception:
527
+ subtest.params = {
528
+ k: _Repr(repr(v)) for k, v in subtest.params.items()}
529
+ return subtest
530
+
531
+ def addError(self, test, err):
532
+ super().addError(test, self._picklable(err))
533
+
534
+ def addFailure(self, test, err):
535
+ super().addFailure(test, self._picklable(err))
536
+
537
+ def addExpectedFailure(self, test, err):
538
+ super().addExpectedFailure(test, self._picklable(err))
539
+
540
+ def addSubTest(self, test, subtest, err):
541
+ if err is not None:
542
+ err = self._picklable(err)
543
+ subtest = self._picklable_subtest(test, subtest)
544
+ super().addSubTest(test, subtest, err)
545
+
546
+
547
+ class PicklableRemoteTestRunner(RemoteTestRunner):
548
+ resultclass = PicklableRemoteTestResult
549
+
550
+
354
551
  class MemoryBackendsParallelTestSuite(ParallelTestSuite):
355
552
  init_worker = _init_worker
553
+ runner_class = PicklableRemoteTestRunner
356
554
 
357
555
 
358
556
  class MemoryBackendsRunnerMixin:
@@ -371,7 +569,10 @@ class MemoryBackendsRunnerMixin:
371
569
  validated, which ``locmem`` never does.
372
570
 
373
571
  A test class decorated with ``override_settings`` keeps its own
374
- backends."""
572
+ backends.
573
+
574
+ With ``--parallel``, the workers also send back the errors Django
575
+ cannot pickle (see :class:`PicklableRemoteTestResult`)."""
375
576
 
376
577
  parallel_test_suite = MemoryBackendsParallelTestSuite
377
578
 
@@ -4,6 +4,8 @@ pre-commit
4
4
  flake8
5
5
  isort
6
6
  coverage
7
+ # Pickles the tracebacks of the --parallel workers.
8
+ tblib
7
9
  psycopg
8
10
  Babel
9
11
  pyjwt>=2.1
@@ -31,3 +33,4 @@ munch
31
33
  # bypasses the error handling of pfx.emails.models.Email.send.
32
34
  mailjet_rest<1.8
33
35
  responses
36
+ openpyxl
@@ -46,6 +46,7 @@ install_requires =
46
46
  [options.extras_require]
47
47
  otp = pyotp>=2.10.0
48
48
  heic-converter = python-magic; pillow-heif
49
+ xlsx = openpyxl
49
50
 
50
51
  [options.packages.find]
51
52
  exclude =
@@ -0,0 +1,89 @@
1
+ """Test cases that fail in ways a parallel worker cannot pickle.
2
+
3
+ Run by test_test_runner.py, not by the discovery (the name does not start
4
+ with ``test``). They live at module level as real test classes do: the
5
+ parent process rebuilds a test case from its class."""
6
+ import threading
7
+ import unittest
8
+
9
+
10
+ class Unpicklable(Exception):
11
+ def __init__(self):
12
+ super().__init__("holds a lock")
13
+ self.lock = threading.Lock()
14
+
15
+
16
+ class KeywordOnly(Exception):
17
+ """Pickles, but cannot be rebuilt from its args."""
18
+
19
+ def __init__(self, *, code):
20
+ super().__init__(f"code {code}")
21
+ self.code = code
22
+
23
+
24
+ class UnpicklableAssertion(AssertionError):
25
+ def __init__(self):
26
+ super().__init__("assertion holding a lock")
27
+ self.lock = threading.Lock()
28
+
29
+
30
+ class Scenarios(unittest.TestCase):
31
+
32
+ def test_plain_failure(self):
33
+ self.assertEqual(1, 2)
34
+
35
+ def test_lock_in_the_exception(self):
36
+ raise Unpicklable()
37
+
38
+ def test_unpicklable_cause(self):
39
+ try:
40
+ raise Unpicklable()
41
+ except Unpicklable as e:
42
+ raise ValueError("wrapped") from e
43
+
44
+ def test_local_class(self):
45
+ class Local(Exception):
46
+ pass
47
+ raise Local("local class")
48
+
49
+ def test_keyword_only_init(self):
50
+ raise KeywordOnly(code=42)
51
+
52
+ def test_unpicklable_assertion(self):
53
+ raise UnpicklableAssertion()
54
+
55
+ @unittest.expectedFailure
56
+ def test_expected_failure(self):
57
+ raise Unpicklable()
58
+
59
+ def test_subtest_then_a_lock(self):
60
+ with self.subTest(i=1):
61
+ self.assertEqual(1, 5)
62
+ self.lock = threading.Lock()
63
+
64
+ def test_subtest_with_a_lock_param(self):
65
+ with self.subTest(lock=threading.Lock()):
66
+ self.fail("with a lock as param")
67
+
68
+ def test_subtest_unpicklable_error(self):
69
+ with self.subTest(n=1):
70
+ raise Unpicklable()
71
+
72
+
73
+ class TearDownError(unittest.TestCase):
74
+
75
+ def tearDown(self):
76
+ raise Unpicklable()
77
+
78
+ def test_ok(self):
79
+ pass
80
+
81
+
82
+ class SetUpClassError(unittest.TestCase):
83
+
84
+ @classmethod
85
+ def setUpClass(cls):
86
+ raise Unpicklable()
87
+
88
+ def test_never_runs(self):
89
+ pass
@@ -0,0 +1,144 @@
1
+ import io
2
+ import os
3
+ import tempfile
4
+ from contextlib import redirect_stdout
5
+ from datetime import date
6
+ from pathlib import Path
7
+
8
+ from django.http import HttpResponse, StreamingHttpResponse
9
+ from django.test import SimpleTestCase, override_settings
10
+
11
+ from openpyxl import Workbook
12
+
13
+ from pfx.pfxcore.test import CSVFileHelper, XLSXFileHelper
14
+
15
+ CSV = "Name,Age\nAlice,30\nBob,\n"
16
+
17
+
18
+ def xlsx(*sheets):
19
+ """An Excel file of `sheets`, (title, rows) pairs."""
20
+ workbook = Workbook()
21
+ workbook.remove(workbook.active)
22
+ for title, rows in sheets:
23
+ sheet = workbook.create_sheet(title)
24
+ for row in rows:
25
+ sheet.append(row)
26
+ output = io.BytesIO()
27
+ workbook.save(output)
28
+ return output.getvalue()
29
+
30
+
31
+ PEOPLE = [['Name', 'Birth'], ['Alice', date(1990, 5, 1)], ['Bob', None]]
32
+
33
+
34
+ class TestCSVFileHelper(SimpleTestCase):
35
+ def test_the_sources_of_a_file(self):
36
+ """Bytes, a string, a response, a streamed one, chunks of text."""
37
+ for data in [
38
+ CSV.encode(), CSV, HttpResponse(CSV),
39
+ StreamingHttpResponse(iter([CSV[:10], CSV[10:]])),
40
+ [CSV[:10].encode(), CSV[10:].encode()]]:
41
+ with self.subTest(data=type(data).__name__):
42
+ f = CSVFileHelper(data)
43
+ self.assertEqual(f.header(), ['Name', 'Age'])
44
+ self.assertEqual(f.len(), 3)
45
+
46
+ def test_lines(self):
47
+ """`line` counts from the headers, `lines` and `dicts` are the data;
48
+ a CSV value is a string."""
49
+ f = CSVFileHelper(CSV)
50
+
51
+ self.assertEqual(f.line(1), ['Alice', '30'])
52
+ self.assertEqual(f.lines(), [['Alice', '30'], ['Bob', '']])
53
+ self.assertEqual(f.column('Age'), 1)
54
+ self.assertEqual(f.line_dict(2), {'Name': 'Bob', 'Age': ''})
55
+ self.assertEqual(
56
+ f.dicts(),
57
+ [{'Name': 'Alice', 'Age': '30'}, {'Name': 'Bob', 'Age': ''}])
58
+
59
+ def test_a_french_excel_csv(self):
60
+ """Semicolons, and the byte order mark Excel writes."""
61
+ f = CSVFileHelper(
62
+ "Nom;Âge\nAlice;30\n".encode(), delimiter=';')
63
+
64
+ self.assertEqual(f.dicts(), [{'Nom': 'Alice', 'Âge': '30'}])
65
+
66
+ def test_headers_below_a_title(self):
67
+ f = CSVFileHelper("Report\nName,Age\nAlice,30\n", header=2)
68
+
69
+ self.assertEqual(f.header(), ['Name', 'Age'])
70
+ self.assertEqual(f.dicts(), [{'Name': 'Alice', 'Age': '30'}])
71
+
72
+ def test_unknown_column(self):
73
+ with self.assertRaises(ValueError):
74
+ CSVFileHelper(CSV).column('Size')
75
+
76
+ def test_a_path(self):
77
+ """A Path is a file to read; a string is the content itself."""
78
+ with tempfile.TemporaryDirectory() as tmp:
79
+ path = Path(tmp, 'file.csv')
80
+ path.write_text(CSV)
81
+
82
+ self.assertEqual(CSVFileHelper(path).len(), 3)
83
+
84
+ def test_write_file_and_pretty_print(self):
85
+ f = CSVFileHelper(CSV)
86
+ with tempfile.TemporaryDirectory() as tmp:
87
+ path = os.path.join(tmp, 'file.csv')
88
+ f.write_file(path)
89
+ with open(path, 'rb') as written:
90
+ self.assertEqual(written.read(), CSV.encode())
91
+
92
+ out = io.StringIO()
93
+ with redirect_stdout(out):
94
+ f.pretty_print()
95
+ self.assertEqual(
96
+ out.getvalue(), "\nName Age\nAlice 30\nBob\n")
97
+
98
+
99
+ class TestXLSXFileHelper(SimpleTestCase):
100
+ def test_values_keep_their_type(self):
101
+ """A date is a datetime, an empty cell None."""
102
+ f = XLSXFileHelper(xlsx(('People', PEOPLE)))
103
+
104
+ self.assertEqual(f.header(), ['Name', 'Birth'])
105
+ self.assertEqual(f.line_dict(1)['Birth'].date(), date(1990, 5, 1))
106
+ self.assertEqual(f.dicts()[1], {'Name': 'Bob', 'Birth': None})
107
+ self.assertEqual(f.len(), 3)
108
+ self.assertEqual(f.column('Birth'), 1)
109
+
110
+ def test_the_sheet_read(self):
111
+ """The active one by default, else by name or by index; the sheet
112
+ is there for the formats and the styles."""
113
+ content = xlsx(('People', PEOPLE), ('Totals', [['Total'], [2]]))
114
+
115
+ self.assertEqual(XLSXFileHelper(content).header(), ['Name', 'Birth'])
116
+ f = XLSXFileHelper(content, sheet='Totals')
117
+ self.assertEqual(f.dicts(), [{'Total': 2}])
118
+ self.assertEqual(f.sheet.title, 'Totals')
119
+ self.assertEqual(
120
+ XLSXFileHelper(content, sheet=1).header(), ['Total'])
121
+
122
+ def test_a_response(self):
123
+ f = XLSXFileHelper(StreamingHttpResponse(
124
+ iter([xlsx(('People', PEOPLE))])))
125
+
126
+ self.assertEqual(f.lines()[0][0], 'Alice')
127
+
128
+ def test_headers_below_a_title(self):
129
+ f = XLSXFileHelper(
130
+ xlsx(('Report', [['Report 2026'], *PEOPLE])), header=2)
131
+
132
+ self.assertEqual(f.header(), ['Name', 'Birth'])
133
+ self.assertEqual(len(f.dicts()), 2)
134
+
135
+ def test_a_file_of_the_local_storage(self):
136
+ """The value of a MediaField, such as the attachment of a job."""
137
+ with tempfile.TemporaryDirectory() as tmp:
138
+ with open(os.path.join(tmp, 'export.xlsx'), 'wb') as f:
139
+ f.write(xlsx(('People', PEOPLE)))
140
+ with override_settings(STORAGE_LOCAL_ROOT=tmp):
141
+ helper = XLSXFileHelper(
142
+ {'key': 'export.xlsx', 'name': 'export.xlsx'})
143
+
144
+ self.assertEqual(helper.header(), ['Name', 'Birth'])