{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# JMESPath Proxy Types Tutorial\n",
    "\n",
    "This notebook demonstrates the usage of JMESPath with the custom proxy types `JMESPathArrayProxy` and `JMESPathObjectProxy` provided by the Evo SDK.\n",
    "\n",
    "## Overview\n",
    "\n",
    "The Evo SDK provides enhanced JMESPath functionality through proxy types that:\n",
    "- Wrap JSON-like data structures to enable JMESPath queries as attribute/index access\n",
    "- Provide convenient serialization methods\n",
    "- Allow seamless chaining of JMESPath expressions\n",
    "\n",
    "## Key Features\n",
    "\n",
    "- `JMESPathObjectProxy`: Wraps dictionaries/mappings to enable JMESPath queries via `obj[\"expression\"]`\n",
    "- `JMESPathArrayProxy`: Wraps lists/sequences to enable JMESPath queries and indexing\n",
    "- Automatic proxy wrapping of nested structures\n",
    "- JSON serialization with support for UUID and MappingProxyType"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import the JMESPath module from the Evo SDK\n",
    "from uuid import uuid4\n",
    "\n",
    "from evo.jmespath import compile, proxy, search"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2",
   "metadata": {},
   "source": [
    "## Sample Data\n",
    "\n",
    "Let's create some sample data structures that we'll use throughout the examples:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Sample data structure representing a company with employees and projects\n",
    "company_data = {\n",
    "    \"name\": \"TechCorp\",\n",
    "    \"founded\": 2010,\n",
    "    \"id\": uuid4(),\n",
    "    \"employees\": [\n",
    "        {\n",
    "            \"id\": uuid4(),\n",
    "            \"name\": \"Alice Johnson\",\n",
    "            \"department\": \"Engineering\",\n",
    "            \"age\": 32,\n",
    "            \"skills\": [\"Python\", \"JavaScript\", \"Docker\"],\n",
    "            \"projects\": [\n",
    "                {\"name\": \"Project Alpha\", \"status\": \"completed\", \"priority\": \"high\"},\n",
    "                {\"name\": \"Project Beta\", \"status\": \"active\", \"priority\": \"medium\"},\n",
    "            ],\n",
    "        },\n",
    "        {\n",
    "            \"id\": uuid4(),\n",
    "            \"name\": \"Bob Smith\",\n",
    "            \"department\": \"Engineering\",\n",
    "            \"age\": 28,\n",
    "            \"skills\": [\"Java\", \"Kubernetes\", \"Python\"],\n",
    "            \"projects\": [\n",
    "                {\"name\": \"Project Gamma\", \"status\": \"active\", \"priority\": \"high\"},\n",
    "                {\"name\": \"Project Delta\", \"status\": \"planning\", \"priority\": \"low\"},\n",
    "            ],\n",
    "        },\n",
    "        {\n",
    "            \"id\": uuid4(),\n",
    "            \"name\": \"Carol Davis\",\n",
    "            \"department\": \"Marketing\",\n",
    "            \"age\": 35,\n",
    "            \"skills\": [\"Social Media\", \"Analytics\", \"Content Creation\"],\n",
    "            \"projects\": [{\"name\": \"Campaign X\", \"status\": \"completed\", \"priority\": \"high\"}],\n",
    "        },\n",
    "    ],\n",
    "    \"departments\": {\n",
    "        \"Engineering\": {\"budget\": 500000, \"head\": \"David Wilson\"},\n",
    "        \"Marketing\": {\"budget\": 200000, \"head\": \"Emma Thompson\"},\n",
    "    },\n",
    "}\n",
    "\n",
    "print(\"Sample data created with\", len(company_data[\"employees\"]), \"employees\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4",
   "metadata": {},
   "source": [
    "## 1. Basic JMESPath Operations\n",
    "\n",
    "### Creating Proxy Objects\n",
    "\n",
    "First, let's see how to create proxy objects using the `proxy()` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create proxy objects\n",
    "company_proxy = proxy(company_data)\n",
    "employees_proxy = proxy(company_data[\"employees\"])\n",
    "\n",
    "print(\"Company proxy type:\", type(company_proxy))\n",
    "print(\"Employees proxy type:\", type(employees_proxy))\n",
    "\n",
    "# Demonstrate that proxies automatically wrap the underlying data\n",
    "print(\"\\nOriginal data is preserved:\")\n",
    "print(\"Company name:\", company_proxy.raw[\"name\"])\n",
    "print(\"Number of employees:\", len(employees_proxy.raw))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6",
   "metadata": {},
   "source": [
    "### Basic Identifiers and Subexpressions\n",
    "\n",
    "JMESPath identifiers select keys from JSON objects. Subexpressions allow access to nested values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Using search() function - traditional JMESPath approach\n",
    "print(\"=== Using search() function ===\")\n",
    "print(\"Company name:\", search(\"name\", company_data))\n",
    "print(\"Founded year:\", search(\"founded\", company_data))\n",
    "print(\"Engineering budget:\", search(\"departments.Engineering.budget\", company_data))\n",
    "\n",
    "print(\"\\n=== Using JMESPathObjectProxy ===\")\n",
    "# Using proxy objects with [] notation for JMESPath expressions\n",
    "print(\"Company name:\", company_proxy[\"name\"])\n",
    "print(\"Founded year:\", company_proxy[\"founded\"])\n",
    "print(\"Engineering budget:\", company_proxy[\"departments.Engineering.budget\"])\n",
    "\n",
    "# Nested access with subexpressions\n",
    "print(\"Marketing head:\", company_proxy[\"departments.Marketing.head\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8",
   "metadata": {},
   "source": [
    "## 2. Array Operations and Indexing\n",
    "\n",
    "The `JMESPathArrayProxy` provides both standard array indexing and JMESPath expressions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Array indexing with JMESPathArrayProxy\n",
    "print(\"=== Array Indexing ===\")\n",
    "print(\"First employee:\", employees_proxy[0][\"name\"])\n",
    "print(\"Last employee:\", employees_proxy[-1][\"name\"])\n",
    "print(\"Second employee's department:\", employees_proxy[1][\"department\"])\n",
    "\n",
    "print(\"\\n=== JMESPath expressions on arrays ===\")\n",
    "# Using JMESPath expressions as strings with arrays\n",
    "print(\"All employee names:\", employees_proxy[\"[*].name\"])\n",
    "print(\"All departments:\", employees_proxy[\"[*].department\"])\n",
    "\n",
    "# Accessing nested array elements\n",
    "print(\"\\n=== Nested array access ===\")\n",
    "print(\"First employee's first skill:\", employees_proxy[0][\"skills[0]\"])\n",
    "print(\"All skills of first employee:\", employees_proxy[0][\"skills\"])\n",
    "\n",
    "# Slicing operations\n",
    "print(\"\\n=== Array slicing ===\")\n",
    "numbers = proxy([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])\n",
    "print(\"First 5 numbers:\", numbers[\"[:5]\"])\n",
    "print(\"Last 5 numbers:\", numbers[\"[5:]\"])\n",
    "print(\"Every other number:\", numbers[\"[::2]\"])\n",
    "print(\"Reverse order:\", numbers[\"[::-1]\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10",
   "metadata": {},
   "source": [
    "## 3. Projections\n",
    "\n",
    "Projections are one of JMESPath's most powerful features, allowing you to apply expressions to collections:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11",
   "metadata": {},
   "outputs": [],
   "source": [
    "# List projections\n",
    "print(\"=== List Projections ===\")\n",
    "print(\"All employee names:\", company_proxy[\"employees[*].name\"])\n",
    "print(\"All employee ages:\", company_proxy[\"employees[*].age\"])\n",
    "\n",
    "# Object projections\n",
    "print(\"\\n=== Object Projections ===\")\n",
    "print(\"All department budgets:\", company_proxy[\"departments.*.budget\"])\n",
    "print(\"All department heads:\", company_proxy[\"departments.*.head\"])\n",
    "\n",
    "# Nested projections\n",
    "print(\"\\n=== Nested Projections ===\")\n",
    "print(\"All project names:\", company_proxy[\"employees[*].projects[*].name\"])\n",
    "print(\"All skills:\", company_proxy[\"employees[*].skills[*]\"])\n",
    "\n",
    "# Flatten projections\n",
    "print(\"\\n=== Flatten Projections ===\")\n",
    "# Compare [*] vs [] - the latter flattens the result\n",
    "nested_projects = company_proxy[\"employees[*].projects[*].name\"]\n",
    "flattened_projects = company_proxy[\"employees[].projects[].name\"]\n",
    "\n",
    "print(\"Nested structure:\", type(nested_projects), \"- length:\", len(nested_projects))\n",
    "print(\"Flattened structure:\", type(flattened_projects), \"- length:\", len(flattened_projects))\n",
    "print(\"Flattened projects:\", flattened_projects)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Filter projections - finding specific data\n",
    "print(\"=== Filter Projections ===\")\n",
    "\n",
    "# Find employees in Engineering department\n",
    "engineers = company_proxy[\"employees[?department=='Engineering'].name\"]\n",
    "print(\"Engineers:\", engineers)\n",
    "\n",
    "# Find active projects\n",
    "active_projects = company_proxy[\"employees[].projects[?status=='active'].name\"]\n",
    "print(\"Active projects:\", active_projects)\n",
    "\n",
    "# Find high priority projects\n",
    "high_priority = company_proxy[\"employees[].projects[?priority=='high']\"]\n",
    "print(\"High priority projects:\", high_priority)\n",
    "\n",
    "# Complex filters - employees older than 30\n",
    "experienced = company_proxy[\"employees[?age>`30`].name\"]\n",
    "print(\"Employees over 30:\", experienced)\n",
    "\n",
    "# Find employees with Python skills\n",
    "python_devs = company_proxy[\"employees[?contains(skills, 'Python')].name\"]\n",
    "print(\"Python developers:\", python_devs)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13",
   "metadata": {},
   "source": [
    "## 4. Pipe Expressions and MultiSelect\n",
    "\n",
    "Pipe expressions allow you to chain operations, while MultiSelect helps create custom data structures:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "14",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Pipe expressions - stopping projections and chaining operations\n",
    "print(\"=== Pipe Expressions ===\")\n",
    "\n",
    "# Get first employee name\n",
    "first_employee = company_proxy[\"employees[*].name | [0]\"]\n",
    "print(\"First employee:\", first_employee)\n",
    "\n",
    "# Get sorted list of employee names\n",
    "sorted_names = company_proxy[\"employees[*].name | sort(@)\"]\n",
    "print(\"Sorted names:\", sorted_names)\n",
    "\n",
    "# MultiSelect lists - creating arrays of selected values\n",
    "print(\"\\n=== MultiSelect Lists ===\")\n",
    "employee_summary = company_proxy[\"employees[*].[name, age, department]\"]\n",
    "print(\"Employee summaries:\", employee_summary)\n",
    "\n",
    "# MultiSelect hashes - creating objects with selected keys\n",
    "print(\"\\n=== MultiSelect Hashes ===\")\n",
    "employee_profiles = company_proxy[\"employees[*].{Name: name, Age: age, Dept: department, SkillCount: length(skills)}\"]\n",
    "print(\"Employee profiles:\")\n",
    "for profile in employee_profiles:\n",
    "    print(f\"  {profile}\")\n",
    "\n",
    "# Complex multiselect with nested data\n",
    "project_info = company_proxy[\"employees[*].{Employee: name, Projects: projects[*].{Name: name, Status: status}}\"]\n",
    "print(\"\\nProject information:\")\n",
    "for info in project_info:\n",
    "    print(f\"  {info['Employee']}: {len(info['Projects'])} projects\")\n",
    "    for proj in info[\"Projects\"]:\n",
    "        print(f\"    - {proj['Name']} ({proj['Status']})\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15",
   "metadata": {},
   "source": [
    "## 5. Functions\n",
    "\n",
    "JMESPath provides many built-in functions for data transformation and analysis:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Length and aggregation functions\n",
    "print(\"=== Length and Aggregation ===\")\n",
    "print(\"Number of employees:\", company_proxy[\"length(employees)\"])\n",
    "print(\"Number of departments:\", company_proxy[\"length(departments)\"])\n",
    "\n",
    "# Math functions\n",
    "print(\"\\n=== Math Functions ===\")\n",
    "print(\"Average age:\", company_proxy[\"avg(employees[*].age)\"])\n",
    "print(\"Max age:\", company_proxy[\"max(employees[*].age)\"])\n",
    "print(\"Min age:\", company_proxy[\"min(employees[*].age)\"])\n",
    "print(\"Sum of all ages:\", company_proxy[\"sum(employees[*].age)\"])\n",
    "\n",
    "# Sorting functions\n",
    "print(\"\\n=== Sorting Functions ===\")\n",
    "print(\"Employees by age (ascending):\", company_proxy[\"sort_by(employees, &age)[*].{name: name, age: age}\"])\n",
    "print(\"Oldest employee:\", company_proxy[\"max_by(employees, &age).name\"])\n",
    "print(\"Youngest employee:\", company_proxy[\"min_by(employees, &age).name\"])\n",
    "\n",
    "# String functions\n",
    "print(\"\\n=== String Functions ===\")\n",
    "all_skills = company_proxy[\"employees[].skills[]\"]\n",
    "print(\"All unique skills:\", company_proxy[\"employees[].skills[] | sort(@)\"])\n",
    "\n",
    "# Type functions\n",
    "print(\"\\n=== Type Functions ===\")\n",
    "print(\"Company ID as string:\", company_proxy[\"to_string(id)\"])\n",
    "print(\"Founded year as string:\", company_proxy[\"to_string(founded)\"])\n",
    "\n",
    "# Advanced: Finding employees with most projects\n",
    "print(\"\\n=== Advanced: Most Active Employee ===\")\n",
    "most_active = company_proxy[\"max_by(employees, &length(projects))\"]\n",
    "print(f\"Most active employee: {most_active['name']} with {len(most_active['projects'])} projects\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17",
   "metadata": {},
   "source": [
    "## 6. Working with Compiled Expressions\n",
    "\n",
    "For better performance when running the same query multiple times, you can compile expressions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "\n",
    "# Compile expressions for reuse\n",
    "print(\"=== Compiled Expressions ===\")\n",
    "\n",
    "# Compile common queries\n",
    "get_names = compile(\"employees[*].name\")\n",
    "get_engineers = compile(\"employees[?department=='Engineering']\")\n",
    "get_active_projects = compile(\"employees[].projects[?status=='active'].name\")\n",
    "\n",
    "# Use compiled expressions\n",
    "print(\"Names:\", get_names.search(company_data))\n",
    "print(\"Engineers:\", [emp[\"name\"] for emp in get_engineers.search(company_data)])\n",
    "print(\"Active projects:\", get_active_projects.search(company_data))\n",
    "\n",
    "# The results are automatically wrapped in proxy types\n",
    "names_result = get_names.search(company_data)\n",
    "print(\"\\nResult type:\", type(names_result))\n",
    "print(\"Can iterate:\", list(names_result))\n",
    "\n",
    "# Performance benefit: compile once, use many times\n",
    "\n",
    "# Test with repeated usage\n",
    "test_data = [company_data] * 1000\n",
    "\n",
    "start = time.perf_counter_ns()\n",
    "for data in test_data:\n",
    "    search(\"employees[*].name\", data)\n",
    "search_time = (time.perf_counter_ns() - start) * 0.000000001\n",
    "\n",
    "start = time.perf_counter_ns()\n",
    "compiled_expr = compile(\"employees[*].name\")\n",
    "for data in test_data:\n",
    "    compiled_expr.search(data)\n",
    "compile_time = (time.perf_counter_ns() - start) * 0.000000001\n",
    "\n",
    "print(f\"\\nPerformance comparison ({len(test_data)} iterations):\")\n",
    "print(f\"Using search(): {search_time:.4f} seconds\")\n",
    "print(f\"Using compile(): {compile_time:.4f} seconds\")\n",
    "print(f\"Speedup: {search_time / compile_time:.2f}x\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19",
   "metadata": {},
   "source": [
    "## 7. JSON Serialization Features\n",
    "\n",
    "The proxy types provide enhanced JSON serialization with support for UUID and other special types:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20",
   "metadata": {},
   "outputs": [],
   "source": [
    "# JSON serialization with proxy types\n",
    "print(\"=== JSON Serialization ===\")\n",
    "\n",
    "# Get a subset of data\n",
    "employee_subset = company_proxy[\"employees[0]\"]\n",
    "print(\"Employee data type:\", type(employee_subset))\n",
    "\n",
    "# The json_dumps method handles UUIDs and other special types automatically\n",
    "print(\"\\nSerialized employee:\")\n",
    "print(employee_subset.json_dumps(indent=2))\n",
    "\n",
    "# Compare with standard json.dumps (would fail with UUID)\n",
    "try:\n",
    "    import json\n",
    "\n",
    "    json.dumps(employee_subset.raw)\n",
    "    print(\"Standard json.dumps worked\")\n",
    "except TypeError as e:\n",
    "    print(f\"Standard json.dumps failed: {e}\")\n",
    "\n",
    "# Demonstrate pretty printing with __repr__\n",
    "print(\"\\n=== Pretty Printing ===\")\n",
    "skills_proxy = company_proxy[\"employees[0].skills\"]\n",
    "print(\"Skills representation:\")\n",
    "print(repr(skills_proxy))\n",
    "\n",
    "# Working with nested proxy results\n",
    "departments_subset = company_proxy[\"departments\"]\n",
    "print(f\"\\nDepartments type: {type(departments_subset)}\")\n",
    "print(\"Departments JSON:\")\n",
    "print(departments_subset.json_dumps(indent=2))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "This tutorial demonstrated the key features of the Evo SDK's JMESPath proxy types:\n",
    "\n",
    "### JMESPathObjectProxy\n",
    "- Wraps dictionaries and allows JMESPath queries via `obj[\"expression\"]`\n",
    "- Supports nested object navigation with dot notation\n",
    "- Automatically returns proxy-wrapped results for chaining\n",
    "\n",
    "### JMESPathArrayProxy  \n",
    "- Wraps lists and supports both integer indexing and JMESPath expressions\n",
    "- Enables array slicing with JMESPath syntax\n",
    "- Supports iteration with automatic proxy wrapping\n",
    "\n",
    "### Key Benefits\n",
    "- **Seamless integration**: Use JMESPath expressions as dictionary keys or array indices\n",
    "- **Automatic wrapping**: Results are automatically wrapped in appropriate proxy types\n",
    "- **Enhanced serialization**: Built-in support for UUID and other special types\n",
    "- **Performance**: Compile expressions for repeated use\n",
    "- **Chaining**: Natural chaining of JMESPath operations\n",
    "\n",
    "### Best Practices\n",
    "1. Use `proxy()` to wrap your data structures for JMESPath operations\n",
    "2. Compile expressions when using them repeatedly for better performance\n",
    "3. Leverage the `json_dumps()` method for serialization with special type support\n",
    "4. Chain operations naturally using the proxy types\n",
    "5. Use filter expressions to find specific data efficiently\n",
    "\n",
    "The proxy types make JMESPath feel more natural in Python while providing all the power of the JMESPath query language!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "evo-sdk",
   "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.10.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
