-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(SDK) Add FormPatchBuilder in python sdk and provide sample CRUD …
…files
- Loading branch information
1 parent
ae4ca4b
commit 7249828
Showing
7 changed files
with
391 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
...stry/src/main/java/com/linkedin/metadata/aspect/patch/template/form/FormInfoTemplate.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package com.linkedin.metadata.aspect.patch.template.form; | ||
|
||
import static com.fasterxml.jackson.databind.node.JsonNodeFactory.instance; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.node.ObjectNode; | ||
import com.linkedin.data.template.RecordTemplate; | ||
import com.linkedin.form.FormInfo; | ||
import com.linkedin.metadata.aspect.patch.template.CompoundKeyTemplate; | ||
import java.util.Collections; | ||
import javax.annotation.Nonnull; | ||
|
||
public class FormInfoTemplate extends CompoundKeyTemplate<FormInfo> { | ||
|
||
private static final String PROMPTS_FIELD_NAME = "prompts"; | ||
private static final String PROMPT_ID_FIELD_NAME = "id"; | ||
private static final String ACTORS_FIELD_NAME = "actors"; | ||
private static final String USERS_FIELD_NAME = "users"; | ||
private static final String GROUPS_FIELD_NAME = "groups"; | ||
|
||
@Override | ||
public FormInfo getSubtype(RecordTemplate recordTemplate) throws ClassCastException { | ||
if (recordTemplate instanceof FormInfo) { | ||
return (FormInfo) recordTemplate; | ||
} | ||
throw new ClassCastException("Unable to cast RecordTemplate to FormInfo"); | ||
} | ||
|
||
@Override | ||
public Class<FormInfo> getTemplateType() { | ||
return FormInfo.class; | ||
} | ||
|
||
@Nonnull | ||
@Override | ||
public FormInfo getDefault() { | ||
FormInfo formInfo = new FormInfo(); | ||
formInfo.setName(""); | ||
|
||
return formInfo; | ||
} | ||
|
||
@Nonnull | ||
@Override | ||
public JsonNode transformFields(JsonNode baseNode) { | ||
JsonNode transformedNode = | ||
arrayFieldToMap( | ||
baseNode, PROMPTS_FIELD_NAME, Collections.singletonList(PROMPT_ID_FIELD_NAME)); | ||
|
||
JsonNode actors = transformedNode.get(ACTORS_FIELD_NAME); | ||
if (actors == null) { | ||
actors = instance.objectNode(); | ||
} | ||
|
||
JsonNode transformedActorsNode = | ||
arrayFieldToMap(actors, USERS_FIELD_NAME, Collections.emptyList()); | ||
transformedActorsNode = | ||
arrayFieldToMap(transformedActorsNode, GROUPS_FIELD_NAME, Collections.emptyList()); | ||
((ObjectNode) transformedNode).set(ACTORS_FIELD_NAME, transformedActorsNode); | ||
|
||
return transformedNode; | ||
} | ||
|
||
@Nonnull | ||
@Override | ||
public JsonNode rebaseFields(JsonNode patched) { | ||
JsonNode transformedNode = | ||
transformedMapToArray( | ||
patched, PROMPTS_FIELD_NAME, Collections.singletonList(PROMPT_ID_FIELD_NAME)); | ||
|
||
JsonNode actors = transformedNode.get(ACTORS_FIELD_NAME); | ||
if (actors == null) { | ||
actors = instance.objectNode(); | ||
} | ||
|
||
JsonNode transformedActorsNode = | ||
transformedMapToArray(actors, USERS_FIELD_NAME, Collections.emptyList()); | ||
transformedActorsNode = | ||
transformedMapToArray(transformedActorsNode, GROUPS_FIELD_NAME, Collections.emptyList()); | ||
((ObjectNode) transformedNode).set(ACTORS_FIELD_NAME, transformedActorsNode); | ||
|
||
return transformedNode; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import logging | ||
|
||
from datahub.emitter.mcp import MetadataChangeProposalWrapper | ||
from datahub.emitter.rest_emitter import DatahubRestEmitter | ||
|
||
# Imports for metadata model classes | ||
from datahub.metadata.schema_classes import ( | ||
FormActorAssignmentClass, | ||
FormInfoClass, | ||
FormPromptClass, | ||
FormPromptTypeClass, | ||
FormTypeClass, | ||
StructuredPropertyParamsClass, | ||
) | ||
from datahub.metadata.urns import FormUrn | ||
|
||
log = logging.getLogger(__name__) | ||
logging.basicConfig(level=logging.INFO) | ||
|
||
# define the prompts for our form | ||
prompt_1 = FormPromptClass( | ||
id="1", # ensure IDs are globally unique | ||
title="First Prompt", | ||
type=FormPromptTypeClass.STRUCTURED_PROPERTY, # structured property type prompt | ||
structuredPropertyParams=StructuredPropertyParamsClass( | ||
urn="urn:li:structuredProperty:property1" | ||
), # reference existing structured property | ||
required=True, | ||
) | ||
prompt_2 = FormPromptClass( | ||
id="2", # ensure IDs are globally unique | ||
title="Second Prompt", | ||
type=FormPromptTypeClass.FIELDS_STRUCTURED_PROPERTY, # structured property prompt on dataset schema fields | ||
structuredPropertyParams=StructuredPropertyParamsClass( | ||
urn="urn:li:structuredProperty:property1" | ||
), | ||
required=False, # dataset schema fields prompts should not be required | ||
) | ||
|
||
form_urn = FormUrn("metadata_initiative_1") | ||
form_info_aspect = FormInfoClass( | ||
name="Metadata Initiative 2024", | ||
description="Please respond to this form for metadata compliance purposes", | ||
type=FormTypeClass.VERIFICATION, | ||
actors=FormActorAssignmentClass(owners=True), | ||
prompts=[prompt_1, prompt_2], | ||
) | ||
|
||
event: MetadataChangeProposalWrapper = MetadataChangeProposalWrapper( | ||
entityUrn=str(form_urn), | ||
aspect=form_info_aspect, | ||
) | ||
|
||
# Create rest emitter | ||
rest_emitter = DatahubRestEmitter(gms_server="http://localhost:8080") | ||
rest_emitter.emit(event) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import logging | ||
|
||
from datahub.ingestion.graph.client import DatahubClientConfig, DataHubGraph | ||
from datahub.metadata.urns import FormUrn | ||
|
||
log = logging.getLogger(__name__) | ||
logging.basicConfig(level=logging.INFO) | ||
|
||
graph = DataHubGraph( | ||
config=DatahubClientConfig( | ||
server="http://localhost:8080", | ||
) | ||
) | ||
|
||
form_urn = FormUrn("metadata_initiative_1") | ||
|
||
# Hard delete the form | ||
graph.delete_entity(urn=str(form_urn), hard=True) | ||
# Delete references to this form (must do) | ||
graph.delete_references_to_urn(urn=str(form_urn), dry_run=False) | ||
|
||
log.info(f"Deleted form {form_urn}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import logging | ||
from typing import Union | ||
|
||
from datahub.configuration.kafka import KafkaProducerConnectionConfig | ||
from datahub.emitter.kafka_emitter import DatahubKafkaEmitter, KafkaEmitterConfig | ||
from datahub.emitter.rest_emitter import DataHubRestEmitter | ||
from datahub.metadata.schema_classes import ( | ||
FormPromptClass, | ||
FormPromptTypeClass, | ||
FormTypeClass, | ||
OwnerClass, | ||
OwnershipTypeClass, | ||
StructuredPropertyParamsClass, | ||
) | ||
from datahub.metadata.urns import FormUrn | ||
from datahub.specific.form import FormPatchBuilder | ||
|
||
log = logging.getLogger(__name__) | ||
logging.basicConfig(level=logging.INFO) | ||
|
||
|
||
# Get an emitter, either REST or Kafka, this example shows you both | ||
def get_emitter() -> Union[DataHubRestEmitter, DatahubKafkaEmitter]: | ||
USE_REST_EMITTER = True | ||
if USE_REST_EMITTER: | ||
gms_endpoint = "http://localhost:8080" | ||
return DataHubRestEmitter(gms_server=gms_endpoint) | ||
else: | ||
kafka_server = "localhost:9092" | ||
schema_registry_url = "http://localhost:8081" | ||
return DatahubKafkaEmitter( | ||
config=KafkaEmitterConfig( | ||
connection=KafkaProducerConnectionConfig( | ||
bootstrap=kafka_server, schema_registry_url=schema_registry_url | ||
) | ||
) | ||
) | ||
|
||
|
||
# input your unique form ID | ||
form_urn = FormUrn("metadata_initiative_1") | ||
|
||
# example prompts to add, must reference an existing structured property | ||
new_prompt = FormPromptClass( | ||
id="abcd", | ||
title="title", | ||
type=FormPromptTypeClass.STRUCTURED_PROPERTY, | ||
structuredPropertyParams=StructuredPropertyParamsClass( | ||
"urn:li:structuredProperty:io.acryl.test" | ||
), | ||
required=True, | ||
) | ||
new_prompt2 = FormPromptClass( | ||
id="1234", | ||
title="title", | ||
type=FormPromptTypeClass.FIELDS_STRUCTURED_PROPERTY, | ||
structuredPropertyParams=StructuredPropertyParamsClass( | ||
"urn:li:structuredProperty:io.acryl.test" | ||
), | ||
required=True, | ||
) | ||
|
||
with get_emitter() as emitter: | ||
for patch_mcp in ( | ||
FormPatchBuilder(str(form_urn)) | ||
.add_owner( | ||
OwnerClass( | ||
owner="urn:li:corpuser:jdoe", type=OwnershipTypeClass.TECHNICAL_OWNER | ||
) | ||
) | ||
.set_name("New Name") | ||
.set_description("New description here") | ||
.set_type(FormTypeClass.VERIFICATION) | ||
.set_ownership_form(True) | ||
.add_prompts([new_prompt, new_prompt2]) | ||
.build() | ||
): | ||
emitter.emit(patch_mcp) |
Oops, something went wrong.