data-factory-testing-framework 0.2.17__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. data_factory_testing_framework-1.0.0/DataFactoryTestingFramework.sln +33 -0
  2. data_factory_testing_framework-1.0.0/MANIFEST.in +5 -0
  3. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/PKG-INFO +35 -26
  4. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/README.md +0 -9
  5. data_factory_testing_framework-1.0.0/VERSION +1 -0
  6. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/pyproject.toml +39 -18
  7. data_factory_testing_framework-1.0.0/setup.cfg +4 -0
  8. data_factory_testing_framework-1.0.0/setup.py +57 -0
  9. data_factory_testing_framework-1.0.0/src/data_factory_testing_framework/__init__.py +14 -0
  10. data_factory_testing_framework-1.0.0/src/data_factory_testing_framework/_expression_runtime/data_factory_expression/__init__.py +5 -0
  11. data_factory_testing_framework-1.0.0/src/data_factory_testing_framework/_expression_runtime/data_factory_expression/data_factory_to_expression_transformer.py +12 -0
  12. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/expression_evaluator.py → data_factory_testing_framework-1.0.0/src/data_factory_testing_framework/_expression_runtime/data_factory_expression/expression_transformer.py +33 -47
  13. data_factory_testing_framework-1.0.0/src/data_factory_testing_framework/_expression_runtime/expression_runtime.py +81 -0
  14. data_factory_testing_framework-1.0.0/src/data_factory_testing_framework/_expression_runtime/functions_repository.py +91 -0
  15. data_factory_testing_framework-1.0.0/src/data_factory_testing_framework/_pythonnet/Evaluator.csproj +13 -0
  16. data_factory_testing_framework-1.0.0/src/data_factory_testing_framework/_pythonnet/__init__.py +20 -0
  17. data_factory_testing_framework-1.0.0/src/data_factory_testing_framework/_pythonnet/data_factory_testing_framework_expressions_evaluator.py +58 -0
  18. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/_data_factory_element.py +4 -3
  19. data_factory_testing_framework-1.0.0/src/data_factory_testing_framework.egg-info/PKG-INFO +152 -0
  20. data_factory_testing_framework-1.0.0/src/data_factory_testing_framework.egg-info/SOURCES.txt +76 -0
  21. data_factory_testing_framework-1.0.0/src/data_factory_testing_framework.egg-info/dependency_links.txt +1 -0
  22. data_factory_testing_framework-1.0.0/src/data_factory_testing_framework.egg-info/requires.txt +3 -0
  23. data_factory_testing_framework-1.0.0/src/data_factory_testing_framework.egg-info/top_level.txt +1 -0
  24. data_factory_testing_framework-1.0.0/tests/test_test_framework.py +111 -0
  25. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/__init__.py +0 -4
  26. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/__init__.py +0 -1
  27. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/__init__.py +0 -5
  28. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/expression_rule_transformer.py +0 -73
  29. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/expression_terminal_transformer.py +0 -80
  30. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/__init__.py +0 -33
  31. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/activity_reference_expression_rule_evaluator.py +0 -31
  32. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/branch_expression_rule_evaluator.py +0 -47
  33. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/dataset_reference_expression_rule_evaluator.py +0 -35
  34. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/evaluation_expression_rule_evaluator.py +0 -43
  35. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/expression_parameter_expression_rule_evaluator.py +0 -29
  36. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/expression_rule_evaluator.py +0 -54
  37. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/function_call_expression_rule_evaluator.py +0 -79
  38. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/item_reference_expression_rule_evaluator.py +0 -26
  39. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/linked_service_reference_expression_rule_evaluator.py +0 -32
  40. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/literal_evaluation_expression_rule_evaluator.py +0 -29
  41. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/literal_interpolation_expression_rule_evaluator.py +0 -34
  42. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/logical_bool_expression_rule_evaluator.py +0 -68
  43. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/pipeline_reference_expression_rule_evaluator.py +0 -39
  44. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/system_variable_reference_expression_rule_evaluator.py +0 -35
  45. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator/rules/variable_reference_expression_rule_evaluator.py +0 -30
  46. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/functions_collection_implementation.py +0 -171
  47. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/functions_conversion_implementation.py +0 -164
  48. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/functions_date_implementation.py +0 -237
  49. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/functions_logical_implementation.py +0 -46
  50. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/functions_math_implementation.py +0 -57
  51. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/functions_repository.py +0 -99
  52. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/functions_string_implementation.py +0 -128
  53. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_pythonnet/__init__.py +0 -6
  54. data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_pythonnet/csharp_datetime.py +0 -85
  55. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/LICENSE +0 -0
  56. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/_deserializers/__init__.py +0 -0
  57. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/_deserializers/_deserializer_base.py +0 -0
  58. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/_deserializers/_deserializer_data_factory.py +0 -0
  59. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/_deserializers/_deserializer_fabric.py +0 -0
  60. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/_deserializers/shared/__init__.py +0 -0
  61. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/_deserializers/shared/_activity_deserializer.py +0 -0
  62. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/_deserializers/shared/_data_factory_element_replacer.py +0 -0
  63. {data_factory_testing_framework-0.2.17/src/data_factory_testing_framework/_functions/evaluator → data_factory_testing_framework-1.0.0/src/data_factory_testing_framework/_expression_runtime/data_factory_expression}/exceptions.py +0 -0
  64. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/_repositories/__init__.py +0 -0
  65. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/_repositories/_factories/base_repository_factory.py +0 -0
  66. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/_repositories/_factories/data_factory_repository_factory.py +0 -0
  67. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/_repositories/_factories/fabric_repository_factory.py +0 -0
  68. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/_repositories/data_factory_repository.py +0 -0
  69. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/_test_framework.py +0 -0
  70. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/__init__.py +0 -0
  71. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/_activity_not_found_error.py +0 -0
  72. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/_activity_output_field_not_found_error.py +0 -0
  73. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/_control_activity_expression_evaluated_not_to_expected_type.py +0 -0
  74. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/_data_factory_element_evaluation_error.py +0 -0
  75. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/_function_call_invalid_arguments_count_error.py +0 -0
  76. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/_parameter_not_found_error.py +0 -0
  77. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/_pipeline_activities_circular_dependency_error.py +0 -0
  78. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/_pipeline_not_found_error.py +0 -0
  79. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/_state_iteration_item_not_set_error.py +0 -0
  80. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/_unsupported_function_error.py +0 -0
  81. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/_user_error.py +0 -0
  82. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/_variable_being_evaluated_does_not_exist_error.py +0 -0
  83. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/exceptions/_variable_not_found_error.py +0 -0
  84. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/__init__.py +0 -0
  85. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/_data_factory_object_type.py +0 -0
  86. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/_pipeline.py +0 -0
  87. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/activities/__init__.py +0 -0
  88. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/activities/_activity.py +0 -0
  89. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/activities/_activity_dependency.py +0 -0
  90. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/activities/_append_variable_activity.py +0 -0
  91. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/activities/_control_activity.py +0 -0
  92. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/activities/_execute_pipeline_activity.py +0 -0
  93. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/activities/_fail_activity.py +0 -0
  94. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/activities/_filter_activity.py +0 -0
  95. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/activities/_for_each_activity.py +0 -0
  96. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/activities/_if_condition_activity.py +0 -0
  97. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/activities/_set_variable_activity.py +0 -0
  98. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/activities/_switch_activity.py +0 -0
  99. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/models/activities/_until_activity.py +0 -0
  100. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/py.typed +0 -0
  101. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/state/__init__.py +0 -0
  102. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/state/_activity_result.py +0 -0
  103. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/state/_dependency_condition.py +0 -0
  104. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/state/_pipeline_run_state.py +0 -0
  105. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/state/_pipeline_run_variable.py +0 -0
  106. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/state/_run_parameter.py +0 -0
  107. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/state/_run_parameter_type.py +0 -0
  108. {data_factory_testing_framework-0.2.17 → data_factory_testing_framework-1.0.0}/src/data_factory_testing_framework/state/_run_state.py +0 -0
