└── vibe-with-py.ipynb /vibe-with-py.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "b1d6d3b1", 6 | "metadata": {}, 7 | "source": [ 8 | "# Vibe Coding with Python: A Practical Guide to AI-Assisted Development\n", 9 | "\n", 10 | "©️2025 Matt Harrison/MetaSnake LLC. All rights reserved.\n", 11 | "\n", 12 | "The core of this course is built around the **RECR Framework**:\n", 13 | "- **R**equirements: Define what you want to accomplish.\n", 14 | "- **E**xecute: Let the AI perform the task.\n", 15 | "- **C**heck: Review critically and provide feedback.\n", 16 | "- **R**epeat: Iterate and refine the process.\n", 17 | "\n", 18 | "Think of RECR as your playbook for managing your AI pair programmer like a junior developer.\n", 19 | "\n", 20 | "Feel free to reach out to Matt if your team needs help with AI-assisted development workflows (or Python/data science in general)! Contact info is at [MetaSnake LLC](https://metasnake.com)." 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "id": "c9f4f88a", 27 | "metadata": { 28 | "vscode": { 29 | "languageId": "plaintext" 30 | } 31 | }, 32 | "outputs": [], 33 | "source": [] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "id": "c269617d", 39 | "metadata": { 40 | "vscode": { 41 | "languageId": "plaintext" 42 | } 43 | }, 44 | "outputs": [], 45 | "source": [] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "id": "017fd715", 50 | "metadata": { 51 | "lines_to_next_cell": 2 52 | }, 53 | "source": [ 54 | "\n", 55 | "\n", 56 | "\n", 57 | "## Segment 1: Let's Get Set Up \n", 58 | "\n", 59 | "In this first segment, we'll get our environment and project structure ready for effective AI-assisted coding.\n", 60 | "\n", 61 | "### What is Aider? Why OpenRouter?\n", 62 | "\n", 63 | "- **OpenRouter**: \n", 64 | "\n", 65 | " - A unified API for accessing multiple Large Language Models (LLMs)\n", 66 | " - Simplifies the process of switching between models\n", 67 | " - Prevents vendor lock-in by allowing easy migration as new models emerge or prices change\n", 68 | "\n", 69 | "If you don't want to use OpenRouter, you can configure Aider to use individual model APIs directly. Here are some discounted (free/low-cost) models: https://aider.chat/docs/llms/cohere.html https://aider.chat/docs/llms/groq.html\n", 70 | "\n", 71 | "- **Aider**: \n", 72 | "\n", 73 | " - Open-source AI pair programmer\n", 74 | " - Terminal-based, works directly in your development environment\n", 75 | " - Deep Git integration for tracking AI-assisted changes\n", 76 | " - Supports multiple AI models via OpenRouter\n", 77 | " - Not really \"agentic\"—more like a collaborative coding partner\n", 78 | " - Great for iterative development using the RECR framework\n", 79 | " - Also great for getting a feel for how AI can assist in coding tasks (understanding contexts, generating code, fixing bugs, etc.)\n", 80 | "\n", 81 | "Other tools like Claude Code are more \"agentic\". However, I opine that you should use a tool that is more behind the scenes initially, so you can intuit how to best work with AI as a coding partner.\n", 82 | "\n", 83 | "\n", 84 | "### Key Features of Aider\n", 85 | "\n", 86 | "Aider comes packed with features to accelerate your development:\n", 87 | "- **Multi-Model Support**: Works with top-tier models like Claude, GPT-4, DeepSeek, and even local models.\n", 88 | "- **Code Intelligence**: Builds an internal map of your entire codebase to make more informed decisions.\n", 89 | "- **Deep Git Integration**: Automatically stages and commits changes with sensible commit messages. All AI work is trackable and reversible with standard Git commands.\n", 90 | "- **IDE Friendly**: Can be used alongside your favorite editor or even monitor files for changes based on comments.\n", 91 | "- **Quality Tools**: Can be configured to automatically run linters and tests, fixing errors in a tight feedback loop.\n", 92 | "\n", 93 | "### Model Options\n", 94 | "\n", 95 | "Models seem to be evolving rapidly. Performance and price can change frequently. With OpenRouter, you can pick a model that fits your needs.\n", 96 | "\n", 97 | "Fall 2025 models I recommend:\n", 98 | "\n", 99 | "- z-ai/glm-4.6\n", 100 | "- qwen/qwen3-next\n", 101 | "- google/gemini-2.5-flash-preview\n", 102 | "- openai/gpt-5 with reasoning set to low\n", 103 | "\n", 104 | "\n" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": null, 110 | "id": "478089c9", 111 | "metadata": { 112 | "vscode": { 113 | "languageId": "plaintext" 114 | } 115 | }, 116 | "outputs": [], 117 | "source": [] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": null, 122 | "id": "42805673", 123 | "metadata": { 124 | "vscode": { 125 | "languageId": "plaintext" 126 | } 127 | }, 128 | "outputs": [], 129 | "source": [] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "id": "5dd002ff", 134 | "metadata": {}, 135 | "source": [ 136 | "\n", 137 | "\n", 138 | "### Installing and Configuring Aider + OpenRouter\n", 139 | "\n", 140 | "We will walk through the installation process and initial configuration.\n", 141 | "\n", 142 | "1. **Install Aider**: The recommended way is using `aider-install`, which sets up an isolated Python environment for you.\n", 143 | " ```bash\n", 144 | " pip install aider-install\n", 145 | " aider-install\n", 146 | " ```\n", 147 | "\n", 148 | "2. **Get an OpenRouter API Key**: Sign up on the OpenRouter website and generate an API key. You can put it in your `.aider.conf.yml` file:\n", 149 | " ```yaml\n", 150 | " api-key:\n", 151 | " - openrouter=YOUR_OPENROUTER_API_KEY_HERE\n", 152 | " ```\n", 153 | "\n", 154 | "3. *Specify Your Model**: Choose a model from OpenRouter that suits your needs. For example, to use Claude 3 Opus:\n", 155 | " ```yaml\n", 156 | " model: openrouter/z-ai/glm-4.6\n", 157 | " ```\n", 158 | "\n", 159 | "\n", 160 | "\n", 161 | "> **Pro-Tip**: For cost and speed savings, consider enabling prompt caching if you use a model that supports it (like Claude or DeepSeek). You can do this with the `--cache-prompts` flag.\n" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": null, 167 | "id": "6c6ede9f", 168 | "metadata": { 169 | "vscode": { 170 | "languageId": "plaintext" 171 | } 172 | }, 173 | "outputs": [], 174 | "source": [] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": null, 179 | "id": "1f6e451a", 180 | "metadata": { 181 | "vscode": { 182 | "languageId": "plaintext" 183 | } 184 | }, 185 | "outputs": [], 186 | "source": [] 187 | }, 188 | { 189 | "cell_type": "markdown", 190 | "id": "f8aca064", 191 | "metadata": {}, 192 | "source": [ 193 | "\n", 194 | "### Modern Python Project Setup with `uv`\n", 195 | "\n", 196 | "When starting a new project, I like to have the boilerplate structure set up correctly (and not rely on AI to do it perfectly).\n", 197 | "\n", 198 | "We'll use `uv` for our project setup. It's a fast, modern Python package and project manager.\n", 199 | "- **Initial Setup**: A great way to start a new library project is `uv init --library `. This creates the correct `src/` layout and a basic `pyproject.toml`.\n", 200 | "- **The `pyproject.toml` File**: This is the heart of your project's configuration. A common pitfall is that AI assistants can struggle with the exact syntax for newer tools like `uv` and dependency groups. Be prepared to manually review and correct this file. A good pattern for dev dependencies with `uv` is:\n", 201 | " ```toml\n", 202 | " [dependency-groups]\n", 203 | " dev = [\n", 204 | " \"pytest>=8.3.5\",\n", 205 | " \"ruff>=0.13.1\",\n", 206 | " ]\n", 207 | " ```\n", 208 | "\n", 209 | "### Git + Aider = Collaborative Development\n", 210 | "\n", 211 | "Aider works best with a git repository. It automatically commits changes made with AI assistance, creating a clear history of your collaboration. We'll see how this works in practice.\n", 212 | "\n", 213 | "### Project Structure for AI Collaboration\n", 214 | "\n", 215 | "Before we start coding, we'll set up a simple project structure. A key best practice is to create a `context/` directory to hold our project \"brain.\"\n", 216 | "- `context/PRD.md`: Product Requirements Document (even a mini-one for our exercise).\n", 217 | "- `context/TASKS.md`: A numbered, ordered list of tasks to complete the PRD.\n", 218 | "\n", 219 | "### The \"One-Shot\" vs. \"PRD\" Approach\n", 220 | "\n", 221 | "A common temptation is to dump all your requirements into a single, massive prompt and ask the AI to build the entire project at once. This is the **\"one-shot\" approach**. While it sounds appealing, it often leads to:\n", 222 | "- Incorrect project structure (e.g., an extra nested directory).\n", 223 | "- Flawed configuration files (`pyproject.toml`).\n", 224 | "- Code with hardcoded logic to pass tests, rather than a correct implementation.\n", 225 | "- A long, expensive, and difficult-to-debug conversation.\n", 226 | "\n", 227 | "This course champions the **\"PRD Approach\"**: structured, iterative development using the RECR framework. It's more reliable, teaches you better habits, and ultimately faster.\n" 228 | ] 229 | }, 230 | { 231 | "cell_type": "code", 232 | "execution_count": null, 233 | "id": "8060268f", 234 | "metadata": { 235 | "vscode": { 236 | "languageId": "plaintext" 237 | } 238 | }, 239 | "outputs": [], 240 | "source": [] 241 | }, 242 | { 243 | "cell_type": "markdown", 244 | "id": "5279ab10", 245 | "metadata": {}, 246 | "source": [ 247 | "### Exercise: Configure Aider and Test an Edit\n", 248 | "\n", 249 | "**Goal**: Set up Aider with your OpenRouter key and make a simple, AI-assisted edit.\n", 250 | "\n", 251 | "1. Set your `OPENAI_API_KEY` environment variable.\n", 252 | "2. Launch Aider with a model of your choice (set up the `.aider.conf.yml`)\n", 253 | "3. At the `>` prompt, ask: \"Make a program that asks for a number and prints its factorial.\"\n", 254 | "4. Review the diff Aider shows you. It will automatically commit the change.\n", 255 | "5. Check your git log (`git log --oneline`) to see the commit Aider created.\n", 256 | "\n", 257 | "### Q&A \n", 258 | "\n", 259 | "A chance to ask questions about setup, configuration, or the tools we've discussed.\n", 260 | "\n", 261 | "### Break \n", 262 | "\n" 263 | ] 264 | }, 265 | { 266 | "cell_type": "code", 267 | "execution_count": null, 268 | "id": "2d283d26", 269 | "metadata": { 270 | "vscode": { 271 | "languageId": "plaintext" 272 | } 273 | }, 274 | "outputs": [], 275 | "source": [] 276 | }, 277 | { 278 | "cell_type": "markdown", 279 | "id": "42c567df", 280 | "metadata": {}, 281 | "source": [ 282 | "\n", 283 | "\n", 284 | "## Segment 2: The RECR Framework in Action\n", 285 | "\n", 286 | "Effective communication is key to getting great results from your AI pair programmer. This segment focuses on applying the RECR framework using Aider's chat modes to build a small project from scratch.\n", 287 | "\n", 288 | "### Aider Chat Modes: `/ask`, `/code`, and `/architect`\n", 289 | "\n", 290 | "Aider has different modes to structure your conversation with the AI. A powerful workflow is to bounce between modes:\n", 291 | "- **`/ask` mode**: Use this for discussion, planning, and getting suggestions. The AI will not edit any files. This is perfect for the **R**equirements phase of RECR.\n", 292 | "- **`/code` mode**: The default mode where the AI makes changes to your files. Use this for the **E**xecute phase.\n", 293 | "- **`/architect` mode**: Uses two models (an \"architect\" and an \"editor\") for complex tasks. Useful for models that are strong at reasoning but weaker at editing.\n", 294 | "\n", 295 | "### Live Coding: A Mini-Project with RECR and Aider Modes\n", 296 | "\n", 297 | "We will build a simple Python library for retirement planning from scratch, using Aider's modes to guide our RECR process. This library will eventually include functions to calculate future savings and retirement goals.\n", 298 | "\n", 299 | "#### **R - Requirements: Define What You Want to Accomplish**\n", 300 | "\n", 301 | "First, we need to give our AI developer a clear mission.\n", 302 | "\n", 303 | "1. We'll ask Aider to help us create a `context/PRD.md` file. We'll prompt it: \"Help me draft a mini-PRD for a simple Python retirement planning library. It should have a prime directive and list the core features, like calculating the future value of an investment.\"\n", 304 | "2. We'll continue: \"Now, create a `context/TASKS.md` file with a numbered list of small, manageable tasks to build this library using TDD. The first task should be to write a test for a function that calculates the future value of a series of regular contributions.\"\n", 305 | "4. Once we're happy with the plan, we can move to execution.\n", 306 | "\n", 307 | "#### **E - Execute: Let the AI Perform the Task**\n", 308 | "\n", 309 | "Now we guide the AI to write code.\n", 310 | "\n", 311 | "1. Tell Aider to run the next task. (You can refer to it by number from `context/TASKS.md`.)\n", 312 | "\n", 313 | "\n", 314 | "#### **C - Check: Review Critically and Provide Feedback**\n", 315 | "\n", 316 | "This is where your expertise is crucial.\n", 317 | "1. We'll review the AI-generated test file. Is the formula for future value correct? Does it test edge cases like a 0% interest rate?\n", 318 | "2. We'll run the tests (e.g., with `uv run pytest`). They will fail initially, which is expected in TDD.\n", 319 | "3. If the test logic is wrong, we'll provide specific feedback: \"The test should use the formula for compound interest on a series of payments, not simple interest.\"\n", 320 | "\n", 321 | "#### **R - Repeat: Iterate and Refine**\n", 322 | "\n", 323 | "We close the loop and move forward.\n", 324 | "1. Based on our feedback, we'll ask Aider to fix the tests.\n", 325 | "2. Once the tests are correct, we'll mark the task as complete and update our `CURRENT_TASK.md` for the next task (implementing the `calculate_future_value` function).\n", 326 | "3. We'll repeat the Execute -> Check -> Repeat cycle for each feature, such as adding a function to calculate required retirement savings.\n", 327 | "4. Often it is useful to clear out the context by using the `/clear` command." 328 | ] 329 | }, 330 | { 331 | "cell_type": "code", 332 | "execution_count": null, 333 | "id": "b1184de1", 334 | "metadata": { 335 | "vscode": { 336 | "languageId": "plaintext" 337 | } 338 | }, 339 | "outputs": [], 340 | "source": [] 341 | }, 342 | { 343 | "cell_type": "markdown", 344 | "id": "89662ded", 345 | "metadata": { 346 | "lines_to_next_cell": 0 347 | }, 348 | "source": [ 349 | "\n", 350 | "### Exercise: Build a Retirement Planning Library with RECR and Aider Modes\n", 351 | "\n", 352 | "**Goal**: Use the RECR framework and Aider's chat modes to build the core functionality of a Python retirement planning library.\n", 353 | "\n", 354 | "1. Start a new Aider session in a new directory (make sure the parent is not in another git repo.)\n", 355 | "2. Create the `context/` directory and the initial `PRD.md` and `TASKS.md` files for the retirement library.\n", 356 | "3. Work through the first 3-4 tasks using the full RECR cycle.\n", 357 | " - Task 1: Write and implement `calculate_future_value(principal, annual_contribution, rate, years)`.\n", 358 | " - Task 2: Write and implement `calculate_savings_goal(goal, current_savings, years, rate)`.\n", 359 | "4. Focus on creating clear requirements, checking the output carefully, and iterating.\n", 360 | "\n", 361 | "### Q&A \n", 362 | "\n", 363 | "### Break \n" 364 | ] 365 | }, 366 | { 367 | "cell_type": "markdown", 368 | "id": "47d6aa70", 369 | "metadata": {}, 370 | "source": [] 371 | }, 372 | { 373 | "cell_type": "markdown", 374 | "id": "d59ca26f", 375 | "metadata": {}, 376 | "source": [ 377 | "\n", 378 | "\n", 379 | "## Segment 3: AI Debugging, Refactoring, and Workflow Optimization \n", 380 | "\n", 381 | "\n", 382 | "### Advanced \"Checking\": Debugging and Refactoring\n", 383 | "\n", 384 | "- **Debugging**: When your code fails, paste the entire traceback into Aider (in `/code` mode) and ask for a fix. This is a core part of the \"Check\" phase.\n", 385 | "- **Manual Fixes**: Don't hesitate to step in and make corrections. Just because you're using an AI assistant doesn't mean you can't type or edit code yourself.\n", 386 | "- **Refactoring**: Ask the AI to improve code structure, add type hints, or optimize performance. This is also a \"Check\" activity—is the existing code good enough? How can it be better?\n", 387 | "- **Testing**: A great use of the \"Execute\" phase. Ask Aider: \"Write pytest unit tests for all the main functions in my application.\" In fact, I often have the PRD specify that I want to use TDD for development.\n", 388 | "\n", 389 | "### Optimizing Your Workflow: Cost, Context, and Quality\n", 390 | "\n", 391 | "Managing your AI assistant effectively is key to staying productive and keeping costs down.\n", 392 | "\n", 393 | "#### **Context Management: Keeping Your AI Focused**\n", 394 | "\n", 395 | "A long chat can confuse the AI and increase costs. We'll discuss best practices:\n", 396 | "- **The Token Tax**: Every request costs money. Be mindful of prompt length and the model's output. Use Aider's `/tokens` command to see your context usage.\n", 397 | "- **One Task, One Session**: For a new, complex task, consider starting a fresh Aider session (`/clear` in Aider) to provide a clean slate. This prevents \"context pollution\" from previous, unrelated work.\n", 398 | "- **Drop Unnecessary Files**: Use the `/drop` command to remove files from the active context. If a file isn't needed for the current task, drop it to save tokens and reduce confusion.\n", 399 | "- **Context is King**: The `context/` command will show you the current context.\n", 400 | "\n", 401 | "#### **Testing**\n", 402 | "\n", 403 | "\n", 404 | "- `!uv run pytest` - will run the test suite and ask to put the results in the context.\n", 405 | "\n", 406 | "- `/test uv run pytest` - will run the tests and automatically attempt to fix any failures.\n", 407 | "\n", 408 | "I often like to use the `-x` flag (in pytest) to allow the AI to only have to deal with one error at a time.\n", 409 | "\n", 410 | "You can use the `-v` flag to get more verbose output if needed. This can also assist the AI in diagnosing issues.\n", 411 | "\n", 412 | "\n", 413 | "\n", 414 | "#### **When to Switch Models**\n", 415 | "\n", 416 | "Not all tasks require the most expensive model. Learn to optimize for performance vs. cost.\n", 417 | "- **Complex Logic / Architecture (Requirements/Execute)**: Use a powerful model like GPT-4 or Claude Opus.\n", 418 | "- **Bug Fixes / Tests / Docs (Execute/Check)**: I generally start with the cheapest model that performs decently. If it appears to struggle, I switch to a better one. Check out the Aider leaderboard for performance reviews.\n", 419 | "- **When Stuck**: If your current model is making the same mistake repeatedly or is confused, use the `/model` command to switch to a more capable one.\n", 420 | "\n" 421 | ] 422 | }, 423 | { 424 | "cell_type": "code", 425 | "execution_count": null, 426 | "id": "5e1dea41", 427 | "metadata": { 428 | "vscode": { 429 | "languageId": "plaintext" 430 | } 431 | }, 432 | "outputs": [], 433 | "source": [] 434 | }, 435 | { 436 | "cell_type": "code", 437 | "execution_count": null, 438 | "id": "adb6a085", 439 | "metadata": { 440 | "vscode": { 441 | "languageId": "plaintext" 442 | } 443 | }, 444 | "outputs": [], 445 | "source": [] 446 | }, 447 | { 448 | "cell_type": "markdown", 449 | "id": "d216476a", 450 | "metadata": {}, 451 | "source": [ 452 | "\n", 453 | "### Exercise: Enhance the Retirement Planning Library\n", 454 | "\n", 455 | "**Goal**: Enhance the retirement planning library with more sophisticated features, robust error handling, and a suite of tests, using the optimized workflow.\n", 456 | "\n", 457 | "1. Open your retirement library project in Aider.\n", 458 | "2. Create a new task: \"Add a function to model retirement withdrawals, projecting how long savings will last. It should handle edge cases like running out of money.\"\n", 459 | "3. Execute the task.\n", 460 | "4. Manually run the tests. Check the results carefully. Does it handle negative values or zero years correctly?\n", 461 | "5. Create another task: \"Add type hints to all public functions in the library.\"\n", 462 | "\n", 463 | "\n", 464 | "\n", 465 | "### Q&A \n", 466 | "\n", 467 | "### Break \n", 468 | "\n", 469 | "\n", 470 | "\n", 471 | "\n" 472 | ] 473 | }, 474 | { 475 | "cell_type": "code", 476 | "execution_count": null, 477 | "id": "53d2567f", 478 | "metadata": { 479 | "vscode": { 480 | "languageId": "plaintext" 481 | } 482 | }, 483 | "outputs": [], 484 | "source": [] 485 | }, 486 | { 487 | "cell_type": "code", 488 | "execution_count": null, 489 | "id": "585ee25e", 490 | "metadata": { 491 | "vscode": { 492 | "languageId": "plaintext" 493 | } 494 | }, 495 | "outputs": [], 496 | "source": [] 497 | }, 498 | { 499 | "cell_type": "code", 500 | "execution_count": null, 501 | "id": "78ce4947", 502 | "metadata": { 503 | "vscode": { 504 | "languageId": "plaintext" 505 | } 506 | }, 507 | "outputs": [], 508 | "source": [] 509 | }, 510 | { 511 | "cell_type": "markdown", 512 | "id": "ad5cda05", 513 | "metadata": {}, 514 | "source": [ 515 | "\n", 516 | "## Conclusion\n", 517 | "\n", 518 | "Remember the RECR framework as your guide to effective AI-assisted development.\n", 519 | "\n", 520 | "- *R* - equirements: Define what you want to accomplish.\n", 521 | "- *E* - xecute: Let the AI perform the task.\n", 522 | "- *C* - heck: Review critically and provide feedback.\n", 523 | "- *R* - epeat: Iterate and refine the process.\n", 524 | "\n", 525 | "\n", 526 | "### When to Trust AI (and When Not to)\n", 527 | "\n", 528 | "- **Trust**: For boilerplate, tests, documentation, refactoring, and exploring solutions.\n", 529 | "- **Verify**: For complex business logic, security-sensitive code, and performance-critical paths. The \"Check\" phase is non-negotiable. You are the senior developer.\n", 530 | "\n", 531 | "### The Manager Mindset: Your New Role\n", 532 | "\n", 533 | "You've moved from being just a coder to a manager of an AI assistant. Your job is to provide clear requirements (the \"What\") and validate the work (the \"Check\"), letting the AI handle the implementation details (the \"How\").\n", 534 | "\n", 535 | "### Experimenting with New Workflows\n", 536 | "\n", 537 | "- Try different prompting strategies.\n", 538 | "- Combine Aider with other tools (e.g., linters, formatters).\n", 539 | "- Use AI to help you learn a new library or framework by asking it to generate example code based on its documentation.\n", 540 | "\n", 541 | "### Final Q&A \n", 542 | "\n", 543 | "### If your team needs help\n", 544 | "\n", 545 | "Please reach out. Happy to see how MetaSnake can assist your team." 546 | ] 547 | } 548 | ], 549 | "metadata": { 550 | "jupytext": { 551 | "cell_metadata_filter": "-all", 552 | "formats": "ipynb,md", 553 | "main_language": "python" 554 | }, 555 | "kernelspec": { 556 | "display_name": "Python 3 (ipykernel)", 557 | "language": "python", 558 | "name": "python3" 559 | }, 560 | "language_info": { 561 | "codemirror_mode": { 562 | "name": "ipython", 563 | "version": 3 564 | }, 565 | "file_extension": ".py", 566 | "mimetype": "text/x-python", 567 | "name": "python", 568 | "nbconvert_exporter": "python", 569 | "pygments_lexer": "ipython3", 570 | "version": "3.9.6" 571 | } 572 | }, 573 | "nbformat": 4, 574 | "nbformat_minor": 5 575 | } 576 | --------------------------------------------------------------------------------