ul-api-utils 9.0.0a6__tar.gz → 9.0.0a8__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.

Potentially problematic release.


This version of ul-api-utils might be problematic. Click here for more details.

Files changed (163) hide show
  1. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/PKG-INFO +1 -1
  2. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/setup.py +1 -1
  3. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/api_resource/api_resource.py +4 -3
  4. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/api_resource/api_resource_fn_typing.py +9 -10
  5. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/api_resource/api_response.py +9 -10
  6. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/debug/debugger.py +3 -3
  7. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/debug/stat.py +2 -7
  8. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/internal_api/internal_api.py +1 -1
  9. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/internal_api/internal_api_response.py +6 -6
  10. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/modules/api_sdk.py +1 -1
  11. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_three_specifier.py +33 -12
  12. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/utils/replace_in_dict.py +1 -1
  13. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/json_encoder.py +3 -1
  14. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/unwrap_typing.py +4 -1
  15. ul-api-utils-9.0.0a8/ul_api_utils/validators/__tests__/test_custom_fields.py +32 -0
  16. ul-api-utils-9.0.0a8/ul_api_utils/validators/custom_fields.py +66 -0
  17. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils.egg-info/PKG-INFO +1 -1
  18. ul-api-utils-9.0.0a6/ul_api_utils/validators/__tests__/test_custom_fields.py +0 -32
  19. ul-api-utils-9.0.0a6/ul_api_utils/validators/custom_fields.py +0 -84
  20. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/LICENSE +0 -0
  21. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/README.md +0 -0
  22. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/__init__.py +0 -0
  23. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/conf.py +0 -0
  24. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/main.py +0 -0
  25. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/models/__init__.py +0 -0
  26. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/permissions.py +0 -0
  27. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/pure_flask_example.py +0 -0
  28. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/rate_limit_load.py +0 -0
  29. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/redis_repository.py +0 -0
  30. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/routes/__init__.py +0 -0
  31. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/routes/api_some.py +0 -0
  32. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/sockets/__init__.py +0 -0
  33. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/sockets/on_connect.py +0 -0
  34. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/sockets/on_disconnect.py +0 -0
  35. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/sockets/on_json.py +0 -0
  36. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/sockets/on_message.py +0 -0
  37. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/sockets/on_open.py +0 -0
  38. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/workers/__init__.py +0 -0
  39. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/example/workers/worker.py +0 -0
  40. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/setup.cfg +0 -0
  41. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/__init__.py +0 -0
  42. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/access/__init__.py +0 -0
  43. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/api_resource/__init__.py +0 -0
  44. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/api_resource/api_request.py +0 -0
  45. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/api_resource/api_resource_config.py +0 -0
  46. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/api_resource/api_resource_error_handling.py +0 -0
  47. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/api_resource/api_resource_type.py +0 -0
  48. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/api_resource/api_response_db.py +0 -0
  49. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/api_resource/api_response_payload_alias.py +0 -0
  50. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/api_resource/db_types.py +0 -0
  51. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/api_resource/signature_check.py +0 -0
  52. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/commands/__init__.py +0 -0
  53. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/commands/cmd_enc_keys.py +0 -0
  54. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/commands/cmd_gen_api_user_token.py +0 -0
  55. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/commands/cmd_gen_new_api_user.py +0 -0
  56. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/commands/cmd_generate_api_docs.py +0 -0
  57. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/commands/cmd_start.py +0 -0
  58. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/commands/cmd_worker_start.py +0 -0
  59. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/commands/start/__init__.py +0 -0
  60. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/commands/start/gunicorn.conf.local.py +0 -0
  61. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/commands/start/gunicorn.conf.py +0 -0
  62. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/commands/start/wsgi.py +0 -0
  63. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/conf/ul-debugger-main.js +0 -0
  64. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/conf/ul-debugger-ui.js +0 -0
  65. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/conf.py +0 -0
  66. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/const.py +0 -0
  67. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/debug/__init__.py +0 -0
  68. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/debug/malloc.py +0 -0
  69. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/encrypt/__init__.py +0 -0
  70. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/encrypt/encrypt_decrypt_abstract.py +0 -0
  71. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/encrypt/encrypt_decrypt_aes_xtea.py +0 -0
  72. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/errors.py +0 -0
  73. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/internal_api/__init__.py +0 -0
  74. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/internal_api/__tests__/__init__.py +0 -0
  75. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/internal_api/__tests__/internal_api.py +0 -0
  76. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/internal_api/__tests__/internal_api_content_type.py +0 -0
  77. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/internal_api/internal_api_check_context.py +0 -0
  78. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/internal_api/internal_api_error.py +0 -0
  79. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/main.py +0 -0
  80. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/modules/__init__.py +0 -0
  81. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/modules/__tests__/__init__.py +0 -0
  82. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/modules/__tests__/test_api_sdk_jwt.py +0 -0
  83. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/modules/api_sdk_config.py +0 -0
  84. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/modules/api_sdk_jwt.py +0 -0
  85. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/modules/intermediate_state.py +0 -0
  86. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/modules/worker_context.py +0 -0
  87. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/modules/worker_sdk.py +0 -0
  88. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/modules/worker_sdk_config.py +0 -0
  89. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/py.typed +0 -0
  90. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/__init__.py +0 -0
  91. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/caching.py +0 -0
  92. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/debugger_scripts.py +0 -0
  93. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/health_check/__init__.py +0 -0
  94. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/health_check/const.py +0 -0
  95. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/health_check/health_check.py +0 -0
  96. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/health_check/health_check_template.py +0 -0
  97. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/health_check/resource.py +0 -0
  98. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/not_implemented.py +0 -0
  99. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/permissions.py +0 -0
  100. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/rate_limitter.py +0 -0
  101. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/socketio.py +0 -0
  102. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/swagger.py +0 -0
  103. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/web_forms/__init__.py +0 -0
  104. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/web_forms/custom_fields/__init__.py +0 -0
  105. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/web_forms/custom_fields/custom_checkbox_select.py +0 -0
  106. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/web_forms/custom_widgets/__init__.py +0 -0
  107. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/web_forms/custom_widgets/custom_select_widget.py +0 -0
  108. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/web_forms/custom_widgets/custom_text_input_widget.py +0 -0
  109. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/resources/web_forms/uni_form.py +0 -0
  110. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/sentry.py +0 -0
  111. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/__init__.py +0 -0
  112. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/__tests__/__init__.py +0 -0
  113. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/__tests__/api_path_version.py +0 -0
  114. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/__tests__/unwrap_typing.py +0 -0
  115. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/api_encoding.py +0 -0
  116. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/api_format.py +0 -0
  117. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/api_method.py +0 -0
  118. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/api_pagination.py +0 -0
  119. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/api_path_version.py +0 -0
  120. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/api_request_info.py +0 -0
  121. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/avro.py +0 -0
  122. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/broker_topics_message_count.py +0 -0
  123. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/cached_per_request.py +0 -0
  124. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/colors.py +0 -0
  125. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/constants.py +0 -0
  126. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/decode_base64.py +0 -0
  127. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/deprecated.py +0 -0
  128. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flags.py +0 -0
  129. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/__init__.py +0 -0
  130. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/conf.py +0 -0
  131. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/exceptions.py +0 -0
  132. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/specifiers/__init__.py +0 -0
  133. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_models.py +0 -0
  134. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_specifier.py +0 -0
  135. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_version.py +0 -0
  136. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/utils/__init__.py +0 -0
  137. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/utils/input_type.py +0 -0
  138. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/utils/parameter_type.py +0 -0
  139. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/utils/request_type.py +0 -0
  140. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/utils/schema_type.py +0 -0
  141. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/flask_swagger_generator/utils/security_type.py +0 -0
  142. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/imports.py +0 -0
  143. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/instance_checks.py +0 -0
  144. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/jinja/__init__.py +0 -0
  145. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/jinja/t_url_for.py +0 -0
  146. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/jinja/to_pretty_json.py +0 -0
  147. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/load_modules.py +0 -0
  148. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/memory_db/__init__.py +0 -0
  149. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/memory_db/__tests__/__init__.py +0 -0
  150. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/memory_db/errors.py +0 -0
  151. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/memory_db/repository.py +0 -0
  152. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/token_check.py +0 -0
  153. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/token_check_through_request.py +0 -0
  154. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/utils/uuid_converter.py +0 -0
  155. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/validators/__init__.py +0 -0
  156. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/validators/__tests__/__init__.py +0 -0
  157. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/validators/validate_empty_object.py +0 -0
  158. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils/validators/validate_uuid.py +0 -0
  159. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils.egg-info/SOURCES.txt +0 -0
  160. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils.egg-info/dependency_links.txt +0 -0
  161. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils.egg-info/entry_points.txt +0 -0
  162. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils.egg-info/requires.txt +0 -0
  163. {ul-api-utils-9.0.0a6 → ul-api-utils-9.0.0a8}/ul_api_utils.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ul-api-utils
