diff --git a/.github/workflows/python-client.yml b/.github/workflows/python-client.yml
new file mode 100644
index 00000000000..751449cd2d4
--- /dev/null
+++ b/.github/workflows/python-client.yml
@@ -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
diff --git a/.gitignore b/.gitignore
index a25f185f9a7..59be9e98823 100755
--- a/.gitignore
+++ b/.gitignore
@@ -50,6 +50,10 @@ test-output
**/build
**/dist
**/apache_atlas.egg-info
+**/.tox
+**/.venv*
+**/__pycache__
+**/.mypy_cache
.python-version
# review board
diff --git a/atlas-examples/sample-app/src/main/python/README.md b/atlas-examples/sample-app/src/main/python/README.md
index b199ecd6eb2..195e77a70d1 100644
--- a/atlas-examples/sample-app/src/main/python/README.md
+++ b/atlas-examples/sample-app/src/main/python/README.md
@@ -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
diff --git a/intg/pom.xml b/intg/pom.xml
index 35b0f2f45a8..5e266cfc7f0 100644
--- a/intg/pom.xml
+++ b/intg/pom.xml
@@ -154,6 +154,31 @@
org.apache.maven.plugins
maven-compiler-plugin
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.1.0
+
+
+ python-test
+
+ exec
+
+ test
+
+ ${skipTests}
+ python3
+
+ ${project.basedir}/src/main/python
+
+
+ -B
+ src/test/python/test_atlas_client.py
+
+
+
+
+
diff --git a/intg/src/main/python/MANIFEST.in b/intg/src/main/python/MANIFEST.in
new file mode 100644
index 00000000000..39f94ab6b14
--- /dev/null
+++ b/intg/src/main/python/MANIFEST.in
@@ -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
diff --git a/intg/src/main/python/Makefile b/intg/src/main/python/Makefile
index ccfe604374f..d74b6d810ca 100644
--- a/intg/src/main/python/Makefile
+++ b/intg/src/main/python/Makefile
@@ -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/*
diff --git a/intg/src/main/python/README.md b/intg/src/main/python/README.md
index a53a1e1bd07..b9fb8d17711 100644
--- a/intg/src/main/python/README.md
+++ b/intg/src/main/python/README.md
@@ -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'] = ''
-test_process.attributes['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.
diff --git a/intg/src/main/python/apache_atlas/exceptions.py b/intg/src/main/python/apache_atlas/exceptions.py
index 812ba1fbd7b..17cda91101d 100644
--- a/intg/src/main/python/apache_atlas/exceptions.py
+++ b/intg/src/main/python/apache_atlas/exceptions.py
@@ -34,6 +34,14 @@ def __init__(self, api, response):
except Exception:
body = response.content
- msg = "Metadata service API {method} {path} failed with status {status}. Response body is: {body}".format(**{'method': api.method, 'path': api.path, 'status': response.status_code, 'body': body})
+ msg = (
+ "Metadata service API {method} {path} failed with status {status}. "
+ "Response body is: {body}"
+ ).format(
+ method=api.method,
+ path=api.path,
+ status=response.status_code,
+ body=body,
+ )
Exception.__init__(self, msg)
diff --git a/intg/src/main/python/requirements-dev.txt b/intg/src/main/python/requirements-dev.txt
index 26c42959fe0..d735a5683e5 100644
--- a/intg/src/main/python/requirements-dev.txt
+++ b/intg/src/main/python/requirements-dev.txt
@@ -1,2 +1,5 @@
flake8
mypy
+pytest
+build
+twine
diff --git a/intg/src/main/python/requirements.txt b/intg/src/main/python/requirements.txt
index 3b87e26dac3..02fa2ea38b6 100644
--- a/intg/src/main/python/requirements.txt
+++ b/intg/src/main/python/requirements.txt
@@ -1 +1 @@
-requests>=2.24
\ No newline at end of file
+requests>=2.34.2
diff --git a/intg/src/main/python/setup.py b/intg/src/main/python/setup.py
index 034b3fc758c..3291491ec9c 100644
--- a/intg/src/main/python/setup.py
+++ b/intg/src/main/python/setup.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env/python
+#!/usr/bin/env python
#
# Licensed to the Apache Software Foundation (ASF) under one
@@ -17,34 +17,42 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from pathlib import Path
+
from setuptools import setup, find_packages
-# External dependencies
-requirements = ['requests>=2.24']
+HERE = Path(__file__).parent.resolve()
+
+
+def read_requirements(filename):
+ with open(HERE / filename, "r", encoding="utf-8") as fh:
+ return [line.strip() for line in fh if line.strip() and not line.startswith("#")]
+
-long_description = ''
-with open("README.md", "r") as fh:
+with open(HERE / "README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setup(
name='apache-atlas',
- version='0.0.15',
+ version='0.0.16',
author="Apache Atlas",
author_email='dev@atlas.apache.org',
description="Apache Atlas Python Client",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/apache/atlas/tree/master/intg/src/main/python",
- license='Apache LICENSE 2.0',
+ license="Apache-2.0",
+ license_files=("LICENSE",),
classifiers=[
- "Programming Language :: Python :: 3.6",
- "License :: OSI Approved :: Apache Software License",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.13",
"Operating System :: OS Independent",
],
packages=find_packages(),
- install_requires=requirements,
+ install_requires=read_requirements("requirements.txt"),
include_package_data=True,
zip_safe=False,
keywords='atlas client, apache atlas',
- python_requires='>=3.6',
+ python_requires='>=3.13',
)
diff --git a/intg/src/main/python/tox.ini b/intg/src/main/python/tox.ini
index 191a709ef35..a9cfa6e3c54 100644
--- a/intg/src/main/python/tox.ini
+++ b/intg/src/main/python/tox.ini
@@ -16,5 +16,20 @@
# limitations under the License.
##########################################################################
+[tox]
+envlist = py313
+skipsdist = true
+
+[testenv]
+setenv =
+ PYTHONPATH={toxinidir}
+deps =
+ -rrequirements.txt
+ -rrequirements-dev.txt
+commands =
+ python -B ../../test/python/test_atlas_client.py
+ python -m flake8 apache_atlas
+ python -m mypy --ignore-missing-imports --follow-imports=skip --strict-optional --warn-no-return apache_atlas
+
[flake8]
max-line-length = 120
diff --git a/intg/src/test/python/test_atlas_client.py b/intg/src/test/python/test_atlas_client.py
new file mode 100644
index 00000000000..9fb97b374ff
--- /dev/null
+++ b/intg/src/test/python/test_atlas_client.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+
+#
+# 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.
+
+import unittest
+
+from unittest.mock import Mock, patch
+
+from apache_atlas.exceptions import AtlasServiceException
+from apache_atlas.model.enums import EntityOperation, EntityStatus
+from apache_atlas.model.instance import AtlasClassification, AtlasEntity, AtlasEntityWithExtInfo
+from apache_atlas.utils import API, HTTPMethod, HTTPStatus, type_coerce
+
+try:
+ from apache_atlas.client.base_client import AtlasClient
+ from apache_atlas.client.entity import EntityClient
+except ModuleNotFoundError: # requests not installed
+ exit() # skipping unit tests
+
+
+class MockResponse:
+ def __init__(self, status_code, response=None, content=None):
+ self.status_code = status_code
+ self.response = response
+ self.content = content
+
+ def json(self):
+ return self.response
+
+
+class TestAtlasClient(unittest.TestCase):
+ URL = 'http://localhost:21000'
+ AUTH = ('admin', 'atlasR0cks!')
+
+ @patch('apache_atlas.client.base_client.Session')
+ def test_get_entity_service_unavailable(self, mock_session):
+ mock_session.return_value.request.return_value = MockResponse(HTTPStatus.SERVICE_UNAVAILABLE)
+
+ with self.assertRaises(AtlasServiceException):
+ AtlasClient(self.URL, self.AUTH).entity.get_entity_by_guid('guid-1')
+
+ @patch('apache_atlas.client.base_client.Session')
+ def test_get_entity_success(self, mock_session):
+ response = {
+ 'entity': {
+ 'typeName': 'hive_db',
+ 'attributes': {'name': 'test_db', 'qualifiedName': 'test_db@prod'},
+ },
+ }
+ mock_session.return_value.request.return_value = MockResponse(
+ HTTPStatus.OK, response=response, content='Success')
+
+ result = AtlasClient(self.URL, self.AUTH).entity.get_entity_by_guid('guid-1')
+
+ self.assertIsInstance(result, AtlasEntityWithExtInfo)
+ self.assertEqual('hive_db', result.entity.typeName)
+
+ @patch('apache_atlas.client.base_client.Session')
+ def test_get_entity_unexpected_status_code(self, mock_session):
+ mock_response = Mock()
+ mock_response.text = 'Internal Server Error'
+ mock_response.content = 'Internal Server Error'
+ mock_response.status_code = 500
+ mock_session.return_value.request.return_value = mock_response
+
+ with self.assertRaises(AtlasServiceException) as context:
+ AtlasClient(self.URL, self.AUTH).entity.get_entity_by_guid('guid-1')
+
+ self.assertIn('500', str(context.exception))
+
+ def test_url_missing_format(self):
+ with self.assertRaises(KeyError):
+ API("{arg1}test{arg2}path{arg3}", HTTPMethod.GET, HTTPStatus.OK).format_path(
+ {'arg1': 1, 'arg2': 2})
+
+ def test_url_invalid_format(self):
+ with self.assertRaises(TypeError):
+ API("{}test{}path{}", HTTPMethod.GET, HTTPStatus.OK).format_path({'1', '2'})
+
+ def test_multipart_urljoin(self):
+ api = API("api/atlas/v2/entity/", HTTPMethod.GET, HTTPStatus.OK)
+ path = api.multipart_urljoin("api/atlas/v2/entity/", "guid", "header")
+
+ self.assertEqual("api/atlas/v2/entity/guid/header", path)
+
+
+class TestAtlasModel(unittest.TestCase):
+ def test_entity_type_coercion(self):
+ entity = AtlasEntity({
+ 'typeName': 'hive_table',
+ 'attributes': {'name': 'test_tbl', 'qualifiedName': 'test_db.test_tbl@prod'},
+ 'classifications': [{
+ 'typeName': 'PII',
+ 'entityGuid': 'guid-1',
+ }],
+ })
+
+ entity.type_coerce_attrs()
+
+ self.assertIsInstance(entity.classifications[0], AtlasClassification)
+ self.assertEqual('PII', entity.classifications[0].typeName)
+
+ def test_type_coerce_from_dict(self):
+ result = type_coerce(
+ {'typeName': 'hive_db', 'attributes': {'name': 'test_db'}},
+ AtlasEntity,
+ )
+
+ self.assertIsInstance(result, AtlasEntity)
+ self.assertEqual('hive_db', result.typeName)
+
+ def test_entity_operation_enum(self):
+ self.assertEqual('CREATE', EntityOperation.CREATE.name)
+ self.assertEqual('DELETE', EntityOperation.DELETE.name)
+
+ def test_entity_status_enum(self):
+ self.assertEqual(0, EntityStatus.ACTIVE.value)
+
+
+class TestEntityClient(unittest.TestCase):
+ def test_get_entity_by_guid_calls_expected_api(self):
+ atlas_client = Mock()
+ atlas_client.call_api.return_value = AtlasEntityWithExtInfo({
+ 'entity': {'typeName': 'hive_db', 'attributes': {'name': 'test_db'}},
+ })
+
+ result = EntityClient(atlas_client).get_entity_by_guid(
+ 'guid-1', min_ext_info=True, ignore_relationships=True)
+
+ atlas_client.call_api.assert_called_once()
+ call_args = atlas_client.call_api.call_args
+ self.assertEqual(AtlasEntityWithExtInfo, call_args[0][1])
+ query_params = call_args[0][2] if len(call_args[0]) > 2 else call_args[1].get('query_params')
+ self.assertEqual({'minExtInfo': True, 'ignoreRelationships': True}, query_params)
+ self.assertIsInstance(result, AtlasEntityWithExtInfo)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pom.xml b/pom.xml
index c0f35938789..462140854cd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1473,6 +1473,13 @@
**/.docz/**
**/*.svg
+
+ **/.tox/**
+ **/.venv*/**
+ **/__pycache__/**
+ **/.mypy_cache/**
+ **/*.egg-info/**
+