Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Added
~~~~~

- ``ConnectorTable`` has a new classmethod, ``extend`` which allows users to
add new connectors to the mapping. ``ConnectorTable.extend()`` returns a new
connector table subclass and does not modify the original. (:pr:`NUMBER`)
Comment thread
m1yag1 marked this conversation as resolved.
52 changes: 50 additions & 2 deletions src/globus_sdk/services/gcs/connector_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class ConnectorTable:
'9436da0c-a444-11eb-af93-12704e0d6a4d'
"""

_connectors: tuple[tuple[str, str, str], ...] = (
_connectors: t.ClassVar[tuple[tuple[str, str, str], ...]] = (
("ACTIVESCALE", "ActiveScale", "7251f6c8-93c9-11eb-95ba-12704e0d6a4d"),
("AZURE_BLOB", "Azure Blob", "9436da0c-a444-11eb-af93-12704e0d6a4d"),
("BLACKPEARL", "BlackPearl", "7e3f3f5e-350c-4717-891a-2f451c24b0d4"),
Expand Down Expand Up @@ -96,7 +96,7 @@ def all_connectors(cls) -> t.Iterable[GlobusConnectServerConnector]:
yield item

@classmethod
def lookup(cls, name_or_id: str | UUIDLike) -> GlobusConnectServerConnector | None:
def lookup(cls, name_or_id: UUIDLike) -> GlobusConnectServerConnector | None:
"""
Convert a name or ID into a connector object.
Returns None if the name or ID is not recognized.
Expand All @@ -116,6 +116,54 @@ def lookup(cls, name_or_id: str | UUIDLike) -> GlobusConnectServerConnector | No
return connector
return None

@classmethod
def extend(
cls,
*,
connector_name: str,
connector_id: UUIDLike,
attribute_name: str | None = None,
) -> type[ConnectorTable]:
"""
Extend the ConnectorTable class with a new connector, returning a new
ConnectorTable subclass.

Usage example:

.. code-block:: pycon

>>> MyTable = ConnectorTable.extend(
... connector_name="Star Trek Transporter",
... connector_id="0b19772d-729a-4c8f-93e1-59d5778cecf3",
... )
>>> obj = MyTable.STAR_TREK_TRANSPORTER
>>> obj.connector_id
'0b19772d-729a-4c8f-93e1-59d5778cecf3'
>>> obj.name
'Star Trek Transporter'

:param connector_name: The name of the connector to add
:param connector_id: The ID of the connector to add
:param attribute_name: The attribute name with which the connector will be
attached to the new subclass. Defaults to the connector name uppercased and
with spaces converted to underscores.
"""
if attribute_name is None:
attribute_name = connector_name.upper().replace(" ", "_")
connector_id_str = str(connector_id)

connectors = cls._connectors + (
(attribute_name, connector_name, connector_id_str),
)
connector_obj = GlobusConnectServerConnector(
name=connector_name, connector_id=connector_id_str
)
return type(
"ExtendedConnectorTable",
(cls,),
{"_connectors": connectors, attribute_name: connector_obj},
)


# "render" the _connectors to live attributes of the ConnectorTable
for _attribute, _name, _id in ConnectorTable._connectors:
Expand Down
48 changes: 48 additions & 0 deletions tests/unit/helpers/gcs/test_connector_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,51 @@ def test_all_connector_attributes_are_assigned():
for attribute in annotated_attributes:
instance = getattr(ConnectorTable, attribute)
assert isinstance(instance, GlobusConnectServerConnector)


def test_table_can_be_extended_with_simple_item():
# don't think too hard about the ethical implications of transporter clones
connector_name = "Star Trek Transporter"
connector_id = uuid.uuid1()

ExtendedTable = ConnectorTable.extend(
connector_name=connector_name, connector_id=connector_id
)

# we get a new subclass named 'ExtendedConnectorTable'
assert issubclass(ExtendedTable, ConnectorTable)
assert ExtendedTable.__name__ == "ExtendedConnectorTable"

# access via name, attribute, and ID all resolve to the same object
data_object = ExtendedTable.lookup(connector_id)
assert isinstance(data_object, GlobusConnectServerConnector)
assert ExtendedTable.STAR_TREK_TRANSPORTER is data_object
assert ExtendedTable.lookup(connector_name) is data_object


def test_table_extended_twice():
connector_id1 = uuid.uuid1()
connector_id2 = uuid.uuid1()

ExtendedTable1 = ConnectorTable.extend(
connector_name="Star Trek Transporter", connector_id=connector_id1
)
ExtendedTable2 = ExtendedTable1.extend(
connector_name="Battlestar Galactica FTL", connector_id=connector_id2
)

# we get new subclasses named 'ExtendedConnectorTable'
assert issubclass(ExtendedTable1, ConnectorTable)
assert ExtendedTable1.__name__ == "ExtendedConnectorTable"
assert issubclass(ExtendedTable2, ExtendedTable1)
assert ExtendedTable2.__name__ == "ExtendedConnectorTable"

# both tables get the same object for connector1
# only table2 has connector2
data_object = ExtendedTable1.lookup(connector_id1)
assert isinstance(data_object, GlobusConnectServerConnector)
assert ExtendedTable2.lookup(connector_id1) is data_object

assert ExtendedTable1.lookup(connector_id2) is None
data_object2 = ExtendedTable2.lookup(connector_id2)
assert isinstance(data_object2, GlobusConnectServerConnector)