gooddata-pipelines 1.65.1.dev2__tar.gz → 1.65.1.dev3__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 (160) hide show
  1. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/PKG-INFO +2 -2
  2. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/pyproject.toml +2 -2
  3. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/__init__.py +2 -0
  4. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/ldm_extension/input_processor.py +76 -25
  5. gooddata_pipelines-1.65.1.dev3/src/gooddata_pipelines/ldm_extension/models/custom_data_object.py +184 -0
  6. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/test_ldm_extension/test_input_processor.py +49 -0
  7. gooddata_pipelines-1.65.1.dev3/tests/test_ldm_extension/test_models/test_custom_data_object.py +208 -0
  8. gooddata_pipelines-1.65.1.dev2/src/gooddata_pipelines/ldm_extension/models/custom_data_object.py +0 -106
  9. gooddata_pipelines-1.65.1.dev2/tests/test_ldm_extension/test_models/test_custom_data_object.py +0 -102
  10. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/.gitignore +0 -0
  11. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/LICENSE.txt +0 -0
  12. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/Makefile +0 -0
  13. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/README.md +0 -0
  14. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/_version.py +0 -0
  15. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/api/__init__.py +0 -0
  16. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/api/gooddata_api.py +0 -0
  17. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/api/gooddata_api_wrapper.py +0 -0
  18. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/__init__.py +0 -0
  19. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/backup_input_processor.py +0 -0
  20. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/backup_manager.py +0 -0
  21. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/base_manager.py +0 -0
  22. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/constants.py +0 -0
  23. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/csv_reader.py +0 -0
  24. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/models/__init__.py +0 -0
  25. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/models/input_type.py +0 -0
  26. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/models/storage.py +0 -0
  27. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/models/workspace_response.py +0 -0
  28. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/restore_manager.py +0 -0
  29. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/storage/__init__.py +0 -0
  30. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/storage/azure_storage.py +0 -0
  31. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/storage/base_storage.py +0 -0
  32. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/storage/local_storage.py +0 -0
  33. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/backup_and_restore/storage/s3_storage.py +0 -0
  34. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/ldm_extension/__init__.py +0 -0
  35. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/ldm_extension/input_validator.py +0 -0
  36. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/ldm_extension/ldm_extension_manager.py +0 -0
  37. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/ldm_extension/models/__init__.py +0 -0
  38. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/ldm_extension/models/aliases.py +0 -0
  39. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/ldm_extension/models/analytical_object.py +0 -0
  40. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/logger/__init__.py +0 -0
  41. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/logger/logger.py +0 -0
  42. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/__init__.py +0 -0
  43. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/assets/wdf_setting.json +0 -0
  44. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/__init__.py +0 -0
  45. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/user_data_filters/__init__.py +0 -0
  46. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/user_data_filters/models/__init__.py +0 -0
  47. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/user_data_filters/models/udf_models.py +0 -0
  48. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/user_data_filters/user_data_filters.py +0 -0
  49. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/users/__init__.py +0 -0
  50. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/users/models/__init__.py +0 -0
  51. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/users/models/permissions.py +0 -0
  52. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/users/models/user_groups.py +0 -0
  53. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/users/models/users.py +0 -0
  54. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/users/permissions.py +0 -0
  55. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/users/user_groups.py +0 -0
  56. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/users/users.py +0 -0
  57. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/workspaces/__init__.py +0 -0
  58. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/workspaces/models.py +0 -0
  59. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/workspaces/workspace.py +0 -0
  60. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/workspaces/workspace_data_filters.py +0 -0
  61. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/workspaces/workspace_data_parser.py +0 -0
  62. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/entities/workspaces/workspace_data_validator.py +0 -0
  63. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/generic/__init__.py +0 -0
  64. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/generic/config.py +0 -0
  65. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/generic/provision.py +0 -0
  66. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/provisioning.py +0 -0
  67. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/utils/__init__.py +0 -0
  68. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/utils/context_objects.py +0 -0
  69. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/utils/exceptions.py +0 -0
  70. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/provisioning/utils/utils.py +0 -0
  71. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/py.typed +0 -0
  72. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/utils/__init__.py +0 -0
  73. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/utils/decorators.py +0 -0
  74. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/utils/file_utils.py +0 -0
  75. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/src/gooddata_pipelines/utils/rate_limiter.py +0 -0
  76. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/__init__.py +0 -0
  77. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/backup_and_restore/__init__.py +0 -0
  78. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/backup_and_restore/test_backup.py +0 -0
  79. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/backup_and_restore/test_backup_input_processor.py +0 -0
  80. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/backup_and_restore/test_restore.py +0 -0
  81. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/conftest.py +0 -0
  82. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/__init__.py +0 -0
  83. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/__init__.py +0 -0
  84. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_conf.yaml +0 -0
  85. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid1/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid1/analytics_model/analytical_dashboard_extensions/.gitkeep +0 -0
  86. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid1/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid1/analytics_model/analytical_dashboards/.gitkeep +0 -0
  87. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid1/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid1/analytics_model/dashboard_plugins/.gitkeep +0 -0
  88. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid1/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid1/analytics_model/filter_contexts/.gitkeep +0 -0
  89. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid1/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid1/analytics_model/metrics/.gitkeep +0 -0
  90. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid1/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid1/analytics_model/visualization_objects/.gitkeep +0 -0
  91. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid1/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid1/ldm/datasets/test.yaml +0 -0
  92. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid1/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid1/ldm/date_instances/testinstance.yaml +0 -0
  93. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid2/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid2/analytics_model/analytical_dashboard_extensions/.gitkeep +0 -0
  94. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid2/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid2/analytics_model/analytical_dashboards/id.yaml +0 -0
  95. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid2/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid2/analytics_model/dashboard_plugins/.gitkeep +0 -0
  96. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid2/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid2/analytics_model/filter_contexts/id.yaml +0 -0
  97. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid2/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid2/analytics_model/metrics/.gitkeep +0 -0
  98. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid2/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid2/analytics_model/visualization_objects/test.yaml +0 -0
  99. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid2/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid2/ldm/datasets/.gitkeep +0 -0
  100. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid2/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid2/ldm/date_instances/.gitkeep +0 -0
  101. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid3/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid3/analytics_model/analytical_dashboard_extensions/.gitkeep +0 -0
  102. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid3/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid3/analytics_model/analytical_dashboards/.gitkeep +0 -0
  103. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid3/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid3/analytics_model/dashboard_plugins/.gitkeep +0 -0
  104. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid3/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid3/analytics_model/filter_contexts/.gitkeep +0 -0
  105. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid3/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid3/analytics_model/metrics/.gitkeep +0 -0
  106. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid3/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid3/analytics_model/visualization_objects/.gitkeep +0 -0
  107. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid3/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid3/ldm/datasets/.gitkeep +0 -0
  108. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid3/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid3/ldm/date_instances/.gitkeep +0 -0
  109. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_exports/services/wsid3/20230713-132759-1_3_1_dev5/gooddata_layouts/services/workspaces/wsid3/user_data_filters/.gitkeep +0 -0
  110. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/backup/test_local_conf.yaml +0 -0
  111. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/custom_fields/response_get_all_dashboards.json +0 -0
  112. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/custom_fields/response_get_all_metrics.json +0 -0
  113. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/custom_fields/response_get_all_visualizations.json +0 -0
  114. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/mock_responses.py +0 -0
  115. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/profiles.yaml +0 -0
  116. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/permissions/existing_upstream_permissions.json +0 -0
  117. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/permissions/permissions_expected_full_load.json +0 -0
  118. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/permissions/permissions_expected_incremental_load.json +0 -0
  119. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/permissions/permissions_input_full_load.json +0 -0
  120. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/permissions/permissions_input_incremental_load.json +0 -0
  121. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/users/existing_upstream_users.json +0 -0
  122. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/users/profile_response_content.json +0 -0
  123. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/users/users_expected_full_load.json +0 -0
  124. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/users/users_expected_incremental_load.json +0 -0
  125. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/users/users_input_full_load.json +0 -0
  126. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/users/users_input_full_load_modifies_protected_user.json +0 -0
  127. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/users/users_input_incremental_load.json +0 -0
  128. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/users/users_input_incremental_load_deletes_protected_user.json +0 -0
  129. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/provisioning/entities/users/users_input_incremental_load_modifies_protected_user.json +0 -0
  130. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/restore/test_conf.yaml +0 -0
  131. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/restore/test_udf_root/filter1.yaml +0 -0
  132. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/restore/test_udf_root/filter2.yaml +0 -0
  133. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/restore/test_udf_root/user_data_filters/filter1.yaml +0 -0
  134. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/data/restore/test_udf_root/user_data_filters/filter2.yaml +0 -0
  135. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/panther/__init__.py +0 -0
  136. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/panther/test_api_wrapper.py +0 -0
  137. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/panther/test_sdk_wrapper.py +0 -0
  138. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/provisioning/__init__.py +0 -0
  139. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/provisioning/entities/__init__.py +0 -0
  140. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/provisioning/entities/users/__init__.py +0 -0
  141. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/provisioning/entities/users/test_permissions.py +0 -0
  142. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/provisioning/entities/users/test_user_groups.py +0 -0
  143. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/provisioning/entities/users/test_users.py +0 -0
  144. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/provisioning/entities/workspaces/__init__.py +0 -0
  145. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/provisioning/entities/workspaces/test_provisioning.py +0 -0
  146. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/provisioning/entities/workspaces/test_workspace.py +0 -0
  147. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/provisioning/entities/workspaces/test_workspace_data_filters.py +0 -0
  148. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/provisioning/entities/workspaces/test_workspace_data_parser.py +0 -0
  149. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/provisioning/entities/workspaces/test_workspace_data_validator.py +0 -0
  150. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/provisioning/test_provisioning.py +0 -0
  151. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/test_ldm_extension/__init__.py +0 -0
  152. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/test_ldm_extension/conftest.py +0 -0
  153. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/test_ldm_extension/test_input_validator.py +0 -0
  154. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/test_ldm_extension/test_ldm_extension_manager.py +0 -0
  155. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/test_ldm_extension/test_merge_ldm.py +0 -0
  156. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/test_ldm_extension/test_models/__init__.py +0 -0
  157. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/test_ldm_extension/test_models/test_analytical_object.py +0 -0
  158. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/utils/test_decorators.py +0 -0
  159. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tests/utils/test_rate_limiter.py +0 -0
  160. {gooddata_pipelines-1.65.1.dev2 → gooddata_pipelines-1.65.1.dev3}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gooddata-pipelines
