Skip to content

Instantly share code, notes, and snippets.

@lucaswiman
Last active April 20, 2022 02:12
Show Gist options
  • Save lucaswiman/4bbe3aab911f0bade43eb9fa6a7a0854 to your computer and use it in GitHub Desktop.
Save lucaswiman/4bbe3aab911f0bade43eb9fa6a7a0854 to your computer and use it in GitHub Desktop.
Using Wrapt to alter a method signature
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "98e1a19f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Collecting wrapt\n",
" Using cached wrapt-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl (35 kB)\n",
"Installing collected packages: wrapt\n",
"Successfully installed wrapt-1.14.0\n",
"\u001b[33mWARNING: You are using pip version 21.2.4; however, version 22.0.4 is available.\n",
"You should consider upgrading via the '/Users/lucaswiman/personal/private-stuff/work/20220402--etl-blog-post/.venv/bin/python -m pip install --upgrade pip' command.\u001b[0m\n"
]
}
],
"source": [
"!pip install wrapt"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "881d2fc1",
"metadata": {},
"outputs": [],
"source": [
"import wrapt\n",
"import uuid\n",
"import inspect\n",
"from typing import *\n",
"\n",
"def get_argspec_including_annotations(\n",
" func: Callable,\n",
" added_annotations: Dict[str, type],\n",
" removed_args: Sequence[str] = (),\n",
" validate=False,\n",
") -> inspect.FullArgSpec:\n",
" signature = inspect.signature(func)\n",
" spec = inspect.getfullargspec(func)._asdict()\n",
" for name, annotation in added_annotations.items():\n",
" if name not in signature.parameters:\n",
" spec[\"kwonlyargs\"].append(name)\n",
" spec[\"annotations\"][name] = annotation\n",
" for name in removed_args:\n",
" if name in signature.parameters:\n",
" if name in spec[\"args\"]:\n",
" spec[\"args\"].remove(name)\n",
" if name in spec[\"kwonlyargs\"]:\n",
" spec[\"kwonlyargs\"].remove(name)\n",
" spec[\"annotations\"].pop(name, None)\n",
"\n",
" return inspect.FullArgSpec(**spec)\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "76a5f833",
"metadata": {},
"outputs": [],
"source": [
"def get_fastapi_method_argspec(func: Callable) -> inspect.ArgSpec:\n",
" return get_argspec_including_annotations(func, {\"client_token\": uuid.UUID}, [\"job_context\"])\n"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "39d7e9a3",
"metadata": {},
"outputs": [],
"source": [
"@wrapt.decorator(adapter=wrapt.adapter_factory(get_fastapi_method_argspec))\n",
"def munge_types(wrapped, instance, args, kwargs):\n",
" client_token = kwargs.pop(\"client_token\", None)\n",
" if not client_token:\n",
" client_token = uuid.uuid4()\n",
" job_context = {\"client_token\": client_token}\n",
" signature = inspect.signature(wrapped)\n",
" bound = signature.bind(*args, **kwargs)\n",
" bound.apply_defaults()\n",
" \n",
" # Note: job_context can be positional or kwarg and it will do the right thing\n",
" bound.arguments[\"job_context\"] = job_context\n",
" print(f\"calling with {bound.args=}, {bound.kwargs=}\")\n",
" return wrapped(*bound.args, **bound.kwargs)\n"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "c10658d6",
"metadata": {},
"outputs": [],
"source": [
"@munge_types\n",
"def add(\n",
" x: int,\n",
" *,\n",
" y: int = 1,\n",
" job_context: Optional[\"JobContext\"] = None,\n",
") -> int:\n",
" print(f\"got {job_context=} for {x=}\")\n",
" return x + 1"
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "47a9e0f0",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"calling with bound.args=(1,), bound.kwargs={'y': 2, 'job_context': {'client_token': UUID('2f2a15e5-9b9d-4b2e-a434-46d9c3e2e9ae')}}\n",
"got job_context={'client_token': UUID('2f2a15e5-9b9d-4b2e-a434-46d9c3e2e9ae')} for x=1\n"
]
},
{
"data": {
"text/plain": [
"2"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"add(1, y=2)"
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "4959bf47",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"calling with bound.args=(1,), bound.kwargs={'y': 1, 'job_context': {'client_token': UUID('84476126-2c91-4f31-a6c0-b5924816d172')}}\n",
"got job_context={'client_token': UUID('84476126-2c91-4f31-a6c0-b5924816d172')} for x=1\n"
]
},
{
"data": {
"text/plain": [
"2"
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"add(1)"
]
},
{
"cell_type": "code",
"execution_count": 35,
"id": "f8f5b4d4",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'x': int,\n",
" 'y': int,\n",
" 'job_context': typing.Optional[ForwardRef('JobContext')],\n",
" 'return': int}"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"add.__annotations__"
]
},
{
"cell_type": "code",
"execution_count": 36,
"id": "4f3786f7",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"FullArgSpec(args=['x'], varargs=None, varkw=None, defaults=None, kwonlyargs=['y', 'client_token'], kwonlydefaults={'y': 1}, annotations={'return': <class 'int'>, 'x': <class 'int'>, 'y': <class 'int'>, 'client_token': <class 'uuid.UUID'>})"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"inspect.getfullargspec(add)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f56442cb",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.9.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment