From b59090e3075793dc5a73bf4c537a0b09287596ff Mon Sep 17 00:00:00 2001 From: Mike Turner Date: Fri, 27 Nov 2020 15:01:26 +0000 Subject: [PATCH] add projects and teams back in and put iterators back in place, plus docs --- docs/source/examples.rst | 213 ++++++++++++++++++++-------------- docs/source/pyepic.client.rst | 16 +++ pyepic/client/__init__.py | 1 + pyepic/client/base.py | 55 +++------ pyepic/client/catalog.py | 69 +++++++---- pyepic/client/desktop.py | 21 ++-- pyepic/client/job.py | 39 +++++-- pyepic/client/projects.py | 43 +++++++ pyepic/client/teams.py | 43 +++++++ 9 files changed, 326 insertions(+), 174 deletions(-) create mode 100644 pyepic/client/projects.py create mode 100644 pyepic/client/teams.py diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 3d48fa0..8a5e9f2 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -36,7 +36,7 @@ To list the available applications you can use the list_applications() method. T # List all applications apps = client.catalog.list_applications() print("ID | Application | Version | Cluster IDs") - for app in apps + for app in apps: for version in app.versions: print("{} | {} | {} | {}".format(version.id, app.product.name, version.version, version.queue_ids)) @@ -96,8 +96,8 @@ To list queues use the list_clusters() method. You can filter by cluster name or # List all clusters clusters = client.catalog.list_clusters() - for cluster in clusters.results: - print("{} | {} | {}".format(cluster.id, cluster.display_name, cluster.display_description)) + for cluster in clusters: + print("{} | {} | {}".format(cluster.id, cluster.name, cluster.description)) # List clusters with a filter for a cluster name clusters = client.catalog.list_clusters(cluster_name="csd3") @@ -206,7 +206,7 @@ To list the types of desktop nodes available in epic use the catalog.list_deskto # Look at the results print("Name | Version Name | Version ID | Valid Node Types | Valid connection Types") - for desktop in desktops.results: + for desktop in desktops: valid_connections = [conn.id for conn in desktop.connection_types] valid_node_types = [node_type.id for node_type in desktop.node_types] for version in desktop.versions: @@ -223,85 +223,83 @@ An example json output from list_desktops is shown below .. code-block:: json - {'count': 2, - 'next': None, - 'previous': None, - 'results': [{'connection_types': [{'description': 'Connect using Nice DCV in ' - 'your browser', - 'id': 3, - 'name': 'DCV'}], - 'description': 'NICE Desktop Cloud Visualization (DCV) enables ' - 'remote access 2D/3D interactive applications ' - 'over a standard network. EPIC will start a DCV ' - 'instance that you can connect to with your ' - 'browser with several versions of Paraview ' - 'installed and ready to go.', - 'id': 2, - 'image': '/media/viz/dcv.png', - 'name': 'DCV (Paraview)', - 'node_types': [{'cores': 4, - 'description': '4 Broadwell CPU Cores, 30.5GiB ' - 'Memory, 1 x Tesla M60 GPU with ' - '2048 CUDA cores and 8GB GPU ' - 'Memory', - 'gpus': 1, - 'id': 1, - 'name': 'Standard GPU Node'}, - {'cores': 32, - 'description': '32 Broadwell CPU Cores, 244GiB ' - 'Memory, 2 x Tesla M60 GPU with ' - '2048 CUDA cores and 8GB GPU ' - 'Memory', - 'gpus': 2, - 'id': 2, - 'name': 'Large GPU Node'}, - {'cores': 64, - 'description': '64 Broadwell CPU Cores, 488GiB ' - 'Memory, 4 x Tesla M60 GPU with ' - '2048 CUDA cores and 8GB GPU ' - 'Memory', - 'gpus': 4, - 'id': 3, - 'name': 'Extra Large GPU Node'}], - 'versions': [{'application_version': 'DCV 2017', 'id': 4}]}, - {'connection_types': [{'description': 'Connect using Nice DCV in ' - 'your browser', - 'id': 3, - 'name': 'DCV'}], - 'description': 'zCAD is an CAD repair and mesh generation tool ' - 'from Zenotech. EPIC will start a DCV instance ' - 'that you can connect to with your browser with ' - 'zCAD and other Zenotech tools installed and ' - 'ready to go.', - 'id': 3, - 'image': '/media/viz/zcad.png', - 'name': 'zCAD', - 'node_types': [{'cores': 4, - 'description': '4 Broadwell CPU Cores, 30.5GiB ' - 'Memory, 1 x Tesla M60 GPU with ' - '2048 CUDA cores and 8GB GPU ' - 'Memory', - 'gpus': 1, - 'id': 1, - 'name': 'Standard GPU Node'}, - {'cores': 32, - 'description': '32 Broadwell CPU Cores, 244GiB ' - 'Memory, 2 x Tesla M60 GPU with ' - '2048 CUDA cores and 8GB GPU ' - 'Memory', - 'gpus': 2, - 'id': 2, - 'name': 'Large GPU Node'}, - {'cores': 64, - 'description': '64 Broadwell CPU Cores, 488GiB ' - 'Memory, 4 x Tesla M60 GPU with ' - '2048 CUDA cores and 8GB GPU ' - 'Memory', - 'gpus': 4, + [ + {'connection_types': [{'description': 'Connect using Nice DCV in ' + 'your browser', 'id': 3, - 'name': 'Extra Large GPU Node'}], - 'versions': [{'application_version': '2016.9', 'id': 5}]}]} - + 'name': 'DCV'}], + 'description': 'NICE Desktop Cloud Visualization (DCV) enables ' + 'remote access 2D/3D interactive applications ' + 'over a standard network. EPIC will start a DCV ' + 'instance that you can connect to with your ' + 'browser with several versions of Paraview ' + 'installed and ready to go.', + 'id': 2, + 'image': '/media/viz/dcv.png', + 'name': 'DCV (Paraview)', + 'node_types': [{'cores': 4, + 'description': '4 Broadwell CPU Cores, 30.5GiB ' + 'Memory, 1 x Tesla M60 GPU with ' + '2048 CUDA cores and 8GB GPU ' + 'Memory', + 'gpus': 1, + 'id': 1, + 'name': 'Standard GPU Node'}, + {'cores': 32, + 'description': '32 Broadwell CPU Cores, 244GiB ' + 'Memory, 2 x Tesla M60 GPU with ' + '2048 CUDA cores and 8GB GPU ' + 'Memory', + 'gpus': 2, + 'id': 2, + 'name': 'Large GPU Node'}, + {'cores': 64, + 'description': '64 Broadwell CPU Cores, 488GiB ' + 'Memory, 4 x Tesla M60 GPU with ' + '2048 CUDA cores and 8GB GPU ' + 'Memory', + 'gpus': 4, + 'id': 3, + 'name': 'Extra Large GPU Node'}], + 'versions': [{'application_version': 'DCV 2017', 'id': 4}]}, + {'connection_types': [{'description': 'Connect using Nice DCV in ' + 'your browser', + 'id': 3, + 'name': 'DCV'}], + 'description': 'zCAD is an CAD repair and mesh generation tool ' + 'from Zenotech. EPIC will start a DCV instance ' + 'that you can connect to with your browser with ' + 'zCAD and other Zenotech tools installed and ' + 'ready to go.', + 'id': 3, + 'image': '/media/viz/zcad.png', + 'name': 'zCAD', + 'node_types': [{'cores': 4, + 'description': '4 Broadwell CPU Cores, 30.5GiB ' + 'Memory, 1 x Tesla M60 GPU with ' + '2048 CUDA cores and 8GB GPU ' + 'Memory', + 'gpus': 1, + 'id': 1, + 'name': 'Standard GPU Node'}, + {'cores': 32, + 'description': '32 Broadwell CPU Cores, 244GiB ' + 'Memory, 2 x Tesla M60 GPU with ' + '2048 CUDA cores and 8GB GPU ' + 'Memory', + 'gpus': 2, + 'id': 2, + 'name': 'Large GPU Node'}, + {'cores': 64, + 'description': '64 Broadwell CPU Cores, 488GiB ' + 'Memory, 4 x Tesla M60 GPU with ' + '2048 CUDA cores and 8GB GPU ' + 'Memory', + 'gpus': 4, + 'id': 3, + 'name': 'Extra Large GPU Node'}], + 'versions': [{'application_version': '2016.9', 'id': 5}]} + ] Jobs ==== @@ -573,7 +571,7 @@ Desktops Listing Desktop Instances ------------------------- -To list your desktop instances use the list and get_details methods in :class:`pyepic.client.EPICClient.desktop`. +To list your desktop instances use the list and get_details methods in :class:`pyepic.client.EPICClient.desktops`. .. code-block:: python @@ -582,10 +580,10 @@ To list your desktop instances use the list and get_details methods in :class:`p client = EPICClient("your_api_token_goes_here") # List all of my desktop instances - desktops = client.desktop.list() + desktops = client.desktops.list() # Get the details of desktop id 3 - desktop_instance = client.desktop.get_details(3) + desktop_instance = client.desktops.get_details(3) Getting a quote for a Desktop @@ -608,7 +606,7 @@ The valid application_version, node_type and connection_type values can be retri my_desktop.runtime = 2 # Get a quote for this desktop - quote = client.desktop.get_quote(my_desktop.get_quote_spec())) + quote = client.desktops.get_quote(my_desktop.get_quote_spec())) An example response for the quote is shown below. @@ -637,7 +635,7 @@ The valid application_version, node_type and connection_type values can be retri my_desktop.runtime = 2 # Launch this desktop - instance = client.desktop.launch(my_desktop.get_launch_spec())) + instance = client.desktops.launch(my_desktop.get_launch_spec())) # Get the newly created desktop instance id. id = instance.id @@ -682,4 +680,43 @@ Terminate a desktop using the terminate client method and the Desktops ID. client = EPICClient("your_api_token_goes_here") # Terminate desktop with ID 3 - client.desktop.terminate(3) + client.desktops.terminate(3) + + +Teams +===== + +.. code-block:: python + + from pyepic import EPICClient + from pyepic.desktops import Desktop + + client = EPICClient("your_api_token_goes_here") + + # List teams + teams = client.teams.list() + + for team in teams: + print(team) + + # Get team ID 334 + team = client.teams.get_details(334) + +Projects +======== + +.. code-block:: python + + from pyepic import EPICClient + from pyepic.desktops import Desktop + + client = EPICClient("your_api_token_goes_here") + + # List projects + projects = client.projects.list() + + for project in projects: + print(project) + + # Get project ID 102 + project = client.projects.get_details(102) \ No newline at end of file diff --git a/docs/source/pyepic.client.rst b/docs/source/pyepic.client.rst index 18b9707..20903df 100644 --- a/docs/source/pyepic.client.rst +++ b/docs/source/pyepic.client.rst @@ -36,6 +36,22 @@ pyepic.client.job module :undoc-members: :show-inheritance: +pyepic.client.projects module +----------------------------- + +.. automodule:: pyepic.client.projects + :members: + :undoc-members: + :show-inheritance: + +pyepic.client.teams module +-------------------------- + +.. automodule:: pyepic.client.teams + :members: + :undoc-members: + :show-inheritance: + Module contents --------------- diff --git a/pyepic/client/__init__.py b/pyepic/client/__init__.py index 3b32369..bc5e86e 100644 --- a/pyepic/client/__init__.py +++ b/pyepic/client/__init__.py @@ -1 +1,2 @@ from .base import EPICClient + diff --git a/pyepic/client/base.py b/pyepic/client/base.py index e715e03..3909ceb 100644 --- a/pyepic/client/base.py +++ b/pyepic/client/base.py @@ -1,43 +1,5 @@ import epiccore - -class APIListResponse(object): - """An abstract representation of the response from an EPIC API list request - """ - - @property - def count(self): - """ - :return: Number of results returned - :rtype: int - """ - pass - - @property - def next(): - """ - :return next: The uri to get the next set of responses - :rtype: str - """ - pass - - @property - def previous(): - """ - :return next: The uri to get the previous set of responses - :rtype: str - """ - pass - - @property - def results(): - """ - :return: A list of the repsonse objects for request - :rtype: list - """ - pass - - class Client(object): """Base client class for API wrappers @@ -51,6 +13,7 @@ class Client(object): def __init__(self, connection_token, connection_url="https://epic.zenotech.com/api/v2"): """Constructor method """ + self.LIMIT = 10 self.configuration = epiccore.Configuration( host=connection_url, api_key={ @@ -71,8 +34,12 @@ class EPICClient(object): :vartype job: :class:`JobClient` :var catalog: API to Catalog functions :vartype catalog: :class:`CatalogClient` - :var desktop: API to Desktops functions - :vartype desktop: :class:`DesktopClient` + :var desktops: API to Desktops functions + :vartype desktops: :class:`DesktopClient` + :var projects: API to Projects functions + :vartype projects: :class:`ProjectClient` + :var teams: API to Teams functions + :vartype teams: :class:`TeamsClient` """ def __init__(self, connection_token, connection_url="https://epic.zenotech.com/api/v2"): @@ -81,7 +48,11 @@ def __init__(self, connection_token, connection_url="https://epic.zenotech.com/a from .job import JobClient from .catalog import CatalogClient from .desktop import DesktopClient - + from .projects import ProjectClient + from .teams import TeamsClient + self.job = JobClient(connection_token, connection_url=connection_url) self.catalog = CatalogClient(connection_token, connection_url=connection_url) - self.desktop = DesktopClient(connection_token, connection_url=connection_url) + self.desktops = DesktopClient(connection_token, connection_url=connection_url) + self.projects = ProjectClient(connection_token, connection_url=connection_url) + self.teams = TeamsClient(connection_token, connection_url=connection_url) diff --git a/pyepic/client/catalog.py b/pyepic/client/catalog.py index 31155e9..c683628 100644 --- a/pyepic/client/catalog.py +++ b/pyepic/client/catalog.py @@ -13,50 +13,69 @@ class CatalogClient(Client): """ - def list_clusters(self, limit=10, offset=0, cluster_name=None, application_id=None): + def list_clusters(self, cluster_name=None, queue_name=None, application_id=None): """List the clusters available in EPIC - :param limit: Number of results to return per request, defaults to 10 - :type limit: int - :param offset: The initial index from which to return the results, defaults to 0 - :type offset: int :param cluster_name: Filter clusters by cluster name. :type cluster_name: str, optional + :param queue_name: Filter clusters by queue name. + :type queue_name: str, optional :param application_id: Filter clusters by those with application application_id available. :type application_id: int, optional - :return: Response with results of returned :class:`epiccore.models.BatchQueueDetails` objects - :rtype: class:`APIListResponse` + + :return: Iterable collection of BatchQueueDetails + :rtype: collections.Iterable[:class:`epiccore.models.BatchQueueDetails`] """ with epiccore.ApiClient(self.configuration) as api_client: + limit = self.LIMIT + offset = 0 instance = epiccore.CatalogApi(api_client) - return instance.catalog_clusters_list(limit=limit, offset=offset, cluster_name=cluster_name, allowed_apps=application_id) + results = instance.catalog_clusters_list(limit=limit, offset=offset, cluster_name=cluster_name, queue_name=queue_name, allowed_apps=application_id) + for result in results.results: + yield result + while results.next is not None: + offset += limit + results = instance.catalog_clusters_list(limit=limit, offset=offset, cluster_name=cluster_name, queue_name=queue_name, allowed_apps=application_id) + for result in results.results: + yield result - def list_applications(self, limit=10, offset=0, product_name=None): + def list_applications(self, product_name=None): """List the applications available in EPIC - :param limit: Number of results to return per request, defaults to 10 - :type limit: int - :param offset: The initial index from which to return the results, defaults to 0 - :type offset: int :param product_name: Filter clusters by application name. :type product_name: str, optional - :return: Response with results of returned :class:`epiccore.models.BatchApplicationDetails` objects - :rtype: class:`APIListResponse` + + :return: Iterable collection of BatchApplicationDetails + :rtype: collections.Iterable[:class:`epiccore.models.BatchApplicationDetails`] """ with epiccore.ApiClient(self.configuration) as api_client: - instance = epiccore.catalog_api.CatalogApi(api_client) - return instance.catalog_applications_list(limit=limit, offset=offset, product_name=product_name) + limit = self.LIMIT + offset = 0 + instance = epiccore.CatalogApi(api_client) + results = instance.catalog_applications_list(limit=limit, offset=offset, product_name=product_name) + for result in results.results: + yield result + while results.next is not None: + offset += limit + results = instance.catalog_applications_list(limit=limit, offset=offset, product_name=product_name) + for result in results.results: + yield result - def list_desktops(self, limit=10, offset=0): + def list_desktops(self): """List the available Desktops in EPIC - :param limit: Number of results to return per request, defaults to 10 - :type limit: int - :param offset: The initial index from which to return the results, defaults to 0 - :type offset: int - :return: Response with results of returned :class:`epiccore.models.BatchApplicationDetails` objects - :rtype: class:`APIListResponse` + :return: Iterable collection of DesktopNodeApp + :rtype: collections.Iterable[:class:`epiccore.models.DesktopNodeApp`] """ with epiccore.ApiClient(self.configuration) as api_client: + limit = self.LIMIT + offset = 0 instance = epiccore.CatalogApi(api_client) - return instance.catalog_desktop_list(limit=limit, offset=offset) + results = instance.catalog_desktop_list(limit=limit, offset=offset) + for result in results.results: + yield result + while results.next is not None: + offset += limit + results = instance.catalog_desktop_list(limit=limit, offset=offset) + for result in results.results: + yield result diff --git a/pyepic/client/desktop.py b/pyepic/client/desktop.py index 6ecd516..2eaed7c 100644 --- a/pyepic/client/desktop.py +++ b/pyepic/client/desktop.py @@ -37,19 +37,24 @@ def launch(self, desktop_spec): instance = epiccore.DesktopApi(api_client) return instance.desktop_create(desktop_spec) - def list(self, limit=10, offset=0): + def list(self): """List all of your Desktops in EPIC. - :param limit: Number of results to return per request, defaults to 10 - :type limit: int - :param offset: The initial index from which to return the results, defaults to 0 - :type offset: int - :return: Response with results of returned :class:`epiccore.models.Job` objects - :rtype: class:`APIListResponse` + :return: Iterable collection of DesktopInstance + :rtype: collections.Iterable[:class:`epiccore.models.DesktopInstance`] """ with epiccore.ApiClient(self.configuration) as api_client: + limit = self.LIMIT + offset = 0 instance = epiccore.DesktopApi(api_client) - return instance.desktop_list(limit=limit, offset=offset) + results = instance.desktop_list(limit=limit, offset=offset) + for result in results.results: + yield result + while results.next is not None: + offset += limit + results = instance.desktop_list(limit=limit, offset=offset) + for result in results.results: + yield result def get_details(self, id): """Get details of desktop with ID id diff --git a/pyepic/client/job.py b/pyepic/client/job.py index 7fbaa32..75b8683 100644 --- a/pyepic/client/job.py +++ b/pyepic/client/job.py @@ -36,29 +36,46 @@ def submit(self, job_array_spec): instance = epiccore.JobApi(api_client) return instance.job_create(job_array_spec) - def list(self, limit=10, offset=0): + def list(self): """List all of the jobs in EPIC. - :return: Response with results of returned :class:`epiccore.models.Job` objects + :return: Iterable collection of Jobs + :rtype: collections.Iterable[:class:`epiccore.models.Job`] """ with epiccore.ApiClient(self.configuration) as api_client: + limit = self.LIMIT + offset = 0 instance = epiccore.JobApi(api_client) - return instance.job_list(limit=limit, offset=offset) - - def list_steps(self, parent_job=None, limit=10, offset=0): + results = instance.job_list(limit=limit, offset=offset) + for result in results.results: + yield result + while results.next is not None: + offset += limit + results = instance.job_list(limit=limit, offset=offset) + for result in results.results: + yield result + + def list_steps(self, parent_job=None): """List all of the job steps in EPIC. :param parent_job: The ID of the parent job to list the steps for :type parent_job: int, optional - :param limit: Number of results to return per request, defaults to 10 - :type limit: int - :param offset: The initial index from which to return the results, defaults to 0 - :type offset: int - :return: Response with results of returned :class:`epiccore.models.JobStep` objects + + :return: Iterable collection of Job Steps + :rtype: collections.Iterable[:class:`epiccore.models.JobStep`] """ with epiccore.ApiClient(self.configuration) as api_client: + limit = self.LIMIT + offset = 0 instance = epiccore.JobstepApi(api_client) - return instance.jobstep_list(parent_job=parent_job, limit=limit, offset=offset) + results = instance.jobstep_list(limit=limit, offset=offset, parent_job=parent_job) + for result in results.results: + yield result + while results.next is not None: + offset += limit + results = instance.job_list(limit=limit, offset=offset) + for result in results.results: + yield result def get_details(self, job_id): """Get details of job with ID job_id diff --git a/pyepic/client/projects.py b/pyepic/client/projects.py new file mode 100644 index 0000000..bb00bfb --- /dev/null +++ b/pyepic/client/projects.py @@ -0,0 +1,43 @@ +import epiccore + +from .base import Client + + +class ProjectClient(Client): + """A wrapper class around the epiccore Projects API. + + :param connection_token: Your EPIC API authentication token + :type connection_token: str + :param connection_url: The API URL for EPIC, defaults to "https://epic.zenotech.com/api/v2" + :type connection_url: str, optional + + """ + + def list(self): + """ List all of the projects you have access to on EPIC. + + :return: An interable list of Projects + :rtype: collections.Iterable[:class:`epiccore.models.Project`] + """ + with epiccore.ApiClient(self.configuration) as api_client: + limit = 10 + offset = 0 + instance = epiccore.ProjectsApi(api_client) + results = instance.projects_list(limit=limit, offset=offset) + for result in results.results: + yield result + while results.next is not None: + offset += limit + results = instance.projects_list(limit=limit, offset=offset) + for result in results.results: + yield result + + def get_details(self, id: int): + """ Get the details for project with ID id + + :return: The Project + :rtype: :class:`epiccore.models.ProjectDetails` + """ + with epiccore.ApiClient(self.configuration) as api_client: + instance = epiccore.ProjectsApi(api_client) + return instance.projects_read(id=id) diff --git a/pyepic/client/teams.py b/pyepic/client/teams.py new file mode 100644 index 0000000..82874c3 --- /dev/null +++ b/pyepic/client/teams.py @@ -0,0 +1,43 @@ +import epiccore + +from .base import Client + + +class TeamsClient(Client): + """A wrapper class around the epiccore Teams API. + + :param connection_token: Your EPIC API authentication token + :type connection_token: str + :param connection_url: The API URL for EPIC, defaults to "https://epic.zenotech.com/api/v2" + :type connection_url: str, optional + + """ + + def list(self): + """ List all of the teams you have access to on EPIC. + + :return: An interable list of Teams + :rtype: collections.Iterable[:class:`epiccore.models.Team`] + """ + with epiccore.ApiClient(self.configuration) as api_client: + limit = 10 + offset = 0 + instance = epiccore.TeamsApi(api_client) + results = instance.teams_list(limit=limit, offset=offset) + for result in results.results: + yield result + while results.next is not None: + offset += limit + results = instance.teams_list(limit=limit, offset=offset) + for result in results.results: + yield result + + def get_details(self, id: int): + """ Get the details for team with ID id + + :return: The Team details + :rtype: :class:`epiccore.models.TeamDetails` + """ + with epiccore.ApiClient(self.configuration) as api_client: + instance = epiccore.TeamsApi(api_client) + return instance.teams_read(id=id)