3
- Version: 1.65.1.dev2
3
+ Version: 1.65.1.dev3
4
4
  Summary: GoodData Cloud lifecycle automation pipelines
5
5
  Author-email: GoodData <support@gooddata.com>
6
6
  License-Expression: MIT
@@ -10,7 +10,7 @@ Requires-Dist: azure-identity<2.0.0,>=1.15.0
10
10
  Requires-Dist: azure-storage-blob<13.0.0,>=12.19.0
11
11
  Requires-Dist: boto3-stubs<2.0.0,>=1.39.3
12
12
  Requires-Dist: boto3<2.0.0,>=1.39.3
13
- Requires-Dist: gooddata-sdk~=1.65.1.dev2
13
+ Requires-Dist: gooddata-sdk~=1.65.1.dev3
14
14
  Requires-Dist: pydantic<3.0.0,>=2.9.2
15
15
  Requires-Dist: requests<3.0.0,>=2.32.3
16
16
  Requires-Dist: types-pyyaml<7.0.0,>=6.0.12.20250326
@@ -1,7 +1,7 @@
1
1
  # (C) 2025 GoodData Corporation
2
2
  [project]
3
3
  name = "gooddata-pipelines"
4
- version = "1.65.1.dev2"
4
+ version = "1.65.1.dev3"
5
5
  description = "GoodData Cloud lifecycle automation pipelines"
