diff --git a/md_processing/data/compact_commands/commands_asset_maker_compact.json b/md_processing/data/compact_commands/commands_asset_maker_compact.json index 96d450ab..1262bd41 100644 --- a/md_processing/data/compact_commands/commands_asset_maker_compact.json +++ b/md_processing/data/compact_commands/commands_asset_maker_compact.json @@ -879,6 +879,25 @@ "max_cardinality": 1, "level": "Advanced", "Journal Entry": "" + }, + "Deep Copy": { + "variable_name": "deep_copy", + "inUpdate": false, + "attr_labels": "", + "examples": "", + "default_value": "true", + "valid_values": [], + "existing_element": "", + "description": "If true (Egeria's default), the template's anchored attachments -- such as a connection and its endpoint -- are copied to the new element. Set to false to create only the top-level element.", + "generated": false, + "style": "Bool", + "user_specified": true, + "unique": false, + "input_required": false, + "min_cardinality": 0, + "max_cardinality": 1, + "level": "Advanced", + "Journal Entry": "2026-09-29 -- maps to TemplateRequestBody.deepCopy; pyegeria previously sent false by default." } }, "bundles": { @@ -920,7 +939,8 @@ "Parent ID", "Parent Relationship Type Name", "Parent Relationship Attributes", - "Parent at End1" + "Parent at End1", + "Deep Copy" ], "Journal Entry": "Added 2026-09-11 -- Asset Maker family, wraps automated_curation.py's create-element-from-template methods. OM_TYPE is best-effort (not yet confirmed against a live server's type system); create-only (no Update) since the underlying OMVS methods have no update counterpart." }, diff --git a/md_processing/data/generated_format_sets.json b/md_processing/data/generated_format_sets.json index b9bf39b1..31905ca9 100644 --- a/md_processing/data/generated_format_sets.json +++ b/md_processing/data/generated_format_sets.json @@ -23687,6 +23687,14 @@ "detail_spec": null, "valid_values": [] }, + { + "name": "Deep Copy", + "key": "deep_copy", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [] + }, { "name": "Initial Status", "key": "initial_status", @@ -23778,6 +23786,14 @@ "detail_spec": null, "valid_values": [] }, + { + "name": "Deep Copy", + "key": "deep_copy", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [] + }, { "name": "Initial Status", "key": "initial_status", @@ -23890,6 +23906,14 @@ "format": false, "detail_spec": null, "valid_values": [] + }, + { + "name": "Deep Copy", + "key": "deep_copy", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [] } ], "columns": [ @@ -23981,6 +24005,14 @@ "format": false, "detail_spec": null, "valid_values": [] + }, + { + "name": "Deep Copy", + "key": "deep_copy", + "value": null, + "format": false, + "detail_spec": null, + "valid_values": [] } ] } diff --git a/md_processing/v2/asset_maker.py b/md_processing/v2/asset_maker.py index b7a12aec..36da7419 100644 --- a/md_processing/v2/asset_maker.py +++ b/md_processing/v2/asset_maker.py @@ -65,6 +65,7 @@ async def apply_changes(self) -> str: "parentRelationshipTypeName": _v(attributes, "Parent Relationship Type Name"), "parentRelationshipProperties": _v(attributes, "Parent Relationship Attributes"), "parentAtEnd1": _v(attributes, "Parent at End1", True), + "deepCopy": _v(attributes, "Deep Copy"), } guid = await client._async_create_elem_from_template(body) display_name = f"{_v(attributes, 'Element Type Name')} element" diff --git a/pyegeria/models/models.py b/pyegeria/models/models.py index ee47adb3..6a9951d2 100644 --- a/pyegeria/models/models.py +++ b/pyegeria/models/models.py @@ -535,7 +535,9 @@ class TemplateRequestBody(RequestBody): template_guid: str replacement_properties: dict[str, Any] | None = None placeholder_property_values: dict[str, Any] = {} - deep_copy: bool | None = False + # None (not False): omitted from the JSON so Egeria's own default (TemplateOptions.deepCopy = true) + # applies. False made every create-from-template skip the template's anchored attachments. + deep_copy: bool | None = None effective_from: datetime | None = None effective_to: datetime | None = None template_substitute: bool | None = False @@ -598,7 +600,8 @@ class NewOpenMetadataElementRequestBody(RequestBody): type_name: str | None = None initial_classifications: Dict[str, InitialClassifications] | None = None anchor_guid: str | None = None - is_own_anchor: bool | None = False + # None: omitted so Egeria's AnchorOptions default (isOwnAnchor=true) applies. + is_own_anchor: bool | None = None effective_from: datetime | None = None effective_to: datetime | None = None properties: dict | None = None @@ -691,9 +694,12 @@ class FilterRequestBody(ResultsRequestBody): class SearchStringRequestBody(ResultsRequestBody): class_: Annotated[Literal["SearchStringRequestBody"], Field(alias="class")] search_string: str | None = None - starts_with: bool = True + # None: omitted from the JSON so Egeria's SearchOptions defaults apply + # (startsWith=false, ignoreCase=true). Callers that want prefix or + # case-sensitive matching pass the value explicitly. + starts_with: bool | None = None ends_with: bool = False - ignore_case: bool = False + ignore_case: bool | None = None class FindRequestBody(ResultsRequestBody): diff --git a/pyegeria/view/base_report_formats.py b/pyegeria/view/base_report_formats.py index e1a0841e..3db7ae63 100644 --- a/pyegeria/view/base_report_formats.py +++ b/pyegeria/view/base_report_formats.py @@ -167,7 +167,7 @@ 'Digital-Product-DrE-Basic': FormatSet(target_type='Digital Product', heading='Digital-Product-DrE-Basic Attributes', description='Auto-generated format for Digital Product (Create, Basic).', family='Digital Product Manager', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Description', key='description'), Column(name='Legal', key='legal'), Column(name='Authors', key='authors'), Column(name='Purpose', key='purpose'), Column(name='Current Version', key='current_version'), Column(name='Introduction Date', key='introduction_date'), Column(name='Maturity', key='maturity'), Column(name='Next Version Date', key='next_version_date'), Column(name='Product Name', key='product_name'), Column(name='Service Life', key='service_life'), Column(name='Withdrawal Date', key='withdrawal_date'), Column(name='Version Identifier', key='version_identifier'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Category', key='category'), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Legal', key='legal'), Column(name='Qualified Name', key='qualified_name'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Authors', key='authors'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Purpose', key='purpose'), Column(name='Current Version', key='current_version'), Column(name='Introduction Date', key='introduction_date'), Column(name='Maturity', key='maturity'), Column(name='Next Version Date', key='next_version_date'), Column(name='Product Name', key='product_name'), Column(name='Service Life', key='service_life'), Column(name='Withdrawal Date', key='withdrawal_date')])], action=ActionParameter(function='CollectionManager.find_collections', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'], spec_params={'metadata_element_type': 'DigitalProduct'})), 'Digital-Subscription-DrE-Advanced': FormatSet(target_type='Digital Subscription', heading='Digital-Subscription-DrE-Advanced Attributes', description='Auto-generated format for Digital Subscription (Create, Advanced).', family='Digital Product Manager', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='Authors', key='authors'), Column(name='Purpose', key='purpose'), Column(name='Agreement Type', key='agreement_type'), Column(name='Support Level', key='support_level'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties'), Column(name='Version Identifier', key='version_identifier'), Column(name='Status', key='status'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='User Defined Content Status', key='user_defined_content_status'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Status', key='status'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Category', key='category'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Qualified Name', key='qualified_name'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Authors', key='authors'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='User Defined Content Status', key='user_defined_content_status'), Column(name='Purpose', key='purpose'), Column(name='Agreement Type', key='agreement_type'), Column(name='Support Level', key='support_level'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties')])], action=ActionParameter(function='CollectionManager.find_collections', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'], spec_params={'metadata_element_type': 'DigitalSubscription'})), 'Digital-Subscription-DrE-Basic': FormatSet(target_type='Digital Subscription', heading='Digital-Subscription-DrE-Basic Attributes', description='Auto-generated format for Digital Subscription (Create, Basic).', family='Digital Product Manager', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Description', key='description'), Column(name='Legal', key='legal'), Column(name='Authors', key='authors'), Column(name='Purpose', key='purpose'), Column(name='Agreement Type', key='agreement_type'), Column(name='Support Level', key='support_level'), Column(name='Version Identifier', key='version_identifier'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Category', key='category'), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Legal', key='legal'), Column(name='Qualified Name', key='qualified_name'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Authors', key='authors'), Column(name='Content Status', key='content_status', valid_values=['DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'ACTIVE', 'DEPRECATED', 'OTHER']), Column(name='Purpose', key='purpose'), Column(name='Agreement Type', key='agreement_type'), Column(name='Support Level', key='support_level')])], action=ActionParameter(function='CollectionManager.find_collections', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'], spec_params={'metadata_element_type': 'DigitalSubscription'})), - 'Element-DrE-Advanced': FormatSet(target_type='Element', heading='Element-DrE-Advanced Attributes', description='Auto-generated format for Element (Create, Advanced).', family='Asset Maker', formats=[Format(types=['LIST'], attributes=[Column(name='Element Type Name', key='type_name'), Column(name='Template GUID', key='template_guid'), Column(name='Placeholder Property Values', key='placeholder_property_values'), Column(name='Template Properties', key='template_properties'), Column(name='Generic Initial Classifications', key='initial_classifications'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Effective From', key='effective_from'), Column(name='Effective To', key='effective_to'), Column(name='Effective Time', key='effective_time'), Column(name='Initial Status', key='initial_status', valid_values=['ACTIVE', 'DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'DEPRECATED', 'OTHER'])]), Format(types=['ALL'], attributes=[Column(name='Element Type Name', key='type_name'), Column(name='Template GUID', key='template_guid'), Column(name='Placeholder Property Values', key='placeholder_property_values'), Column(name='Template Properties', key='template_properties'), Column(name='Initial Status', key='initial_status', valid_values=['ACTIVE', 'DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'DEPRECATED', 'OTHER']), Column(name='Generic Initial Classifications', key='initial_classifications'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Effective From', key='effective_from'), Column(name='Effective To', key='effective_to'), Column(name='Effective Time', key='effective_time')])], action=ActionParameter(function='MetadataExpert.find_metadata_elements_with_string', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'])), + 'Element-DrE-Advanced': FormatSet(target_type='Element', heading='Element-DrE-Advanced Attributes', description='Auto-generated format for Element (Create, Advanced).', family='Asset Maker', formats=[Format(types=['LIST'], attributes=[Column(name='Element Type Name', key='type_name'), Column(name='Template GUID', key='template_guid'), Column(name='Placeholder Property Values', key='placeholder_property_values'), Column(name='Template Properties', key='template_properties'), Column(name='Generic Initial Classifications', key='initial_classifications'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Effective From', key='effective_from'), Column(name='Effective To', key='effective_to'), Column(name='Effective Time', key='effective_time'), Column(name='Deep Copy', key='deep_copy'), Column(name='Initial Status', key='initial_status', valid_values=['ACTIVE', 'DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'DEPRECATED', 'OTHER'])]), Format(types=['ALL'], attributes=[Column(name='Element Type Name', key='type_name'), Column(name='Template GUID', key='template_guid'), Column(name='Placeholder Property Values', key='placeholder_property_values'), Column(name='Template Properties', key='template_properties'), Column(name='Initial Status', key='initial_status', valid_values=['ACTIVE', 'DRAFT', 'PREPARED', 'PROPOSED', 'APPROVED', 'REJECTED', 'DEPRECATED', 'OTHER']), Column(name='Generic Initial Classifications', key='initial_classifications'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Effective From', key='effective_from'), Column(name='Effective To', key='effective_to'), Column(name='Effective Time', key='effective_time'), Column(name='Deep Copy', key='deep_copy')])], action=ActionParameter(function='MetadataExpert.find_metadata_elements_with_string', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'])), 'Element-DrE-Basic': FormatSet(target_type='Element', heading='Element-DrE-Basic Attributes', description='Auto-generated format for Element (Create, Basic).', family='Asset Maker', formats=[Format(types=['LIST'], attributes=[Column(name='Template GUID', key='template_guid'), Column(name='Placeholder Property Values', key='placeholder_property_values'), Column(name='Template Properties', key='template_properties')]), Format(types=['ALL'], attributes=[Column(name='Template GUID', key='template_guid'), Column(name='Placeholder Property Values', key='placeholder_property_values'), Column(name='Template Properties', key='template_properties')])], action=ActionParameter(function='MetadataExpert.find_metadata_elements_with_string', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'])), 'Embedded-Process-DrE-Advanced': FormatSet(target_type='Embedded Process', heading='Embedded-Process-DrE-Advanced Attributes', description='Auto-generated format for Embedded Process (Create, Advanced).', family='Action Author', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='Deployed Implementation Type', key='deployed_impl_type'), Column(name='Namespace Path', key='namespace_path'), Column(name='Resource Name', key='resource_name'), Column(name='Source', key='source'), Column(name='Formula', key='formula'), Column(name='Formula Type', key='formula_type'), Column(name='Expected Behaviour', key='expected_behaviour'), Column(name='Priority', key='priority'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties'), Column(name='Version Identifier', key='version_identifier'), Column(name='Status', key='status'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Effective From', key='effective_from'), Column(name='Effective Time', key='effective_time'), Column(name='Effective To', key='effective_to'), Column(name='Glossary Term', key='glossary_term'), Column(name='Is Own Anchor', key='is_own_anchor'), Column(name='Status', key='status'), Column(name='Zone Membership', key='zone_membership'), Column(name='Additional Properties', key='additional_properties'), Column(name='Category', key='category'), Column(name='Confidence Classification', key='confidence_classification', valid_values=['UNCLASSIFIED', 'AD_HOC', 'TRANSACTIONAL', 'AUTHORITATIVE', 'DERIVED', 'OBSOLETE', 'OTHER']), Column(name='Confidentiality Classification', key='confidentiality_classification', valid_values=['UNCLASSIFIED', 'INTERNAL', 'CONFIDENTIAL', 'SENSITIVE', 'RESTRICTED', 'OTHER']), Column(name='Criticality Classification', key='criticality_classification', valid_values=['UNCLASSIFIED', 'MARGINAL', 'IMPORTANT', 'CRITICAL', 'CATASTROPHIC', 'OTHER']), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Identifier', key='identifier'), Column(name='Impact Classification', key='impact_classification', valid_values=['UNCLASSIFIED', 'LOW', 'MEDIUM', 'HIGH', 'OTHER']), Column(name='Legal', key='legal'), Column(name='Policy Management Point', key='policy_management_point'), Column(name='Qualified Name', key='qualified_name'), Column(name='Retention Classification', key='retention_classification', valid_values=['UNCLASSIFIED', 'TEMPORARY', 'PROJECT_LIFETIME', 'TEAM_LIFETIME', 'CONTRACT_LIFETIME', 'REGULATED_LIFETIME', 'TIMEBOXED_LIFETIME', 'OTHER']), Column(name='Security Tags', key='security_tags'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Deployed Implementation Type', key='deployed_impl_type'), Column(name='Namespace Path', key='namespace_path'), Column(name='Resource Name', key='resource_name'), Column(name='Source', key='source'), Column(name='Formula', key='formula'), Column(name='Formula Type', key='formula_type'), Column(name='Expected Behaviour', key='expected_behaviour'), Column(name='Priority', key='priority'), Column(name='User Defined Status', key='user_defined_status'), Column(name='Classifications', key='classifications'), Column(name='Anchor Scope Name', key='anchor_scope_guid'), Column(name='Supplementary Properties', key='supplementary_properties')])], action=ActionParameter(function='MetadataExpert.find_metadata_elements_with_string', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'])), 'Embedded-Process-DrE-Basic': FormatSet(target_type='Embedded Process', heading='Embedded-Process-DrE-Basic Attributes', description='Auto-generated format for Embedded Process (Create, Basic).', family='Action Author', formats=[Format(types=['LIST'], attributes=[Column(name='Display Name', key='display_name'), Column(name='Qualified Name', key='qualified_name'), Column(name='GUID', key='guid'), Column(name='Description', key='description'), Column(name='Legal', key='legal'), Column(name='Deployed Implementation Type', key='deployed_impl_type'), Column(name='Namespace Path', key='namespace_path'), Column(name='Resource Name', key='resource_name'), Column(name='Priority', key='priority'), Column(name='Version Identifier', key='version_identifier'), Column(name='Category', key='category')]), Format(types=['ALL'], attributes=[Column(name='Category', key='category'), Column(name='Description', key='description'), Column(name='Display Name', key='display_name'), Column(name='GUID', key='guid'), Column(name='Legal', key='legal'), Column(name='Qualified Name', key='qualified_name'), Column(name='URL', key='url'), Column(name='Version Identifier', key='version_identifier'), Column(name='Deployed Implementation Type', key='deployed_impl_type'), Column(name='Namespace Path', key='namespace_path'), Column(name='Resource Name', key='resource_name'), Column(name='Priority', key='priority')])], action=ActionParameter(function='MetadataExpert.find_metadata_elements_with_string', required_params=['search_string'], optional_params=['sequencing_order', 'sequencing_property', 'page_size', 'start_from', 'starts_with', 'ends_with', 'ignore_case', 'classification_names', 'metadata_element_subtypes', 'metadata_element_type'])), diff --git a/sample-data/templates/advanced/Asset Maker/Create_Element.md b/sample-data/templates/advanced/Asset Maker/Create_Element.md index 919dee1b..13fd3373 100644 --- a/sample-data/templates/advanced/Asset Maker/Create_Element.md +++ b/sample-data/templates/advanced/Asset Maker/Create_Element.md @@ -233,6 +233,16 @@ ___ > **Default Value**: true +### Deep Copy +> **Input Required**: False + +> **Attribute Type**: Bool + +> **Description**: If true (Egeria's default), the template's anchored attachments -- such as a connection and its endpoint -- are copied to the new element. Set to false to create only the top-level element. + +> **Default Value**: true + + ### Status > **Input Required**: False diff --git a/tests/micro-tests/test_template_deep_copy_default.py b/tests/micro-tests/test_template_deep_copy_default.py new file mode 100644 index 00000000..bcd273f4 --- /dev/null +++ b/tests/micro-tests/test_template_deep_copy_default.py @@ -0,0 +1,95 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright Contributors to the ODPi Egeria project. +""" +TemplateRequestBody.deep_copy used to default to False, while Egeria's +TemplateOptions defaults deepCopy to true. Every create-from-template body is +validated through the model and serialised with exclude_none=True, so a caller +that never mentioned deepCopy sent "deepCopy": false -- and Egeria created the +element without any of the template's anchored attachments (e.g. a PostgreSQL +data set with no connection). An unset value must now be left out, so +Egeria's own default applies. +""" +import json +from typing import Any, cast + +import pytest +from pydantic import TypeAdapter + +from md_processing.v2.asset_maker import AssetMakerProcessor +from md_processing.v2.extraction import DrECommand +from pyegeria.models import TemplateRequestBody + +ADAPTER = TypeAdapter(TemplateRequestBody) +TEMPLATE_GUID = "3f9a0ab3-072c-4cb1-a14f-6e0492e00dd7" + + +def _serialised(body: dict) -> dict: + # Mirrors _async_create_element_from_template's validate + dump. + return json.loads(ADAPTER.validate_python(body).model_dump_json(exclude_none=True, by_alias=True)) + + +def test_unset_deep_copy_is_left_to_egeria_default(): + sent = _serialised({"class": "TemplateRequestBody", "templateGUID": TEMPLATE_GUID}) + assert "deepCopy" not in sent + + +@pytest.mark.parametrize("value", [True, False]) +def test_explicit_deep_copy_is_sent(value): + sent = _serialised({"class": "TemplateRequestBody", "templateGUID": TEMPLATE_GUID, "deepCopy": value}) + assert sent["deepCopy"] is value + + +class _FakeCuration: + def __init__(self): + self.bodies = [] + + async def _async_create_elem_from_template(self, body): + self.bodies.append(body) + return "cccccccc-0000-0000-0000-000000000003" + + +class _FakeClient: + def __init__(self): + self.automated_curation = _FakeCuration() + + +@pytest.mark.asyncio +@pytest.mark.parametrize("attributes, expected", [ + ({}, None), + ({"Deep Copy": {"value": False}}, False), + ({"Deep Copy": {"value": True}}, True), +]) +async def test_create_element_maps_deep_copy(attributes, expected): + client = _FakeClient() + p = AssetMakerProcessor(client=cast(Any, client), + command=DrECommand(verb="Create", object_type="Element", attributes={}, + raw_block="## Create Element"), context={}) + p.get_command_spec = lambda: {} + p.parsed_output = {"qualified_name": None, + "attributes": {"Template GUID": {"value": TEMPLATE_GUID}, **attributes}} + p.render_result_markdown = lambda guid: _async_value(guid) + + await p.apply_changes() + + assert client.automated_curation.bodies[0]["deepCopy"] is expected + + +async def _async_value(value): + return value + + +# The same rule for the other request models whose pyegeria defaults differed +# from the Egeria beans they mirror: an unset value is left out, so Egeria's +# default applies (AnchorOptions.isOwnAnchor=true, SearchOptions.startsWith=false, +# SearchOptions.ignoreCase=true). +@pytest.mark.parametrize("model_name, body, omitted", [ + ("NewOpenMetadataElementRequestBody", {"class": "NewOpenMetadataElementRequestBody"}, ["isOwnAnchor"]), + ("SearchStringRequestBody", {"class": "SearchStringRequestBody", "searchString": "x"}, + ["startsWith", "ignoreCase"]), +]) +def test_unset_flags_are_left_to_egeria_defaults(model_name, body, omitted): + import pyegeria.models as models + sent = json.loads(TypeAdapter(getattr(models, model_name)).validate_python(body) + .model_dump_json(exclude_none=True, by_alias=True)) + for key in omitted: + assert key not in sent