@@ -0,0 +1,33 @@
1
+ 
2
+ Microsoft Visual Studio Solution File, Format Version 12.00
3
+ # Visual Studio Version 17
4
+ VisualStudioVersion = 17.9.34616.47
5
+ MinimumVisualStudioVersion = 10.0.40219.1
6
+ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{35B3C0BC-05AE-4FA2-8250-DC710420F84A}"
7
+ EndProject
8
+ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Evaluator", "src/data_factory_testing_framework/_pythonnet/Evaluator.csproj", "{636E6B65-E552-41D1-94D5-63B8E6D09F73}"
9
+ EndProject
10
+ Global
11
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
12
+ Release|Any CPU = Release|Any CPU
13
+ Release|x64 = Release|x64
14
+ Release|x86 = Release|x86
15
+ EndGlobalSection
16
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
17
+ {636E6B65-E552-41D1-94D5-63B8E6D09F73}.Release|Any CPU.ActiveCfg = DataFactoryTestingFrameworkEvaluator|Any CPU
18
+ {636E6B65-E552-41D1-94D5-63B8E6D09F73}.Release|Any CPU.Build.0 = DataFactoryTestingFrameworkEvaluator|Any CPU
19
+ {636E6B65-E552-41D1-94D5-63B8E6D09F73}.Release|x64.ActiveCfg = DataFactoryTestingFrameworkEvaluator|Any CPU
20
+ {636E6B65-E552-41D1-94D5-63B8E6D09F73}.Release|x64.Build.0 = DataFactoryTestingFrameworkEvaluator|Any CPU
21
+ {636E6B65-E552-41D1-94D5-63B8E6D09F73}.Release|x86.ActiveCfg = DataFactoryTestingFrameworkEvaluator|Any CPU
22
+ {636E6B65-E552-41D1-94D5-63B8E6D09F73}.Release|x86.Build.0 = DataFactoryTestingFrameworkEvaluator|Any CPU
23
+ EndGlobalSection
24
+ GlobalSection(SolutionProperties) = preSolution
25
+ HideSolutionNode = FALSE
26
+ EndGlobalSection
27
+ GlobalSection(NestedProjects) = preSolution
28
+ {636E6B65-E552-41D1-94D5-63B8E6D09F73} = {35B3C0BC-05AE-4FA2-8250-DC710420F84A}
29
+ EndGlobalSection
30
+ GlobalSection(ExtensibilityGlobals) = postSolution
31
+ SolutionGuid = {63AF779A-5A49-4A39-B72B-22512AB23EFE}
32
+ EndGlobalSection
33
+ EndGlobal
@@ -0,0 +1,5 @@
1
+ include VERSION
2
+ include DataFactoryTestingFramework.sln
3
+ recursive-include src *.cs *.cs
4
+ recursive-include src *.csproj *.csproj
5
+ prune src/data_factory_testing_framework/_pythonnet/obj
@@ -1,28 +1,47 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: data-factory-testing-framework
3
- Version: 0.2.17
3
+ Version: 1.0.0
4
4
  Summary: A stand-alone test framework that allows to write unit tests for Data Factory pipelines on Microsoft Fabric and Azure Data Factory.