6
6
  authors = [{ name = "GoodData", email = "support@gooddata.com" }]
7
7
  license = "MIT"
@@ -11,7 +11,7 @@ dependencies = [
11
11
  "pydantic (>=2.9.2,<3.0.0)",
12
12
  "requests (>=2.32.3,<3.0.0)",
13
13
  "types-requests (>1.0.0,<3.0.0)",
14
- "gooddata-sdk~=1.65.1.dev2",
14
+ "gooddata-sdk~=1.65.1.dev3",
15
15
  "boto3 (>=1.39.3,<2.0.0)",
16
16
  "boto3-stubs (>=1.39.3,<2.0.0)",
17
17
  "azure-storage-blob (>=12.19.0,<13.0.0)",
@@ -26,6 +26,7 @@ from .ldm_extension.models.custom_data_object import (
26
26
  CustomDatasetDefinition,
27
27
  CustomFieldDefinition,
28
28
  CustomFieldType,
29
+ ParentDatasetReference,
29
30
  )
30
31
 
31
32
  # -------- Provisioning --------
@@ -93,6 +94,7 @@ __all__ = [
93
94
  "CustomFieldDefinition",
94
95
  "ColumnDataType",
95
96
  "CustomFieldType",
97
+ "ParentDatasetReference",
96
98
  "provision",
97
99
  "WorkflowType",
98
100
  "__version__",
@@ -154,6 +154,46 @@ class LdmExtensionDataProcessor:
154
154
  ],
155
155
  )
156
156
 