3
- Version: 9.0.0a6
3
+ Version: 9.0.0a8
4
4
  Summary: Python api utils
5
5
  Author: Unic-lab
6
6
  Author-email:
@@ -9,7 +9,7 @@ with open(path.join(HERE, 'README.md'), encoding='utf-8') as f:
9
9
 
10
10
  setup(
11
11
  name='ul-api-utils',
12
- version='9.0.0a6',
12
+ version='9.0.0a8',
13
13
  description='Python api utils',
14
14
  author='Unic-lab',
15
15
  long_description=long_description,
@@ -16,8 +16,9 @@ from ul_api_utils.api_resource.api_resource_error_handling import WEB_EXCEPTION_
16
16
  ProcessingExceptionsParams, WEB_UNKNOWN_ERROR_PARAMS
17
17
  from ul_api_utils.api_resource.api_resource_fn_typing import ApiResourceFnTyping
18
18
  from ul_api_utils.api_resource.api_resource_type import ApiResourceType
19
- from ul_api_utils.api_resource.api_response import JsonApiResponse, HtmlApiResponse, FileApiResponse, JsonApiResponsePayload, EmptyJsonApiResponse, \
20
- RedirectApiResponse, ProxyJsonApiResponse, RootJsonApiResponse
19
+ from ul_api_utils.api_resource.api_response import JsonApiResponse, HtmlApiResponse, FileApiResponse, \
20
+ JsonApiResponsePayload, EmptyJsonApiResponse, \
21
+ RedirectApiResponse, ProxyJsonApiResponse, RootJsonApiResponse, RootJsonApiResponsePayload
21
22
  from ul_api_utils.conf import APPLICATION_ENV, APPLICATION_JWT_PUBLIC_KEY, APPLICATION_DEBUG
22
23
  from ul_api_utils.const import REQUEST_HEADER__X_FORWARDED_FOR, \
23
24
  RESPONSE_HEADER__WWW_AUTH, OOPS, REQUEST_HEADER__USER_AGENT
@@ -38,7 +39,7 @@ TPayload = TypeVar('TPayload')
38
39
 
39
40
 
40
41
  T = TypeVar('T')
41
- TResp = TypeVar('TResp', bound=JsonApiResponsePayload)
42
+ TResp = TypeVar('TResp', bound=Union[JsonApiResponsePayload, RootJsonApiResponsePayload[Any]])
42
43
 
43
44
 
44
45
  class ApiResource:
@@ -2,7 +2,7 @@ import inspect
2
2
  from typing import NamedTuple, Any, Callable, Optional, List, Dict, Type, Tuple, TYPE_CHECKING, Union, get_origin, get_args
3
3
 
4
4
  from flask import request
5
- from pydantic import BaseModel, ValidationError, validate_call, TypeAdapter
5
+ from pydantic import BaseModel, ValidationError, validate_call, TypeAdapter, RootModel
6
6
  from pydantic.v1.utils import deep_update
7
7
  from pydantic_core import ErrorDetails
8
8
 
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
25
25
  FN_SYSTEM_PROPS = {"api_resource", "query", "body", "return", "body_validation_error", "query_validation_error"}
26
26
 
27
27
 
28
- def _is_complex_type(annotation):
28
+ def _is_complex_type(annotation: Any) -> bool:
29
29
  origin = get_origin(annotation)
30
30
 
31
31
  # Optional[type_] is typing.Union
@@ -82,18 +82,18 @@ class ApiResourceFnTyping(NamedTuple):
82
82
 
83
83
  if self.request_body_optional:
84
84
  if self.request_body_many:
85
- class BodyTypingOptList(BaseModel):
86
- __root__: Optional[List[body_typing]] = None # type: ignore
85
+ class BodyTypingOptList(RootModel[List[body_typing] | None]): # type: ignore
86
+ pass
87
87
 
88
88
  return BodyTypingOptList
89
89
 
90
- class BodyTypingOpt(BaseModel):
91
- __root__: Optional[body_typing] = None # type: ignore
90
+ class BodyTypingOpt(RootModel[Optional[body_typing]]): # type: ignore
91
+ pass
92
92
  return BodyTypingOpt
93
93
 
94
94
  if self.request_body_many:
95
- class BodyTypingList(BaseModel):
96
- __root__: List[body_typing] # type: ignore
95
+ class BodyTypingList(RootModel[List[body_typing]]): # type: ignore
96
+ pass
97
97
 
98
98
  return BodyTypingList
99
99
  return body_typing
@@ -165,8 +165,7 @@ class ApiResourceFnTyping(NamedTuple):
165
165
  return kwargs, errors
166
166
  body = self._get_body()
167
167
  try:
168
- expected_typing: Type[BaseModel | List[BaseModel]] = List[self.body_typing] \
169
- if self.request_body_many else self.body_typing
168
+ expected_typing: Type[BaseModel | List[BaseModel]] = List[self.body_typing] if self.request_body_many else self.body_typing # type: ignore
170
169
  kwargs['body'] = TypeAdapter(expected_typing).validate_python(body)
171
170
  except ValidationError as ve:
172
171
  if self.has_body_validation_error:
@@ -1,6 +1,5 @@
1
1
  import io
2
2
  from datetime import datetime
3
- from types import NoneType
4
3
  from typing import TypeVar, Generic, List, Optional, Dict, Any, Callable, Union, BinaryIO, Tuple, Type
5
4
 
6
5
  import msgpack
@@ -64,8 +63,8 @@ class HtmlApiResponse(ApiResponse):
64
63
 
65
64
  @classmethod
66
65
  def _internal_use__mk_schema(cls, inner_type: Optional[Type[BaseModel]]) -> Type[BaseModel]:
67
- class _ResponseNoneType(BaseModel):
68
- __root__: NoneType # type: ignore
66
+ class _ResponseNoneType(RootModel[None]):
67
+ pass
69
68
  return _ResponseNoneType
70
69
 
71
70
  def to_flask_response(self, debugger: Optional[Debugger] = None) -> BaseResponse:
@@ -100,8 +99,8 @@ class FileApiResponse(ApiResponse):
100
99
 
101
100
  @classmethod
102
101
  def _internal_use__mk_schema(cls, inner_type: Optional[Type[BaseModel]]) -> Type[BaseModel]:
103
- class _ResponseNoneType(BaseModel):
104
- __root__: NoneType # type: ignore
102
+ class _ResponseNoneType(RootModel[None]):
103
+ pass
105
104
  return _ResponseNoneType
106
105
 
107
106
  def to_flask_response(self, debugger: Optional[Debugger] = None) -> BaseResponse:
@@ -125,8 +124,8 @@ class EmptyJsonApiResponse(ApiResponse):
125
124
 
126
125
  @classmethod
127
126
  def _internal_use__mk_schema(cls, inner_type: Optional[Type[BaseModel]]) -> Type[BaseModel]:
128
- class _ResponseNoneType(BaseModel):
129
- __root__: NoneType # type: ignore
127
+ class _ResponseNoneType(RootModel[None]):
128
+ pass
130
129
  return _ResponseNoneType
131
130
 
132
131
  def to_flask_response(self, debugger: Optional[Debugger] = None) -> BaseResponse:
@@ -162,7 +161,7 @@ class DictJsonApiResponsePayload(RootJsonApiResponsePayload[Dict[str, Any]]):
162
161
  pass
163
162
 
164
163
 
165
- TProxyPayload = TypeVar('TProxyPayload', bound=Union[JsonApiResponsePayload, List[JsonApiResponsePayload], None])
164
+ TProxyPayload = TypeVar('TProxyPayload', bound=Union[JsonApiResponsePayload, List[JsonApiResponsePayload], RootJsonApiResponsePayload[Any], List[RootJsonApiResponsePayload[Any]], None])
166
165
 
167
166
 
168
167
  class ProxyJsonApiResponse(Generic[TProxyPayload], EmptyJsonApiResponse):
@@ -193,8 +192,8 @@ class RootJsonApiResponse(Generic[TJsonObjApiResponsePayload], EmptyJsonApiRespo
193
192
 
194
193
  @classmethod
195
194
  def _internal_use__mk_schema(cls, inner_type: Optional[Type[BaseModel]]) -> Type[BaseModel]:
196
- class _ResponseStd(BaseModel):
197
- __root__: inner_type # type: ignore
195
+ class _ResponseStd(RootModel[inner_type]): # type: ignore
196
+ pass
198
197
  return _ResponseStd
199
198
 
200
199
  def to_flask_response(self, debugger: Optional[Debugger] = None) -> BaseResponse:
@@ -79,7 +79,7 @@ class Debugger:
79
79
  return
80
80
 
81
81
  started_at = stat.get_request_started_at()
82
- ended_at = time.time()
82
+ ended_at = time.perf_counter()
83
83
  name = self.name
84
84
 
85
85
  stats = stat.get_stat(code_spans=True, started_at=started_at, ended_at=ended_at)
@@ -90,7 +90,7 @@ class Debugger:
90
90
  def render_dict(self, status_code: int) -> Dict[str, Any]:
91
91
  if not collecting_enabled():
92
92
  return {}
93
- stats = [st.unwrap() for st in stat.get_stat(started_at=stat.get_request_started_at(), ended_at=time.time())]
93
+ stats = [st.unwrap() for st in stat.get_stat(started_at=stat.get_request_started_at(), ended_at=time.perf_counter())]
94
94
 
95
95
  return {
96
96
  RESPONSE_PROP_DEBUG_STATS: stats,
@@ -107,7 +107,7 @@ class Debugger:
107
107
  return script
108
108
 
109
109
  started_at = stat.get_request_started_at()
110
- ended_at = time.time()
110
+ ended_at = time.perf_counter()
111
111
 
112
112
  stats = stat.get_stat(code_spans=True, started_at=started_at, ended_at=ended_at)
113
113
 
@@ -18,7 +18,7 @@ INDENT = ' '
18
18
 
19
19
 
20
20
  def time_now() -> float:
21
- return time.time()
21
+ return time.perf_counter()
22
22
 
23
23
 
24
24
  def mark_request_started() -> None:
@@ -439,11 +439,6 @@ def mk_stat_string(
439
439
  header = f'{IND}{" " * (i_size * 2 + 2)}{IND}Request{" " * MAX_LEN_OF_TYPE}{INDENT * max_lvl}{col_duration(started_at, ended_at, cm=cm)}{INDENT}{INDENT}{total_str}'
440
440
 
441
441
  short_identifier = uuid.uuid4().hex[:8]
442
- started_dt = datetime.fromtimestamp(started_at)
443
- ended_dt = datetime.fromtimestamp(ended_at)
444
- date_str = f'{cm[C_FG_GRAY]}{started_dt.date().isoformat()}{cm[C_NC]}' \
445
- f'{IND}{started_dt.strftime("%H:%M:%S")}{cm[C_FG_GRAY]}.{started_dt.microsecond // 1000:0<3}{cm[C_NC]}' \
446
- f' - {ended_dt.strftime("%H:%M:%S")}{cm[C_FG_GRAY]}.{ended_dt.microsecond // 1000:0<3}{cm[C_NC]}'
447
- return f'▀▀▀▀▀ {name} {cm[C_FG_GRAY]}{short_identifier}{cm[C_NC]}{IND}{date_str} {"▀" * 55}' \
442
+ return f'▀▀▀▀▀ {name} {cm[C_FG_GRAY]}{short_identifier}{cm[C_NC]}{IND}{"▀" * 55}' \
448
443
  f'\n{header}\n' \
449
444
  f'{result_s}▄▄▄▄▄ {name} {cm[C_FG_GRAY]}{short_identifier}{cm[C_NC]} {"▄" * 96}'
@@ -278,7 +278,7 @@ class InternalApi:
278
278
  ):
279
279
  return self._override[path][method](has_std_schema)
280
280
 
281
- started_at = time.time()
281
+ started_at = time.perf_counter()
282
282
  q = ApiPathVersion.cleanup_q(q)
283
283
  url = f'{self._entry_point.rstrip("/")}{path}'
284
284
 
@@ -7,7 +7,7 @@ from typing import Any, Optional, List, Generic, Type, TypeVar, Union, Dict, Nam
7
7
  import requests
8
8
  from pydantic import ValidationError
9
9
 
10
- from ul_api_utils.api_resource.api_response import JsonApiResponsePayload
10
+ from ul_api_utils.api_resource.api_response import JsonApiResponsePayload, RootJsonApiResponsePayload
11
11
  from ul_api_utils.api_resource.signature_check import set_model
12
12
  from ul_api_utils.const import RESPONSE_PROP_DEBUG_STATS, RESPONSE_PROP_PAYLOAD, RESPONSE_PROP_OK, RESPONSE_PROP_ERRORS, RESPONSE_PROP_TOTAL, RESPONSE_PROP_COUNT, MIME__JSON, \
13
13
  RESPONSE_HEADER__CONTENT_TYPE
@@ -18,7 +18,7 @@ from ul_api_utils.utils.api_format import ApiFormat
18
18
  from ul_api_utils.internal_api.internal_api_error import InternalApiResponseErrorObj
19
19
  from ul_api_utils.utils.unwrap_typing import UnwrappedOptionalObjOrListOfObj
20
20
 
21
- TPyloadType = TypeVar('TPyloadType', bound=Union[JsonApiResponsePayload, List[JsonApiResponsePayload], None])
21
+ TPyloadType = TypeVar('TPyloadType', bound=Union[JsonApiResponsePayload, List[JsonApiResponsePayload], RootJsonApiResponsePayload[Any], List[RootJsonApiResponsePayload[Any]], None])
22
22
  TResp = TypeVar('TResp', bound='InternalApiResponse[Any]')
23
23
 
24
24
 
@@ -73,17 +73,17 @@ class InternalApiResponse(Generic[TPyloadType]):
73
73
 
74
74
  self._parsed_schema: Optional[InternalApiResponseSchema] = None
75
75
  self._parsed_json: Any = None
76
- self._parsed_payload: Union[None, JsonApiResponsePayload, List[JsonApiResponsePayload]] = None
76
+ self._parsed_payload: Union[None, JsonApiResponsePayload, List[JsonApiResponsePayload], RootJsonApiResponsePayload[Any], List[RootJsonApiResponsePayload[Any]]] = None
77
77
 
78
78
  self._status_checked = False
79
79
  self._info = info
80
80
 
81
81
  if payload_type is not None:
82
- self._payload_type = UnwrappedOptionalObjOrListOfObj.parse(payload_type, JsonApiResponsePayload)
82
+ self._payload_type = UnwrappedOptionalObjOrListOfObj.parse(payload_type, JsonApiResponsePayload) or UnwrappedOptionalObjOrListOfObj.parse(payload_type, RootJsonApiResponsePayload)
83
83
  if self._payload_type is None:
84
- tn = JsonApiResponsePayload.__name__
84
+ tn1, tn2 = JsonApiResponsePayload.__name__, RootJsonApiResponsePayload.__name__
85
85
  raise ValueError(
86
- f'payload_typing is invalid. must be Union[Type[{tn}], Optional[Type[{tn}]], List[Type[{tn}]], Optional[List[Type[{tn}]]]]. {payload_type} was given',
86
+ f'payload_typing is invalid. must be Union[Type[{tn1} | {tn2}], Optional[Type[{tn1} | {tn2}]], List[Type[{tn1} | {tn2}]], Optional[List[Type[{tn1} | {tn2}]]]]. {payload_type} was given',
87
87
  )
88
88
 
89
89
  def typed(self, payload_type: Type[TPyloadType]) -> 'InternalApiResponse[TPyloadType]':
@@ -117,7 +117,7 @@ class ApiSdk:
117
117
 
118
118
  self._config = config
119
119
  self._routes_loaded = False
120
- self._request_started_at = time.time()
120
+ self._request_started_at = time.perf_counter()
121
121
  self._initialized_flask_name: Optional[str] = None
122
122
  self._flask_app_cache: Optional[Flask] = None
123
123
  self._limiter_enabled = False
@@ -68,8 +68,8 @@ class SwaggerSchema(SwaggerModel):
68
68
  if '$ref' in json.dumps(json_schema):
69
69
  while '$ref' in json.dumps(json_schema):
70
70
  json_schema = replace_value_in_dict(json_schema.copy(), json_schema.copy())
71
- if 'definitions' in json_schema:
72
- del json_schema['definitions']
71
+ if '$defs' in json_schema:
72
+ del json_schema['$defs']
73
73
  self.type = json_schema['type']
74
74
  if json_schema['type'] == 'object':
75
75
  self.properties = json_schema.get('properties', dict())
@@ -508,17 +508,29 @@ class SwaggerPath(SwaggerModel):
508
508
  swagger_request_type.add_swagger_model(parameters)
509
509
  query_schema = query_model.model_json_schema()
510
510
  query_required_fields = set(query_schema.get('required')) if query_schema.get('required') is not None else set()
511
- query_definitions = query_schema.get('definitions')
511
+ query_definitions = query_schema.get('$defs')
512
512
  for parameter_name, parameter_spec in query_schema.get('properties').items():
513
- if parameter_spec.get('type') is not None:
514
- parameter_models.add(SwaggerQueryParameter(
515
- input_type=parameter_spec.get('type'),
516
- input_format=parameter_spec.get('format'),
517
- default_value=parameter_spec.get('default'),
518
- name=parameter_name,
519
- description=parameter_spec.get('description'),
520
- required=parameter_name in query_required_fields,
521
- ))
513
+ if 'anyOf' in parameter_spec.keys():
514
+ if parameter_spec.get('anyOf')[0].get('$ref') is not None:
515
+ definition = query_definitions.get(parameter_spec.get('$ref', '').split('/')[-1], {})
516
+ parameter_models.add(SwaggerQueryParameter(
517
+ input_type=definition.get('type', 'string'),
518
+ input_format=definition.get('format'),
519
+ default_value=definition.get('default'),
520
+ name=parameter_name,
521
+ description=definition.get('description'),
522
+ enum=definition.get('enum'),
523
+ required=parameter_name in query_required_fields,
524
+ ))
525
+ if parameter_spec.get('anyOf')[0].get('type') is not None:
526
+ parameter_models.add(SwaggerQueryParameter(
527
+ input_type=parameter_spec.get('anyOf')[0].get('type'),
528
+ input_format=parameter_spec.get('format'),
529
+ default_value=parameter_spec.get('default'),
530
+ name=parameter_name,
531
+ description=parameter_spec.get('description'),
532
+ required=parameter_name in query_required_fields,
533
+ ))
522
534
  elif parameter_spec.get('$ref') is not None:
523
535
  definition = query_definitions.get(parameter_spec.get('$ref', '').split('/')[-1], {})
524
536
  parameter_models.add(SwaggerQueryParameter(
@@ -530,6 +542,15 @@ class SwaggerPath(SwaggerModel):
530
542
  enum=definition.get('enum'),
531
543
  required=parameter_name in query_required_fields,
532
544
  ))
545
+ elif parameter_spec.get('type') is not None:
546
+ parameter_models.add(SwaggerQueryParameter(
547
+ input_type=parameter_spec.get('type') or parameter_spec.get('anyOf')[0].get('type'),
548
+ input_format=parameter_spec.get('format'),
549
+ default_value=parameter_spec.get('default'),
550
+ name=parameter_name,
551
+ description=parameter_spec.get('description'),
552
+ required=parameter_name in query_required_fields,
553
+ ))
533
554
  if isinstance(parameters, SwaggerModel):
534
555
  parameters.add_swagger_models(parameter_models)
535
556
  elif isinstance(parameters, list):
@@ -5,7 +5,7 @@ def replace_value_in_dict(item: Union[List, Dict], original_schema): # type: ig
5
5
  if isinstance(item, list):
6
6
  return [replace_value_in_dict(i, original_schema) for i in item]
7
7
  elif isinstance(item, dict):
8
- if list(item.keys()) == ['$ref']:
8
+ if '$ref' in list(item.keys()):
9
9
  definitions = item['$ref'][2:].split('/')
10
10
  res = original_schema.copy()
11
11
  for definition in definitions:
@@ -2,7 +2,7 @@ import dataclasses
2
2
  import decimal
3
3
  import json
4
4
  from base64 import b64encode
5
- from datetime import date, datetime
5
+ from datetime import date, datetime, time
6
6
  from enum import Enum
7
7
  from json import JSONEncoder
8
8
  from typing import Dict, Any, Union, List, Optional, TYPE_CHECKING
@@ -66,6 +66,8 @@ class CustomJSONEncoder(JSONEncoder):
66
66
  return str(obj.isoformat())
67
67
  if isinstance(obj, date):
68
68
  return str(obj.isoformat())
69
+ if isinstance(obj, time):
70
+ return str(obj.isoformat())
69
71
  if isinstance(obj, UUID):
70
72
  return str(obj)
71
73
  if isinstance(obj, Enum):
@@ -2,6 +2,8 @@ from dataclasses import dataclass
2
2
  from types import NoneType
3
3
  from typing import NamedTuple, Type, Tuple, _GenericAlias, _UnionGenericAlias, Any, Union, Optional, TypeVar, Generic, Callable # type: ignore
4
4
 
5
+ from ul_api_utils.api_resource.api_response import RootJsonApiResponsePayload
6
+
5
7
 
6
8
  class TypingType(NamedTuple):
7
9
  value: Type[Any]
@@ -47,6 +49,7 @@ def unwrap_typing(t: Type[Any]) -> Union[TypingGeneric, TypingUnion, TypingType,
47
49
 
48
50
  T = TypeVar('T')
49
51
  TVal = TypeVar('TVal')
52
+ TValRoot = TypeVar('TValRoot', bound=RootJsonApiResponsePayload[Any])
50
53
 
51
54
 
52
55
  def default_constructor(value_type: Type[TVal], data: Any) -> TVal:
@@ -72,7 +75,7 @@ class UnwrappedOptionalObjOrListOfObj(Generic[TVal]):
72
75
  return constructor(self.value_type, payload) # type: ignore
73
76
 
74
77
  @staticmethod
75
- def parse(t: Type[Any], type_constraint: Optional[Type[TVal]]) -> 'Optional[UnwrappedOptionalObjOrListOfObj[TVal]]':
78
+ def parse(t: Type[Any], type_constraint: Optional[Type[TVal | TValRoot]]) -> 'Optional[UnwrappedOptionalObjOrListOfObj[TVal]]':
76
79
  unwrapped_t = unwrap_typing(t)
77
80
 
78
81
  many = False
@@ -0,0 +1,32 @@
1
+ import pytest
2
+
3
+ from pydantic import BaseModel
4
+ from typing import Union, List
5
+ from ul_api_utils.validators.custom_fields import QueryParamsSeparatedList
6
+
7
+
8
+ class ModelStr(BaseModel):
9
+ param: QueryParamsSeparatedList[str]
10
+
11
+
12
+ class ModelInt(BaseModel):
13
+ param: QueryParamsSeparatedList[int]
14
+
15
+
16
+ @pytest.mark.parametrize(
17
+ "model, input_data, expected_output",
18
+ [
19
+ pytest.param(ModelStr, "first_array_element,second,third,this", ["first_array_element", "second", "third", "this"]),
20
+ pytest.param(ModelStr, ["first_array_element,second,third,this"], ["first_array_element", "second", "third", "this"]),
21
+ pytest.param(ModelInt, ["1,2,3,4,5"], [1, 2, 3, 4, 5]),
22
+ pytest.param(ModelStr, 'first_array_element,"second,third",this', ["first_array_element", "second,third", "this"]),
23
+ pytest.param(ModelStr, ['first_array_element,"second,third",this'], ["first_array_element", "second,third", "this"]),
24
+ pytest.param(ModelStr, '"first_array_element,second,third",this, "1,2"', ["first_array_element,second,third", "this", "1,2"]),
25
+ ],
26
+ )
27
+ def test__query_params_separated_list(
28
+ model: Union[ModelStr, ModelInt], input_data: Union[List[str], str], expected_output: List[Union[str, int]],
29
+ ) -> None:
30
+ instance = model(param=input_data) # type: ignore
31
+ assert isinstance(instance.param, list)
32
+ assert instance.param == expected_output
@@ -0,0 +1,66 @@
1
+ import csv
2
+ from typing import TypeVar, Generic, List, Union, Annotated, Any, get_args
3
+ from uuid import UUID
4
+
5
+ from pydantic import Field, StringConstraints, BeforeValidator
6
+
7
+ from ul_api_utils.const import CRON_EXPRESSION_VALIDATION_REGEX, MIN_UTC_OFFSET_SECONDS, MAX_UTC_OFFSET_SECONDS
8
+
9
+ NotEmptyListAnnotation = Annotated[list[Any], Field(min_length=1)]
10
+ NotEmptyListStrAnnotation = Annotated[list[str], Field(min_length=1)]
11
+ NotEmptyListIntAnnotation = Annotated[list[int], Field(min_length=1)]
12
+ NotEmptyListUUIDAnnotation = Annotated[list[UUID], Field(min_length=1)]
13
+ CronScheduleAnnotation = Annotated[str, StringConstraints(pattern=CRON_EXPRESSION_VALIDATION_REGEX)]
14
+ WhiteSpaceStrippedStrAnnotation = Annotated[str, StringConstraints(strip_whitespace=True)]
15
+ UTCOffsetSecondsAnnotation = Annotated[int, Field(ge=MIN_UTC_OFFSET_SECONDS, le=MAX_UTC_OFFSET_SECONDS)]
16
+ PgTypePasswordStrAnnotation = Annotated[str, StringConstraints(min_length=6, max_length=72)]
17
+ PgTypeShortStrAnnotation = Annotated[str, StringConstraints(min_length=0, max_length=255)]
18
+ PgTypeLongStrAnnotation = Annotated[str, StringConstraints(min_length=0, max_length=1000)]
19
+ PgTypeInt16Annotation = Annotated[int, Field(ge=-32768, le=32768)]
20
+ PgTypePositiveInt16Annotation = Annotated[int, Field(ge=0, le=32768)]
21
+ PgTypeInt32Annotation = Annotated[int, Field(ge=-2147483648, le=2147483648)]
22
+ PgTypePositiveInt32Annotation = Annotated[int, Field(ge=0, le=2147483648)]
23
+ PgTypeInt64Annotation = Annotated[int, Field(ge=-9223372036854775808, le=9223372036854775808)]
24
+ PgTypePositiveInt64Annotation = Annotated[int, Field(ge=0, le=9223372036854775808)]
25
+
26
+
27
+ QueryParamsSeparatedListValueType = TypeVar('QueryParamsSeparatedListValueType')
28
+
29
+
30
+ def validate_query_params(value: Union[str, List[str]], type_: type) -> List[QueryParamsSeparatedListValueType]:
31
+ def process_item(item: str) -> QueryParamsSeparatedListValueType:
32
+ return type_(item.strip())
33
+
34
+ if isinstance(value, list):
35
+ result: list[QueryParamsSeparatedListValueType] = []
36
+ for item in value:
37
+ if isinstance(item, str):
38
+ reader = csv.reader([item], skipinitialspace=True)
39
+ result.extend(process_item(sub_item) for row in reader for sub_item in row)
40
+ else:
41
+ raise ValueError("List items must be strings")
42
+ elif isinstance(value, str):
43
+ reader = csv.reader([value], skipinitialspace=True)
44
+ result = [process_item(item) for row in reader for item in row]
45
+ else:
46
+ raise ValueError("Value must be a string or a list of strings")
47
+
48
+ return result
49
+
50
+
51
+ class QueryParamsSeparatedList(Generic[QueryParamsSeparatedListValueType]):
52
+ def __init__(self, value: Union[str, List[str]]):
53
+ self._values: List[QueryParamsSeparatedListValueType] = validate_query_params(value)
54
+
55
+ def to_list(self) -> List[QueryParamsSeparatedListValueType]:
56
+ return list(self._values)
57
+
58
+ @classmethod
59
+ def __get_pydantic_core_schema__(cls, source_type: type, handler: Any) -> Any:
60
+ inner_type = get_args(source_type)[0]
61
+ return handler(
62
+ Annotated[
63
+ List[inner_type], # type: ignore
64
+ BeforeValidator(lambda x: validate_query_params(x, inner_type))
65
+ ]
66
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ul-api-utils
3
- Version: 9.0.0a6
3
+ Version: 9.0.0a8
4
4
  Summary: Python api utils
5
5
  Author: Unic-lab
6
6
  Author-email:
@@ -1,32 +0,0 @@
1
- # import pytest
2
- #
3
- # from pydantic import BaseModel
4
- # from typing import Union, List
5
- # from ul_api_utils.validators.custom_fields import QueryParamsSeparatedList
6
- #
7
- #
8
- # class ModelStr(BaseModel):
9
- # param: QueryParamsSeparatedList[str]
10
- #
11
- #
12
- # class ModelInt(BaseModel):
13
- # param: QueryParamsSeparatedList[int]
14
- #
15
- #
16
- # @pytest.mark.parametrize(
17
- # "model, input_data, expected_output",
18
- # [
19
- # pytest.param(ModelStr, "first_array_element,second,third,this", ["first_array_element", "second", "third", "this"]),
20
- # pytest.param(ModelStr, ["first_array_element,second,third,this"], ["first_array_element", "second", "third", "this"]),
21
- # pytest.param(ModelInt, ["1,2,3,4,5"], [1, 2, 3, 4, 5]),
22
- # pytest.param(ModelStr, 'first_array_element,"second,third",this', ["first_array_element", "second,third", "this"]),
23
- # pytest.param(ModelStr, ['first_array_element,"second,third",this'], ["first_array_element", "second,third", "this"]),
24
- # pytest.param(ModelStr, '"first_array_element,second,third",this, "1,2"', ["first_array_element,second,third", "this", "1,2"]),
25
- # ],
26
- # )
27
- # def test__query_params_separated_list(
28
- # model: Union[ModelStr, ModelInt], input_data: Union[List[str], str], expected_output: List[Union[str, int]],
29
- # ) -> None:
30
- # instance = model(param=input_data) # type: ignore
31
- # assert isinstance(instance.param, list)
32
- # assert instance.param == expected_output
@@ -1,84 +0,0 @@
1
- import csv
2
- from typing import TypeVar, Generic, List, Union, Generator, Callable, Annotated, Any
3
- from uuid import UUID
4
-
5
- from pydantic import Field, StringConstraints, TypeAdapter
6
- from pydantic_core import ValidationError, InitErrorDetails
7
- from pydantic_core.core_schema import ValidationInfo
8
-
9
- from ul_api_utils.const import CRON_EXPRESSION_VALIDATION_REGEX, MIN_UTC_OFFSET_SECONDS, MAX_UTC_OFFSET_SECONDS
10
-
11
- NotEmptyListAnnotation = Annotated[list[Any], Field(min_length=1)]
12
- NotEmptyListStrAnnotation = Annotated[list[str], Field(min_length=1)]
13
- NotEmptyListIntAnnotation = Annotated[list[int], Field(min_length=1)]
14
- NotEmptyListUUIDAnnotation = Annotated[list[UUID], Field(min_length=1)]
15
- CronScheduleAnnotation = Annotated[str, StringConstraints(pattern=CRON_EXPRESSION_VALIDATION_REGEX)]
16
- WhiteSpaceStrippedStrAnnotation = Annotated[str, StringConstraints(strip_whitespace=True)]
17
- UTCOffsetSecondsAnnotation = Annotated[int, Field(ge=MIN_UTC_OFFSET_SECONDS, le=MAX_UTC_OFFSET_SECONDS)]
18
- PgTypePasswordStrAnnotation = Annotated[str, StringConstraints(min_length=6, max_length=72)]
19
- PgTypeShortStrAnnotation = Annotated[str, StringConstraints(min_length=0, max_length=255)]
20
- PgTypeLongStrAnnotation = Annotated[str, StringConstraints(min_length=0, max_length=1000)]
21
- PgTypeInt16Annotation = Annotated[int, Field(ge=-32768, le=32768)]
22
- PgTypePositiveInt16Annotation = Annotated[int, Field(ge=0, le=32768)]
23
- PgTypeInt32Annotation = Annotated[int, Field(ge=-2147483648, le=2147483648)]
24
- PgTypePositiveInt32Annotation = Annotated[int, Field(ge=0, le=2147483648)]
25
- PgTypeInt64Annotation = Annotated[int, Field(ge=-9223372036854775808, le=9223372036854775808)]
26
- PgTypePositiveInt64Annotation = Annotated[int, Field(ge=0, le=9223372036854775808)]
27
-
28
-
29
- QueryParamsSeparatedListValueType = TypeVar('QueryParamsSeparatedListValueType')
30
-
31
-
32
- class QueryParamsSeparatedList(Generic[QueryParamsSeparatedListValueType]):
33
- """
34
- Supports cases when query parameters are being sent as a string, but you have to assume
35
- that it is a list.
36
-
37
- F.E. Query string is ?foo=1,2
38
-
39
- Note:
40
- Sent as a string, but interpreted as List.
41
- """
42
- _contains_type: Any = None
43
-
44
- @classmethod
45
- def __class_getitem__(cls, item: Any) -> QueryParamsSeparatedListValueType:
46
- new_cls = super().__class_getitem__(item) # type: ignore
47
- new_cls._contains_type = item
48
- return new_cls
49
-
50
- @classmethod
51
- def __get_validators__(cls) -> Generator[Callable[[Union[List[str], str], ValidationInfo], List[QueryParamsSeparatedListValueType]], None, None]:
52
- yield cls.validate
53
-
54
- @classmethod
55
- def validate(cls, query_param: Union[List[str], str], info: ValidationInfo) -> List[QueryParamsSeparatedListValueType]:
56
- """
57
- Validate and convert the query parameter string into a list of the specified type.
58
- """
59
- if cls._contains_type is None:
60
- raise TypeError("QueryParamsSeparatedList must be parameterized with a type, e.g., QueryParamsSeparatedList[int]")
61
-
62
- adapter = TypeAdapter(cls._contains_type)
63
-
64
- if not isinstance(query_param, list):
65
- query_param = [query_param]
66
-
67
- reader = csv.reader(query_param, skipinitialspace=True)
68
- splitted = next(reader)
69
-
70
- validated_items = []
71
- errors: List[InitErrorDetails] = []
72
-
73
- for idx, value in enumerate(splitted):
74
- try:
75
- validated_items.append(adapter.validate_python(value))
76
- except ValidationError as e:
77
- for error in e.errors(include_url=False):
78
- error['loc'] = ('param', idx)
79
- errors.append(error) # type: ignore
80
-
81
- if errors:
82
- raise ValidationError.from_exception_data("List validation error", errors)
83
-
84
- return validated_items
File without changes
File without changes