5
- Home-page: https://github.com/microsoft/data-factory-testing-framework
6
- License: MIT
5
+ Author-email: Data Factory Testing Framework <dftf@microsoft.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) Microsoft Corporation.
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE
27
+
28
+ Project-URL: Homepage, https://github.com/microsoft/data-factory-testing-framework
29
+ Project-URL: Documentation, https://github.com/microsoft/data-factory-testing-framework/blob/main/README.md
30
+ Project-URL: Issues, https://github.com/microsoft/data-factory-testing-framework/issues
31
+ Project-URL: Source, https://github.com/microsoft/data-factory-testing-framework.git
7
32
  Keywords: fabric,datafactory,unit-testing,functional-testing,azure
8
- Author: Data Factory Testing Framework Team
9
- Author-email: dftf@microsoft.com
10
- Requires-Python: >=3.9,<3.13
11
- Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Programming Language :: Python :: 3
13
33
  Classifier: Programming Language :: Python :: 3.9
14
34
  Classifier: Programming Language :: Python :: 3.10
15
35
  Classifier: Programming Language :: Python :: 3.11
16
36
  Classifier: Programming Language :: Python :: 3.12
17
- Requires-Dist: azure-core (>=1.29.5,<2.0.0)
18
- Requires-Dist: lark (>=1.1.8,<2.0.0)
19
- Requires-Dist: lxml (>=4.9.3,<5.0.0)
20
- Requires-Dist: pythonnet (>=3.0.3,<4.0.0)
21
- Requires-Dist: xmltodict (>=0.13.0,<0.14.0)
22
- Project-URL: Documentation, https://github.com/microsoft/data-factory-testing-framework/blob/main/README.md
23
- Project-URL: Issues, https://github.com/microsoft/data-factory-testing-framework/issues
24
- Project-URL: Repository, https://github.com/microsoft/data-factory-testing-framework.git
37
+ Classifier: License :: OSI Approved :: MIT License
38
+ Classifier: Operating System :: OS Independent
39
+ Requires-Python: <3.13,>=3.9
25
40
  Description-Content-Type: text/markdown
41
+ License-File: LICENSE
42
+ Requires-Dist: azure-core<2.0.0,>=1.29.5
43
+ Requires-Dist: lark<2.0.0,>=1.1.8
44
+ Requires-Dist: pythonnet<4.0.0,>=3.0.3
26
45
 
27
46
  # Data Factory - Testing Framework :hammer_and_wrench:
28
47
 
@@ -110,15 +129,6 @@ Azure Data Factory:
110
129
  1. [Copy blobs example](examples/data_factory/copy_blobs/README.md)
111
130
  2. [Batch job example](examples/data_factory/batch_job/README.md)
112
131
 
113
- ## Limitations :warning:
114
-
115
- The framework has the following limitations at the moment:
116
-
117
- 1. The framework reimplements the Data Factory expression language in Python. This means that it might not be 100% accurate with the actual Data Factory language, especially when it comes to xml functions.
118
- 2. The framework does not support automatic type conversion of arguments passed to functions in expressions.
119
-
120
- > These limitations are top priority to be resolved in the future.
121
-
122
132
  ## Contributing :handshake:
123
133
 