157
+ @staticmethod
158
+ def _build_parent_reference_sources(
159
+ definition: CustomDatasetDefinition,
160
+ ) -> list[CatalogDeclarativeReferenceSource]:
161
+ """Build the reference sources from either the new list or the legacy triple."""
162
+ if definition.parent_dataset_references:
163
+ return [
164
+ CatalogDeclarativeReferenceSource(
165
+ column=ref.source_column,
166
+ data_type=ref.data_type.value,
167
+ target=CatalogGrainIdentifier(
168
+ id=ref.attribute_id,
169
+ type=CustomFieldType.ATTRIBUTE.value,
170
+ ),
171
+ )
172
+ for ref in definition.parent_dataset_references
173
+ ]
174
+
175
+ # `check_reference_form` on the model guarantees all three legacy
176
+ # fields are set when `parent_dataset_references` is empty.
177
+ if (
178
+ definition.parent_dataset_reference_attribute_id is None
179
+ or definition.dataset_reference_source_column is None
180
+ or definition.dataset_reference_source_column_data_type is None
181
+ ):
182
+ raise ValueError(
183
+ "Legacy reference fields must be set when "
184
+ "`parent_dataset_references` is not provided."
185
+ )
186
+ return [
187
+ CatalogDeclarativeReferenceSource(
188
+ column=definition.dataset_reference_source_column,
189
+ data_type=definition.dataset_reference_source_column_data_type.value,
190
+ target=CatalogGrainIdentifier(
191
+ id=definition.parent_dataset_reference_attribute_id,
192
+ type=CustomFieldType.ATTRIBUTE.value,
193
+ ),
194
+ )
195
+ ]
196
+
157
197
  @staticmethod
158
198
  def _get_sources(
159
199
  dataset: CustomDataset,
@@ -253,6 +293,39 @@ class LdmExtensionDataProcessor:
253
293
  # Get the data source info
254
294
  dataset_source_table_id, dataset_sql = self._get_sources(dataset)
255
295
 
296
+ parent_reference_sources = self._build_parent_reference_sources(
297
+ dataset.definition
298
+ )
299
+
300
+ wdf_columns: (
301
+ list[CatalogDeclarativeWorkspaceDataFilterColumn] | None
302
+ ) = None
303
+ wdf_references: (
304
+ list[CatalogDeclarativeWorkspaceDataFilterReferences] | None
305
+ ) = None
306
+ wdf_id = dataset.definition.workspace_data_filter_id
307
+ wdf_column_name = (
308
+ dataset.definition.workspace_data_filter_column_name
309
+ )
310
+ # `check_wdf_pair` on the model guarantees both fields are set
311
+ # together or both omitted.
312
+ if wdf_id is not None and wdf_column_name is not None:
313
+ wdf_columns = [
314
+ CatalogDeclarativeWorkspaceDataFilterColumn(
315
+ name=wdf_column_name,
316
+ data_type=ColumnDataType.STRING.value,
317
+ )
318
+ ]
319
+ wdf_references = [
320
+ CatalogDeclarativeWorkspaceDataFilterReferences(
321
+ filter_id=CatalogDatasetWorkspaceDataFilterIdentifier(
322
+ id=wdf_id
323
+ ),
324
+ filter_column=wdf_column_name,
325
+ filter_column_data_type=ColumnDataType.STRING.value,
326
+ )
327
+ ]
328
+
256
329
  # Construct the declarative dataset object and append it to the list.
257
330
  declarative_datasets.append(
258
331
  CatalogDeclarativeDataset(
@@ -265,16 +338,7 @@ class LdmExtensionDataProcessor:
265
338
  id=dataset.definition.parent_dataset_reference,
266
339
  ),
267
340
  multivalue=True,
268
- sources=[
269
- CatalogDeclarativeReferenceSource(
270
- column=dataset.definition.dataset_reference_source_column,
271
- data_type=dataset.definition.dataset_reference_source_column_data_type.value,
272
- target=CatalogGrainIdentifier(
273
- id=dataset.definition.parent_dataset_reference_attribute_id,
274
- type=CustomFieldType.ATTRIBUTE.value,
275
- ),
276
- )
277
- ],
341
+ sources=parent_reference_sources,
278
342
  ),
279
343
  ]
280
344
  + date_references,
@@ -283,21 +347,8 @@ class LdmExtensionDataProcessor:
283
347
  facts=facts,
284
348
  data_source_table_id=dataset_source_table_id,
285
349
  sql=dataset_sql,
286
- workspace_data_filter_columns=[
287
- CatalogDeclarativeWorkspaceDataFilterColumn(
288
- name=dataset.definition.workspace_data_filter_column_name,
289
- data_type=ColumnDataType.STRING.value,
290
- )
291
- ],
292
- workspace_data_filter_references=[
293
- CatalogDeclarativeWorkspaceDataFilterReferences(
294
- filter_id=CatalogDatasetWorkspaceDataFilterIdentifier(
295
- id=dataset.definition.workspace_data_filter_id
296
- ),
297
- filter_column=dataset.definition.workspace_data_filter_column_name,
298
- filter_column_data_type=ColumnDataType.STRING.value,
299
- )
300
- ],
350
+ workspace_data_filter_columns=wdf_columns,
351
+ workspace_data_filter_references=wdf_references,
301
352
  tags=_effective_dataset_tags(dataset.definition),
302
353
  )
303
354
  )
