{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# Evo SDK Common\n",
    "\n",
    "Evo SDK Common is a Python package that establishes a common framework for use by client libraries that interact\n",
    "with Evo APIs. This notebook demonstrates how to use the package to interact with Evo APIs."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1",
   "metadata": {},
   "source": [
    "## ITransport\n",
    "\n",
    "The `ITransport` interface is used to make HTTP requests to Evo APIs. The `AioTransport` class is an implementation\n",
    "based on the `aiohttp` library, which is an optional dependency. Different HTTP client libraries can be substituted by\n",
    "implementing a facade that implements the `ITransport` interface.\n",
    "\n",
    "Transport objects must be re-entrant so that they can be used by multiple coroutines at the same time. `AioTransport`\n",
    "uses an internal counter to track the number of places where the transport is being used. When the counter reaches zero,\n",
    "the underlying HTTP client session is closed, and any related resources are released. The next time the transport is\n",
    "opened, a new session will be created."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.aio import AioTransport\n",
    "from evo.common.utils import BackoffIncremental\n",
    "\n",
    "# Configure the transport.\n",
    "transport = AioTransport(\n",
    "    user_agent=\"your-app-name\",\n",
    "    max_attempts=3,\n",
    "    backoff_method=BackoffIncremental(2),\n",
    "    num_pools=4,\n",
    "    verify_ssl=True,\n",
    ")\n",
    "\n",
    "# Open the transport outside a context manager so that the underlying session is left open. This can save\n",
    "# time if you are going to make multiple batches of requests in the same area of code. Ideally, the transport should\n",
    "# be closed when it is no longer needed.\n",
    "await transport.open()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3",
   "metadata": {},
   "source": [
    "## Logging in to Evo\n",
    "\n",
    "The `IAuthorizer` interface is used to authenticate with Evo APIs, by automatically attaching the default headers to\n",
    "API requests. The `AuthorizationCodeAuthorizer` class is an OAuth implementation of `IAuthorizer`, utilizing a reference OAuth\n",
    "implementation that is built using the `aiohttp` library. `aiohttp` is an optional dependency, so it must be installed\n",
    "for the `AuthorizationCodeAuthorizer` implementation to work."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4",
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.oauth import AuthorizationCodeAuthorizer, OAuthConnector\n",
    "\n",
    "# OAuth client app credentials\n",
    "# See: https://developer.seequent.com/docs/guides/getting-started/apps-and-tokens\n",
    "CLIENT_ID = \"your-client-id\"\n",
    "REDIRECT_URL = \"http://localhost:3000/signin-callback\"\n",
    "\n",
    "authorizer = AuthorizationCodeAuthorizer(\n",
    "    redirect_url=REDIRECT_URL,\n",
    "    oauth_connector=OAuthConnector(\n",
    "        client_id=CLIENT_ID,\n",
    "        transport=transport,\n",
    "    ),\n",
    ")\n",
    "\n",
    "# Login to the Evo platform.\n",
    "await authorizer.login()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5",
   "metadata": {},
   "source": [
    "Alternatively, a client of `client credientials` grant type can use the `ClientCredentialsAuthorizer` for authorization into Evo. This allows for service-to-service requests, instead of user login and redirects. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.oauth import ClientCredentialsAuthorizer, EvoScopes, OAuthConnector\n",
    "\n",
    "CLIENT_NAME = \"Your Client Name\"\n",
    "CLIENT_ID = \"your-client-id\"\n",
    "CLIENT_SECRET = \"your-client-secret\"\n",
    "\n",
    "authorizer = ClientCredentialsAuthorizer(\n",
    "    oauth_connector=OAuthConnector(\n",
    "        transport=transport,\n",
    "        client_id=CLIENT_ID,\n",
    "        client_secret=CLIENT_SECRET,\n",
    "    ),\n",
    "    scopes=EvoScopes.all_evo,\n",
    ")\n",
    "\n",
    "# Authorize the client.\n",
    "await authorizer.authorize()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7",
   "metadata": {},
   "source": [
    "## Listing organizations\n",
    "\n",
    "In most user-facing environments it will be necessary to list the organizations that the user has access to. The\n",
    "`DiscoveryAPIClient` interacts with the Discovery API to retrieve this information. Simply give it a connector\n",
    "pointing to the appropriate host, and it will do the rest."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8",
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.common import APIConnector\n",
    "from evo.discovery import DiscoveryAPIClient\n",
    "\n",
    "# Select an organization\n",
    "async with APIConnector(\"https://discover.api.seequent.com\", transport, authorizer) as idp_connector:\n",
    "    discovery_client = DiscoveryAPIClient(idp_connector)\n",
    "    organizations = await discovery_client.list_organizations()\n",
    "\n",
    "# Select the first organization for this example\n",
    "selected_organization = organizations[0]\n",
    "print(\"Selected organization:\", selected_organization)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9",
   "metadata": {},
   "source": [
    "## Listing workspaces\n",
    "\n",
    "Once an organization has been selected, the next step is to list the workspaces that the user has access to.\n",
    "\n",
    "We will create a connector targeting the hub URL, which we can reuse later for talking to individual services. The\n",
    "transport and authorizer objects are also reused."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.workspaces import WorkspaceAPIClient\n",
    "\n",
    "# You will only have one hub for your organization\n",
    "hub = selected_organization.hubs[0]\n",
    "print(hub)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11",
   "metadata": {},
   "source": [
    "Once you have a hub, you may proceed with listing workspaces."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12",
   "metadata": {},
   "outputs": [],
   "source": [
    "# This connector can be used to connect to any service supported by the hub\n",
    "hub_connector = APIConnector(hub.url, transport, authorizer)\n",
    "\n",
    "# List the workspaces\n",
    "async with hub_connector:\n",
    "    workspace_client = WorkspaceAPIClient(hub_connector, selected_organization.id)\n",
    "    workspaces = await workspace_client.list_workspaces()\n",
    "\n",
    "# Select the first workspace for this example (you may have multiple)\n",
    "selected_workspace = workspaces[0]\n",
    "print(\"Selected workspace:\", selected_workspace.display_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13",
   "metadata": {},
   "source": [
    "## Interacting with API clients\n",
    "\n",
    "The `Workspace` object can generate an `Environment`, which contains the organization and workspace IDs, and can be\n",
    "used to resolve cache locations. Evo SDK Common does not implement any specific API clients, but it provides\n",
    "a `BaseAPIClient` type that should be used as a base class for API clients.\n",
    "\n",
    "The `BaseAPIClient` defines a shared constructor for API clients, as well as convenient cache management via the `cache` property and the `clear_cache()` method. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "14",
   "metadata": {},
   "outputs": [],
   "source": [
    "from evo.common import BaseAPIClient\n",
    "\n",
    "## Interact with a service.\n",
    "async with hub_connector:\n",
    "    service_client = BaseAPIClient(selected_workspace.get_environment(), hub_connector)\n",
    "    ...  # Do something with the service client."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "eps",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