124
134
  This project welcomes contributions and suggestions. Most contributions require you to agree to a
@@ -140,4 +150,3 @@ trademarks or logos is subject to and must follow
140
150
  [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
141
151
  Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
142
152
  Any use of third-party trademarks or logos are subject to those third-party's policies.
143
-
@@ -84,15 +84,6 @@ Azure Data Factory:
84
84
  1. [Copy blobs example](examples/data_factory/copy_blobs/README.md)
85
85
  2. [Batch job example](examples/data_factory/batch_job/README.md)
86
86
 
87
- ## Limitations :warning:
88
-
89
- The framework has the following limitations at the moment:
90
-
91
- 1. The framework reimplements the Data Factory expression language in Python. This means that it might not be 100% accurate with the actual Data Factory language, especially when it comes to xml functions.
92
- 2. The framework does not support automatic type conversion of arguments passed to functions in expressions.
93
-
94
- > These limitations are top priority to be resolved in the future.
95
-
96
87
  ## Contributing :handshake:
97
88
 
98
89
  This project welcomes contributions and suggestions. Most contributions require you to agree to a
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -1,24 +1,41 @@
1
- [tool.poetry]
1
+ [project]
2
2
  name = "data-factory-testing-framework"
3
- version = "0.2.17"
3
+ dynamic = ["version"]
4
+ authors = [
5
+ {name = "Data Factory Testing Framework", email = "dftf@microsoft.com"},
6
+ ]
7
+ requires-python = ">=3.9,<3.13"
8
+ classifiers = [
9
+ "Programming Language :: Python :: 3.9",
10
+ "Programming Language :: Python :: 3.10",
11
+ "Programming Language :: Python :: 3.11",
12
+ "Programming Language :: Python :: 3.12",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Operating System :: OS Independent",
15
+ ]
4
16
  description = "A stand-alone test framework that allows to write unit tests for Data Factory pipelines on Microsoft Fabric and Azure Data Factory."
5
- authors = ["Data Factory Testing Framework Team <dftf@microsoft.com>"]
6
17
  readme = "README.md"
7
- license = "MIT"
8
- include = [ "README.md", "LICENSE" ]
18
+ license = { file="LICENSE" }
9
19
  keywords = ["fabric", "datafactory", "unit-testing", "functional-testing", "azure"]
10
- homepage = "https://github.com/microsoft/data-factory-testing-framework"
11
- documentation = "https://github.com/microsoft/data-factory-testing-framework/blob/main/README.md"
12
- repository = "https://github.com/microsoft/data-factory-testing-framework.git"
13
20
 
14
- [tool.poetry.urls]
15
- "Issues" = "https://github.com/microsoft/data-factory-testing-framework/issues"
21
+ dependencies = [
22
+ "azure-core>=1.29.5,<2.0.0",
23
+ "lark>=1.1.8,<2.0.0",
24
+ "pythonnet>=3.0.3,<4.0.0"
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/microsoft/data-factory-testing-framework"
29
+ Documentation = "https://github.com/microsoft/data-factory-testing-framework/blob/main/README.md"
30
+ Issues = "https://github.com/microsoft/data-factory-testing-framework/issues"
31
+ Source = "https://github.com/microsoft/data-factory-testing-framework.git"
32
+
33
+ [tool.poetry]
34
+ package-mode = false
16
35
 
17
36
  [tool.poetry.dependencies]
18
37
  python = ">=3.9,<3.13"
19
38
  azure-core = "^1.29.5"
20
- xmltodict = "^0.13.0"
21
- lxml = "^4.9.3"
22
39
  lark = "^1.1.8"
23
40
  pythonnet = "^3.0.3"
24
41
 
@@ -29,12 +46,9 @@ ruff = "^0.1.5"
29
46
  pre-commit = "^3.5.0"
30
47
  astor = "^0.8.1"
31
48
  docstring-parser = "^0.15"
32
-
33
-
34
- [build-system]
35
- requires = ["poetry-core"]
36
- build-backend = "poetry.core.masonry.api"
37
-
49
+ build = "^1.1.1"
50
+ wheel = "^0.43.0"
51
+ setuptools = "^69.0"
38
52
 
39
53
  [tool.ruff]
40
54
  select = [
@@ -83,3 +97,10 @@ convention = "google"
83
97
  [tool.pytest.ini_options]
84
98
  minversion = "7.0"
85
99
  pythonpath = ["."]
100
+
101
+ [tool.setuptools.package-data]
102
+ data_factory_testing_framework = []
103
+
104
+ [build-system]
105
+ requires = ["setuptools>=61.0", "wheel"]
106
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,57 @@
1
+ """Custom build script for building the C# project with setuptools.
2
+
3
+ Based on the following discussion: https://github.com/pypa/setuptools/discussions/3762
4
+ """
5
+ import shutil
6
+ import subprocess
7
+ from contextlib import suppress
8
+ from pathlib import Path
9
+
10
+ from setuptools import Command, setup
11
+ from setuptools.command.build import build
12
+
13
+
14
+ class CustomCommand(Command):
15
+ def initialize_options(self) -> None:
16
+ self.pkg_name = self.distribution.get_name().replace("-", "_")
17
+ self.bdist_dir = None
18
+
19
+ def finalize_options(self) -> None:
20
+ with suppress(Exception):
21
+ self.bdist_dir = Path(self.get_finalized_command("bdist_wheel").bdist_dir)
22
+
23
+ def run(self) -> None:
24
+ if self.bdist_dir:
25
+ dotnet_path = shutil.which("dotnet")
26
+ output_dir = self.bdist_dir / self.pkg_name / "_pythonnet" / "bin"
27
+ output_dir.mkdir(parents=True, exist_ok=True)
28
+
29
+ if dotnet_path is None:
30
+ raise Exception("dotnet not found")
31
+
32
+ subprocess.check_call(
33
+ [
34
+ dotnet_path,
35
+ "build",
36
+ "-c",
37
+ "Release",
38
+ "-o",
39
+ str(output_dir),
40
+ Path("src", self.pkg_name, "_pythonnet", "Evaluator.csproj"),
41
+ ]
42
+ )
43
+
44
+
45
+ class CustomBuild(build):
46
+ sub_commands = [("build_custom", None)] + build.sub_commands
47
+
48
+
49
+ try:
50
+ root = Path(__file__).parent
51
+ with open(root / "VERSION") as version_file:
52
+ version = version_file.read().strip()
53
+ except FileNotFoundError:
54
+ # Set a default version (for local builds)
55
+ version = "0.0.0.dev0"
56
+
57
+ setup(cmdclass={"build": CustomBuild, "build_custom": CustomCommand}, version=version)
@@ -0,0 +1,14 @@
1
+ from pathlib import Path as _Path
2
+
3
+ from data_factory_testing_framework._test_framework import TestFramework, TestFrameworkType
4
+
5
+ try:
6
+ root = _Path(__file__).parent
7
+ with open(root / "VERSION") as version_file:
8
+ version = version_file.read().strip()
9
+ except FileNotFoundError:
10
+ # Set a default version (for local builds)
11
+ version = "0.0.0.dev0"
12
+
13
+ __all__ = ["TestFramework", "TestFrameworkType"]
14
+ __version__ = version
@@ -0,0 +1,5 @@
1
+ from .expression_transformer import ExpressionTransformer
2
+
3
+ __all__ = [
4
+ "ExpressionTransformer",
5
+ ]
@@ -0,0 +1,12 @@
1
+ from typing import Union
2
+
3
+ from lark import Token, Transformer, Tree, v_args
4
+
5
+
6
+ @v_args(tree=True)
7
+ class DataFactoryExpressionTransformer(Transformer):
8
+ def expression_datafactory_parameters_reference(self, tree: Tree) -> Union[Token, Tree]:
9
+ pipeline_property = tree.children[0]
10
+ variable_name = Token("EXPRESSION_PIPELINE_PROPERTY", f"{pipeline_property}")
11
+ tree = Tree(Token("RULE", "expression_pipeline_reference"), [variable_name])
12
+ return tree
@@ -1,23 +1,17 @@
1
- from lark import Lark, Token, Tree, UnexpectedCharacters
2
-
3
- from data_factory_testing_framework._functions.evaluator.exceptions import (
4
- ExpressionEvaluationError,
5
- ExpressionParsingError,
1
+ from data_factory_testing_framework._expression_runtime.data_factory_expression.data_factory_to_expression_transformer import (
2
+ DataFactoryExpressionTransformer,
6
3
  )
7
- from data_factory_testing_framework._functions.evaluator.expression_rule_transformer import (
8
- ExpressionRuleTransformer,
9
- )
10
- from data_factory_testing_framework._functions.evaluator.expression_terminal_transformer import (
11
- ExpressionTerminalTransformer,
4
+ from data_factory_testing_framework._expression_runtime.data_factory_expression.exceptions import (
5
+ ExpressionParsingError,
12
6
  )
13
- from data_factory_testing_framework._functions.evaluator.rules import ExpressionRuleEvaluator
14
- from data_factory_testing_framework._functions.evaluator.rules.expression_rule_evaluator import EvaluationResult
15
- from data_factory_testing_framework._functions.functions_repository import FunctionsRepository
7
+ from data_factory_testing_framework._expression_runtime.functions_repository import FunctionsRepository
16
8
  from data_factory_testing_framework.models._data_factory_object_type import DataFactoryObjectType
17
9
  from data_factory_testing_framework.state import PipelineRunState
10
+ from lark import Lark, Token, Tree, UnexpectedCharacters
11
+ from lark.reconstruct import Reconstructor
18
12
 
19
13
 
20
- class ExpressionEvaluator:
14
+ class ExpressionTransformer:
21
15
  def __init__(self) -> None:
22
16
  """Evaluator for the expression language."""
23
17
  literal_grammar = """
@@ -45,27 +39,26 @@ class ExpressionEvaluator:
45
39
  """
46
40
 
47
41
  expression_grammar = f"""
48
- // TODO: add support for array index
49
- ?expression_start: expression_evaluation
50
- # TODO: probably object accessor does not apply to all below in expression_evaluation
42
+ expression_start: "@" expression_evaluation
51
43
  expression_evaluation: (expression_logical_bool | expression_branch | expression_call) ((("." EXPRESSION_PARAMETER_NAME) | EXPRESSION_ARRAY_INDEX)+)?
52
44
  ?expression_call: expression_function_call
53
- | expression_pipeline_reference
54
- | expression_variable_reference
55
- | expression_activity_reference
56
- | expression_dataset_reference
57
- | expression_linked_service_reference
58
- | expression_item_reference
59
- | expression_system_variable_reference
45
+ // used to translate to expression_pipeline_reference
46
+ | expression_datafactory_parameters_reference
47
+ | expression_pipeline_reference
48
+ // parse other language constructs
49
+ | expression_variable_reference
50
+ | expression_item_reference
51
+ | expression_datafactory_activity_reference
60
52
 
61
53
  // reference rules:
62
- expression_pipeline_reference: "pipeline" "()" "." EXPRESSION_PIPELINE_PROPERTY "." EXPRESSION_PARAMETER_NAME
63
54
  expression_variable_reference: "variables" "(" EXPRESSION_VARIABLE_NAME ")"
64
- expression_activity_reference: "activity" "(" EXPRESSION_ACTIVITY_NAME ")"
65
- expression_dataset_reference: "dataset" "()" "." EXPRESSION_PARAMETER_NAME
66
- expression_linked_service_reference: "linkedService" "()" "." EXPRESSION_PARAMETER_NAME
55
+ expression_datafactory_parameters_reference: EXPRESSION_DATAFACTORY_REFERENCE "()"
56
+ expression_datafactory_activity_reference: "activity" "(" EXPRESSION_ACTIVITY_NAME ")"
67
57
  expression_item_reference: "item" "()"
68
- expression_system_variable_reference: "pipeline" "()" "." EXPRESSION_SYSTEM_VARIABLE_NAME
58
+ expression_pipeline_reference: "pipeline" "()" "." EXPRESSION_PIPELINE_PROPERTY
59
+
60
+
61
+
69
62
  // branch rules
70
63
  expression_logical_bool: EXPRESSION_LOGICAL_BOOL "(" expression_parameter "," expression_parameter ")"
71
64
  expression_branch: "if" "(" expression_parameter "," expression_parameter "," expression_parameter ")"
@@ -73,10 +66,10 @@ class ExpressionEvaluator:
73
66
 
74
67
  // function call rules
75
68
  expression_function_call: EXPRESSION_FUNCTION_NAME "(" [expression_parameter ("," expression_parameter )*] ")"
76
- ?expression_parameter: EXPRESSION_WS* (EXPRESSION_NULL | EXPRESSION_INTEGER | EXPRESSION_FLOAT | EXPRESSION_BOOLEAN | EXPRESSION_STRING | expression_start) EXPRESSION_WS*
69
+ ?expression_parameter: EXPRESSION_WS* (EXPRESSION_NULL | EXPRESSION_INTEGER | EXPRESSION_FLOAT | EXPRESSION_BOOLEAN | EXPRESSION_STRING | expression_evaluation) EXPRESSION_WS*
77
70
 
78
71
  // expression terminals
79
- // EXPRESSION_PIPELINE_PROPERTY requires higher priority, because it clashes with pipeline().system_variable.field in the rule: expression_pipeline_reference
72
+ EXPRESSION_DATAFACTORY_REFERENCE: "dataset" | "linkedService"
80
73
  EXPRESSION_ACTIVITY_NAME: "'" /[^']*/ "'"
81
74
  EXPRESSION_ARRAY_INDEX: ARRAY_INDEX
82
75
  EXPRESSION_BOOLEAN: BOOLEAN
@@ -86,7 +79,7 @@ class ExpressionEvaluator:
86
79
  EXPRESSION_LOGICAL_BOOL: "or" | "and"
87
80
  EXPRESSION_NULL: NULL
88
81
  EXPRESSION_PARAMETER_NAME: /[a-zA-Z0-9_]+/
89
- EXPRESSION_PIPELINE_PROPERTY.2: "parameters" | "globalParameters"
82
+ EXPRESSION_PIPELINE_PROPERTY: /[a-zA-Z0-9_]+/
90
83
  EXPRESSION_STRING: SINGLE_QUOTED_STRING
91
84
  EXPRESSION_SYSTEM_VARIABLE_NAME: /[a-zA-Z0-9_]+/
92
85
  EXPRESSION_VARIABLE_NAME: "'" /[^']*/ "'"
@@ -94,7 +87,7 @@ class ExpressionEvaluator:
94
87
  """ # noqa: E501
95
88
 
96
89
  base_grammar = """
97
- ?start: ("@" expression_start) | (["@@"] literal_start) | (literal_interpolation)
90
+ start: (expression_start) | (["@@"]+ literal_start) | (literal_interpolation)
98
91
 
99
92
  // shared custom basic data type rules:
100
93
  ARRAY_INDEX: "[" /[0-9]+/ "]"
@@ -113,15 +106,14 @@ class ExpressionEvaluator:
113
106
  self.lark_parser = Lark(grammer, start="start", maybe_placeholders=False)
114
107
 
115
108
  def _supported_functions(self) -> str:
116
- functions = list(FunctionsRepository._functions.keys())
117
- functions = [f'"{f}"' for f in functions]
109
+ functions = [f'"{f}"' for f in FunctionsRepository._functions]
118
110
  return " | ".join(functions)
119
111
 
120
112
  def _parse(self, expression: str) -> Tree[Token]:
121
113
  tree = self.lark_parser.parse(expression)
122
114
  return tree
123
115
 
124
- def evaluate(self, expression: str, state: PipelineRunState) -> DataFactoryObjectType:
116
+ def transform_to_dftf_evaluator_expression(self, expression: str, state: PipelineRunState) -> DataFactoryObjectType:
125
117
  try:
126
118
  parse_tree = self._parse(expression)
127
119
 
@@ -135,14 +127,8 @@ class ExpressionEvaluator:
135
127
  char: {uc.char}
136
128
  """
137
129
  raise ExpressionParsingError(msg) from uc
138
- # we start with a raw parse tree for the lark grammer
139
- # and then semantically analysis it (in our case transforming values step by step)
140
- ast = parse_tree
141
- ast = ExpressionTerminalTransformer().transform(ast)
142
- ast = ExpressionRuleTransformer(state).transform(ast)
143
- if not isinstance(ast, ExpressionRuleEvaluator):
144
- raise ExpressionEvaluationError()
145
- result = ast.evaluate()
146
- if not isinstance(result, EvaluationResult):
147
- raise ExpressionEvaluationError()
148
- return result.value
130
+
131
+ rule_transformer = DataFactoryExpressionTransformer()
132
+ transformed_ast = rule_transformer.transform(parse_tree)
133
+ expression_reconstructed = Reconstructor(self.lark_parser).reconstruct(transformed_ast)
134
+ return expression_reconstructed
@@ -0,0 +1,81 @@
1
+ import re
2
+
3
+ from data_factory_testing_framework._expression_runtime.data_factory_expression.expression_transformer import (
4
+ ExpressionTransformer as DataFactoryTestingFrameworkExpressionsTransformer,
5
+ )
6
+ from data_factory_testing_framework._pythonnet.data_factory_testing_framework_expressions_evaluator import (
7
+ DataFactoryTestingFrameworkExpressionsEvaluator,
8
+ )
9
+ from data_factory_testing_framework.exceptions import (
10
+ ActivityNotFoundError,
11
+ ParameterNotFoundError,
12
+ StateIterationItemNotSetError,
13
+ VariableNotFoundError,
14
+ )
15
+ from data_factory_testing_framework.state import PipelineRunState, RunParameterType
16
+
17
+
18
+ class ExpressionRuntime:
19
+ def __init__(self) -> None:
20
+ """Initializes the expression runtime to transform and evaluate the expressions."""
21
+ self.dftf_expressions_transformer = DataFactoryTestingFrameworkExpressionsTransformer()
22
+ self.dftf_expressions_evaluator = DataFactoryTestingFrameworkExpressionsEvaluator()
23
+
24
+ def evaluate(self, expression: str, state: PipelineRunState) -> str:
25
+ dftf_transformed_expression = self.dftf_expressions_transformer.transform_to_dftf_evaluator_expression(
26
+ expression, state
27
+ )
28
+ try:
29
+ result = self.dftf_expressions_evaluator.evaluate(dftf_transformed_expression, state)
30
+ except Exception as e:
31
+ # match the exception type (coming from .NET) to the one we expect
32
+ missing_parameter_match = re.match(
33
+ r"(The template language expression \')(.*)(\' cannot be evaluated because property \')(.*)\' doesn't exist, available properties are .*",
34
+ str(e),
35
+ )
36
+
37
+ missing_variable_match = re.match(
38
+ r"(Template language expression cannot be evaluated: the template variable \')(.*)(\' cannot be found\.)",
39
+ str(e),
40
+ )
41
+
42
+ missing_activity_match = re.match(
43
+ r"(Template language expression cannot be evaluated: the template action \')(.*)(\' is not defined at current scope\.)",
44
+ str(e),
45
+ )
46
+
47
+ # The template function 'item' is not expected at this location.
48
+ missing_item_match = re.match(
49
+ r"(The template function \')(.*)(\' is not expected at this location\.)",
50
+ str(e),
51
+ )
52
+
53
+ if missing_parameter_match:
54
+ expression = missing_parameter_match.group(2)
55
+ property_name = missing_parameter_match.group(4)
56
+
57
+ if expression.startswith("pipeline().parameters"):
58
+ raise ParameterNotFoundError(RunParameterType.Pipeline, property_name) from e
59
+ elif expression.startswith("pipeline().globalParameters"):
60
+ raise ParameterNotFoundError(RunParameterType.Global, property_name) from e
61
+ elif expression.startswith("pipeline().dataset"):
62
+ raise ParameterNotFoundError(RunParameterType.Dataset, property_name) from e
63
+ elif expression.startswith("pipeline().linkedService"):
64
+ raise ParameterNotFoundError(RunParameterType.LinkedService, property_name) from e
65
+ else:
66
+ raise ParameterNotFoundError(RunParameterType.System, property_name) from e
67
+
68
+ if missing_variable_match:
69
+ variable_name = missing_variable_match.group(2)
70
+ raise VariableNotFoundError(variable_name) from e
71
+
72
+ if missing_activity_match:
73
+ activity_name = missing_activity_match.group(2)
74
+ raise ActivityNotFoundError(activity_name) from e
75
+
76
+ if missing_item_match:
77
+ raise StateIterationItemNotSetError() from e
78
+
79
+ raise Exception(f"Unknown error: {e}") from e
80
+
81
+ return result
@@ -0,0 +1,91 @@
1
+ from typing import List
2
+
3
+
4
+ class FunctionsRepository:
5
+ _functions: List[str] = {
6
+ "add",
7
+ "addDays",
8
+ "addHours",
9
+ "addMinutes",
10
+ "addSeconds",
11
+ "addToTime",
12
+ "array",
13
+ "base64",
14
+ "base64ToBinary",
15
+ "base64ToString",
16
+ "binary",
17
+ "bool",
18
+ "coalesce",
19
+ "concat",
20
+ "contains",
21
+ "convertFromUtc",
22
+ "convertTimeZone",
23
+ "convertToUtc",
24
+ "createArray",
25
+ "dataUri",
26
+ "dataUriToBinary",
27
+ "dataUriToString",
28
+ "dayOfMonth",
29
+ "dayOfWeek",
30
+ "dayOfYear",
31
+ "decodeBase64",
32
+ "decodeDataUri",
33
+ "decodeUriComponent",
34
+ "div",
35
+ "encodeUriComponent",
36
+ "empty",
37
+ "endsWith",
38
+ "equals",
39
+ "first",
40
+ "float",
41
+ "formatDateTime",
42
+ "getFutureTime",
43
+ "getPastTime",
44
+ "greater",
45
+ "greaterOrEquals",
46
+ "guid",
47
+ "indexOf",
48
+ "int",
49
+ "json",
50
+ "intersection",
51
+ "join",
52
+ "last",
53
+ "lastIndexOf",
54
+ "length",
55
+ "less",
56
+ "lessOrEquals",
57
+ "max",
58
+ "min",
59
+ "mod",
60
+ "mul",
61
+ "not",
62
+ "rand",
63
+ "range",
64
+ "replace",
65
+ "skip",
66
+ "split",
67
+ "startOfDay",
68
+ "startOfHour",
69
+ "startOfMonth",
70
+ "startsWith",
71
+ "string",
72
+ "sub",
73
+ "substring",
74
+ "subtractFromTime",
75
+ "take",
76
+ "ticks",
77
+ "toLower",
78
+ "toUpper",
79
+ "trim",
80
+ "union",
81
+ "uriComponent",
82
+ "uriComponentToBinary",
83
+ "uriComponentToString",
84
+ "utcNow",
85
+ "utcnow",
86
+ "xml",
87
+ "xpath",
88
+ "if",
89
+ "or",
90
+ "and",
91
+ }
@@ -0,0 +1,13 @@
1
+ <Project Sdk="Microsoft.NET.Sdk">
2
+ <PropertyGroup>
3
+ <TargetFramework>net5.0</TargetFramework>
4
+ <ImplicitUsings>false</ImplicitUsings>
5
+ <Nullable>disable</Nullable>
6
+ <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
7
+ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
8
+ <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
9
+ </PropertyGroup>
10
+ <ItemGroup>
11
+ <PackageReference Include="Microsoft.Azure.DataFactoryTestingFramework.Expressions" Version="0.2.7" />
12
+ </ItemGroup>
13
+ </Project>