@@ -0,0 +1,184 @@
1
+ # (C) 2025 GoodData Corporation
2
+ """This module defines enums and models used to represent the input data.
3
+
4
+ Models defined here are used to validate and structure the input data before
5
+ further processing.
6
+ """
7
+
8
+ from enum import Enum
9
+
10
+ from pydantic import BaseModel, Field, model_validator
11
+
12
+
13
+ class CustomFieldType(str, Enum):
14
+ """GoodData field types."""
15
+
16
+ # NOTE: Start using StrEnum with Python 3.11
17
+ ATTRIBUTE = "attribute"
18
+ FACT = "fact"
19
+ DATE = "date"
20
+
21
+
22
+ class ColumnDataType(str, Enum):
23
+ """Supported data types"""
24
+
25
+ # NOTE: Start using StrEnum with Python 3.11
26
+ INT = "INT"
27
+ STRING = "STRING"
28
+ DATE = "DATE"
29
+ NUMERIC = "NUMERIC"
30
+ TIMESTAMP = "TIMESTAMP"
31
+ TIMESTAMP_TZ = "TIMESTAMP_TZ"
32
+ BOOLEAN = "BOOLEAN"
33
+
34
+
35
+ class CustomFieldDefinition(BaseModel):
36
+ """Input model for custom field definition."""
37
+
38
+ workspace_id: str
39
+ dataset_id: str
40
+ custom_field_id: str
41
+ custom_field_name: str
42
+ custom_field_type: CustomFieldType
43
+ custom_field_source_column: str
44
+ custom_field_source_column_data_type: ColumnDataType
45
+ description: str | None = Field(
46
+ default=None,
47
+ description="Declarative description on the attribute, fact, or date dataset.",
48
+ )
49
+ tags: list[str] | None = Field(
50
+ default=None,
51
+ description="If set, replaces the default tag list (dataset display name only).",
52
+ )
53
+
54
+ @model_validator(mode="after")
55
+ def check_ids_not_equal(self) -> "CustomFieldDefinition":
56
+ """Check that custom field ID is not the same as dataset ID."""
57
+ if self.custom_field_id == self.dataset_id:
58
+ raise ValueError(
59
+ f"Custom field ID {self.custom_field_id} cannot be the same as dataset ID {self.dataset_id}"
60
+ )
61
+ return self
62
+
63
+
64
+ class ParentDatasetReference(BaseModel):
65
+ """One column of a (possibly composite) join to the parent dataset.
66
+
67
+ A list of these on ``CustomDatasetDefinition.parent_dataset_references``
68
+ supports multi-column foreign keys. Each entry binds a source column on the
69
+ new dataset to a grain attribute on the parent.
70
+ """
71
+
72
+ attribute_id: str = Field(
73
+ description="Attribute ID on the parent dataset that this column joins to.",
74
+ )
75
+ source_column: str = Field(
76
+ description="Column name on this dataset used to join to the parent.",
77
+ )
78
+ data_type: ColumnDataType = Field(
79
+ description="Data type of the source column.",
80
+ )
81
+
82
+
83
+ class CustomDatasetDefinition(BaseModel):
84
+ """Input model for custom dataset definition."""
85
+
86
+ workspace_id: str
87
+ dataset_id: str
88
+ dataset_name: str
89
+ dataset_datasource_id: str
90
+ dataset_source_table: str | None
91
+ dataset_source_sql: str | None
92
+ parent_dataset_reference: str
93
+ parent_dataset_reference_attribute_id: str | None = Field(
94
+ default=None,
95
+ deprecated=(
96
+ "Use `parent_dataset_references` instead. "
97
+ "This field will be removed in a future release."
98
+ ),
99
+ )
100
+ dataset_reference_source_column: str | None = Field(
101
+ default=None,
102
+ deprecated=(
103
+ "Use `parent_dataset_references` instead. "
104
+ "This field will be removed in a future release."
105
+ ),
106
+ )
107
+ dataset_reference_source_column_data_type: ColumnDataType | None = Field(
108
+ default=None,
109
+ deprecated=(
110
+ "Use `parent_dataset_references` instead. "
111
+ "This field will be removed in a future release."
112
+ ),
113
+ )
114
+ parent_dataset_references: list[ParentDatasetReference] | None = Field(
115
+ default=None,
116
+ description="List of references to the parent dataset.",
117
+ )
118
+ workspace_data_filter_id: str | None = None
119
+ workspace_data_filter_column_name: str | None = None
120
+ dataset_description: str | None = Field(
121
+ default=None,
122
+ description="Declarative description on the custom dataset.",
123
+ )
124
+ dataset_tags: list[str] | None = Field(
125
+ default=None,
126
+ description="If set, replaces the default tag list (dataset display name only).",
127
+ )
128
+
129
+ @model_validator(mode="after")
130
+ def check_source(self) -> "CustomDatasetDefinition":
131
+ """At least one of dataset_source_table or dataset_source_sql is provided."""
132
+ if not (self.dataset_source_table or self.dataset_source_sql):
133
+ raise ValueError(
134
+ "One of dataset_source_table and dataset_source_sql must be provided"
135
+ )
136
+ if self.dataset_source_table and self.dataset_source_sql:
137
+ raise ValueError(
138
+ "Only one of dataset_source_table and dataset_source_sql can be provided"
139
+ )
140
+ return self
141
+
142
+ @model_validator(mode="after")
143
+ def check_reference_form(self) -> "CustomDatasetDefinition":
144
+ """Exactly one reference form must be set: either the new list or the legacy triple."""
145
+ has_new = bool(self.parent_dataset_references)
146
+ has_legacy = (
147
+ self.parent_dataset_reference_attribute_id is not None
148
+ or self.dataset_reference_source_column is not None
149
+ or self.dataset_reference_source_column_data_type is not None
150
+ )
151
+ if has_new and has_legacy:
152
+ raise ValueError(
153
+ "Set either `parent_dataset_references` or the legacy single-column "
154
+ "fields (`parent_dataset_reference_attribute_id`, "
155
+ "`dataset_reference_source_column`, "
156
+ "`dataset_reference_source_column_data_type`), not both."
157
+ )
158
+ if not has_new and not has_legacy:
159
+ raise ValueError(
160
+ "Provide either `parent_dataset_references` or the legacy single-column "
161
+ "fields (`parent_dataset_reference_attribute_id`, "
162
+ "`dataset_reference_source_column`, "
163
+ "`dataset_reference_source_column_data_type`)."
164
+ )
165
+ return self
166
+
167
+ @model_validator(mode="after")
168
+ def check_wdf_pair(self) -> "CustomDatasetDefinition":
169
+ """Workspace data filter id and column name must be provided together or both omitted."""
170
+ has_id = self.workspace_data_filter_id is not None
171
+ has_col = self.workspace_data_filter_column_name is not None
172
+ if has_id != has_col:
173
+ raise ValueError(
174
+ "workspace_data_filter_id and workspace_data_filter_column_name "
175
+ "must both be set or both be omitted"
176
+ )
177
+ return self
178
+
179
+
180
+ class CustomDataset(BaseModel):
181
+ """Custom dataset with its definition and custom fields."""
182
+
183
+ definition: CustomDatasetDefinition
184
+ custom_fields: list[CustomFieldDefinition]
@@ -129,3 +129,52 @@ def test_datasets_to_ldm(mock_custom_dataset):
129
129
  assert ds.workspace_data_filter_references[0].filter_id.id == "wdf1"
