-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #955 from sirosen/gcs-connector-map
Introduce GCS `ConnectorTable` as mapping-like
- Loading branch information
Showing
9 changed files
with
277 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
Added | ||
~~~~~ | ||
|
||
- Add ``globus_sdk.ConnectorTable`` which provides information on supported | ||
Globus Connect Server connectors. This object maps names to IDs and vice | ||
versa. (:pr:`NUMBER`) | ||
|
||
Deprecated | ||
~~~~~~~~~~ | ||
|
||
- ``GCSClient.connector_id_to_name`` has been deprecated. Use | ||
``ConnectorTable.lookup`` instead. (:pr:`NUMBER`) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
from __future__ import annotations | ||
|
||
import dataclasses | ||
import re | ||
import typing as t | ||
|
||
from globus_sdk._types import UUIDLike | ||
|
||
_NORMALIZATION_PATTERN = re.compile(r"[_\- ]+") | ||
|
||
|
||
def _normalize_name(name: str) -> str: | ||
return _NORMALIZATION_PATTERN.sub("-", name.strip()).lower() | ||
|
||
|
||
@dataclasses.dataclass | ||
class GlobusConnectServerConnector: | ||
""" | ||
A container for Globus Connect Server Connector descriptions. | ||
Contains a ``name`` and a ``connector_id``. | ||
""" | ||
|
||
name: str | ||
connector_id: str | ||
|
||
|
||
class ConnectorTable: | ||
""" | ||
This class defines the known Globus Connect Server Connectors in a mapping | ||
structure. | ||
It supports access by attribute or via a helper method for doing lookups. | ||
For example, all of the following three usages retrieve the Azure Blob connector: | ||
.. code-block:: pycon | ||
>>> ConnectorTable.AZURE_BLOB | ||
>>> ConnectorTable.lookup("Azure Blob") | ||
>>> ConnectorTable.lookup("9436da0c-a444-11eb-af93-12704e0d6a4d") | ||
Given the results of such a lookup, you can retrieve the canonical name and ID for | ||
a connector like so: | ||
.. code-block:: pycon | ||
>>> connector = ConnectorTable.AZURE_BLOB | ||
>>> connector.name | ||
'Azure Blob' | ||
>>> connector.connector_id | ||
'9436da0c-a444-11eb-af93-12704e0d6a4d' | ||
""" | ||
|
||
_connectors: 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"), | ||
("BOX", "Box", "7c100eae-40fe-11e9-95a3-9cb6d0d9fd63"), | ||
("CEPH", "Ceph", "1b6374b0-f6a4-4cf7-a26f-f262d9c6ca72"), | ||
("DROPBOX", "Dropbox", "49b00fd6-63f1-48ae-b27f-d8af4589f876"), | ||
( | ||
"GOOGLE_CLOUD_STORAGE", | ||
"Google Cloud Storage", | ||
"56366b96-ac98-11e9-abac-9cb6d0d9fd63", | ||
), | ||
("GOOGLE_DRIVE", "Google Drive", "976cf0cf-78c3-4aab-82d2-7c16adbcc281"), | ||
("HPSS", "HPSS", "fb656a17-0f69-4e59-95ff-d0a62ca7bdf5"), | ||
("IRODS", "iRODS", "e47b6920-ff57-11ea-8aaa-000c297ab3c2"), | ||
("POSIX", "POSIX", "145812c8-decc-41f1-83cf-bb2a85a2a70b"), | ||
("POSIX_STAGING", "POSIX Staging", "052be037-7dda-4d20-b163-3077314dc3e6"), | ||
("ONEDRIVE", "OneDrive", "28ef55da-1f97-11eb-bdfd-12704e0d6a4d"), | ||
("S3", "S3", "7643e831-5f6c-4b47-a07f-8ee90f401d23"), | ||
) | ||
|
||
ACTIVESCALE: t.ClassVar[GlobusConnectServerConnector] | ||
AZURE_BLOB: t.ClassVar[GlobusConnectServerConnector] | ||
BLACKPEARL: t.ClassVar[GlobusConnectServerConnector] | ||
BOX: t.ClassVar[GlobusConnectServerConnector] | ||
CEPH: t.ClassVar[GlobusConnectServerConnector] | ||
DROPBOX: t.ClassVar[GlobusConnectServerConnector] | ||
GOOGLE_CLOUD_STORAGE: t.ClassVar[GlobusConnectServerConnector] | ||
GOOGLE_DRIVE: t.ClassVar[GlobusConnectServerConnector] | ||
HPSS: t.ClassVar[GlobusConnectServerConnector] | ||
IRODS: t.ClassVar[GlobusConnectServerConnector] | ||
ONEDRIVE: t.ClassVar[GlobusConnectServerConnector] | ||
POSIX: t.ClassVar[GlobusConnectServerConnector] | ||
POSIX_STAGING: t.ClassVar[GlobusConnectServerConnector] | ||
S3: t.ClassVar[GlobusConnectServerConnector] | ||
|
||
@classmethod | ||
def all_connectors(cls) -> t.Iterable[GlobusConnectServerConnector]: | ||
""" | ||
Return an iterator of all known connectors. | ||
""" | ||
for attribute, _, _ in cls._connectors: | ||
item: GlobusConnectServerConnector = getattr(cls, attribute) | ||
yield item | ||
|
||
@classmethod | ||
def lookup(cls, name_or_id: str | UUIDLike) -> GlobusConnectServerConnector | None: | ||
""" | ||
Convert a name or ID into a connector object. | ||
Returns None if the name or ID is not recognized. | ||
Names are normalized before lookup so that they are case-insensitive and | ||
spaces, dashes, and underscores are all treated equivalently. For | ||
example, ``Google Drive``, ``google-drive``, and ``gOOgle_dRiVe`` are | ||
all equivalent. | ||
:param name_or_id: The name or ID of the connector | ||
""" | ||
normalized = _normalize_name(str(name_or_id)) | ||
for connector in cls.all_connectors(): | ||
if normalized == connector.connector_id or normalized == _normalize_name( | ||
connector.name | ||
): | ||
return connector | ||
return None | ||
|
||
|
||
# "render" the _connectors to live attributes of the ConnectorTable | ||
for _attribute, _name, _id in ConnectorTable._connectors: | ||
setattr( | ||
ConnectorTable, | ||
_attribute, | ||
GlobusConnectServerConnector(name=_name, connector_id=_id), | ||
) | ||
del _attribute, _name, _id |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
from __future__ import annotations | ||
|
||
import inspect | ||
import sys | ||
import uuid | ||
|
||
import pytest | ||
|
||
from globus_sdk import ConnectorTable, GCSClient, GlobusConnectServerConnector, exc | ||
|
||
|
||
def test_deprecated_connector_lookup_method_warns(): | ||
client = GCSClient("foo.bar.example.org") | ||
with pytest.warns(exc.RemovedInV4Warning): | ||
assert client.connector_id_to_name("foo") is None | ||
|
||
|
||
@pytest.mark.parametrize("connector_data", ConnectorTable._connectors) | ||
def test_lookup_by_attribute(connector_data): | ||
attrname, connector_name, _ = connector_data | ||
|
||
connector = getattr(ConnectorTable, attrname) | ||
assert connector.name == connector_name | ||
|
||
|
||
@pytest.mark.parametrize("connector_data", ConnectorTable._connectors) | ||
@pytest.mark.parametrize("as_uuid", (True, False)) | ||
def test_lookup_by_id(connector_data, as_uuid): | ||
_, connector_name, connector_id = connector_data | ||
|
||
if as_uuid: | ||
connector_id = uuid.UUID(connector_id) | ||
|
||
connector = ConnectorTable.lookup(connector_id) | ||
assert connector.name == connector_name | ||
|
||
|
||
@pytest.mark.parametrize("connector_data", ConnectorTable._connectors) | ||
def test_lookup_by_name(connector_data): | ||
_, connector_name, connector_id = connector_data | ||
|
||
connector = ConnectorTable.lookup(connector_name) | ||
assert connector.connector_id == connector_id | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"lookup_name, expect_name", | ||
( | ||
("Google Drive", "Google Drive"), | ||
("google drive", "Google Drive"), | ||
("google_drive", "Google Drive"), | ||
("google-drive", "Google Drive"), | ||
("google-----drive", "Google Drive"), | ||
("google-_-drive", "Google Drive"), # moody | ||
(" google_-drIVE", "Google Drive"), | ||
("google_-drIVE ", "Google Drive"), | ||
(" GOOGLE DRIVE ", "Google Drive"), | ||
), | ||
) | ||
def test_lookup_by_name_normalization(lookup_name, expect_name): | ||
connector = ConnectorTable.lookup(lookup_name) | ||
assert connector.name == expect_name | ||
|
||
|
||
@pytest.mark.parametrize("name", [c.name for c in ConnectorTable.all_connectors()]) | ||
def test_all_connector_names_map_to_attributes(name): | ||
connector = ConnectorTable.lookup(name) | ||
assert connector is not None | ||
name = name.replace(" ", "_").upper() | ||
assert getattr(ConnectorTable, name) == connector | ||
|
||
|
||
@pytest.mark.skipif( | ||
sys.version_info < (3, 10), reason="inspect.get_annotations added in 3.10" | ||
) | ||
def test_all_connector_attributes_are_assigned(): | ||
# build a list of attribute names annotated with | ||
# `t.ClassVar[GlobusConnectServerConnector]` | ||
annotated_attributes = [] | ||
for attribute, annotation in inspect.get_annotations(ConnectorTable).items(): | ||
# get_annotations does not interpret string-ized annotations by default, so we | ||
# receive the relevant values as strings, making comparison simple | ||
if annotation != "t.ClassVar[GlobusConnectServerConnector]": | ||
continue | ||
annotated_attributes.append(attribute) | ||
|
||
# confirm that we got the right number of annotated items | ||
assert len(annotated_attributes) == len(ConnectorTable._connectors) | ||
# now confirm that all of these are assigned values | ||
for attribute in annotated_attributes: | ||
instance = getattr(ConnectorTable, attribute) | ||
assert isinstance(instance, GlobusConnectServerConnector) |