Skip to content

Commit 20a6324

Browse files
authored
Merge branch 'main' into APP-6579
2 parents 1e865d3 + 06fe81f commit 20a6324

File tree

7 files changed

+284
-21
lines changed

7 files changed

+284
-21
lines changed

pyatlan/cache/connection_cache.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,18 @@ def __init__(
175175
elif isinstance(connection, str):
176176
tokens = connection.split("/")
177177
if len(tokens) > 1:
178-
self.type = AtlanConnectorType(tokens[0]).value # type: ignore[call-arg]
179-
self.name = connection[len(tokens[0]) + 1 :] # noqa
178+
# Try enum conversion; fallback to custom connector if it fails
179+
try:
180+
self.type = AtlanConnectorType(tokens[0]).value # type: ignore[call-arg]
181+
self.name = connection[len(tokens[0]) + 1 :] # noqa
182+
except ValueError:
183+
custom_connector = AtlanConnectorType.CREATE_CUSTOM(
184+
# Ensure the enum name is converted to UPPER_SNAKE_CASE from kebab-case
185+
name=tokens[0].replace("-", "_").upper(),
186+
value=tokens[0],
187+
)
188+
self.type = custom_connector.value
189+
self.name = connection[len(tokens[0]) + 1 :]
180190

181191
def __hash__(self):
182192
return hash((self.name, self.type))

pyatlan/model/enums.py

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,21 @@ class AtlanConnectionCategory(str, Enum):
140140
CUSTOM = "custom"
141141

142142

143-
class AtlanConnectorType(str, Enum):
143+
class AtlanConnectorType(str, Enum, metaclass=utils.ExtendableEnumMeta):
144144
category: AtlanConnectionCategory
145145

146+
@classmethod
147+
def get_values(cls):
148+
return [member.value for member in cls._member_map_.values()]
149+
150+
@classmethod
151+
def get_names(cls):
152+
return list(cls._member_map_.keys())
153+
154+
@classmethod
155+
def get_items(cls):
156+
return [(name, member.value) for name, member in cls._member_map_.items()]
157+
146158
@classmethod
147159
def _get_connector_type_from_qualified_name(
148160
cls, qualified_name: str
@@ -152,12 +164,17 @@ def _get_connector_type_from_qualified_name(
152164
raise ValueError(
153165
f"Qualified name '{qualified_name}' does not contain enough segments."
154166
)
155-
connector_type_key = tokens[1].upper()
156-
# Check if the connector_type_key exists in AtlanConnectorType
167+
168+
connector_value = tokens[1]
169+
# Ensure the enum name is converted to UPPER_SNAKE_CASE from kebab-case
170+
connector_type_key = tokens[1].replace("-", "_").upper()
171+
172+
# Check if the connector_type_key exists in AtlanConnectorType;
173+
# if so, return it directly. Otherwise, it may be a custom type.
157174
if connector_type_key not in AtlanConnectorType.__members__:
158-
raise ValueError(
159-
f"Could not determine AtlanConnectorType from '{qualified_name}'; "
160-
f"'{connector_type_key}' is not a valid connector type."
175+
return AtlanConnectorType.CREATE_CUSTOM(
176+
name=connector_type_key,
177+
value=connector_value,
161178
)
162179
return AtlanConnectorType[connector_type_key]
163180

@@ -169,6 +186,12 @@ def __new__(
169186
obj.category = category
170187
return obj
171188

189+
@classmethod
190+
def CREATE_CUSTOM(
191+
cls, name: str, value: str, category=AtlanConnectionCategory.CUSTOM
192+
) -> "AtlanConnectorType":
193+
return cls.add_value(name, value, category)
194+
172195
def to_qualified_name(self):
173196
return f"default/{self.value}/{int(utils.get_epoch_timestamp())}"
174197

@@ -193,14 +216,22 @@ def get_connector_name(
193216
fields = qualified_name.split("/")
194217
if len(fields) != qualified_name_len:
195218
raise ValueError(err)
219+
220+
connector_value = fields[1]
221+
# Try enum conversion; fallback to custom connector if it fails
196222
try:
197-
connector_name = AtlanConnectorType(fields[1]).value # type:ignore
198-
if attribute_name != "connection_qualified_name":
199-
connection_qn = f"{fields[0]}/{fields[1]}/{fields[2]}"
200-
return connection_qn, connector_name
201-
return connector_name
202-
except ValueError as e:
203-
raise ValueError(err) from e
223+
connector_name = AtlanConnectorType(connector_value).value # type: ignore
224+
except ValueError:
225+
custom_connection = AtlanConnectorType.CREATE_CUSTOM(
226+
# Ensure the enum name is converted to UPPER_SNAKE_CASE from kebab-case
227+
name=connector_value.replace("-", "_").upper(),
228+
value=connector_value,
229+
)
230+
connector_name = custom_connection.value
231+
if attribute_name != "connection_qualified_name":
232+
connection_qn = f"{fields[0]}/{fields[1]}/{fields[2]}"
233+
return connection_qn, connector_name
234+
return connector_name
204235

205236
SNOWFLAKE = ("snowflake", AtlanConnectionCategory.WAREHOUSE)
206237
TABLEAU = ("tableau", AtlanConnectionCategory.BI)

pyatlan/utils.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from concurrent.futures import ThreadPoolExecutor
1212
from contextvars import ContextVar, copy_context
1313
from datetime import datetime
14-
from enum import Enum
14+
from enum import Enum, EnumMeta
1515
from functools import reduce, wraps
1616
from typing import Any, Dict, List, Mapping, Optional
1717

@@ -518,4 +518,20 @@ def _fn():
518518
var.set(value)
519519
return fn(*args, **kwargs)
520520

521-
return super().submit(_fn)
521+
522+
class ExtendableEnumMeta(EnumMeta):
523+
def __init__(cls, name, bases, namespace):
524+
super().__init__(name, bases, namespace)
525+
cls._additional_members = {}
526+
527+
def add_value(cls, name, value, category=None):
528+
member = str.__new__(cls, value)
529+
member._name_ = name
530+
member._value_ = value
531+
member.category = category
532+
533+
cls._member_map_[name] = member
534+
cls._value2member_map_[value] = member
535+
cls._member_names_.append(name)
536+
cls._additional_members[name] = member
537+
return member

tests/integration/connection_test.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# SPDX-License-Identifier: Apache-2.0
22
# Copyright 2022 Atlan Pte. Ltd.
3+
from typing import Generator
4+
35
import pytest
46

57
from pyatlan.client.atlan import AtlanClient
68
from pyatlan.model.assets import Connection
7-
from pyatlan.model.enums import AtlanConnectorType
8-
from tests.integration.client import TestId
9+
from pyatlan.model.enums import AtlanConnectionCategory, AtlanConnectorType
10+
from tests.integration.client import TestId, delete_asset
911

1012
MODULE_NAME = TestId.make_unique("CONN")
1113

@@ -24,6 +26,29 @@ def create_connection(
2426
)
2527

2628

29+
@pytest.fixture(scope="module")
30+
def custom_connection(client: AtlanClient) -> Generator[Connection, None, None]:
31+
CUSTOM_CONNECTOR_TYPE = AtlanConnectorType.CREATE_CUSTOM(
32+
name=f"{MODULE_NAME}_NAME",
33+
value=f"{MODULE_NAME}_type",
34+
category=AtlanConnectionCategory.API,
35+
)
36+
result = create_connection(
37+
client=client, name=MODULE_NAME, connector_type=CUSTOM_CONNECTOR_TYPE
38+
)
39+
yield result
40+
# TODO: proper connection delete workflow
41+
delete_asset(client, guid=result.guid, asset_type=Connection)
42+
43+
44+
def test_custom_connection(custom_connection: Connection):
45+
assert custom_connection.name == MODULE_NAME
46+
assert custom_connection.connector_name == f"{MODULE_NAME}_type"
47+
assert custom_connection.qualified_name
48+
assert f"default/{MODULE_NAME}_type" in custom_connection.qualified_name
49+
assert AtlanConnectorType[f"{MODULE_NAME}_NAME"].value == f"{MODULE_NAME}_type"
50+
51+
2752
def test_invalid_connection(client: AtlanClient):
2853
with pytest.raises(
2954
ValueError, match="One of admin_user, admin_groups or admin_roles is required"

tests/unit/model/a_d_l_s_object_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def test_create():
134134
),
135135
(
136136
ADLS_OBJECT_NAME,
137-
"default/adls-invalid/production",
137+
"default/adls/invalid/production",
138138
"abc",
139139
ADLS_CONTAINER_NAME,
140140
ADLS_CONTAINER_QUALIFIED_NAME,

tests/unit/model/connection_test.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from pyatlan.client.atlan import AtlanClient
77
from pyatlan.client.token import TokenClient
88
from pyatlan.model.assets import Connection
9-
from pyatlan.model.enums import AtlanConnectorType
9+
from pyatlan.model.enums import AtlanConnectionCategory, AtlanConnectorType
1010
from tests.unit.model.constants import CONNECTION_NAME, CONNECTION_QUALIFIED_NAME
1111

1212

@@ -174,6 +174,69 @@ def test_create(
174174
assert sut.admin_roles == set(admin_roles)
175175

176176

177+
@pytest.mark.parametrize(
178+
"name, connector_type, admin_users, admin_groups, admin_roles",
179+
[
180+
(
181+
CONNECTION_NAME,
182+
AtlanConnectorType.CREATE_CUSTOM(
183+
name="FOO", value="foo", category=AtlanConnectionCategory.BI
184+
),
185+
["ernest"],
186+
[],
187+
[],
188+
),
189+
(
190+
CONNECTION_NAME,
191+
AtlanConnectorType.CREATE_CUSTOM(
192+
name="BAR", value="bar", category=AtlanConnectionCategory.API
193+
),
194+
[],
195+
["ernest"],
196+
[],
197+
),
198+
(
199+
CONNECTION_NAME,
200+
AtlanConnectorType.CREATE_CUSTOM(
201+
name="BAZ", value="baz", category=AtlanConnectionCategory.WAREHOUSE
202+
),
203+
[],
204+
[],
205+
["ernest"],
206+
),
207+
],
208+
)
209+
def test_creator_with_custom_type(
210+
name: str,
211+
connector_type: AtlanConnectorType,
212+
admin_users: List[str],
213+
admin_groups: List[str],
214+
admin_roles: List[str],
215+
mock_role_cache,
216+
mock_user_cache,
217+
mock_group_cache,
218+
):
219+
mock_role_cache.validate_idstrs
220+
mock_user_cache.validate_names
221+
mock_group_cache.validate_aliases
222+
223+
sut = Connection.create(
224+
name=name,
225+
connector_type=connector_type,
226+
admin_users=admin_users,
227+
admin_groups=admin_groups,
228+
admin_roles=admin_roles,
229+
)
230+
231+
assert sut.name == name
232+
assert sut.qualified_name
233+
assert sut.qualified_name[:20] == connector_type.to_qualified_name()[:20]
234+
assert sut.connector_name == connector_type.value
235+
assert sut.admin_users == set(admin_users)
236+
assert sut.admin_groups == set(admin_groups)
237+
assert sut.admin_roles == set(admin_roles)
238+
239+
177240
@pytest.mark.parametrize(
178241
"qualified_name, name, message",
179242
[

0 commit comments

Comments
 (0)