130
130
  assert len(ldm.date_instances) == 1
131
131
  assert ldm.date_instances[0].id == "date1"
132
+
133
+
134
+ def test_datasets_to_ldm_parent_dataset_references_composite():
135
+ """Multi-column references via `parent_dataset_references` produce N sources."""
136
+ from gooddata_pipelines.ldm_extension.models.custom_data_object import (
137
+ CustomDatasetDefinition,
138
+ ParentDatasetReference,
139
+ )
140
+
141
+ definition = CustomDatasetDefinition(
142
+ workspace_id="workspace1",
143
+ dataset_id="ds_composite",
144
+ dataset_name="Composite Dataset",
145
+ dataset_source_table="table1",
146
+ dataset_datasource_id="ds_source",
147
+ dataset_source_sql=None,
148
+ parent_dataset_reference="parent_ds",
149
+ parent_dataset_references=[
150
+ ParentDatasetReference(
151
+ attribute_id="parent_pk1",
152
+ source_column="src_col1",
153
+ data_type=ColumnDataType.STRING,
154
+ ),
155
+ ParentDatasetReference(
156
+ attribute_id="parent_pk2",
157
+ source_column="src_col2",
158
+ data_type=ColumnDataType.INT,
159
+ ),
160
+ ],
161
+ workspace_data_filter_id="wdf1",
162
+ workspace_data_filter_column_name="col1",
163
+ )
164
+ ds = CustomDataset(definition=definition, custom_fields=[])
165
+ processor = LdmExtensionDataProcessor()
166
+ model = processor.datasets_to_ldm({"ds_composite": ds})
167
+ parent_ref = model.ldm.datasets[0].references[0]
168
+ assert len(parent_ref.sources) == 2
169
+ assert [s.column for s in parent_ref.sources] == ["src_col1", "src_col2"]
170
+
171
+
172
+ def test_datasets_to_ldm_legacy_reference_fallback(mock_dataset_definition):
173
+ """When `parent_dataset_references` is not set, fall back to legacy fields."""
174
+ mock_dataset_definition.parent_dataset_references = None
175
+ ds = CustomDataset(definition=mock_dataset_definition, custom_fields=[])
176
+ processor = LdmExtensionDataProcessor()
177
+ model = processor.datasets_to_ldm({"ds1": ds})
178
+ parent_ref = model.ldm.datasets[0].references[0]
179
+ assert len(parent_ref.sources) == 1
180
+ assert parent_ref.sources[0].column == "ref_col"
@@ -0,0 +1,208 @@
1
+ # (C) 2025 GoodData Corporation
2
+ import pytest
3
+ from pydantic import ValidationError
4
+
5
+ from gooddata_pipelines.ldm_extension.models.custom_data_object import (
6
+ ColumnDataType,
7
+ CustomDataset,
8
+ CustomDatasetDefinition,
9
+ CustomFieldDefinition,
10
+ CustomFieldType,
11
+ ParentDatasetReference,
12
+ )
13
+
14
+
15
+ def make_valid_field_def(**kwargs):
16
+ data = {
17
+ "workspace_id": "ws1",
18
+ "dataset_id": "ds1",
19
+ "custom_field_id": "cf1",
20
+ "custom_field_name": "Custom Field",
21
+ "custom_field_type": CustomFieldType.ATTRIBUTE,
22
+ "custom_field_source_column": "col1",
23
+ "custom_field_source_column_data_type": ColumnDataType.STRING,
24
+ }
25
+ data.update(kwargs)
26
+ return data
27
+
28
+
29
+ def make_valid_dataset_def(**kwargs):
30
+ data = {
31
+ "workspace_id": "ws1",
32
+ "dataset_id": "ds1",
33
+ "dataset_name": "Dataset",
34
+ "dataset_datasource_id": "dsrc1",
35
+ "dataset_source_table": "table1",
36
+ "dataset_source_sql": None,
37
+ "parent_dataset_reference": "parent_ds",
38
+ "parent_dataset_reference_attribute_id": "parent_attr",
39
+ "dataset_reference_source_column": "src_col",
40
+ "dataset_reference_source_column_data_type": ColumnDataType.STRING,
41
+ "workspace_data_filter_id": "wdf1",
42
+ "workspace_data_filter_column_name": "col1",
43
+ }
44
+ data.update(kwargs)
45
+ return data
46
+
47
+
48
+ def test_custom_field_definition_valid():
49
+ field = CustomFieldDefinition(**make_valid_field_def())
50
+ assert field.custom_field_id == "cf1"
51
+ assert field.custom_field_type == CustomFieldType.ATTRIBUTE
52
+
53
+
54
+ def test_custom_field_definition_cf_id_equals_dataset_id_raises():
55
+ data = make_valid_field_def(custom_field_id="ds1")
56
+ with pytest.raises(ValidationError) as exc:
57
+ CustomFieldDefinition(**data)
58
+ assert "cannot be the same as dataset ID" in str(exc.value)
59
+
60
+
61
+ def test_custom_dataset_definition_valid_table():
62
+ ds = CustomDatasetDefinition(**make_valid_dataset_def())
63
+ assert ds.dataset_source_table == "table1"
64
+ assert ds.dataset_source_sql is None
65
+
66
+
67
+ def test_custom_dataset_definition_valid_sql():
68
+ data = make_valid_dataset_def(
69
+ dataset_source_table=None, dataset_source_sql="SELECT 1"
70
+ )
71
+ ds = CustomDatasetDefinition(**data)
72
+ assert ds.dataset_source_sql == "SELECT 1"
73
+ assert ds.dataset_source_table is None
74
+
75
+
76
+ def test_custom_dataset_definition_both_none_raises():
77
+ data = make_valid_dataset_def(
78
+ dataset_source_table=None, dataset_source_sql=None
79
+ )
80
+ with pytest.raises(ValidationError) as exc:
81
+ CustomDatasetDefinition(**data)
82
+ assert "must be provided" in str(exc.value)
83
+
84
+
85
+ def test_custom_dataset_definition_both_provided_raises():
86
+ data = make_valid_dataset_def(
87
+ dataset_source_table="table1", dataset_source_sql="SELECT 1"
88
+ )
89
+ with pytest.raises(ValidationError) as exc:
90
+ CustomDatasetDefinition(**data)
91
+ assert (
92
+ "Only one of dataset_source_table and dataset_source_sql can be provided"
93
+ in str(exc.value)
94
+ )
95
+
96
+
97
+ def test_custom_dataset_model():
98
+ ds_def = CustomDatasetDefinition(**make_valid_dataset_def())
99
+ field_def = CustomFieldDefinition(**make_valid_field_def())
100
+ dataset = CustomDataset(definition=ds_def, custom_fields=[field_def])
101
+ assert dataset.definition.dataset_id == "ds1"
102
+ assert len(dataset.custom_fields) == 1
103
+ assert dataset.custom_fields[0].custom_field_id == "cf1"
104
+
105
+
106
+ def test_custom_dataset_definition_parent_dataset_references_optional():
107
+ """The new composite-reference field is optional and defaults to None."""
108
+ ds = CustomDatasetDefinition(**make_valid_dataset_def())
109
+ assert ds.parent_dataset_references is None
110
+
111
+
112
+ def test_custom_dataset_definition_parent_dataset_references_accepted():
113
+ """Composite references can be provided via the new list field."""
114
+ refs = [
115
+ ParentDatasetReference(
116
+ attribute_id="parent_pk1",
117
+ source_column="src_col1",
118
+ data_type=ColumnDataType.STRING,
119
+ ),
120
+ ParentDatasetReference(
121
+ attribute_id="parent_pk2",
122
+ source_column="src_col2",
123
+ data_type=ColumnDataType.INT,
124
+ ),
125
+ ]
126
+ data = make_valid_dataset_def(
127
+ parent_dataset_reference_attribute_id=None,
128
+ dataset_reference_source_column=None,
129
+ dataset_reference_source_column_data_type=None,
130
+ parent_dataset_references=refs,
131
+ )
132
+ ds = CustomDatasetDefinition(**data)
133
+ assert ds.parent_dataset_references is not None
134
+ assert len(ds.parent_dataset_references) == 2
135
+ assert ds.parent_dataset_references[1].data_type == ColumnDataType.INT
136
+
137
+
138
+ def test_custom_dataset_definition_no_reference_form_raises():
139
+ """Providing neither the legacy fields nor `parent_dataset_references` is rejected."""
140
+ data = make_valid_dataset_def(
141
+ parent_dataset_reference_attribute_id=None,
142
+ dataset_reference_source_column=None,
143
+ dataset_reference_source_column_data_type=None,
144
+ )
145
+ with pytest.raises(ValidationError) as exc:
146
+ CustomDatasetDefinition(**data)
147
+ assert "Provide either" in str(exc.value)
148
+
149
+
150
+ def test_custom_dataset_definition_mixed_reference_forms_raises():
151
+ """Setting both legacy fields and `parent_dataset_references` is rejected."""
152
+ data = make_valid_dataset_def(
153
+ parent_dataset_references=[
154
+ ParentDatasetReference(
155
+ attribute_id="parent_pk",
156
+ source_column="src_col",
157
+ data_type=ColumnDataType.STRING,
158
+ )
159
+ ],
160
+ )
161
+ with pytest.raises(ValidationError) as exc:
162
+ CustomDatasetDefinition(**data)
163
+ assert "not both" in str(exc.value)
164
+
165
+
166
+ def test_custom_dataset_definition_legacy_reference_fields_optional():
167
+ data = make_valid_dataset_def(
168
+ parent_dataset_reference_attribute_id=None,
169
+ dataset_reference_source_column=None,
170
+ dataset_reference_source_column_data_type=None,
171
+ parent_dataset_references=[
172
+ ParentDatasetReference(
173
+ attribute_id="parent_pk",
174
+ source_column="src_col",
175
+ data_type=ColumnDataType.STRING,
176
+ )
177
+ ],
178
+ )
179
+ ds = CustomDatasetDefinition(**data)
180
+ assert ds.dataset_reference_source_column is None
181
+ assert ds.parent_dataset_references is not None
182
+
183
+
184
+ def test_custom_dataset_definition_wdf_optional_both_none():
185
+ data = make_valid_dataset_def(
186
+ workspace_data_filter_id=None, workspace_data_filter_column_name=None
187
+ )
188
+ ds = CustomDatasetDefinition(**data)
189
+ assert ds.workspace_data_filter_id is None
190
+ assert ds.workspace_data_filter_column_name is None
191
+
192
+
193
+ def test_custom_dataset_definition_wdf_only_id_raises():
194
+ data = make_valid_dataset_def(
195
+ workspace_data_filter_id="wdf1", workspace_data_filter_column_name=None
196
+ )
197
+ with pytest.raises(ValidationError) as exc:
198
+ CustomDatasetDefinition(**data)
199
+ assert "both be set or both be omitted" in str(exc.value)
200
+
201
+
202
+ def test_custom_dataset_definition_wdf_only_column_raises():
203
+ data = make_valid_dataset_def(
204
+ workspace_data_filter_id=None, workspace_data_filter_column_name="col1"
205
+ )
206
+ with pytest.raises(ValidationError) as exc:
207
+ CustomDatasetDefinition(**data)
208
+ assert "both be set or both be omitted" in str(exc.value)