Python SDK
Overview
We are now going to present a high-level Python API as a type-safe and gentle wrapper for the OpenMetadata backend.-
The Python SDK is part of the
openmetadata-ingestionbase package. You can install it from PyPI. -
Make sure to use the same
openmetadata-ingestionversion as your server version. For example, if you have the OpenMetadata server at version 2.0.5, you need to install:
- Everything is handled via the API, and
- Data structures (Entity definitions) are at the heart of the solution.
Which Python SDK Should I Use?
Theopenmetadata-ingestion package contains two Python clients. Both talk to the same OpenMetadata API.
For new scripts and jobs, use SDK 2.0. It needs less setup and reads more simply. Use the original client when you need an operation that SDK 2.0 doesn’t offer yet, or when you maintain existing code that already uses it.
Using Both Clients in One Script
Both ship in the same package, so one script can use both. Set up each one separately: callconfigure() for SDK 2.0 and build an OpenMetadata client for the original client.
Migrating an Existing Job
Change one call at a time:Tables.create(request) also updates an existing table, the same as create_or_update.
Three details differ from what you might expect:
- In
configure(host=...), include/apiat the end of the URL, the same ashostPortin the original client. - When you list entities, pass query filters as
filters={"databaseSchema": "service.database.schema"}and read the results from.entities. - With the original client, set
authProvider="openmetadata"when you authenticate with a JWT token. SDK 2.0 doesn’t need anauthProvider.
SDK 2.0 Quick Start
Connect withconfigure(), then create, update, and delete a table:
name, id, and fullyQualifiedName are wrapper objects. Use .root to get the plain value, for example table.fullyQualifiedName.root.
Call configure() with no arguments to read OPENMETADATA_HOST and OPENMETADATA_JWT_TOKEN from your environment.
Introducing the Python API (Original Client)
Let’s suppose that we have our local OpenMetadata server running athttp:localhost:8585. We can play with it with simple cURL or httpie commands, and if we just want to take a look at the Entity instances we have lying around, that might probably be enough.
However, let’s imagine that we want to create or update an ML Model Entity with a PUT. To do so, we need to make sure that we are providing a proper JSON, covering all the attributes and types required by the Entity definition.
By reviewing the JSON Schema for the create operation and the fields definitions of the Entity, we could come up with a rather simple description of a toy ML Model:
Using Generated Sources
As OpenMetadata is a data-centric solution, we need to make sure we have the right ingredients at all times. That is why we have developed a high-level Python API, using pydantic models automatically generated from the JSON Schemas.OBS: If you are using a published version of the Ingestion Framework, you are already good to go, as we package the code with the metadata.generated module. If you are developing a new feature, you can get more information here.This API wrapper helps developers and consumers in:
- Validating data during development and with specific error messages at runtime,
- Receiving typed responses to ease further processing.
pydantic the example above can be rewritten using only Python classes, and thus being able to get help from IDEs and the Python interpreter. We can rewrite the previous JSON as:
One syntax to rule them all
Now that we know how to directly use the pydantic models, we can start showcasing the solution. This module has been built with two main principles in mind:- Reusability: We should be able to support existing and new entities with minimum effort,
- Extensibility: However, we are aware that not all Entities are the same. Some of them may require specific functionalities or slight variations (such as
LineageorLocation), so it should be easy to identify those special methods and create new ones when needed.
OpenMetadata (source) based on Python’s TypeVar. Thanks to this we can exploit the complete power of the pydantic models, having methods with Type Parameters that know how to respond to each Entity.
At the same time, we have the Mixins (source) module, with special extensions to some Entities.
Walkthrough
Let’s use Python’s API to create, update and delete aTable Entity. Choosing the Table is a nice starter, as its attributes define the following hierarchy:
1. Initialize OpenMetadata
OpenMetadata is the class holding the connection to the API and handling the requests. We can instantiate this by passing the proper configuration to reach the server API:jwtToken. For a real-world deployment, we can also use different authentication methods and specify other settings of the connection (such as sslConfig).
For the OpenMetadataConnection client, use verifySSL="validate" with sslConfig.caCertificate to validate an HTTPS server using your CA certificate.
The fluent SDK (metadata.sdk.configure) uses separate options: verify_ssl=True verifies the server certificate against the system trust store, or against ca_bundle when supplied. OpenMetadata 2.0.3 fixes a bug that disabled verification when verify_ssl=True. Install the matching 2.0.3 ingestion package to receive this fix.
The following example configures certificate validation with OpenMetadataConnection:
OpenMetadata methods.
An interesting validation we can already make at this point is verifying that the service is reachable and healthy. To do so, we can validate the Bool output from:
2. Create the DatabaseService
Following the hierarchy, we need to start by defining aDatabaseService. This will be system hosting our Database, which will contain the Table.
Recall how we have mainly two types of models:
- Entity definitions, such as
Table,MlModelorTopic. - API definitions, useful when running a
PUT,POSTorPATCHrequest:CreateTable,CreateMlModelorCreateTopic.
pydantic models with the API definitions.
Let’s imagine that we are defining a MySQL:
String definitions for the attributes, as well as specific types when possible, such as serviceType=DatabaseServiceType.Mysql. The less information we need to hardcode, the better.
Another important point here is that the connection definitions are centralized as JSON Schemas. Here you can find the root of all of them.
We can review the information that will be passed to the API by visiting the JSON definition of the class we just instantiated. As all these models are powered by pydantic, this conversion is transparent to us:
create_service variable already holds the proper datatype, there is a single line to execute:
create_or_update will return us the Entity type, so we can explore its attributes easily:
3. Create the Database
We can now repeat the process to create aDatabase Entity. However, if we review the definition of the CreateDatabaseEntityRequest model…
name and service. The type of service, however, is FullyQualifiedEntityName. This is expected, as there we need to pass the information of an existing Entity. In our case, the fullyQualifiedName of the DatabaseService we just created.
The owners field takes a list of EntityReference. To instantiate an EntityReference, pass an id: uuid.UUID and type: str. Specify the id and type of a User.
Querying by name
The id we actually saw it by printing the service_entity JSON. However, let’s imagine that it did not happen, and the only information we have from the DatabaseService is its name.
To retrieve the id, we should then ask the metadata to find our Entity by its FQN:
get_by_name method. This method is the same that we will use for any Entity. This is why as an argument, we need to provide the entity field. Again, instead of relying on error-prone handwritten parameters, we can just pass the pydantic model we expect to get back. In our case, a DatabaseService.
4. Create the Schema
With the addition of the Schema Entity in 0.10, we now also need to create a Schema, which will be the one containing the Tables. As this entity is a link between other entities, an Entity Reference will be required too.5. Create the Table
Now that we have all the preparations ready, we can just reuse the same steps to create theTable:
6. Update the Table
Let’s now update theTable by adding an owner. This will require us to create a User, and then update the Table with it. Afterwards, we will validate that the information has been properly stored.
First, make sure that no owner has been set during the creation:
User:
create_table instance to add the owners field. Use the Create class, because this runs a PUT. Then update the Entity:
updated_table_entity variable, query it to review the owners field. Run get_by_name with the FQN of the table:
7. Cleanup
Finally, we can clean up the Table by running thedelete method: