Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/python-client.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Python Client Tests

on:
push:
paths:
- 'intg/src/main/python/**'
- 'intg/src/test/python/**'
- 'intg/pom.xml'
- '.github/workflows/python-client.yml'
pull_request:
paths:
- 'intg/src/main/python/**'
- 'intg/src/test/python/**'
- 'intg/pom.xml'
- '.github/workflows/python-client.yml'

jobs:
python-client-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install dependencies
run: |
cd intg/src/main/python
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements-dev.txt
pip install -e .

- name: Run unit tests
run: |
cd intg
PYTHONPATH=src/main/python python -B src/test/python/test_atlas_client.py

- name: Lint and type-check
run: |
cd intg/src/main/python
make lint mypy

- name: Build and validate package
run: |
cd intg/src/main/python
make build check
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ test-output
**/build
**/dist
**/apache_atlas.egg-info
**/.tox
**/.venv*
**/__pycache__
**/.mypy_cache
.python-version

# review board
Expand Down
3 changes: 2 additions & 1 deletion atlas-examples/sample-app/src/main/python/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Python Sample App

This is a Python sample app to showcase basic functionality of Atlas. We are using Python client
to call Atlas APIs. Make sure to install Atlas Python client first before trying to run this project. Currently, compatible with Python 3.5+
to call Atlas APIs. Make sure to install Atlas Python client first before trying to run this project.
Requires Python 3.13 or later (see `apache-atlas` 0.0.16+ on PyPI).

## Installation

Expand Down
25 changes: 25 additions & 0 deletions intg/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,31 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>python-test</id>
<goals>
<goal>exec</goal>
</goals>
<phase>test</phase>
<configuration>
<skip>${skipTests}</skip>
<executable>python3</executable>
<environmentVariables>
<PYTHONPATH>${project.basedir}/src/main/python</PYTHONPATH>
</environmentVariables>
<arguments>
<argument>-B</argument>
<argument>src/test/python/test_atlas_client.py</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
19 changes: 19 additions & 0 deletions intg/src/main/python/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

include README.md
include requirements.txt
include LICENSE
22 changes: 15 additions & 7 deletions intg/src/main/python/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,24 @@
# limitations under the License.
##########################################################################

.PHONY: test_unit
PYTHONPATH ?= .
TEST_FILE ?= ../../test/python/test_atlas_client.py

.PHONY: test_unit lint mypy test build check

test_unit:
python3 -b -m pytest tests
PYTHONPATH=$(PYTHONPATH) python3 -B $(TEST_FILE)

lint:
python3 -m flake8
python3 -m flake8 apache_atlas

.PHONY: mypy
mypy:
mypy --ignore-missing-imports --follow-imports=skip --strict-optional --warn-no-return .
python3 -m mypy --ignore-missing-imports --follow-imports=skip --strict-optional --warn-no-return apache_atlas

test: test_unit lint mypy

build:
python3 -m build

.PHONY: test
test: lint mypy
check: build
twine check dist/*
189 changes: 66 additions & 123 deletions intg/src/main/python/README.md
Original file line number Diff line number Diff line change
@@ -1,162 +1,105 @@
# Apache Atlas Python Client

Python library for Apache Atlas.
`apache-atlas` is the official Python client package for Apache Atlas.
It provides typed helpers for entity, type definition, discovery, glossary,
lineage, relationship, and admin APIs.

## Requirements

- Python 3.13 or later
- Apache Atlas server reachable from your Python process

## Installation

Use the package manager [pip](https://pip.pypa.io/en/stable/) to install Python client for Apache Atlas.
Install the client from PyPI:

```bash
> pip install apache-atlas
pip install apache-atlas
```

Verify if apache-atlas client is installed:
```bash
> pip list
For Kerberos authentication, install the optional Kerberos dependency:

Package Version
------------ ---------
apache-atlas 0.0.15
```bash
pip install requests-kerberos
```

## Usage
Verify the installed package:

```python atlas_example.py```
```python
# atlas_example.py

import time
```bash
python -m pip show apache-atlas
```

from apache_atlas.client.base_client import AtlasClient
from apache_atlas.model.instance import AtlasEntity, AtlasEntityWithExtInfo, AtlasEntitiesWithExtInfo, AtlasRelatedObjectId
from apache_atlas.model.enums import EntityOperation
## Supported Clients

`AtlasClient` is the main entry point. It exposes the following API clients:

## Step 1: create a client to connect to Apache Atlas server
client = AtlasClient('http://localhost:21000', ('admin', 'atlasR0cks!'))
- `entity`: create, read, update, delete entities and classifications
- `typedef`: manage type definitions
- `discovery`: search and full-text queries
- `glossary`: glossary terms and categories
- `lineage`: lineage graph queries
- `relationship`: relationship CRUD
- `admin`: server admin operations

# For Kerberos authentication, use HTTPKerberosAuth as shown below
#
# from requests_kerberos import HTTPKerberosAuth
#
# client = AtlasClient('http://localhost:21000', HTTPKerberosAuth())
## Quick Start

# to disable SSL certificate validation (not recommended for production use!)
#
# client.session.verify = False
```python
from apache_atlas.client.base_client import AtlasClient
from apache_atlas.model.instance import AtlasEntity, AtlasEntityWithExtInfo

client = AtlasClient('http://localhost:21000', ('admin', 'atlasR0cks!'))

## Step 2: Let's create a database entity
test_db = AtlasEntity({ 'typeName': 'hive_db' })
test_db.attributes = { 'name': 'test_db', 'clusterName': 'prod', 'qualifiedName': 'test_db@prod' }
test_db = AtlasEntity({'typeName': 'hive_db'})
test_db.attributes = {'name': 'test_db', 'clusterName': 'prod', 'qualifiedName': 'test_db@prod'}

entity_info = AtlasEntityWithExtInfo()
entity_info.entity = test_db

print('Creating test_db')

resp = client.entity.create_entity(entity_info)
guid = resp.get_assigned_guid(test_db.guid)
print(f'created test_db: guid={guid}')
```

guid_db = resp.get_assigned_guid(test_db.guid)

print(' created test_db: guid=' + guid_db)


## Step 3: Let's create a table entity, and two column entities - in one call
test_tbl = AtlasEntity({ 'typeName': 'hive_table' })
test_tbl.attributes = { 'name': 'test_tbl', 'qualifiedName': 'test_db.test_tbl@prod' }
test_tbl.relationshipAttributes = { 'db': AtlasRelatedObjectId({ 'guid': guid_db }) }

test_col1 = AtlasEntity({ 'typeName': 'hive_column' })
test_col1.attributes = { 'name': 'test_col1', 'type': 'string', 'qualifiedName': 'test_db.test_tbl.test_col1@prod' }
test_col1.relationshipAttributes = { 'table': AtlasRelatedObjectId({ 'guid': test_tbl.guid }) }

test_col2 = AtlasEntity({ 'typeName': 'hive_column' })
test_col2.attributes = { 'name': 'test_col2', 'type': 'string', 'qualifiedName': 'test_db.test_tbl.test_col2@prod' }
test_col2.relationshipAttributes = { 'table': AtlasRelatedObjectId({ 'guid': test_tbl.guid }) }

entities_info = AtlasEntitiesWithExtInfo()
entities_info.entities = [ test_tbl, test_col1, test_col2 ]

print('Creating test_tbl')

resp = client.entity.create_entities(entities_info)

guid_tbl = resp.get_assigned_guid(test_tbl.guid)
guid_col1 = resp.get_assigned_guid(test_col1.guid)
guid_col2 = resp.get_assigned_guid(test_col2.guid)

print(' created test_tbl: guid=' + guid_tbl)
print(' created test_tbl.test_col1: guid=' + guid_col1)
print(' created test_tbl.test_col2: guid=' + guid_col2)


## Step 4: Let's create a view entity that feeds from the table created earlier
# Also create a lineage between the table and the view, and lineages between their columns as well
test_view = AtlasEntity({ 'typeName': 'hive_table' })
test_view.attributes = { 'name': 'test_view', 'qualifiedName': 'test_db.test_view@prod' }
test_view.relationshipAttributes = { 'db': AtlasRelatedObjectId({ 'guid': guid_db }) }

test_view_col1 = AtlasEntity({ 'typeName': 'hive_column' })
test_view_col1.attributes = { 'name': 'test_col1', 'type': 'string', 'qualifiedName': 'test_db.test_view.test_col1@prod' }
test_view_col1.relationshipAttributes = { 'table': AtlasRelatedObjectId({ 'guid': test_view.guid }) }

test_view_col2 = AtlasEntity({ 'typeName': 'hive_column' })
test_view_col2.attributes = { 'name': 'test_col2', 'type': 'string', 'qualifiedName': 'test_db.test_view.test_col2@prod' }
test_view_col2.relationshipAttributes = { 'table': AtlasRelatedObjectId({ 'guid': test_view.guid }) }

test_process = AtlasEntity({ 'typeName': 'hive_process' })
test_process.attributes = { 'name': 'create_test_view', 'userName': 'admin', 'operationType': 'CREATE', 'qualifiedName': 'create_test_view@prod' }
test_process.attributes['queryText'] = 'create view test_view as select * from test_tbl'
test_process.attributes['queryPlan'] = '<queryPlan>'
test_process.attributes['queryId'] = '<queryId>'
test_process.attributes['startTime'] = int(time.time() * 1000)
test_process.attributes['endTime'] = int(time.time() * 1000)
test_process.relationshipAttributes = { 'inputs': [ AtlasRelatedObjectId({ 'guid': guid_tbl }) ], 'outputs': [ AtlasRelatedObjectId({ 'guid': test_view.guid }) ] }
## Authentication

test_col1_lineage = AtlasEntity({ 'typeName': 'hive_column_lineage' })
test_col1_lineage.attributes = { 'name': 'test_view.test_col1 lineage', 'depenendencyType': 'read', 'qualifiedName': 'test_db.test_view.test_col1@prod' }
test_col1_lineage.attributes['query'] = { 'guid': test_process.guid }
test_col1_lineage.relationshipAttributes = { 'inputs': [ AtlasRelatedObjectId({ 'guid': guid_col1 }) ], 'outputs': [ AtlasRelatedObjectId({ 'guid': test_view_col1.guid }) ] }
Use the authentication mechanism configured for your Atlas deployment:

test_col2_lineage = AtlasEntity({ 'typeName': 'hive_column_lineage' })
test_col2_lineage.attributes = { 'name': 'test_view.test_col2 lineage', 'depenendencyType': 'read', 'qualifiedName': 'test_db.test_view.test_col2@prod' }
test_col2_lineage.attributes['query'] = { 'guid': test_process.guid }
test_col2_lineage.relationshipAttributes = { 'inputs': [ AtlasRelatedObjectId({ 'guid': guid_col2 }) ], 'outputs': [ AtlasRelatedObjectId({ 'guid': test_view_col2.guid }) ] }
- Basic auth: pass a `(username, password)` tuple to `AtlasClient`.
- Kerberos/SPNEGO: pass `requests_kerberos.HTTPKerberosAuth()` after installing
`requests-kerberos`.
- To disable SSL certificate validation (not recommended for production):
`client.session.verify = False`

entities_info = AtlasEntitiesWithExtInfo()
entities_info.entities = [ test_process, test_col1_lineage, test_col2_lineage ]
Example Kerberos setup:

entities_info.add_referenced_entity(test_view)
entities_info.add_referenced_entity(test_view_col1)
entities_info.add_referenced_entity(test_view_col2)
```python
from requests_kerberos import HTTPKerberosAuth
from apache_atlas.client.base_client import AtlasClient

print('Creating test_view')
client = AtlasClient('http://localhost:21000', HTTPKerberosAuth())
```

resp = client.entity.create_entities(entities_info)
## Examples and Code References

guid_view = resp.get_assigned_guid(test_view.guid)
guid_view_col1 = resp.get_assigned_guid(test_view_col1.guid)
guid_view_col2 = resp.get_assigned_guid(test_view_col2.guid)
guid_process = resp.get_assigned_guid(test_process.guid)
guid_col1_lineage = resp.get_assigned_guid(test_col1_lineage.guid)
guid_col2_lineage = resp.get_assigned_guid(test_col2_lineage.guid)
Runnable examples and additional usage patterns:

print(' created test_view: guid=' + guid_view)
print(' created test_view.test_col1: guid=' + guid_view_col1)
print(' created test_view.test_col2: guid=' + guid_view_col1)
print(' created test_view lineage: guid=' + guid_process)
print(' created test_col1 lineage: guid=' + guid_col1_lineage)
print(' created test_col2 lineage: guid=' + guid_col2_lineage)
- Sample app:
[`atlas-examples/sample-app/src/main/python/sample_client.py`](https://github.com/apache/atlas/blob/master/atlas-examples/sample-app/src/main/python/sample_client.py)
- Entity, lineage, glossary, discovery, and typedef examples in the same directory
- Unit tests:
[`intg/src/test/python/test_atlas_client.py`](https://github.com/apache/atlas/blob/master/intg/src/test/python/test_atlas_client.py)

Run unit tests from the `intg/` module:

## Step 5: Finally, cleanup by deleting entities created above
print('Deleting entities')
```bash
PYTHONPATH=src/main/python python -B src/test/python/test_atlas_client.py
```

resp = client.entity.delete_entities_by_guids([ guid_col1_lineage, guid_col2_lineage, guid_process, guid_view, guid_tbl, guid_db ])
## Version 0.0.16 Highlights

deleted_count = len(resp.mutatedEntities[EntityOperation.DELETE.name]) if resp and resp.mutatedEntities and EntityOperation.DELETE.name in resp.mutatedEntities else 0
- Requires Python 3.13 or later (`python_requires='>=3.13'`)
- Updated `requests` dependency floor to `>=2.34.2`
- Expanded unit test coverage for client and model coercion

print(' ' + str(deleted_count) + ' entities deleted')
```
For more examples, checkout `sample-app` python project in [atlas-examples](https://github.com/apache/atlas/blob/master/atlas-examples/sample-app/src/main/python/sample_client.py) module.
**Breaking change:** version 0.0.16 and later require Python 3.13+. Use 0.0.15
if you need an older Python runtime.
Loading
Loading