TrueRAG MCP Server

{ "cells": [ { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Collecting gql\n", " Downloading gql-3.5.0-py2.py3-none-any.whl.metadata (9.2 kB)\n", "Collecting graphql-core<3.3,>=3.2 (from gql)\n", " Downloading graphql_core-3.2.5-py3-none-any.whl.metadata (10 kB)\n", "Requirement already satisfied: yarl<2.0,>=1.6 in /Users/guy/opt/anaconda3/lib/python3.8/site-packages (from gql) (1.8.1)\n", "Requirement already satisfied: backoff<3.0,>=1.11.1 in /Users/guy/opt/anaconda3/lib/python3.8/site-packages (from gql) (2.1.2)\n", "Requirement already satisfied: anyio<5,>=3.0 in /Users/guy/opt/anaconda3/lib/python3.8/site-packages (from gql) (4.3.0)\n", "Requirement already satisfied: idna>=2.8 in /Users/guy/opt/anaconda3/lib/python3.8/site-packages (from anyio<5,>=3.0->gql) (2.10)\n", "Requirement already satisfied: sniffio>=1.1 in /Users/guy/opt/anaconda3/lib/python3.8/site-packages (from anyio<5,>=3.0->gql) (1.3.1)\n", "Requirement already satisfied: exceptiongroup>=1.0.2 in /Users/guy/opt/anaconda3/lib/python3.8/site-packages (from anyio<5,>=3.0->gql) (1.1.3)\n", "Requirement already satisfied: typing-extensions>=4.1 in /Users/guy/opt/anaconda3/lib/python3.8/site-packages (from anyio<5,>=3.0->gql) (4.7.1)\n", "Requirement already satisfied: multidict>=4.0 in /Users/guy/opt/anaconda3/lib/python3.8/site-packages (from yarl<2.0,>=1.6->gql) (6.0.2)\n", "Downloading gql-3.5.0-py2.py3-none-any.whl (74 kB)\n", "Downloading graphql_core-3.2.5-py3-none-any.whl (203 kB)\n", "\u001b[33mWARNING: Error parsing dependencies of attrs: [Errno 2] No such file or directory: '/Users/guy/opt/anaconda3/lib/python3.8/site-packages/attrs-20.3.0.dist-info/METADATA'\u001b[0m\u001b[33m\n", "\u001b[0m\u001b[33mWARNING: Error parsing dependencies of pyodbc: Invalid version: '4.0.0-unsupported'\u001b[0m\u001b[33m\n", "\u001b[0mInstalling collected packages: graphql-core, gql\n", "\u001b[31mERROR: Exception:\n", "Traceback (most recent call last):\n", " File \"/Users/guy/opt/anaconda3/lib/python3.8/site-packages/pip/_internal/cli/base_command.py\", line 105, in _run_wrapper\n", " status = _inner_run()\n", " File \"/Users/guy/opt/anaconda3/lib/python3.8/site-packages/pip/_internal/cli/base_command.py\", line 96, in _inner_run\n", " return self.run(options, args)\n", " File \"/Users/guy/opt/anaconda3/lib/python3.8/site-packages/pip/_internal/cli/req_command.py\", line 67, in wrapper\n", " return func(self, options, args)\n", " File \"/Users/guy/opt/anaconda3/lib/python3.8/site-packages/pip/_internal/commands/install.py\", line 483, in run\n", " installed_versions[distribution.canonical_name] = distribution.version\n", " File \"/Users/guy/opt/anaconda3/lib/python3.8/site-packages/pip/_internal/metadata/pkg_resources.py\", line 192, in version\n", " return parse_version(self._dist.version)\n", " File \"/Users/guy/opt/anaconda3/lib/python3.8/site-packages/pip/_vendor/packaging/version.py\", line 56, in parse\n", " return Version(version)\n", " File \"/Users/guy/opt/anaconda3/lib/python3.8/site-packages/pip/_vendor/packaging/version.py\", line 202, in __init__\n", " raise InvalidVersion(f\"Invalid version: '{version}'\")\n", "pip._vendor.packaging.version.InvalidVersion: Invalid version: '4.0.0-unsupported'\u001b[0m\u001b[31m\n", "\u001b[0mNote: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip install gql " ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [], "source": [ "from gql import Client, gql\n", "from gql.transport.aiohttp import AIOHTTPTransport\n", "from graphql import build_ast_schema, parse, print_schema\n", "\n" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [], "source": [ "from dotenv import load_dotenv\n", "import os\n", "from typing import Optional, Dict, Any\n", "\n", "\n", "load_dotenv() # Load the .env file\n", "\n", "GRAPHQL_API_KEY = os.environ.get(\"GRAPHQL_API_KEY\")\n", "GRAPHQL_ENDPOINT = os.environ.get(\"GRAPHQL_ENDPOINT\")\n", "\n", "class GraphQLClient:\n", " def __init__(self, endpoint: str):\n", " self.transport = AIOHTTPTransport(\n", " url=endpoint,\n", " headers={\n", " \"x-api-key\": GRAPHQL_API_KEY,\n", " 'Content-Type': 'application/json',\n", " },\n", " )\n", " self.client = Client(\n", " transport=self.transport, \n", " fetch_schema_from_transport=True\n", " )\n", " \n", " async def get_schema(self) -> str:\n", " \"\"\"Fetch and return the GraphQL schema as a string\"\"\"\n", " async with self.client as session:\n", " schema = session.client.schema\n", " return print_schema(schema)\n", " # async with self.client as session:\n", " # response = await session.execute(gql(\"query { __schema { types { name } } }\"))\n", " # schema = response['__schema']['types']\n", " # return ' '.join([t['name'] for t in schema])\n", " \n", " async def execute_query(self, query: str, variables: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:\n", " \"\"\"Execute a GraphQL query and return the results\"\"\"\n", " async with self.client as session:\n", " result = await session.execute(gql(query), variable_values=variables)\n", " return result\n", "\n", "client = GraphQLClient(GRAPHQL_ENDPOINT)" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "<__main__.GraphQLClient at 0x7fad19d2cee0>" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\n", "\n", "client" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [], "source": [ "schema = await client.get_schema()\n" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'\"\"\"This directive allows results to be deferred during execution\"\"\"\\ndirective @defer on FIELD\\n\\n\"\"\"Directs the schema to enforce authorization on a field\"\"\"\\ndirective @aws_auth(\\n \"\"\"List of cognito user pool groups which have access on this field\"\"\"\\n cognito_groups: [String]\\n) on FIELD_DEFINITION\\n\\n\"\"\"\\nTells the service which subscriptions will be published to when this mutation is called. This directive is deprecated use @aws_susbscribe directive instead.\\n\"\"\"\\ndirective @aws_publish(\\n \"\"\"\\n List of subscriptions which will be published to when this mutation is called.\\n \"\"\"\\n subscriptions: [String]\\n) on FIELD_DEFINITION\\n\\n\"\"\"\\nTells the service this field/object has access authorized by a Lambda Authorizer.\\n\"\"\"\\ndirective @aws_lambda on OBJECT | FIELD_DEFINITION\\n\\n\"\"\"\\nTells the service this field/object has access authorized by an OIDC token.\\n\"\"\"\\ndirective @aws_oidc on OBJECT | FIELD_DEFINITION\\n\\n\"\"\"\\nTells the service this field/object has access authorized by sigv4 signing.\\n\"\"\"\\ndirective @aws_iam on OBJECT | FIELD_DEFINITION\\n\\n\"\"\"\\nTells the service this field/object has access authorized by an API key.\\n\"\"\"\\ndirective @aws_api_key on OBJECT | FIELD_DEFINITION\\n\\n\"\"\"\\nTells the service this field/object has access authorized by a Cognito User Pools token.\\n\"\"\"\\ndirective @aws_cognito_user_pools(\\n \"\"\"List of cognito user pool groups which have access on this field\"\"\"\\n cognito_groups: [String]\\n) on OBJECT | FIELD_DEFINITION\\n\\n\"\"\"Tells the service which mutation triggers this subscription.\"\"\"\\ndirective @aws_subscribe(\\n \"\"\"\\n List of mutations which will trigger this subscription when they are called.\\n \"\"\"\\n mutations: [String]\\n) on FIELD_DEFINITION\\n\\ntype Query {\\n organization: Organization!\\n state(id: ID!): State\\n directory_schema: DirectorySchema\\n node(node_id: ID!): Node\\n}\\n\\n\"\"\" The following types are the hierarchy types with connections up and down\\n\"\"\"\\ntype Organization {\\n node_id: ID!\\n name: String!\\n regions(filter: RegionFilter): [Region!]!\\n policies(filter: PolicyFilter): [Policy!]!\\n}\\n\\ntype Region {\\n node_id: ID!\\n region: String!\\n organization: Organization!\\n countries(filter: CountryFilter): [Country!]!\\n policies(filter: PolicyFilter): [Policy!]!\\n}\\n\\ntype Country {\\n node_id: ID!\\n country: String!\\n region: Region!\\n states(filter: StateFilter): [State!]!\\n policies(filter: PolicyFilter): [Policy!]!\\n}\\n\\ntype State {\\n node_id: ID!\\n state_short: String!\\n state_name: String!\\n country: Country!\\n policies(filter: PolicyFilter): [Policy!]!\\n is_authorized(request: PermissionRequest!): Boolean\\n}\\n\\ntype Policy {\\n policy_id: ID!\\n policy_type: String!\\n policy_document: AWSJSON!\\n\\n \"\"\" The node the policy is attached to\"\"\"\\n node_id: ID\\n policy_holder: Node\\n}\\n\\n\"\"\"\\nThe `AWSJSON` scalar type provided by AWS AppSync, represents a JSON string that complies with [RFC 8259](https://tools.ietf.org/html/rfc8259). Maps like \"**{\\\\\\\\\"upvotes\\\\\\\\\": 10}**\", lists like \"**[1,2,3]**\", and scalar values like \"**\\\\\\\\\"AWSJSON example string\\\\\\\\\"**\", \"**1**\", and \"**true**\" are accepted as valid JSON and will automatically be parsed and loaded in the resolver mapping templates as Maps, Lists, or Scalar values rather than as the literal input strings. Invalid JSON strings like \"**{a: 1}**\", \"**{\\'a\\': 1}**\" and \"**Unquoted string**\" will throw GraphQL validation errors.\\n\"\"\"\\nscalar AWSJSON\\n\\nunion Node = Organization | Region | Country | State\\n\\n\"\"\" To allow filtering the policies needed\"\"\"\\ninput PolicyFilter {\\n policy_type: String\\n}\\n\\ninput PermissionRequest {\\n principal: PermissionPrincipal\\n action: PermissionAction!\\n resource: PermissionResource\\n}\\n\\n\" This is the graphQL Schema for the OrgShield access API\\\\n The following types are used to check permissions\"\\ninput PermissionPrincipal {\\n \"\"\" dafault type is User\"\"\"\\n entity_type: String\\n entity_id: ID!\\n}\\n\\ninput PermissionAction {\\n \"\"\" dafault type is Action\"\"\"\\n action_type: String\\n action_id: ID!\\n}\\n\\ninput PermissionResource {\\n \"\"\" dafault type is Store\"\"\"\\n entity_type: String\\n entity_id: ID!\\n}\\n\\ninput StateFilter {\\n state_short: String!\\n}\\n\\ninput CountryFilter {\\n country: String!\\n}\\n\\ninput RegionFilter {\\n region: String\\n}\\n\\ntype DirectorySchema {\\n name: String\\n document: AWSJSON!\\n}\\n\\ntype Mutation {\\n \"\"\" Attach and detack poclicies\"\"\"\\n addPolicy(node_id: ID!, policy_type: String!, policy_document: AWSJSON!): Policy!\\n updatePolicy(policy_id: ID!, policy_document: AWSJSON!): ID!\\n removePolicy(node_id: ID!, policy_id: ID): Policy!\\n\\n \"\"\" Adding nodes to the OrgShield\"\"\"\\n addRegion(parent_node_id: ID!, node: RegionInput!): ID!\\n addCountry(parent_node_id: ID!, node: CountryInput!): ID!\\n addState(parent_node_id: ID!, node: StateInput!): ID!\\n\\n \"\"\" Updating nodes in the OrgShield\"\"\"\\n updateOrganization(node_id: ID!, node: OrganizationInput!): ID!\\n updateRegion(node_id: ID!, node: RegionInput!): ID!\\n updateCountry(node_id: ID!, node: CountryInput!): ID!\\n updateState(node_id: ID!, node: StateInput!): ID!\\n\\n \"\"\" Deleting nodes in the OrgShield\"\"\"\\n deleteOrganization(node_id: ID!, node: OrganizationInput!): ID!\\n deleteRegion(node_id: ID!, node: RegionInput!): ID!\\n deleteCountry(node_id: ID!, node: CountryInput!): ID!\\n deleteState(node_id: ID!, node: StateInput!): ID!\\n}\\n\\ninput RegionInput {\\n region: String!\\n}\\n\\ninput CountryInput {\\n country: String!\\n}\\n\\ninput StateInput {\\n state_short: String!\\n state_name: String!\\n}\\n\\ninput OrganizationInput {\\n name: String!\\n}'" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "schema" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [], "source": [ "query = \"\"\"\n", "query {\n", " organization {\n", " node_id\n", " name\n", " }\n", "}\n", "\"\"\"\n", "vars_dict = None" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [], "source": [ "result = await client.execute_query(query, vars_dict)" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'node_id': 'AQEtg9I7HiFD34gYqa5U5JsrOfglIOZlQ_C-0OjogD5gnA',\n", " 'name': 'RentalX'}" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "result['organization']" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/html": [ "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'organization'</span>: <span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'node_id'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'AQEtg9I7HiFD34gYqa5U5JsrOfglIOZlQ_C-0OjogD5gnA'</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'name'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'RentalX'</span><span style=\"font-weight: bold\">}}</span>\n", "</pre>\n" ], "text/plain": [ "\u001b[1m{\u001b[0m\u001b[32m'organization'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'node_id'\u001b[0m: \u001b[32m'AQEtg9I7HiFD34gYqa5U5JsrOfglIOZlQ_C-0OjogD5gnA'\u001b[0m, \u001b[32m'name'\u001b[0m: \u001b[32m'RentalX'\u001b[0m\u001b[1m}\u001b[0m\u001b[1m}\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from rich.console import Console\n", "console = Console()\n", "console.print(result)" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [], "source": [ "async def generate_query(description: str) -> str:\n", " \"\"\"Generate a GraphQL query based on the schema and user description\"\"\"\n", " try:\n", " schema = await client.get_schema()\n", " \n", " # Provide context about the schema and request to help generate the query\n", " prompt = f\"\"\"Given this GraphQL schema:\n", "\n", "{schema}\n", "\n", "Generate a GraphQL query that: {description}\n", "\n", "Return only the GraphQL query without any explanation.\"\"\"\n", " \n", " return prompt\n", " except Exception as e:\n", " return f\"Error generating query: {str(e)}\"" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [], "source": [ "generated_query = await generate_query(\"name of organization\")" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Given this GraphQL schema:\\n\\n\"\"\"This directive allows results to be deferred during execution\"\"\"\\ndirective @defer on FIELD\\n\\n\"\"\"Directs the schema to enforce authorization on a field\"\"\"\\ndirective @aws_auth(\\n \"\"\"List of cognito user pool groups which have access on this field\"\"\"\\n cognito_groups: [String]\\n) on FIELD_DEFINITION\\n\\n\"\"\"\\nTells the service which subscriptions will be published to when this mutation is called. This directive is deprecated use @aws_susbscribe directive instead.\\n\"\"\"\\ndirective @aws_publish(\\n \"\"\"\\n List of subscriptions which will be published to when this mutation is called.\\n \"\"\"\\n subscriptions: [String]\\n) on FIELD_DEFINITION\\n\\n\"\"\"\\nTells the service this field/object has access authorized by a Lambda Authorizer.\\n\"\"\"\\ndirective @aws_lambda on OBJECT | FIELD_DEFINITION\\n\\n\"\"\"\\nTells the service this field/object has access authorized by an OIDC token.\\n\"\"\"\\ndirective @aws_oidc on OBJECT | FIELD_DEFINITION\\n\\n\"\"\"\\nTells the service this field/object has access authorized by sigv4 signing.\\n\"\"\"\\ndirective @aws_iam on OBJECT | FIELD_DEFINITION\\n\\n\"\"\"\\nTells the service this field/object has access authorized by an API key.\\n\"\"\"\\ndirective @aws_api_key on OBJECT | FIELD_DEFINITION\\n\\n\"\"\"\\nTells the service this field/object has access authorized by a Cognito User Pools token.\\n\"\"\"\\ndirective @aws_cognito_user_pools(\\n \"\"\"List of cognito user pool groups which have access on this field\"\"\"\\n cognito_groups: [String]\\n) on OBJECT | FIELD_DEFINITION\\n\\n\"\"\"Tells the service which mutation triggers this subscription.\"\"\"\\ndirective @aws_subscribe(\\n \"\"\"\\n List of mutations which will trigger this subscription when they are called.\\n \"\"\"\\n mutations: [String]\\n) on FIELD_DEFINITION\\n\\ntype Query {\\n organization: Organization!\\n state(id: ID!): State\\n directory_schema: DirectorySchema\\n node(node_id: ID!): Node\\n}\\n\\n\"\"\" The following types are the hierarchy types with connections up and down\\n\"\"\"\\ntype Organization {\\n node_id: ID!\\n name: String!\\n regions(filter: RegionFilter): [Region!]!\\n policies(filter: PolicyFilter): [Policy!]!\\n}\\n\\ntype Region {\\n node_id: ID!\\n region: String!\\n organization: Organization!\\n countries(filter: CountryFilter): [Country!]!\\n policies(filter: PolicyFilter): [Policy!]!\\n}\\n\\ntype Country {\\n node_id: ID!\\n country: String!\\n region: Region!\\n states(filter: StateFilter): [State!]!\\n policies(filter: PolicyFilter): [Policy!]!\\n}\\n\\ntype State {\\n node_id: ID!\\n state_short: String!\\n state_name: String!\\n country: Country!\\n policies(filter: PolicyFilter): [Policy!]!\\n is_authorized(request: PermissionRequest!): Boolean\\n}\\n\\ntype Policy {\\n policy_id: ID!\\n policy_type: String!\\n policy_document: AWSJSON!\\n\\n \"\"\" The node the policy is attached to\"\"\"\\n node_id: ID\\n policy_holder: Node\\n}\\n\\n\"\"\"\\nThe `AWSJSON` scalar type provided by AWS AppSync, represents a JSON string that complies with [RFC 8259](https://tools.ietf.org/html/rfc8259). Maps like \"**{\\\\\\\\\"upvotes\\\\\\\\\": 10}**\", lists like \"**[1,2,3]**\", and scalar values like \"**\\\\\\\\\"AWSJSON example string\\\\\\\\\"**\", \"**1**\", and \"**true**\" are accepted as valid JSON and will automatically be parsed and loaded in the resolver mapping templates as Maps, Lists, or Scalar values rather than as the literal input strings. Invalid JSON strings like \"**{a: 1}**\", \"**{\\'a\\': 1}**\" and \"**Unquoted string**\" will throw GraphQL validation errors.\\n\"\"\"\\nscalar AWSJSON\\n\\nunion Node = Organization | Region | Country | State\\n\\n\"\"\" To allow filtering the policies needed\"\"\"\\ninput PolicyFilter {\\n policy_type: String\\n}\\n\\ninput PermissionRequest {\\n principal: PermissionPrincipal\\n action: PermissionAction!\\n resource: PermissionResource\\n}\\n\\n\" This is the graphQL Schema for the OrgShield access API\\\\n The following types are used to check permissions\"\\ninput PermissionPrincipal {\\n \"\"\" dafault type is User\"\"\"\\n entity_type: String\\n entity_id: ID!\\n}\\n\\ninput PermissionAction {\\n \"\"\" dafault type is Action\"\"\"\\n action_type: String\\n action_id: ID!\\n}\\n\\ninput PermissionResource {\\n \"\"\" dafault type is Store\"\"\"\\n entity_type: String\\n entity_id: ID!\\n}\\n\\ninput StateFilter {\\n state_short: String!\\n}\\n\\ninput CountryFilter {\\n country: String!\\n}\\n\\ninput RegionFilter {\\n region: String\\n}\\n\\ntype DirectorySchema {\\n name: String\\n document: AWSJSON!\\n}\\n\\ntype Mutation {\\n \"\"\" Attach and detack poclicies\"\"\"\\n addPolicy(node_id: ID!, policy_type: String!, policy_document: AWSJSON!): Policy!\\n updatePolicy(policy_id: ID!, policy_document: AWSJSON!): ID!\\n removePolicy(node_id: ID!, policy_id: ID): Policy!\\n\\n \"\"\" Adding nodes to the OrgShield\"\"\"\\n addRegion(parent_node_id: ID!, node: RegionInput!): ID!\\n addCountry(parent_node_id: ID!, node: CountryInput!): ID!\\n addState(parent_node_id: ID!, node: StateInput!): ID!\\n\\n \"\"\" Updating nodes in the OrgShield\"\"\"\\n updateOrganization(node_id: ID!, node: OrganizationInput!): ID!\\n updateRegion(node_id: ID!, node: RegionInput!): ID!\\n updateCountry(node_id: ID!, node: CountryInput!): ID!\\n updateState(node_id: ID!, node: StateInput!): ID!\\n\\n \"\"\" Deleting nodes in the OrgShield\"\"\"\\n deleteOrganization(node_id: ID!, node: OrganizationInput!): ID!\\n deleteRegion(node_id: ID!, node: RegionInput!): ID!\\n deleteCountry(node_id: ID!, node: CountryInput!): ID!\\n deleteState(node_id: ID!, node: StateInput!): ID!\\n}\\n\\ninput RegionInput {\\n region: String!\\n}\\n\\ninput CountryInput {\\n country: String!\\n}\\n\\ninput StateInput {\\n state_short: String!\\n state_name: String!\\n}\\n\\ninput OrganizationInput {\\n name: String!\\n}\\n\\nGenerate a GraphQL query that: name of organization\\n\\nReturn only the GraphQL query without any explanation.'" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "generated_query" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "base", "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.8.5" } }, "nbformat": 4, "nbformat_minor": 2 }