[{"data":1,"prerenderedAt":26500},["ShallowReactive",2],{"navigation_docs":3,"-python-fastapi-sqlalchemy-orm":3379,"-python-fastapi-sqlalchemy-orm-surround":26496},[4,1707,1912,2366,2547,2649,2856,2978,3028,3085,3119,3245,3322,3375],{"title":5,"icon":6,"path":7,"stem":8,"children":9},"C#","i-devicon-csharp","\u002Fcsharp","01.csharp",[10,13,60,90,120,202,219,253,379,404,457,650,1364,1654,1703],{"title":11,"path":7,"stem":12},"C# та .NET","01.csharp\u002Findex",{"title":14,"icon":15,"path":16,"stem":17,"children":18,"page":59},"Fundamentals","i-lucide-book-open","\u002Fcsharp\u002Ffundamentals","01.csharp\u002F01.fundamentals",[19,23,27,31,35,39,43,47,51,55],{"title":20,"path":21,"stem":22},"Вступ до екосистеми .NET","\u002Fcsharp\u002Ffundamentals\u002Fintroduction-to-ecosystem","01.csharp\u002F01.fundamentals\u002F01.introduction-to-ecosystem",{"title":24,"path":25,"stem":26},"Структура програми на C#","\u002Fcsharp\u002Ffundamentals\u002Fprogram-structure","01.csharp\u002F01.fundamentals\u002F02.program-structure",{"title":28,"path":29,"stem":30},"Змінні та Типи Даних","\u002Fcsharp\u002Ffundamentals\u002Fvariables-data-types","01.csharp\u002F01.fundamentals\u002F03.variables-data-types",{"title":32,"path":33,"stem":34},"Масиви","\u002Fcsharp\u002Ffundamentals\u002Farrays","01.csharp\u002F01.fundamentals\u002F04.arrays",{"title":36,"path":37,"stem":38},"Strings & Text Handling","\u002Fcsharp\u002Ffundamentals\u002Fstrings-text-handling","01.csharp\u002F01.fundamentals\u002F05.strings-text-handling",{"title":40,"path":41,"stem":42},"Дати і Час","\u002Fcsharp\u002Ffundamentals\u002Fdates-time-handling","01.csharp\u002F01.fundamentals\u002F06.dates-time-handling",{"title":44,"path":45,"stem":46},"Потік Керування","\u002Fcsharp\u002Ffundamentals\u002Fcontrol-flow","01.csharp\u002F01.fundamentals\u002F07.control-flow",{"title":48,"path":49,"stem":50},"Методи","\u002Fcsharp\u002Ffundamentals\u002Fmethods","01.csharp\u002F01.fundamentals\u002F08.methods",{"title":52,"path":53,"stem":54},"Основи Відлагодження","\u002Fcsharp\u002Ffundamentals\u002Fdebugging-basics","01.csharp\u002F01.fundamentals\u002F09.debugging-basics",{"title":56,"path":57,"stem":58},"Інтерактивна Консоль (Classic)","\u002Fcsharp\u002Ffundamentals\u002Finteractive-console","01.csharp\u002F01.fundamentals\u002F10.interactive-console",false,{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":59},"OOP","i-lucide-box","\u002Fcsharp\u002Foop","01.csharp\u002F02.oop",[66,70,74,78,82,86],{"title":67,"path":68,"stem":69},"Package Management (Управління Пакетами)","\u002Fcsharp\u002Foop\u002Fpackage-management","01.csharp\u002F02.oop\u002F01.package-management",{"title":71,"path":72,"stem":73},"Класи та Об'єкти","\u002Fcsharp\u002Foop\u002Fclasses-objects","01.csharp\u002F02.oop\u002F02.classes-objects",{"title":75,"path":76,"stem":77},"Властивості та Поля","\u002Fcsharp\u002Foop\u002Fproperties-fields","01.csharp\u002F02.oop\u002F03.properties-fields",{"title":79,"path":80,"stem":81},"Стовпи ООП","\u002Fcsharp\u002Foop\u002Foop-pillars","01.csharp\u002F02.oop\u002F04.oop-pillars",{"title":83,"path":84,"stem":85},"Advanced Types","\u002Fcsharp\u002Foop\u002Fadvanced-types","01.csharp\u002F02.oop\u002F05.advanced-types",{"title":87,"path":88,"stem":89},"Namespaces (Простори Імен)","\u002Fcsharp\u002Foop\u002Fnamespaces","01.csharp\u002F02.oop\u002F06.namespaces",{"title":91,"icon":92,"path":93,"stem":94,"children":95,"page":59},"Advanced Core","i-lucide-zap","\u002Fcsharp\u002Fadvanced-core","01.csharp\u002F03.advanced-core",[96,100,104,108,112,116],{"title":97,"path":98,"stem":99},"Generics (Узагальнення)","\u002Fcsharp\u002Fadvanced-core\u002Fgenerics","01.csharp\u002F03.advanced-core\u002F01.generics",{"title":101,"path":102,"stem":103},"Делегати, Події та Лямбда-вирази","\u002Fcsharp\u002Fadvanced-core\u002Fdelegates-events-lambdas","01.csharp\u002F03.advanced-core\u002F02.delegates-events-lambdas",{"title":105,"path":106,"stem":107},"Interfaces Deep Dive (Інтерфейси: Поглиблений Розгляд)","\u002Fcsharp\u002Fadvanced-core\u002Finterfaces-deep-dive","01.csharp\u002F03.advanced-core\u002F03.interfaces-deep-dive",{"title":109,"path":110,"stem":111},"Обробка Винятків","\u002Fcsharp\u002Fadvanced-core\u002Fexception-handling","01.csharp\u002F03.advanced-core\u002F04.exception-handling",{"title":113,"path":114,"stem":115},"Pattern Matching","\u002Fcsharp\u002Fadvanced-core\u002Fpattern-matching","01.csharp\u002F03.advanced-core\u002F05.pattern-matching",{"title":117,"path":118,"stem":119},"Додаткові Можливості C#","\u002Fcsharp\u002Fadvanced-core\u002Fadditional-features","01.csharp\u002F03.advanced-core\u002F06.additional-features",{"title":121,"icon":122,"path":123,"stem":124,"children":125,"page":59},"Architecture Best Practices","i-lucide-building-2","\u002Fcsharp\u002Farchitecture-best-practices","01.csharp\u002F04.architecture-best-practices",[126,130,149,153,157,161,165,169],{"title":127,"path":128,"stem":129},"Software Design Principles (Частина 1)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fsoftware-design-principles","01.csharp\u002F04.architecture-best-practices\u002F01.software-design-principles",{"title":131,"icon":132,"path":133,"stem":134,"children":135,"page":59},"Design Patterns","i-lucide-folder","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns",[136],{"title":137,"icon":132,"path":138,"stem":139,"children":140,"page":59},"Creational","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational",[141,145],{"title":142,"path":143,"stem":144},"Singleton (Одинак)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational\u002Fsingleton","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational\u002F01.singleton",{"title":146,"path":147,"stem":148},"Builder (Будівельник)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdesign-patterns\u002Fcreational\u002Fbuilder","01.csharp\u002F04.architecture-best-practices\u002F02.design-patterns\u002Fcreational\u002F02.builder",{"title":150,"path":151,"stem":152},"Building Professional CLIs","\u002Fcsharp\u002Farchitecture-best-practices\u002Fbuilding-professional-clis","01.csharp\u002F04.architecture-best-practices\u002F03.building-professional-clis",{"title":154,"path":155,"stem":156},"Validation & Flow Control","\u002Fcsharp\u002Farchitecture-best-practices\u002Fvalidation-flow-control","01.csharp\u002F04.architecture-best-practices\u002F04.validation-flow-control",{"title":158,"path":159,"stem":160},"The Modern .NET Host (Microsoft.Extensions)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fmodern-dotnet-host","01.csharp\u002F04.architecture-best-practices\u002F05.modern-dotnet-host",{"title":162,"path":163,"stem":164},"Data Mapper: Repository та DAO патерни (Частина 1)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdata-mapper-part1","01.csharp\u002F04.architecture-best-practices\u002F06.data-mapper-part1",{"title":166,"path":167,"stem":168},"Data Mapper: Repository та DAO патерни (Частина 2)","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdata-mapper-part2","01.csharp\u002F04.architecture-best-practices\u002F07.data-mapper-part2",{"title":170,"icon":132,"path":171,"stem":172,"children":173,"page":59},"Di Ioc","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc",[174,178,182,186,190,194,198],{"title":175,"path":176,"stem":177},"Проблема залежностей та Інверсія Контролю","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fthe-dependency-problem","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F01.the-dependency-problem",{"title":179,"path":180,"stem":181},"Будуємо власний Service Container","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fbuild-your-own-container","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F02.build-your-own-container",{"title":183,"path":184,"stem":185},"Service Locator: Паттерн та Анти-паттерн","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fservice-locator-pattern","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F03.service-locator-pattern",{"title":187,"path":188,"stem":189},"Паттерни Dependency Injection","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fdependency-injection-patterns","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F04.dependency-injection-patterns",{"title":191,"path":192,"stem":193},"Microsoft DI: IServiceCollection та IServiceProvider","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fmicrosoft-di-deep-dive","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F05.microsoft-di-deep-dive",{"title":195,"path":196,"stem":197},"Service Lifetimes та Scopes","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fservice-lifetimes-and-scopes","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F06.service-lifetimes-and-scopes",{"title":199,"path":200,"stem":201},"DI Анти-паттерни та Найкращі Практики","\u002Fcsharp\u002Farchitecture-best-practices\u002Fdi-ioc\u002Fdi-anti-patterns-and-best-practices","01.csharp\u002F04.architecture-best-practices\u002F08.di-ioc\u002F07.di-anti-patterns-and-best-practices",{"title":203,"icon":132,"path":204,"stem":205,"children":206,"page":59},"Standard Library","\u002Fcsharp\u002Fstandard-library","01.csharp\u002F05.standard-library",[207,211,215],{"title":208,"path":209,"stem":210},"Collections (Колекції)","\u002Fcsharp\u002Fstandard-library\u002Fcollections","01.csharp\u002F05.standard-library\u002F01.collections",{"title":212,"path":213,"stem":214},"High Performance Types (Високопродуктивні Типи)","\u002Fcsharp\u002Fstandard-library\u002Fhigh-performance-types","01.csharp\u002F05.standard-library\u002F02.high-performance-types",{"title":216,"path":217,"stem":218},"LINQ (Language Integrated Query)","\u002Fcsharp\u002Fstandard-library\u002Flinq","01.csharp\u002F05.standard-library\u002F03.linq",{"title":220,"icon":221,"path":222,"stem":223,"children":224,"page":59},"System Internals Concurrency","i-lucide-server","\u002Fcsharp\u002Fsystem-internals-concurrency","01.csharp\u002F06.system-internals-concurrency",[225,229,233,237,241,245,249],{"title":226,"path":227,"stem":228},"Memory Management","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fmemory-management","01.csharp\u002F06.system-internals-concurrency\u002F01.memory-management",{"title":230,"path":231,"stem":232},"Reflection API: System.Type та Метадані","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Freflection-fundamentals","01.csharp\u002F06.system-internals-concurrency\u002F02.reflection-fundamentals",{"title":234,"path":235,"stem":236},"Attributes та Dynamic Language Runtime","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fattributes-dynamic","01.csharp\u002F06.system-internals-concurrency\u002F03.attributes-dynamic",{"title":238,"path":239,"stem":240},"Expression Trees: Швидка Альтернатива Рефлексії","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fexpression-trees-compiled","01.csharp\u002F06.system-internals-concurrency\u002F04.expression-trees-compiled",{"title":242,"path":243,"stem":244},"Source Generators: Compile-Time Code Generation","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fsource-generators","01.csharp\u002F06.system-internals-concurrency\u002F05.source-generators",{"title":246,"path":247,"stem":248},"Multithreading Fundamentals","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fmultithreading-fundamentals","01.csharp\u002F06.system-internals-concurrency\u002F06.multithreading-fundamentals",{"title":250,"path":251,"stem":252},"Synchronization Primitives","\u002Fcsharp\u002Fsystem-internals-concurrency\u002Fsynchronization-primitives","01.csharp\u002F06.system-internals-concurrency\u002F07.synchronization-primitives",{"title":254,"icon":255,"path":256,"stem":257,"children":258,"page":59},"System Programming Windows","i-lucide-cpu","\u002Fcsharp\u002Fsystem-programming-windows","01.csharp\u002F07.system-programming-windows",[259,263,267,271,275,279,283,287,291,295,299,303,307,311,315,319,323,327,331,335,339,343,347,351,355,359,363,367,371,375],{"title":260,"path":261,"stem":262},"Як Працює Операційна Система","\u002Fcsharp\u002Fsystem-programming-windows\u002Fhow-os-works","01.csharp\u002F07.system-programming-windows\u002F01.how-os-works",{"title":264,"path":265,"stem":266},"Процеси в .NET — API та Запуск","\u002Fcsharp\u002Fsystem-programming-windows\u002Fprocesses-in-dotnet","01.csharp\u002F07.system-programming-windows\u002F02.processes-in-dotnet",{"title":268,"path":269,"stem":270},"Процеси в .NET — IPC та Міжпроцесна Комунікація","\u002Fcsharp\u002Fsystem-programming-windows\u002F02a.processes-ipc","01.csharp\u002F07.system-programming-windows\u002F02a.processes-ipc",{"title":272,"path":273,"stem":274},"Application Domains та Збірки — AppDomain і AssemblyLoadContext","\u002Fcsharp\u002Fsystem-programming-windows\u002Fappdomains-assemblies","01.csharp\u002F07.system-programming-windows\u002F03.appdomains-assemblies",{"title":276,"path":277,"stem":278},"Application Domains та Збірки — Plug-in Система з Hot-Reload","\u002Fcsharp\u002Fsystem-programming-windows\u002F03a.appdomains-plugin-system","01.csharp\u002F07.system-programming-windows\u002F03a.appdomains-plugin-system",{"title":280,"path":281,"stem":282},"Потоки — Основи та API Thread","\u002Fcsharp\u002Fsystem-programming-windows\u002Fthread-fundamentals","01.csharp\u002F07.system-programming-windows\u002F04.thread-fundamentals",{"title":284,"path":285,"stem":286},"Потоки — Lifecycle, Пріоритети та Безпечне Завершення","\u002Fcsharp\u002Fsystem-programming-windows\u002F04a.thread-lifecycle-priorities","01.csharp\u002F07.system-programming-windows\u002F04a.thread-lifecycle-priorities",{"title":288,"path":289,"stem":290},"Проблеми Спільного Стану — Race Condition та Data Race","\u002Fcsharp\u002Fsystem-programming-windows\u002Fshared-state-problems","01.csharp\u002F07.system-programming-windows\u002F05.shared-state-problems",{"title":292,"path":293,"stem":294},"Проблеми Спільного Стану — Memory Model та volatile","\u002Fcsharp\u002Fsystem-programming-windows\u002F05a.shared-state-memory-model","01.csharp\u002F07.system-programming-windows\u002F05a.shared-state-memory-model",{"title":296,"path":297,"stem":298},"Синхронізація — Monitor, lock та еволюція примітивів","\u002Fcsharp\u002Fsystem-programming-windows\u002Fsynchronization-fundamentals","01.csharp\u002F07.system-programming-windows\u002F06.synchronization-fundamentals",{"title":300,"path":301,"stem":302},"Синхронізація — Наскрізний Приклад та Deadlock Detection","\u002Fcsharp\u002Fsystem-programming-windows\u002F06a.synchronization-walkthrough","01.csharp\u002F07.system-programming-windows\u002F06a.synchronization-walkthrough",{"title":304,"path":305,"stem":306},"Синхронізація — Mutex, Semaphore та Event-Based Primitives","\u002Fcsharp\u002Fsystem-programming-windows\u002Fsynchronization-advanced","01.csharp\u002F07.system-programming-windows\u002F07.synchronization-advanced",{"title":308,"path":309,"stem":310},"Синхронізація — Interlocked, Volatile та Lock-Free Структури","\u002Fcsharp\u002Fsystem-programming-windows\u002F07a.synchronization-advanced-walkthrough","01.csharp\u002F07.system-programming-windows\u002F07a.synchronization-advanced-walkthrough",{"title":312,"path":313,"stem":314},"Interlocked, CAS та Lock-Free Структури","\u002Fcsharp\u002Fsystem-programming-windows\u002Finterlocked-cas-lockfree","01.csharp\u002F07.system-programming-windows\u002F08.interlocked-cas-lockfree",{"title":316,"path":317,"stem":318},"Volatile, Memory Model та Spinning","\u002Fcsharp\u002Fsystem-programming-windows\u002F08a.volatile-memory-model","01.csharp\u002F07.system-programming-windows\u002F08a.volatile-memory-model",{"title":320,"path":321,"stem":322},"ThreadPool — Пул Потоків для Ефективного Виконання","\u002Fcsharp\u002Fsystem-programming-windows\u002Fthread-pool","01.csharp\u002F07.system-programming-windows\u002F09.thread-pool",{"title":324,"path":325,"stem":326},"ThreadPool — Просунуті Сценарії та Внутрішня Будова","\u002Fcsharp\u002Fsystem-programming-windows\u002F09a.thread-pool-advanced","01.csharp\u002F07.system-programming-windows\u002F09a.thread-pool-advanced",{"title":328,"path":329,"stem":330},"Concurrent та Immutable Collections","\u002Fcsharp\u002Fsystem-programming-windows\u002Fconcurrent-collections","01.csharp\u002F07.system-programming-windows\u002F10.concurrent-collections",{"title":332,"path":333,"stem":334},"TPL, Task та Композиція — Від Thread до Task","\u002Fcsharp\u002Fsystem-programming-windows\u002Ftpl-parallel-plinq","01.csharp\u002F07.system-programming-windows\u002F11.tpl-parallel-plinq",{"title":336,"path":337,"stem":338},"Parallel Class та PLINQ — Data Parallelism","\u002Fcsharp\u002Fsystem-programming-windows\u002F11a.tpl-parallel-plinq-advanced","01.csharp\u002F07.system-programming-windows\u002F11a.tpl-parallel-plinq-advanced",{"title":340,"path":341,"stem":342},"Async\u002FAwait — Фундамент Асинхронного Програмування","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-fundamentals","01.csharp\u002F07.system-programming-windows\u002F12.async-fundamentals",{"title":344,"path":345,"stem":346},"SynchronizationContext та ConfigureAwait — Контекст Виконання","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-context-configureawait","01.csharp\u002F07.system-programming-windows\u002F13.async-context-configureawait",{"title":348,"path":349,"stem":350},"Async — Просунуті Паттерни","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-advanced","01.csharp\u002F07.system-programming-windows\u002F14.async-advanced",{"title":352,"path":353,"stem":354},"System.Threading.Channels — Async Producer-Consumer","\u002Fcsharp\u002Fsystem-programming-windows\u002Fchannels","01.csharp\u002F07.system-programming-windows\u002F15.channels",{"title":356,"path":357,"stem":358},"Асинхронна Синхронізація","\u002Fcsharp\u002Fsystem-programming-windows\u002Fasync-synchronization","01.csharp\u002F07.system-programming-windows\u002F16.async-synchronization",{"title":360,"path":361,"stem":362},"Unsafe Code та Вказівники","\u002Fcsharp\u002Fsystem-programming-windows\u002Funsafe-code","01.csharp\u002F07.system-programming-windows\u002F17.unsafe-code",{"title":364,"path":365,"stem":366},"P\u002FInvoke та Windows API — Міст між .NET та Native Code","\u002Fcsharp\u002Fsystem-programming-windows\u002Fpinvoke-winapi","01.csharp\u002F07.system-programming-windows\u002F18.pinvoke-winapi",{"title":368,"path":369,"stem":370},"Реєстр Windows — Центральна База Конфігурації Системи","\u002Fcsharp\u002Fsystem-programming-windows\u002Fwindows-registry","01.csharp\u002F07.system-programming-windows\u002F19.windows-registry",{"title":372,"path":373,"stem":374},"Windows Hooks, Hotkeys та Services — Глибока Інтеграція з ОС","\u002Fcsharp\u002Fsystem-programming-windows\u002Fwindows-hooks-services","01.csharp\u002F07.system-programming-windows\u002F20.windows-hooks-services",{"title":376,"path":377,"stem":378},"Системне Програмування C# (Windows) — 07.system-programming-windows","\u002Fcsharp\u002Fsystem-programming-windows\u002Fimplementation_plan","01.csharp\u002F07.system-programming-windows\u002Fimplementation_plan",{"title":380,"icon":132,"path":381,"stem":382,"children":383,"page":59},"Io","\u002Fcsharp\u002Fio","01.csharp\u002F08.io",[384,388,392,396,400],{"title":385,"path":386,"stem":387},"8.1.1. Основи роботи з файловою системою","\u002Fcsharp\u002Fio\u002Ffile-system-basics","01.csharp\u002F08.io\u002F01.file-system-basics",{"title":389,"path":390,"stem":391},"8.1.2. Потоки (Streams) та Серіалізація Даних","\u002Fcsharp\u002Fio\u002Fstreams-serialization","01.csharp\u002F08.io\u002F02.streams-serialization",{"title":393,"path":394,"stem":395},"8.2.1. JSON Serialization з System.Text.Json","\u002Fcsharp\u002Fio\u002Fjson-serialization","01.csharp\u002F08.io\u002F03.json-serialization",{"title":397,"path":398,"stem":399},"8.2.2. XML Serialization та LINQ to XML","\u002Fcsharp\u002Fio\u002Fxml-serialization","01.csharp\u002F08.io\u002F04.xml-serialization",{"title":401,"path":402,"stem":403},"8.2.3. Binary Serialization: MessagePack та Protocol Buffers","\u002Fcsharp\u002Fio\u002Fbinary-serialization","01.csharp\u002F08.io\u002F05.binary-serialization",{"title":405,"icon":132,"path":406,"stem":407,"children":408,"page":59},"Ado Net","\u002Fcsharp\u002Fado-net","01.csharp\u002F09.ado-net",[409,413,417,421,425,429,433,437,441,445,449,453],{"title":410,"path":411,"stem":412},"9.1. Введення в ADO.NET","\u002Fcsharp\u002Fado-net\u002Fintroduction-to-adonet","01.csharp\u002F09.ado-net\u002F01.introduction-to-adonet",{"title":414,"path":415,"stem":416},"9.2. Клас DbConnection — з'єднання з базою даних","\u002Fcsharp\u002Fado-net\u002Fconnection","01.csharp\u002F09.ado-net\u002F02.connection",{"title":418,"path":419,"stem":420},"9.3. Клас DbCommand — виконання SQL-запитів","\u002Fcsharp\u002Fado-net\u002Fcommand-and-queries","01.csharp\u002F09.ado-net\u002F03.command-and-queries",{"title":422,"path":423,"stem":424},"9.4. Клас DbDataReader — ефективне читання даних","\u002Fcsharp\u002Fado-net\u002Fdatareader","01.csharp\u002F09.ado-net\u002F04.datareader",{"title":426,"path":427,"stem":428},"9.5. Параметризовані запити та захист від SQL Injection","\u002Fcsharp\u002Fado-net\u002Fparameters-and-sql-injection","01.csharp\u002F09.ado-net\u002F05.parameters-and-sql-injection",{"title":430,"path":431,"stem":432},"9.6. Транзакції в ADO.NET","\u002Fcsharp\u002Fado-net\u002Ftransactions","01.csharp\u002F09.ado-net\u002F06.transactions",{"title":434,"path":435,"stem":436},"9.7. DbProviderFactory — провайдер-незалежний код","\u002Fcsharp\u002Fado-net\u002Fprovider-factory","01.csharp\u002F09.ado-net\u002F07.provider-factory",{"title":438,"path":439,"stem":440},"9.8. Асинхронний доступ до даних","\u002Fcsharp\u002Fado-net\u002Fasync-data-access","01.csharp\u002F09.ado-net\u002F08.async-data-access",{"title":442,"path":443,"stem":444},"9.9. Від'єднаний режим: DataSet, DataTable, DataRow","\u002Fcsharp\u002Fado-net\u002Fdisconnected-mode-dataset","01.csharp\u002F09.ado-net\u002F09.disconnected-mode-dataset",{"title":446,"path":447,"stem":448},"9.10. DataAdapter — міст між DataSet та базою даних","\u002Fcsharp\u002Fado-net\u002Fdata-adapter","01.csharp\u002F09.ado-net\u002F10.data-adapter",{"title":450,"path":451,"stem":452},"9.11. Data Mapper та Repository: Архітектура доступу до даних","\u002Fcsharp\u002Fado-net\u002Fdata-mapper-repository","01.csharp\u002F09.ado-net\u002F11.data-mapper-repository",{"title":454,"path":455,"stem":456},"9.12. Identity Map, Unit of Work та Specification Pattern","\u002Fcsharp\u002Fado-net\u002Fadvanced-patterns","01.csharp\u002F09.ado-net\u002F12.advanced-patterns",{"title":458,"icon":255,"path":459,"stem":460,"children":461,"page":59},"Ef Core","\u002Fcsharp\u002Fef-core","01.csharp\u002F10.ef-core",[462,466,470,474,478,482,486,490,494,498,502,506,510,514,518,522,526,532,538,542,546,550,554,558,562,566,570,574,578,582,586,590,594,598,602,606,610,614,618,622,626,630,634,638,642,646],{"title":463,"path":464,"stem":465},"Що таке ORM? Від SQL до об'єктів","\u002Fcsharp\u002Fef-core\u002Fwhat-is-orm","01.csharp\u002F10.ef-core\u002F01.what-is-orm",{"title":467,"path":468,"stem":469},"Перший проєкт — від нуля до CRUD","\u002Fcsharp\u002Fef-core\u002Ffirst-project","01.csharp\u002F10.ef-core\u002F02.first-project",{"title":471,"path":472,"stem":473},"DbContext — Серце EF Core","\u002Fcsharp\u002Fef-core\u002Fdbcontext-deep-dive","01.csharp\u002F10.ef-core\u002F03.dbcontext-deep-dive",{"title":475,"path":476,"stem":477},"Провайдери баз даних — Архітектура та Вибір СУБД","\u002Fcsharp\u002Fef-core\u002Fdatabase-providers","01.csharp\u002F10.ef-core\u002F04.database-providers",{"title":479,"path":480,"stem":481},"Конвенції EF Core — Магія без конфігурації","\u002Fcsharp\u002Fef-core\u002Fconventions","01.csharp\u002F10.ef-core\u002F05.conventions",{"title":483,"path":484,"stem":485},"Fluent API та Data Annotations — Явна конфігурація моделі","\u002Fcsharp\u002Fef-core\u002Ffluent-api-vs-annotations","01.csharp\u002F10.ef-core\u002F06.fluent-api-vs-annotations",{"title":487,"path":488,"stem":489},"Зв'язки — One-to-One та One-to-Many","\u002Fcsharp\u002Fef-core\u002Frelationships-basics","01.csharp\u002F10.ef-core\u002F07.relationships-basics",{"title":491,"path":492,"stem":493},"Зв'язки Advanced — Many-to-Many та Складні Сценарії","\u002Fcsharp\u002Fef-core\u002Frelationships-advanced","01.csharp\u002F10.ef-core\u002F08.relationships-advanced",{"title":495,"path":496,"stem":497},"Властивості — Типи, Конвертери, Компаратори (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fproperty-configuration-part1","01.csharp\u002F10.ef-core\u002F09.property-configuration-part1",{"title":499,"path":500,"stem":501},"Властивості — Value Comparers, Generators, Shadow Properties (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fproperty-configuration-part2","01.csharp\u002F10.ef-core\u002F09.property-configuration-part2",{"title":503,"path":504,"stem":505},"Складні типи — Owned Types та Complex Types (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fcomplex-types-owned-part1","01.csharp\u002F10.ef-core\u002F10.complex-types-owned-part1",{"title":507,"path":508,"stem":509},"Складні типи — Complex Types, Keyless Entities, Порівняння (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fcomplex-types-owned-part2","01.csharp\u002F10.ef-core\u002F10.complex-types-owned-part2",{"title":511,"path":512,"stem":513},"JSON Columns — Складні дані у JSON (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fjson-columns-part1","01.csharp\u002F10.ef-core\u002F11.json-columns-part1",{"title":515,"path":516,"stem":517},"JSON Columns — Value Comparers, Індекси, Провайдери (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fjson-columns-part2","01.csharp\u002F10.ef-core\u002F11.json-columns-part2",{"title":519,"path":520,"stem":521},"Успадкування — Абстрактні класи та TPH (Частина 1)","\u002Fcsharp\u002Fef-core\u002Finheritance-part1","01.csharp\u002F10.ef-core\u002F12.inheritance-part1",{"title":523,"path":524,"stem":525},"Успадкування — TPT, TPC та Порівняння Стратегій (Частина 2)","\u002Fcsharp\u002Fef-core\u002Finheritance-part2","01.csharp\u002F10.ef-core\u002F12.inheritance-part2",{"title":527,"path":528,"stem":529,"children":530},"Індекси, Обмеження та Схема (Частина 1)","\u002Fcsharp\u002Fef-core\u002Findexes-constraints-part1","01.csharp\u002F10.ef-core\u002F13.indexes-constraints-part1",[531],{"title":527,"path":528,"stem":529},{"title":533,"path":534,"stem":535,"children":536},"Індекси, Обмеження та Схема (Частина 2)","\u002Fcsharp\u002Fef-core\u002Findexes-constraints-part2","01.csharp\u002F10.ef-core\u002F13.indexes-constraints-part2",[537],{"title":533,"path":534,"stem":535},{"title":539,"path":540,"stem":541},"Seed Data — Початкові Дані (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fseeding-part1","01.csharp\u002F10.ef-core\u002F14.seeding-part1",{"title":543,"path":544,"stem":545},"Seed Data — SQL-скрипти, Bogus та Стратегії (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fseeding-part2","01.csharp\u002F10.ef-core\u002F14.seeding-part2",{"title":547,"path":548,"stem":549},"Global Query Filters — Глобальні Фільтри (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fglobal-query-filters-part1","01.csharp\u002F10.ef-core\u002F15.global-query-filters-part1",{"title":551,"path":552,"stem":553},"Global Query Filters — Підводні камені та Інтеграція (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fglobal-query-filters-part2","01.csharp\u002F10.ef-core\u002F15.global-query-filters-part2",{"title":555,"path":556,"stem":557},"LINQ-запити в EF Core (Частина 1)","\u002Fcsharp\u002Fef-core\u002Flinq-queries-part1","01.csharp\u002F10.ef-core\u002F16.linq-queries-part1",{"title":559,"path":560,"stem":561},"LINQ-запити в EF Core (Частина 2)","\u002Fcsharp\u002Fef-core\u002Flinq-queries-part2","01.csharp\u002F10.ef-core\u002F16.linq-queries-part2",{"title":563,"path":564,"stem":565},"Завантаження Пов'язаних Даних (Частина 1)","\u002Fcsharp\u002Fef-core\u002Floading-related-data-part1","01.csharp\u002F10.ef-core\u002F17.loading-related-data-part1",{"title":567,"path":568,"stem":569},"Завантаження Пов'язаних Даних (Частина 2)","\u002Fcsharp\u002Fef-core\u002Floading-related-data-part2","01.csharp\u002F10.ef-core\u002F17.loading-related-data-part2",{"title":571,"path":572,"stem":573},"Raw SQL, Views та Stored Procedures (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fraw-sql-part1","01.csharp\u002F10.ef-core\u002F18.raw-sql-part1",{"title":575,"path":576,"stem":577},"Raw SQL — Stored Procedures, DbFunction та Bulk Operations (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fraw-sql-part2","01.csharp\u002F10.ef-core\u002F18.raw-sql-part2",{"title":579,"path":580,"stem":581},"Продвинуті Запити — Compiled Queries, Bulk та Оптимізація (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fadvanced-queries-part1","01.csharp\u002F10.ef-core\u002F19.advanced-queries-part1",{"title":583,"path":584,"stem":585},"Продвинуті Запити — Query Tags, Bulk та Interceptors (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fadvanced-queries-part2","01.csharp\u002F10.ef-core\u002F19.advanced-queries-part2",{"title":587,"path":588,"stem":589},"Change Tracker — Відстеження Змін (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fchange-tracking-part1","01.csharp\u002F10.ef-core\u002F20.change-tracking-part1",{"title":591,"path":592,"stem":593},"Change Tracker — Графи Об'єктів та Disconnected (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fchange-tracking-part2","01.csharp\u002F10.ef-core\u002F20.change-tracking-part2",{"title":595,"path":596,"stem":597},"Збереження Даних та Транзакції (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fsaving-data-part1","01.csharp\u002F10.ef-core\u002F21.saving-data-part1",{"title":599,"path":600,"stem":601},"Збереження Даних — Concurrency та Outbox (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fsaving-data-part2","01.csharp\u002F10.ef-core\u002F21.saving-data-part2",{"title":603,"path":604,"stem":605},"Конкурентність та Блокування (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fconcurrency-part1","01.csharp\u002F10.ef-core\u002F22.concurrency-part1",{"title":607,"path":608,"stem":609},"Конкурентність — Дедлоки та Queue Processing (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fconcurrency-part2","01.csharp\u002F10.ef-core\u002F22.concurrency-part2",{"title":611,"path":612,"stem":613},"Міграції в EF Core — Основи (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fmigrations-basics-part1","01.csharp\u002F10.ef-core\u002F23.migrations-basics-part1",{"title":615,"path":616,"stem":617},"Міграції в EF Core — Основи (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fmigrations-basics-part2","01.csharp\u002F10.ef-core\u002F23.migrations-basics-part2",{"title":619,"path":620,"stem":621},"Міграції — Просунуті Сценарії (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fmigrations-advanced-part1","01.csharp\u002F10.ef-core\u002F24.migrations-advanced-part1",{"title":623,"path":624,"stem":625},"Міграції — Просунуті Сценарії (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fmigrations-advanced-part2","01.csharp\u002F10.ef-core\u002F24.migrations-advanced-part2",{"title":627,"path":628,"stem":629},"Управління Схемою та Database-First (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fschema-management-part1","01.csharp\u002F10.ef-core\u002F25.schema-management-part1",{"title":631,"path":632,"stem":633},"Управління Схемою та Database-First (Частина 2)","\u002Fcsharp\u002Fef-core\u002Fschema-management-part2","01.csharp\u002F10.ef-core\u002F25.schema-management-part2",{"title":635,"path":636,"stem":637},"Продуктивність EF Core — Основи (Частина 1)","\u002Fcsharp\u002Fef-core\u002Fperformance-fundamentals-part1","01.csharp\u002F10.ef-core\u002F26.performance-fundamentals-part1",{"title":639,"path":640,"stem":641},"Interceptors в EF Core (Частина 1)","\u002Fcsharp\u002Fef-core\u002Finterceptors-part1","01.csharp\u002F10.ef-core\u002F29.interceptors-part1",{"title":643,"path":644,"stem":645},"Interceptors в EF Core — Connection, Transaction та Materialization (Частина 2)","\u002Fcsharp\u002Fef-core\u002Finterceptors-part2","01.csharp\u002F10.ef-core\u002F29.interceptors-part2",{"title":647,"path":648,"stem":649},"План вивчення Entity Framework Core — Повний курс","\u002Fcsharp\u002Fef-core\u002Fimplementation_plan","01.csharp\u002F10.ef-core\u002Fimplementation_plan",{"title":651,"icon":652,"path":653,"stem":654,"children":655,"page":59},"ASP.NET","i-devicon-dotnetcore","\u002Fcsharp\u002Faspnet","01.csharp\u002F11.aspnet",[656,730,791,869,927,941,967,1057,1111,1182,1212,1289,1346],{"title":657,"icon":658,"path":659,"stem":660,"children":661,"page":59},"Minimal API","i-lucide-network","\u002Fcsharp\u002Faspnet\u002Fminimal-api","01.csharp\u002F11.aspnet\u002F01.minimal-api",[662,666,670,674,678,682,686,690,694,698,702,706,710,714,718,722,726],{"title":663,"path":664,"stem":665},"Вступ до ASP.NET та еволюція фреймворку","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fintroduction","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F01.introduction",{"title":667,"path":668,"stem":669},"Перший додаток на ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Ffirst-application","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F02.first-application",{"title":671,"path":672,"stem":673},"WebApplication, Builder та Dependency Injection","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fwebapplication-builder","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F03.webapplication-builder",{"title":675,"path":676,"stem":677},"Конвеєр запитів та Middleware","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frequest-pipeline-middleware","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F04.request-pipeline-middleware",{"title":679,"path":680,"stem":681},"Маршрутизація в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frouting-basics","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F05.routing-basics",{"title":683,"path":684,"stem":685},"Маршрутизація в ASP.NET Core: Розширені можливості","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Frouting-advanced","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F06.routing-advanced",{"title":687,"path":688,"stem":689},"Статичні файли в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstatic-files","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F07.static-files",{"title":691,"path":692,"stem":693},"Статичні Активи: MapStaticAssets (ASP.NET Core 9.0)","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstatic-assets","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F08.static-assets",{"title":695,"path":696,"stem":697},"Конфігурація в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fconfiguration-fundamentals","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F09.configuration-fundamentals",{"title":699,"path":700,"stem":701},"Конфігурація: Паттерн Options","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fconfiguration-options","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F10.configuration-options",{"title":703,"path":704,"stem":705},"Логування в ASP.NET Core: Основи","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Flogging-basics","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F11.logging-basics",{"title":707,"path":708,"stem":709},"Логування: Serilog та Middleware","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Flogging-advanced","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F12.logging-advanced",{"title":711,"path":712,"stem":713},"Управління станом: HttpContext.Items та Cookies","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fstate-management","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F13.state-management",{"title":715,"path":716,"stem":717},"Стан сесії: Sessions","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fsession-state","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F14.session-state",{"title":719,"path":720,"stem":721},"Структура проєкту: від хаосу до архітектури","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fproject-structure","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F15.project-structure",{"title":723,"path":724,"stem":725},"Scalar у Minimal API: повний проєкт і Fluent OpenAPI","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fscalar-openapi-fluent","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F16.scalar-openapi-fluent",{"title":727,"path":728,"stem":729},"Swagger \u002F Swashbuckle у Minimal API: окремий класичний шлях","\u002Fcsharp\u002Faspnet\u002Fminimal-api\u002Fswagger-swashbuckle","01.csharp\u002F11.aspnet\u002F01.minimal-api\u002F17.swagger-swashbuckle",{"title":731,"icon":658,"path":732,"stem":733,"children":734,"page":59},"API","\u002Fcsharp\u002Faspnet\u002Fapi","01.csharp\u002F11.aspnet\u002F02.api",[735,739,743,747,751,755,759,763,767,771,775,779,783,787],{"title":736,"path":737,"stem":738},"Що таке API. Клієнт-серверна архітектура","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fwhat-is-api","01.csharp\u002F11.aspnet\u002F02.api\u002F01.what-is-api",{"title":740,"path":741,"stem":742},"Формати даних: JSON, XML, TOML та бінарні формати","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fdata-formats","01.csharp\u002F11.aspnet\u002F02.api\u002F02.data-formats",{"title":744,"path":745,"stem":746},"Парадигми API та концепція REST","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-paradigms-rest","01.csharp\u002F11.aspnet\u002F02.api\u002F03.api-paradigms-rest",{"title":748,"path":749,"stem":750},"HTTP-методи, статус-коди та заголовки","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fhttp-methods-status-codes","01.csharp\u002F11.aspnet\u002F02.api\u002F04.http-methods-status-codes",{"title":752,"path":753,"stem":754},"Організація HTTP API за принципами REST","\u002Fcsharp\u002Faspnet\u002Fapi\u002Frest-organizing","01.csharp\u002F11.aspnet\u002F02.api\u002F05.rest-organizing",{"title":756,"path":757,"stem":758},"Номенклатура URL та CRUD-операції","\u002Fcsharp\u002Faspnet\u002Fapi\u002Furl-nomenclature-crud","01.csharp\u002F11.aspnet\u002F02.api\u002F06.url-nomenclature-crud",{"title":760,"path":761,"stem":762},"Правила дизайну: іменування та стандарти","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-naming","01.csharp\u002F11.aspnet\u002F02.api\u002F07.api-design-naming",{"title":764,"path":765,"stem":766},"Валідація, ліміти та обробка помилок","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-validation","01.csharp\u002F11.aspnet\u002F02.api\u002F08.api-design-validation",{"title":768,"path":769,"stem":770},"Обробка помилок у Minimal API","\u002Fcsharp\u002Faspnet\u002Fapi\u002Ferror-handling-http","01.csharp\u002F11.aspnet\u002F02.api\u002F09.error-handling-http",{"title":772,"path":773,"stem":774},"Ідемпотентність та синхронізація стану","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fidempotency-sync","01.csharp\u002F11.aspnet\u002F02.api\u002F10.idempotency-sync",{"title":776,"path":777,"stem":778},"Пагінація та організація списків","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fpagination-lists","01.csharp\u002F11.aspnet\u002F02.api\u002F11.pagination-lists",{"title":780,"path":781,"stem":782},"Безпека API, кешування та інтернаціоналізація","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fsecurity-auth","01.csharp\u002F11.aspnet\u002F02.api\u002F12.security-auth",{"title":784,"path":785,"stem":786},"Процес проєктування API та документування","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fapi-design-process","01.csharp\u002F11.aspnet\u002F02.api\u002F13.api-design-process",{"title":788,"path":789,"stem":790},"OpenAPI: контракт, специфікація та документація API","\u002Fcsharp\u002Faspnet\u002Fapi\u002Fopenapi","01.csharp\u002F11.aspnet\u002F02.api\u002F14.openapi",{"title":792,"icon":793,"path":794,"stem":795,"children":796,"page":59},"Auth","i-lucide-shield-check","\u002Fcsharp\u002Faspnet\u002Fauth","01.csharp\u002F11.aspnet\u002F03.auth",[797,801,805,809,813,817,821,825,829,833,837,841,845,849,853,857,861,865],{"title":798,"path":799,"stem":800},"Основи аутентифікації та авторизації","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fauth-fundamentals","01.csharp\u002F11.aspnet\u002F03.auth\u002F01.auth-fundamentals",{"title":802,"path":803,"stem":804},"JWT-аутентифікація","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fjwt-authentication","01.csharp\u002F11.aspnet\u002F03.auth\u002F02.jwt-authentication",{"title":806,"path":807,"stem":808},"Авторизація: ролі, політики та resource-based доступ","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fauthorization-policies","01.csharp\u002F11.aspnet\u002F03.auth\u002F03.authorization-policies",{"title":810,"path":811,"stem":812},"Cookie-аутентифікація та ASP.NET Core Identity","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fcookie-auth-identity","01.csharp\u002F11.aspnet\u002F03.auth\u002F04.cookie-auth-identity",{"title":814,"path":815,"stem":816},"JWT + Refresh Tokens (HttpOnly Cookie)","\u002Fcsharp\u002Faspnet\u002Fauth\u002F04b.identity-auth-jwt","01.csharp\u002F11.aspnet\u002F03.auth\u002F04b.identity-auth-jwt",{"title":818,"path":819,"stem":820},"Identity: Підтвердження Email та Скидання Пароля","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-email-confirmation","01.csharp\u002F11.aspnet\u002F03.auth\u002F05.identity-email-confirmation",{"title":822,"path":823,"stem":824},"Identity: Двофакторна Аутентифікація (2FA)","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-two-factor","01.csharp\u002F11.aspnet\u002F03.auth\u002F06.identity-two-factor",{"title":826,"path":827,"stem":828},"Identity: Внутрішня Архітектура та Кастомізація","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fidentity-internals","01.csharp\u002F11.aspnet\u002F03.auth\u002F07.identity-internals",{"title":830,"path":831,"stem":832},"OAuth 2.0 та зовнішні провайдери","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foauth-external-providers","01.csharp\u002F11.aspnet\u002F03.auth\u002F08.oauth-external-providers",{"title":834,"path":835,"stem":836},"Безпека на практиці: CORS, HTTPS та захист від атак","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fsecurity-hardening","01.csharp\u002F11.aspnet\u002F03.auth\u002F09.security-hardening",{"title":838,"path":839,"stem":840},"Теорія OAuth 2.0: Поняття, Аналогії та Флоу","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foauth-theory","01.csharp\u002F11.aspnet\u002F03.auth\u002F10.oauth-theory",{"title":842,"path":843,"stem":844},"OIDC, OAuth 2.0 та Keycloak в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Foidc-keycloak","01.csharp\u002F11.aspnet\u002F03.auth\u002F10.oidc-keycloak",{"title":846,"path":847,"stem":848},"API Keys аутентифікація в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fapi-keys","01.csharp\u002F11.aspnet\u002F03.auth\u002F11.api-keys",{"title":850,"path":851,"stem":852},"Rate Limiting та Throttling в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frate-limiting","01.csharp\u002F11.aspnet\u002F03.auth\u002F12.rate-limiting",{"title":854,"path":855,"stem":856},"Refresh Token Rotation в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frefresh-token-rotation","01.csharp\u002F11.aspnet\u002F03.auth\u002F13.refresh-token-rotation",{"title":858,"path":859,"stem":860},"Certificate Authentication та mTLS в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fcertificate-auth","01.csharp\u002F11.aspnet\u002F03.auth\u002F14.certificate-auth",{"title":862,"path":863,"stem":864},"RBAC, ABAC та ReBAC в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Frbac-abac-rebac","01.csharp\u002F11.aspnet\u002F03.auth\u002F15.rbac-abac-rebac",{"title":866,"path":867,"stem":868},"Multi-tenancy та ізоляція даних в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fauth\u002Fmulti-tenancy","01.csharp\u002F11.aspnet\u002F03.auth\u002F16.multi-tenancy",{"title":870,"icon":871,"path":872,"stem":873,"children":874,"page":59},"Нотифікації","i-lucide-bell","\u002Fcsharp\u002Faspnet\u002Fnotifications","01.csharp\u002F11.aspnet\u002F04.notifications",[875,879,883,887,891,895,899,903,907,911,915,919,923],{"title":876,"path":877,"stem":878},"In-App нотифікації через базу даних","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fin-app-database-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F01.in-app-database-notifications",{"title":880,"path":881,"stem":882},"Polling: Регулярний запит оновлень","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fpolling","01.csharp\u002F11.aspnet\u002F04.notifications\u002F02.polling",{"title":884,"path":885,"stem":886},"Server-Sent Events: Однострімовий push від сервера","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fserver-sent-events","01.csharp\u002F11.aspnet\u002F04.notifications\u002F03.server-sent-events",{"title":888,"path":889,"stem":890},"WebSockets: Двостороннє з'єднання в реальному часі","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fwebsockets","01.csharp\u002F11.aspnet\u002F04.notifications\u002F04.websockets",{"title":892,"path":893,"stem":894},"SignalR: Абстракція над транспортами реального часу","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fsignalr","01.csharp\u002F11.aspnet\u002F04.notifications\u002F05.signalr",{"title":896,"path":897,"stem":898},"Background Services: Фонові задачі в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fbackground-services","01.csharp\u002F11.aspnet\u002F04.notifications\u002F06.background-services",{"title":900,"path":901,"stem":902},"Web Push нотифікації","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fweb-push","01.csharp\u002F11.aspnet\u002F04.notifications\u002F07.web-push",{"title":904,"path":905,"stem":906},"Email нотифікації","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Femail-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F08.email-notifications",{"title":908,"path":909,"stem":910},"Порівняння підходів: Як вибрати правильну технологію нотифікацій","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fchoosing-the-right-approach","01.csharp\u002F11.aspnet\u002F04.notifications\u002F09.choosing-the-right-approach",{"title":912,"path":913,"stem":914},"Hangfire: Надійне планування фонових задач","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire","01.csharp\u002F11.aspnet\u002F04.notifications\u002F10.hangfire",{"title":916,"path":917,"stem":918},"Практика: Конвертація зображень у WebP через Hangfire","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire-image-webp","01.csharp\u002F11.aspnet\u002F04.notifications\u002F11.hangfire-image-webp",{"title":920,"path":921,"stem":922},"Практика: Підготовка відео до HLS-стрімінгу через Hangfire","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Fhangfire-video-hls","01.csharp\u002F11.aspnet\u002F04.notifications\u002F12.hangfire-video-hls",{"title":924,"path":925,"stem":926},"Telegram-нотифікації: від одного повідомлення до масових розсилок і мульти-канального підходу","\u002Fcsharp\u002Faspnet\u002Fnotifications\u002Ftelegram-notifications","01.csharp\u002F11.aspnet\u002F04.notifications\u002F13.telegram-notifications",{"title":928,"icon":929,"path":930,"stem":931,"children":932,"page":59},"Інтернаціоналізація","i-lucide-languages","\u002Fcsharp\u002Faspnet\u002Fi18n","01.csharp\u002F11.aspnet\u002F05.i18n",[933,937],{"title":934,"path":935,"stem":936},"Інтернаціоналізація (i18n) у Minimal API: від A до Я","\u002Fcsharp\u002Faspnet\u002Fi18n\u002Finternationalization","01.csharp\u002F11.aspnet\u002F05.i18n\u002F01.internationalization",{"title":938,"path":939,"stem":940},"Humanizer: людиномовні рядки у .NET","\u002Fcsharp\u002Faspnet\u002Fi18n\u002Fhumanizer","01.csharp\u002F11.aspnet\u002F05.i18n\u002F02.humanizer",{"title":942,"icon":943,"path":944,"stem":945,"children":946,"page":59},"Кешування","i-lucide-layers","\u002Fcsharp\u002Faspnet\u002Fcaching","01.csharp\u002F11.aspnet\u002F06.caching",[947,951,955,959,963],{"title":948,"path":949,"stem":950},"Огляд кешування: чотири рівні і коли що обирати","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fcaching","01.csharp\u002F11.aspnet\u002F06.caching\u002F01.caching",{"title":952,"path":953,"stem":954},"IMemoryCache: кеш в оперативній пам'яті","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fmemory-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F02.memory-cache",{"title":956,"path":957,"stem":958},"IDistributedCache і Redis: розподілений кеш","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fdistributed-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F03.distributed-cache",{"title":960,"path":961,"stem":962},"Response Cache: HTTP-кешування через Cache-Control","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Fresponse-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F04.response-cache",{"title":964,"path":965,"stem":966},"Output Cache: серверний кеш HTTP-відповідей (.NET 7+)","\u002Fcsharp\u002Faspnet\u002Fcaching\u002Foutput-cache","01.csharp\u002F11.aspnet\u002F06.caching\u002F05.output-cache",{"title":968,"icon":969,"path":970,"stem":971,"children":972,"page":59},"Тестування","i-lucide-test-tube","\u002Fcsharp\u002Faspnet\u002Ftesting","01.csharp\u002F11.aspnet\u002F07.testing",[973,977,981,985,989,993,997,1001,1005,1009,1013,1017,1021,1025,1029,1033,1037,1041,1045,1049,1053],{"title":974,"path":975,"stem":976},"Що таке тестування? Від інтуїції до науки","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwhat-is-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F01.what-is-testing",{"title":978,"path":979,"stem":980},"Піраміда тестування — Стратегія, а не Догма","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-pyramid","01.csharp\u002F11.aspnet\u002F07.testing\u002F02.testing-pyramid",{"title":982,"path":983,"stem":984},"Дві Школи Тестування — Лондон проти Детройту","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-schools","01.csharp\u002F11.aspnet\u002F07.testing\u002F03.testing-schools",{"title":986,"path":987,"stem":988},"TDD та BDD — Тести як Дизайн-інструмент","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftdd-and-bdd","01.csharp\u002F11.aspnet\u002F07.testing\u002F04.tdd-and-bdd",{"title":990,"path":991,"stem":992},"Що саме тестувати — Техніки аналізу та Циклomatична складність","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwhat-to-test","01.csharp\u002F11.aspnet\u002F07.testing\u002F05.what-to-test",{"title":994,"path":995,"stem":996},"Тестові Фреймворки — Навіщо вони і що всередині","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftest-frameworks","01.csharp\u002F11.aspnet\u002F07.testing\u002F06.test-frameworks",{"title":998,"path":999,"stem":1000},"xUnit — Факти, Теорії та Lifecycle тестів","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fxunit-basics","01.csharp\u002F11.aspnet\u002F07.testing\u002F07.xunit-basics",{"title":1002,"path":1003,"stem":1004},"xUnit Advanced — Fixtures, Кастомізація та Розширення","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fxunit-advanced","01.csharp\u002F11.aspnet\u002F07.testing\u002F08.xunit-advanced",{"title":1006,"path":1007,"stem":1008},"Moq — Глибоке занурення в мокування","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fmocking-with-moq","01.csharp\u002F11.aspnet\u002F07.testing\u002F09.mocking-with-moq",{"title":1010,"path":1011,"stem":1012},"Тестування Баз Даних — EF Core, SQLite та Testcontainers","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fdatabase-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F10.database-testing",{"title":1014,"path":1015,"stem":1016},"Integration Testing — Частина 1 [Теорія та WebApplicationFactory]","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fintegration-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F11.integration-testing",{"title":1018,"path":1019,"stem":1020},"Інтеграційне тестування — Практика","\u002Fcsharp\u002Faspnet\u002Ftesting\u002F11a.integration-testing-practice","01.csharp\u002F11.aspnet\u002F07.testing\u002F11a.integration-testing-practice",{"title":1022,"path":1023,"stem":1024},"Integration Testing — Частина 2 [Просунуті Сценарії та Testcontainers]","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fintegration-testing-advanced","01.csharp\u002F11.aspnet\u002F07.testing\u002F12.integration-testing-advanced",{"title":1026,"path":1027,"stem":1028},"Професійний Postman: Колекції, Змінні та GitHub Інтеграція","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fpostman-professional","01.csharp\u002F11.aspnet\u002F07.testing\u002F13.postman-professional",{"title":1030,"path":1031,"stem":1032},"HttpClient у Тестах Частина 1: Архітектура та MockHttpMessageHandler","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fhttpclient-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F14.httpclient-testing",{"title":1034,"path":1035,"stem":1036},"HttpClient у Тестах Частина 2: WireMock.Net та Resilience","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fwiremock-net","01.csharp\u002F11.aspnet\u002F07.testing\u002F15.wiremock-net",{"title":1038,"path":1039,"stem":1040},"Патерни та Анти-патерни Тестування: Test Smells","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Ftesting-patterns","01.csharp\u002F11.aspnet\u002F07.testing\u002F16.testing-patterns",{"title":1042,"path":1043,"stem":1044},"Просунуті інструменти: Time, Snapshots та Властивості","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fadvanced-testing-tools","01.csharp\u002F11.aspnet\u002F07.testing\u002F17.advanced-testing-tools",{"title":1046,"path":1047,"stem":1048},"Тестування Архітектури з NetArchTest","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Farchitecture-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F18.architecture-testing",{"title":1050,"path":1051,"stem":1052},"Тестування Продуктивності: BenchmarkDotNet, NBomber та k6","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fperformance-testing","01.csharp\u002F11.aspnet\u002F07.testing\u002F19.performance-testing",{"title":1054,"path":1055,"stem":1056},"Залишок плану для курсу \"Тестування ASP.NET Minimal API\"","\u002Fcsharp\u002Faspnet\u002Ftesting\u002Fremaining_plan","01.csharp\u002F11.aspnet\u002F07.testing\u002Fremaining_plan",{"title":1058,"icon":1059,"path":1060,"stem":1061,"children":1062,"page":59},"Платежі","i-lucide-credit-card","\u002Fcsharp\u002Faspnet\u002Fpayments","01.csharp\u002F11.aspnet\u002F08.payments",[1063,1067,1071,1075,1079,1083,1087,1091,1095,1099,1103,1107],{"title":1064,"path":1065,"stem":1066},"Основи платіжної інфраструктури","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-fundamentals","01.csharp\u002F11.aspnet\u002F08.payments\u002F01.payment-fundamentals",{"title":1068,"path":1069,"stem":1070},"Методи оплати в Україні","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-methods-ukraine","01.csharp\u002F11.aspnet\u002F08.payments\u002F02.payment-methods-ukraine",{"title":1072,"path":1073,"stem":1074},"PCI DSS та безпека платежів","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpci-dss-security","01.csharp\u002F11.aspnet\u002F08.payments\u002F03.pci-dss-security",{"title":1076,"path":1077,"stem":1078},"Архітектура платіжної підсистеми","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fpayment-architecture","01.csharp\u002F11.aspnet\u002F08.payments\u002F04.payment-architecture",{"title":1080,"path":1081,"stem":1082},"Інтеграція LiqPay (ПриватБанк)","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fliqpay-integration","01.csharp\u002F11.aspnet\u002F08.payments\u002F05.liqpay-integration",{"title":1084,"path":1085,"stem":1086},"Інтеграція Monobank Acquiring API","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fmonobank-acquiring","01.csharp\u002F11.aspnet\u002F08.payments\u002F06.monobank-acquiring",{"title":1088,"path":1089,"stem":1090},"Інтеграція Stripe","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fstripe-integration","01.csharp\u002F11.aspnet\u002F08.payments\u002F07.stripe-integration",{"title":1092,"path":1093,"stem":1094},"Webhooks — глибоке занурення","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fwebhooks-deep-dive","01.csharp\u002F11.aspnet\u002F08.payments\u002F08.webhooks-deep-dive",{"title":1096,"path":1097,"stem":1098},"Підписки та рекурентні платежі","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fsubscriptions-recurring","01.csharp\u002F11.aspnet\u002F08.payments\u002F09.subscriptions-recurring",{"title":1100,"path":1101,"stem":1102},"Повернення коштів та диспути","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Frefunds-disputes","01.csharp\u002F11.aspnet\u002F08.payments\u002F10.refunds-disputes",{"title":1104,"path":1105,"stem":1106},"Тестування платіжних інтеграцій","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Ftesting-payments","01.csharp\u002F11.aspnet\u002F08.payments\u002F11.testing-payments",{"title":1108,"path":1109,"stem":1110},"Чекліст виходу в Production","\u002Fcsharp\u002Faspnet\u002Fpayments\u002Fproduction-checklist","01.csharp\u002F11.aspnet\u002F08.payments\u002F12.production-checklist",{"title":1112,"icon":1113,"items":1114,"path":1127,"stem":1128,"children":1129,"page":59},"Популярні бібліотеки","lucide:box",[1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126],"01.fluent-validation","02.mapster","03.erroror-result-pattern","04.serilog","05.mediatr","06.polly","07.health-checks","08.feature-management","09.fluent-email","10.quest-pdf","11.bogus","12.humanizer-guard","\u002Fcsharp\u002Faspnet\u002Flibraries","01.csharp\u002F11.aspnet\u002F09.libraries",[1130,1134,1138,1142,1146,1150,1154,1158,1162,1166,1170,1174,1178],{"title":1131,"path":1132,"stem":1133},"Валідація з FluentValidation в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffluent-validation","01.csharp\u002F11.aspnet\u002F09.libraries\u002F01.fluent-validation",{"title":1135,"path":1136,"stem":1137},"Маппінг об","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fmapster","01.csharp\u002F11.aspnet\u002F09.libraries\u002F02.mapster",{"title":1139,"path":1140,"stem":1141},"Обробка помилок з ErrorOr та Result Pattern в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ferroror-result-pattern","01.csharp\u002F11.aspnet\u002F09.libraries\u002F03.erroror-result-pattern",{"title":1143,"path":1144,"stem":1145},"Структуроване логування з Serilog в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fserilog","01.csharp\u002F11.aspnet\u002F09.libraries\u002F04.serilog",{"title":1147,"path":1148,"stem":1149},"CQRS та Mediator з MediatR в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fmediatr","01.csharp\u002F11.aspnet\u002F09.libraries\u002F05.mediatr",{"title":1151,"path":1152,"stem":1153},"Відмовостійкість з Polly в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fpolly","01.csharp\u002F11.aspnet\u002F09.libraries\u002F06.polly",{"title":1155,"path":1156,"stem":1157},"Health Checks в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fhealth-checks","01.csharp\u002F11.aspnet\u002F09.libraries\u002F07.health-checks",{"title":1159,"path":1160,"stem":1161},"Feature Management та Feature Flags в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffeature-management","01.csharp\u002F11.aspnet\u002F09.libraries\u002F08.feature-management",{"title":1163,"path":1164,"stem":1165},"Відправка Email з FluentEmail в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Ffluent-email","01.csharp\u002F11.aspnet\u002F09.libraries\u002F09.fluent-email",{"title":1167,"path":1168,"stem":1169},"Генерація PDF з QuestPDF в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fquest-pdf","01.csharp\u002F11.aspnet\u002F09.libraries\u002F10.quest-pdf",{"title":1171,"path":1172,"stem":1173},"Генерація тестових даних з Bogus в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fbogus","01.csharp\u002F11.aspnet\u002F09.libraries\u002F11.bogus",{"title":1175,"path":1176,"stem":1177},"Humanizer та Guard Clauses в ASP.NET Core","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fhumanizer-guard","01.csharp\u002F11.aspnet\u002F09.libraries\u002F12.humanizer-guard",{"title":1179,"path":1180,"stem":1181},"План модуля 10.libraries — Популярні бібліотеки ASP.NET","\u002Fcsharp\u002Faspnet\u002Flibraries\u002Fplan","01.csharp\u002F11.aspnet\u002F09.libraries\u002Fplan",{"title":1183,"icon":1184,"path":1185,"stem":1186,"children":1187,"page":59},"Razor Pages","i-lucide-layout-template","\u002Fcsharp\u002Faspnet\u002Frazor-pages","01.csharp\u002F11.aspnet\u002F10.razor-pages",[1188,1192,1196,1200,1204,1208],{"title":1189,"path":1190,"stem":1191},"Від Minimal API до Razor Pages: концептуальний перехід","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Ffrom-minimal-api","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F01.from-minimal-api",{"title":1193,"path":1194,"stem":1195},"PageModel: логіка сторінки Razor Pages","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fpage-model","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F02.page-model",{"title":1197,"path":1198,"stem":1199},"Razor синтаксис: шаблонізатор у .cshtml","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Frazor-syntax","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F03.razor-syntax",{"title":1201,"path":1202,"stem":1203},"Tag Helpers: типізований HTML","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Ftag-helpers","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F04.tag-helpers",{"title":1205,"path":1206,"stem":1207},"Форми і валідація: повний цикл обробки даних","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fforms-validation","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F05.forms-validation",{"title":1209,"path":1210,"stem":1211},"Практичний проєкт: TaskManager на Razor Pages","\u002Fcsharp\u002Faspnet\u002Frazor-pages\u002Fproject-task-manager","01.csharp\u002F11.aspnet\u002F10.razor-pages\u002F06.project-task-manager",{"title":1213,"path":1214,"stem":1215,"children":1216,"page":59},"ASP.NET Core MVC","\u002Fcsharp\u002Faspnet\u002Fmvc","01.csharp\u002F11.aspnet\u002F11.mvc",[1217,1221,1225,1229,1233,1237,1241,1245,1249,1253,1257,1261,1265,1269,1273,1277,1281,1285],{"title":1218,"path":1219,"stem":1220},"Патерн MVC: архітектура, що змінила веб","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmvc-pattern","01.csharp\u002F11.aspnet\u002F11.mvc\u002F01.mvc-pattern",{"title":1222,"path":1223,"stem":1224},"Від Razor Pages до MVC: концептуальний перехід","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffrom-razor-pages","01.csharp\u002F11.aspnet\u002F11.mvc\u002F02.from-razor-pages",{"title":1226,"path":1227,"stem":1228},"Controllers та Actions: серце MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fcontrollers-actions","01.csharp\u002F11.aspnet\u002F11.mvc\u002F03.controllers-actions",{"title":1230,"path":1231,"stem":1232},"Маршрутизація в MVC: Convention vs Attribute Routing","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Frouting-mvc","01.csharp\u002F11.aspnet\u002F11.mvc\u002F04.routing-mvc",{"title":1234,"path":1235,"stem":1236},"Model Binding: від HTTP до C#","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmodel-binding","01.csharp\u002F11.aspnet\u002F11.mvc\u002F05.model-binding",{"title":1238,"path":1239,"stem":1240},"Views, ViewData, ViewBag, TempData і ViewModel","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fviews-viewdata-tempdata","01.csharp\u002F11.aspnet\u002F11.mvc\u002F06.views-viewdata-tempdata",{"title":1242,"path":1243,"stem":1244},"Filters: аспектно-орієнтоване програмування в MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffilters","01.csharp\u002F11.aspnet\u002F11.mvc\u002F07.filters",{"title":1246,"path":1247,"stem":1248},"Areas: структурування великих застосунків","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fareas","01.csharp\u002F11.aspnet\u002F11.mvc\u002F08.areas",{"title":1250,"path":1251,"stem":1252},"View Components: повторювані незалежні блоки UI","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fview-components","01.csharp\u002F11.aspnet\u002F11.mvc\u002F09.view-components",{"title":1254,"path":1255,"stem":1256},"Display та Editor Templates","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fdisplay-editor-templates","01.csharp\u002F11.aspnet\u002F11.mvc\u002F10.display-editor-templates",{"title":1258,"path":1259,"stem":1260},"Валідація: IValidatableObject та FluentValidation","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fvalidation-advanced","01.csharp\u002F11.aspnet\u002F11.mvc\u002F11.validation-advanced",{"title":1262,"path":1263,"stem":1264},"HTMX: інтерактивність через HTML-атрибути","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fhtmx","01.csharp\u002F11.aspnet\u002F11.mvc\u002F12.htmx",{"title":1266,"path":1267,"stem":1268},"HTMX у ASP.NET Core MVC: серверна інтеграція","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fajax-htmx-mvc","01.csharp\u002F11.aspnet\u002F11.mvc\u002F13.ajax-htmx-mvc",{"title":1270,"path":1271,"stem":1272},"Практичний проєкт: Каталог товарів з HTMX","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fhtmx-project","01.csharp\u002F11.aspnet\u002F11.mvc\u002F14.htmx-project",{"title":1274,"path":1275,"stem":1276},"Завантаження та обробка файлів","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Ffile-upload","01.csharp\u002F11.aspnet\u002F11.mvc\u002F15.file-upload",{"title":1278,"path":1279,"stem":1280},"Глобалізація та Локалізація MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fglobalization-localization","01.csharp\u002F11.aspnet\u002F11.mvc\u002F16.globalization-localization",{"title":1282,"path":1283,"stem":1284},"Підсумковий проєкт: Блог-платформа","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fmvc-project","01.csharp\u002F11.aspnet\u002F11.mvc\u002F17.mvc-project",{"title":1286,"path":1287,"stem":1288},"План курсу: ASP.NET Core MVC","\u002Fcsharp\u002Faspnet\u002Fmvc\u002Fplan","01.csharp\u002F11.aspnet\u002F11.mvc\u002Fplan",{"title":1290,"path":1291,"stem":1292,"children":1293,"page":59},"Web Api","\u002Fcsharp\u002Faspnet\u002Fweb-api","01.csharp\u002F11.aspnet\u002F12.web-api",[1294,1298,1302,1306,1310,1314,1318,1322,1326,1330,1334,1338,1342],{"title":1295,"path":1296,"stem":1297},"Від Minimal API до Controller-based API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Ffrom-minimal-api-to-controllers","01.csharp\u002F11.aspnet\u002F12.web-api\u002F01.from-minimal-api-to-controllers",{"title":1299,"path":1300,"stem":1301},"ControllerBase, ActionResult\u003CT> та Response Types","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fcontroller-base-actionresult","01.csharp\u002F11.aspnet\u002F12.web-api\u002F02.controller-base-actionresult",{"title":1303,"path":1304,"stem":1305},"Content Negotiation - JSON, XML та власні форматери","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fcontent-negotiation","01.csharp\u002F11.aspnet\u002F12.web-api\u002F03.content-negotiation",{"title":1307,"path":1308,"stem":1309},"Версіонування API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fapi-versioning","01.csharp\u002F11.aspnet\u002F12.web-api\u002F04.api-versioning",{"title":1311,"path":1312,"stem":1313},"ProblemDetails та структурована обробка помилок","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fproblemdetails-error-handling","01.csharp\u002F11.aspnet\u002F12.web-api\u002F05.problemdetails-error-handling",{"title":1315,"path":1316,"stem":1317},"Фільтри у Web API контексті","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Ffilters-for-api","01.csharp\u002F11.aspnet\u002F12.web-api\u002F06.filters-for-api",{"title":1319,"path":1320,"stem":1321},"Пагінація, фільтрація та сортування","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fpagination-filtering-sorting","01.csharp\u002F11.aspnet\u002F12.web-api\u002F07.pagination-filtering-sorting",{"title":1323,"path":1324,"stem":1325},"HATEOAS та Resource Expansion","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fhateoas-resource-expansion","01.csharp\u002F11.aspnet\u002F12.web-api\u002F08.hateoas-resource-expansion",{"title":1327,"path":1328,"stem":1329},"Гібридна архітектура - Minimal API + Controllers","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fminimal-api-vs-controllers-hybrid","01.csharp\u002F11.aspnet\u002F12.web-api\u002F09.minimal-api-vs-controllers-hybrid",{"title":1331,"path":1332,"stem":1333},"Документація API - Swashbuckle, NSwag та генерація клієнтів","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fapi-documentation-generation","01.csharp\u002F11.aspnet\u002F12.web-api\u002F10.api-documentation-generation",{"title":1335,"path":1336,"stem":1337},"Health Checks та моніторинг API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fhealth-checks-monitoring","01.csharp\u002F11.aspnet\u002F12.web-api\u002F11.health-checks-monitoring",{"title":1339,"path":1340,"stem":1341},"Підсумковий проєкт - Production-Ready REST API","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fweb-api-project","01.csharp\u002F11.aspnet\u002F12.web-api\u002F12.web-api-project",{"title":1343,"path":1344,"stem":1345},"План курсу: ASP.NET Core Web API (Controllers)","\u002Fcsharp\u002Faspnet\u002Fweb-api\u002Fplan","01.csharp\u002F11.aspnet\u002F12.web-api\u002Fplan",{"title":1347,"icon":1348,"path":1349,"stem":1350,"children":1351,"page":59},"Моніторинг","i-lucide-activity","\u002Fcsharp\u002Faspnet\u002Fmonitoring","01.csharp\u002F11.aspnet\u002F13.monitoring",[1352,1356,1360],{"title":1353,"path":1354,"stem":1355},"Спостережуваність: від console.log до production-систем","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fobservability-intro","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F01.observability-intro",{"title":1357,"path":1358,"stem":1359},"Health Checks: перший рівень observability","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fhealth-checks","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F02.health-checks",{"title":1361,"path":1362,"stem":1363},"Вбудовані метрики .NET 10 та System.Diagnostics.Metrics","\u002Fcsharp\u002Faspnet\u002Fmonitoring\u002Fdotnet-metrics","01.csharp\u002F11.aspnet\u002F13.monitoring\u002F03.dotnet-metrics",{"title":1365,"icon":1366,"path":1367,"stem":1368,"children":1369,"page":59},"Desktop UI","i-lucide-app-window","\u002Fcsharp\u002Fdesktop-ui","01.csharp\u002F12.desktop-ui",[1370,1374,1378,1382,1386,1390,1394,1398,1402,1406,1410,1414,1418,1422,1426,1430,1434,1438,1442,1446,1450,1454,1458,1462,1466,1470,1474,1478,1482,1486,1490,1494,1498,1502,1506,1510,1514,1518,1522,1526,1530,1534,1538,1542,1546,1550,1554,1558,1562,1566,1570,1574,1578,1582,1586,1590,1594,1598,1602,1606,1610,1614,1618,1622,1626,1630,1634,1638,1642,1646,1650],{"title":1371,"path":1372,"stem":1373},"Що таке десктопна розробка?","\u002Fcsharp\u002Fdesktop-ui\u002Fwhat-is-desktop-dev","01.csharp\u002F12.desktop-ui\u002F01.what-is-desktop-dev",{"title":1375,"path":1376,"stem":1377},"Архітектура WPF — як влаштований графічний інтерфейс","\u002Fcsharp\u002Fdesktop-ui\u002Fwpf-architecture","01.csharp\u002F12.desktop-ui\u002F02.wpf-architecture",{"title":1379,"path":1380,"stem":1381},"Перший WPF-проєкт — від нуля до вікна","\u002Fcsharp\u002Fdesktop-ui\u002Ffirst-wpf-app","01.csharp\u002F12.desktop-ui\u002F03.first-wpf-app",{"title":1383,"path":1384,"stem":1385},"Перший Avalonia-проєкт: WPF для всіх платформ","\u002Fcsharp\u002Fdesktop-ui\u002F03a.first-avalonia-app","01.csharp\u002F12.desktop-ui\u002F03a.first-avalonia-app",{"title":1387,"path":1388,"stem":1389},"XAML: декларативний інтерфейс","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-basics","01.csharp\u002F12.desktop-ui\u002F04.xaml-basics",{"title":1391,"path":1392,"stem":1393},"Fluent UI у WPF — сучасний дизайн Windows 11","\u002Fcsharp\u002Fdesktop-ui\u002F04a.wpf-fluent-ui","01.csharp\u002F12.desktop-ui\u002F04a.wpf-fluent-ui",{"title":1395,"path":1396,"stem":1397},"WPF UI — сучасна бібліотека Fluent контролів","\u002Fcsharp\u002Fdesktop-ui\u002F04b.wpf-ui-library","01.csharp\u002F12.desktop-ui\u002F04b.wpf-ui-library",{"title":1399,"path":1400,"stem":1401},"HandyControl — велика бібліотека UI контролів для WPF","\u002Fcsharp\u002Fdesktop-ui\u002F04c.handycontrol-library","01.csharp\u002F12.desktop-ui\u002F04c.handycontrol-library",{"title":1403,"path":1404,"stem":1405},"Простори імен та ресурси XAML","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-namespaces-resources","01.csharp\u002F12.desktop-ui\u002F05.xaml-namespaces-resources",{"title":1407,"path":1408,"stem":1409},"XAML в Avalonia: ключові відмінності від WPF","\u002Fcsharp\u002Fdesktop-ui\u002F05a.avalonia-xaml-differences","01.csharp\u002F12.desktop-ui\u002F05a.avalonia-xaml-differences",{"title":1411,"path":1412,"stem":1413},"Розширення розмітки XAML (Markup Extensions)","\u002Fcsharp\u002Fdesktop-ui\u002Fxaml-markup-extensions","01.csharp\u002F12.desktop-ui\u002F06.xaml-markup-extensions",{"title":1415,"path":1416,"stem":1417},"Панелі Layout: StackPanel, WrapPanel, DockPanel","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-panels-part1","01.csharp\u002F12.desktop-ui\u002F07.layout-panels-part1",{"title":1419,"path":1420,"stem":1421},"Grid, Canvas, UniformGrid","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-panels-part2","01.csharp\u002F12.desktop-ui\u002F07.layout-panels-part2",{"title":1423,"path":1424,"stem":1425},"Просунуті техніки Layout","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-advanced","01.csharp\u002F12.desktop-ui\u002F08.layout-advanced",{"title":1427,"path":1428,"stem":1429},"Адаптивний Layout та найкращі практики","\u002Fcsharp\u002Fdesktop-ui\u002Flayout-responsive","01.csharp\u002F12.desktop-ui\u002F09.layout-responsive",{"title":1431,"path":1432,"stem":1433},"Layout в Avalonia: відмінності та нові можливості","\u002Fcsharp\u002Fdesktop-ui\u002F09a.layout-avalonia","01.csharp\u002F12.desktop-ui\u002F09a.layout-avalonia",{"title":1435,"path":1436,"stem":1437},"Button, Image, ProgressBar та інші базові контроли","\u002Fcsharp\u002Fdesktop-ui\u002Fbasic-controls","01.csharp\u002F12.desktop-ui\u002F10.basic-controls",{"title":1439,"path":1440,"stem":1441},"Контроли в Avalonia: відмінності від WPF","\u002Fcsharp\u002Fdesktop-ui\u002F10a.controls-avalonia","01.csharp\u002F12.desktop-ui\u002F10a.controls-avalonia",{"title":1443,"path":1444,"stem":1445},"Текстові контроли — TextBlock, TextBox, RichTextBox","\u002Fcsharp\u002Fdesktop-ui\u002Ftext-controls","01.csharp\u002F12.desktop-ui\u002F11.text-controls",{"title":1447,"path":1448,"stem":1449},"Контроли вибору — CheckBox, RadioButton, ComboBox, ListBox, DatePicker","\u002Fcsharp\u002Fdesktop-ui\u002Fselection-controls","01.csharp\u002F12.desktop-ui\u002F12.selection-controls",{"title":1451,"path":1452,"stem":1453},"Content Model — GroupBox, Expander, TabControl, StatusBar","\u002Fcsharp\u002Fdesktop-ui\u002Fcontent-controls","01.csharp\u002F12.desktop-ui\u002F13.content-controls",{"title":1455,"path":1456,"stem":1457},"UI\u002FUX принципи десктопних застосунків","\u002Fcsharp\u002Fdesktop-ui\u002F13a.ui-ux-principles","01.csharp\u002F12.desktop-ui\u002F13a.ui-ux-principles",{"title":1459,"path":1460,"stem":1461},"Dependency Properties — Концепція та Value Resolution","\u002Fcsharp\u002Fdesktop-ui\u002Fdependency-properties-part1","01.csharp\u002F12.desktop-ui\u002F14.dependency-properties-part1",{"title":1463,"path":1464,"stem":1465},"Avalonia Property System — StyledProperty та DirectProperty","\u002Fcsharp\u002Fdesktop-ui\u002F14a.avalonia-property-system","01.csharp\u002F12.desktop-ui\u002F14a.avalonia-property-system",{"title":1467,"path":1468,"stem":1469},"Attached Properties — Властивості без меж","\u002Fcsharp\u002Fdesktop-ui\u002Fattached-properties","01.csharp\u002F12.desktop-ui\u002F15.attached-properties",{"title":1471,"path":1472,"stem":1473},"Routed Events — Маршрутизація подій у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Frouted-events","01.csharp\u002F12.desktop-ui\u002F16.routed-events",{"title":1475,"path":1476,"stem":1477},"Data Binding — Від Code-Behind до Декларативності","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-basics-part1","01.csharp\u002F12.desktop-ui\u002F17.data-binding-basics-part1",{"title":1479,"path":1480,"stem":1481},"INotifyPropertyChanged — Живе оновлення UI","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-basics-part2","01.csharp\u002F12.desktop-ui\u002F17.data-binding-basics-part2",{"title":1483,"path":1484,"stem":1485},"Compiled Bindings в Avalonia — Безпека на етапі компіляції","\u002Fcsharp\u002Fdesktop-ui\u002F17a.avalonia-compiled-bindings","01.csharp\u002F12.desktop-ui\u002F17a.avalonia-compiled-bindings",{"title":1487,"path":1488,"stem":1489},"Просунутий Data Binding — ElementName, RelativeSource, MultiBinding","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-binding-advanced","01.csharp\u002F12.desktop-ui\u002F18.data-binding-advanced",{"title":1491,"path":1492,"stem":1493},"Value Converters — Перетворення типів даних у Data Binding","\u002Fcsharp\u002Fdesktop-ui\u002Fvalue-converters","01.csharp\u002F12.desktop-ui\u002F19.value-converters",{"title":1495,"path":1496,"stem":1497},"Data Templates — Візуалізація об'єктів у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-templates","01.csharp\u002F12.desktop-ui\u002F20.data-templates",{"title":1499,"path":1500,"stem":1501},"Collections Binding Part 1 — ObservableCollection та ItemsControl","\u002Fcsharp\u002Fdesktop-ui\u002Fcollections-binding-part1","01.csharp\u002F12.desktop-ui\u002F21.collections-binding-part1",{"title":1503,"path":1504,"stem":1505},"Collections Binding Part 2 — ICollectionView, Filtering, Sorting та Virtualization","\u002Fcsharp\u002Fdesktop-ui\u002Fcollections-binding-part2","01.csharp\u002F12.desktop-ui\u002F21.collections-binding-part2",{"title":1507,"path":1508,"stem":1509},"MVVM Pattern — Від Spaghetti Code до архітектури","\u002Fcsharp\u002Fdesktop-ui\u002Fmvvm-pattern","01.csharp\u002F12.desktop-ui\u002F22.mvvm-pattern",{"title":1511,"path":1512,"stem":1513},"ViewModel Implementation — Від BaseViewModel до валідації","\u002Fcsharp\u002Fdesktop-ui\u002Fviewmodel-implementation","01.csharp\u002F12.desktop-ui\u002F23.viewmodel-implementation",{"title":1515,"path":1516,"stem":1517},"Commands — Від event handlers до декларативних команд","\u002Fcsharp\u002Fdesktop-ui\u002Fcommands","01.csharp\u002F12.desktop-ui\u002F24.commands",{"title":1519,"path":1520,"stem":1521},"MVVM Toolkit — MVVM без boilerplate через Source Generators","\u002Fcsharp\u002Fdesktop-ui\u002Fmvvm-toolkit","01.csharp\u002F12.desktop-ui\u002F25.mvvm-toolkit",{"title":1523,"path":1524,"stem":1525},"Messenger Pattern — Комунікація між ViewModel без прямих посилань","\u002Fcsharp\u002Fdesktop-ui\u002Fmessenger-pattern","01.csharp\u002F12.desktop-ui\u002F26.messenger-pattern",{"title":1527,"path":1528,"stem":1529},"Стилі WPF — CSS для десктопу","\u002Fcsharp\u002Fdesktop-ui\u002Fstyles-basics","01.csharp\u002F12.desktop-ui\u002F27.styles-basics",{"title":1531,"path":1532,"stem":1533},"CSS-like стилі Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002F27a.avalonia-css-styling","01.csharp\u002F12.desktop-ui\u002F27a.avalonia-css-styling",{"title":1535,"path":1536,"stem":1537},"Control Templates — Частина 1. Концепція та TemplateBinding","\u002Fcsharp\u002Fdesktop-ui\u002Fcontrol-templates-part1","01.csharp\u002F12.desktop-ui\u002F28.control-templates-part1",{"title":1539,"path":1540,"stem":1541},"Control Templates — Частина 2. Named Parts та ContentPresenter","\u002Fcsharp\u002Fdesktop-ui\u002Fcontrol-templates-part2","01.csharp\u002F12.desktop-ui\u002F28.control-templates-part2",{"title":1543,"path":1544,"stem":1545},"Control Themes в Avalonia — нова ера стилізації","\u002Fcsharp\u002Fdesktop-ui\u002F28a.avalonia-control-themes","01.csharp\u002F12.desktop-ui\u002F28a.avalonia-control-themes",{"title":1547,"path":1548,"stem":1549},"Triggers та Visual State Manager у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Ftriggers-visual-states","01.csharp\u002F12.desktop-ui\u002F29.triggers-visual-states",{"title":1551,"path":1552,"stem":1553},"Pseudo-classes в Avalonia — замість WPF Triggers","\u002Fcsharp\u002Fdesktop-ui\u002F29a.avalonia-pseudo-classes","01.csharp\u002F12.desktop-ui\u002F29a.avalonia-pseudo-classes",{"title":1555,"path":1556,"stem":1557},"Теми та ресурсні словники у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fresources-themes","01.csharp\u002F12.desktop-ui\u002F30.resources-themes",{"title":1559,"path":1560,"stem":1561},"Avalonia Themes — Fluent Design та система тематизації","\u002Fcsharp\u002Fdesktop-ui\u002F30a.avalonia-themes-fluent","01.csharp\u002F12.desktop-ui\u002F30a.avalonia-themes-fluent",{"title":1563,"path":1564,"stem":1565},"Контроли колекцій — глибоке занурення","\u002Fcsharp\u002Fdesktop-ui\u002Fcollection-controls","01.csharp\u002F12.desktop-ui\u002F31.collection-controls",{"title":1567,"path":1568,"stem":1569},"DataGrid — колонки та базове відображення","\u002Fcsharp\u002Fdesktop-ui\u002Fdatagrid-part1","01.csharp\u002F12.desktop-ui\u002F32.datagrid-part1",{"title":1571,"path":1572,"stem":1573},"DataGrid — сортування, фільтрація, редагування","\u002Fcsharp\u002Fdesktop-ui\u002Fdatagrid-part2","01.csharp\u002F12.desktop-ui\u002F32.datagrid-part2",{"title":1575,"path":1576,"stem":1577},"TreeView та GridView","\u002Fcsharp\u002Fdesktop-ui\u002Ftreeview-listview","01.csharp\u002F12.desktop-ui\u002F33.treeview-listview",{"title":1579,"path":1580,"stem":1581},"Меню, Toolbar, ContextMenu, StatusBar","\u002Fcsharp\u002Fdesktop-ui\u002Fmenus-toolbars","01.csharp\u002F12.desktop-ui\u002F34.menus-toolbars",{"title":1583,"path":1584,"stem":1585},"Навігація та керування вікнами. Частина 1: вікна та сторінки","\u002Fcsharp\u002Fdesktop-ui\u002Fnavigation-windows-part1","01.csharp\u002F12.desktop-ui\u002F35.navigation-windows-part1",{"title":1587,"path":1588,"stem":1589},"Навігація та керування вікнами. Частина 2: MVVM-навігація","\u002Fcsharp\u002Fdesktop-ui\u002Fnavigation-windows-part2","01.csharp\u002F12.desktop-ui\u002F35.navigation-windows-part2",{"title":1591,"path":1592,"stem":1593},"Avalonia — Навігація та діалоги","\u002Fcsharp\u002Fdesktop-ui\u002F35a.avalonia-navigation-dialogs","01.csharp\u002F12.desktop-ui\u002F35a.avalonia-navigation-dialogs",{"title":1595,"path":1596,"stem":1597},"Діалоги та File Pickers у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fdialogs-file-pickers","01.csharp\u002F12.desktop-ui\u002F36.dialogs-file-pickers",{"title":1599,"path":1600,"stem":1601},"UserControl: компонентний підхід у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fuser-controls","01.csharp\u002F12.desktop-ui\u002F37.user-controls",{"title":1603,"path":1604,"stem":1605},"Custom Controls: Lookless Controls у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fcustom-controls","01.csharp\u002F12.desktop-ui\u002F38.custom-controls",{"title":1607,"path":1608,"stem":1609},"Avalonia TemplatedControl — Lookless Controls","\u002Fcsharp\u002Fdesktop-ui\u002F38a.avalonia-templated-controls","01.csharp\u002F12.desktop-ui\u002F38a.avalonia-templated-controls",{"title":1611,"path":1612,"stem":1613},"Анімації у WPF: Storyboard та Easing Functions","\u002Fcsharp\u002Fdesktop-ui\u002Fanimations-transitions","01.csharp\u002F12.desktop-ui\u002F39.animations-transitions",{"title":1615,"path":1616,"stem":1617},"Анімації в Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002F39a.avalonia-animations","01.csharp\u002F12.desktop-ui\u002F39a.avalonia-animations",{"title":1619,"path":1620,"stem":1621},"2D Графіка та Мультимедіа у WPF","\u002Fcsharp\u002Fdesktop-ui\u002Fmedia-graphics","01.csharp\u002F12.desktop-ui\u002F40.media-graphics",{"title":1623,"path":1624,"stem":1625},"Dependency Injection у WPF та Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002Fdi-integration","01.csharp\u002F12.desktop-ui\u002F41.di-integration",{"title":1627,"path":1628,"stem":1629},"SQLite та EF Core у десктопних додатках","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-persistence-part1","01.csharp\u002F12.desktop-ui\u002F42.data-persistence-part1",{"title":1631,"path":1632,"stem":1633},"Repository Pattern та Unit of Work","\u002Fcsharp\u002Fdesktop-ui\u002Fdata-persistence-part2","01.csharp\u002F12.desktop-ui\u002F43.data-persistence-part2",{"title":1635,"path":1636,"stem":1637},"Тестування ViewModels","\u002Fcsharp\u002Fdesktop-ui\u002Fviewmodel-testing","01.csharp\u002F12.desktop-ui\u002F44.viewmodel-testing",{"title":1639,"path":1640,"stem":1641},"Avalonia Headless Testing — тестування UI без вікон","\u002Fcsharp\u002Fdesktop-ui\u002F44a.avalonia-headless-testing","01.csharp\u002F12.desktop-ui\u002F44a.avalonia-headless-testing",{"title":1643,"path":1644,"stem":1645},"Кросплатформна розробка з Avalonia","\u002Fcsharp\u002Fdesktop-ui\u002Favalonia-cross-platform","01.csharp\u002F12.desktop-ui\u002F45.avalonia-cross-platform",{"title":1647,"path":1648,"stem":1649},"Пакування та розгортання Avalonia додатків","\u002Fcsharp\u002Fdesktop-ui\u002Favalonia-packaging-deployment","01.csharp\u002F12.desktop-ui\u002F46.avalonia-packaging-deployment",{"title":1651,"path":1652,"stem":1653},"Розгортання WPF застосунків","\u002Fcsharp\u002Fdesktop-ui\u002Fwpf-packaging-deployment","01.csharp\u002F12.desktop-ui\u002F47.wpf-packaging-deployment",{"title":1655,"icon":658,"path":1656,"stem":1657,"children":1658,"page":59},"Network Programming","\u002Fcsharp\u002Fnetwork-programming","01.csharp\u002F13.network-programming",[1659,1663,1667,1671,1675,1679,1683,1687,1691,1695,1699],{"title":1660,"path":1661,"stem":1662},"Основи комп'ютерних мереж","\u002Fcsharp\u002Fnetwork-programming\u002Ffoundations","01.csharp\u002F13.network-programming\u002F01.foundations",{"title":1664,"path":1665,"stem":1666},"Модель OSI та стек TCP\u002FIP","\u002Fcsharp\u002Fnetwork-programming\u002Fosi-model","01.csharp\u002F13.network-programming\u002F02.osi-model",{"title":1668,"path":1669,"stem":1670},"IP-протокол та адресація","\u002Fcsharp\u002Fnetwork-programming\u002Fip-addressing","01.csharp\u002F13.network-programming\u002F03.ip-addressing",{"title":1672,"path":1673,"stem":1674},"UDP — протокол без з'єднання","\u002Fcsharp\u002Fnetwork-programming\u002Fudp","01.csharp\u002F13.network-programming\u002F05.udp",{"title":1676,"path":1677,"stem":1678},"UDP Broadcast та Multicast","\u002Fcsharp\u002Fnetwork-programming\u002Fudp-broadcast-multicast","01.csharp\u002F13.network-programming\u002F06.udp-broadcast-multicast",{"title":1680,"path":1681,"stem":1682},"HTTP — протокол вебу","\u002Fcsharp\u002Fnetwork-programming\u002Fhttp-fundamentals","01.csharp\u002F13.network-programming\u002F07.http-fundamentals",{"title":1684,"path":1685,"stem":1686},"HttpListener — вбудований HTTP-сервер .NET","\u002Fcsharp\u002Fnetwork-programming\u002F07a.http-listener","01.csharp\u002F13.network-programming\u002F07a.http-listener",{"title":1688,"path":1689,"stem":1690},"HTTP Advanced — cookies, аутентифікація та HTTPS","\u002Fcsharp\u002Fnetwork-programming\u002Fhttp-advanced","01.csharp\u002F13.network-programming\u002F08.http-advanced",{"title":1692,"path":1693,"stem":1694},"SMTP та протоколи електронної пошти","\u002Fcsharp\u002Fnetwork-programming\u002Fsmtp","01.csharp\u002F13.network-programming\u002F09.smtp",{"title":1696,"path":1697,"stem":1698},"WebSocket — повнодуплексний протокол реального часу","\u002Fcsharp\u002Fnetwork-programming\u002Fwebsockets","01.csharp\u002F13.network-programming\u002F10.websockets",{"title":1700,"path":1701,"stem":1702},"TLS\u002FSSL — криптографічний захист мережевих з'єднань","\u002Fcsharp\u002Fnetwork-programming\u002Ftls-ssl","01.csharp\u002F13.network-programming\u002F11.tls-ssl",{"title":1704,"path":1705,"stem":1706},"C# & .NET: The Ultimate Roadmap","\u002Fcsharp\u002Froadmap","01.csharp\u002Froadmap",{"title":1708,"icon":1709,"path":1710,"stem":1711,"children":1712,"page":59},"C++","i-devicon-cplusplus","\u002Fcpp","02.cpp",[1713,1717,1721,1725,1729,1733,1737,1741,1745,1748,1752,1756,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796,1800,1804,1808,1812,1816,1820,1824,1828,1832,1836,1840,1844,1848,1852,1856,1860,1864,1868,1872,1876,1880,1884,1888,1892,1896,1900,1904,1908],{"title":1714,"path":1715,"stem":1716},"Вступ у програмування та алгоритми","\u002Fcpp\u002Fintro-algorithms","02.cpp\u002F01.intro-algorithms",{"title":1718,"path":1719,"stem":1720},"Code Style: угоди про оформлення коду","\u002Fcpp\u002Fcode-style","02.cpp\u002F02.code-style",{"title":1722,"path":1723,"stem":1724},"Середовище розробки та перший проєкт","\u002Fcpp\u002Fide-setup","02.cpp\u002F03.ide-setup",{"title":1726,"path":1727,"stem":1728},"Вивід даних на екран","\u002Fcpp\u002Fdata-output","02.cpp\u002F04.data-output",{"title":1730,"path":1731,"stem":1732},"Типи даних, змінні та константи","\u002Fcpp\u002Fdata-types-variables","02.cpp\u002F05.data-types-variables",{"title":1734,"path":1735,"stem":1736},"Ввід даних з клавіатури","\u002Fcpp\u002Fdata-input","02.cpp\u002F06.data-input",{"title":1738,"path":1739,"stem":1740},"Оператори, перетворення типів та логічні операції","\u002Fcpp\u002Foperators-type-conversion","02.cpp\u002F07.operators-type-conversion",{"title":1742,"path":1743,"stem":1744},"Цикли","\u002Fcpp\u002Floops","02.cpp\u002F08.loops",{"title":32,"path":1746,"stem":1747},"\u002Fcpp\u002Farrays","02.cpp\u002F09.arrays",{"title":1749,"path":1750,"stem":1751},"Алгоритми сортування та аналіз складності","\u002Fcpp\u002Fsorting","02.cpp\u002F10.sorting",{"title":1753,"path":1754,"stem":1755},"Алгоритми пошуку","\u002Fcpp\u002Fsearching","02.cpp\u002F11.searching",{"title":1757,"path":1758,"stem":1759},"Функції: основи","\u002Fcpp\u002Ffunctions-basics","02.cpp\u002F12.functions-basics",{"title":1761,"path":1762,"stem":1763},"Функції: прототипи, область видимості та додаткові можливості","\u002Fcpp\u002Ffunctions-scope","02.cpp\u002F13.functions-scope",{"title":1765,"path":1766,"stem":1767},"Функції: перевантаження та шаблони","\u002Fcpp\u002Ffunctions-overloading-templates","02.cpp\u002F14.functions-overloading-templates",{"title":1769,"path":1770,"stem":1771},"Вказівники: основи","\u002Fcpp\u002Fpointers-basics","02.cpp\u002F15.pointers-basics",{"title":1773,"path":1774,"stem":1775},"Посилання (References)","\u002Fcpp\u002Freferences","02.cpp\u002F16.references",{"title":1777,"path":1778,"stem":1779},"Вказівники, const і масиви","\u002Fcpp\u002Fpointers-const-arrays","02.cpp\u002F17.pointers-const-arrays",{"title":1781,"path":1782,"stem":1783},"Адресна арифметика","\u002Fcpp\u002Fpointer-arithmetic","02.cpp\u002F18.pointer-arithmetic",{"title":1785,"path":1786,"stem":1787},"Динамічна пам'ять","\u002Fcpp\u002Fdynamic-memory","02.cpp\u002F19.dynamic-memory",{"title":1789,"path":1790,"stem":1791},"Вказівники типу void","\u002Fcpp\u002Fvoid-pointers","02.cpp\u002F20.void-pointers",{"title":1793,"path":1794,"stem":1795},"Вказівники на вказівники","\u002Fcpp\u002Fpointers-to-pointers","02.cpp\u002F21.pointers-to-pointers",{"title":1797,"path":1798,"stem":1799},"Оператор доступу до членів через вказівник (->)","\u002Fcpp\u002Fmember-access-operator","02.cpp\u002F22.member-access-operator",{"title":1801,"path":1802,"stem":1803},"Цикл for-each (Range-based for)","\u002Fcpp\u002Fforeach-loop","02.cpp\u002F23.foreach-loop",{"title":1805,"path":1806,"stem":1807},"Вказівники на функції","\u002Fcpp\u002Ffunction-pointers","02.cpp\u002F24.function-pointers",{"title":1809,"path":1810,"stem":1811},"Лямбда-вирази","\u002Fcpp\u002Flambdas","02.cpp\u002F25.lambdas",{"title":1813,"path":1814,"stem":1815},"Лямбда-захоплення","\u002Fcpp\u002Flambda-captures","02.cpp\u002F26.lambda-captures",{"title":1817,"path":1818,"stem":1819},"Еліпсис","\u002Fcpp\u002Fellipsis","02.cpp\u002F27.ellipsis",{"title":1821,"path":1822,"stem":1823},"Безпечні альтернативи еліпсису","\u002Fcpp\u002F27a.ellipsis","02.cpp\u002F27a.ellipsis",{"title":1825,"path":1826,"stem":1827},"Аргументи командного рядка","\u002Fcpp\u002Fcommand-line-arguments","02.cpp\u002F28.command-line-arguments",{"title":1829,"path":1830,"stem":1831},"Перерахування (enum)","\u002Fcpp\u002Fenum","02.cpp\u002F29.enum",{"title":1833,"path":1834,"stem":1835},"Класи-перерахування (enum class)","\u002Fcpp\u002Fenum-class","02.cpp\u002F30.enum-class",{"title":1837,"path":1838,"stem":1839},"Псевдоніми типів (typedef і using)","\u002Fcpp\u002Ftype-aliases","02.cpp\u002F31.type-aliases",{"title":1841,"path":1842,"stem":1843},"Системи числення та двійкова арифметика","\u002Fcpp\u002Fnumber-systems","02.cpp\u002F32.number-systems",{"title":1845,"path":1846,"stem":1847},"Структури (struct): агрегування даних","\u002Fcpp\u002Fstruct","02.cpp\u002F33.struct",{"title":1849,"path":1850,"stem":1851},"Структури у функціях","\u002Fcpp\u002Fstruct-functions","02.cpp\u002F34.struct-functions",{"title":1853,"path":1854,"stem":1855},"Масиви структур і вкладені структури","\u002Fcpp\u002Fstruct-arrays","02.cpp\u002F35.struct-arrays",{"title":1857,"path":1858,"stem":1859},"Патерни struct та межі застосування","\u002Fcpp\u002Fstruct-patterns","02.cpp\u002F36.struct-patterns",{"title":1861,"path":1862,"stem":1863},"Символи та таблиця ASCII","\u002Fcpp\u002Fascii-characters","02.cpp\u002F37.ascii-characters",{"title":1865,"path":1866,"stem":1867},"Unicode та кодування UTF","\u002Fcpp\u002Funicode-utf","02.cpp\u002F38.unicode-utf",{"title":1869,"path":1870,"stem":1871},"C-style рядки","\u002Fcpp\u002Fc-strings","02.cpp\u002F39.c-strings",{"title":1873,"path":1874,"stem":1875},"Вступ до std::string","\u002Fcpp\u002Fstd-string-intro","02.cpp\u002F40.std-string-intro",{"title":1877,"path":1878,"stem":1879},"Довжина, ємність та доступ до символів std::string","\u002Fcpp\u002Fstd-string-capacity-access","02.cpp\u002F41.std-string-capacity-access",{"title":1881,"path":1882,"stem":1883},"Модифікація std::string: присвоювання, додавання, вставка, видалення та заміна","\u002Fcpp\u002Fstd-string-modification","02.cpp\u002F42.std-string-modification",{"title":1885,"path":1886,"stem":1887},"Пошук у std::string: find, npos та практичні патерни","\u002Fcpp\u002Fstd-string-search","02.cpp\u002F43.std-string-search",{"title":1889,"path":1890,"stem":1891},"std::string_view: невласницький погляд на рядок без копіювання","\u002Fcpp\u002Fstd-string-view","02.cpp\u002F44.std-string-view",{"title":1893,"path":1894,"stem":1895},"Об'єднання (union): один блок пам'яті, кілька інтерпретацій","\u002Fcpp\u002Funion","02.cpp\u002F45.union",{"title":1897,"path":1898,"stem":1899},"Організація коду: файли, препроцесор, простори імен","\u002Fcpp\u002Fmultifile-programs","02.cpp\u002F46.multifile-programs",{"title":1901,"path":1902,"stem":1903},"Робота з файлами: C-стиль (stdio.h)","\u002Fcpp\u002Fc-style-files","02.cpp\u002F47.c-style-files",{"title":1905,"path":1906,"stem":1907},"Робота з файлами: C++-стиль (fstream)","\u002Fcpp\u002Fcpp-style-files","02.cpp\u002F48.cpp-style-files",{"title":1909,"path":1910,"stem":1911},"План навчання: Курс C++ — Продовження (Статті 29–60+)","\u002Fcpp\u002Fcurriculum-plan","02.cpp\u002Fcurriculum-plan",{"title":1913,"icon":1914,"path":1915,"stem":1916,"children":1917,"page":59},"JavaScript","i-devicon-javascript","\u002Fjavascript","03.javascript",[1918,1944,1998,2020,2324,2362],{"title":1919,"icon":1920,"path":1921,"stem":1922,"children":1923,"page":59},"Events","i-lucide-mouse-pointer-click","\u002Fjavascript\u002Fevents","03.javascript\u002F01.events",[1924,1928,1932,1936,1940],{"title":1925,"path":1926,"stem":1927},"Вступ до подій браузера","\u002Fjavascript\u002Fevents\u002Fintro","03.javascript\u002F01.events\u002F01.intro",{"title":1929,"path":1930,"stem":1931},"Бульбашковий механізм (Bubbling) та занурення (Capturing)","\u002Fjavascript\u002Fevents\u002Fbubbling-capturing","03.javascript\u002F01.events\u002F02.bubbling-capturing",{"title":1933,"path":1934,"stem":1935},"Делегування подій (Event Delegation)","\u002Fjavascript\u002Fevents\u002Fdelegate-events","03.javascript\u002F01.events\u002F03.delegate-events",{"title":1937,"path":1938,"stem":1939},"Типові дії браузера та preventDefault()","\u002Fjavascript\u002Fevents\u002Fprevent-default","03.javascript\u002F01.events\u002F04.prevent-default",{"title":1941,"path":1942,"stem":1943},"Запуск користувацьких подій (Custom Events)","\u002Fjavascript\u002Fevents\u002Fcustom-events","03.javascript\u002F01.events\u002F05.custom-events",{"title":1945,"icon":1946,"path":1947,"stem":1948,"children":1949,"page":59},"Network","i-lucide-globe","\u002Fjavascript\u002Fnetwork","03.javascript\u002F02.network",[1950,1954,1958,1962,1966,1970,1974,1978,1982,1986,1990,1994],{"title":1951,"path":1952,"stem":1953},"Fetch API - Сучасний підхід до HTTP-запитів","\u002Fjavascript\u002Fnetwork\u002F01-fetch-api","03.javascript\u002F02.network\u002F01-fetch-api",{"title":1955,"path":1956,"stem":1957},"FormData - Робота з формами та файлами","\u002Fjavascript\u002Fnetwork\u002F02-formdata","03.javascript\u002F02.network\u002F02-formdata",{"title":1959,"path":1960,"stem":1961},"Відстеження прогресу завантаження","\u002Fjavascript\u002Fnetwork\u002F03-download-progress","03.javascript\u002F02.network\u002F03-download-progress",{"title":1963,"path":1964,"stem":1965},"Переривання fetch-запитів","\u002Fjavascript\u002Fnetwork\u002F04-abort-requests","03.javascript\u002F02.network\u002F04-abort-requests",{"title":1967,"path":1968,"stem":1969},"CORS - Запити між різними джерелами","\u002Fjavascript\u002Fnetwork\u002F05-cors","03.javascript\u002F02.network\u002F05-cors",{"title":1971,"path":1972,"stem":1973},"Fetch API - Повний довідник опцій","\u002Fjavascript\u002Fnetwork\u002F06-fetch-options","03.javascript\u002F02.network\u002F06-fetch-options",{"title":1975,"path":1976,"stem":1977},"URL Objects - Робота з посиланнями","\u002Fjavascript\u002Fnetwork\u002F07-url-objects","03.javascript\u002F02.network\u002F07-url-objects",{"title":1979,"path":1980,"stem":1981},"XMLHttpRequest - AJAX та низькорівневі запити","\u002Fjavascript\u002Fnetwork\u002F08-xmlhttprequest","03.javascript\u002F02.network\u002F08-xmlhttprequest",{"title":1983,"path":1984,"stem":1985},"Відновлюване завантаження файлів","\u002Fjavascript\u002Fnetwork\u002F09-resumable-upload","03.javascript\u002F02.network\u002F09-resumable-upload",{"title":1987,"path":1988,"stem":1989},"Cookies, document.cookie та світ після \"Cookiepocalypse\"","\u002Fjavascript\u002Fnetwork\u002F10-cookies","03.javascript\u002F02.network\u002F10-cookies",{"title":1991,"path":1992,"stem":1993},"js-cookie: Керування Cookies без Болю","\u002Fjavascript\u002Fnetwork\u002F11-js-cookie","03.javascript\u002F02.network\u002F11-js-cookie",{"title":1995,"path":1996,"stem":1997},"Axios: Потужний HTTP-клієнт для JavaScript","\u002Fjavascript\u002Fnetwork\u002F12-axios","03.javascript\u002F02.network\u002F12-axios",{"title":1999,"icon":2000,"path":2001,"stem":2002,"children":2003,"page":59},"Bom","i-lucide-monitor","\u002Fjavascript\u002Fbom","03.javascript\u002F03.bom",[2004,2008,2012,2016],{"title":2005,"path":2006,"stem":2007},"LocalStorage, SessionStorage та patterns збереження даних","\u002Fjavascript\u002Fbom\u002F01-localstorage","03.javascript\u002F03.bom\u002F01-localstorage",{"title":2009,"path":2010,"stem":2011},"Location Object - Керування адресою сторінки","\u002Fjavascript\u002Fbom\u002F02-location-object","03.javascript\u002F03.bom\u002F02-location-object",{"title":2013,"path":2014,"stem":2015},"History API - Керування історією браузера","\u002Fjavascript\u002Fbom\u002F03-history-api","03.javascript\u002F03.bom\u002F03-history-api",{"title":2017,"path":2018,"stem":2019},"Navigator Object - Ідентифікація та Можливості Пристрою","\u002Fjavascript\u002Fbom\u002F04-navigator-object","03.javascript\u002F03.bom\u002F04-navigator-object",{"title":2021,"icon":2022,"path":2023,"stem":2024,"children":2025},"React","i-devicon-react","\u002Fjavascript\u002Freact","03.javascript\u002F04.react\u002Findex",[2026,2027,2031,2035,2039,2043,2106,2141,2293],{"title":2021,"path":2023,"stem":2024},{"title":2028,"path":2029,"stem":2030},"Робота з Формами в React","\u002Fjavascript\u002Freact\u002Freact-forms","03.javascript\u002F04.react\u002F01.react-forms",{"title":2032,"path":2033,"stem":2034},"React Hook Form: Професійна Робота з Формами","\u002Fjavascript\u002Freact\u002Freact-hook-form","03.javascript\u002F04.react\u002F02.react-hook-form",{"title":2036,"path":2037,"stem":2038},"React Hook Form: Глибоке Розуміння Архітектури та Оптимізації","\u002Fjavascript\u002Freact\u002Freact-hook-form-new","03.javascript\u002F04.react\u002F02.react-hook-form-new",{"title":2040,"path":2041,"stem":2042},"Axios та React: Професійна Архітектура Запитів","\u002Fjavascript\u002Freact\u002Fdata-fetching-axios","03.javascript\u002F04.react\u002F03.data-fetching-axios",{"title":2044,"icon":132,"path":2045,"stem":2046,"children":2047},"Tanstack Query","\u002Fjavascript\u002Freact\u002Ftanstack-query","03.javascript\u002F04.react\u002F04.tanstack-query\u002Findex",[2048,2050,2054,2058,2062,2066,2070,2074,2078,2082,2086,2090,2094,2098,2102],{"title":2049,"path":2045,"stem":2046},"TanStack Query: Майстерність Керування Станом Сервера",{"title":2051,"path":2052,"stem":2053},"Парадигма Server State: Чому useEffect недостатньо","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fserver-state-paradigm","03.javascript\u002F04.react\u002F04.tanstack-query\u002F01.server-state-paradigm",{"title":2055,"path":2056,"stem":2057},"Встановлення та Налаштування: Фундамент","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Finstallation-and-devtools","03.javascript\u002F04.react\u002F04.tanstack-query\u002F02.installation-and-devtools",{"title":2059,"path":2060,"stem":2061},"Основи Запитів та Магія Ключів","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fquery-basics-and-keys","03.javascript\u002F04.react\u002F04.tanstack-query\u002F03.query-basics-and-keys",{"title":2063,"path":2064,"stem":2065},"Синхронізація Даних: Життєвий Цикл Запиту","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fdata-synchronization","03.javascript\u002F04.react\u002F04.tanstack-query\u002F04.data-synchronization",{"title":2067,"path":2068,"stem":2069},"Мутації та Інвалідація: Зміна Даних","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fmutations-and-invalidation","03.javascript\u002F04.react\u002F04.tanstack-query\u002F05.mutations-and-invalidation",{"title":2071,"path":2072,"stem":2073},"Оптимістичні Оновлення: Швидше за Світло","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Foptimistic-updates","03.javascript\u002F04.react\u002F04.tanstack-query\u002F06.optimistic-updates",{"title":2075,"path":2076,"stem":2077},"Пагінація та Infinite Scroll","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fpagination-and-load-more","03.javascript\u002F04.react\u002F04.tanstack-query\u002F07.pagination-and-load-more",{"title":2079,"path":2080,"stem":2081},"Просунуті Патерни та Оптимізація","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fadvanced-patterns","03.javascript\u002F04.react\u002F04.tanstack-query\u002F08.advanced-patterns",{"title":2083,"path":2084,"stem":2085},"Архітектура та Best Practices","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Farchitecture-and-best-practices","03.javascript\u002F04.react\u002F04.tanstack-query\u002F09.architecture-and-best-practices",{"title":2087,"path":2088,"stem":2089},"Server-Side Rendering (SSR) та Гідратація","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fserver-side-rendering","03.javascript\u002F04.react\u002F04.tanstack-query\u002F10.server-side-rendering",{"title":2091,"path":2092,"stem":2093},"Стратегії Тестування","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Ftesting-strategies","03.javascript\u002F04.react\u002F04.tanstack-query\u002F11.testing-strategies",{"title":2095,"path":2096,"stem":2097},"Аутентифікація та Обробка Помилок","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fauthentication-and-errors","03.javascript\u002F04.react\u002F04.tanstack-query\u002F12.authentication-and-errors",{"title":2099,"path":2100,"stem":2101},"React Suspense та Майбутнє","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Freact-suspense","03.javascript\u002F04.react\u002F04.tanstack-query\u002F13.react-suspense",{"title":2103,"path":2104,"stem":2105},"Глибоке Занурення в Продуктивність","\u002Fjavascript\u002Freact\u002Ftanstack-query\u002Fperformance-deep-dive","03.javascript\u002F04.react\u002F04.tanstack-query\u002F14.performance-deep-dive",{"title":2107,"icon":2022,"path":2108,"stem":2109,"children":2110},"React Router","\u002Fjavascript\u002Freact\u002Freact-router","03.javascript\u002F04.react\u002F05.react-router\u002Findex",[2111,2113,2117,2121,2125,2129,2133,2137],{"title":2112,"path":2108,"stem":2109},"React Router: Навігаційна система сучасного вебу",{"title":2114,"path":2115,"stem":2116},"Налаштування та Базовий Роутинг","\u002Fjavascript\u002Freact\u002Freact-router\u002Fsetup-and-basic-routing","03.javascript\u002F04.react\u002F05.react-router\u002F01.setup-and-basic-routing",{"title":2118,"path":2119,"stem":2120},"Динамічна Навігація","\u002Fjavascript\u002Freact\u002Freact-router\u002Fnavigation-and-links","03.javascript\u002F04.react\u002F05.react-router\u002F02.navigation-and-links",{"title":2122,"path":2123,"stem":2124},"Вкладені Маршрути та Макети","\u002Fjavascript\u002Freact\u002Freact-router\u002Fnested-routes-and-layouts","03.javascript\u002F04.react\u002F05.react-router\u002F03.nested-routes-and-layouts",{"title":2126,"path":2127,"stem":2128},"Динамічні Маршрути та Параметри","\u002Fjavascript\u002Freact\u002Freact-router\u002Fdynamic-routing","03.javascript\u002F04.react\u002F05.react-router\u002F04.dynamic-routing",{"title":2130,"path":2131,"stem":2132},"Data APIs: Loaders та Actions","\u002Fjavascript\u002Freact\u002Freact-router\u002Fdata-loading","03.javascript\u002F04.react\u002F05.react-router\u002F05.data-loading",{"title":2134,"path":2135,"stem":2136},"Просунуті Патерни","\u002Fjavascript\u002Freact\u002Freact-router\u002Fadvanced-patterns","03.javascript\u002F04.react\u002F05.react-router\u002F06.advanced-patterns",{"title":2138,"path":2139,"stem":2140},"Legacy Routing: Компонентний підхід","\u002Fjavascript\u002Freact\u002Freact-router\u002Flegacy-routing","03.javascript\u002F04.react\u002F05.react-router\u002F07.legacy-routing",{"title":2142,"icon":132,"path":2143,"stem":2144,"children":2145},"Redux","\u002Fjavascript\u002Freact\u002Fredux","03.javascript\u002F04.react\u002F06.redux\u002Findex",[2146,2148,2164,2193,2202,2223,2239,2268],{"title":2147,"path":2143,"stem":2144},"Redux: Еволюція управління станом",{"title":14,"icon":15,"path":2149,"stem":2150,"children":2151,"page":59},"\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals",[2152,2156,2160],{"title":2153,"path":2154,"stem":2155},"Вступ до State Management","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fintro-state-management","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F01.intro-state-management",{"title":2157,"path":2158,"stem":2159},"Філософія Redux та Три Принципи","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fredux-philosophy","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F02.redux-philosophy",{"title":2161,"path":2162,"stem":2163},"Чисті функції та Іммутабельність","\u002Fjavascript\u002Freact\u002Fredux\u002Ffundamentals\u002Fpure-functions-immutability","03.javascript\u002F04.react\u002F06.redux\u002F01.fundamentals\u002F03.pure-functions-immutability",{"title":2165,"icon":132,"path":2166,"stem":2167,"children":2168,"page":59},"Classic Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux",[2169,2173,2177,2181,2185,2189],{"title":2170,"path":2171,"stem":2172},"Створення Store (Classic Redux)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fstore-setup","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F01.store-setup",{"title":2174,"path":2175,"stem":2176},"Actions, Constants та Action Creators","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Factions-constants","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F02.actions-constants",{"title":2178,"path":2179,"stem":2180},"Логіка Reducers","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Freducers","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F03.reducers",{"title":2182,"path":2183,"stem":2184},"Комбінування Reducers (Root Reducer)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fdata-flow","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F04.data-flow",{"title":2186,"path":2187,"stem":2188},"Підключення до React (React-Redux)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Freact-redux-connection","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F05.react-redux-connection",{"title":2190,"path":2191,"stem":2192},"Middleware та Асинхронність (Redux Thunk)","\u002Fjavascript\u002Freact\u002Fredux\u002Fclassic-redux\u002Fmiddleware-thunk","03.javascript\u002F04.react\u002F06.redux\u002F02.classic-redux\u002F06.middleware-thunk",{"title":2194,"icon":132,"path":2195,"stem":2196,"children":2197,"page":59},"Transition To Rtk","\u002Fjavascript\u002Freact\u002Fredux\u002Ftransition-to-rtk","03.javascript\u002F04.react\u002F06.redux\u002F03.transition-to-rtk",[2198],{"title":2199,"path":2200,"stem":2201},"Проблеми класичного Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Ftransition-to-rtk\u002Fproblems-with-classic","03.javascript\u002F04.react\u002F06.redux\u002F03.transition-to-rtk\u002F01.problems-with-classic",{"title":2203,"icon":132,"path":2204,"stem":2205,"children":2206,"page":59},"Redux Toolkit","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit",[2207,2211,2215,2219],{"title":2208,"path":2209,"stem":2210},"Налаштування Store з configureStore","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fconfigure-store","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F01.configure-store",{"title":2212,"path":2213,"stem":2214},"createSlice: Революція в Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fcreate-slice","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F02.create-slice",{"title":2216,"path":2217,"stem":2218},"Асинхронність з createAsyncThunk","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fasync-thunks","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F03.async-thunks",{"title":2220,"path":2221,"stem":2222},"04. Entity Adapter: Керування нормалізованим станом","\u002Fjavascript\u002Freact\u002Fredux\u002Fredux-toolkit\u002Fentity-adapter","03.javascript\u002F04.react\u002F06.redux\u002F04.redux-toolkit\u002F04.entity-adapter",{"title":2224,"icon":92,"path":2225,"stem":2226,"children":2227,"page":59},"Advanced","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced",[2228,2232,2236],{"title":2229,"path":2230,"stem":2231},"Мемоізація та Селектори: Повний Гайд по Reselect","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Fselectors-reselect","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F01.selectors-reselect",{"title":2233,"path":2234,"stem":2235},"RTK Query: Архітектура Серверного Кешу","\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Frtk-query-intro","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F02.rtk-query-intro",{"title":2083,"path":2237,"stem":2238},"\u002Fjavascript\u002Freact\u002Fredux\u002Fadvanced\u002Farchitecture-best-practices","03.javascript\u002F04.react\u002F06.redux\u002F05.advanced\u002F03.architecture-best-practices",{"title":2240,"icon":132,"path":2241,"stem":2242,"children":2243,"page":59},"Project Kanban","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban",[2244,2248,2252,2256,2260,2264],{"title":2245,"path":2246,"stem":2247},"Проєкт: Kanban Board (Trello Clone)","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fproject-overview","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F01.project-overview",{"title":2249,"path":2250,"stem":2251},"Налаштування та Типізація","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fsetup-and-types","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F02.setup-and-types",{"title":2253,"path":2254,"stem":2255},"Board Slice: Серце Дошки","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fboard-slice","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F03.board-slice",{"title":2257,"path":2258,"stem":2259},"Логіка Drag & Drop","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Fdrag-and-drop-logic","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F04.drag-and-drop-logic",{"title":2261,"path":2262,"stem":2263},"Інтеграція з RTK Query","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Frtk-query-integration","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F05.rtk-query-integration",{"title":2265,"path":2266,"stem":2267},"Optimistic Updates","\u002Fjavascript\u002Freact\u002Fredux\u002Fproject-kanban\u002Foptimistic-updates","03.javascript\u002F04.react\u002F06.redux\u002F06.project-kanban\u002F06.optimistic-updates",{"title":2269,"icon":132,"path":2270,"stem":2271,"children":2272,"page":59},"Testing","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting","03.javascript\u002F04.react\u002F06.redux\u002F07.testing",[2273,2277,2281,2285,2289],{"title":2274,"path":2275,"stem":2276},"Тестування Redux","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Fintro-testing","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F01.intro-testing",{"title":2278,"path":2279,"stem":2280},"Тестування Reducers","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-reducers","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F02.testing-reducers",{"title":2282,"path":2283,"stem":2284},"Тестування Селекторів","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-selectors","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F03.testing-selectors",{"title":2286,"path":2287,"stem":2288},"Тестування Компонентів (Integration)","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-components","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F04.testing-components",{"title":2290,"path":2291,"stem":2292},"Тестування Async Thunks","\u002Fjavascript\u002Freact\u002Fredux\u002Ftesting\u002Ftesting-thunks","03.javascript\u002F04.react\u002F06.redux\u002F07.testing\u002F05.testing-thunks",{"title":2294,"icon":132,"path":2295,"stem":2296,"children":2297},"Ui Libraries","\u002Fjavascript\u002Freact\u002Fui-libraries","03.javascript\u002F04.react\u002F07.ui-libraries\u002Findex",[2298,2300,2304,2308,2312,2316,2320],{"title":2299,"path":2295,"stem":2296},"UI Бібліотеки в React",{"title":2301,"path":2302,"stem":2303},"Вступ до UI Бібліотек: Навіщо Винаходити Велосипед Двічі?","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fintroduction-to-ui-libraries","03.javascript\u002F04.react\u002F07.ui-libraries\u002F01.introduction-to-ui-libraries",{"title":2305,"path":2306,"stem":2307},"Філософія shadcn\u002Fui: \"Not a Component Library\"","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-philosophy","03.javascript\u002F04.react\u002F07.ui-libraries\u002F02.shadcn-philosophy",{"title":2309,"path":2310,"stem":2311},"Установка та Налаштування shadcn\u002Fui","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-installation","03.javascript\u002F04.react\u002F07.ui-libraries\u002F03.shadcn-installation",{"title":2313,"path":2314,"stem":2315},"Базові Компоненти shadcn\u002Fui: Фундамент Інтерфейсу","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-basics","03.javascript\u002F04.react\u002F07.ui-libraries\u002F04.shadcn-components-basics",{"title":2317,"path":2318,"stem":2319},"Компоненти Форм: Побудова Інтерактивних Form","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-forms","03.javascript\u002F04.react\u002F07.ui-libraries\u002F05.shadcn-components-forms",{"title":2321,"path":2322,"stem":2323},"Складні Компоненти: Dialog, Dropdown, Table та Command","\u002Fjavascript\u002Freact\u002Fui-libraries\u002Fshadcn-components-advanced","03.javascript\u002F04.react\u002F07.ui-libraries\u002F06.shadcn-components-advanced",{"title":2325,"icon":2326,"path":2327,"stem":2328,"children":2329,"page":59},"TypeScript","i-devicon-typescript","\u002Fjavascript\u002Ftypescript","03.javascript\u002F05.typescript",[2330,2334,2338,2342,2346,2350,2354,2358],{"title":2331,"path":2332,"stem":2333},"TypeScript: Броня для вашого коду","\u002Fjavascript\u002Ftypescript\u002Fintro-and-basic-types","03.javascript\u002F05.typescript\u002F01.intro-and-basic-types",{"title":2335,"path":2336,"stem":2337},"Майстерність Моделювання Даних: Інтерфейси та Просунуті Типи","\u002Fjavascript\u002Ftypescript\u002Finterfaces-and-advanced-types","03.javascript\u002F05.typescript\u002F02.interfaces-and-advanced-types",{"title":2339,"path":2340,"stem":2341},"Алхімія Типів: Generics та Utility Types","\u002Fjavascript\u002Ftypescript\u002Fgenerics-and-utilities","03.javascript\u002F05.typescript\u002F03.generics-and-utilities",{"title":2343,"path":2344,"stem":2345},"Архітектура та Шаблони: Класи в TypeScript","\u002Fjavascript\u002Ftypescript\u002Fclasses-and-oop","03.javascript\u002F05.typescript\u002F04.classes-and-oop",{"title":2347,"path":2348,"stem":2349},"Продакшн та Екосистема: Advanced Config & Workflow","\u002Fjavascript\u002Ftypescript\u002Fadvanced-patterns-and-config","03.javascript\u002F05.typescript\u002F05.advanced-patterns-and-config",{"title":2351,"path":2352,"stem":2353},"TypeScript у світі React","\u002Fjavascript\u002Ftypescript\u002Freact-basics","03.javascript\u002F05.typescript\u002F06.react-basics",{"title":2355,"path":2356,"stem":2357},"React + TypeScript: Продвинуті патерни","\u002Fjavascript\u002Ftypescript\u002Freact-advanced","03.javascript\u002F05.typescript\u002F07.react-advanced",{"title":2359,"path":2360,"stem":2361},"React + TypeScript: Екосистема та бібліотеки","\u002Fjavascript\u002Ftypescript\u002Freact-ecosystem","03.javascript\u002F05.typescript\u002F08.react-ecosystem",{"title":2363,"path":2364,"stem":2365},"Atomic Design","\u002Fjavascript\u002Fatomic-design","03.javascript\u002F2.atomic-design",{"title":2367,"icon":2368,"path":2369,"stem":2370,"children":2371,"page":59},"Java","i-devicon-java","\u002Fjava","04.java",[2372,2375,2378,2382,2386,2390,2394],{"title":162,"path":2373,"stem":2374},"\u002Fjava\u002Fdata-mapper-part1","04.java\u002F01.data-mapper-part1",{"title":166,"path":2376,"stem":2377},"\u002Fjava\u002Fdata-mapper-part2","04.java\u002F02.data-mapper-part2",{"title":2379,"path":2380,"stem":2381},"Service Layer: Організація бізнес-логіки","\u002Fjava\u002Fservice-layer","04.java\u002F03.service-layer",{"title":2383,"path":2384,"stem":2385},"Rich Domain Model та State Pattern","\u002Fjava\u002Frich-domain-model","04.java\u002F04.rich-domain-model",{"title":2387,"path":2388,"stem":2389},"Патерни для складної бізнес-логіки","\u002Fjava\u002Fbusiness-logic-patterns","04.java\u002F05.business-logic-patterns",{"title":2391,"path":2392,"stem":2393},"Обробка помилок та валідація","\u002Fjava\u002Ferror-handling-validation","04.java\u002F06.error-handling-validation",{"title":2395,"path":2396,"stem":2397,"children":2398,"page":59},"Проектування баз даних","\u002Fjava\u002Fpr2","04.java\u002Fpr2",[2399,2403,2407,2411,2415,2419,2423,2427,2431,2435,2439,2443,2447,2451,2455,2459,2463,2467,2471,2475,2479,2483,2487,2491,2495,2499,2503,2507,2511,2515,2519,2523,2527,2531,2535,2539,2543],{"title":2400,"path":2401,"stem":2402},"Концептуальне моделювання: Мистецтво розуміння предметної області","\u002Fjava\u002Fpr2\u002Fconceptual-modeling","04.java\u002Fpr2\u002F01.conceptual-modeling",{"title":2404,"path":2405,"stem":2406},"Логічне моделювання: Від бізнес-ідей до структур даних","\u002Fjava\u002Fpr2\u002Flogical-modeling","04.java\u002Fpr2\u002F02.logical-modeling",{"title":2408,"path":2409,"stem":2410},"Нормалізація: Гігієна даних та боротьба з аномаліями","\u002Fjava\u002Fpr2\u002Fnormalization","04.java\u002Fpr2\u002F03.normalization",{"title":2412,"path":2413,"stem":2414},"Фізична схема: Від абстракції до DDL","\u002Fjava\u002Fpr2\u002Fphysical-schema","04.java\u002Fpr2\u002F04.physical-schema",{"title":2416,"path":2417,"stem":2418},"Архітектурна класифікація таблиць","\u002Fjava\u002Fpr2\u002Ftable-classification","04.java\u002Fpr2\u002F05.table-classification",{"title":2420,"path":2421,"stem":2422},"Database Migrations: Версіонування схеми з Flyway","\u002Fjava\u002Fpr2\u002Fdatabase-migrations","04.java\u002Fpr2\u002F06.database-migrations",{"title":2424,"path":2425,"stem":2426},"А що, якби це була не реляційна БД?","\u002Fjava\u002Fpr2\u002Fbeyond-relational","04.java\u002Fpr2\u002F07.beyond-relational",{"title":2428,"path":2429,"stem":2430},"Object-Relational Impedance Mismatch: Два світи, що не хочуть дружити","\u002Fjava\u002Fpr2\u002Fimpedance-mismatch","04.java\u002Fpr2\u002F09.impedance-mismatch",{"title":2432,"path":2433,"stem":2434},"JDBC: Перший контакт із базою даних","\u002Fjava\u002Fpr2\u002Fjdbc-fundamentals","04.java\u002Fpr2\u002F10.jdbc-fundamentals",{"title":2436,"path":2437,"stem":2438},"Якість коду: Spotless, SpotBugs та SonarQube","\u002Fjava\u002Fpr2\u002F10a.code-quality","04.java\u002Fpr2\u002F10a.code-quality",{"title":2440,"path":2441,"stem":2442},"Connection Pool: Патерн Object Pool для JDBC-з'єднань","\u002Fjava\u002Fpr2\u002Fconnection-pool","04.java\u002Fpr2\u002F11.connection-pool",{"title":2444,"path":2445,"stem":2446},"Row Data Gateway: Об'єкт як обгортка рядка таблиці","\u002Fjava\u002Fpr2\u002Frow-data-gateway","04.java\u002Fpr2\u002F12.row-data-gateway",{"title":2448,"path":2449,"stem":2450},"Table Data Gateway: Фасад таблиці як архітектурний відступ","\u002Fjava\u002Fpr2\u002Ftable-data-gateway","04.java\u002Fpr2\u002F13.table-data-gateway",{"title":2452,"path":2453,"stem":2454},"Repository + Data Mapper: Правильна шарова архітектура з JDBC","\u002Fjava\u002Fpr2\u002Frepository-data-mapper","04.java\u002Fpr2\u002F14.repository-data-mapper",{"title":2456,"path":2457,"stem":2458},"Identity Map: Кешування сутностей у рамках сесії","\u002Fjava\u002Fpr2\u002Fidentity-map","04.java\u002Fpr2\u002F15.identity-map",{"title":2460,"path":2461,"stem":2462},"Unit of Work: Відстеження змін і координація JDBC-транзакцій","\u002Fjava\u002Fpr2\u002Funit-of-work","04.java\u002Fpr2\u002F16.unit-of-work",{"title":2464,"path":2465,"stem":2466},"Strategy: Замінювані SQL-стратегії для підтримки різних СУБД","\u002Fjava\u002Fpr2\u002Fstrategy-sql","04.java\u002Fpr2\u002F17.strategy-sql",{"title":2468,"path":2469,"stem":2470},"Proxy: Lazy Loading для One-To-Many колекцій","\u002Fjava\u002Fpr2\u002Fproxy-lazy-loading","04.java\u002Fpr2\u002F18.proxy-lazy-loading",{"title":2472,"path":2473,"stem":2474},"Generic Repository через Java Reflection: анотації та динамічний SQL","\u002Fjava\u002Fpr2\u002Fgeneric-repository-reflection","04.java\u002Fpr2\u002F19.generic-repository-reflection",{"title":2476,"path":2477,"stem":2478},"Specification Pattern: Композиція бізнес-правил для складних запитів","\u002Fjava\u002Fpr2\u002Fspecification-pattern","04.java\u002Fpr2\u002F20.specification-pattern",{"title":2480,"path":2481,"stem":2482},"Розширені можливості Specification Pattern: підзапити, агрегації та гібридний підхід","\u002Fjava\u002Fpr2\u002F20a.advanced-specifications","04.java\u002Fpr2\u002F20a.advanced-specifications",{"title":2484,"path":2485,"stem":2486},"Асинхронність у JDBC: Від блокуючих викликів до CompletableFuture","\u002Fjava\u002Fpr2\u002Fasynchronous-jdbc","04.java\u002Fpr2\u002F21.asynchronous-jdbc",{"title":2488,"path":2489,"stem":2490},"Інтеграційне тестування JDBC-репозиторіїв: Embedded H2 та патерн AAA","\u002Fjava\u002Fpr2\u002Fintegration-testing-h2","04.java\u002Fpr2\u002F22.integration-testing-h2",{"title":2492,"path":2493,"stem":2494},"Testcontainers: Тестування з реальною PostgreSQL у Docker-контейнерах","\u002Fjava\u002Fpr2\u002Fintegration-testing-testcontainers","04.java\u002Fpr2\u002F23.integration-testing-testcontainers",{"title":2496,"path":2497,"stem":2498},"Google Guice: Впровадження залежностей у JavaFX-проєкті","\u002Fjava\u002Fpr2\u002Fdependency-injection-guice","04.java\u002Fpr2\u002F24.dependency-injection-guice",{"title":2500,"path":2501,"stem":2502},"JavaFX: Основи побудови графічних інтерфейсів","\u002Fjava\u002Fpr2\u002Fjavafx-fundamentals","04.java\u002Fpr2\u002F25.javafx-fundamentals",{"title":2504,"path":2505,"stem":2506},"Properties та Bindings: Реактивність у JavaFX","\u002Fjava\u002Fpr2\u002Fjavafx-properties-bindings","04.java\u002Fpr2\u002F26.javafx-properties-bindings",{"title":2508,"path":2509,"stem":2510},"MVC vs MVP vs MVVM: Еволюція архітектурних патернів UI","\u002Fjava\u002Fpr2\u002Fui-architecture-patterns","04.java\u002Fpr2\u002F27.ui-architecture-patterns",{"title":2512,"path":2513,"stem":2514},"MVVM на практиці: Побудова ViewModel","\u002Fjava\u002Fpr2\u002Fmvvm-viewmodel-implementation","04.java\u002Fpr2\u002F28.mvvm-viewmodel-implementation",{"title":2516,"path":2517,"stem":2518},"View та Controller: Зв'язування з ViewModel через FXML","\u002Fjava\u002Fpr2\u002Fmvvm-view-controller","04.java\u002Fpr2\u002F29.mvvm-view-controller",{"title":2520,"path":2521,"stem":2522},"Інтеграція MVVM з Guice: Автоматична ін'єкція залежностей","\u002Fjava\u002Fpr2\u002Fmvvm-guice-integration","04.java\u002Fpr2\u002F30.mvvm-guice-integration",{"title":2524,"path":2525,"stem":2526},"Валідація та обробка помилок у MVVM","\u002Fjava\u002Fpr2\u002Fmvvm-validation-error-handling","04.java\u002Fpr2\u002F31.mvvm-validation-error-handling",{"title":2528,"path":2529,"stem":2530},"Навігація та управління екранами у JavaFX MVVM","\u002Fjava\u002Fpr2\u002Fmvvm-navigation-screen-management","04.java\u002Fpr2\u002F32.mvvm-navigation-screen-management",{"title":2532,"path":2533,"stem":2534},"Тестування JavaFX MVVM-додатків","\u002Fjava\u002Fpr2\u002Fmvvm-testing","04.java\u002Fpr2\u002F33.mvvm-testing",{"title":2536,"path":2537,"stem":2538},"Стилізація та теми у JavaFX: CSS та User Experience","\u002Fjava\u002Fpr2\u002Fjavafx-styling-themes","04.java\u002Fpr2\u002F34.javafx-styling-themes",{"title":2540,"path":2541,"stem":2542},"AtlantaFX: Сучасні теми для JavaFX додатків","\u002Fjava\u002Fpr2\u002Fatlantafx-modern-themes","04.java\u002Fpr2\u002F35.atlantafx-modern-themes",{"title":2544,"path":2545,"stem":2546},"Пакування та розповсюдження JavaFX-додатків","\u002Fjava\u002Fpr2\u002Fjar-packaging-distribution","04.java\u002Fpr2\u002F36.jar-packaging-distribution",{"title":2548,"icon":2549,"path":2550,"stem":2551,"children":2552,"page":59},"Python","i-devicon-python","\u002Fpython","05.python",[2553,2557,2560,2564,2568,2572,2576,2580,2584,2588,2592,2596,2600,2604,2608,2645],{"title":2554,"path":2555,"stem":2556},"Модулі, Пакети та Віртуальні Середовища","\u002Fpython\u002Fmodules-packages-venv","05.python\u002F00.modules-packages-venv",{"title":71,"path":2558,"stem":2559},"\u002Fpython\u002Fclasses-objects","05.python\u002F01.classes-objects",{"title":2561,"path":2562,"stem":2563},"Інкапсуляція, Керування Доступом та Властивості","\u002Fpython\u002Fencapsulation","05.python\u002F02.encapsulation",{"title":2565,"path":2566,"stem":2567},"Наслідування, MRO та суперсила super()","\u002Fpython\u002Finheritance-mro","05.python\u002F03.inheritance-mro",{"title":2569,"path":2570,"stem":2571},"Абстракція — ABC проти Статичних Протоколів (PEP 544)","\u002Fpython\u002Fabstraction-protocols","05.python\u002F04.abstraction-protocols",{"title":2573,"path":2574,"stem":2575},"Магічні методи (Dunder) та Емуляція протоколів","\u002Fpython\u002Fdunder-methods","05.python\u002F05.dunder-methods",{"title":2577,"path":2578,"stem":2579},"Декоратори та Керування життєвим циклом методів","\u002Fpython\u002Fdecorators-static-class","05.python\u002F06.decorators-static-class",{"title":2581,"path":2582,"stem":2583},"Дескриптори — Магія доступу до атрибутів","\u002Fpython\u002Fdescriptors","05.python\u002F07.descriptors",{"title":2585,"path":2586,"stem":2587},"Метакласи — Динамічне створення класів під капотом CPython","\u002Fpython\u002Fmetaclasses","05.python\u002F08.metaclasses",{"title":2589,"path":2590,"stem":2591},"Dataclasses, NamedTuple та сучасні контейнери Python","\u002Fpython\u002Fmodern-containers","05.python\u002F09.modern-containers",{"title":2593,"path":2594,"stem":2595},"GIL та модель конкурентності CPython — фундамент перед потоками і процесами","\u002Fpython\u002Fgil-concurrency-intro","05.python\u002F11.gil-concurrency-intro",{"title":2597,"path":2598,"stem":2599},"Threading — конкурентність для I\u002FO-bound задач","\u002Fpython\u002Fthreading","05.python\u002F12.threading",{"title":2601,"path":2602,"stem":2603},"Multiprocessing — справжній паралелізм для CPU-bound задач","\u002Fpython\u002Fmultiprocessing","05.python\u002F13.multiprocessing",{"title":2605,"path":2606,"stem":2607},"asyncio — кооперативна конкурентність та event loop","\u002Fpython\u002Fasyncio","05.python\u002F14.asyncio",{"title":2609,"icon":92,"path":2610,"stem":2611,"children":2612,"page":59},"FastAPI","\u002Fpython\u002Ffastapi","05.python\u002Ffastapi",[2613,2617,2621,2625,2629,2633,2637,2641],{"title":2614,"path":2615,"stem":2616},"Глибокий Typing та Pydantic v2 — від анотацій до валідації","\u002Fpython\u002Ffastapi\u002Ftyping-pydantic","05.python\u002Ffastapi\u002F15.typing-pydantic",{"title":2618,"path":2619,"stem":2620},"WSGI, ASGI та Python Web-екосистема","\u002Fpython\u002Ffastapi\u002Fwsgi-asgi-ecosystem","05.python\u002Ffastapi\u002F16.wsgi-asgi-ecosystem",{"title":2622,"path":2623,"stem":2624},"FastAPI: Перший додаток, Uvicorn та OpenAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-intro","05.python\u002Ffastapi\u002F17.fastapi-intro",{"title":2626,"path":2627,"stem":2628},"Маршрутизація, параметри запитів та APIRouter","\u002Fpython\u002Ffastapi\u002Ffastapi-routing-params","05.python\u002Ffastapi\u002F18.fastapi-routing-params",{"title":2630,"path":2631,"stem":2632},"Pydantic v2 у FastAPI — схеми, валідація та серіалізація","\u002Fpython\u002Ffastapi\u002Ffastapi-pydantic-schemas","05.python\u002Ffastapi\u002F19.fastapi-pydantic-schemas",{"title":2634,"path":2635,"stem":2636},"Dependency Injection — серце архітектури FastAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-dependency-injection","05.python\u002Ffastapi\u002F20.fastapi-dependency-injection",{"title":2638,"path":2639,"stem":2640},"Обробка помилок, Middleware та CORS у FastAPI","\u002Fpython\u002Ffastapi\u002Ffastapi-errors-middleware","05.python\u002Ffastapi\u002F21.fastapi-errors-middleware",{"title":2642,"path":2643,"stem":2644},"SQLAlchemy 2.0 — ORM, Core та Async Engine","\u002Fpython\u002Ffastapi\u002Fsqlalchemy-orm","05.python\u002Ffastapi\u002F22.sqlalchemy-orm",{"title":2646,"path":2647,"stem":2648},"[object Object]","\u002Fpython\u002Foop-plan","05.python\u002Foop-plan",{"title":2650,"icon":2651,"path":2652,"stem":2653,"children":2654,"page":59},"Бази даних","i-lucide-database","\u002Fdatabases","06.databases",[2655,2685,2708,2745,2774,2792,2826,2838,2847],{"title":2656,"icon":2657,"path":2658,"stem":2659,"children":2660,"page":59},"Intro","i-lucide-play","\u002Fdatabases\u002Fintro","06.databases\u002F01.intro",[2661,2665,2669,2673,2677,2681],{"title":2662,"path":2663,"stem":2664},"Введення в теорію баз даних","\u002Fdatabases\u002Fintro\u002Fintroduction-to-databases","06.databases\u002F01.intro\u002F01.introduction-to-databases",{"title":2666,"path":2667,"stem":2668},"Реляційна модель даних","\u002Fdatabases\u002Fintro\u002Frelational-model-theory","06.databases\u002F01.intro\u002F02.relational-model-theory",{"title":2670,"path":2671,"stem":2672},"ER-моделювання","\u002Fdatabases\u002Fintro\u002Fer-modeling","06.databases\u002F01.intro\u002F03.er-modeling",{"title":2674,"path":2675,"stem":2676},"Логічне проектування БД","\u002Fdatabases\u002Fintro\u002Flogical-schema","06.databases\u002F01.intro\u002F04.logical-schema",{"title":2678,"path":2679,"stem":2680},"Класифікація таблиць","\u002Fdatabases\u002Fintro\u002Ftable-classification","06.databases\u002F01.intro\u002F05.table-classification",{"title":2682,"path":2683,"stem":2684},"PlantUML для баз даних","\u002Fdatabases\u002Fintro\u002Fplantuml-diagrams","06.databases\u002F01.intro\u002F06.plantuml-diagrams",{"title":2686,"icon":2651,"path":2687,"stem":2688,"children":2689,"page":59},"MS SQL Server Start","\u002Fdatabases\u002Fms-sql-server-start","06.databases\u002F02.ms-sql-server-start",[2690,2694,2700,2704],{"title":2691,"path":2692,"stem":2693},"Типи даних у MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fdata-types","06.databases\u002F02.ms-sql-server-start\u002F01.data-types",{"title":2695,"path":2696,"stem":2697,"children":2698},"Індекси у MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fsql-indexes","06.databases\u002F02.ms-sql-server-start\u002F02.sql-indexes",[2699],{"title":2695,"path":2696,"stem":2697},{"title":2701,"path":2702,"stem":2703},"Системні бази даних MS SQL Server","\u002Fdatabases\u002Fms-sql-server-start\u002Fsystem-databases","06.databases\u002F02.ms-sql-server-start\u002F03.system-databases",{"title":2705,"path":2706,"stem":2707},"Огляд мови SQL та запитів","\u002Fdatabases\u002Fms-sql-server-start\u002Fsql-queries-overview","06.databases\u002F02.ms-sql-server-start\u002F04.sql-queries-overview",{"title":2709,"icon":2651,"path":2710,"stem":2711,"children":2712,"page":59},"SQL","\u002Fdatabases\u002Fsql","06.databases\u002F03.sql",[2713,2717,2721,2725,2729,2733,2737,2741],{"title":2714,"path":2715,"stem":2716},"Налаштування демонстраційної бази даних","\u002Fdatabases\u002Fsql\u002Fsample-database-setup","06.databases\u002F03.sql\u002F00.sample-database-setup",{"title":2718,"path":2719,"stem":2720},"DDL - Створення таблиць (CREATE TABLE)","\u002Fdatabases\u002Fsql\u002Fddl-create-table","06.databases\u002F03.sql\u002F01.ddl-create-table",{"title":2722,"path":2723,"stem":2724},"DDL - Зміна та видалення таблиць (ALTER, DROP)","\u002Fdatabases\u002Fsql\u002Fddl-alter-drop-table","06.databases\u002F03.sql\u002F02.ddl-alter-drop-table",{"title":2726,"path":2727,"stem":2728},"SELECT запити - Основи","\u002Fdatabases\u002Fsql\u002Fselect-queries-fundamentals","06.databases\u002F03.sql\u002F03.select-queries-fundamentals",{"title":2730,"path":2731,"stem":2732},"SELECT запити - Розширені можливості","\u002Fdatabases\u002Fsql\u002Fselect-queries-advanced","06.databases\u002F03.sql\u002F04.select-queries-advanced",{"title":2734,"path":2735,"stem":2736},"INSERT запити - Додавання даних","\u002Fdatabases\u002Fsql\u002Finsert-queries","06.databases\u002F03.sql\u002F05.insert-queries",{"title":2738,"path":2739,"stem":2740},"UPDATE та DELETE запити","\u002Fdatabases\u002Fsql\u002Fupdate-delete-queries","06.databases\u002F03.sql\u002F06.update-delete-queries",{"title":2742,"path":2743,"stem":2744},"Транзакції в SQL","\u002Fdatabases\u002Fsql\u002Ftransactions","06.databases\u002F03.sql\u002F07.transactions",{"title":2746,"icon":2651,"path":2747,"stem":2748,"children":2749,"page":59},"Multi Table Databases","\u002Fdatabases\u002Fmulti-table-databases","06.databases\u002F04.multi-table-databases",[2750,2754,2758,2762,2766,2770],{"title":2751,"path":2752,"stem":2753},"Зв'язки та нормалізація БД","\u002Fdatabases\u002Fmulti-table-databases\u002Frelationships-and-normalization","06.databases\u002F04.multi-table-databases\u002F00.relationships-and-normalization",{"title":2755,"path":2756,"stem":2757},"INNER JOIN - З'єднання таблиць","\u002Fdatabases\u002Fmulti-table-databases\u002Finner-join","06.databases\u002F04.multi-table-databases\u002F01.inner-join",{"title":2759,"path":2760,"stem":2761},"OUTER JOINs - LEFT, RIGHT, FULL","\u002Fdatabases\u002Fmulti-table-databases\u002Fouter-joins","06.databases\u002F04.multi-table-databases\u002F02.outer-joins",{"title":2763,"path":2764,"stem":2765},"CROSS та SELF JOINs","\u002Fdatabases\u002Fmulti-table-databases\u002Fcross-self-joins","06.databases\u002F04.multi-table-databases\u002F03.cross-self-joins",{"title":2767,"path":2768,"stem":2769},"Підзапити (Subqueries)","\u002Fdatabases\u002Fmulti-table-databases\u002Fsubqueries","06.databases\u002F04.multi-table-databases\u002F04.subqueries",{"title":2771,"path":2772,"stem":2773},"Агрегації з JOIN","\u002Fdatabases\u002Fmulti-table-databases\u002Faggregations-with-joins","06.databases\u002F04.multi-table-databases\u002F05.aggregations-with-joins",{"title":2775,"icon":2776,"path":2777,"stem":2778,"children":2779,"page":59},"Aggregate Functions","i-lucide-calculator","\u002Fdatabases\u002Faggregate-functions","06.databases\u002F05.aggregate-functions",[2780,2784,2788],{"title":2781,"path":2782,"stem":2783},"Функції агрегування в MS SQL Server","\u002Fdatabases\u002Faggregate-functions\u002Fintroduction-aggregate-functions","06.databases\u002F05.aggregate-functions\u002F01.introduction-aggregate-functions",{"title":2785,"path":2786,"stem":2787},"Групування даних в MS SQL Server","\u002Fdatabases\u002Faggregate-functions\u002Fgrouping-data","06.databases\u002F05.aggregate-functions\u002F02.grouping-data",{"title":2789,"path":2790,"stem":2791},"Підзапити з агрегатними функціями","\u002Fdatabases\u002Faggregate-functions\u002Fsubqueries-aggregates","06.databases\u002F05.aggregate-functions\u002F03.subqueries-aggregates",{"title":2793,"icon":2794,"path":2795,"stem":2796,"children":2797,"page":59},"Тригери та зберігаємі процедури","i-lucide-database-zap","\u002Fdatabases\u002Ftriggers-stored-procedures","06.databases\u002F07.triggers-stored-procedures",[2798,2802,2806,2810,2814,2818,2822],{"title":2799,"path":2800,"stem":2801},"DML-тригери","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fdml-triggers","06.databases\u002F07.triggers-stored-procedures\u002F01.dml-triggers",{"title":2803,"path":2804,"stem":2805},"DDL-тригери","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fddl-triggers","06.databases\u002F07.triggers-stored-procedures\u002F02.ddl-triggers",{"title":2807,"path":2808,"stem":2809},"Transact-SQL розширення","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Ftransact-sql-extensions","06.databases\u002F07.triggers-stored-procedures\u002F03.transact-sql-extensions",{"title":2811,"path":2812,"stem":2813},"Транзакції","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Ftransactions","06.databases\u002F07.triggers-stored-procedures\u002F04.transactions",{"title":2815,"path":2816,"stem":2817},"Зберігаємі процедури","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fstored-procedures","06.databases\u002F07.triggers-stored-procedures\u002F05.stored-procedures",{"title":2819,"path":2820,"stem":2821},"Користувацькі функції","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fuser-defined-functions","06.databases\u002F07.triggers-stored-procedures\u002F06.user-defined-functions",{"title":2823,"path":2824,"stem":2825},"Безпека баз даних","\u002Fdatabases\u002Ftriggers-stored-procedures\u002Fsecurity","06.databases\u002F07.triggers-stored-procedures\u002F08.security",{"title":2823,"icon":793,"path":2827,"stem":2828,"children":2829,"page":59},"\u002Fdatabases\u002Fsecurity","06.databases\u002F08.security",[2830,2834],{"title":2831,"path":2832,"stem":2833},"Вступ до безпеки баз даних","\u002Fdatabases\u002Fsecurity\u002Fintroduction","06.databases\u002F08.security\u002F01.introduction",{"title":2835,"path":2836,"stem":2837},"Системні представлення та метадані","\u002Fdatabases\u002Fsecurity\u002Fsystem-views","06.databases\u002F08.security\u002F02.system-views",{"title":2839,"icon":2840,"path":2841,"stem":2842,"children":2843,"page":59},"Резервне копіювання та відновлення","i-lucide-database-backup","\u002Fdatabases\u002Fbackup-recovery","06.databases\u002F09.backup-recovery",[2844],{"title":2839,"path":2845,"stem":2846},"\u002Fdatabases\u002Fbackup-recovery\u002Fbackup-restore","06.databases\u002F09.backup-recovery\u002F01.backup-restore",{"title":2848,"icon":2849,"path":2850,"stem":2851,"children":2852,"page":59},"Повнотекстовий пошук","i-lucide-search","\u002Fdatabases\u002Ffull-text-search","06.databases\u002F10.full-text-search",[2853],{"title":2848,"path":2854,"stem":2855},"\u002Fdatabases\u002Ffull-text-search\u002Ffull-text-search","06.databases\u002F10.full-text-search\u002F01.full-text-search",{"title":2857,"icon":2858,"path":2859,"stem":2860,"children":2861,"page":59},"Tools","i-lucide-wrench","\u002Ftools","07.tools",[2862,2938],{"title":2863,"icon":2864,"path":2865,"stem":2866,"children":2867},"Docker","i-simple-icons-docker","\u002Ftools\u002Fdocker","07.tools\u002F01.docker\u002Findex",[2868,2870,2874,2878,2882,2886,2890,2894,2898,2902,2906,2910,2914,2918,2922,2926,2930,2934],{"title":2869,"path":2865,"stem":2866},"Docker: від нуля до production",{"title":2871,"path":2872,"stem":2873},"Контейнеризація — від проблеми до рішення","\u002Ftools\u002Fdocker\u002Fcontainerization-concept","07.tools\u002F01.docker\u002F01.containerization-concept",{"title":2875,"path":2876,"stem":2877},"Docker — що це і навіщо?","\u002Ftools\u002Fdocker\u002Fdocker-what-and-why","07.tools\u002F01.docker\u002F02.docker-what-and-why",{"title":2879,"path":2880,"stem":2881},"Архітектура Docker Engine","\u002Ftools\u002Fdocker\u002Fdocker-architecture","07.tools\u002F01.docker\u002F03.docker-architecture",{"title":2883,"path":2884,"stem":2885},"Встановлення Docker","\u002Ftools\u002Fdocker\u002Finstallation","07.tools\u002F01.docker\u002F04.installation",{"title":2887,"path":2888,"stem":2889},"Перший контейнер — docker run","\u002Ftools\u002Fdocker\u002Ffirst-container","07.tools\u002F01.docker\u002F05.first-container",{"title":2891,"path":2892,"stem":2893},"Життєвий цикл контейнера","\u002Ftools\u002Fdocker\u002Fcontainer-lifecycle","07.tools\u002F01.docker\u002F06.container-lifecycle",{"title":2895,"path":2896,"stem":2897},"Docker Images — фундаментальні концепції","\u002Ftools\u002Fdocker\u002Fdocker-images-fundamentals","07.tools\u002F01.docker\u002F07.docker-images-fundamentals",{"title":2899,"path":2900,"stem":2901},"Dockerfile — основи","\u002Ftools\u002Fdocker\u002Fdockerfile-basics","07.tools\u002F01.docker\u002F08.dockerfile-basics",{"title":2903,"path":2904,"stem":2905},"Dockerfile — просунуті техніки","\u002Ftools\u002Fdocker\u002Fdockerfile-advanced","07.tools\u002F01.docker\u002F09.dockerfile-advanced",{"title":2907,"path":2908,"stem":2909},"Build Context та кешування шарів","\u002Ftools\u002Fdocker\u002Fbuild-context-and-cache","07.tools\u002F01.docker\u002F10.build-context-and-cache",{"title":2911,"path":2912,"stem":2913},"Реєстри Docker-образів","\u002Ftools\u002Fdocker\u002Fimage-registries","07.tools\u002F01.docker\u002F11.image-registries",{"title":2915,"path":2916,"stem":2917},"Контейнеризація .NET додатків","\u002Ftools\u002Fdocker\u002Fdotnet-containerization","07.tools\u002F01.docker\u002F12.dotnet-containerization",{"title":2919,"path":2920,"stem":2921},"Томи та збереження даних","\u002Ftools\u002Fdocker\u002Fvolumes-and-data","07.tools\u002F01.docker\u002F13.volumes-and-data",{"title":2923,"path":2924,"stem":2925},"Основи мережі в Docker","\u002Ftools\u002Fdocker\u002Fnetworking-basics","07.tools\u002F01.docker\u002F14.networking-basics",{"title":2927,"path":2928,"stem":2929},"Змінні оточення та конфігурація","\u002Ftools\u002Fdocker\u002Fenvironment-and-configuration","07.tools\u002F01.docker\u002F15.environment-and-configuration",{"title":2931,"path":2932,"stem":2933},"Docker Compose — оркестрація контейнерів","\u002Ftools\u002Fdocker\u002Fdocker-compose-basics","07.tools\u002F01.docker\u002F16.docker-compose-basics",{"title":2935,"path":2936,"stem":2937},"Docker Compose — Multi-Service застосунки","\u002Ftools\u002Fdocker\u002Fcompose-multi-service","07.tools\u002F01.docker\u002F17.compose-multi-service",{"title":2939,"icon":2940,"path":2941,"stem":2942,"children":2943},"Kubernetes","simple-icons:kubernetes","\u002Ftools\u002Fkubernetes","07.tools\u002F02.kubernetes\u002Findex",[2944,2946,2950,2954,2958,2962,2966,2970,2974],{"title":2945,"path":2941,"stem":2942},"Kubernetes: від розробки до production",{"title":2947,"path":2948,"stem":2949},"Kubernetes — коли Docker Compose більше не вистачає","\u002Ftools\u002Fkubernetes\u002Fwhy-kubernetes","07.tools\u002F02.kubernetes\u002F01.why-kubernetes",{"title":2951,"path":2952,"stem":2953},"Архітектура Kubernetes — анатомія кластера","\u002Ftools\u002Fkubernetes\u002Fkubernetes-architecture","07.tools\u002F02.kubernetes\u002F02.kubernetes-architecture",{"title":2955,"path":2956,"stem":2957},"Локальне середовище — minikube, kind та k3s","\u002Ftools\u002Fkubernetes\u002Flocal-environment","07.tools\u002F02.kubernetes\u002F03.local-environment",{"title":2959,"path":2960,"stem":2961},"Pod — атомарна одиниця Kubernetes","\u002Ftools\u002Fkubernetes\u002Fpods-and-containers","07.tools\u002F02.kubernetes\u002F04.pods-and-containers",{"title":2963,"path":2964,"stem":2965},"Патерни використання Pod","\u002Ftools\u002Fkubernetes\u002Fpod-patterns","07.tools\u002F02.kubernetes\u002F05.pod-patterns",{"title":2967,"path":2968,"stem":2969},"Deployment — декларативне управління Pod","\u002Ftools\u002Fkubernetes\u002Fdeployment-basics","07.tools\u002F02.kubernetes\u002F06.deployment-basics",{"title":2971,"path":2972,"stem":2973},"Rolling Updates та управління життєвим циклом Deployment","\u002Ftools\u002Fkubernetes\u002Fdeployment-rolling-updates","07.tools\u002F02.kubernetes\u002F07.deployment-rolling-updates",{"title":2975,"path":2976,"stem":2977},"Service — мережева абстракція для Pod","\u002Ftools\u002Fkubernetes\u002Fservices-networking","07.tools\u002F02.kubernetes\u002F08.services-networking",{"title":2979,"icon":2980,"path":2981,"stem":2982,"children":2983,"page":59},"Software Engineering","i-lucide-code-2","\u002Fsoftware-engineering","09.software-engineering",[2984,2988,2992,2996,3000,3004,3008,3012,3016,3020,3024],{"title":2985,"path":2986,"stem":2987},"1. Аналіз предметної області. Експертні знання та складність","\u002Fsoftware-engineering\u002Fintro-subdomains","09.software-engineering\u002F01.intro-subdomains",{"title":2989,"path":2990,"stem":2991},"2. Обмежені контексти. Інтеграція обмежених контекстів","\u002Fsoftware-engineering\u002Fintegrating-limited-contexts","09.software-engineering\u002F02.integrating-limited-contexts",{"title":2993,"path":2994,"stem":2995},"3. Реалізація простої бізнес-логіки","\u002Fsoftware-engineering\u002Fsimple","09.software-engineering\u002F03.simple",{"title":2997,"path":2998,"stem":2999},"4. Опрацювання складної бізнес-логіки","\u002Fsoftware-engineering\u002Fcomplex-business-logic","09.software-engineering\u002F04.complex-business-logic",{"title":3001,"path":3002,"stem":3003},"5. Моделювання фактора часу. Подієво-орієнтована архітектура.","\u002Fsoftware-engineering\u002Fmodelling-the-time-factor","09.software-engineering\u002F05.modelling-the-time-factor",{"title":3005,"path":3006,"stem":3007},"6. Архітектурні патерни","\u002Fsoftware-engineering\u002Farchitectural-patterns","09.software-engineering\u002F06.architectural-patterns",{"title":3009,"path":3010,"stem":3011},"Паттерни взаємодії","\u002Fsoftware-engineering\u002Fpatterns-of-interaction","09.software-engineering\u002F07.patterns-of-interaction",{"title":3013,"path":3014,"stem":3015},"Евристика проєктування","\u002Fsoftware-engineering\u002Fdesign-heuristics","09.software-engineering\u002F08.design-heuristics",{"title":3017,"path":3018,"stem":3019},"Еволюція проєктних рішень","\u002Fsoftware-engineering\u002Fevolution-of-design-solutions","09.software-engineering\u002F09.evolution-of-design-solutions",{"title":3021,"path":3022,"stem":3023},"EventStorming","\u002Fsoftware-engineering\u002Feventstorming","09.software-engineering\u002F10.eventstorming",{"title":3025,"path":3026,"stem":3027},"DDD на практиці","\u002Fsoftware-engineering\u002Fddd-in-practice","09.software-engineering\u002F11.ddd-in-practice",{"title":3029,"icon":943,"path":3030,"stem":3031,"children":3032,"page":59},"DDD","\u002Fddd","10.ddd",[3033,3037,3041,3045,3049,3053,3057,3061,3065,3069,3073,3077,3081],{"title":3034,"path":3035,"stem":3036},"Аналіз предметної області","\u002Fddd\u002Fdomain-analysis","10.ddd\u002F01.domain-analysis",{"title":3038,"path":3039,"stem":3040},"Експертні знання про предметну область","\u002Fddd\u002Fdomain-expert-knowledge","10.ddd\u002F02.domain-expert-knowledge",{"title":3042,"path":3043,"stem":3044},"Як осмислити складність предметної області","\u002Fddd\u002Fmanaging-domain-complexity","10.ddd\u002F03.managing-domain-complexity",{"title":3046,"path":3047,"stem":3048},"Інтеграція обмежених контекстів","\u002Fddd\u002Fbounded-context-integration","10.ddd\u002F04.bounded-context-integration",{"title":3050,"path":3051,"stem":3052},"Реалізація простої бізнес-логіки","\u002Fddd\u002Fsimple-business-logic","10.ddd\u002F05.simple-business-logic",{"title":3054,"path":3055,"stem":3056},"Обробка складної бізнес-логіки","\u002Fddd\u002Fcomplex-business-logic","10.ddd\u002F06.complex-business-logic",{"title":3058,"path":3059,"stem":3060},"Моделювання фактора часу","\u002Fddd\u002Ftime-modeling","10.ddd\u002F07.time-modeling",{"title":3062,"path":3063,"stem":3064},"Глава 8. Архітектурні Патерни","\u002Fddd\u002Farchitectural-patterns","10.ddd\u002F08.architectural-patterns",{"title":3066,"path":3067,"stem":3068},"Глава 9. Патерни Взаємодії","\u002Fddd\u002Finteraction-patterns","10.ddd\u002F09.interaction-patterns",{"title":3070,"path":3071,"stem":3072},"Глава 10. Проектні Евристики","\u002Fddd\u002Fdesign-heuristics","10.ddd\u002F10.design-heuristics",{"title":3074,"path":3075,"stem":3076},"Глава 11. Еволюція Проектних Рішень","\u002Fddd\u002Fevolution-of-design-decisions","10.ddd\u002F11.evolution-of-design-decisions",{"title":3078,"path":3079,"stem":3080},"Глава 12. EventStorming","\u002Fddd\u002Fevent-storming","10.ddd\u002F12.event-storming",{"title":3082,"path":3083,"stem":3084},"Глава 13. DDD на Практиці","\u002Fddd\u002Fddd-in-practice","10.ddd\u002F13.ddd-in-practice",{"title":3086,"icon":3087,"path":3088,"stem":3089,"children":3090,"page":59},"Media Streaming","i-lucide-video","\u002Fmedia-streaming","11.media-streaming",[3091,3095,3099,3103,3107,3111,3115],{"title":3092,"path":3093,"stem":3094},"01. Магія Стрімінгу: Що відбувається, коли ви натискаєте \"Play\"","\u002Fmedia-streaming\u002Fintroduction","11.media-streaming\u002F01.introduction",{"title":3096,"path":3097,"stem":3098},"02. Анатомія Медіа: Кодеки, Контейнери та Стиснення","\u002Fmedia-streaming\u002Faudio-video-anatomy","11.media-streaming\u002F02.audio-video-anatomy",{"title":3100,"path":3101,"stem":3102},"03. The Gym: FFmpeg Deep Dive","\u002Fmedia-streaming\u002Fffmpeg-gym","11.media-streaming\u002F03.ffmpeg-gym",{"title":3104,"path":3105,"stem":3106},"04. HLS Protocol: HTTP Live Streaming у Деталях","\u002Fmedia-streaming\u002Fhls-protocol","11.media-streaming\u002F04.hls-protocol",{"title":3108,"path":3109,"stem":3110},"05. DASH Protocol: Відкритий Стандарт","\u002Fmedia-streaming\u002Fdash-protocol","11.media-streaming\u002F05.dash-protocol",{"title":3112,"path":3113,"stem":3114},"06. Масштабування: CDN та Adaptive Bitrate","\u002Fmedia-streaming\u002Fcdn-and-adaptive-bitrate","11.media-streaming\u002F06.cdn-and-adaptive-bitrate",{"title":3116,"path":3117,"stem":3118},"07. Війна із Затримкою (Latency)","\u002Fmedia-streaming\u002Frealtime-latency","11.media-streaming\u002F07.realtime-latency",{"title":3120,"icon":3121,"path":3122,"stem":3123,"children":3124,"page":59},"HTML & CSS","i-devicon-html5","\u002Fhtml-css","12.html-css",[3125,3129,3133,3137,3141,3145,3149,3153,3157,3161,3165,3169,3173,3177,3181,3185,3189,3193,3197,3201,3205,3209,3213,3217,3221,3225,3229,3233,3237,3241],{"title":3126,"path":3127,"stem":3128},"Вступ до HTML. Структура документа","\u002Fhtml-css\u002Fintro-html-structure","12.html-css\u002F01.intro-html-structure",{"title":3130,"path":3131,"stem":3132},"Форматування тексту в HTML","\u002Fhtml-css\u002Fhtml-text-formatting","12.html-css\u002F02.html-text-formatting",{"title":3134,"path":3135,"stem":3136},"Посилання та зображення в HTML","\u002Fhtml-css\u002Fhtml-links-images","12.html-css\u002F03.html-links-images",{"title":3138,"path":3139,"stem":3140},"Списки та таблиці в HTML","\u002Fhtml-css\u002Fhtml-lists-tables","12.html-css\u002F04.html-lists-tables",{"title":3142,"path":3143,"stem":3144},"Форми в HTML","\u002Fhtml-css\u002Fhtml-forms","12.html-css\u002F05.html-forms",{"title":3146,"path":3147,"stem":3148},"Семантичні елементи HTML5","\u002Fhtml-css\u002Fhtml-semantic-elements","12.html-css\u002F06.html-semantic-elements",{"title":3150,"path":3151,"stem":3152},"Мультимедіа та розширені елементи HTML","\u002Fhtml-css\u002Fhtml-multimedia-advanced","12.html-css\u002F07.html-multimedia-advanced",{"title":3154,"path":3155,"stem":3156},"Мікророзмітка та SEO в HTML","\u002Fhtml-css\u002Fhtml-microdata-seo","12.html-css\u002F08.html-microdata-seo",{"title":3158,"path":3159,"stem":3160},"Вступ до CSS. Селектори та специфічність","\u002Fhtml-css\u002Fcss-intro-selectors","12.html-css\u002F09.css-intro-selectors",{"title":3162,"path":3163,"stem":3164},"Блокова модель CSS. Відступи. Box Sizing","\u002Fhtml-css\u002Fcss-box-model","12.html-css\u002F10.css-box-model",{"title":3166,"path":3167,"stem":3168},"Розміри у CSS: повний довідник одиниць і ключових слів","\u002Fhtml-css\u002F10a.css-sizing","12.html-css\u002F10a.css-sizing",{"title":3170,"path":3171,"stem":3172},"Типографіка в CSS. Шрифти та текст","\u002Fhtml-css\u002Fcss-typography","12.html-css\u002F11.css-typography",{"title":3174,"path":3175,"stem":3176},"Кольори та фони в CSS","\u002Fhtml-css\u002Fcss-colors-backgrounds","12.html-css\u002F12.css-colors-backgrounds",{"title":3178,"path":3179,"stem":3180},"Тіні та фільтри в CSS","\u002Fhtml-css\u002F12b.css-shadows-filters","12.html-css\u002F12b.css-shadows-filters",{"title":3182,"path":3183,"stem":3184},"CSS Flexbox: Фундамент гнучких макетів","\u002Fhtml-css\u002Fcss-flexbox-fundamentals","12.html-css\u002F13.css-flexbox-fundamentals",{"title":3186,"path":3187,"stem":3188},"CSS Flexbox: Вирівнювання та Позиціонування","\u002Fhtml-css\u002Fcss-flexbox-alignment-sizing-and-patterns","12.html-css\u002F14.css-flexbox-alignment-sizing-and-patterns",{"title":3190,"path":3191,"stem":3192},"CSS Grid. Двовимірний макет. Частина 1","\u002Fhtml-css\u002Fcss-layout-grid","12.html-css\u002F15.css-layout-grid",{"title":3194,"path":3195,"stem":3196},"CSS Grid. Двовимірний макет. Частина 2","\u002Fhtml-css\u002Fcss-layout-grid-advanced","12.html-css\u002F16.css-layout-grid-advanced",{"title":3198,"path":3199,"stem":3200},"Позиціонування в CSS. Z-index. Stacking Context","\u002Fhtml-css\u002Fcss-positioning","12.html-css\u002F17.css-positioning",{"title":3202,"path":3203,"stem":3204},"CSS Анімації та Переходи","\u002Fhtml-css\u002Fcss-animations-transitions","12.html-css\u002F18.css-animations-transitions",{"title":3206,"path":3207,"stem":3208},"Адаптивний дизайн. Media Queries. Частина 1","\u002Fhtml-css\u002Fcss-responsive-media-queries","12.html-css\u002F19.css-responsive-media-queries",{"title":3210,"path":3211,"stem":3212},"Адаптивний дизайн. Частина 2: clamp(), Container Queries, @layer","\u002Fhtml-css\u002Fcss-responsive-advanced","12.html-css\u002F20.css-responsive-advanced",{"title":3214,"path":3215,"stem":3216},"CSS Custom Properties. Методології. Сучасний CSS","\u002Fhtml-css\u002Fcss-variables-methodologies","12.html-css\u002F21.css-variables-methodologies",{"title":3218,"path":3219,"stem":3220},"Сучасний CSS 2023–2025: Нові можливості","\u002Fhtml-css\u002Fcss-modern-features","12.html-css\u002F22.css-modern-features",{"title":3222,"path":3223,"stem":3224},"CSS Nesting, @layer, @scope та @property: нативний препроцесор","\u002Fhtml-css\u002F22a.css-nesting-modern-syntax","12.html-css\u002F22a.css-nesting-modern-syntax",{"title":3226,"path":3227,"stem":3228},"CSS для форм та інтерактивних станів","\u002Fhtml-css\u002Fcss-forms-interactive-states","12.html-css\u002F23.css-forms-interactive-states",{"title":3230,"path":3231,"stem":3232},"Доступність у CSS (CSS Accessibility)","\u002Fhtml-css\u002Fcss-accessibility","12.html-css\u002F24.css-accessibility",{"title":3234,"path":3235,"stem":3236},"CSS-функції та сучасні sizing primitives","\u002Fhtml-css\u002Fcss-functions-sizing","12.html-css\u002F25.css-functions-sizing",{"title":3238,"path":3239,"stem":3240},"Rendering Pipeline і CSS Performance","\u002Fhtml-css\u002Fcss-rendering-performance","12.html-css\u002F26.css-rendering-performance",{"title":3242,"path":3243,"stem":3244},"CSS Best Practices: типові ситуації та правильні рішення","\u002Fhtml-css\u002Fcss-best-practices","12.html-css\u002F27.css-best-practices",{"title":3246,"path":3247,"stem":3248,"children":3249,"page":59},"AWS","\u002Faws","13.aws",[3250,3254,3258,3262,3266,3270,3274,3278,3282,3286,3290,3294,3298,3302,3306,3310,3314,3318],{"title":3251,"path":3252,"stem":3253},"Реєстрація AWS акаунту та студентські програми","\u002Faws\u002Faccount-registration","13.aws\u002F00.account-registration",{"title":3255,"path":3256,"stem":3257},"Вступ до хмарних обчислень та AWS","\u002Faws\u002Fintroduction-to-cloud","13.aws\u002F01.introduction-to-cloud",{"title":3259,"path":3260,"stem":3261},"AWS IAM — Identity and Access Management","\u002Faws\u002Fiam","13.aws\u002F02.iam",{"title":3263,"path":3264,"stem":3265},"AWS IAM CLI — Довідник команд","\u002Faws\u002F02a.iam-doc","13.aws\u002F02a.iam-doc",{"title":3267,"path":3268,"stem":3269},"Docker та контейнеризація в AWS — ECR, ECS та Fargate","\u002Faws\u002Fdocker-ecs","13.aws\u002F03.docker-ecs",{"title":3271,"path":3272,"stem":3273},"AWS ECR \u002F ECS CLI — Довідник команд","\u002Faws\u002F03a.docker-ecs-doc","13.aws\u002F03a.docker-ecs-doc",{"title":3275,"path":3276,"stem":3277},"Amazon EC2 — Elastic Compute Cloud","\u002Faws\u002Fec2","13.aws\u002F04.ec2",{"title":3279,"path":3280,"stem":3281},"AWS EC2 CLI — Довідник команд","\u002Faws\u002F04a.ec2-doc","13.aws\u002F04a.ec2-doc",{"title":3283,"path":3284,"stem":3285},"Elastic Load Balancing та Auto Scaling","\u002Faws\u002Falb-asg","13.aws\u002F05.alb-asg",{"title":3287,"path":3288,"stem":3289},"Amazon S3 — Simple Storage Service","\u002Faws\u002Fs3","13.aws\u002F06.s3",{"title":3291,"path":3292,"stem":3293},"Amazon CloudFront — Content Delivery Network","\u002Faws\u002Fcloudfront","13.aws\u002F07.cloudfront",{"title":3295,"path":3296,"stem":3297},"Amazon RDS — Relational Database Service","\u002Faws\u002Frds","13.aws\u002F08.rds",{"title":3299,"path":3300,"stem":3301},"Amazon DynamoDB — NoSQL Database","\u002Faws\u002Fdynamodb","13.aws\u002F09.dynamodb",{"title":3303,"path":3304,"stem":3305},"AWS Lambda та Serverless Compute","\u002Faws\u002Flambda","13.aws\u002F10.lambda",{"title":3307,"path":3308,"stem":3309},"Amazon Bedrock - Foundation Models, RAG та Agents","\u002Faws\u002Fbedrock","13.aws\u002F22.bedrock",{"title":3311,"path":3312,"stem":3313},"Amazon Rekognition - Комп'ютерний зір","\u002Faws\u002Frekognition","13.aws\u002F23.rekognition",{"title":3315,"path":3316,"stem":3317},"Amazon Textract - Інтелектуальний аналіз документів","\u002Faws\u002Ftextract","13.aws\u002F24.textract",{"title":3319,"path":3320,"stem":3321},"Amazon Polly, Transcribe, Comprehend та Translate","\u002Faws\u002Faudio-nlp-services","13.aws\u002F25.audio-nlp-services",{"title":3323,"path":3324,"stem":3325,"children":3326,"page":59},"Tailwind","\u002Ftailwind","21.tailwind",[3327,3331,3335,3339,3343,3347,3351,3355,3359,3363,3367,3371],{"title":3328,"path":3329,"stem":3330},"Що таке Tailwind CSS і навіщо він потрібен","\u002Ftailwind\u002Ftailwind-intro-philosophy","21.tailwind\u002F01.tailwind-intro-philosophy",{"title":3332,"path":3333,"stem":3334},"Встановлення та налаштування Tailwind CSS v4","\u002Ftailwind\u002Ftailwind-installation-setup","21.tailwind\u002F02.tailwind-installation-setup",{"title":3336,"path":3337,"stem":3338},"Utility-класи: основи та система Tailwind","\u002Ftailwind\u002Ftailwind-utility-classes-core","21.tailwind\u002F03.tailwind-utility-classes-core",{"title":3340,"path":3341,"stem":3342},"Layout: Flexbox та Grid через Tailwind","\u002Ftailwind\u002Ftailwind-flexbox-grid","21.tailwind\u002F04.tailwind-flexbox-grid",{"title":3344,"path":3345,"stem":3346},"Кастомізація теми через @theme у Tailwind v4","\u002Ftailwind\u002Ftailwind-theme-customization","21.tailwind\u002F05.tailwind-theme-customization",{"title":3348,"path":3349,"stem":3350},"Варіанти: hover, focus, responsive, dark mode та нові v4","\u002Ftailwind\u002Ftailwind-variants-states","21.tailwind\u002F06.tailwind-variants-states",{"title":3352,"path":3353,"stem":3354},"Типографіка та система кольорів у Tailwind v4","\u002Ftailwind\u002Ftailwind-typography-colors","21.tailwind\u002F07.tailwind-typography-colors",{"title":3356,"path":3357,"stem":3358},"Компоненти та повторюваність: @apply, @utility та патерни","\u002Ftailwind\u002Ftailwind-components-patterns","21.tailwind\u002F08.tailwind-components-patterns",{"title":3360,"path":3361,"stem":3362},"Темна тема та система дизайн-токенів у Tailwind v4","\u002Ftailwind\u002Ftailwind-dark-mode-theming","21.tailwind\u002F09.tailwind-dark-mode-theming",{"title":3364,"path":3365,"stem":3366},"Довільні значення та контейнерні запити у Tailwind v4","\u002Ftailwind\u002Ftailwind-arbitrary-container-queries","21.tailwind\u002F10.tailwind-arbitrary-container-queries",{"title":3368,"path":3369,"stem":3370},"Анімації, трансформації та 3D у Tailwind v4","\u002Ftailwind\u002Ftailwind-animations-transforms","21.tailwind\u002F11.tailwind-animations-transforms",{"title":3372,"path":3373,"stem":3374},"Tailwind CLI, PostCSS та інтеграція з фреймворками","\u002Ftailwind\u002Ftailwind-cli-tooling","21.tailwind\u002F12.tailwind-cli-tooling",{"title":3376,"path":3377,"stem":3378},"Тестування компонентів діаграм","\u002Ftest-components","98.test-components",{"id":3380,"title":2642,"body":3381,"description":26490,"extension":26491,"links":26492,"meta":26493,"navigation":3630,"path":2643,"seo":26494,"stem":2644,"__hash__":26495},"docs\u002F05.python\u002Ffastapi\u002F22.sqlalchemy-orm.md",{"type":3382,"value":3383,"toc":26415},"minimark",[3384,3393,3408,3416,3440,3443,3448,3451,3495,3502,3841,3844,3849,3858,3861,3911,3915,3920,3923,3964,3976,3978,3982,3985,4187,4207,4209,4213,4224,4228,4231,4237,4244,4397,4401,4412,4418,4432,4437,4443,4826,4829,4835,5060,5079,5083,5086,5157,5243,5245,5249,5260,5281,5426,5429,5434,5447,5539,5586,5589,5602,5698,5717,5721,5726,5765,5813,5840,5844,5972,5978,5984,6127,6138,6145,6155,6248,6259,6273,6284,6286,6294,6305,6311,6329,6337,6356,6359,6642,6644,6649,6653,6769,6775,6780,6788,6874,6882,6892,6898,6900,6906,6918,7066,7070,7144,7146,7151,7159,7163,7175,7182,7194,7196,7442,7444,7453,7456,7458,7467,7481,7500,7631,7633,7638,7651,7664,7681,7889,7891,7896,7903,7921,7934,7951,8186,8188,8193,8201,8217,8223,8237,8509,8526,8528,8537,8550,8721,8723,8727,8733,8891,8893,8897,8900,8904,8907,9264,9270,9308,9314,9325,9381,9384,9422,9428,9446,9453,9773,9777,9780,9971,9974,10664,10743,10747,10762,11111,11127,11129,11133,11136,11150,11281,11283,11289,11304,11312,11377,11381,11421,11423,11427,11445,11772,11775,11826,11830,11833,12230,12234,12251,12254,12263,12279,12314,12325,12370,12372,12378,12698,12710,13060,13062,13068,13073,13263,13267,13481,13483,13487,13497,13501,13510,13521,13532,13537,13632,13639,13649,13658,13664,13675,13789,13795,13803,13905,13916,14165,14180,14201,14203,14207,14215,14478,14502,14504,14508,14518,14538,14542,14553,14556,14672,14679,14681,14687,14694,14698,14720,14736,14743,14749,15467,15471,15493,15500,16055,16059,16083,16090,16150,16156,16174,16191,16194,16220,16236,16239,16388,16395,16409,16420,16426,16437,16450,16470,16474,16637,16641,16646,16691,16697,16705,16715,16722,16778,16782,16791,16800,16827,17198,17202,17206,17300,17305,17313,17317,17320,17458,17462,17468,17472,17494,17594,17598,17601,17740,17744,17750,17752,17758,17765,17768,17787,17792,17799,17820,17824,17827,17877,17880,17884,18016,18020,18026,18030,18033,18187,18191,18197,18199,18205,18212,18215,18234,18240,18269,18273,18305,18308,18312,18315,18401,18405,18411,18415,18418,18519,18523,18529,18531,18537,18544,18547,18566,18572,18591,18594,18598,18671,18675,18681,18685,18688,18812,18816,19142,19144,19150,19155,19648,19652,19796,19798,19802,19814,19818,19823,19836,19934,19939,19974,19978,19986,19997,20096,20103,20113,20117,20127,20146,20330,20336,20338,20346,20355,20362,20674,20677,21232,21262,21266,21269,21432,21447,21451,21454,21464,21472,21488,21493,22498,22607,22609,22613,22622,22626,26129,26133,26136,26165,26167,26171,26174,26181,26191,26220,26224,26232,26274,26278,26283,26307,26309,26313,26316,26411],[3385,3386,3387,3388,3392],"p",{},"Жоден production-рівень API не живе у вакуумі. Будь-який реальний вебсервіс неминуче стикається з необхідністю зберігати, читати, оновлювати та видаляти дані у постійному сховищі — реляційній базі даних. Саме тут на сцену виходить ",[3389,3390,3391],"strong",{},"SQLAlchemy"," — найповніший, найзріліший і найпопулярніший інструментарій для роботи з базами даних у Python, який часто називають «швейцарським ножем» між ORM та SQL-будівником.",[3385,3394,3395,3396,3399,3400,3403,3404,3407],{},"Проте SQLAlchemy — це не просто ще один ORM. Це ціла ",[3389,3397,3398],{},"екосистема",", що складається з двох чітко розмежованих рівнів: ",[3389,3401,3402],{},"Core"," (низькорівневий будівник SQL-запитів) і ",[3389,3405,3406],{},"ORM"," (об'єктно-реляційне відображення). Знання обох рівнів і розуміння того, де закінчується один і починається інший, є ознакою зрілого Python-розробника.",[3385,3409,3410,3411,3415],{},"У версії 2.0, яка вийшла у лютому 2023 року після тривалого перехідного періоду, SQLAlchemy отримала принципово новий, сучасний Python-синтаксис із підтримкою повної статичної типізації. Якщо ви бачили старий стиль SQLAlchemy 1.x — із класичними декларативними моделями на базі ",[3412,3413,3414],"code",{},"Column()"," та рядковими рефернесами — забудьте про нього. SQLAlchemy 2.0 виглядає і відчувається зовсім по-іншому. Власне, саме для цього і написана ця стаття.",[3417,3418,3419,3420,3423,3424,3427,3428,3431,3432,3435,3436,3439],"note",{},"Ця стаття є частиною великого циклу про FastAPI та базується на знаннях із попередніх матеріалів. Зокрема, концепція ",[3389,3421,3422],{},"asyncio"," та асинхронного програмування (стаття 14) буде критично важливою у розділі про ",[3412,3425,3426],{},"AsyncSession",". Знання ",[3389,3429,3430],{},"type hints"," і Pydantic (стаття 15) допоможе розібратись у новому синтаксисі ",[3412,3433,3434],{},"Mapped[T]"," та ",[3412,3437,3438],{},"mapped_column()",".",[3441,3442],"hr",{},[3444,3445,3447],"h2",{"id":3446},"початок-з-далека-навіщо-взагалі-потрібен-orm","«Початок з далека»: Навіщо взагалі потрібен ORM?",[3385,3449,3450],{},"Уявіть, що ви пишете FastAPI-ендпоінт, який має отримати список проектів конкретного користувача з бази даних PostgreSQL. Без жодного ORM або допоміжного інструменту вам доведеться:",[3452,3453,3454,3466,3469,3472,3475,3482],"ol",{},[3455,3456,3457,3458,3461,3462,3465],"li",{},"Відкрити низькорівневе підключення до бази даних через Python-драйвер (",[3412,3459,3460],{},"psycopg2"," або ",[3412,3463,3464],{},"asyncpg",").",[3455,3467,3468],{},"Сформувати SQL-рядок вручну — потенційно вразливий до SQL-ін'єкцій.",[3455,3470,3471],{},"Виконати запит і отримати сирий результат у вигляді списку кортежів або словників.",[3455,3473,3474],{},"Вручну перетворити ці дані у Python-об'єкти (або Pydantic-моделі).",[3455,3476,3477,3478,3481],{},"При оновленні — стежити за тим, які поля змінилися, і самостійно генерувати ",[3412,3479,3480],{},"UPDATE","-запит.",[3455,3483,3484,3485,3488,3489,3488,3492,3439],{},"Явно керувати транзакціями: ",[3412,3486,3487],{},"BEGIN",", ",[3412,3490,3491],{},"COMMIT",[3412,3493,3494],{},"ROLLBACK",[3385,3496,3497,3498,3501],{},"Це величезний обсяг шаблонного коду (boilerplate), схильного до помилок і складного у тестуванні. ",[3389,3499,3500],{},"ORM (Object-Relational Mapper)"," вирішує цю проблему, надаючи абстракцію між реляційним світом таблиць і рядків та об'єктно-орієнтованим світом Python-класів і екземплярів.",[3503,3504,3505,3533],"card-group",{},[3506,3507,3509],"card",{"icon":2980,"title":3508},"Без ORM (Raw SQL)",[3510,3511,3512,3515,3518,3524,3527,3530],"ul",{},[3455,3513,3514],{},"Ручне формування SQL-рядків",[3455,3516,3517],{},"Ризик SQL-ін'єкцій без параметризації",[3455,3519,3520,3521],{},"Сирі дані у вигляді кортежів ",[3412,3522,3523],{},"(42, 'arakviel', 'active')",[3455,3525,3526],{},"Ручне перетворення у Python-об'єкти",[3455,3528,3529],{},"Самостійне управління транзакціями",[3455,3531,3532],{},"Код прив'язаний до конкретного діалекту SQL",[3506,3534,3536,3563,3565,3569,3572,3581],{"icon":943,"title":3535},"З SQLAlchemy ORM",[3510,3537,3538,3545,3548,3554,3557,3560],{},[3455,3539,3540,3541,3544],{},"Моделі як звичайні Python-класи (",[3412,3542,3543],{},"class User(Base): ...",")",[3455,3546,3547],{},"Безпечні параметризовані запити «з коробки»",[3455,3549,3550,3551],{},"Результати як типізовані об'єкти ",[3412,3552,3553],{},"user.username",[3455,3555,3556],{},"Автоматичне відстеження змін (Change Tracking)",[3455,3558,3559],{},"Управління транзакціями через контекстний менеджер",[3455,3561,3562],{},"Підтримка різних БД (PostgreSQL, MySQL, SQLite) без зміни коду",[3441,3564],{},[3444,3566,3568],{"id":3567},"архітектура-sqlalchemy-два-рівні-абстракції","Архітектура SQLAlchemy: Два рівні абстракції",[3385,3570,3571],{},"Однією з найважливіших речей, яку потрібно зрозуміти про SQLAlchemy ще до написання першого рядка коду, є її дворівнева архітектура. Ця концепція корінним чином відрізняє SQLAlchemy від більшості інших ORM (як, наприклад, Django ORM або Peewee), які надають лише один рівень — об'єктний.",[3385,3573,3574],{},[3575,3576],"img",{"alt":3577,"className":3578,"src":3580},"Архітектура SQLAlchemy",[3579],"diagram-img","\u002Fimages\u002Fpython\u002Ffastapi\u002Fsqlalchemy-orm\u002F01.svg",[3582,3583,3584],"plant-uml",{},[3585,3586,3591],"pre",{"className":3587,"code":3588,"language":3589,"meta":3590,"style":3590},"language-plantuml shiki shiki-themes light-plus dark-plus dark-plus","@startuml\nskinparam style plain\nskinparam linetype ortho\nskinparam defaultFontSize 13\nskinparam backgroundColor #ffffff\n\npackage \"SQLAlchemy Ecosystem\" {\n    package \"ORM Layer (Об'єктний рівень)\" #e8f4f8 {\n        component [DeclarativeBase\\nMapped Classes] as models\n        component [Session\\nAsyncSession] as session\n        component [Unit of Work\\nIdentity Map] as uow\n        component [Relationships\\nLazy\u002FEager Loading] as rel\n    }\n\n    package \"Core Layer (SQL рівень)\" #fff3cd {\n        component [Engine\\nAsyncEngine] as engine\n        component [Connection Pool] as pool\n        component [select() insert()\\nupdate() delete()] as dml\n        component [Table\\nColumn\\nMetaData] as meta\n    }\n\n    package \"DBAPI (Драйвер)\" #f8d7da {\n        component [psycopg2 \u002F psycopg\\n(Sync PostgreSQL)] as sync_drv\n        component [asyncpg\\n(Async PostgreSQL)] as async_drv\n    }\n\n    database \"PostgreSQL\" as pg\n}\n\nmodels --> session\nsession --> uow\nuow --> dml\nsession --> dml\ndml --> engine\nengine --> pool\npool --> sync_drv\npool --> async_drv\nsync_drv --> pg\nasync_drv --> pg\nmeta --> dml\n\n@enduml\n","plantuml","",[3412,3592,3593,3601,3607,3613,3619,3625,3632,3638,3644,3650,3656,3662,3668,3674,3679,3685,3691,3697,3703,3709,3714,3719,3725,3731,3737,3742,3747,3753,3759,3764,3770,3776,3782,3788,3794,3800,3806,3812,3818,3824,3830,3835],{"__ignoreMap":3590},[3594,3595,3598],"span",{"class":3596,"line":3597},"line",1,[3594,3599,3600],{},"@startuml\n",[3594,3602,3604],{"class":3596,"line":3603},2,[3594,3605,3606],{},"skinparam style plain\n",[3594,3608,3610],{"class":3596,"line":3609},3,[3594,3611,3612],{},"skinparam linetype ortho\n",[3594,3614,3616],{"class":3596,"line":3615},4,[3594,3617,3618],{},"skinparam defaultFontSize 13\n",[3594,3620,3622],{"class":3596,"line":3621},5,[3594,3623,3624],{},"skinparam backgroundColor #ffffff\n",[3594,3626,3628],{"class":3596,"line":3627},6,[3594,3629,3631],{"emptyLinePlaceholder":3630},true,"\n",[3594,3633,3635],{"class":3596,"line":3634},7,[3594,3636,3637],{},"package \"SQLAlchemy Ecosystem\" {\n",[3594,3639,3641],{"class":3596,"line":3640},8,[3594,3642,3643],{},"    package \"ORM Layer (Об'єктний рівень)\" #e8f4f8 {\n",[3594,3645,3647],{"class":3596,"line":3646},9,[3594,3648,3649],{},"        component [DeclarativeBase\\nMapped Classes] as models\n",[3594,3651,3653],{"class":3596,"line":3652},10,[3594,3654,3655],{},"        component [Session\\nAsyncSession] as session\n",[3594,3657,3659],{"class":3596,"line":3658},11,[3594,3660,3661],{},"        component [Unit of Work\\nIdentity Map] as uow\n",[3594,3663,3665],{"class":3596,"line":3664},12,[3594,3666,3667],{},"        component [Relationships\\nLazy\u002FEager Loading] as rel\n",[3594,3669,3671],{"class":3596,"line":3670},13,[3594,3672,3673],{},"    }\n",[3594,3675,3677],{"class":3596,"line":3676},14,[3594,3678,3631],{"emptyLinePlaceholder":3630},[3594,3680,3682],{"class":3596,"line":3681},15,[3594,3683,3684],{},"    package \"Core Layer (SQL рівень)\" #fff3cd {\n",[3594,3686,3688],{"class":3596,"line":3687},16,[3594,3689,3690],{},"        component [Engine\\nAsyncEngine] as engine\n",[3594,3692,3694],{"class":3596,"line":3693},17,[3594,3695,3696],{},"        component [Connection Pool] as pool\n",[3594,3698,3700],{"class":3596,"line":3699},18,[3594,3701,3702],{},"        component [select() insert()\\nupdate() delete()] as dml\n",[3594,3704,3706],{"class":3596,"line":3705},19,[3594,3707,3708],{},"        component [Table\\nColumn\\nMetaData] as meta\n",[3594,3710,3712],{"class":3596,"line":3711},20,[3594,3713,3673],{},[3594,3715,3717],{"class":3596,"line":3716},21,[3594,3718,3631],{"emptyLinePlaceholder":3630},[3594,3720,3722],{"class":3596,"line":3721},22,[3594,3723,3724],{},"    package \"DBAPI (Драйвер)\" #f8d7da {\n",[3594,3726,3728],{"class":3596,"line":3727},23,[3594,3729,3730],{},"        component [psycopg2 \u002F psycopg\\n(Sync PostgreSQL)] as sync_drv\n",[3594,3732,3734],{"class":3596,"line":3733},24,[3594,3735,3736],{},"        component [asyncpg\\n(Async PostgreSQL)] as async_drv\n",[3594,3738,3740],{"class":3596,"line":3739},25,[3594,3741,3673],{},[3594,3743,3745],{"class":3596,"line":3744},26,[3594,3746,3631],{"emptyLinePlaceholder":3630},[3594,3748,3750],{"class":3596,"line":3749},27,[3594,3751,3752],{},"    database \"PostgreSQL\" as pg\n",[3594,3754,3756],{"class":3596,"line":3755},28,[3594,3757,3758],{},"}\n",[3594,3760,3762],{"class":3596,"line":3761},29,[3594,3763,3631],{"emptyLinePlaceholder":3630},[3594,3765,3767],{"class":3596,"line":3766},30,[3594,3768,3769],{},"models --> session\n",[3594,3771,3773],{"class":3596,"line":3772},31,[3594,3774,3775],{},"session --> uow\n",[3594,3777,3779],{"class":3596,"line":3778},32,[3594,3780,3781],{},"uow --> dml\n",[3594,3783,3785],{"class":3596,"line":3784},33,[3594,3786,3787],{},"session --> dml\n",[3594,3789,3791],{"class":3596,"line":3790},34,[3594,3792,3793],{},"dml --> engine\n",[3594,3795,3797],{"class":3596,"line":3796},35,[3594,3798,3799],{},"engine --> pool\n",[3594,3801,3803],{"class":3596,"line":3802},36,[3594,3804,3805],{},"pool --> sync_drv\n",[3594,3807,3809],{"class":3596,"line":3808},37,[3594,3810,3811],{},"pool --> async_drv\n",[3594,3813,3815],{"class":3596,"line":3814},38,[3594,3816,3817],{},"sync_drv --> pg\n",[3594,3819,3821],{"class":3596,"line":3820},39,[3594,3822,3823],{},"async_drv --> pg\n",[3594,3825,3827],{"class":3596,"line":3826},40,[3594,3828,3829],{},"meta --> dml\n",[3594,3831,3833],{"class":3596,"line":3832},41,[3594,3834,3631],{"emptyLinePlaceholder":3630},[3594,3836,3838],{"class":3596,"line":3837},42,[3594,3839,3840],{},"@enduml\n",[3385,3842,3843],{},"Розглянемо кожен рівень детально.",[3845,3846,3848],"h3",{"id":3847},"sqlalchemy-core-конструктор-sql","SQLAlchemy Core — «Конструктор SQL»",[3385,3850,3851,3853,3854,3857],{},[3389,3852,3402],{}," — це нижній рівень SQLAlchemy. Він надає Python-API для побудови SQL-виразів як об'єктів мови Python, а не як рядків. Цей рівень є «мовонезалежним» у розумінні бази даних: ви будуєте вираз ",[3412,3855,3856],{},"select(users_table).where(users_table.c.id == 42)",", а SQLAlchemy сама генерує правильний SQL-діалект для PostgreSQL, MySQL, SQLite чи Oracle.",[3385,3859,3860],{},"Ключові компоненти Core:",[3510,3862,3863,3871,3884,3903],{},[3455,3864,3865,3870],{},[3389,3866,3867],{},[3412,3868,3869],{},"Engine"," — «серце» SQLAlchemy, яке управляє пулом підключень до бази даних.",[3455,3872,3873,3435,3878,3883],{},[3389,3874,3875],{},[3412,3876,3877],{},"MetaData",[3389,3879,3880],{},[3412,3881,3882],{},"Table"," — опис структури таблиць як Python-об'єктів.",[3455,3885,3886,3889,3890,3488,3893,3488,3896,3488,3899,3902],{},[3389,3887,3888],{},"DML-функції"," — ",[3412,3891,3892],{},"select()",[3412,3894,3895],{},"insert()",[3412,3897,3898],{},"update()",[3412,3900,3901],{},"delete()"," для побудови запитів.",[3455,3904,3905,3910],{},[3389,3906,3907],{},[3412,3908,3909],{},"Connection"," — об'єкт одного активного підключення до БД.",[3845,3912,3914],{"id":3913},"sqlalchemy-orm-обєктне-відображення","SQLAlchemy ORM — «Об'єктне відображення»",[3385,3916,3917,3919],{},[3389,3918,3406],{}," — це верхній рівень, побудований поверх Core. Він надає класичне об'єктно-реляційне відображення: ви працюєте з Python-класами (моделями), а SQLAlchemy автоматично перетворює операції над ними у відповідні SQL-запити.",[3385,3921,3922],{},"Ключові компоненти ORM:",[3510,3924,3925,3938,3950,3958],{},[3455,3926,3927,3435,3932,3937],{},[3389,3928,3929],{},[3412,3930,3931],{},"DeclarativeBase",[3389,3933,3934],{},[3412,3935,3936],{},"Mapped"," — оголошення моделей (таблиць) як Python-класів.",[3455,3939,3940,3435,3945,3949],{},[3389,3941,3942],{},[3412,3943,3944],{},"Session",[3389,3946,3947],{},[3412,3948,3426],{}," — «workspace» для роботи з об'єктами, реалізація патерну Unit of Work.",[3455,3951,3952,3957],{},[3389,3953,3954],{},[3412,3955,3956],{},"relationship()"," — декларативний опис зв'язків між таблицями (one-to-many, many-to-many тощо).",[3455,3959,3960,3963],{},[3389,3961,3962],{},"Identity Map"," — внутрішній реєстр, що гарантує унікальність об'єктів у межах однієї сесії.",[3417,3965,3966,3967,3488,3969,3971,3972,3975],{},"У SQLAlchemy 2.0 ця межа стала ще чіткішою: ORM-запити тепер будуються виключно через Core DML-функції (",[3412,3968,3892],{},[3412,3970,3895],{}," тощо), а не через застарілий ",[3412,3973,3974],{},"session.query()"," API. Тобто ORM тепер «говорить» мовою Core, що робить код більш уніфікованим і передбачуваним.",[3441,3977],{},[3444,3979,3981],{"id":3980},"порівняння-ef-core-sqlalchemy","Порівняння: EF Core ↔ SQLAlchemy",[3385,3983,3984],{},"Перед тим як занурюватися в код, варто провести паралелі з інструментарієм ASP.NET, щоб сформувати правильні ментальні моделі. Якщо ви вже працювали з Entity Framework Core, більшість концепцій SQLAlchemy одразу стануть інтуїтивно зрозумілими — назви відрізняються, але ідеї залишаються тими самими.",[3986,3987,3988,4005],"table",{},[3989,3990,3991],"thead",{},[3992,3993,3994,3999,4002],"tr",{},[3995,3996,3998],"th",{"align":3997},"left","Концепція",[3995,4000,4001],{"align":3997},"Entity Framework Core (ASP.NET)",[3995,4003,4004],{"align":3997},"SQLAlchemy 2.0 (Python)",[4006,4007,4008,4028,4048,4067,4083,4100,4123,4140,4157,4170],"tbody",{},[3992,4009,4010,4016,4021],{},[4011,4012,4013],"td",{"align":3997},[3389,4014,4015],{},"Реєстрація «бази об'єктів»",[4011,4017,4018],{"align":3997},[3412,4019,4020],{},"DbContext",[4011,4022,4023,4025,4026],{"align":3997},[3412,4024,3944],{}," \u002F ",[3412,4027,3426],{},[3992,4029,4030,4035,4040],{},[4011,4031,4032],{"align":3997},[3389,4033,4034],{},"Набір записів таблиці",[4011,4036,4037],{"align":3997},[3412,4038,4039],{},"DbSet\u003CTEntity>",[4011,4041,4042,4045,4046],{"align":3997},[3412,4043,4044],{},"Mapped[]"," + ",[3412,4047,3882],{},[3992,4049,4050,4055,4060],{},[4011,4051,4052],{"align":3997},[3389,4053,4054],{},"Підключення до БД",[4011,4056,4057],{"align":3997},[3412,4058,4059],{},"DbContext.Database",[4011,4061,4062,4025,4064],{"align":3997},[3412,4063,3869],{},[3412,4065,4066],{},"AsyncEngine",[3992,4068,4069,4074,4079],{},[4011,4070,4071],{"align":3997},[3389,4072,4073],{},"ORM-модель (сутність)",[4011,4075,4076],{"align":3997},[3412,4077,4078],{},"class User : BaseEntity",[4011,4080,4081],{"align":3997},[3412,4082,3543],{},[3992,4084,4085,4090,4095],{},[4011,4086,4087],{"align":3997},[3389,4088,4089],{},"Відстеження змін",[4011,4091,4092],{"align":3997},[3412,4093,4094],{},"DbContext.ChangeTracker",[4011,4096,4097,4099],{"align":3997},[3412,4098,3944],{}," (Unit of Work)",[3992,4101,4102,4107,4112],{},[4011,4103,4104],{"align":3997},[3389,4105,4106],{},"Тільки читання",[4011,4108,4109],{"align":3997},[3412,4110,4111],{},"AsNoTracking()",[4011,4113,4114,3461,4117,4119,4120],{"align":3997},[3412,4115,4116],{},"execution_options(populate_existing=True)",[3412,4118,3892],{}," без ",[3412,4121,4122],{},"session.add()",[3992,4124,4125,4130,4135],{},[4011,4126,4127],{"align":3997},[3389,4128,4129],{},"Міграції",[4011,4131,4132],{"align":3997},[3412,4133,4134],{},"dotnet ef migrations add",[4011,4136,4137],{"align":3997},[3412,4138,4139],{},"alembic revision --autogenerate",[3992,4141,4142,4147,4152],{},[4011,4143,4144],{"align":3997},[3389,4145,4146],{},"Eager Loading",[4011,4148,4149],{"align":3997},[3412,4150,4151],{},".Include(u => u.Posts)",[4011,4153,4154],{"align":3997},[3412,4155,4156],{},"selectinload(User.posts)",[3992,4158,4159,4164,4167],{},[4011,4160,4161],{"align":3997},[3389,4162,4163],{},"Lazy Loading",[4011,4165,4166],{"align":3997},"Автоматично (за замовчуванням)",[4011,4168,4169],{"align":3997},"Вимкнено за замовчуванням (вимагає явного налаштування)",[3992,4171,4172,4177,4182],{},[4011,4173,4174],{"align":3997},[3389,4175,4176],{},"LINQ-запити",[4011,4178,4179],{"align":3997},[3412,4180,4181],{},"context.Users.Where(u => u.IsActive)",[4011,4183,4184],{"align":3997},[3412,4185,4186],{},"select(User).where(User.is_active == True)",[3385,4188,4189,4190,4192,4193,4196,4197,4201,4202,4206],{},"Найважливіша відмінність: в EF Core ",[3412,4191,4020],{}," є одночасно і «сховищем об'єктів», і точкою доступу до запитів (через ",[3412,4194,4195],{},"DbSet","). У SQLAlchemy ці ролі чітко розділені: ",[3389,4198,4199],{},[3412,4200,3869],{}," управляє підключеннями, а ",[3389,4203,4204],{},[3412,4205,3944],{}," управляє об'єктами. Це розділення є більш явним і дозволяє краще контролювати життєвий цикл обох сутностей.",[3441,4208],{},[3444,4210,4212],{"id":4211},"engine-та-connection-pool-фундамент-sqlalchemy","Engine та Connection Pool: Фундамент SQLAlchemy",[3385,4214,4215,4219,4220,4223],{},[3389,4216,4217],{},[3412,4218,3869],{}," — це перший об'єкт, який потрібно створити для роботи з SQLAlchemy. Він є центральною точкою конфігурації та управляє ",[3389,4221,4222],{},"пулом підключень (connection pool)"," до бази даних.",[3845,4225,4227],{"id":4226},"чому-потрібен-connection-pool","Чому потрібен Connection Pool?",[3385,4229,4230],{},"Відкриття нового підключення до PostgreSQL — це відносно «дорога» операція: вона включає TCP-рукостискання, автентифікацію та ініціалізацію внутрішнього стану сесії PostgreSQL. У вебзастосунку, де одночасно можуть надходити сотні або тисячі запитів, відкривати і закривати з'єднання для кожного запиту є неприпустимо повільним підходом.",[3385,4232,4233,4236],{},[3389,4234,4235],{},"Connection Pool"," вирішує цю проблему: він заздалегідь відкриває кілька підключень і тримає їх «живими». Коли FastAPI-обробнику потрібне підключення до бази даних, він бере готове з пулу, використовує його, і повертає назад — підключення не закривається, а очікує наступного запиту.",[3385,4238,4239],{},[3575,4240],{"alt":4241,"className":4242,"src":4243},"Пул підключень (Connection Pool)",[3579],"\u002Fimages\u002Fpython\u002Ffastapi\u002Fsqlalchemy-orm\u002F02.svg",[3582,4245,4246],{},[3585,4247,4249],{"className":3587,"code":4248,"language":3589,"meta":3590,"style":3590},"@startuml\nskinparam style plain\nskinparam linetype ortho\nskinparam defaultFontSize 13\nskinparam ArrowColor #555555\nskinparam backgroundColor #ffffff\n\npackage \"FastAPI Application\" #e8f4f8 {\n    component [Request 1] as R1\n    component [Request 2] as R2\n    component [Request 3] as R3\n}\n\npackage \"Connection Pool (QueuePool)\" #fff3cd {\n    component [Connection 1\\n(зайнята)] as C1 #ffcccc\n    component [Connection 2\\n(вільна)]  as C2 #ccffcc\n    component [Connection 3\\n(вільна)]  as C3 #ccffcc\n    component [Connection 4\\n(overflow)] as C4 #ffe0b2\n}\n\ndatabase \"PostgreSQL Server\" as PG #f8d7da\n\nR1 --> C1\nR2 --> C2\nR3 --> C3\n\nC1 --> PG\nC2 --> PG\nC3 --> PG\nC4 .right.> PG : створюється при\\nперевантаженні\n\n@enduml\n",[3412,4250,4251,4255,4259,4263,4267,4272,4276,4280,4285,4290,4295,4300,4304,4308,4313,4318,4323,4328,4333,4337,4341,4346,4350,4355,4360,4365,4369,4374,4379,4384,4389,4393],{"__ignoreMap":3590},[3594,4252,4253],{"class":3596,"line":3597},[3594,4254,3600],{},[3594,4256,4257],{"class":3596,"line":3603},[3594,4258,3606],{},[3594,4260,4261],{"class":3596,"line":3609},[3594,4262,3612],{},[3594,4264,4265],{"class":3596,"line":3615},[3594,4266,3618],{},[3594,4268,4269],{"class":3596,"line":3621},[3594,4270,4271],{},"skinparam ArrowColor #555555\n",[3594,4273,4274],{"class":3596,"line":3627},[3594,4275,3624],{},[3594,4277,4278],{"class":3596,"line":3634},[3594,4279,3631],{"emptyLinePlaceholder":3630},[3594,4281,4282],{"class":3596,"line":3640},[3594,4283,4284],{},"package \"FastAPI Application\" #e8f4f8 {\n",[3594,4286,4287],{"class":3596,"line":3646},[3594,4288,4289],{},"    component [Request 1] as R1\n",[3594,4291,4292],{"class":3596,"line":3652},[3594,4293,4294],{},"    component [Request 2] as R2\n",[3594,4296,4297],{"class":3596,"line":3658},[3594,4298,4299],{},"    component [Request 3] as R3\n",[3594,4301,4302],{"class":3596,"line":3664},[3594,4303,3758],{},[3594,4305,4306],{"class":3596,"line":3670},[3594,4307,3631],{"emptyLinePlaceholder":3630},[3594,4309,4310],{"class":3596,"line":3676},[3594,4311,4312],{},"package \"Connection Pool (QueuePool)\" #fff3cd {\n",[3594,4314,4315],{"class":3596,"line":3681},[3594,4316,4317],{},"    component [Connection 1\\n(зайнята)] as C1 #ffcccc\n",[3594,4319,4320],{"class":3596,"line":3687},[3594,4321,4322],{},"    component [Connection 2\\n(вільна)]  as C2 #ccffcc\n",[3594,4324,4325],{"class":3596,"line":3693},[3594,4326,4327],{},"    component [Connection 3\\n(вільна)]  as C3 #ccffcc\n",[3594,4329,4330],{"class":3596,"line":3699},[3594,4331,4332],{},"    component [Connection 4\\n(overflow)] as C4 #ffe0b2\n",[3594,4334,4335],{"class":3596,"line":3705},[3594,4336,3758],{},[3594,4338,4339],{"class":3596,"line":3711},[3594,4340,3631],{"emptyLinePlaceholder":3630},[3594,4342,4343],{"class":3596,"line":3716},[3594,4344,4345],{},"database \"PostgreSQL Server\" as PG #f8d7da\n",[3594,4347,4348],{"class":3596,"line":3721},[3594,4349,3631],{"emptyLinePlaceholder":3630},[3594,4351,4352],{"class":3596,"line":3727},[3594,4353,4354],{},"R1 --> C1\n",[3594,4356,4357],{"class":3596,"line":3733},[3594,4358,4359],{},"R2 --> C2\n",[3594,4361,4362],{"class":3596,"line":3739},[3594,4363,4364],{},"R3 --> C3\n",[3594,4366,4367],{"class":3596,"line":3744},[3594,4368,3631],{"emptyLinePlaceholder":3630},[3594,4370,4371],{"class":3596,"line":3749},[3594,4372,4373],{},"C1 --> PG\n",[3594,4375,4376],{"class":3596,"line":3755},[3594,4377,4378],{},"C2 --> PG\n",[3594,4380,4381],{"class":3596,"line":3761},[3594,4382,4383],{},"C3 --> PG\n",[3594,4385,4386],{"class":3596,"line":3766},[3594,4387,4388],{},"C4 .right.> PG : створюється при\\nперевантаженні\n",[3594,4390,4391],{"class":3596,"line":3772},[3594,4392,3631],{"emptyLinePlaceholder":3630},[3594,4394,4395],{"class":3596,"line":3778},[3594,4396,3840],{},[3845,4398,4400],{"id":4399},"python-db-api-20-що-таке-драйвер-і-як-він-працює","Python DB-API 2.0: Що таке «драйвер» і як він працює?",[3385,4402,4403,4404,4407,4408,4411],{},"Перш ніж розбирати ",[3412,4405,4406],{},"create_engine()",", важливо зрозуміти рівень, що знаходиться ",[3389,4409,4410],{},"нижче"," SQLAlchemy, — безпосередній Python-драйвер для PostgreSQL.",[3385,4413,4414,4417],{},[3389,4415,4416],{},"DB-API 2.0"," (PEP 249) — це стандартний Python-інтерфейс для роботи з реляційними базами даних. Він визначає мінімальний набір функцій і об'єктів, які має реалізувати будь-який Python-пакет для роботи з конкретною СУБД. Завдяки цьому стандарту SQLAlchemy може прозоро перемикатися між різними драйверами без зміни вашого коду.",[3385,4419,4420,4423,4424,3488,4426,3488,4428,4431],{},[3389,4421,4422],{},"Аналогія:"," DB-API 2.0 — це «стандартна розетка», а конкретні драйвери (",[3412,4425,3460],{},[3412,4427,3464],{},[3412,4429,4430],{},"psycopg",") — це «прилади», що підключаються до неї. SQLAlchemy — це «подовжувач», який надає вам зручний API поверх будь-якого «приладу».",[4433,4434,4436],"h4",{"id":4435},"як-виглядає-робота-з-драйвером-напряму","Як виглядає робота з драйвером напряму?",[3385,4438,4439,4440,4442],{},"Щоб по-справжньому оцінити, від чого абстрагує SQLAlchemy, подивимося на «голий» DB-API 2.0 з ",[3412,4441,3460],{},":",[3585,4444,4449],{"className":4445,"code":4446,"filename":4447,"language":4448,"meta":3590,"style":3590},"language-python shiki shiki-themes light-plus dark-plus dark-plus","import psycopg2\nfrom psycopg2.extras import RealDictCursor\n\n# 1. Відкриваємо підключення вручну\nconn = psycopg2.connect(\n    host=\"localhost\",\n    port=5432,\n    dbname=\"taskforge_db\",\n    user=\"user\",\n    password=\"password\",\n)\n\ntry:\n    # 2. Створюємо курсор (об'єкт для виконання запитів)\n    # RealDictCursor → результати у вигляді dict замість tuple\n    with conn.cursor(cursor_factory=RealDictCursor) as cur:\n\n        # 3. Виконуємо параметризований запит (захист від SQL-ін'єкцій)\n        cur.execute(\n            \"SELECT id, username, email FROM users WHERE is_active = %s LIMIT %s\",\n            (True, 10),  # Параметри — окремим кортежем, НЕ через f-string!\n        )\n\n        # 4. Отримуємо результати\n        rows = cur.fetchall()\n        for row in rows:\n            print(row[\"id\"], row[\"username\"])  # row — це dict\n\n        # 5. INSERT з поверненням згенерованого id\n        cur.execute(\n            \"INSERT INTO users (username, email) VALUES (%s, %s) RETURNING id\",\n            (\"new_user\", \"new@example.com\"),\n        )\n        new_id = cur.fetchone()[\"id\"]\n\n    # 6. Підтверджуємо транзакцію вручну\n    conn.commit()\n\nexcept Exception:\n    conn.rollback()  # Відкат при будь-якій помилці\n    raise\nfinally:\n    conn.close()  # Завжди закриваємо підключення\n","raw_dbapi_demo.py","python",[3412,4450,4451,4461,4474,4478,4484,4489,4505,4518,4530,4542,4554,4559,4563,4571,4576,4581,4601,4605,4610,4615,4634,4653,4658,4662,4667,4672,4686,4710,4714,4719,4723,4739,4754,4758,4768,4772,4777,4782,4786,4797,4805,4810,4817],{"__ignoreMap":3590},[3594,4452,4453,4457],{"class":3596,"line":3597},[3594,4454,4456],{"class":4455},"s8xlr","import",[3594,4458,4460],{"class":4459},"sHH4Y"," psycopg2\n",[3594,4462,4463,4466,4469,4471],{"class":3596,"line":3603},[3594,4464,4465],{"class":4455},"from",[3594,4467,4468],{"class":4459}," psycopg2.extras ",[3594,4470,4456],{"class":4455},[3594,4472,4473],{"class":4459}," RealDictCursor\n",[3594,4475,4476],{"class":3596,"line":3609},[3594,4477,3631],{"emptyLinePlaceholder":3630},[3594,4479,4480],{"class":3596,"line":3615},[3594,4481,4483],{"class":4482},"spJ8K","# 1. Відкриваємо підключення вручну\n",[3594,4485,4486],{"class":3596,"line":3621},[3594,4487,4488],{"class":4459},"conn = psycopg2.connect(\n",[3594,4490,4491,4495,4498,4502],{"class":3596,"line":3627},[3594,4492,4494],{"class":4493},"siwwj","    host",[3594,4496,4497],{"class":4459},"=",[3594,4499,4501],{"class":4500},"sbdoH","\"localhost\"",[3594,4503,4504],{"class":4459},",\n",[3594,4506,4507,4510,4512,4516],{"class":3596,"line":3634},[3594,4508,4509],{"class":4493},"    port",[3594,4511,4497],{"class":4459},[3594,4513,4515],{"class":4514},"sJj4R","5432",[3594,4517,4504],{"class":4459},[3594,4519,4520,4523,4525,4528],{"class":3596,"line":3640},[3594,4521,4522],{"class":4493},"    dbname",[3594,4524,4497],{"class":4459},[3594,4526,4527],{"class":4500},"\"taskforge_db\"",[3594,4529,4504],{"class":4459},[3594,4531,4532,4535,4537,4540],{"class":3596,"line":3646},[3594,4533,4534],{"class":4493},"    user",[3594,4536,4497],{"class":4459},[3594,4538,4539],{"class":4500},"\"user\"",[3594,4541,4504],{"class":4459},[3594,4543,4544,4547,4549,4552],{"class":3596,"line":3652},[3594,4545,4546],{"class":4493},"    password",[3594,4548,4497],{"class":4459},[3594,4550,4551],{"class":4500},"\"password\"",[3594,4553,4504],{"class":4459},[3594,4555,4556],{"class":3596,"line":3658},[3594,4557,4558],{"class":4459},")\n",[3594,4560,4561],{"class":3596,"line":3664},[3594,4562,3631],{"emptyLinePlaceholder":3630},[3594,4564,4565,4568],{"class":3596,"line":3670},[3594,4566,4567],{"class":4455},"try",[3594,4569,4570],{"class":4459},":\n",[3594,4572,4573],{"class":3596,"line":3676},[3594,4574,4575],{"class":4482},"    # 2. Створюємо курсор (об'єкт для виконання запитів)\n",[3594,4577,4578],{"class":3596,"line":3681},[3594,4579,4580],{"class":4482},"    # RealDictCursor → результати у вигляді dict замість tuple\n",[3594,4582,4583,4586,4589,4592,4595,4598],{"class":3596,"line":3687},[3594,4584,4585],{"class":4455},"    with",[3594,4587,4588],{"class":4459}," conn.cursor(",[3594,4590,4591],{"class":4493},"cursor_factory",[3594,4593,4594],{"class":4459},"=RealDictCursor) ",[3594,4596,4597],{"class":4455},"as",[3594,4599,4600],{"class":4459}," cur:\n",[3594,4602,4603],{"class":3596,"line":3693},[3594,4604,3631],{"emptyLinePlaceholder":3630},[3594,4606,4607],{"class":3596,"line":3699},[3594,4608,4609],{"class":4482},"        # 3. Виконуємо параметризований запит (захист від SQL-ін'єкцій)\n",[3594,4611,4612],{"class":3596,"line":3705},[3594,4613,4614],{"class":4459},"        cur.execute(\n",[3594,4616,4617,4620,4624,4627,4629,4632],{"class":3596,"line":3711},[3594,4618,4619],{"class":4500},"            \"SELECT id, username, email FROM users WHERE is_active = ",[3594,4621,4623],{"class":4622},"su1O8","%s",[3594,4625,4626],{"class":4500}," LIMIT ",[3594,4628,4623],{"class":4622},[3594,4630,4631],{"class":4500},"\"",[3594,4633,4504],{"class":4459},[3594,4635,4636,4639,4642,4644,4647,4650],{"class":3596,"line":3716},[3594,4637,4638],{"class":4459},"            (",[3594,4640,4641],{"class":4622},"True",[3594,4643,3488],{"class":4459},[3594,4645,4646],{"class":4514},"10",[3594,4648,4649],{"class":4459},"),  ",[3594,4651,4652],{"class":4482},"# Параметри — окремим кортежем, НЕ через f-string!\n",[3594,4654,4655],{"class":3596,"line":3721},[3594,4656,4657],{"class":4459},"        )\n",[3594,4659,4660],{"class":3596,"line":3727},[3594,4661,3631],{"emptyLinePlaceholder":3630},[3594,4663,4664],{"class":3596,"line":3733},[3594,4665,4666],{"class":4482},"        # 4. Отримуємо результати\n",[3594,4668,4669],{"class":3596,"line":3739},[3594,4670,4671],{"class":4459},"        rows = cur.fetchall()\n",[3594,4673,4674,4677,4680,4683],{"class":3596,"line":3744},[3594,4675,4676],{"class":4455},"        for",[3594,4678,4679],{"class":4459}," row ",[3594,4681,4682],{"class":4455},"in",[3594,4684,4685],{"class":4459}," rows:\n",[3594,4687,4688,4692,4695,4698,4701,4704,4707],{"class":3596,"line":3749},[3594,4689,4691],{"class":4690},"s8Opu","            print",[3594,4693,4694],{"class":4459},"(row[",[3594,4696,4697],{"class":4500},"\"id\"",[3594,4699,4700],{"class":4459},"], row[",[3594,4702,4703],{"class":4500},"\"username\"",[3594,4705,4706],{"class":4459},"])  ",[3594,4708,4709],{"class":4482},"# row — це dict\n",[3594,4711,4712],{"class":3596,"line":3755},[3594,4713,3631],{"emptyLinePlaceholder":3630},[3594,4715,4716],{"class":3596,"line":3761},[3594,4717,4718],{"class":4482},"        # 5. INSERT з поверненням згенерованого id\n",[3594,4720,4721],{"class":3596,"line":3766},[3594,4722,4614],{"class":4459},[3594,4724,4725,4728,4730,4732,4734,4737],{"class":3596,"line":3772},[3594,4726,4727],{"class":4500},"            \"INSERT INTO users (username, email) VALUES (",[3594,4729,4623],{"class":4622},[3594,4731,3488],{"class":4500},[3594,4733,4623],{"class":4622},[3594,4735,4736],{"class":4500},") RETURNING id\"",[3594,4738,4504],{"class":4459},[3594,4740,4741,4743,4746,4748,4751],{"class":3596,"line":3778},[3594,4742,4638],{"class":4459},[3594,4744,4745],{"class":4500},"\"new_user\"",[3594,4747,3488],{"class":4459},[3594,4749,4750],{"class":4500},"\"new@example.com\"",[3594,4752,4753],{"class":4459},"),\n",[3594,4755,4756],{"class":3596,"line":3784},[3594,4757,4657],{"class":4459},[3594,4759,4760,4763,4765],{"class":3596,"line":3790},[3594,4761,4762],{"class":4459},"        new_id = cur.fetchone()[",[3594,4764,4697],{"class":4500},[3594,4766,4767],{"class":4459},"]\n",[3594,4769,4770],{"class":3596,"line":3796},[3594,4771,3631],{"emptyLinePlaceholder":3630},[3594,4773,4774],{"class":3596,"line":3802},[3594,4775,4776],{"class":4482},"    # 6. Підтверджуємо транзакцію вручну\n",[3594,4778,4779],{"class":3596,"line":3808},[3594,4780,4781],{"class":4459},"    conn.commit()\n",[3594,4783,4784],{"class":3596,"line":3814},[3594,4785,3631],{"emptyLinePlaceholder":3630},[3594,4787,4788,4791,4795],{"class":3596,"line":3820},[3594,4789,4790],{"class":4455},"except",[3594,4792,4794],{"class":4793},"sN1BT"," Exception",[3594,4796,4570],{"class":4459},[3594,4798,4799,4802],{"class":3596,"line":3826},[3594,4800,4801],{"class":4459},"    conn.rollback()  ",[3594,4803,4804],{"class":4482},"# Відкат при будь-якій помилці\n",[3594,4806,4807],{"class":3596,"line":3832},[3594,4808,4809],{"class":4455},"    raise\n",[3594,4811,4812,4815],{"class":3596,"line":3837},[3594,4813,4814],{"class":4455},"finally",[3594,4816,4570],{"class":4459},[3594,4818,4820,4823],{"class":3596,"line":4819},43,[3594,4821,4822],{"class":4459},"    conn.close()  ",[3594,4824,4825],{"class":4482},"# Завжди закриваємо підключення\n",[3385,4827,4828],{},"Цей код працює, але очевидно, наскільки він багатослівний: ручне управління з'єднаннями, курсорами, транзакціями та перетворенням результатів — все це ваша відповідальність. SQLAlchemy автоматизує весь цей boilerplate.",[3385,4830,4831,4832,4834],{},"Асинхронний аналог із ",[3412,4833,3464],{}," виглядає інакше (asyncpg не реалізує DB-API 2.0, а має власний API, оптимізований для asyncio):",[3585,4836,4839],{"className":4445,"code":4837,"filename":4838,"language":4448,"meta":3590,"style":3590},"import asyncpg\n\nasync def main():\n    # asyncpg підключається напряму без курсорів\n    conn = await asyncpg.connect(\n        host=\"localhost\", port=5432,\n        database=\"taskforge_db\", user=\"user\", password=\"password\",\n    )\n    try:\n        # asyncpg повертає список Record-об'єктів (схожі на dict)\n        rows = await conn.fetch(\n            \"SELECT id, username FROM users WHERE is_active = $1 LIMIT $2\",\n            True, 10,  # Параметри через $1, $2 (PostgreSQL-стиль)\n        )\n        for row in rows:\n            print(row[\"id\"], row[\"username\"])\n\n        new_id = await conn.fetchval(\n            \"INSERT INTO users (username, email) VALUES ($1, $2) RETURNING id\",\n            \"new_user\", \"new@example.com\",\n        )\n    finally:\n        await conn.close()\n","raw_asyncpg_demo.py",[3412,4840,4841,4848,4852,4866,4871,4882,4902,4931,4936,4943,4948,4958,4965,4980,4984,4994,5009,5013,5023,5030,5041,5045,5052],{"__ignoreMap":3590},[3594,4842,4843,4845],{"class":3596,"line":3597},[3594,4844,4456],{"class":4455},[3594,4846,4847],{"class":4459}," asyncpg\n",[3594,4849,4850],{"class":3596,"line":3603},[3594,4851,3631],{"emptyLinePlaceholder":3630},[3594,4853,4854,4857,4860,4863],{"class":3596,"line":3609},[3594,4855,4856],{"class":4622},"async",[3594,4858,4859],{"class":4622}," def",[3594,4861,4862],{"class":4690}," main",[3594,4864,4865],{"class":4459},"():\n",[3594,4867,4868],{"class":3596,"line":3615},[3594,4869,4870],{"class":4482},"    # asyncpg підключається напряму без курсорів\n",[3594,4872,4873,4876,4879],{"class":3596,"line":3621},[3594,4874,4875],{"class":4459},"    conn = ",[3594,4877,4878],{"class":4455},"await",[3594,4880,4881],{"class":4459}," asyncpg.connect(\n",[3594,4883,4884,4887,4889,4891,4893,4896,4898,4900],{"class":3596,"line":3627},[3594,4885,4886],{"class":4493},"        host",[3594,4888,4497],{"class":4459},[3594,4890,4501],{"class":4500},[3594,4892,3488],{"class":4459},[3594,4894,4895],{"class":4493},"port",[3594,4897,4497],{"class":4459},[3594,4899,4515],{"class":4514},[3594,4901,4504],{"class":4459},[3594,4903,4904,4907,4909,4911,4913,4916,4918,4920,4922,4925,4927,4929],{"class":3596,"line":3634},[3594,4905,4906],{"class":4493},"        database",[3594,4908,4497],{"class":4459},[3594,4910,4527],{"class":4500},[3594,4912,3488],{"class":4459},[3594,4914,4915],{"class":4493},"user",[3594,4917,4497],{"class":4459},[3594,4919,4539],{"class":4500},[3594,4921,3488],{"class":4459},[3594,4923,4924],{"class":4493},"password",[3594,4926,4497],{"class":4459},[3594,4928,4551],{"class":4500},[3594,4930,4504],{"class":4459},[3594,4932,4933],{"class":3596,"line":3640},[3594,4934,4935],{"class":4459},"    )\n",[3594,4937,4938,4941],{"class":3596,"line":3646},[3594,4939,4940],{"class":4455},"    try",[3594,4942,4570],{"class":4459},[3594,4944,4945],{"class":3596,"line":3652},[3594,4946,4947],{"class":4482},"        # asyncpg повертає список Record-об'єктів (схожі на dict)\n",[3594,4949,4950,4953,4955],{"class":3596,"line":3658},[3594,4951,4952],{"class":4459},"        rows = ",[3594,4954,4878],{"class":4455},[3594,4956,4957],{"class":4459}," conn.fetch(\n",[3594,4959,4960,4963],{"class":3596,"line":3664},[3594,4961,4962],{"class":4500},"            \"SELECT id, username FROM users WHERE is_active = $1 LIMIT $2\"",[3594,4964,4504],{"class":4459},[3594,4966,4967,4970,4972,4974,4977],{"class":3596,"line":3670},[3594,4968,4969],{"class":4622},"            True",[3594,4971,3488],{"class":4459},[3594,4973,4646],{"class":4514},[3594,4975,4976],{"class":4459},",  ",[3594,4978,4979],{"class":4482},"# Параметри через $1, $2 (PostgreSQL-стиль)\n",[3594,4981,4982],{"class":3596,"line":3676},[3594,4983,4657],{"class":4459},[3594,4985,4986,4988,4990,4992],{"class":3596,"line":3681},[3594,4987,4676],{"class":4455},[3594,4989,4679],{"class":4459},[3594,4991,4682],{"class":4455},[3594,4993,4685],{"class":4459},[3594,4995,4996,4998,5000,5002,5004,5006],{"class":3596,"line":3687},[3594,4997,4691],{"class":4690},[3594,4999,4694],{"class":4459},[3594,5001,4697],{"class":4500},[3594,5003,4700],{"class":4459},[3594,5005,4703],{"class":4500},[3594,5007,5008],{"class":4459},"])\n",[3594,5010,5011],{"class":3596,"line":3693},[3594,5012,3631],{"emptyLinePlaceholder":3630},[3594,5014,5015,5018,5020],{"class":3596,"line":3699},[3594,5016,5017],{"class":4459},"        new_id = ",[3594,5019,4878],{"class":4455},[3594,5021,5022],{"class":4459}," conn.fetchval(\n",[3594,5024,5025,5028],{"class":3596,"line":3705},[3594,5026,5027],{"class":4500},"            \"INSERT INTO users (username, email) VALUES ($1, $2) RETURNING id\"",[3594,5029,4504],{"class":4459},[3594,5031,5032,5035,5037,5039],{"class":3596,"line":3711},[3594,5033,5034],{"class":4500},"            \"new_user\"",[3594,5036,3488],{"class":4459},[3594,5038,4750],{"class":4500},[3594,5040,4504],{"class":4459},[3594,5042,5043],{"class":3596,"line":3716},[3594,5044,4657],{"class":4459},[3594,5046,5047,5050],{"class":3596,"line":3721},[3594,5048,5049],{"class":4455},"    finally",[3594,5051,4570],{"class":4459},[3594,5053,5054,5057],{"class":3596,"line":3727},[3594,5055,5056],{"class":4455},"        await",[3594,5058,5059],{"class":4459}," conn.close()\n",[3417,5061,5062,5063,5065,5066,5068,5069,5072,5073,5065,5075,5078],{},"Зверніть на різницю у плейсхолдерах: ",[3412,5064,3460],{}," використовує ",[3412,5067,4623],{}," (стиль Python ",[3412,5070,5071],{},"printf","), тоді як ",[3412,5074,3464],{},[3412,5076,5077],{},"$1, $2, $3"," (нативний PostgreSQL-стиль). SQLAlchemy абстрагує цю різницю — ви завжди пишете однаковий Python-код, незалежно від драйвера.",[4433,5080,5082],{"id":5081},"які-драйвери-обирати-у-2026-році","Які драйвери обирати у 2026+ році?",[3385,5084,5085],{},"На сьогодні для PostgreSQL існує три основних варіанти драйверів. Ось актуальна картина:",[3986,5087,5088,5104],{},[3989,5089,5090],{},[3992,5091,5092,5095,5098,5101],{},[3995,5093,5094],{"align":3997},"Драйвер",[3995,5096,5097],{"align":3997},"Тип",[3995,5099,5100],{"align":3997},"Статус",[3995,5102,5103],{"align":3997},"Коли використовувати",[4006,5105,5106,5121,5140],{},[3992,5107,5108,5112,5115,5118],{},[4011,5109,5110],{"align":3997},[3389,5111,3460],{},[4011,5113,5114],{"align":3997},"Синхронний",[4011,5116,5117],{"align":3997},"Стабільний, але legacy",[4011,5119,5120],{"align":3997},"Старі проєкти, де вже використовується. Нові проєкти — краще уникати.",[3992,5122,5123,5128,5131,5134],{},[4011,5124,5125,5127],{"align":3997},[3389,5126,4430],{}," (v3)",[4011,5129,5130],{"align":3997},"Sync + Async",[4011,5132,5133],{"align":3997},"✅ Актуальний",[4011,5135,5136,5139],{"align":3997},[3389,5137,5138],{},"Рекомендований вибір для синхронних проєктів."," Прямий наступник psycopg2.",[3992,5141,5142,5146,5149,5151],{},[4011,5143,5144],{"align":3997},[3389,5145,3464],{},[4011,5147,5148],{"align":3997},"Асинхронний",[4011,5150,5133],{"align":3997},[4011,5152,5153,5156],{"align":3997},[3389,5154,5155],{},"Рекомендований вибір для async FastAPI."," Найшвидший async-драйвер.",[3503,5158,5159,5187],{},[3506,5160,5161,5170],{"icon":92,"title":3464},[3385,5162,5163,5166,5167,3439],{},[3389,5164,5165],{},"Рекомендований для async FastAPI.","\nНаписаний на Cython, використовує бінарний PostgreSQL-протокол. За бенчмарками в 3–5 разів швидший за psycopg2. Не реалізує DB-API 2.0, зате надає зручний нативний async API. SQLAlchemy підтримує через ",[3412,5168,5169],{},"postgresql+asyncpg:\u002F\u002F",[3585,5171,5175],{"className":5172,"code":5173,"language":5174,"meta":3590,"style":3590},"language-bash shiki shiki-themes light-plus dark-plus dark-plus","pip install asyncpg\n","bash",[3412,5176,5177],{"__ignoreMap":3590},[3594,5178,5179,5182,5185],{"class":3596,"line":3597},[3594,5180,5181],{"class":4690},"pip",[3594,5183,5184],{"class":4500}," install",[3594,5186,4847],{"class":4500},[3506,5188,5191,5201,5215],{"icon":5189,"title":5190},"i-lucide-check-circle","psycopg (v3)",[3385,5192,5193,5196,5197,5200],{},[3389,5194,5195],{},"Рекомендований для синхронних проєктів або міграції з psycopg2.","\nПідтримує як sync, так і async режими в одному пакеті. Реалізує оновлений DB-API 2.0. Значно продуктивніший за psycopg2. SQLAlchemy підтримує через ",[3412,5198,5199],{},"postgresql+psycopg:\u002F\u002F"," (без цифри 2).",[3585,5202,5204],{"className":5172,"code":5203,"language":5174,"meta":3590,"style":3590},"pip install \"psycopg[binary]\"\n",[3412,5205,5206],{"__ignoreMap":3590},[3594,5207,5208,5210,5212],{"class":3596,"line":3597},[3594,5209,5181],{"class":4690},[3594,5211,5184],{"class":4500},[3594,5213,5214],{"class":4500}," \"psycopg[binary]\"\n",[5216,5217,5218,5221,5237],"tip",{},[3385,5219,5220],{},"Для нового FastAPI-проєкту з async SQLAlchemy — стандартна комбінація:",[3585,5222,5224],{"className":5172,"code":5223,"language":5174,"meta":3590,"style":3590},"pip install sqlalchemy asyncpg\n",[3412,5225,5226],{"__ignoreMap":3590},[3594,5227,5228,5230,5232,5235],{"class":3596,"line":3597},[3594,5229,5181],{"class":4690},[3594,5231,5184],{"class":4500},[3594,5233,5234],{"class":4500}," sqlalchemy",[3594,5236,4847],{"class":4500},[3385,5238,5239,5240],{},"DSN: ",[3412,5241,5242],{},"postgresql+asyncpg:\u002F\u002Fuser:password@localhost\u002Fdbname",[3441,5244],{},[4433,5246,5248],{"id":5247},"які-бази-даних-підтримує-sqlalchemy-з-коробки","Які бази даних підтримує SQLAlchemy «з коробки»?",[3385,5250,5251,5252,5255,5256,5259],{},"SQLAlchemy не прив'язана до PostgreSQL. Офіційна документація визначає п'ять ",[3389,5253,5254],{},"вбудованих (included) діалектів"," — тобто БД, для яких підтримка вбудована безпосередньо в пакет ",[3412,5257,5258],{},"sqlalchemy"," без встановлення сторонніх розширень.",[3417,5261,5262,5265,5266,5269,5270,5273,5274,5276,5277,5280],{},[3389,5263,5264],{},"Діалект"," (dialect) у термінах SQLAlchemy — це модуль, що «перекладає» узагальнений SQLAlchemy-SQL у SQL-синтаксис конкретної СУБД. Наприклад, функція ",[3412,5267,5268],{},"func.now()"," у PostgreSQL стає ",[3412,5271,5272],{},"NOW()",", а у MySQL — ",[3412,5275,5272],{}," чи ",[3412,5278,5279],{},"SYSDATE()"," залежно від версії. Ваш Python-код залишається незмінним.",[3986,5282,5283,5299],{},[3989,5284,5285],{},[3992,5286,5287,5290,5293,5296],{},[3995,5288,5289],{"align":3997},"СУБД",[3995,5291,5292],{"align":3997},"SQLAlchemy DSN-префікс",[3995,5294,5295],{"align":3997},"Sync-драйвери",[3995,5297,5298],{"align":3997},"Async-драйвери",[4006,5300,5301,5326,5354,5377,5401],{},[3992,5302,5303,5308,5313,5319],{},[4011,5304,5305],{"align":3997},[3389,5306,5307],{},"PostgreSQL",[4011,5309,5310],{"align":3997},[3412,5311,5312],{},"postgresql+\u003Cdriver>:\u002F\u002F",[4011,5314,5315,3488,5317],{"align":3997},[3412,5316,3460],{},[3412,5318,4430],{},[4011,5320,5321,3488,5323,5325],{"align":3997},[3412,5322,3464],{},[3412,5324,4430],{}," (async)",[3992,5327,5328,5333,5338,5346],{},[4011,5329,5330],{"align":3997},[3389,5331,5332],{},"MySQL \u002F MariaDB",[4011,5334,5335],{"align":3997},[3412,5336,5337],{},"mysql+\u003Cdriver>:\u002F\u002F",[4011,5339,5340,3488,5343],{"align":3997},[3412,5341,5342],{},"mysqlclient",[3412,5344,5345],{},"PyMySQL",[4011,5347,5348,3488,5351],{"align":3997},[3412,5349,5350],{},"asyncmy",[3412,5352,5353],{},"aiomysql",[3992,5355,5356,5361,5366,5372],{},[4011,5357,5358],{"align":3997},[3389,5359,5360],{},"SQLite",[4011,5362,5363],{"align":3997},[3412,5364,5365],{},"sqlite+\u003Cdriver>:\u002F\u002F\u002F",[4011,5367,5368,5369],{"align":3997},"вбудований ",[3412,5370,5371],{},"sqlite3",[4011,5373,5374],{"align":3997},[3412,5375,5376],{},"aiosqlite",[3992,5378,5379,5384,5389,5397],{},[4011,5380,5381],{"align":3997},[3389,5382,5383],{},"Oracle Database",[4011,5385,5386],{"align":3997},[3412,5387,5388],{},"oracle+\u003Cdriver>:\u002F\u002F",[4011,5390,5391,3488,5394],{"align":3997},[3412,5392,5393],{},"cx_Oracle",[3412,5395,5396],{},"oracledb",[4011,5398,5399,5325],{"align":3997},[3412,5400,5396],{},[3992,5402,5403,5408,5413,5421],{},[4011,5404,5405],{"align":3997},[3389,5406,5407],{},"Microsoft SQL Server",[4011,5409,5410],{"align":3997},[3412,5411,5412],{},"mssql+\u003Cdriver>:\u002F\u002F",[4011,5414,5415,3488,5418],{"align":3997},[3412,5416,5417],{},"pyodbc",[3412,5419,5420],{},"pymssql",[4011,5422,5423],{"align":3997},[3412,5424,5425],{},"aioodbc",[3385,5427,5428],{},"Розглянемо кожну БД детальніше.",[5430,5431,5433],"h5",{"id":5432},"mysql-та-mariadb","MySQL та MariaDB",[3385,5435,5436,5439,5440,5443,5444,5446],{},[3389,5437,5438],{},"MySQL"," та його повністю сумісний форк ",[3389,5441,5442],{},"MariaDB"," є найпоширенішими реляційними СУБД у веброзробці. SQLAlchemy підтримує обидві через однаковий ",[3412,5445,5337],{}," префікс (MariaDB автоматично визначається за версією сервера).",[5448,5449,5450,5480,5510],"code-group",{},[3585,5451,5454],{"className":4445,"code":5452,"filename":5453,"language":4448,"meta":3590,"style":3590},"# mysqlclient: найшвидший синхронний драйвер для MySQL (C-розширення)\n# pip install mysqlclient\nengine = create_engine(\n    \"mysql+mysqldb:\u002F\u002Fuser:password@localhost:3306\u002Fmydb?charset=utf8mb4\"\n)\n","MySQL — sync (mysqlclient)",[3412,5455,5456,5461,5466,5471,5476],{"__ignoreMap":3590},[3594,5457,5458],{"class":3596,"line":3597},[3594,5459,5460],{"class":4482},"# mysqlclient: найшвидший синхронний драйвер для MySQL (C-розширення)\n",[3594,5462,5463],{"class":3596,"line":3603},[3594,5464,5465],{"class":4482},"# pip install mysqlclient\n",[3594,5467,5468],{"class":3596,"line":3609},[3594,5469,5470],{"class":4459},"engine = create_engine(\n",[3594,5472,5473],{"class":3596,"line":3615},[3594,5474,5475],{"class":4500},"    \"mysql+mysqldb:\u002F\u002Fuser:password@localhost:3306\u002Fmydb?charset=utf8mb4\"\n",[3594,5477,5478],{"class":3596,"line":3621},[3594,5479,4558],{"class":4459},[3585,5481,5484],{"className":4445,"code":5482,"filename":5483,"language":4448,"meta":3590,"style":3590},"# asyncmy: сучасний async-драйвер для MySQL\u002FMariaDB\n# pip install asyncmy\nasync_engine = create_async_engine(\n    \"mysql+asyncmy:\u002F\u002Fuser:password@localhost:3306\u002Fmydb?charset=utf8mb4\"\n)\n","MySQL — async (asyncmy)",[3412,5485,5486,5491,5496,5501,5506],{"__ignoreMap":3590},[3594,5487,5488],{"class":3596,"line":3597},[3594,5489,5490],{"class":4482},"# asyncmy: сучасний async-драйвер для MySQL\u002FMariaDB\n",[3594,5492,5493],{"class":3596,"line":3603},[3594,5494,5495],{"class":4482},"# pip install asyncmy\n",[3594,5497,5498],{"class":3596,"line":3609},[3594,5499,5500],{"class":4459},"async_engine = create_async_engine(\n",[3594,5502,5503],{"class":3596,"line":3615},[3594,5504,5505],{"class":4500},"    \"mysql+asyncmy:\u002F\u002Fuser:password@localhost:3306\u002Fmydb?charset=utf8mb4\"\n",[3594,5507,5508],{"class":3596,"line":3621},[3594,5509,4558],{"class":4459},[3585,5511,5514],{"className":4445,"code":5512,"filename":5513,"language":4448,"meta":3590,"style":3590},"# PyMySQL: pure-Python драйвер, простий у встановленні (без компіляції)\n# pip install pymysql\nengine = create_engine(\n    \"mysql+pymysql:\u002F\u002Fuser:password@localhost:3306\u002Fmydb?charset=utf8mb4\"\n)\n","MariaDB — sync (PyMySQL)",[3412,5515,5516,5521,5526,5530,5535],{"__ignoreMap":3590},[3594,5517,5518],{"class":3596,"line":3597},[3594,5519,5520],{"class":4482},"# PyMySQL: pure-Python драйвер, простий у встановленні (без компіляції)\n",[3594,5522,5523],{"class":3596,"line":3603},[3594,5524,5525],{"class":4482},"# pip install pymysql\n",[3594,5527,5528],{"class":3596,"line":3609},[3594,5529,5470],{"class":4459},[3594,5531,5532],{"class":3596,"line":3615},[3594,5533,5534],{"class":4500},"    \"mysql+pymysql:\u002F\u002Fuser:password@localhost:3306\u002Fmydb?charset=utf8mb4\"\n",[3594,5536,5537],{"class":3596,"line":3621},[3594,5538,4558],{"class":4459},[5540,5541,5542,5545],"warning",{},[3385,5543,5544],{},"MySQL та MariaDB мають кілька важливих відмінностей від PostgreSQL, які варто знати:",[3510,5546,5547,5558,5570],{},[3455,5548,5549,5550,5553,5554,5557],{},"За замовчуванням ",[3389,5551,5552],{},"рушій InnoDB"," у MySQL підтримує транзакції та зовнішні ключі. Старий ",[3412,5555,5556],{},"MyISAM"," — не підтримує. SQLAlchemy очікує InnoDB.",[3455,5559,5560,5565,5566,5569],{},[3389,5561,5562],{},[3412,5563,5564],{},"BOOLEAN"," у MySQL зберігається як ",[3412,5567,5568],{},"TINYINT(1)"," (0\u002F1), а не справжній булевий тип.",[3455,5571,5572,5575,5576,4025,5579,5582,5583,3439],{},[3412,5573,5574],{},"AUTO_INCREMENT"," замість PostgreSQL ",[3412,5577,5578],{},"SERIAL",[3412,5580,5581],{},"GENERATED ALWAYS AS IDENTITY"," — SQLAlchemy абстрагує це автоматично через ",[3412,5584,5585],{},"primary_key=True",[5430,5587,5360],{"id":5588},"sqlite",[3385,5590,5591,5593,5594,5597,5598,5601],{},[3389,5592,5360],{}," — вбудована у Python (",[3412,5595,5596],{},"import sqlite3",") легковажна СУБД без сервера: вся база даних зберігається в одному ",[3412,5599,5600],{},".db","-файлі на диску. Ідеально підходить для локальної розробки, тестів та невеликих проєктів.",[5448,5603,5604,5650],{},[3585,5605,5608],{"className":4445,"code":5606,"filename":5607,"language":4448,"meta":3590,"style":3590},"# Не потрібен додатковий пакет — sqlite3 вбудований у Python\nengine = create_engine(\n    \"sqlite:\u002F\u002F\u002F.\u002Ftaskforge_dev.db\"  # відносний шлях до файлу\n)\n\n# In-memory БД (існує лише у RAM, зникає при закритті):\nengine_memory = create_engine(\"sqlite:\u002F\u002F\u002F:memory:\")\n","SQLite — sync (вбудований)",[3412,5609,5610,5615,5619,5627,5631,5635,5640],{"__ignoreMap":3590},[3594,5611,5612],{"class":3596,"line":3597},[3594,5613,5614],{"class":4482},"# Не потрібен додатковий пакет — sqlite3 вбудований у Python\n",[3594,5616,5617],{"class":3596,"line":3603},[3594,5618,5470],{"class":4459},[3594,5620,5621,5624],{"class":3596,"line":3609},[3594,5622,5623],{"class":4500},"    \"sqlite:\u002F\u002F\u002F.\u002Ftaskforge_dev.db\"",[3594,5625,5626],{"class":4482},"  # відносний шлях до файлу\n",[3594,5628,5629],{"class":3596,"line":3615},[3594,5630,4558],{"class":4459},[3594,5632,5633],{"class":3596,"line":3621},[3594,5634,3631],{"emptyLinePlaceholder":3630},[3594,5636,5637],{"class":3596,"line":3627},[3594,5638,5639],{"class":4482},"# In-memory БД (існує лише у RAM, зникає при закритті):\n",[3594,5641,5642,5645,5648],{"class":3596,"line":3634},[3594,5643,5644],{"class":4459},"engine_memory = create_engine(",[3594,5646,5647],{"class":4500},"\"sqlite:\u002F\u002F\u002F:memory:\"",[3594,5649,4558],{"class":4459},[3585,5651,5654],{"className":4445,"code":5652,"filename":5653,"language":4448,"meta":3590,"style":3590},"# aiosqlite: async-обгортка над sqlite3\n# pip install aiosqlite\nasync_engine = create_async_engine(\n    \"sqlite+aiosqlite:\u002F\u002F\u002F.\u002Ftaskforge_dev.db\"\n)\n\n# Async in-memory:\nasync_engine_memory = create_async_engine(\"sqlite+aiosqlite:\u002F\u002F\u002F:memory:\")\n","SQLite — async (aiosqlite)",[3412,5655,5656,5661,5666,5670,5675,5679,5683,5688],{"__ignoreMap":3590},[3594,5657,5658],{"class":3596,"line":3597},[3594,5659,5660],{"class":4482},"# aiosqlite: async-обгортка над sqlite3\n",[3594,5662,5663],{"class":3596,"line":3603},[3594,5664,5665],{"class":4482},"# pip install aiosqlite\n",[3594,5667,5668],{"class":3596,"line":3609},[3594,5669,5500],{"class":4459},[3594,5671,5672],{"class":3596,"line":3615},[3594,5673,5674],{"class":4500},"    \"sqlite+aiosqlite:\u002F\u002F\u002F.\u002Ftaskforge_dev.db\"\n",[3594,5676,5677],{"class":3596,"line":3621},[3594,5678,4558],{"class":4459},[3594,5680,5681],{"class":3596,"line":3627},[3594,5682,3631],{"emptyLinePlaceholder":3630},[3594,5684,5685],{"class":3596,"line":3634},[3594,5686,5687],{"class":4482},"# Async in-memory:\n",[3594,5689,5690,5693,5696],{"class":3596,"line":3640},[3594,5691,5692],{"class":4459},"async_engine_memory = create_async_engine(",[3594,5694,5695],{"class":4500},"\"sqlite+aiosqlite:\u002F\u002F\u002F:memory:\"",[3594,5697,4558],{"class":4459},[5216,5699,5700,5701,5704,5705,5708,5709,5712,5713,5716],{},"SQLite — стандартний вибір для ",[3389,5702,5703],{},"тестів"," у FastAPI-проєктах: не потрібен Docker чи реальний PostgreSQL. Просто підміняйте ",[3412,5706,5707],{},"DATABASE_URL"," через змінну середовища в ",[3412,5710,5711],{},"conftest.py",". In-memory SQLite (",[3412,5714,5715],{},"sqlite:\u002F\u002F\u002F:memory:",") робить тести максимально ізольованими — кожен тест починає з чистої бази.",[5430,5718,5720],{"id":5719},"microsoft-sql-server-mssql","Microsoft SQL Server (MSSQL)",[3385,5722,5723,5725],{},[3389,5724,5407],{}," широко використовується у корпоративному середовищі (особливо там, де вже є інфраструктура Microsoft). SQLAlchemy підтримує його через ODBC-підключення.",[3585,5727,5730],{"className":4445,"code":5728,"filename":5729,"language":4448,"meta":3590,"style":3590},"# pip install pyodbc\n# Вимагає встановленого ODBC Driver for SQL Server на ОС\nengine = create_engine(\n    \"mssql+pyodbc:\u002F\u002Fuser:password@mssql-server:1433\u002Fmydb\"\n    \"?driver=ODBC+Driver+18+for+SQL+Server\"\n    \"&TrustServerCertificate=yes\"\n)\n","MSSQL — sync (pyodbc)",[3412,5731,5732,5737,5742,5746,5751,5756,5761],{"__ignoreMap":3590},[3594,5733,5734],{"class":3596,"line":3597},[3594,5735,5736],{"class":4482},"# pip install pyodbc\n",[3594,5738,5739],{"class":3596,"line":3603},[3594,5740,5741],{"class":4482},"# Вимагає встановленого ODBC Driver for SQL Server на ОС\n",[3594,5743,5744],{"class":3596,"line":3609},[3594,5745,5470],{"class":4459},[3594,5747,5748],{"class":3596,"line":3615},[3594,5749,5750],{"class":4500},"    \"mssql+pyodbc:\u002F\u002Fuser:password@mssql-server:1433\u002Fmydb\"\n",[3594,5752,5753],{"class":3596,"line":3621},[3594,5754,5755],{"class":4500},"    \"?driver=ODBC+Driver+18+for+SQL+Server\"\n",[3594,5757,5758],{"class":3596,"line":3627},[3594,5759,5760],{"class":4500},"    \"&TrustServerCertificate=yes\"\n",[3594,5762,5763],{"class":3596,"line":3634},[3594,5764,4558],{"class":4459},[3585,5766,5769],{"className":4445,"code":5767,"filename":5768,"language":4448,"meta":3590,"style":3590},"# pip install aioodbc\nfrom sqlalchemy.ext.asyncio import create_async_engine\n\nasync_engine = create_async_engine(\n    \"mssql+aioodbc:\u002F\u002Fuser:password@mssql-server:1433\u002Fmydb\"\n    \"?driver=ODBC+Driver+18+for+SQL+Server\"\n    \"&TrustServerCertificate=yes\"\n)\n","MSSQL — async (aioodbc)",[3412,5770,5771,5776,5788,5792,5796,5801,5805,5809],{"__ignoreMap":3590},[3594,5772,5773],{"class":3596,"line":3597},[3594,5774,5775],{"class":4482},"# pip install aioodbc\n",[3594,5777,5778,5780,5783,5785],{"class":3596,"line":3603},[3594,5779,4465],{"class":4455},[3594,5781,5782],{"class":4459}," sqlalchemy.ext.asyncio ",[3594,5784,4456],{"class":4455},[3594,5786,5787],{"class":4459}," create_async_engine\n",[3594,5789,5790],{"class":3596,"line":3609},[3594,5791,3631],{"emptyLinePlaceholder":3630},[3594,5793,5794],{"class":3596,"line":3615},[3594,5795,5500],{"class":4459},[3594,5797,5798],{"class":3596,"line":3621},[3594,5799,5800],{"class":4500},"    \"mssql+aioodbc:\u002F\u002Fuser:password@mssql-server:1433\u002Fmydb\"\n",[3594,5802,5803],{"class":3596,"line":3627},[3594,5804,5755],{"class":4500},[3594,5806,5807],{"class":3596,"line":3634},[3594,5808,5760],{"class":4500},[3594,5810,5811],{"class":3596,"line":3640},[3594,5812,4558],{"class":4459},[3417,5814,5815,5816,5819,5820,3488,5822,5819,5825,5828,5829,5819,5832,5835,5836,5839],{},"SQL Server відрізняється від PostgreSQL кількома деталями: використовує ",[3412,5817,5818],{},"IDENTITY"," замість ",[3412,5821,5578],{},[3412,5823,5824],{},"NVARCHAR",[3412,5826,5827],{},"VARCHAR"," для Unicode, та ",[3412,5830,5831],{},"TOP N",[3412,5833,5834],{},"LIMIT N",". SQLAlchemy повністю приховує ці відмінності — ваш Python-код з ",[3412,5837,5838],{},"select(User).limit(10)"," однаково коректно виконається і на PostgreSQL, і на SQL Server.",[5430,5841,5843],{"id":5842},"зведена-таблиця-вибору-бд-та-драйвера-для-нових-проєктів","Зведена таблиця вибору БД та драйвера для нових проєктів",[3986,5845,5846,5860],{},[3989,5847,5848],{},[3992,5849,5850,5853,5855,5857],{},[3995,5851,5852],{"align":3997},"Сценарій",[3995,5854,5289],{"align":3997},[3995,5856,5094],{"align":3997},[3995,5858,5859],{"align":3997},"DSN",[4006,5861,5862,5879,5896,5914,5932,5953],{},[3992,5863,5864,5867,5871,5875],{},[4011,5865,5866],{"align":3997},"FastAPI production (async)",[4011,5868,5869],{"align":3997},[3389,5870,5307],{},[4011,5872,5873],{"align":3997},[3412,5874,3464],{},[4011,5876,5877],{"align":3997},[3412,5878,5169],{},[3992,5880,5881,5884,5888,5892],{},[4011,5882,5883],{"align":3997},"FastAPI production (sync)",[4011,5885,5886],{"align":3997},[3389,5887,5307],{},[4011,5889,5890,5127],{"align":3997},[3412,5891,4430],{},[4011,5893,5894],{"align":3997},[3412,5895,5199],{},[3992,5897,5898,5901,5905,5909],{},[4011,5899,5900],{"align":3997},"Локальна розробка \u002F тести",[4011,5902,5903],{"align":3997},[3389,5904,5360],{},[4011,5906,5907],{"align":3997},[3412,5908,5376],{},[4011,5910,5911],{"align":3997},[3412,5912,5913],{},"sqlite+aiosqlite:\u002F\u002F\u002F.\u002Fdev.db",[3992,5915,5916,5919,5923,5927],{},[4011,5917,5918],{"align":3997},"Unit-тести (in-memory)",[4011,5920,5921],{"align":3997},[3389,5922,5360],{},[4011,5924,5925],{"align":3997},[3412,5926,5376],{},[4011,5928,5929],{"align":3997},[3412,5930,5931],{},"sqlite+aiosqlite:\u002F\u002F\u002F:memory:",[3992,5933,5934,5937,5942,5948],{},[4011,5935,5936],{"align":3997},"Корпоративний стек Microsoft",[4011,5938,5939],{"align":3997},[3389,5940,5941],{},"MSSQL",[4011,5943,5944,4025,5946],{"align":3997},[3412,5945,5417],{},[3412,5947,5425],{},[4011,5949,5950],{"align":3997},[3412,5951,5952],{},"mssql+pyodbc:\u002F\u002F",[3992,5954,5955,5958,5963,5967],{},[4011,5956,5957],{"align":3997},"Спадковий стек \u002F хостинги",[4011,5959,5960],{"align":3997},[3389,5961,5962],{},"MySQL\u002FMariaDB",[4011,5964,5965],{"align":3997},[3412,5966,5350],{},[4011,5968,5969],{"align":3997},[3412,5970,5971],{},"mysql+asyncmy:\u002F\u002F",[3845,5973,5975,5976],{"id":5974},"синхронний-engine-create_engine","Синхронний Engine: ",[3412,5977,4406],{},[3385,5979,5980,5981,5983],{},"Функція ",[3412,5982,4406],{}," приймає рядок підключення (DSN — Data Source Name) та низку опцій, що конфігурують поведінку пулу.",[3585,5985,5988],{"className":4445,"code":5986,"filename":5987,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import create_engine\n\n# Рядок підключення (DSN) для PostgreSQL з psycopg2\nDATABASE_URL = \"postgresql+psycopg2:\u002F\u002Fuser:password@localhost:5432\u002Ftaskforge_db\"\n\nengine = create_engine(\n    DATABASE_URL,\n    # --- Параметри Connection Pool ---\n    pool_size=10,       # Кількість «постійних» підключень у пулі\n    max_overflow=20,    # Максимум тимчасових підключень понад pool_size\n    pool_timeout=30,    # Секунд очікування вільного підключення з пулу\n    pool_recycle=1800,  # Перестворювати підключення кожні 30 хвилин\n                        # (щоб уникнути \"stale connections\" від PostgreSQL)\n    # --- Налагодження ---\n    echo=True,          # Виводити всі SQL-запити у консоль (лише для розробки!)\n)\n","core\u002Fdatabase.py",[3412,5989,5990,6002,6006,6011,6019,6023,6027,6032,6037,6052,6068,6083,6098,6103,6108,6123],{"__ignoreMap":3590},[3594,5991,5992,5994,5997,5999],{"class":3596,"line":3597},[3594,5993,4465],{"class":4455},[3594,5995,5996],{"class":4459}," sqlalchemy ",[3594,5998,4456],{"class":4455},[3594,6000,6001],{"class":4459}," create_engine\n",[3594,6003,6004],{"class":3596,"line":3603},[3594,6005,3631],{"emptyLinePlaceholder":3630},[3594,6007,6008],{"class":3596,"line":3609},[3594,6009,6010],{"class":4482},"# Рядок підключення (DSN) для PostgreSQL з psycopg2\n",[3594,6012,6013,6016],{"class":3596,"line":3615},[3594,6014,6015],{"class":4459},"DATABASE_URL = ",[3594,6017,6018],{"class":4500},"\"postgresql+psycopg2:\u002F\u002Fuser:password@localhost:5432\u002Ftaskforge_db\"\n",[3594,6020,6021],{"class":3596,"line":3621},[3594,6022,3631],{"emptyLinePlaceholder":3630},[3594,6024,6025],{"class":3596,"line":3627},[3594,6026,5470],{"class":4459},[3594,6028,6029],{"class":3596,"line":3634},[3594,6030,6031],{"class":4459},"    DATABASE_URL,\n",[3594,6033,6034],{"class":3596,"line":3640},[3594,6035,6036],{"class":4482},"    # --- Параметри Connection Pool ---\n",[3594,6038,6039,6042,6044,6046,6049],{"class":3596,"line":3646},[3594,6040,6041],{"class":4493},"    pool_size",[3594,6043,4497],{"class":4459},[3594,6045,4646],{"class":4514},[3594,6047,6048],{"class":4459},",       ",[3594,6050,6051],{"class":4482},"# Кількість «постійних» підключень у пулі\n",[3594,6053,6054,6057,6059,6062,6065],{"class":3596,"line":3652},[3594,6055,6056],{"class":4493},"    max_overflow",[3594,6058,4497],{"class":4459},[3594,6060,6061],{"class":4514},"20",[3594,6063,6064],{"class":4459},",    ",[3594,6066,6067],{"class":4482},"# Максимум тимчасових підключень понад pool_size\n",[3594,6069,6070,6073,6075,6078,6080],{"class":3596,"line":3658},[3594,6071,6072],{"class":4493},"    pool_timeout",[3594,6074,4497],{"class":4459},[3594,6076,6077],{"class":4514},"30",[3594,6079,6064],{"class":4459},[3594,6081,6082],{"class":4482},"# Секунд очікування вільного підключення з пулу\n",[3594,6084,6085,6088,6090,6093,6095],{"class":3596,"line":3664},[3594,6086,6087],{"class":4493},"    pool_recycle",[3594,6089,4497],{"class":4459},[3594,6091,6092],{"class":4514},"1800",[3594,6094,4976],{"class":4459},[3594,6096,6097],{"class":4482},"# Перестворювати підключення кожні 30 хвилин\n",[3594,6099,6100],{"class":3596,"line":3670},[3594,6101,6102],{"class":4482},"                        # (щоб уникнути \"stale connections\" від PostgreSQL)\n",[3594,6104,6105],{"class":3596,"line":3676},[3594,6106,6107],{"class":4482},"    # --- Налагодження ---\n",[3594,6109,6110,6113,6115,6117,6120],{"class":3596,"line":3681},[3594,6111,6112],{"class":4493},"    echo",[3594,6114,4497],{"class":4459},[3594,6116,4641],{"class":4622},[3594,6118,6119],{"class":4459},",          ",[3594,6121,6122],{"class":4482},"# Виводити всі SQL-запити у консоль (лише для розробки!)\n",[3594,6124,6125],{"class":3596,"line":3687},[3594,6126,4558],{"class":4459},[5540,6128,6129,6130,6133,6134,6137],{},"Параметр ",[3412,6131,6132],{},"echo=True"," є надзвичайно корисним інструментом для налагодження — він дозволяє бачити кожен SQL-запит, що генерує SQLAlchemy. Проте у production-середовищі він ",[3389,6135,6136],{},"категорично вимкнений",", оскільки виводить чутливі дані та значно навантажує систему логування.",[3845,6139,6141,6142],{"id":6140},"асинхронний-engine-create_async_engine","Асинхронний Engine: ",[3412,6143,6144],{},"create_async_engine()",[3385,6146,6147,6148,6152,6153,3439],{},"Для роботи з FastAPI у повністю асинхронному режимі (що є рекомендованим підходом, як ми дізналися у статті 14) необхідно використовувати ",[3389,6149,6150],{},[3412,6151,6144],{}," разом із асинхронним драйвером ",[3412,6154,3464],{},[3585,6156,6158],{"className":4445,"code":6157,"filename":5987,"language":4448,"meta":3590,"style":3590},"from sqlalchemy.ext.asyncio import create_async_engine\n\n# Зверніть на префікс: postgresql+asyncpg (не psycopg2!)\nASYNC_DATABASE_URL = \"postgresql+asyncpg:\u002F\u002Fuser:password@localhost:5432\u002Ftaskforge_db\"\n\nasync_engine = create_async_engine(\n    ASYNC_DATABASE_URL,\n    pool_size=10,\n    max_overflow=20,\n    pool_recycle=1800,\n    echo=False,  # У production завжди False\n)\n",[3412,6159,6160,6170,6174,6179,6187,6191,6195,6200,6210,6220,6230,6244],{"__ignoreMap":3590},[3594,6161,6162,6164,6166,6168],{"class":3596,"line":3597},[3594,6163,4465],{"class":4455},[3594,6165,5782],{"class":4459},[3594,6167,4456],{"class":4455},[3594,6169,5787],{"class":4459},[3594,6171,6172],{"class":3596,"line":3603},[3594,6173,3631],{"emptyLinePlaceholder":3630},[3594,6175,6176],{"class":3596,"line":3609},[3594,6177,6178],{"class":4482},"# Зверніть на префікс: postgresql+asyncpg (не psycopg2!)\n",[3594,6180,6181,6184],{"class":3596,"line":3615},[3594,6182,6183],{"class":4459},"ASYNC_DATABASE_URL = ",[3594,6185,6186],{"class":4500},"\"postgresql+asyncpg:\u002F\u002Fuser:password@localhost:5432\u002Ftaskforge_db\"\n",[3594,6188,6189],{"class":3596,"line":3621},[3594,6190,3631],{"emptyLinePlaceholder":3630},[3594,6192,6193],{"class":3596,"line":3627},[3594,6194,5500],{"class":4459},[3594,6196,6197],{"class":3596,"line":3634},[3594,6198,6199],{"class":4459},"    ASYNC_DATABASE_URL,\n",[3594,6201,6202,6204,6206,6208],{"class":3596,"line":3640},[3594,6203,6041],{"class":4493},[3594,6205,4497],{"class":4459},[3594,6207,4646],{"class":4514},[3594,6209,4504],{"class":4459},[3594,6211,6212,6214,6216,6218],{"class":3596,"line":3646},[3594,6213,6056],{"class":4493},[3594,6215,4497],{"class":4459},[3594,6217,6061],{"class":4514},[3594,6219,4504],{"class":4459},[3594,6221,6222,6224,6226,6228],{"class":3596,"line":3652},[3594,6223,6087],{"class":4493},[3594,6225,4497],{"class":4459},[3594,6227,6092],{"class":4514},[3594,6229,4504],{"class":4459},[3594,6231,6232,6234,6236,6239,6241],{"class":3596,"line":3658},[3594,6233,6112],{"class":4493},[3594,6235,4497],{"class":4459},[3594,6237,6238],{"class":4622},"False",[3594,6240,4976],{"class":4459},[3594,6242,6243],{"class":4482},"# У production завжди False\n",[3594,6245,6246],{"class":3596,"line":3664},[3594,6247,4558],{"class":4459},[3385,6249,6250,6251,3435,6253,6255,6256,4442],{},"Ключова різниця між ",[3412,6252,4406],{},[3412,6254,6144],{}," полягає у ",[3389,6257,6258],{},"внутрішньому DBAPI-драйвері",[3510,6260,6261,6267],{},[3455,6262,6263,6266],{},[3412,6264,6265],{},"postgresql+psycopg2"," → синхронний драйвер, блокує потік виконання.",[3455,6268,6269,6272],{},[3412,6270,6271],{},"postgresql+asyncpg"," → асинхронний драйвер, звільняє event loop Python під час очікування відповіді від БД.",[5216,6274,6275,6276,6279,6280,6283],{},"Починаючи з SQLAlchemy 2.0 та psycopg (версія 3, не psycopg2), з'явилася можливість використовувати ",[3412,6277,6278],{},"postgresql+psycopg"," (без суфікса ",[3412,6281,6282],{},"2",") — це сучасна версія драйвера, що підтримує як синхронний, так і асинхронний режими. Вона є пріоритетним вибором для нових проєктів.",[3441,6285],{},[3444,6287,6289,6290,6293],{"id":6288},"session-та-sessionmaker-одиниця-роботи","Session та ",[3412,6291,6292],{},"sessionmaker",": Одиниця роботи",[3385,6295,6296,6300,6301,6304],{},[3389,6297,6298],{},[3412,6299,3944],{}," — це центральний об'єкт для роботи з ORM. Він реалізує патерн ",[3389,6302,6303],{},"Unit of Work"," (Одиниця роботи), що є фундаментальним архітектурним патерном для роботи з базами даних (ми детально розглянемо його у розділі «Під капотом»).",[3385,6306,6307,6308,6310],{},"Коротко: ",[3412,6309,3944],{}," — це «записник» або «тимчасова пам'ять», яка:",[3452,6312,6313,6316,6319],{},[3455,6314,6315],{},"Відстежує всі Python-об'єкти (ORM-моделі), завантажені з БД або додані до нього.",[3455,6317,6318],{},"Накопичує всі зміни (INSERT, UPDATE, DELETE), не надсилаючи їх одразу до БД.",[3455,6320,6321,6322,3461,6325,6328],{},"При виклику ",[3412,6323,6324],{},"session.flush()",[3412,6326,6327],{},"session.commit()"," — генерує відповідні SQL-запити та виконує їх у межах однієї транзакції.",[3845,6330,6332,3435,6334],{"id":6331},"sessionmaker-та-async_sessionmaker",[3412,6333,6292],{},[3412,6335,6336],{},"async_sessionmaker",[3385,6338,6339,6340,6342,6343,6347,6348,6351,6352,3435,6354,3439],{},"Оскільки ",[3412,6341,3944],{}," має бути ",[6344,6345,6346],"em",{},"короткоживучим"," об'єктом (один запит = одна сесія), зручно мати ",[3389,6349,6350],{},"фабрику",", яка створює нові сесії з потрібними налаштуваннями. Для цього слугують ",[3412,6353,6292],{},[3412,6355,6336],{},[3385,6357,6358],{},"Базовий шаблон виглядає так:",[5448,6360,6361,6502],{},[3585,6362,6365],{"className":4445,"code":6363,"filename":6364,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import create_engine\nfrom sqlalchemy.orm import sessionmaker\n\nengine = create_engine(\"postgresql+psycopg:\u002F\u002Fuser:pass@localhost\u002Fdb\")\n\nSessionLocal = sessionmaker(\n    bind=engine,\n    autocommit=False,\n    autoflush=False,\n    expire_on_commit=False,\n)\n\n# Один HTTP-запит = одна сесія:\nwith SessionLocal() as session:\n    user = session.get(User, 1)\n    user.is_active = False\n    session.commit()\n","sync — sessionmaker",[3412,6366,6367,6377,6389,6393,6403,6407,6412,6420,6431,6442,6453,6457,6461,6466,6479,6489,6497],{"__ignoreMap":3590},[3594,6368,6369,6371,6373,6375],{"class":3596,"line":3597},[3594,6370,4465],{"class":4455},[3594,6372,5996],{"class":4459},[3594,6374,4456],{"class":4455},[3594,6376,6001],{"class":4459},[3594,6378,6379,6381,6384,6386],{"class":3596,"line":3603},[3594,6380,4465],{"class":4455},[3594,6382,6383],{"class":4459}," sqlalchemy.orm ",[3594,6385,4456],{"class":4455},[3594,6387,6388],{"class":4459}," sessionmaker\n",[3594,6390,6391],{"class":3596,"line":3609},[3594,6392,3631],{"emptyLinePlaceholder":3630},[3594,6394,6395,6398,6401],{"class":3596,"line":3615},[3594,6396,6397],{"class":4459},"engine = create_engine(",[3594,6399,6400],{"class":4500},"\"postgresql+psycopg:\u002F\u002Fuser:pass@localhost\u002Fdb\"",[3594,6402,4558],{"class":4459},[3594,6404,6405],{"class":3596,"line":3621},[3594,6406,3631],{"emptyLinePlaceholder":3630},[3594,6408,6409],{"class":3596,"line":3627},[3594,6410,6411],{"class":4459},"SessionLocal = sessionmaker(\n",[3594,6413,6414,6417],{"class":3596,"line":3634},[3594,6415,6416],{"class":4493},"    bind",[3594,6418,6419],{"class":4459},"=engine,\n",[3594,6421,6422,6425,6427,6429],{"class":3596,"line":3640},[3594,6423,6424],{"class":4493},"    autocommit",[3594,6426,4497],{"class":4459},[3594,6428,6238],{"class":4622},[3594,6430,4504],{"class":4459},[3594,6432,6433,6436,6438,6440],{"class":3596,"line":3646},[3594,6434,6435],{"class":4493},"    autoflush",[3594,6437,4497],{"class":4459},[3594,6439,6238],{"class":4622},[3594,6441,4504],{"class":4459},[3594,6443,6444,6447,6449,6451],{"class":3596,"line":3652},[3594,6445,6446],{"class":4493},"    expire_on_commit",[3594,6448,4497],{"class":4459},[3594,6450,6238],{"class":4622},[3594,6452,4504],{"class":4459},[3594,6454,6455],{"class":3596,"line":3658},[3594,6456,4558],{"class":4459},[3594,6458,6459],{"class":3596,"line":3664},[3594,6460,3631],{"emptyLinePlaceholder":3630},[3594,6462,6463],{"class":3596,"line":3670},[3594,6464,6465],{"class":4482},"# Один HTTP-запит = одна сесія:\n",[3594,6467,6468,6471,6474,6476],{"class":3596,"line":3676},[3594,6469,6470],{"class":4455},"with",[3594,6472,6473],{"class":4459}," SessionLocal() ",[3594,6475,4597],{"class":4455},[3594,6477,6478],{"class":4459}," session:\n",[3594,6480,6481,6484,6487],{"class":3596,"line":3681},[3594,6482,6483],{"class":4459},"    user = session.get(User, ",[3594,6485,6486],{"class":4514},"1",[3594,6488,4558],{"class":4459},[3594,6490,6491,6494],{"class":3596,"line":3687},[3594,6492,6493],{"class":4459},"    user.is_active = ",[3594,6495,6496],{"class":4622},"False\n",[3594,6498,6499],{"class":3596,"line":3693},[3594,6500,6501],{"class":4459},"    session.commit()\n",[3585,6503,6506],{"className":4445,"code":6504,"filename":6505,"language":4448,"meta":3590,"style":3590},"from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession\n\nasync_engine = create_async_engine(\"postgresql+asyncpg:\u002F\u002Fuser:pass@localhost\u002Fdb\")\n\nAsyncSessionLocal = async_sessionmaker(\n    bind=async_engine,\n    class_=AsyncSession,\n    autocommit=False,\n    autoflush=False,\n    expire_on_commit=False,\n)\n\n# Один HTTP-запит = одна async-сесія:\nasync with AsyncSessionLocal() as session:\n    user = await session.get(User, 1)\n    user.is_active = False\n    await session.commit()\n","async — async_sessionmaker",[3412,6507,6508,6519,6523,6533,6537,6542,6549,6557,6567,6577,6587,6591,6595,6600,6614,6628,6634],{"__ignoreMap":3590},[3594,6509,6510,6512,6514,6516],{"class":3596,"line":3597},[3594,6511,4465],{"class":4455},[3594,6513,5782],{"class":4459},[3594,6515,4456],{"class":4455},[3594,6517,6518],{"class":4459}," create_async_engine, async_sessionmaker, AsyncSession\n",[3594,6520,6521],{"class":3596,"line":3603},[3594,6522,3631],{"emptyLinePlaceholder":3630},[3594,6524,6525,6528,6531],{"class":3596,"line":3609},[3594,6526,6527],{"class":4459},"async_engine = create_async_engine(",[3594,6529,6530],{"class":4500},"\"postgresql+asyncpg:\u002F\u002Fuser:pass@localhost\u002Fdb\"",[3594,6532,4558],{"class":4459},[3594,6534,6535],{"class":3596,"line":3615},[3594,6536,3631],{"emptyLinePlaceholder":3630},[3594,6538,6539],{"class":3596,"line":3621},[3594,6540,6541],{"class":4459},"AsyncSessionLocal = async_sessionmaker(\n",[3594,6543,6544,6546],{"class":3596,"line":3627},[3594,6545,6416],{"class":4493},[3594,6547,6548],{"class":4459},"=async_engine,\n",[3594,6550,6551,6554],{"class":3596,"line":3634},[3594,6552,6553],{"class":4493},"    class_",[3594,6555,6556],{"class":4459},"=AsyncSession,\n",[3594,6558,6559,6561,6563,6565],{"class":3596,"line":3640},[3594,6560,6424],{"class":4493},[3594,6562,4497],{"class":4459},[3594,6564,6238],{"class":4622},[3594,6566,4504],{"class":4459},[3594,6568,6569,6571,6573,6575],{"class":3596,"line":3646},[3594,6570,6435],{"class":4493},[3594,6572,4497],{"class":4459},[3594,6574,6238],{"class":4622},[3594,6576,4504],{"class":4459},[3594,6578,6579,6581,6583,6585],{"class":3596,"line":3652},[3594,6580,6446],{"class":4493},[3594,6582,4497],{"class":4459},[3594,6584,6238],{"class":4622},[3594,6586,4504],{"class":4459},[3594,6588,6589],{"class":3596,"line":3658},[3594,6590,4558],{"class":4459},[3594,6592,6593],{"class":3596,"line":3664},[3594,6594,3631],{"emptyLinePlaceholder":3630},[3594,6596,6597],{"class":3596,"line":3670},[3594,6598,6599],{"class":4482},"# Один HTTP-запит = одна async-сесія:\n",[3594,6601,6602,6604,6607,6610,6612],{"class":3596,"line":3676},[3594,6603,4856],{"class":4455},[3594,6605,6606],{"class":4455}," with",[3594,6608,6609],{"class":4459}," AsyncSessionLocal() ",[3594,6611,4597],{"class":4455},[3594,6613,6478],{"class":4459},[3594,6615,6616,6619,6621,6624,6626],{"class":3596,"line":3681},[3594,6617,6618],{"class":4459},"    user = ",[3594,6620,4878],{"class":4455},[3594,6622,6623],{"class":4459}," session.get(User, ",[3594,6625,6486],{"class":4514},[3594,6627,4558],{"class":4459},[3594,6629,6630,6632],{"class":3596,"line":3687},[3594,6631,6493],{"class":4459},[3594,6633,6496],{"class":4622},[3594,6635,6636,6639],{"class":3596,"line":3693},[3594,6637,6638],{"class":4455},"    await",[3594,6640,6641],{"class":4459}," session.commit()\n",[3441,6643],{},[3845,6645,5975,6647],{"id":6646},"синхронний-engine-create_engine-1",[3412,6648,4406],{},[3385,6650,5980,6651,5983],{},[3412,6652,4406],{},[3585,6654,6655],{"className":4445,"code":5986,"filename":5987,"language":4448,"meta":3590,"style":3590},[3412,6656,6657,6667,6671,6675,6681,6685,6689,6693,6697,6709,6721,6733,6745,6749,6753,6765],{"__ignoreMap":3590},[3594,6658,6659,6661,6663,6665],{"class":3596,"line":3597},[3594,6660,4465],{"class":4455},[3594,6662,5996],{"class":4459},[3594,6664,4456],{"class":4455},[3594,6666,6001],{"class":4459},[3594,6668,6669],{"class":3596,"line":3603},[3594,6670,3631],{"emptyLinePlaceholder":3630},[3594,6672,6673],{"class":3596,"line":3609},[3594,6674,6010],{"class":4482},[3594,6676,6677,6679],{"class":3596,"line":3615},[3594,6678,6015],{"class":4459},[3594,6680,6018],{"class":4500},[3594,6682,6683],{"class":3596,"line":3621},[3594,6684,3631],{"emptyLinePlaceholder":3630},[3594,6686,6687],{"class":3596,"line":3627},[3594,6688,5470],{"class":4459},[3594,6690,6691],{"class":3596,"line":3634},[3594,6692,6031],{"class":4459},[3594,6694,6695],{"class":3596,"line":3640},[3594,6696,6036],{"class":4482},[3594,6698,6699,6701,6703,6705,6707],{"class":3596,"line":3646},[3594,6700,6041],{"class":4493},[3594,6702,4497],{"class":4459},[3594,6704,4646],{"class":4514},[3594,6706,6048],{"class":4459},[3594,6708,6051],{"class":4482},[3594,6710,6711,6713,6715,6717,6719],{"class":3596,"line":3652},[3594,6712,6056],{"class":4493},[3594,6714,4497],{"class":4459},[3594,6716,6061],{"class":4514},[3594,6718,6064],{"class":4459},[3594,6720,6067],{"class":4482},[3594,6722,6723,6725,6727,6729,6731],{"class":3596,"line":3658},[3594,6724,6072],{"class":4493},[3594,6726,4497],{"class":4459},[3594,6728,6077],{"class":4514},[3594,6730,6064],{"class":4459},[3594,6732,6082],{"class":4482},[3594,6734,6735,6737,6739,6741,6743],{"class":3596,"line":3664},[3594,6736,6087],{"class":4493},[3594,6738,4497],{"class":4459},[3594,6740,6092],{"class":4514},[3594,6742,4976],{"class":4459},[3594,6744,6097],{"class":4482},[3594,6746,6747],{"class":3596,"line":3670},[3594,6748,6102],{"class":4482},[3594,6750,6751],{"class":3596,"line":3676},[3594,6752,6107],{"class":4482},[3594,6754,6755,6757,6759,6761,6763],{"class":3596,"line":3681},[3594,6756,6112],{"class":4493},[3594,6758,4497],{"class":4459},[3594,6760,4641],{"class":4622},[3594,6762,6119],{"class":4459},[3594,6764,6122],{"class":4482},[3594,6766,6767],{"class":3596,"line":3687},[3594,6768,4558],{"class":4459},[5540,6770,6129,6771,6133,6773,6137],{},[3412,6772,6132],{},[3389,6774,6136],{},[3845,6776,6141,6778],{"id":6777},"асинхронний-engine-create_async_engine-1",[3412,6779,6144],{},[3385,6781,6147,6782,6152,6786,3439],{},[3389,6783,6784],{},[3412,6785,6144],{},[3412,6787,3464],{},[3585,6789,6790],{"className":4445,"code":6157,"filename":5987,"language":4448,"meta":3590,"style":3590},[3412,6791,6792,6802,6806,6810,6816,6820,6824,6828,6838,6848,6858,6870],{"__ignoreMap":3590},[3594,6793,6794,6796,6798,6800],{"class":3596,"line":3597},[3594,6795,4465],{"class":4455},[3594,6797,5782],{"class":4459},[3594,6799,4456],{"class":4455},[3594,6801,5787],{"class":4459},[3594,6803,6804],{"class":3596,"line":3603},[3594,6805,3631],{"emptyLinePlaceholder":3630},[3594,6807,6808],{"class":3596,"line":3609},[3594,6809,6178],{"class":4482},[3594,6811,6812,6814],{"class":3596,"line":3615},[3594,6813,6183],{"class":4459},[3594,6815,6186],{"class":4500},[3594,6817,6818],{"class":3596,"line":3621},[3594,6819,3631],{"emptyLinePlaceholder":3630},[3594,6821,6822],{"class":3596,"line":3627},[3594,6823,5500],{"class":4459},[3594,6825,6826],{"class":3596,"line":3634},[3594,6827,6199],{"class":4459},[3594,6829,6830,6832,6834,6836],{"class":3596,"line":3640},[3594,6831,6041],{"class":4493},[3594,6833,4497],{"class":4459},[3594,6835,4646],{"class":4514},[3594,6837,4504],{"class":4459},[3594,6839,6840,6842,6844,6846],{"class":3596,"line":3646},[3594,6841,6056],{"class":4493},[3594,6843,4497],{"class":4459},[3594,6845,6061],{"class":4514},[3594,6847,4504],{"class":4459},[3594,6849,6850,6852,6854,6856],{"class":3596,"line":3652},[3594,6851,6087],{"class":4493},[3594,6853,4497],{"class":4459},[3594,6855,6092],{"class":4514},[3594,6857,4504],{"class":4459},[3594,6859,6860,6862,6864,6866,6868],{"class":3596,"line":3658},[3594,6861,6112],{"class":4493},[3594,6863,4497],{"class":4459},[3594,6865,6238],{"class":4622},[3594,6867,4976],{"class":4459},[3594,6869,6243],{"class":4482},[3594,6871,6872],{"class":3596,"line":3664},[3594,6873,4558],{"class":4459},[3385,6875,6250,6876,3435,6878,6255,6880,4442],{},[3412,6877,4406],{},[3412,6879,6144],{},[3389,6881,6258],{},[3510,6883,6884,6888],{},[3455,6885,6886,6266],{},[3412,6887,6265],{},[3455,6889,6890,6272],{},[3412,6891,6271],{},[5216,6893,6275,6894,6279,6896,6283],{},[3412,6895,6278],{},[3412,6897,6282],{},[3441,6899],{},[4433,6901,6903,6904],{"id":6902},"параметри-конфігурації-connection-pool-у-engine","Параметри конфігурації Connection Pool у ",[3412,6905,3869],{},[3385,6907,6908,6909,3461,6911,6913,6914,6917],{},"При створенні ",[3412,6910,3869],{},[3412,6912,4066],{}," за допомогою параметрів можна тонко налаштувати поведінку вбудованого пулу з'єднань (",[3412,6915,6916],{},"QueuePool","). Розглянемо ключові параметри, їхні значення за замовчуванням та вплив на систему:",[3510,6919,6920,6957,6997,7029],{},[3455,6921,6922,6927,6928,6931,6932,6935,6936],{},[3389,6923,6924],{},[3412,6925,6926],{},"pool_size"," (тип ",[3412,6929,6930],{},"int",", за замовчуванням ",[3412,6933,6934],{},"5",")\nВизначає кількість «постійно відкритих» (keep-alive) підключень, які пул зберігає в пам'яті. Ці з'єднання ніколи не закриваються при звільненні.",[3510,6937,6938,6944],{},[3455,6939,6940,6943],{},[6344,6941,6942],{},"На що впливає:"," Лімітує базову пропускну здатність. Якщо ваші FastAPI-воркери роблять мало паралельних запитів, 5–10 підключень цілком достатньо. Для навантажених додатків це значення збільшують до 20–50.",[3455,6945,6946,6949,6950,6952,6953,6956],{},[6344,6947,6948],{},"Застереження:"," Кожне з'єднання споживає RAM на сервері PostgreSQL (близько 10 МБ на з'єднання). Збільшення ",[3412,6951,6926],{}," понад ліміт ",[3412,6954,6955],{},"max_connections"," у конфігурації PostgreSQL призведе до того, що база даних відхилятиме нові підключення.",[3455,6958,6959,6927,6964,6931,6966,6968,6969,6972,6973,6975,6976],{},[3389,6960,6961],{},[3412,6962,6963],{},"max_overflow",[3412,6965,6930],{},[3412,6967,4646],{},")\nВизначає максимальну кількість ",[6344,6970,6971],{},"тимчасових"," додаткових підключень, які пул може створити, якщо всі з'єднання з ",[3412,6974,6926],{}," зараз зайняті.",[3510,6977,6978,6983],{},[3455,6979,6980,6982],{},[6344,6981,6942],{}," Дозволяє додатку безболісно переживати раптові піки навантаження. Коли пік минає, ці «overflow» підключення автоматично закриваються.",[3455,6984,6985,6988,6989,6992,6993,6996],{},[6344,6986,6987],{},"Розрахунок:"," Загальна максимальна кількість одночасно відкритих підключень з одного екземпляра додатка дорівнює ",[3412,6990,6991],{},"pool_size + max_overflow",". Наприклад, ",[3412,6994,6995],{},"pool_size=10, max_overflow=20"," дає ліміт у 30 з'єднань.",[3455,6998,6999,6927,7004,6931,7006,7009,7010],{},[3389,7000,7001],{},[3412,7002,7003],{},"pool_recycle",[3412,7005,6930],{},[3412,7007,7008],{},"-1",", тобто вимкнено)\nВизначає максимальний вік підключення у секундах. Якщо підключення старше за це значення, при поверненні в пул воно буде закрите, а замість нього буде створено нове.",[3510,7011,7012],{},[3455,7013,7014,7016,7017,7020,7021,7024,7025,7028],{},[6344,7015,6942],{}," Рятує від проблеми ",[3389,7018,7019],{},"stale connections"," (застарілих підключень). Деякі мережеві екрани (firewalls), хмарні бази даних (наприклад, AWS RDS, Heroku Postgres) або сам PostgreSQL можуть примусово розривати неактивні TCP-сесії після певного періоду простою (наприклад, 10 або 30 хвилин). Якщо пул спробує використати таке з'єднання, виникне помилка на кшталт ",[3412,7022,7023],{},"OperationalError: Connection handshake failed",". Встановлення ",[3412,7026,7027],{},"pool_recycle=1800"," (30 хвилин) змушує оновлювати підключення превентивно.",[3455,7030,7031,6927,7036,6931,7039,7041,7042,7044,7045,7048,7049],{},[3389,7032,7033],{},[3412,7034,7035],{},"pool_pre_ping",[3412,7037,7038],{},"bool",[3412,7040,6238],{},")\nЯкщо встановлено в ",[3412,7043,4641],{},", при кожному отриманні з'єднання з пулу SQLAlchemy виконує швидкий тестовий запит (зазвичай ",[3412,7046,7047],{},"SELECT 1",") перед тим, як віддати з'єднання вашому коду.",[3510,7050,7051,7056],{},[3455,7052,7053,7055],{},[6344,7054,6942],{}," Забезпечує високу стійкість до збоїв. Якщо з'єднання виявиться «мертвим» (через перезапуск бази даних або мережевий збій), пул тихо закриє його, відкриє нове працездатне підключення та прозоро віддасть вашому коду. Розробник навіть не помітить, що з'єднання розривалося.",[3455,7057,7058,7061,7062,7065],{},[6344,7059,7060],{},"Рекомендація:"," Завжди ставте ",[3412,7063,7064],{},"pool_pre_ping=True"," у production. Хоча це додає крихітний оверхед на один SQL-запит-тест, стійкість додатку зростає в рази.",[5430,7067,7069],{"id":7068},"приклад-повної-production-конфігурації-engine","Приклад повної production конфігурації Engine:",[3585,7071,7073],{"className":4445,"code":7072,"language":4448,"meta":3590,"style":3590},"async_engine = create_async_engine(\n    DATABASE_URL,\n    pool_size=20,           # Збільшено для навантаженого API\n    max_overflow=10,        # Додаткові 10 з'єднань під час піків\n    pool_recycle=1800,      # Оновлювати кожні 30 хвилин (захист від AWS RDS таймаутів)\n    pool_pre_ping=True,     # ✅ Перевіряти працездатність з'єднання перед відправкою запиту\n)\n",[3412,7074,7075,7079,7083,7097,7111,7125,7140],{"__ignoreMap":3590},[3594,7076,7077],{"class":3596,"line":3597},[3594,7078,5500],{"class":4459},[3594,7080,7081],{"class":3596,"line":3603},[3594,7082,6031],{"class":4459},[3594,7084,7085,7087,7089,7091,7094],{"class":3596,"line":3609},[3594,7086,6041],{"class":4493},[3594,7088,4497],{"class":4459},[3594,7090,6061],{"class":4514},[3594,7092,7093],{"class":4459},",           ",[3594,7095,7096],{"class":4482},"# Збільшено для навантаженого API\n",[3594,7098,7099,7101,7103,7105,7108],{"class":3596,"line":3615},[3594,7100,6056],{"class":4493},[3594,7102,4497],{"class":4459},[3594,7104,4646],{"class":4514},[3594,7106,7107],{"class":4459},",        ",[3594,7109,7110],{"class":4482},"# Додаткові 10 з'єднань під час піків\n",[3594,7112,7113,7115,7117,7119,7122],{"class":3596,"line":3621},[3594,7114,6087],{"class":4493},[3594,7116,4497],{"class":4459},[3594,7118,6092],{"class":4514},[3594,7120,7121],{"class":4459},",      ",[3594,7123,7124],{"class":4482},"# Оновлювати кожні 30 хвилин (захист від AWS RDS таймаутів)\n",[3594,7126,7127,7130,7132,7134,7137],{"class":3596,"line":3627},[3594,7128,7129],{"class":4493},"    pool_pre_ping",[3594,7131,4497],{"class":4459},[3594,7133,4641],{"class":4622},[3594,7135,7136],{"class":4459},",     ",[3594,7138,7139],{"class":4482},"# ✅ Перевіряти працездатність з'єднання перед відправкою запиту\n",[3594,7141,7142],{"class":3596,"line":3634},[3594,7143,4558],{"class":4459},[3441,7145],{},[3444,7147,6289,7149,6293],{"id":7148},"session-та-sessionmaker-одиниця-роботи-1",[3412,7150,6292],{},[3385,7152,7153,6300,7157,6304],{},[3389,7154,7155],{},[3412,7156,3944],{},[3389,7158,6303],{},[3385,7160,6307,7161,6310],{},[3412,7162,3944],{},[3452,7164,7165,7167,7169],{},[3455,7166,6315],{},[3455,7168,6318],{},[3455,7170,6321,7171,3461,7173,6328],{},[3412,7172,6324],{},[3412,7174,6327],{},[3845,7176,7178,3435,7180],{"id":7177},"sessionmaker-та-async_sessionmaker-1",[3412,7179,6292],{},[3412,7181,6336],{},[3385,7183,6339,7184,6342,7186,6347,7188,6351,7190,3435,7192,3439],{},[3412,7185,3944],{},[6344,7187,6346],{},[3389,7189,6350],{},[3412,7191,6292],{},[3412,7193,6336],{},[3385,7195,6358],{},[5448,7197,7198,7318],{},[3585,7199,7200],{"className":4445,"code":6363,"filename":6364,"language":4448,"meta":3590,"style":3590},[3412,7201,7202,7212,7222,7226,7234,7238,7242,7248,7258,7268,7278,7282,7286,7290,7300,7308,7314],{"__ignoreMap":3590},[3594,7203,7204,7206,7208,7210],{"class":3596,"line":3597},[3594,7205,4465],{"class":4455},[3594,7207,5996],{"class":4459},[3594,7209,4456],{"class":4455},[3594,7211,6001],{"class":4459},[3594,7213,7214,7216,7218,7220],{"class":3596,"line":3603},[3594,7215,4465],{"class":4455},[3594,7217,6383],{"class":4459},[3594,7219,4456],{"class":4455},[3594,7221,6388],{"class":4459},[3594,7223,7224],{"class":3596,"line":3609},[3594,7225,3631],{"emptyLinePlaceholder":3630},[3594,7227,7228,7230,7232],{"class":3596,"line":3615},[3594,7229,6397],{"class":4459},[3594,7231,6400],{"class":4500},[3594,7233,4558],{"class":4459},[3594,7235,7236],{"class":3596,"line":3621},[3594,7237,3631],{"emptyLinePlaceholder":3630},[3594,7239,7240],{"class":3596,"line":3627},[3594,7241,6411],{"class":4459},[3594,7243,7244,7246],{"class":3596,"line":3634},[3594,7245,6416],{"class":4493},[3594,7247,6419],{"class":4459},[3594,7249,7250,7252,7254,7256],{"class":3596,"line":3640},[3594,7251,6424],{"class":4493},[3594,7253,4497],{"class":4459},[3594,7255,6238],{"class":4622},[3594,7257,4504],{"class":4459},[3594,7259,7260,7262,7264,7266],{"class":3596,"line":3646},[3594,7261,6435],{"class":4493},[3594,7263,4497],{"class":4459},[3594,7265,6238],{"class":4622},[3594,7267,4504],{"class":4459},[3594,7269,7270,7272,7274,7276],{"class":3596,"line":3652},[3594,7271,6446],{"class":4493},[3594,7273,4497],{"class":4459},[3594,7275,6238],{"class":4622},[3594,7277,4504],{"class":4459},[3594,7279,7280],{"class":3596,"line":3658},[3594,7281,4558],{"class":4459},[3594,7283,7284],{"class":3596,"line":3664},[3594,7285,3631],{"emptyLinePlaceholder":3630},[3594,7287,7288],{"class":3596,"line":3670},[3594,7289,6465],{"class":4482},[3594,7291,7292,7294,7296,7298],{"class":3596,"line":3676},[3594,7293,6470],{"class":4455},[3594,7295,6473],{"class":4459},[3594,7297,4597],{"class":4455},[3594,7299,6478],{"class":4459},[3594,7301,7302,7304,7306],{"class":3596,"line":3681},[3594,7303,6483],{"class":4459},[3594,7305,6486],{"class":4514},[3594,7307,4558],{"class":4459},[3594,7309,7310,7312],{"class":3596,"line":3687},[3594,7311,6493],{"class":4459},[3594,7313,6496],{"class":4622},[3594,7315,7316],{"class":3596,"line":3693},[3594,7317,6501],{"class":4459},[3585,7319,7320],{"className":4445,"code":6504,"filename":6505,"language":4448,"meta":3590,"style":3590},[3412,7321,7322,7332,7336,7344,7348,7352,7358,7364,7374,7384,7394,7398,7402,7406,7418,7430,7436],{"__ignoreMap":3590},[3594,7323,7324,7326,7328,7330],{"class":3596,"line":3597},[3594,7325,4465],{"class":4455},[3594,7327,5782],{"class":4459},[3594,7329,4456],{"class":4455},[3594,7331,6518],{"class":4459},[3594,7333,7334],{"class":3596,"line":3603},[3594,7335,3631],{"emptyLinePlaceholder":3630},[3594,7337,7338,7340,7342],{"class":3596,"line":3609},[3594,7339,6527],{"class":4459},[3594,7341,6530],{"class":4500},[3594,7343,4558],{"class":4459},[3594,7345,7346],{"class":3596,"line":3615},[3594,7347,3631],{"emptyLinePlaceholder":3630},[3594,7349,7350],{"class":3596,"line":3621},[3594,7351,6541],{"class":4459},[3594,7353,7354,7356],{"class":3596,"line":3627},[3594,7355,6416],{"class":4493},[3594,7357,6548],{"class":4459},[3594,7359,7360,7362],{"class":3596,"line":3634},[3594,7361,6553],{"class":4493},[3594,7363,6556],{"class":4459},[3594,7365,7366,7368,7370,7372],{"class":3596,"line":3640},[3594,7367,6424],{"class":4493},[3594,7369,4497],{"class":4459},[3594,7371,6238],{"class":4622},[3594,7373,4504],{"class":4459},[3594,7375,7376,7378,7380,7382],{"class":3596,"line":3646},[3594,7377,6435],{"class":4493},[3594,7379,4497],{"class":4459},[3594,7381,6238],{"class":4622},[3594,7383,4504],{"class":4459},[3594,7385,7386,7388,7390,7392],{"class":3596,"line":3652},[3594,7387,6446],{"class":4493},[3594,7389,4497],{"class":4459},[3594,7391,6238],{"class":4622},[3594,7393,4504],{"class":4459},[3594,7395,7396],{"class":3596,"line":3658},[3594,7397,4558],{"class":4459},[3594,7399,7400],{"class":3596,"line":3664},[3594,7401,3631],{"emptyLinePlaceholder":3630},[3594,7403,7404],{"class":3596,"line":3670},[3594,7405,6599],{"class":4482},[3594,7407,7408,7410,7412,7414,7416],{"class":3596,"line":3676},[3594,7409,4856],{"class":4455},[3594,7411,6606],{"class":4455},[3594,7413,6609],{"class":4459},[3594,7415,4597],{"class":4455},[3594,7417,6478],{"class":4459},[3594,7419,7420,7422,7424,7426,7428],{"class":3596,"line":3681},[3594,7421,6618],{"class":4459},[3594,7423,4878],{"class":4455},[3594,7425,6623],{"class":4459},[3594,7427,6486],{"class":4514},[3594,7429,4558],{"class":4459},[3594,7431,7432,7434],{"class":3596,"line":3687},[3594,7433,6493],{"class":4459},[3594,7435,6496],{"class":4622},[3594,7437,7438,7440],{"class":3596,"line":3693},[3594,7439,6638],{"class":4455},[3594,7441,6641],{"class":4459},[3441,7443],{},[3845,7445,7447,7448,3435,7450,7452],{"id":7446},"параметри-sessionmaker-та-async_sessionmaker-повний-розбір","Параметри ",[3412,7449,6292],{},[3412,7451,6336],{},": повний розбір",[3385,7454,7455],{},"Обидві фабрики приймають однакові параметри. Нижче — вичерпна документація кожного з них із прикладами впливу на поведінку.",[3441,7457],{},[4433,7459,7461,4025,7464],{"id":7460},"bind-autobegin",[3412,7462,7463],{},"bind",[3412,7465,7466],{},"autobegin",[3385,7468,7469,7470,3461,7472,7474,7475,7477,7478,3439],{},"Прив'язує фабрику до конкретного ",[3412,7471,3869],{},[3412,7473,4066],{},". Усі сесії, створені цією фабрикою, будуть автоматично використовувати саме це підключення до БД. Якщо не вказати тут — доведеться передавати ",[3412,7476,7463],{}," при кожному виклику ",[3412,7479,7480],{},"Session()",[3385,7482,7483,7484,7486,7487,7489,7490,7492,7493,7496,7497,7499],{},"Якщо ",[3412,7485,4641],{},", SQLAlchemy автоматично починає нову транзакцію (",[3412,7488,3487],{},") при першій операції з БД. Якщо ",[3412,7491,6238],{}," — транзакцію потрібно починати вручну через ",[3412,7494,7495],{},"session.begin()",". Значення ",[3412,7498,4641],{}," (за замовчуванням) є зручним та безпечним для більшості випадків.",[3585,7501,7504],{"className":4445,"code":7502,"filename":7503,"language":4448,"meta":3590,"style":3590},"# Стандартне використання — bind + autobegin=True (за замовчуванням)\nSessionLocal = sessionmaker(bind=engine, autobegin=True)\n\nwith SessionLocal() as session:\n    # При першому execute() SQLAlchemy автоматично відкриває транзакцію:\n    # BEGIN (implicit)\n    user = session.get(User, 1)\n    session.commit()\n    # COMMIT\n\n# autobegin=False — повний ручний контроль (рідко потрібен):\nManualSession = sessionmaker(bind=engine, autobegin=False)\nwith ManualSession() as session:\n    with session.begin():         # Явний BEGIN\n        user = session.get(User, 1)\n    # COMMIT автоматично при виході з session.begin()\n","bind + autobegin",[3412,7505,7506,7511,7529,7533,7543,7548,7553,7561,7565,7570,7574,7579,7596,7607,7617,7626],{"__ignoreMap":3590},[3594,7507,7508],{"class":3596,"line":3597},[3594,7509,7510],{"class":4482},"# Стандартне використання — bind + autobegin=True (за замовчуванням)\n",[3594,7512,7513,7516,7518,7521,7523,7525,7527],{"class":3596,"line":3603},[3594,7514,7515],{"class":4459},"SessionLocal = sessionmaker(",[3594,7517,7463],{"class":4493},[3594,7519,7520],{"class":4459},"=engine, ",[3594,7522,7466],{"class":4493},[3594,7524,4497],{"class":4459},[3594,7526,4641],{"class":4622},[3594,7528,4558],{"class":4459},[3594,7530,7531],{"class":3596,"line":3609},[3594,7532,3631],{"emptyLinePlaceholder":3630},[3594,7534,7535,7537,7539,7541],{"class":3596,"line":3615},[3594,7536,6470],{"class":4455},[3594,7538,6473],{"class":4459},[3594,7540,4597],{"class":4455},[3594,7542,6478],{"class":4459},[3594,7544,7545],{"class":3596,"line":3621},[3594,7546,7547],{"class":4482},"    # При першому execute() SQLAlchemy автоматично відкриває транзакцію:\n",[3594,7549,7550],{"class":3596,"line":3627},[3594,7551,7552],{"class":4482},"    # BEGIN (implicit)\n",[3594,7554,7555,7557,7559],{"class":3596,"line":3634},[3594,7556,6483],{"class":4459},[3594,7558,6486],{"class":4514},[3594,7560,4558],{"class":4459},[3594,7562,7563],{"class":3596,"line":3640},[3594,7564,6501],{"class":4459},[3594,7566,7567],{"class":3596,"line":3646},[3594,7568,7569],{"class":4482},"    # COMMIT\n",[3594,7571,7572],{"class":3596,"line":3652},[3594,7573,3631],{"emptyLinePlaceholder":3630},[3594,7575,7576],{"class":3596,"line":3658},[3594,7577,7578],{"class":4482},"# autobegin=False — повний ручний контроль (рідко потрібен):\n",[3594,7580,7581,7584,7586,7588,7590,7592,7594],{"class":3596,"line":3664},[3594,7582,7583],{"class":4459},"ManualSession = sessionmaker(",[3594,7585,7463],{"class":4493},[3594,7587,7520],{"class":4459},[3594,7589,7466],{"class":4493},[3594,7591,4497],{"class":4459},[3594,7593,6238],{"class":4622},[3594,7595,4558],{"class":4459},[3594,7597,7598,7600,7603,7605],{"class":3596,"line":3670},[3594,7599,6470],{"class":4455},[3594,7601,7602],{"class":4459}," ManualSession() ",[3594,7604,4597],{"class":4455},[3594,7606,6478],{"class":4459},[3594,7608,7609,7611,7614],{"class":3596,"line":3676},[3594,7610,4585],{"class":4455},[3594,7612,7613],{"class":4459}," session.begin():         ",[3594,7615,7616],{"class":4482},"# Явний BEGIN\n",[3594,7618,7619,7622,7624],{"class":3596,"line":3681},[3594,7620,7621],{"class":4459},"        user = session.get(User, ",[3594,7623,6486],{"class":4514},[3594,7625,4558],{"class":4459},[3594,7627,7628],{"class":3596,"line":3687},[3594,7629,7630],{"class":4482},"    # COMMIT автоматично при виході з session.begin()\n",[3441,7632],{},[4433,7634,7636],{"id":7635},"autocommit",[3412,7637,7635],{},[3385,7639,7640,7646,7647,7650],{},[3389,7641,7642,7643,7645],{},"Чому ",[3412,7644,6238],{},"?"," Це один з найважливіших параметрів, що забезпечує ",[3389,7648,7649],{},"атомарність"," операцій.",[3385,7652,7483,7653,7656,7657,7660,7661,7663],{},[3412,7654,7655],{},"autocommit=True"," — кожен SQL-запит виконується у своїй власній транзакції і негайно комітиться. Це означає, що неможливо атомарно виконати кілька операцій: якщо після першого ",[3412,7658,7659],{},"INSERT"," програма впаде — другий ",[3412,7662,7659],{}," вже буде збережений у БД, а перший — ні. Дані опиняться у неузгодженому стані.",[3385,7665,7483,7666,7669,7670,4025,7673,7676,7677,7680],{},[3412,7667,7668],{},"autocommit=False"," — всі операції в межах сесії виконуються в одній транзакції, яку ви контролюєте через ",[3412,7671,7672],{},"commit()",[3412,7674,7675],{},"rollback()",". Це гарантує ",[3389,7678,7679],{},"ACID","-властивості.",[3585,7682,7685],{"className":4445,"code":7683,"filename":7684,"language":4448,"meta":3590,"style":3590},"# ❌ autocommit=True — небезпечно, не використовуйте у production\nAutoCommitSession = sessionmaker(bind=engine, autocommit=True)\n\nwith AutoCommitSession() as session:\n    # Кожен виклик одразу комітиться окремо!\n    session.add(User(username=\"alice\"))  # SQL: INSERT + COMMIT\n    # Якщо тут виникне помилка:\n    raise RuntimeError(\"Щось пішло не так!\")\n    session.add(User(username=\"bob\"))    # Цей INSERT ніколи не виконається\n    # alice вже збережена у БД — неузгодженість!\n\n# ✅ autocommit=False — безпечно (за замовчуванням)\nSafeSession = sessionmaker(bind=engine, autocommit=False)\n\nwith SafeSession() as session:\n    try:\n        session.add(User(username=\"alice\"))\n        session.add(User(username=\"bob\"))\n        session.commit()  # Обидва INSERT у ONE транзакції\n    except Exception:\n        session.rollback()  # Обидва відкочуються — БД у чистому стані\n","Різниця між autocommit=True та autocommit=False",[3412,7686,7687,7692,7709,7713,7724,7729,7748,7753,7769,7786,7791,7795,7800,7817,7821,7832,7838,7852,7864,7872,7881],{"__ignoreMap":3590},[3594,7688,7689],{"class":3596,"line":3597},[3594,7690,7691],{"class":4482},"# ❌ autocommit=True — небезпечно, не використовуйте у production\n",[3594,7693,7694,7697,7699,7701,7703,7705,7707],{"class":3596,"line":3603},[3594,7695,7696],{"class":4459},"AutoCommitSession = sessionmaker(",[3594,7698,7463],{"class":4493},[3594,7700,7520],{"class":4459},[3594,7702,7635],{"class":4493},[3594,7704,4497],{"class":4459},[3594,7706,4641],{"class":4622},[3594,7708,4558],{"class":4459},[3594,7710,7711],{"class":3596,"line":3609},[3594,7712,3631],{"emptyLinePlaceholder":3630},[3594,7714,7715,7717,7720,7722],{"class":3596,"line":3615},[3594,7716,6470],{"class":4455},[3594,7718,7719],{"class":4459}," AutoCommitSession() ",[3594,7721,4597],{"class":4455},[3594,7723,6478],{"class":4459},[3594,7725,7726],{"class":3596,"line":3621},[3594,7727,7728],{"class":4482},"    # Кожен виклик одразу комітиться окремо!\n",[3594,7730,7731,7734,7737,7739,7742,7745],{"class":3596,"line":3627},[3594,7732,7733],{"class":4459},"    session.add(User(",[3594,7735,7736],{"class":4493},"username",[3594,7738,4497],{"class":4459},[3594,7740,7741],{"class":4500},"\"alice\"",[3594,7743,7744],{"class":4459},"))  ",[3594,7746,7747],{"class":4482},"# SQL: INSERT + COMMIT\n",[3594,7749,7750],{"class":3596,"line":3634},[3594,7751,7752],{"class":4482},"    # Якщо тут виникне помилка:\n",[3594,7754,7755,7758,7761,7764,7767],{"class":3596,"line":3640},[3594,7756,7757],{"class":4455},"    raise",[3594,7759,7760],{"class":4793}," RuntimeError",[3594,7762,7763],{"class":4459},"(",[3594,7765,7766],{"class":4500},"\"Щось пішло не так!\"",[3594,7768,4558],{"class":4459},[3594,7770,7771,7773,7775,7777,7780,7783],{"class":3596,"line":3646},[3594,7772,7733],{"class":4459},[3594,7774,7736],{"class":4493},[3594,7776,4497],{"class":4459},[3594,7778,7779],{"class":4500},"\"bob\"",[3594,7781,7782],{"class":4459},"))    ",[3594,7784,7785],{"class":4482},"# Цей INSERT ніколи не виконається\n",[3594,7787,7788],{"class":3596,"line":3652},[3594,7789,7790],{"class":4482},"    # alice вже збережена у БД — неузгодженість!\n",[3594,7792,7793],{"class":3596,"line":3658},[3594,7794,3631],{"emptyLinePlaceholder":3630},[3594,7796,7797],{"class":3596,"line":3664},[3594,7798,7799],{"class":4482},"# ✅ autocommit=False — безпечно (за замовчуванням)\n",[3594,7801,7802,7805,7807,7809,7811,7813,7815],{"class":3596,"line":3670},[3594,7803,7804],{"class":4459},"SafeSession = sessionmaker(",[3594,7806,7463],{"class":4493},[3594,7808,7520],{"class":4459},[3594,7810,7635],{"class":4493},[3594,7812,4497],{"class":4459},[3594,7814,6238],{"class":4622},[3594,7816,4558],{"class":4459},[3594,7818,7819],{"class":3596,"line":3676},[3594,7820,3631],{"emptyLinePlaceholder":3630},[3594,7822,7823,7825,7828,7830],{"class":3596,"line":3681},[3594,7824,6470],{"class":4455},[3594,7826,7827],{"class":4459}," SafeSession() ",[3594,7829,4597],{"class":4455},[3594,7831,6478],{"class":4459},[3594,7833,7834,7836],{"class":3596,"line":3687},[3594,7835,4940],{"class":4455},[3594,7837,4570],{"class":4459},[3594,7839,7840,7843,7845,7847,7849],{"class":3596,"line":3693},[3594,7841,7842],{"class":4459},"        session.add(User(",[3594,7844,7736],{"class":4493},[3594,7846,4497],{"class":4459},[3594,7848,7741],{"class":4500},[3594,7850,7851],{"class":4459},"))\n",[3594,7853,7854,7856,7858,7860,7862],{"class":3596,"line":3699},[3594,7855,7842],{"class":4459},[3594,7857,7736],{"class":4493},[3594,7859,4497],{"class":4459},[3594,7861,7779],{"class":4500},[3594,7863,7851],{"class":4459},[3594,7865,7866,7869],{"class":3596,"line":3705},[3594,7867,7868],{"class":4459},"        session.commit()  ",[3594,7870,7871],{"class":4482},"# Обидва INSERT у ONE транзакції\n",[3594,7873,7874,7877,7879],{"class":3596,"line":3711},[3594,7875,7876],{"class":4455},"    except",[3594,7878,4794],{"class":4793},[3594,7880,4570],{"class":4459},[3594,7882,7883,7886],{"class":3596,"line":3716},[3594,7884,7885],{"class":4459},"        session.rollback()  ",[3594,7887,7888],{"class":4482},"# Обидва відкочуються — БД у чистому стані\n",[3441,7890],{},[4433,7892,7894],{"id":7893},"autoflush",[3412,7895,7893],{},[3385,7897,7898],{},[3389,7899,7900,7901,7645],{},"Чому у FastAPI зазвичай ставлять ",[3412,7902,6238],{},[3385,7904,5549,7905,7908,7909,7912,7913,7920],{},[3412,7906,7907],{},"autoflush=True",": SQLAlchemy автоматично виконує ",[3412,7910,7911],{},"flush()"," (відправляє накопичені зміни у БД у рамках поточної транзакції) ",[3389,7914,7915,7916,7919],{},"перед кожним ",[3412,7917,7918],{},"SELECT","-запитом"," у межах поточної сесії.",[3385,7922,7923,7924,7926,7927,7929,7930,7933],{},"На перший погляд це зручно: ви завжди бачите актуальні дані навіть до ",[3412,7925,7672],{},". Але у FastAPI-сервісах, де сесія часто передається через DI між кількома шарами (router → service → repository), ",[3412,7928,7907],{}," може призводити до ",[3389,7931,7932],{},"неочікуваних SQL-запитів"," і ускладнює налагодження.",[3385,7935,7936,7939,7940,7943,7944,7946,7947,7950],{},[3389,7937,7938],{},"Рекомендація для FastAPI:"," ",[3412,7941,7942],{},"autoflush=False",". Ви самостійно викликаєте ",[3412,7945,6324],{}," тільки коли це потрібно (наприклад, щоб отримати згенерований ",[3412,7948,7949],{},"id","). Це дає повний контроль і передбачувану поведінку.",[3585,7952,7955],{"className":4445,"code":7953,"filename":7954,"language":4448,"meta":3590,"style":3590},"# ✅ autoflush=True (за замовчуванням у SQLAlchemy)\nAutoFlushSession = sessionmaker(bind=engine, autoflush=True)\n\nwith AutoFlushSession() as session:\n    new_user = User(username=\"charlie\")\n    session.add(new_user)\n    # ↓ Тут ще немає flush()\n\n    # Але при будь-якому SELECT — автоматичний flush перед ним:\n    existing = session.execute(select(User).where(User.username == \"alice\")).scalar()\n    # SQLAlchemy робить:\n    # 1. INSERT INTO users (username) VALUES ('charlie')  ← autoflush!\n    # 2. SELECT * FROM users WHERE username = 'alice'\n\n    # Це зручно, але може бути несподіваним, якщо charlie ще «не готовий»\n    # (наприклад, не встановлено обов'язкове поле email)\n\n# ✅ autoflush=False (рекомендовано для FastAPI)\nManualFlushSession = sessionmaker(bind=engine, autoflush=False)\n\nwith ManualFlushSession() as session:\n    new_user = User(username=\"charlie\")\n    session.add(new_user)\n\n    # SELECT виконується одразу, без autoflush\n    existing = session.execute(select(User).where(User.username == \"alice\")).scalar()\n    # SQL: SELECT * FROM users WHERE username = 'alice'\n    # charlie ще не INSERT-нутий — SELECT не «бачить» його\n\n    # Явний flush лише тоді, коли потрібен id:\n    session.flush()  # INSERT INTO users ...\n    print(new_user.id)  # Тепер id доступний\n\n    session.commit()\n","Різниця між autoflush=True та autoflush=False",[3412,7956,7957,7962,7979,7983,7994,8008,8013,8018,8022,8027,8037,8042,8047,8052,8056,8061,8066,8070,8075,8092,8096,8107,8119,8123,8127,8132,8140,8145,8150,8154,8159,8167,8178,8182],{"__ignoreMap":3590},[3594,7958,7959],{"class":3596,"line":3597},[3594,7960,7961],{"class":4482},"# ✅ autoflush=True (за замовчуванням у SQLAlchemy)\n",[3594,7963,7964,7967,7969,7971,7973,7975,7977],{"class":3596,"line":3603},[3594,7965,7966],{"class":4459},"AutoFlushSession = sessionmaker(",[3594,7968,7463],{"class":4493},[3594,7970,7520],{"class":4459},[3594,7972,7893],{"class":4493},[3594,7974,4497],{"class":4459},[3594,7976,4641],{"class":4622},[3594,7978,4558],{"class":4459},[3594,7980,7981],{"class":3596,"line":3609},[3594,7982,3631],{"emptyLinePlaceholder":3630},[3594,7984,7985,7987,7990,7992],{"class":3596,"line":3615},[3594,7986,6470],{"class":4455},[3594,7988,7989],{"class":4459}," AutoFlushSession() ",[3594,7991,4597],{"class":4455},[3594,7993,6478],{"class":4459},[3594,7995,7996,7999,8001,8003,8006],{"class":3596,"line":3621},[3594,7997,7998],{"class":4459},"    new_user = User(",[3594,8000,7736],{"class":4493},[3594,8002,4497],{"class":4459},[3594,8004,8005],{"class":4500},"\"charlie\"",[3594,8007,4558],{"class":4459},[3594,8009,8010],{"class":3596,"line":3627},[3594,8011,8012],{"class":4459},"    session.add(new_user)\n",[3594,8014,8015],{"class":3596,"line":3634},[3594,8016,8017],{"class":4482},"    # ↓ Тут ще немає flush()\n",[3594,8019,8020],{"class":3596,"line":3640},[3594,8021,3631],{"emptyLinePlaceholder":3630},[3594,8023,8024],{"class":3596,"line":3646},[3594,8025,8026],{"class":4482},"    # Але при будь-якому SELECT — автоматичний flush перед ним:\n",[3594,8028,8029,8032,8034],{"class":3596,"line":3652},[3594,8030,8031],{"class":4459},"    existing = session.execute(select(User).where(User.username == ",[3594,8033,7741],{"class":4500},[3594,8035,8036],{"class":4459},")).scalar()\n",[3594,8038,8039],{"class":3596,"line":3658},[3594,8040,8041],{"class":4482},"    # SQLAlchemy робить:\n",[3594,8043,8044],{"class":3596,"line":3664},[3594,8045,8046],{"class":4482},"    # 1. INSERT INTO users (username) VALUES ('charlie')  ← autoflush!\n",[3594,8048,8049],{"class":3596,"line":3670},[3594,8050,8051],{"class":4482},"    # 2. SELECT * FROM users WHERE username = 'alice'\n",[3594,8053,8054],{"class":3596,"line":3676},[3594,8055,3631],{"emptyLinePlaceholder":3630},[3594,8057,8058],{"class":3596,"line":3681},[3594,8059,8060],{"class":4482},"    # Це зручно, але може бути несподіваним, якщо charlie ще «не готовий»\n",[3594,8062,8063],{"class":3596,"line":3687},[3594,8064,8065],{"class":4482},"    # (наприклад, не встановлено обов'язкове поле email)\n",[3594,8067,8068],{"class":3596,"line":3693},[3594,8069,3631],{"emptyLinePlaceholder":3630},[3594,8071,8072],{"class":3596,"line":3699},[3594,8073,8074],{"class":4482},"# ✅ autoflush=False (рекомендовано для FastAPI)\n",[3594,8076,8077,8080,8082,8084,8086,8088,8090],{"class":3596,"line":3705},[3594,8078,8079],{"class":4459},"ManualFlushSession = sessionmaker(",[3594,8081,7463],{"class":4493},[3594,8083,7520],{"class":4459},[3594,8085,7893],{"class":4493},[3594,8087,4497],{"class":4459},[3594,8089,6238],{"class":4622},[3594,8091,4558],{"class":4459},[3594,8093,8094],{"class":3596,"line":3711},[3594,8095,3631],{"emptyLinePlaceholder":3630},[3594,8097,8098,8100,8103,8105],{"class":3596,"line":3716},[3594,8099,6470],{"class":4455},[3594,8101,8102],{"class":4459}," ManualFlushSession() ",[3594,8104,4597],{"class":4455},[3594,8106,6478],{"class":4459},[3594,8108,8109,8111,8113,8115,8117],{"class":3596,"line":3721},[3594,8110,7998],{"class":4459},[3594,8112,7736],{"class":4493},[3594,8114,4497],{"class":4459},[3594,8116,8005],{"class":4500},[3594,8118,4558],{"class":4459},[3594,8120,8121],{"class":3596,"line":3727},[3594,8122,8012],{"class":4459},[3594,8124,8125],{"class":3596,"line":3733},[3594,8126,3631],{"emptyLinePlaceholder":3630},[3594,8128,8129],{"class":3596,"line":3739},[3594,8130,8131],{"class":4482},"    # SELECT виконується одразу, без autoflush\n",[3594,8133,8134,8136,8138],{"class":3596,"line":3744},[3594,8135,8031],{"class":4459},[3594,8137,7741],{"class":4500},[3594,8139,8036],{"class":4459},[3594,8141,8142],{"class":3596,"line":3749},[3594,8143,8144],{"class":4482},"    # SQL: SELECT * FROM users WHERE username = 'alice'\n",[3594,8146,8147],{"class":3596,"line":3755},[3594,8148,8149],{"class":4482},"    # charlie ще не INSERT-нутий — SELECT не «бачить» його\n",[3594,8151,8152],{"class":3596,"line":3761},[3594,8153,3631],{"emptyLinePlaceholder":3630},[3594,8155,8156],{"class":3596,"line":3766},[3594,8157,8158],{"class":4482},"    # Явний flush лише тоді, коли потрібен id:\n",[3594,8160,8161,8164],{"class":3596,"line":3772},[3594,8162,8163],{"class":4459},"    session.flush()  ",[3594,8165,8166],{"class":4482},"# INSERT INTO users ...\n",[3594,8168,8169,8172,8175],{"class":3596,"line":3778},[3594,8170,8171],{"class":4690},"    print",[3594,8173,8174],{"class":4459},"(new_user.id)  ",[3594,8176,8177],{"class":4482},"# Тепер id доступний\n",[3594,8179,8180],{"class":3596,"line":3784},[3594,8181,3631],{"emptyLinePlaceholder":3630},[3594,8183,8184],{"class":3596,"line":3790},[3594,8185,6501],{"class":4459},[3441,8187],{},[4433,8189,8191],{"id":8190},"expire_on_commit",[3412,8192,8190],{},[3385,8194,8195],{},[3389,8196,8197,8198,8200],{},"Найважливіший параметр для async FastAPI — завжди ставте ",[3412,8199,6238],{},"!",[3385,8202,8203,8204,8206,8207,8209,8210,8213,8214,8216],{},"Після виклику ",[3412,8205,7672],{}," SQLAlchemy за замовчуванням (",[3412,8208,4641],{},") «відмічає» всі об'єкти у сесії як ",[3389,8211,8212],{},"expired"," (застарілі). Це означає: при наступному зверненні до будь-якого атрибуту об'єкта SQLAlchemy виконає новий ",[3412,8215,7918],{},"-запит, щоб «освіжити» дані з БД.",[3385,8218,8219,8222],{},[3389,8220,8221],{},"У синхронному режимі"," це відбувається непомітно — lazy refresh є автоматичним.",[3385,8224,8225,8228,8229,8232,8233,8236],{},[3389,8226,8227],{},"В асинхронному режимі"," це катастрофа: після ",[3412,8230,8231],{},"await session.commit()"," сесія більше не активна в async-контексті. Спроба отримати атрибут expired-об'єкта викличе ",[3412,8234,8235],{},"MissingGreenlet: greenlet_spawn has not been called"," — одна з найпоширеніших помилок початківців у async SQLAlchemy.",[3585,8238,8241],{"className":4445,"code":8239,"filename":8240,"language":4448,"meta":3590,"style":3590},"# --- SYNC: expire_on_commit=True (за замовчуванням) ---\nSyncSession = sessionmaker(bind=engine, expire_on_commit=True)\n\nwith SyncSession() as session:\n    user = session.get(User, 1)\n    user.username = \"updated\"\n    session.commit()\n    # user тепер expired!\n\n    # Але sync-режим «рятує» нас автоматично:\n    print(user.username)\n    # SELECT * FROM users WHERE id = 1  ← автоматичний lazy SELECT!\n    # Виводить: \"updated\"   ✅ Працює, але виконує зайвий запит\n\n# --- ASYNC: expire_on_commit=True — ПОМИЛКА! ---\nBadAsyncSession = async_sessionmaker(bind=async_engine, expire_on_commit=True)\n\nasync with BadAsyncSession() as session:\n    user = await session.get(User, 1)\n    user.username = \"updated\"\n    await session.commit()\n    # user тепер expired!\n\n    print(user.username)\n    # ❌ MissingGreenlet: greenlet_spawn has not been called\n    # Немає активної async-транзакції для lazy SELECT!\n\n# --- ASYNC: expire_on_commit=False — правильно! ---\nGoodAsyncSession = async_sessionmaker(bind=async_engine, expire_on_commit=False)\n\nasync with GoodAsyncSession() as session:\n    user = await session.get(User, 1)\n    user.username = \"updated\"\n    await session.commit()\n    # user НЕ expired — значення в пам'яті збережено\n\n    print(user.username)  # ✅ \"updated\" — з пам'яті Python, без SQL\n","expire_on_commit — sync vs async",[3412,8242,8243,8248,8265,8269,8280,8288,8296,8300,8305,8309,8314,8321,8326,8331,8335,8340,8358,8362,8375,8387,8393,8399,8403,8407,8413,8418,8423,8427,8432,8449,8453,8466,8478,8484,8490,8495,8499],{"__ignoreMap":3590},[3594,8244,8245],{"class":3596,"line":3597},[3594,8246,8247],{"class":4482},"# --- SYNC: expire_on_commit=True (за замовчуванням) ---\n",[3594,8249,8250,8253,8255,8257,8259,8261,8263],{"class":3596,"line":3603},[3594,8251,8252],{"class":4459},"SyncSession = sessionmaker(",[3594,8254,7463],{"class":4493},[3594,8256,7520],{"class":4459},[3594,8258,8190],{"class":4493},[3594,8260,4497],{"class":4459},[3594,8262,4641],{"class":4622},[3594,8264,4558],{"class":4459},[3594,8266,8267],{"class":3596,"line":3609},[3594,8268,3631],{"emptyLinePlaceholder":3630},[3594,8270,8271,8273,8276,8278],{"class":3596,"line":3615},[3594,8272,6470],{"class":4455},[3594,8274,8275],{"class":4459}," SyncSession() ",[3594,8277,4597],{"class":4455},[3594,8279,6478],{"class":4459},[3594,8281,8282,8284,8286],{"class":3596,"line":3621},[3594,8283,6483],{"class":4459},[3594,8285,6486],{"class":4514},[3594,8287,4558],{"class":4459},[3594,8289,8290,8293],{"class":3596,"line":3627},[3594,8291,8292],{"class":4459},"    user.username = ",[3594,8294,8295],{"class":4500},"\"updated\"\n",[3594,8297,8298],{"class":3596,"line":3634},[3594,8299,6501],{"class":4459},[3594,8301,8302],{"class":3596,"line":3640},[3594,8303,8304],{"class":4482},"    # user тепер expired!\n",[3594,8306,8307],{"class":3596,"line":3646},[3594,8308,3631],{"emptyLinePlaceholder":3630},[3594,8310,8311],{"class":3596,"line":3652},[3594,8312,8313],{"class":4482},"    # Але sync-режим «рятує» нас автоматично:\n",[3594,8315,8316,8318],{"class":3596,"line":3658},[3594,8317,8171],{"class":4690},[3594,8319,8320],{"class":4459},"(user.username)\n",[3594,8322,8323],{"class":3596,"line":3664},[3594,8324,8325],{"class":4482},"    # SELECT * FROM users WHERE id = 1  ← автоматичний lazy SELECT!\n",[3594,8327,8328],{"class":3596,"line":3670},[3594,8329,8330],{"class":4482},"    # Виводить: \"updated\"   ✅ Працює, але виконує зайвий запит\n",[3594,8332,8333],{"class":3596,"line":3676},[3594,8334,3631],{"emptyLinePlaceholder":3630},[3594,8336,8337],{"class":3596,"line":3681},[3594,8338,8339],{"class":4482},"# --- ASYNC: expire_on_commit=True — ПОМИЛКА! ---\n",[3594,8341,8342,8345,8347,8350,8352,8354,8356],{"class":3596,"line":3687},[3594,8343,8344],{"class":4459},"BadAsyncSession = async_sessionmaker(",[3594,8346,7463],{"class":4493},[3594,8348,8349],{"class":4459},"=async_engine, ",[3594,8351,8190],{"class":4493},[3594,8353,4497],{"class":4459},[3594,8355,4641],{"class":4622},[3594,8357,4558],{"class":4459},[3594,8359,8360],{"class":3596,"line":3693},[3594,8361,3631],{"emptyLinePlaceholder":3630},[3594,8363,8364,8366,8368,8371,8373],{"class":3596,"line":3699},[3594,8365,4856],{"class":4455},[3594,8367,6606],{"class":4455},[3594,8369,8370],{"class":4459}," BadAsyncSession() ",[3594,8372,4597],{"class":4455},[3594,8374,6478],{"class":4459},[3594,8376,8377,8379,8381,8383,8385],{"class":3596,"line":3705},[3594,8378,6618],{"class":4459},[3594,8380,4878],{"class":4455},[3594,8382,6623],{"class":4459},[3594,8384,6486],{"class":4514},[3594,8386,4558],{"class":4459},[3594,8388,8389,8391],{"class":3596,"line":3711},[3594,8390,8292],{"class":4459},[3594,8392,8295],{"class":4500},[3594,8394,8395,8397],{"class":3596,"line":3716},[3594,8396,6638],{"class":4455},[3594,8398,6641],{"class":4459},[3594,8400,8401],{"class":3596,"line":3721},[3594,8402,8304],{"class":4482},[3594,8404,8405],{"class":3596,"line":3727},[3594,8406,3631],{"emptyLinePlaceholder":3630},[3594,8408,8409,8411],{"class":3596,"line":3733},[3594,8410,8171],{"class":4690},[3594,8412,8320],{"class":4459},[3594,8414,8415],{"class":3596,"line":3739},[3594,8416,8417],{"class":4482},"    # ❌ MissingGreenlet: greenlet_spawn has not been called\n",[3594,8419,8420],{"class":3596,"line":3744},[3594,8421,8422],{"class":4482},"    # Немає активної async-транзакції для lazy SELECT!\n",[3594,8424,8425],{"class":3596,"line":3749},[3594,8426,3631],{"emptyLinePlaceholder":3630},[3594,8428,8429],{"class":3596,"line":3755},[3594,8430,8431],{"class":4482},"# --- ASYNC: expire_on_commit=False — правильно! ---\n",[3594,8433,8434,8437,8439,8441,8443,8445,8447],{"class":3596,"line":3761},[3594,8435,8436],{"class":4459},"GoodAsyncSession = async_sessionmaker(",[3594,8438,7463],{"class":4493},[3594,8440,8349],{"class":4459},[3594,8442,8190],{"class":4493},[3594,8444,4497],{"class":4459},[3594,8446,6238],{"class":4622},[3594,8448,4558],{"class":4459},[3594,8450,8451],{"class":3596,"line":3766},[3594,8452,3631],{"emptyLinePlaceholder":3630},[3594,8454,8455,8457,8459,8462,8464],{"class":3596,"line":3772},[3594,8456,4856],{"class":4455},[3594,8458,6606],{"class":4455},[3594,8460,8461],{"class":4459}," GoodAsyncSession() ",[3594,8463,4597],{"class":4455},[3594,8465,6478],{"class":4459},[3594,8467,8468,8470,8472,8474,8476],{"class":3596,"line":3778},[3594,8469,6618],{"class":4459},[3594,8471,4878],{"class":4455},[3594,8473,6623],{"class":4459},[3594,8475,6486],{"class":4514},[3594,8477,4558],{"class":4459},[3594,8479,8480,8482],{"class":3596,"line":3784},[3594,8481,8292],{"class":4459},[3594,8483,8295],{"class":4500},[3594,8485,8486,8488],{"class":3596,"line":3790},[3594,8487,6638],{"class":4455},[3594,8489,6641],{"class":4459},[3594,8491,8492],{"class":3596,"line":3796},[3594,8493,8494],{"class":4482},"    # user НЕ expired — значення в пам'яті збережено\n",[3594,8496,8497],{"class":3596,"line":3802},[3594,8498,3631],{"emptyLinePlaceholder":3630},[3594,8500,8501,8503,8506],{"class":3596,"line":3808},[3594,8502,8171],{"class":4690},[3594,8504,8505],{"class":4459},"(user.username)  ",[3594,8507,8508],{"class":4482},"# ✅ \"updated\" — з пам'яті Python, без SQL\n",[5540,8510,8511,8514,8515,8517,8518,8521,8522,8525],{},[3412,8512,8513],{},"expire_on_commit=False"," означає, що після ",[3412,8516,7672],{}," атрибути об'єкта ",[3389,8519,8520],{},"можуть не відображати реальний стан БД"," (якщо хтось зовні змінив той самий запис). У FastAPI-додатках це не є проблемою — кожен HTTP-запит отримує свіжу сесію та свіжий SELECT. Але якщо ваша логіка потребує «свіжих» даних після commit — виконайте ",[3412,8523,8524],{},"await session.refresh(obj)"," вручну.",[3441,8527],{},[4433,8529,8531,8534,8535,3544],{"id":8530},"class_-лише-для-async_sessionmaker",[3412,8532,8533],{},"class_"," (лише для ",[3412,8536,6336],{},[3385,8538,8539,8540,8542,8543,8546,8547,8549],{},"Визначає конкретний клас, який буде створювати фабрика. Для ",[3412,8541,6336],{}," потрібно явно передати ",[3412,8544,8545],{},"class*=AsyncSession",". Це дозволяє підставляти власні підкласи ",[3412,8548,3426],{}," з кастомною логікою (наприклад, автоматичним логуванням або метриками).",[3585,8551,8554],{"className":4445,"code":8552,"filename":8553,"language":4448,"meta":3590,"style":3590},"from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker\n\nclass LoggedSession(AsyncSession):\n    \"\"\"AsyncSession з логуванням кожного коміту.\"\"\"\n    async def commit(self):\n        print(f\"[DB] Committing transaction in session {id(self)}\")\n        await super().commit()\n\nLoggedSessionLocal = async_sessionmaker(\n    bind=async_engine,\n    class_=LoggedSession,  # Використовуємо наш кастомний клас\n    expire_on_commit=False,\n)\n\nasync with LoggedSessionLocal() as session:\n    # ... операції ...\n    await session.commit()\n    # Виведе: [DB] Committing transaction in session 140234567890\n","class_ — кастомна сесія",[3412,8555,8556,8567,8571,8586,8591,8608,8639,8649,8653,8658,8664,8674,8684,8688,8692,8705,8710,8716],{"__ignoreMap":3590},[3594,8557,8558,8560,8562,8564],{"class":3596,"line":3597},[3594,8559,4465],{"class":4455},[3594,8561,5782],{"class":4459},[3594,8563,4456],{"class":4455},[3594,8565,8566],{"class":4459}," AsyncSession, async_sessionmaker\n",[3594,8568,8569],{"class":3596,"line":3603},[3594,8570,3631],{"emptyLinePlaceholder":3630},[3594,8572,8573,8576,8579,8581,8583],{"class":3596,"line":3609},[3594,8574,8575],{"class":4622},"class",[3594,8577,8578],{"class":4793}," LoggedSession",[3594,8580,7763],{"class":4459},[3594,8582,3426],{"class":4793},[3594,8584,8585],{"class":4459},"):\n",[3594,8587,8588],{"class":3596,"line":3615},[3594,8589,8590],{"class":4500},"    \"\"\"AsyncSession з логуванням кожного коміту.\"\"\"\n",[3594,8592,8593,8596,8598,8601,8603,8606],{"class":3596,"line":3621},[3594,8594,8595],{"class":4622},"    async",[3594,8597,4859],{"class":4622},[3594,8599,8600],{"class":4690}," commit",[3594,8602,7763],{"class":4459},[3594,8604,8605],{"class":4493},"self",[3594,8607,8585],{"class":4459},[3594,8609,8610,8613,8615,8618,8621,8624,8626,8628,8630,8632,8635,8637],{"class":3596,"line":3627},[3594,8611,8612],{"class":4690},"        print",[3594,8614,7763],{"class":4459},[3594,8616,8617],{"class":4622},"f",[3594,8619,8620],{"class":4500},"\"[DB] Committing transaction in session ",[3594,8622,8623],{"class":4622},"{",[3594,8625,7949],{"class":4690},[3594,8627,7763],{"class":4459},[3594,8629,8605],{"class":4622},[3594,8631,3544],{"class":4459},[3594,8633,8634],{"class":4622},"}",[3594,8636,4631],{"class":4500},[3594,8638,4558],{"class":4459},[3594,8640,8641,8643,8646],{"class":3596,"line":3634},[3594,8642,5056],{"class":4455},[3594,8644,8645],{"class":4793}," super",[3594,8647,8648],{"class":4459},"().commit()\n",[3594,8650,8651],{"class":3596,"line":3640},[3594,8652,3631],{"emptyLinePlaceholder":3630},[3594,8654,8655],{"class":3596,"line":3646},[3594,8656,8657],{"class":4459},"LoggedSessionLocal = async_sessionmaker(\n",[3594,8659,8660,8662],{"class":3596,"line":3652},[3594,8661,6416],{"class":4493},[3594,8663,6548],{"class":4459},[3594,8665,8666,8668,8671],{"class":3596,"line":3658},[3594,8667,6553],{"class":4493},[3594,8669,8670],{"class":4459},"=LoggedSession,  ",[3594,8672,8673],{"class":4482},"# Використовуємо наш кастомний клас\n",[3594,8675,8676,8678,8680,8682],{"class":3596,"line":3664},[3594,8677,6446],{"class":4493},[3594,8679,4497],{"class":4459},[3594,8681,6238],{"class":4622},[3594,8683,4504],{"class":4459},[3594,8685,8686],{"class":3596,"line":3670},[3594,8687,4558],{"class":4459},[3594,8689,8690],{"class":3596,"line":3676},[3594,8691,3631],{"emptyLinePlaceholder":3630},[3594,8693,8694,8696,8698,8701,8703],{"class":3596,"line":3681},[3594,8695,4856],{"class":4455},[3594,8697,6606],{"class":4455},[3594,8699,8700],{"class":4459}," LoggedSessionLocal() ",[3594,8702,4597],{"class":4455},[3594,8704,6478],{"class":4459},[3594,8706,8707],{"class":3596,"line":3687},[3594,8708,8709],{"class":4482},"    # ... операції ...\n",[3594,8711,8712,8714],{"class":3596,"line":3693},[3594,8713,6638],{"class":4455},[3594,8715,6641],{"class":4459},[3594,8717,8718],{"class":3596,"line":3699},[3594,8719,8720],{"class":4482},"    # Виведе: [DB] Committing transaction in session 140234567890\n",[3441,8722],{},[4433,8724,8726],{"id":8725},"підсумковий-конфіг-для-production-fastapi","Підсумковий конфіг для production FastAPI",[3385,8728,8729,8730,8732],{},"Ось канонічна конфігурація ",[3412,8731,6336],{}," для production FastAPI-додатку на основі всього вищесказаного:",[3585,8734,8737],{"className":4445,"code":8735,"filename":8736,"language":4448,"meta":3590,"style":3590},"from sqlalchemy.ext.asyncio import (\n    create_async_engine,\n    async_sessionmaker,\n    AsyncSession,\n)\n\nasync_engine = create_async_engine(\n    \"postgresql+asyncpg:\u002F\u002Fuser:password@localhost:5432\u002Ftaskforge_db\",\n    pool_size=10,\n    max_overflow=20,\n    pool_recycle=1800,\n    echo=False,\n)\n\nAsyncSessionLocal = async_sessionmaker(\n    bind=async_engine,\n    class_=AsyncSession,\n    autocommit=False,    # ✅ Явний контроль транзакцій (ACID-безпека)\n    autoflush=False,     # ✅ Явний flush() — передбачувана поведінка\n    expire_on_commit=False,  # ✅ Обов'язково для async FastAPI\n)\n","core\u002Fdatabase.py — production config",[3412,8738,8739,8750,8755,8760,8765,8769,8773,8777,8784,8794,8804,8814,8824,8828,8832,8836,8842,8848,8861,8874,8887],{"__ignoreMap":3590},[3594,8740,8741,8743,8745,8747],{"class":3596,"line":3597},[3594,8742,4465],{"class":4455},[3594,8744,5782],{"class":4459},[3594,8746,4456],{"class":4455},[3594,8748,8749],{"class":4459}," (\n",[3594,8751,8752],{"class":3596,"line":3603},[3594,8753,8754],{"class":4459},"    create_async_engine,\n",[3594,8756,8757],{"class":3596,"line":3609},[3594,8758,8759],{"class":4459},"    async_sessionmaker,\n",[3594,8761,8762],{"class":3596,"line":3615},[3594,8763,8764],{"class":4459},"    AsyncSession,\n",[3594,8766,8767],{"class":3596,"line":3621},[3594,8768,4558],{"class":4459},[3594,8770,8771],{"class":3596,"line":3627},[3594,8772,3631],{"emptyLinePlaceholder":3630},[3594,8774,8775],{"class":3596,"line":3634},[3594,8776,5500],{"class":4459},[3594,8778,8779,8782],{"class":3596,"line":3640},[3594,8780,8781],{"class":4500},"    \"postgresql+asyncpg:\u002F\u002Fuser:password@localhost:5432\u002Ftaskforge_db\"",[3594,8783,4504],{"class":4459},[3594,8785,8786,8788,8790,8792],{"class":3596,"line":3646},[3594,8787,6041],{"class":4493},[3594,8789,4497],{"class":4459},[3594,8791,4646],{"class":4514},[3594,8793,4504],{"class":4459},[3594,8795,8796,8798,8800,8802],{"class":3596,"line":3652},[3594,8797,6056],{"class":4493},[3594,8799,4497],{"class":4459},[3594,8801,6061],{"class":4514},[3594,8803,4504],{"class":4459},[3594,8805,8806,8808,8810,8812],{"class":3596,"line":3658},[3594,8807,6087],{"class":4493},[3594,8809,4497],{"class":4459},[3594,8811,6092],{"class":4514},[3594,8813,4504],{"class":4459},[3594,8815,8816,8818,8820,8822],{"class":3596,"line":3664},[3594,8817,6112],{"class":4493},[3594,8819,4497],{"class":4459},[3594,8821,6238],{"class":4622},[3594,8823,4504],{"class":4459},[3594,8825,8826],{"class":3596,"line":3670},[3594,8827,4558],{"class":4459},[3594,8829,8830],{"class":3596,"line":3676},[3594,8831,3631],{"emptyLinePlaceholder":3630},[3594,8833,8834],{"class":3596,"line":3681},[3594,8835,6541],{"class":4459},[3594,8837,8838,8840],{"class":3596,"line":3687},[3594,8839,6416],{"class":4493},[3594,8841,6548],{"class":4459},[3594,8843,8844,8846],{"class":3596,"line":3693},[3594,8845,6553],{"class":4493},[3594,8847,6556],{"class":4459},[3594,8849,8850,8852,8854,8856,8858],{"class":3596,"line":3699},[3594,8851,6424],{"class":4493},[3594,8853,4497],{"class":4459},[3594,8855,6238],{"class":4622},[3594,8857,6064],{"class":4459},[3594,8859,8860],{"class":4482},"# ✅ Явний контроль транзакцій (ACID-безпека)\n",[3594,8862,8863,8865,8867,8869,8871],{"class":3596,"line":3705},[3594,8864,6435],{"class":4493},[3594,8866,4497],{"class":4459},[3594,8868,6238],{"class":4622},[3594,8870,7136],{"class":4459},[3594,8872,8873],{"class":4482},"# ✅ Явний flush() — передбачувана поведінка\n",[3594,8875,8876,8878,8880,8882,8884],{"class":3596,"line":3711},[3594,8877,6446],{"class":4493},[3594,8879,4497],{"class":4459},[3594,8881,6238],{"class":4622},[3594,8883,4976],{"class":4459},[3594,8885,8886],{"class":4482},"# ✅ Обов'язково для async FastAPI\n",[3594,8888,8889],{"class":3596,"line":3716},[3594,8890,4558],{"class":4459},[3441,8892],{},[3444,8894,8896],{"id":8895},"orm-моделі-у-sqlalchemy-20-новий-python-first-синтаксис","ORM-Моделі у SQLAlchemy 2.0: Новий «Python-first» синтаксис",[3385,8898,8899],{},"Якщо у вас є досвід роботи зі SQLAlchemy 1.x, перший погляд на код SQLAlchemy 2.0 може викликати легкий шок — він виглядає зовсім інакше. Це навмисна зміна: команда SQLAlchemy чітко взяла курс на максимальну інтеграцію з сучасним Python-синтаксисом типізації, наслідуючи ідеї Pydantic та dataclasses.",[3845,8901,8903],{"id":8902},"еволюція-синтаксису-від-1x-до-20","Еволюція синтаксису: від 1.x до 2.0",[3385,8905,8906],{},"Щоб оцінити масштаб змін, подивимося на одну і ту саму модель у різних синтаксисах:",[5448,8908,8909,9089],{},[3585,8910,8913],{"className":4445,"code":8911,"filename":8912,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import Column, Integer, String, Boolean\nfrom sqlalchemy.ext.declarative import declarative_base\n\nBase = declarative_base()  # Стара функція\n\nclass User(Base):\n    __tablename__ = \"users\"\n\n    id = Column(Integer, primary_key=True, index=True)\n    username = Column(String(50), unique=True, nullable=False)\n    email = Column(String(100), unique=True, nullable=False)\n    is_active = Column(Boolean, default=True)\n\n    # ❌ Немає type hints — IDE не знає тип атрибутів\n    # ❌ user.id може бути int або None — незрозуміло без читання Column()\n","SQLAlchemy 1.x (застарілий)",[3412,8914,8915,8926,8938,8942,8950,8954,8968,8976,8980,9006,9035,9061,9075,9079,9084],{"__ignoreMap":3590},[3594,8916,8917,8919,8921,8923],{"class":3596,"line":3597},[3594,8918,4465],{"class":4455},[3594,8920,5996],{"class":4459},[3594,8922,4456],{"class":4455},[3594,8924,8925],{"class":4459}," Column, Integer, String, Boolean\n",[3594,8927,8928,8930,8933,8935],{"class":3596,"line":3603},[3594,8929,4465],{"class":4455},[3594,8931,8932],{"class":4459}," sqlalchemy.ext.declarative ",[3594,8934,4456],{"class":4455},[3594,8936,8937],{"class":4459}," declarative_base\n",[3594,8939,8940],{"class":3596,"line":3609},[3594,8941,3631],{"emptyLinePlaceholder":3630},[3594,8943,8944,8947],{"class":3596,"line":3615},[3594,8945,8946],{"class":4459},"Base = declarative_base()  ",[3594,8948,8949],{"class":4482},"# Стара функція\n",[3594,8951,8952],{"class":3596,"line":3621},[3594,8953,3631],{"emptyLinePlaceholder":3630},[3594,8955,8956,8958,8961,8963,8966],{"class":3596,"line":3627},[3594,8957,8575],{"class":4622},[3594,8959,8960],{"class":4793}," User",[3594,8962,7763],{"class":4459},[3594,8964,8965],{"class":4793},"Base",[3594,8967,8585],{"class":4459},[3594,8969,8970,8973],{"class":3596,"line":3634},[3594,8971,8972],{"class":4459},"    __tablename__ = ",[3594,8974,8975],{"class":4500},"\"users\"\n",[3594,8977,8978],{"class":3596,"line":3640},[3594,8979,3631],{"emptyLinePlaceholder":3630},[3594,8981,8982,8985,8988,8991,8993,8995,8997,9000,9002,9004],{"class":3596,"line":3646},[3594,8983,8984],{"class":4690},"    id",[3594,8986,8987],{"class":4459}," = Column(Integer, ",[3594,8989,8990],{"class":4493},"primary_key",[3594,8992,4497],{"class":4459},[3594,8994,4641],{"class":4622},[3594,8996,3488],{"class":4459},[3594,8998,8999],{"class":4493},"index",[3594,9001,4497],{"class":4459},[3594,9003,4641],{"class":4622},[3594,9005,4558],{"class":4459},[3594,9007,9008,9011,9014,9017,9020,9022,9024,9026,9029,9031,9033],{"class":3596,"line":3652},[3594,9009,9010],{"class":4459},"    username = Column(String(",[3594,9012,9013],{"class":4514},"50",[3594,9015,9016],{"class":4459},"), ",[3594,9018,9019],{"class":4493},"unique",[3594,9021,4497],{"class":4459},[3594,9023,4641],{"class":4622},[3594,9025,3488],{"class":4459},[3594,9027,9028],{"class":4493},"nullable",[3594,9030,4497],{"class":4459},[3594,9032,6238],{"class":4622},[3594,9034,4558],{"class":4459},[3594,9036,9037,9040,9043,9045,9047,9049,9051,9053,9055,9057,9059],{"class":3596,"line":3658},[3594,9038,9039],{"class":4459},"    email = Column(String(",[3594,9041,9042],{"class":4514},"100",[3594,9044,9016],{"class":4459},[3594,9046,9019],{"class":4493},[3594,9048,4497],{"class":4459},[3594,9050,4641],{"class":4622},[3594,9052,3488],{"class":4459},[3594,9054,9028],{"class":4493},[3594,9056,4497],{"class":4459},[3594,9058,6238],{"class":4622},[3594,9060,4558],{"class":4459},[3594,9062,9063,9066,9069,9071,9073],{"class":3596,"line":3664},[3594,9064,9065],{"class":4459},"    is_active = Column(Boolean, ",[3594,9067,9068],{"class":4493},"default",[3594,9070,4497],{"class":4459},[3594,9072,4641],{"class":4622},[3594,9074,4558],{"class":4459},[3594,9076,9077],{"class":3596,"line":3670},[3594,9078,3631],{"emptyLinePlaceholder":3630},[3594,9080,9081],{"class":3596,"line":3676},[3594,9082,9083],{"class":4482},"    # ❌ Немає type hints — IDE не знає тип атрибутів\n",[3594,9085,9086],{"class":3596,"line":3681},[3594,9087,9088],{"class":4482},"    # ❌ user.id може бути int або None — незрозуміло без читання Column()\n",[3585,9090,9093],{"className":4445,"code":9091,"filename":9092,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import String\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column\n\nclass Base(DeclarativeBase):  # Новий стиль — клас, а не виклик функції\n    pass\n\nclass User(Base):\n    __tablename__ = \"users\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    username: Mapped[str] = mapped_column(String(50), unique=True)\n    email: Mapped[str] = mapped_column(String(100), unique=True)\n    is_active: Mapped[bool] = mapped_column(default=True)\n\n    # ✅ Повні type hints — IDE точно знає типи\n    # ✅ Mapped[int] означає NOT NULL, Mapped[int | None] — NULLABLE\n","SQLAlchemy 2.0 (сучасний)",[3412,9094,9095,9106,9117,9121,9138,9143,9147,9159,9165,9169,9189,9212,9233,9250,9254,9259],{"__ignoreMap":3590},[3594,9096,9097,9099,9101,9103],{"class":3596,"line":3597},[3594,9098,4465],{"class":4455},[3594,9100,5996],{"class":4459},[3594,9102,4456],{"class":4455},[3594,9104,9105],{"class":4459}," String\n",[3594,9107,9108,9110,9112,9114],{"class":3596,"line":3603},[3594,9109,4465],{"class":4455},[3594,9111,6383],{"class":4459},[3594,9113,4456],{"class":4455},[3594,9115,9116],{"class":4459}," DeclarativeBase, Mapped, mapped_column\n",[3594,9118,9119],{"class":3596,"line":3609},[3594,9120,3631],{"emptyLinePlaceholder":3630},[3594,9122,9123,9125,9128,9130,9132,9135],{"class":3596,"line":3615},[3594,9124,8575],{"class":4622},[3594,9126,9127],{"class":4793}," Base",[3594,9129,7763],{"class":4459},[3594,9131,3931],{"class":4793},[3594,9133,9134],{"class":4459},"):  ",[3594,9136,9137],{"class":4482},"# Новий стиль — клас, а не виклик функції\n",[3594,9139,9140],{"class":3596,"line":3621},[3594,9141,9142],{"class":4455},"    pass\n",[3594,9144,9145],{"class":3596,"line":3627},[3594,9146,3631],{"emptyLinePlaceholder":3630},[3594,9148,9149,9151,9153,9155,9157],{"class":3596,"line":3634},[3594,9150,8575],{"class":4622},[3594,9152,8960],{"class":4793},[3594,9154,7763],{"class":4459},[3594,9156,8965],{"class":4793},[3594,9158,8585],{"class":4459},[3594,9160,9161,9163],{"class":3596,"line":3640},[3594,9162,8972],{"class":4459},[3594,9164,8975],{"class":4500},[3594,9166,9167],{"class":3596,"line":3646},[3594,9168,3631],{"emptyLinePlaceholder":3630},[3594,9170,9171,9173,9176,9178,9181,9183,9185,9187],{"class":3596,"line":3652},[3594,9172,8984],{"class":4690},[3594,9174,9175],{"class":4459},": Mapped[",[3594,9177,6930],{"class":4793},[3594,9179,9180],{"class":4459},"] = mapped_column(",[3594,9182,8990],{"class":4493},[3594,9184,4497],{"class":4459},[3594,9186,4641],{"class":4622},[3594,9188,4558],{"class":4459},[3594,9190,9191,9194,9197,9200,9202,9204,9206,9208,9210],{"class":3596,"line":3658},[3594,9192,9193],{"class":4459},"    username: Mapped[",[3594,9195,9196],{"class":4793},"str",[3594,9198,9199],{"class":4459},"] = mapped_column(String(",[3594,9201,9013],{"class":4514},[3594,9203,9016],{"class":4459},[3594,9205,9019],{"class":4493},[3594,9207,4497],{"class":4459},[3594,9209,4641],{"class":4622},[3594,9211,4558],{"class":4459},[3594,9213,9214,9217,9219,9221,9223,9225,9227,9229,9231],{"class":3596,"line":3664},[3594,9215,9216],{"class":4459},"    email: Mapped[",[3594,9218,9196],{"class":4793},[3594,9220,9199],{"class":4459},[3594,9222,9042],{"class":4514},[3594,9224,9016],{"class":4459},[3594,9226,9019],{"class":4493},[3594,9228,4497],{"class":4459},[3594,9230,4641],{"class":4622},[3594,9232,4558],{"class":4459},[3594,9234,9235,9238,9240,9242,9244,9246,9248],{"class":3596,"line":3670},[3594,9236,9237],{"class":4459},"    is_active: Mapped[",[3594,9239,7038],{"class":4793},[3594,9241,9180],{"class":4459},[3594,9243,9068],{"class":4493},[3594,9245,4497],{"class":4459},[3594,9247,4641],{"class":4622},[3594,9249,4558],{"class":4459},[3594,9251,9252],{"class":3596,"line":3676},[3594,9253,3631],{"emptyLinePlaceholder":3630},[3594,9255,9256],{"class":3596,"line":3681},[3594,9257,9258],{"class":4482},"    # ✅ Повні type hints — IDE точно знає типи\n",[3594,9260,9261],{"class":3596,"line":3687},[3594,9262,9263],{"class":4482},"    # ✅ Mapped[int] означає NOT NULL, Mapped[int | None] — NULLABLE\n",[3385,9265,9266,9267,4442],{},"Різниця кардинальна. У новому синтаксисі тип стовпця (nullable\u002Fnot-nullable) тепер виводиться ",[3389,9268,9269],{},"автоматично з типової анотації",[3510,9271,9272,9281,9292,9300],{},[3455,9273,9274,9277,9278],{},[3412,9275,9276],{},"Mapped[int]"," → ",[3412,9279,9280],{},"INTEGER NOT NULL",[3455,9282,9283,3461,9286,9277,9289],{},[3412,9284,9285],{},"Mapped[int | None]",[3412,9287,9288],{},"Mapped[Optional[int]]",[3412,9290,9291],{},"INTEGER NULL",[3455,9293,9294,9277,9297],{},[3412,9295,9296],{},"Mapped[str]",[3412,9298,9299],{},"VARCHAR NOT NULL",[3455,9301,9302,9277,9305],{},[3412,9303,9304],{},"Mapped[bool]",[3412,9306,9307],{},"BOOLEAN NOT NULL",[3845,9309,9311,9313],{"id":9310},"declarativebase-базовий-клас-нового-покоління",[3412,9312,3931],{},": Базовий клас нового покоління",[3385,9315,9316,9317,9321,9322,3439],{},"У SQLAlchemy 2.0 рекомендованим способом створення базового класу для всіх моделей є успадкування від ",[3389,9318,9319],{},[3412,9320,3931],{}," замість виклику застарілої функції ",[3412,9323,9324],{},"declarative_base()",[3585,9326,9329],{"className":4445,"code":9327,"filename":9328,"language":4448,"meta":3590,"style":3590},"from sqlalchemy.orm import DeclarativeBase\n\nclass Base(DeclarativeBase):\n    \"\"\"\n    Базовий клас для всіх ORM-моделей проєкту.\n    Всі моделі мають успадковуватися від цього класу.\n    \"\"\"\n    pass\n","models\u002Fbase.py",[3412,9330,9331,9342,9346,9358,9363,9368,9373,9377],{"__ignoreMap":3590},[3594,9332,9333,9335,9337,9339],{"class":3596,"line":3597},[3594,9334,4465],{"class":4455},[3594,9336,6383],{"class":4459},[3594,9338,4456],{"class":4455},[3594,9340,9341],{"class":4459}," DeclarativeBase\n",[3594,9343,9344],{"class":3596,"line":3603},[3594,9345,3631],{"emptyLinePlaceholder":3630},[3594,9347,9348,9350,9352,9354,9356],{"class":3596,"line":3609},[3594,9349,8575],{"class":4622},[3594,9351,9127],{"class":4793},[3594,9353,7763],{"class":4459},[3594,9355,3931],{"class":4793},[3594,9357,8585],{"class":4459},[3594,9359,9360],{"class":3596,"line":3615},[3594,9361,9362],{"class":4500},"    \"\"\"\n",[3594,9364,9365],{"class":3596,"line":3621},[3594,9366,9367],{"class":4500},"    Базовий клас для всіх ORM-моделей проєкту.\n",[3594,9369,9370],{"class":3596,"line":3627},[3594,9371,9372],{"class":4500},"    Всі моделі мають успадковуватися від цього класу.\n",[3594,9374,9375],{"class":3596,"line":3634},[3594,9376,9362],{"class":4500},[3594,9378,9379],{"class":3596,"line":3640},[3594,9380,9142],{"class":4455},[3385,9382,9383],{},"Ця проста конструкція робить кілька речей одночасно:",[3452,9385,9386,9399,9408],{},[3455,9387,9388,9391,9392,9394,9395,9398],{},[3389,9389,9390],{},"Реєструє метадані"," — кожен підклас ",[3412,9393,8965],{}," автоматично реєструє своє визначення таблиці у ",[3412,9396,9397],{},"Base.metadata",", що використовується при міграціях Alembic.",[3455,9400,9401,9404,9405,9407],{},[3389,9402,9403],{},"Забезпечує підтримку type checking"," — сучасні IDE (PyCharm, VS Code з Pylance) повністю розуміють ",[3412,9406,3434],{}," і надають автодоповнення та перевірку типів.",[3455,9409,9410,9413,9414,9417,9418,9421],{},[3389,9411,9412],{},"Дозволяє типізовану конфігурацію"," через ",[3412,9415,9416],{},"__class_getitem__"," механізм — можна визначати ",[3412,9419,9420],{},"type_annotation_map"," для кастомних типів.",[3845,9423,9425,9427],{"id":9424},"mapped_column-детальна-конфігурація-стовпців",[3412,9426,3438],{},": Детальна конфігурація стовпців",[3385,9429,5980,9430,9434,9435,9437,9438,9440,9441,9443,9444,3465],{},[3389,9431,9432],{},[3412,9433,3438],{}," є аналогом старого ",[3412,9436,3414],{},", але спроєктована для роботи разом із анотаціями ",[3412,9439,3434],{},". Вона приймає всі ті самі аргументи, що і ",[3412,9442,3414],{},", але вже не потребує явного вказання типу (він виводиться з ",[3412,9445,3434],{},[3385,9447,9448,9449,9452],{},"Розглянемо повний приклад моделі ",[3412,9450,9451],{},"User"," з усіма типовими сценаріями:",[3585,9454,9457],{"className":4445,"code":9455,"filename":9456,"language":4448,"meta":3590,"style":3590},"from datetime import datetime\nfrom sqlalchemy import String, Text, func\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column\n\nclass Base(DeclarativeBase):\n    pass\n\nclass User(Base):\n    __tablename__ = \"users\"\n\n    # --- Первинний ключ ---\n    id: Mapped[int] = mapped_column(primary_key=True)\n\n    # --- Рядкові поля з обмеженнями ---\n    username: Mapped[str] = mapped_column(String(50), unique=True)\n    email: Mapped[str] = mapped_column(String(100), unique=True, index=True)\n\n    # --- Nullable поле (опціональне) ---\n    # Mapped[str | None] → VARCHAR NULL (поле може бути пустим)\n    bio: Mapped[str | None] = mapped_column(Text, default=None)\n\n    # --- Поле з серверним значенням за замовчуванням ---\n    # server_default=func.now() → DEFAULT NOW() виконується на стороні PostgreSQL\n    created_at: Mapped[datetime] = mapped_column(\n        server_default=func.now()\n    )\n\n    # --- Поле з клієнтським значенням за замовчуванням ---\n    # default=True → значення встановлюється Python-кодом, не SQL\n    is_active: Mapped[bool] = mapped_column(default=True)\n\n    def __repr__(self) -> str:\n        return f\"\u003CUser id={self.id} username={self.username!r}>\"\n","models\u002Fuser.py",[3412,9458,9459,9471,9482,9492,9496,9508,9512,9516,9528,9534,9538,9543,9561,9565,9570,9590,9618,9622,9627,9632,9656,9660,9665,9670,9675,9683,9687,9691,9696,9701,9717,9721,9740],{"__ignoreMap":3590},[3594,9460,9461,9463,9466,9468],{"class":3596,"line":3597},[3594,9462,4465],{"class":4455},[3594,9464,9465],{"class":4459}," datetime ",[3594,9467,4456],{"class":4455},[3594,9469,9470],{"class":4459}," datetime\n",[3594,9472,9473,9475,9477,9479],{"class":3596,"line":3603},[3594,9474,4465],{"class":4455},[3594,9476,5996],{"class":4459},[3594,9478,4456],{"class":4455},[3594,9480,9481],{"class":4459}," String, Text, func\n",[3594,9483,9484,9486,9488,9490],{"class":3596,"line":3609},[3594,9485,4465],{"class":4455},[3594,9487,6383],{"class":4459},[3594,9489,4456],{"class":4455},[3594,9491,9116],{"class":4459},[3594,9493,9494],{"class":3596,"line":3615},[3594,9495,3631],{"emptyLinePlaceholder":3630},[3594,9497,9498,9500,9502,9504,9506],{"class":3596,"line":3621},[3594,9499,8575],{"class":4622},[3594,9501,9127],{"class":4793},[3594,9503,7763],{"class":4459},[3594,9505,3931],{"class":4793},[3594,9507,8585],{"class":4459},[3594,9509,9510],{"class":3596,"line":3627},[3594,9511,9142],{"class":4455},[3594,9513,9514],{"class":3596,"line":3634},[3594,9515,3631],{"emptyLinePlaceholder":3630},[3594,9517,9518,9520,9522,9524,9526],{"class":3596,"line":3640},[3594,9519,8575],{"class":4622},[3594,9521,8960],{"class":4793},[3594,9523,7763],{"class":4459},[3594,9525,8965],{"class":4793},[3594,9527,8585],{"class":4459},[3594,9529,9530,9532],{"class":3596,"line":3646},[3594,9531,8972],{"class":4459},[3594,9533,8975],{"class":4500},[3594,9535,9536],{"class":3596,"line":3652},[3594,9537,3631],{"emptyLinePlaceholder":3630},[3594,9539,9540],{"class":3596,"line":3658},[3594,9541,9542],{"class":4482},"    # --- Первинний ключ ---\n",[3594,9544,9545,9547,9549,9551,9553,9555,9557,9559],{"class":3596,"line":3664},[3594,9546,8984],{"class":4690},[3594,9548,9175],{"class":4459},[3594,9550,6930],{"class":4793},[3594,9552,9180],{"class":4459},[3594,9554,8990],{"class":4493},[3594,9556,4497],{"class":4459},[3594,9558,4641],{"class":4622},[3594,9560,4558],{"class":4459},[3594,9562,9563],{"class":3596,"line":3670},[3594,9564,3631],{"emptyLinePlaceholder":3630},[3594,9566,9567],{"class":3596,"line":3676},[3594,9568,9569],{"class":4482},"    # --- Рядкові поля з обмеженнями ---\n",[3594,9571,9572,9574,9576,9578,9580,9582,9584,9586,9588],{"class":3596,"line":3681},[3594,9573,9193],{"class":4459},[3594,9575,9196],{"class":4793},[3594,9577,9199],{"class":4459},[3594,9579,9013],{"class":4514},[3594,9581,9016],{"class":4459},[3594,9583,9019],{"class":4493},[3594,9585,4497],{"class":4459},[3594,9587,4641],{"class":4622},[3594,9589,4558],{"class":4459},[3594,9591,9592,9594,9596,9598,9600,9602,9604,9606,9608,9610,9612,9614,9616],{"class":3596,"line":3687},[3594,9593,9216],{"class":4459},[3594,9595,9196],{"class":4793},[3594,9597,9199],{"class":4459},[3594,9599,9042],{"class":4514},[3594,9601,9016],{"class":4459},[3594,9603,9019],{"class":4493},[3594,9605,4497],{"class":4459},[3594,9607,4641],{"class":4622},[3594,9609,3488],{"class":4459},[3594,9611,8999],{"class":4493},[3594,9613,4497],{"class":4459},[3594,9615,4641],{"class":4622},[3594,9617,4558],{"class":4459},[3594,9619,9620],{"class":3596,"line":3693},[3594,9621,3631],{"emptyLinePlaceholder":3630},[3594,9623,9624],{"class":3596,"line":3699},[3594,9625,9626],{"class":4482},"    # --- Nullable поле (опціональне) ---\n",[3594,9628,9629],{"class":3596,"line":3705},[3594,9630,9631],{"class":4482},"    # Mapped[str | None] → VARCHAR NULL (поле може бути пустим)\n",[3594,9633,9634,9637,9639,9642,9645,9648,9650,9652,9654],{"class":3596,"line":3711},[3594,9635,9636],{"class":4459},"    bio: Mapped[",[3594,9638,9196],{"class":4793},[3594,9640,9641],{"class":4459}," | ",[3594,9643,9644],{"class":4622},"None",[3594,9646,9647],{"class":4459},"] = mapped_column(Text, ",[3594,9649,9068],{"class":4493},[3594,9651,4497],{"class":4459},[3594,9653,9644],{"class":4622},[3594,9655,4558],{"class":4459},[3594,9657,9658],{"class":3596,"line":3716},[3594,9659,3631],{"emptyLinePlaceholder":3630},[3594,9661,9662],{"class":3596,"line":3721},[3594,9663,9664],{"class":4482},"    # --- Поле з серверним значенням за замовчуванням ---\n",[3594,9666,9667],{"class":3596,"line":3727},[3594,9668,9669],{"class":4482},"    # server_default=func.now() → DEFAULT NOW() виконується на стороні PostgreSQL\n",[3594,9671,9672],{"class":3596,"line":3733},[3594,9673,9674],{"class":4459},"    created_at: Mapped[datetime] = mapped_column(\n",[3594,9676,9677,9680],{"class":3596,"line":3739},[3594,9678,9679],{"class":4493},"        server_default",[3594,9681,9682],{"class":4459},"=func.now()\n",[3594,9684,9685],{"class":3596,"line":3744},[3594,9686,4935],{"class":4459},[3594,9688,9689],{"class":3596,"line":3749},[3594,9690,3631],{"emptyLinePlaceholder":3630},[3594,9692,9693],{"class":3596,"line":3755},[3594,9694,9695],{"class":4482},"    # --- Поле з клієнтським значенням за замовчуванням ---\n",[3594,9697,9698],{"class":3596,"line":3761},[3594,9699,9700],{"class":4482},"    # default=True → значення встановлюється Python-кодом, не SQL\n",[3594,9702,9703,9705,9707,9709,9711,9713,9715],{"class":3596,"line":3766},[3594,9704,9237],{"class":4459},[3594,9706,7038],{"class":4793},[3594,9708,9180],{"class":4459},[3594,9710,9068],{"class":4493},[3594,9712,4497],{"class":4459},[3594,9714,4641],{"class":4622},[3594,9716,4558],{"class":4459},[3594,9718,9719],{"class":3596,"line":3772},[3594,9720,3631],{"emptyLinePlaceholder":3630},[3594,9722,9723,9726,9729,9731,9733,9736,9738],{"class":3596,"line":3778},[3594,9724,9725],{"class":4622},"    def",[3594,9727,9728],{"class":4690}," __repr__",[3594,9730,7763],{"class":4459},[3594,9732,8605],{"class":4493},[3594,9734,9735],{"class":4459},") -> ",[3594,9737,9196],{"class":4793},[3594,9739,4570],{"class":4459},[3594,9741,9742,9745,9748,9751,9754,9757,9759,9762,9764,9767,9770],{"class":3596,"line":3784},[3594,9743,9744],{"class":4455},"        return",[3594,9746,9747],{"class":4622}," f",[3594,9749,9750],{"class":4500},"\"\u003CUser id=",[3594,9752,9753],{"class":4622},"{self",[3594,9755,9756],{"class":4459},".id",[3594,9758,8634],{"class":4622},[3594,9760,9761],{"class":4500}," username=",[3594,9763,9753],{"class":4622},[3594,9765,9766],{"class":4459},".username",[3594,9768,9769],{"class":4622},"!r}",[3594,9771,9772],{"class":4500},">\"\n",[3845,9774,9776],{"id":9775},"типи-стовпців-sqlalchemy","Типи стовпців SQLAlchemy",[3385,9778,9779],{},"SQLAlchemy надає багатий набір типів стовпців, що відображаються на відповідні SQL-типи конкретної бази даних:",[3986,9781,9782,9797],{},[3989,9783,9784],{},[3992,9785,9786,9791,9794],{},[3995,9787,9788,9789],{"align":3997},"Python тип в ",[3412,9790,3434],{},[3995,9792,9793],{"align":3997},"SQLAlchemy тип",[3995,9795,9796],{"align":3997},"PostgreSQL тип",[4006,9798,9799,9815,9837,9852,9869,9886,9903,9920,9937,9954],{},[3992,9800,9801,9805,9810],{},[4011,9802,9803],{"align":3997},[3412,9804,6930],{},[4011,9806,9807],{"align":3997},[3412,9808,9809],{},"Integer",[4011,9811,9812],{"align":3997},[3412,9813,9814],{},"INTEGER",[3992,9816,9817,9821,9829],{},[4011,9818,9819],{"align":3997},[3412,9820,9196],{},[4011,9822,9823,4025,9826],{"align":3997},[3412,9824,9825],{},"String(n)",[3412,9827,9828],{},"Text",[4011,9830,9831,4025,9834],{"align":3997},[3412,9832,9833],{},"VARCHAR(n)",[3412,9835,9836],{},"TEXT",[3992,9838,9839,9843,9848],{},[4011,9840,9841],{"align":3997},[3412,9842,7038],{},[4011,9844,9845],{"align":3997},[3412,9846,9847],{},"Boolean",[4011,9849,9850],{"align":3997},[3412,9851,5564],{},[3992,9853,9854,9859,9864],{},[4011,9855,9856],{"align":3997},[3412,9857,9858],{},"float",[4011,9860,9861],{"align":3997},[3412,9862,9863],{},"Float",[4011,9865,9866],{"align":3997},[3412,9867,9868],{},"FLOAT",[3992,9870,9871,9876,9881],{},[4011,9872,9873],{"align":3997},[3412,9874,9875],{},"datetime",[4011,9877,9878],{"align":3997},[3412,9879,9880],{},"DateTime",[4011,9882,9883],{"align":3997},[3412,9884,9885],{},"TIMESTAMP",[3992,9887,9888,9893,9898],{},[4011,9889,9890],{"align":3997},[3412,9891,9892],{},"date",[4011,9894,9895],{"align":3997},[3412,9896,9897],{},"Date",[4011,9899,9900],{"align":3997},[3412,9901,9902],{},"DATE",[3992,9904,9905,9910,9915],{},[4011,9906,9907],{"align":3997},[3412,9908,9909],{},"Decimal",[4011,9911,9912],{"align":3997},[3412,9913,9914],{},"Numeric(p, s)",[4011,9916,9917],{"align":3997},[3412,9918,9919],{},"NUMERIC(p, s)",[3992,9921,9922,9927,9932],{},[4011,9923,9924],{"align":3997},[3412,9925,9926],{},"bytes",[4011,9928,9929],{"align":3997},[3412,9930,9931],{},"LargeBinary",[4011,9933,9934],{"align":3997},[3412,9935,9936],{},"BYTEA",[3992,9938,9939,9944,9949],{},[4011,9940,9941],{"align":3997},[3412,9942,9943],{},"dict",[4011,9945,9946],{"align":3997},[3412,9947,9948],{},"JSON",[4011,9950,9951],{"align":3997},[3412,9952,9953],{},"JSONB",[3992,9955,9956,9961,9967],{},[4011,9957,9958],{"align":3997},[3412,9959,9960],{},"UUID",[4011,9962,9963,9966],{"align":3997},[3412,9964,9965],{},"Uuid"," (2.0+)",[4011,9968,9969],{"align":3997},[3412,9970,9960],{},[3385,9972,9973],{},"Детальний опис найбільш популярних типів стовпців SQLAlchemy, їхнього призначення, особливостей використання та поширених помилок:",[9975,9976,9977,10064,10146,10227,10317,10407,10474,10537],"field-group",{},[9978,9979,9982,9985,10016],"field",{"name":9980,"type":9276,"default":9981},"Integer \u002F BigInteger","INTEGER \u002F BIGINT",[3385,9983,9984],{},"Представляє цілі числа різної розрядності.",[3510,9986,9987,10003],{},[3455,9988,9989,9991,9992,9994,9995,9998,9999,10002],{},[3389,9990,5103],{},": ",[3412,9993,9809],{}," підходить для стандартних лічильників, невеликих ID та статусів. ",[3412,9996,9997],{},"BigInteger"," (відображається в ",[3412,10000,10001],{},"BIGINT",") є обов'язковим для первинних ключів (ID) у великих або швидкозростаючих таблицях (де кількість записів може перевищити 2 мільярди), грошових сум у копійках, Telegram ID чи інших великих зовнішніх ідентифікаторів.",[3455,10004,10005,10008,10009,10011,10012,10015],{},[3389,10006,10007],{},"Коли НЕ використовувати",": Уникайте ",[3412,10010,9809],{}," для первинних ключів у великих таблицях — переповнення ",[3412,10013,10014],{},"INT"," є класичною причиною збоїв великих систем. Також не використовуйте цілі числа для фінансових розрахунків, якщо тільки ви не зберігаєте суми строго у мінімальних одиницях валюти (наприклад, центах).",[3585,10017,10019],{"className":4445,"code":10018,"language":4448,"meta":3590,"style":3590},"# Приклад використання\nid: Mapped[int] = mapped_column(BigInteger, primary_key=True)\nviews_count: Mapped[int] = mapped_column(Integer, default=0)\n",[3412,10020,10021,10026,10045],{"__ignoreMap":3590},[3594,10022,10023],{"class":3596,"line":3597},[3594,10024,10025],{"class":4482},"# Приклад використання\n",[3594,10027,10028,10030,10032,10034,10037,10039,10041,10043],{"class":3596,"line":3603},[3594,10029,7949],{"class":4690},[3594,10031,9175],{"class":4459},[3594,10033,6930],{"class":4793},[3594,10035,10036],{"class":4459},"] = mapped_column(BigInteger, ",[3594,10038,8990],{"class":4493},[3594,10040,4497],{"class":4459},[3594,10042,4641],{"class":4622},[3594,10044,4558],{"class":4459},[3594,10046,10047,10050,10052,10055,10057,10059,10062],{"class":3596,"line":3609},[3594,10048,10049],{"class":4459},"views_count: Mapped[",[3594,10051,6930],{"class":4793},[3594,10053,10054],{"class":4459},"] = mapped_column(Integer, ",[3594,10056,9068],{"class":4493},[3594,10058,4497],{"class":4459},[3594,10060,10061],{"class":4514},"0",[3594,10063,4558],{"class":4459},[9978,10065,10068,10071,10105],{"name":10066,"type":9296,"default":10067},"String(length) \u002F Text","VARCHAR(length) \u002F TEXT",[3385,10069,10070],{},"Текстові дані фіксованої обмеженої або необмеженої довжини.",[3510,10072,10073,10083],{},[3455,10074,10075,9991,10077,10079,10080,10082],{},[3389,10076,5103],{},[3412,10078,9825],{}," підходить для коротких обмежених рядків (імена, email, slug, паролі). Завжди вказуйте довжину для оптимізації та валідації на рівні СУБД. ",[3412,10081,9828],{}," використовується для довгих текстів без чіткого ліміту довжини (коментарі, статті, описи).",[3455,10084,10085,10087,10088,10091,10092,10095,10096,10098,10099,10101,10102,10104],{},[3389,10086,10007],{},": Не використовуйте ",[3412,10089,10090],{},"String"," без ліміту (просто ",[3412,10093,10094],{},"String()",") у базах даних, які не підтримують необмежений ",[3412,10097,5827],{}," без явного зазначення довжини (наприклад, MySQL вимагає довжину, тоді як PostgreSQL дозволяє ",[3412,10100,5827],{}," без довжини, що еквівалентно ",[3412,10103,9836],{},"). Не зберігайте складну структуру (JSON, списки) у звичайних текстових стовпцях.",[3585,10106,10108],{"className":4445,"code":10107,"language":4448,"meta":3590,"style":3590},"# Приклад використання\nemail: Mapped[str] = mapped_column(String(255), unique=True)\ncontent: Mapped[str] = mapped_column(Text)\n",[3412,10109,10110,10114,10136],{"__ignoreMap":3590},[3594,10111,10112],{"class":3596,"line":3597},[3594,10113,10025],{"class":4482},[3594,10115,10116,10119,10121,10123,10126,10128,10130,10132,10134],{"class":3596,"line":3603},[3594,10117,10118],{"class":4459},"email: Mapped[",[3594,10120,9196],{"class":4793},[3594,10122,9199],{"class":4459},[3594,10124,10125],{"class":4514},"255",[3594,10127,9016],{"class":4459},[3594,10129,9019],{"class":4493},[3594,10131,4497],{"class":4459},[3594,10133,4641],{"class":4622},[3594,10135,4558],{"class":4459},[3594,10137,10138,10141,10143],{"class":3596,"line":3609},[3594,10139,10140],{"class":4459},"content: Mapped[",[3594,10142,9196],{"class":4793},[3594,10144,10145],{"class":4459},"] = mapped_column(Text)\n",[9978,10147,10149,10156,10200],{"name":9847,"type":9304,"default":10148},"BOOLEAN \u002F TINYINT(1)",[3385,10150,10151,10152,3461,10154,3439],{},"Логічний тип для збереження значень ",[3412,10153,4641],{},[3412,10155,6238],{},[3510,10157,10158,10172],{},[3455,10159,10160,10162,10163,3488,10166,3488,10169,3439],{},[3389,10161,5103],{},": Для бінарних прапорців стану, таких як ",[3412,10164,10165],{},"is_active",[3412,10167,10168],{},"has_permission",[3412,10170,10171],{},"email_verified",[3455,10173,10174,10176,10177,3488,10180,3488,10183,3488,10186,10189,10190,10193,10194,3488,10197,3439],{},[3389,10175,10007],{},": Якщо стан сутності має складніший життєвий цикл (наприклад, статус замовлення: ",[3412,10178,10179],{},"draft",[3412,10181,10182],{},"processing",[3412,10184,10185],{},"shipped",[3412,10187,10188],{},"delivered","), краще використовувати ",[3412,10191,10192],{},"Enum"," або зв'язану таблицю статусів замість створення кількох булевих полів на кшталт ",[3412,10195,10196],{},"is_draft",[3412,10198,10199],{},"is_processed",[3585,10201,10203],{"className":4445,"code":10202,"language":4448,"meta":3590,"style":3590},"# Приклад використання\nis_verified: Mapped[bool] = mapped_column(Boolean, default=False)\n",[3412,10204,10205,10209],{"__ignoreMap":3590},[3594,10206,10207],{"class":3596,"line":3597},[3594,10208,10025],{"class":4482},[3594,10210,10211,10214,10216,10219,10221,10223,10225],{"class":3596,"line":3603},[3594,10212,10213],{"class":4459},"is_verified: Mapped[",[3594,10215,7038],{"class":4793},[3594,10217,10218],{"class":4459},"] = mapped_column(Boolean, ",[3594,10220,9068],{"class":4493},[3594,10222,4497],{"class":4459},[3594,10224,6238],{"class":4622},[3594,10226,4558],{"class":4459},[9978,10228,10232,10235,10270],{"name":10229,"type":10230,"default":10231},"Numeric(precision, scale) \u002F Float","Mapped[Decimal] \u002F Mapped[float]","NUMERIC \u002F DOUBLE PRECISION",[3385,10233,10234],{},"Числа з фіксованою точністю та з плаваючою крапкою.",[3510,10236,10237,10255],{},[3455,10238,10239,9991,10241,10243,10244,10247,10248,10251,10252,10254],{},[3389,10240,5103],{},[3412,10242,9914],{}," (або ",[3412,10245,10246],{},"DECIMAL",") є ",[3389,10249,10250],{},"єдиним"," правильним вибором для збереження грошових сум, фінансових розрахунків та точних відсотків, де недопустимі помилки округлення. ",[3412,10253,9863],{}," підходить для наукових обчислень, гео-координат (широта\u002Fдовгота), метрик, де крихітні похибки через особливості репрезентації float-чисел у пам'яті комп'ютера не мають критичного значення.",[3455,10256,10257,9991,10259,10262,10263,10265,10266,10269],{},[3389,10258,10007],{},[3389,10260,10261],{},"Ніколи"," не використовуйте ",[3412,10264,9863],{}," для збереження грошей. Помилки округлення на кшталт ",[3412,10267,10268],{},"0.1 + 0.2 = 0.30000000000000004"," призведуть до фінансових розбіжностей.",[3585,10271,10273],{"className":4445,"code":10272,"language":4448,"meta":3590,"style":3590},"# Приклад використання\nfrom decimal import Decimal\nprice: Mapped[Decimal] = mapped_column(Numeric(10, 2))  # Максимум 99999999.99\nlatitude: Mapped[float] = mapped_column(Float)\n",[3412,10274,10275,10279,10291,10307],{"__ignoreMap":3590},[3594,10276,10277],{"class":3596,"line":3597},[3594,10278,10025],{"class":4482},[3594,10280,10281,10283,10286,10288],{"class":3596,"line":3603},[3594,10282,4465],{"class":4455},[3594,10284,10285],{"class":4459}," decimal ",[3594,10287,4456],{"class":4455},[3594,10289,10290],{"class":4459}," Decimal\n",[3594,10292,10293,10296,10298,10300,10302,10304],{"class":3596,"line":3609},[3594,10294,10295],{"class":4459},"price: Mapped[Decimal] = mapped_column(Numeric(",[3594,10297,4646],{"class":4514},[3594,10299,3488],{"class":4459},[3594,10301,6282],{"class":4514},[3594,10303,7744],{"class":4459},[3594,10305,10306],{"class":4482},"# Максимум 99999999.99\n",[3594,10308,10309,10312,10314],{"class":3596,"line":3615},[3594,10310,10311],{"class":4459},"latitude: Mapped[",[3594,10313,9858],{"class":4793},[3594,10315,10316],{"class":4459},"] = mapped_column(Float)\n",[9978,10318,10322,10325,10362],{"name":10319,"type":10320,"default":10321},"DateTime(timezone) \u002F Date \u002F Time","Mapped[datetime] \u002F Mapped[date] \u002F Mapped[time]","TIMESTAMP \u002F DATE \u002F TIME",[3385,10323,10324],{},"Типи для роботи з часом та датами.",[3510,10326,10327,10343],{},[3455,10328,10329,10331,10332,10335,10336,10338,10339,10342],{},[3389,10330,5103],{},": Для часових позначок створення\u002Fоновлення, логів та подій завжди використовуйте ",[3412,10333,10334],{},"DateTime(timezone=True)"," (збереження UTC часової зони). ",[3412,10337,9897],{}," підходить для днів народження чи календарних дат (без прив'язки до годин). ",[3412,10340,10341],{},"Time"," — для розкладів (наприклад, \"о 18:00 кожні вихідні\").",[3455,10344,10345,10347,10348,10351,10352,10354,10355,10357,10358,10361],{},[3389,10346,10007],{},": Уникайте збереження часових міток як цілих чисел (",[3412,10349,10350],{},"timestamp"," в Unix Epoch ",[3412,10353,6930],{},"), оскільки це ускладнює роботу зі SQL-запитами безпосередньо у базі даних (ускладнює агрегації по датах, групування тощо). Уникайте ",[3412,10356,9880],{}," без часової зони (",[3412,10359,10360],{},"timezone=False","), якщо додатком користуються користувачі в різних часових поясах.",[3585,10363,10365],{"className":4445,"code":10364,"language":4448,"meta":3590,"style":3590},"# Приклад використання\nfrom datetime import datetime, date\nregistered_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())\nbirth_date: Mapped[date] = mapped_column(Date)\n",[3412,10366,10367,10371,10382,10402],{"__ignoreMap":3590},[3594,10368,10369],{"class":3596,"line":3597},[3594,10370,10025],{"class":4482},[3594,10372,10373,10375,10377,10379],{"class":3596,"line":3603},[3594,10374,4465],{"class":4455},[3594,10376,9465],{"class":4459},[3594,10378,4456],{"class":4455},[3594,10380,10381],{"class":4459}," datetime, date\n",[3594,10383,10384,10387,10390,10392,10394,10396,10399],{"class":3596,"line":3609},[3594,10385,10386],{"class":4459},"registered_at: Mapped[datetime] = mapped_column(DateTime(",[3594,10388,10389],{"class":4493},"timezone",[3594,10391,4497],{"class":4459},[3594,10393,4641],{"class":4622},[3594,10395,9016],{"class":4459},[3594,10397,10398],{"class":4493},"server_default",[3594,10400,10401],{"class":4459},"=func.now())\n",[3594,10403,10404],{"class":3596,"line":3615},[3594,10405,10406],{"class":4459},"birth_date: Mapped[date] = mapped_column(Date)\n",[9978,10408,10412,10415,10435],{"name":10409,"type":10410,"default":10411},"JSON \u002F pg.JSONB","Mapped[dict | list]","JSON \u002F JSONB",[3385,10413,10414],{},"Збереження неструктурованих або напівструктурованих даних (словників та списків).",[3510,10416,10417,10426],{},[3455,10418,10419,10421,10422,10425],{},[3389,10420,5103],{},": Для конфігурацій, гнучких метаданих додаткових полів користувача, кешування або історії змін. У PostgreSQL завжди надавайте перевагу ",[3412,10423,10424],{},"sqlalchemy.dialects.postgresql.JSONB",", оскільки він зберігає дані у декомпонованому бінарному форматі, що дозволяє індексувати JSON-ключі (через GIN-індекси) та виконувати швидкий пошук по них.",[3455,10427,10428,10430,10431,10434],{},[3389,10429,10007],{},": Не використовуйте JSON для полів, які беруть участь у реляційних зв'язках (",[3412,10432,10433],{},"JOIN","), або полів, які часто оновлюються окремо. Якщо вам постійно доводиться робити запити всередину JSON-документа для фільтрації, краще винести ці ключі в окремі класичні SQL-стовпці.",[3585,10436,10438],{"className":4445,"code":10437,"language":4448,"meta":3590,"style":3590},"# Приклад використання\nfrom sqlalchemy.dialects.postgresql import JSONB\nmetadata_info: Mapped[dict] = mapped_column(JSONB, default=dict)\n",[3412,10439,10440,10444,10456],{"__ignoreMap":3590},[3594,10441,10442],{"class":3596,"line":3597},[3594,10443,10025],{"class":4482},[3594,10445,10446,10448,10451,10453],{"class":3596,"line":3603},[3594,10447,4465],{"class":4455},[3594,10449,10450],{"class":4459}," sqlalchemy.dialects.postgresql ",[3594,10452,4456],{"class":4455},[3594,10454,10455],{"class":4459}," JSONB\n",[3594,10457,10458,10461,10463,10466,10468,10470,10472],{"class":3596,"line":3609},[3594,10459,10460],{"class":4459},"metadata_info: Mapped[",[3594,10462,9943],{"class":4793},[3594,10464,10465],{"class":4459},"] = mapped_column(JSONB, ",[3594,10467,9068],{"class":4493},[3594,10469,4497],{"class":4459},[3594,10471,9943],{"class":4793},[3594,10473,4558],{"class":4459},[9978,10475,10477,10480,10500],{"name":9965,"type":10476,"default":9960},"Mapped[uuid.UUID]",[3385,10478,10479],{},"Універсальний унікальний ідентифікатор (UUID).",[3510,10481,10482,10489],{},[3455,10483,10484,10486,10487,3439],{},[3389,10485,5103],{},": Ідеачно для первинних ключів у розподілених системах, де клієнт або мікросервіс може самостійно згенерувати ID до запису в БД, або для публічних ID сутностей, щоб приховати внутрішню послідовність (sequential IDs) від перебору (enumeration attack). У SQLAlchemy 2.0 є нативний тип ",[3412,10488,9965],{},[3455,10490,10491,10493,10494,10496,10497,10499],{},[3389,10492,10007],{},": У дуже великих таблицях, які потребують високої швидкості вставки (",[3412,10495,7659],{},"). UUIDv4 генерується випадковим чином, через що індекси B-Tree сильно фрагментуються, сповільнюючи операції запису. Для таких випадків краще використовувати UUIDv7 (сортовані за часом) або залишати автоінкрементний ",[3412,10498,9997],{}," для внутрішніх зв'язків, а UUID використовувати лише як зовнішній ідентифікатор.",[3585,10501,10503],{"className":4445,"code":10502,"language":4448,"meta":3590,"style":3590},"# Приклад використання\nimport uuid\nfrom sqlalchemy import Uuid\nuuid_id: Mapped[uuid.UUID] = mapped_column(Uuid, default=uuid.uuid4)\n",[3412,10504,10505,10509,10516,10527],{"__ignoreMap":3590},[3594,10506,10507],{"class":3596,"line":3597},[3594,10508,10025],{"class":4482},[3594,10510,10511,10513],{"class":3596,"line":3603},[3594,10512,4456],{"class":4455},[3594,10514,10515],{"class":4459}," uuid\n",[3594,10517,10518,10520,10522,10524],{"class":3596,"line":3609},[3594,10519,4465],{"class":4455},[3594,10521,5996],{"class":4459},[3594,10523,4456],{"class":4455},[3594,10525,10526],{"class":4459}," Uuid\n",[3594,10528,10529,10532,10534],{"class":3596,"line":3615},[3594,10530,10531],{"class":4459},"uuid_id: Mapped[uuid.UUID] = mapped_column(Uuid, ",[3594,10533,9068],{"class":4493},[3594,10535,10536],{"class":4459},"=uuid.uuid4)\n",[9978,10538,10541,10547,10577],{"name":10192,"type":10539,"default":10540},"Mapped[YourEnumClass]","VARCHAR \u002F ENUM",[3385,10542,10543,10544,3439],{},"Обмежений набір значень на основі Python ",[3412,10545,10546],{},"enum.Enum",[3510,10548,10549,10554],{},[3455,10550,10551,10553],{},[3389,10552,5103],{},": Для статусів, ролей користувачів, категорій, де набір значень є чітким та заздалегідь визначеним на рівні коду.",[3455,10555,10556,10558,10559,10562,10563,10566,10567,10570,10571,10573,10574,3439],{},[3389,10557,10007],{},": Не створюйте нативний ENUM бази даних (параметр ",[3412,10560,10561],{},"native_enum=True","), якщо список статусів буде часто змінюватися, оскільки операція оновлення нативного ENUM у схемі БД (наприклад, ",[3412,10564,10565],{},"ALTER TYPE ... ADD VALUE",") в деяких СУБД є блокуючою або складною для міграцій. Встановлюйте ",[3412,10568,10569],{},"native_enum=False"," (за замовчуванням у SQLAlchemy для багатьох БД) — тоді значення зберігатимуться як ",[3412,10572,5827],{}," з обмеженням ",[3412,10575,10576],{},"CHECK constraint",[3585,10578,10580],{"className":4445,"code":10579,"language":4448,"meta":3590,"style":3590},"# Приклад використання\nimport enum\nfrom sqlalchemy import Enum\n\nclass UserRole(enum.Enum):\n    ADMIN = \"admin\"\n    USER = \"user\"\n    GUEST = \"guest\"\n\nrole: Mapped[UserRole] = mapped_column(Enum(UserRole), default=UserRole.USER)\n",[3412,10581,10582,10586,10593,10604,10608,10626,10634,10642,10650,10654],{"__ignoreMap":3590},[3594,10583,10584],{"class":3596,"line":3597},[3594,10585,10025],{"class":4482},[3594,10587,10588,10590],{"class":3596,"line":3603},[3594,10589,4456],{"class":4455},[3594,10591,10592],{"class":4459}," enum\n",[3594,10594,10595,10597,10599,10601],{"class":3596,"line":3609},[3594,10596,4465],{"class":4455},[3594,10598,5996],{"class":4459},[3594,10600,4456],{"class":4455},[3594,10602,10603],{"class":4459}," Enum\n",[3594,10605,10606],{"class":3596,"line":3615},[3594,10607,3631],{"emptyLinePlaceholder":3630},[3594,10609,10610,10612,10615,10617,10620,10622,10624],{"class":3596,"line":3621},[3594,10611,8575],{"class":4622},[3594,10613,10614],{"class":4793}," UserRole",[3594,10616,7763],{"class":4459},[3594,10618,10619],{"class":4793},"enum",[3594,10621,3439],{"class":4459},[3594,10623,10192],{"class":4793},[3594,10625,8585],{"class":4459},[3594,10627,10628,10631],{"class":3596,"line":3627},[3594,10629,10630],{"class":4459},"    ADMIN = ",[3594,10632,10633],{"class":4500},"\"admin\"\n",[3594,10635,10636,10639],{"class":3596,"line":3634},[3594,10637,10638],{"class":4459},"    USER = ",[3594,10640,10641],{"class":4500},"\"user\"\n",[3594,10643,10644,10647],{"class":3596,"line":3640},[3594,10645,10646],{"class":4459},"    GUEST = ",[3594,10648,10649],{"class":4500},"\"guest\"\n",[3594,10651,10652],{"class":3596,"line":3646},[3594,10653,3631],{"emptyLinePlaceholder":3630},[3594,10655,10656,10659,10661],{"class":3596,"line":3652},[3594,10657,10658],{"class":4459},"role: Mapped[UserRole] = mapped_column(Enum(UserRole), ",[3594,10660,9068],{"class":4493},[3594,10662,10663],{"class":4459},"=UserRole.USER)\n",[5216,10665,10666,10679],{},[3385,10667,10668,10669,10671,10672,10674,10675,10678],{},"Для поля ",[3412,10670,9960],{}," у SQLAlchemy 2.0 з'явився нативний тип ",[3412,10673,9965],{},", що автоматично конвертує ",[3412,10676,10677],{},"uuid.UUID"," з Python у відповідний тип бази даних:",[3585,10680,10682],{"className":4445,"code":10681,"language":4448,"meta":3590,"style":3590},"import uuid\nfrom sqlalchemy import Uuid\n\nclass Project(Base):\n    __tablename__ = \"projects\"\n    id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)\n",[3412,10683,10684,10690,10700,10704,10717,10724],{"__ignoreMap":3590},[3594,10685,10686,10688],{"class":3596,"line":3597},[3594,10687,4456],{"class":4455},[3594,10689,10515],{"class":4459},[3594,10691,10692,10694,10696,10698],{"class":3596,"line":3603},[3594,10693,4465],{"class":4455},[3594,10695,5996],{"class":4459},[3594,10697,4456],{"class":4455},[3594,10699,10526],{"class":4459},[3594,10701,10702],{"class":3596,"line":3609},[3594,10703,3631],{"emptyLinePlaceholder":3630},[3594,10705,10706,10708,10711,10713,10715],{"class":3596,"line":3615},[3594,10707,8575],{"class":4622},[3594,10709,10710],{"class":4793}," Project",[3594,10712,7763],{"class":4459},[3594,10714,8965],{"class":4793},[3594,10716,8585],{"class":4459},[3594,10718,10719,10721],{"class":3596,"line":3621},[3594,10720,8972],{"class":4459},[3594,10722,10723],{"class":4500},"\"projects\"\n",[3594,10725,10726,10728,10731,10733,10735,10737,10739,10741],{"class":3596,"line":3627},[3594,10727,8984],{"class":4690},[3594,10729,10730],{"class":4459},": Mapped[uuid.UUID] = mapped_column(Uuid, ",[3594,10732,8990],{"class":4493},[3594,10734,4497],{"class":4459},[3594,10736,4641],{"class":4622},[3594,10738,3488],{"class":4459},[3594,10740,9068],{"class":4493},[3594,10742,10536],{"class":4459},[3845,10744,10746],{"id":10745},"стандартний-абстрактний-базовий-клас-з-аудитними-полями","Стандартний абстрактний базовий клас з аудитними полями",[3385,10748,10749,10750,3488,10752,3488,10755,10758,10759,4442],{},"У реальних проєктах зручно винести загальні поля (наприклад, ",[3412,10751,7949],{},[3412,10753,10754],{},"created_at",[3412,10756,10757],{},"updated_at",") до абстрактного класу, від якого успадковуватимуться всі моделі — так само, як це роблять у EF Core через клас ",[3412,10760,10761],{},"BaseEntity",[5448,10763,10764,10925],{},[3585,10765,10770],{"className":10766,"code":10767,"filename":10768,"language":10769,"meta":3590,"style":3590},"language-csharp shiki shiki-themes light-plus dark-plus dark-plus","public abstract class BaseEntity\n{\n    public int Id { get; set; }\n    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;\n    public DateTime? UpdatedAt { get; set; }\n}\n\npublic class User : BaseEntity\n{\n    public string Username { get; set; } = default!;\n}\n","EF Core — BaseEntity","csharp",[3412,10771,10772,10786,10791,10817,10848,10870,10874,10878,10892,10896,10921],{"__ignoreMap":3590},[3594,10773,10774,10777,10780,10783],{"class":3596,"line":3597},[3594,10775,10776],{"class":4622},"public",[3594,10778,10779],{"class":4622}," abstract",[3594,10781,10782],{"class":4622}," class",[3594,10784,10785],{"class":4793}," BaseEntity\n",[3594,10787,10788],{"class":3596,"line":3603},[3594,10789,10790],{"class":4459},"{\n",[3594,10792,10793,10796,10799,10802,10805,10808,10811,10814],{"class":3596,"line":3609},[3594,10794,10795],{"class":4622},"    public",[3594,10797,10798],{"class":4622}," int",[3594,10800,10801],{"class":4493}," Id",[3594,10803,10804],{"class":4459}," { ",[3594,10806,10807],{"class":4622},"get",[3594,10809,10810],{"class":4459},"; ",[3594,10812,10813],{"class":4622},"set",[3594,10815,10816],{"class":4459},"; }\n",[3594,10818,10819,10821,10824,10827,10829,10831,10833,10835,10838,10840,10842,10845],{"class":3596,"line":3615},[3594,10820,10795],{"class":4622},[3594,10822,10823],{"class":4793}," DateTime",[3594,10825,10826],{"class":4493}," CreatedAt",[3594,10828,10804],{"class":4459},[3594,10830,10807],{"class":4622},[3594,10832,10810],{"class":4459},[3594,10834,10813],{"class":4622},[3594,10836,10837],{"class":4459},"; } = ",[3594,10839,9880],{"class":4493},[3594,10841,3439],{"class":4459},[3594,10843,10844],{"class":4493},"UtcNow",[3594,10846,10847],{"class":4459},";\n",[3594,10849,10850,10852,10854,10857,10860,10862,10864,10866,10868],{"class":3596,"line":3621},[3594,10851,10795],{"class":4622},[3594,10853,10823],{"class":4793},[3594,10855,10856],{"class":4459},"? ",[3594,10858,10859],{"class":4493},"UpdatedAt",[3594,10861,10804],{"class":4459},[3594,10863,10807],{"class":4622},[3594,10865,10810],{"class":4459},[3594,10867,10813],{"class":4622},[3594,10869,10816],{"class":4459},[3594,10871,10872],{"class":3596,"line":3627},[3594,10873,3758],{"class":4459},[3594,10875,10876],{"class":3596,"line":3634},[3594,10877,3631],{"emptyLinePlaceholder":3630},[3594,10879,10880,10882,10884,10886,10889],{"class":3596,"line":3640},[3594,10881,10776],{"class":4622},[3594,10883,10782],{"class":4622},[3594,10885,8960],{"class":4793},[3594,10887,10888],{"class":4459}," : ",[3594,10890,10891],{"class":4793},"BaseEntity\n",[3594,10893,10894],{"class":3596,"line":3646},[3594,10895,10790],{"class":4459},[3594,10897,10898,10900,10903,10906,10908,10910,10912,10914,10916,10918],{"class":3596,"line":3652},[3594,10899,10795],{"class":4622},[3594,10901,10902],{"class":4622}," string",[3594,10904,10905],{"class":4493}," Username",[3594,10907,10804],{"class":4459},[3594,10909,10807],{"class":4622},[3594,10911,10810],{"class":4459},[3594,10913,10813],{"class":4622},[3594,10915,10837],{"class":4459},[3594,10917,9068],{"class":4622},[3594,10919,10920],{"class":4459},"!;\n",[3594,10922,10923],{"class":3596,"line":3658},[3594,10924,3758],{"class":4459},[3585,10926,10929],{"className":4445,"code":10927,"filename":10928,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import String, func\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column\nfrom datetime import datetime\n\nclass Base(DeclarativeBase):\n    pass\n\nclass TimestampMixin:\n    \"\"\"Mixin з аудитними полями для всіх моделей.\"\"\"\n    created_at: Mapped[datetime] = mapped_column(\n        server_default=func.now()\n    )\n    updated_at: Mapped[datetime | None] = mapped_column(\n        onupdate=func.now(),\n        default=None\n    )\n\nclass User(TimestampMixin, Base):\n    __tablename__ = \"users\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    username: Mapped[str] = mapped_column(String(50), unique=True)\n","SQLAlchemy 2.0 — TimestampMixin",[3412,10930,10931,10942,10952,10962,10966,10978,10982,10986,10995,11000,11004,11010,11014,11024,11032,11042,11046,11050,11067,11073,11091],{"__ignoreMap":3590},[3594,10932,10933,10935,10937,10939],{"class":3596,"line":3597},[3594,10934,4465],{"class":4455},[3594,10936,5996],{"class":4459},[3594,10938,4456],{"class":4455},[3594,10940,10941],{"class":4459}," String, func\n",[3594,10943,10944,10946,10948,10950],{"class":3596,"line":3603},[3594,10945,4465],{"class":4455},[3594,10947,6383],{"class":4459},[3594,10949,4456],{"class":4455},[3594,10951,9116],{"class":4459},[3594,10953,10954,10956,10958,10960],{"class":3596,"line":3609},[3594,10955,4465],{"class":4455},[3594,10957,9465],{"class":4459},[3594,10959,4456],{"class":4455},[3594,10961,9470],{"class":4459},[3594,10963,10964],{"class":3596,"line":3615},[3594,10965,3631],{"emptyLinePlaceholder":3630},[3594,10967,10968,10970,10972,10974,10976],{"class":3596,"line":3621},[3594,10969,8575],{"class":4622},[3594,10971,9127],{"class":4793},[3594,10973,7763],{"class":4459},[3594,10975,3931],{"class":4793},[3594,10977,8585],{"class":4459},[3594,10979,10980],{"class":3596,"line":3627},[3594,10981,9142],{"class":4455},[3594,10983,10984],{"class":3596,"line":3634},[3594,10985,3631],{"emptyLinePlaceholder":3630},[3594,10987,10988,10990,10993],{"class":3596,"line":3640},[3594,10989,8575],{"class":4622},[3594,10991,10992],{"class":4793}," TimestampMixin",[3594,10994,4570],{"class":4459},[3594,10996,10997],{"class":3596,"line":3646},[3594,10998,10999],{"class":4500},"    \"\"\"Mixin з аудитними полями для всіх моделей.\"\"\"\n",[3594,11001,11002],{"class":3596,"line":3652},[3594,11003,9674],{"class":4459},[3594,11005,11006,11008],{"class":3596,"line":3658},[3594,11007,9679],{"class":4493},[3594,11009,9682],{"class":4459},[3594,11011,11012],{"class":3596,"line":3664},[3594,11013,4935],{"class":4459},[3594,11015,11016,11019,11021],{"class":3596,"line":3670},[3594,11017,11018],{"class":4459},"    updated_at: Mapped[datetime | ",[3594,11020,9644],{"class":4622},[3594,11022,11023],{"class":4459},"] = mapped_column(\n",[3594,11025,11026,11029],{"class":3596,"line":3676},[3594,11027,11028],{"class":4493},"        onupdate",[3594,11030,11031],{"class":4459},"=func.now(),\n",[3594,11033,11034,11037,11039],{"class":3596,"line":3681},[3594,11035,11036],{"class":4493},"        default",[3594,11038,4497],{"class":4459},[3594,11040,11041],{"class":4622},"None\n",[3594,11043,11044],{"class":3596,"line":3687},[3594,11045,4935],{"class":4459},[3594,11047,11048],{"class":3596,"line":3693},[3594,11049,3631],{"emptyLinePlaceholder":3630},[3594,11051,11052,11054,11056,11058,11061,11063,11065],{"class":3596,"line":3699},[3594,11053,8575],{"class":4622},[3594,11055,8960],{"class":4793},[3594,11057,7763],{"class":4459},[3594,11059,11060],{"class":4793},"TimestampMixin",[3594,11062,3488],{"class":4459},[3594,11064,8965],{"class":4793},[3594,11066,8585],{"class":4459},[3594,11068,11069,11071],{"class":3596,"line":3705},[3594,11070,8972],{"class":4459},[3594,11072,8975],{"class":4500},[3594,11074,11075,11077,11079,11081,11083,11085,11087,11089],{"class":3596,"line":3711},[3594,11076,8984],{"class":4690},[3594,11078,9175],{"class":4459},[3594,11080,6930],{"class":4793},[3594,11082,9180],{"class":4459},[3594,11084,8990],{"class":4493},[3594,11086,4497],{"class":4459},[3594,11088,4641],{"class":4622},[3594,11090,4558],{"class":4459},[3594,11092,11093,11095,11097,11099,11101,11103,11105,11107,11109],{"class":3596,"line":3716},[3594,11094,9193],{"class":4459},[3594,11096,9196],{"class":4793},[3594,11098,9199],{"class":4459},[3594,11100,9013],{"class":4514},[3594,11102,9016],{"class":4459},[3594,11104,9019],{"class":4493},[3594,11106,4497],{"class":4459},[3594,11108,4641],{"class":4622},[3594,11110,4558],{"class":4459},[3385,11112,6129,11113,11116,11117,11119,11120,11122,11123,11126],{},[3412,11114,11115],{},"onupdate=func.now()"," у ",[3412,11118,3438],{}," — це «клієнтський» тригер: SQLAlchemy автоматично підставляє поточний час у ",[3412,11121,3480],{},"-запит, якщо об'єкт змінився. Відповідником у PostgreSQL є тригер ",[3412,11124,11125],{},"BEFORE UPDATE",", але цей підхід зручніший — логіка знаходиться у Python-коді, а не у SQL.",[3441,11128],{},[3444,11130,11132],{"id":11131},"relationships-звязки-між-таблицями","Relationships: Зв'язки між таблицями",[3385,11134,11135],{},"Реляційні бази даних отримали свою назву саме через зв'язки між таблицями. Головна сила ORM полягає в тому, що ці зв'язки можна виразити декларативно у Python-коді, а потім працювати з ними як зі звичайними атрибутами об'єктів.",[3385,11137,11138,11139,11143,11144,11149],{},"У SQLAlchemy для визначення зв'язків між моделями використовується функція ",[3389,11140,11141],{},[3412,11142,3956],{}," у поєднанні з ",[3389,11145,11146],{},[3412,11147,11148],{},"ForeignKey"," у визначенні стовпця.",[9975,11151,11152,11161,11165,11249,11260],{},[9978,11153,11156,11157,11160],{"name":11154,"type":11155,"default":9644},"argument","str | type","Перший позиційний аргумент — клас моделі, з якою створюється зв'язок (або його рядкове ім'я, наприклад ",[3412,11158,11159],{},"\"Post\"","). Рядкові імена запобігають помилкам циклічного імпорту в Python.",[9978,11162,11164],{"name":11163,"type":9196,"default":9644},"back_populates","Ім'я атрибуту зв'язку на цільовій моделі, що посилається назад на поточну модель. Необхідний для підтримки двонаправленої синхронізації об'єктів у пам'яті Python.",[9978,11166,11169,11172],{"name":11167,"type":9196,"default":11168},"cascade","save-update, merge",[3385,11170,11171],{},"Правила розповсюдження операцій від батьківського об'єкта до дочірніх. Може містити комбінацію наступних значень через кому:",[3510,11173,11174,11183,11193,11203,11213,11226,11236],{},[3455,11175,11176,11179,11180,11182],{},[3412,11177,11178],{},"save-update"," (за замовчуванням): додавання батьківського об'єкта до сесії (",[3412,11181,4122],{},") автоматично додає всі пов'язані дочірні об'єкти.",[3455,11184,11185,11188,11189,11192],{},[3412,11186,11187],{},"merge"," (за замовчуванням): злиття стану батьківського об'єкта (",[3412,11190,11191],{},"session.merge()",") також зливає стан пов'язаних дочірніх об'єктів.",[3455,11194,11195,11198,11199,11202],{},[3412,11196,11197],{},"delete",": при видаленні батьківського об'єкта (",[3412,11200,11201],{},"session.delete(parent)",") автоматично видаляються всі пов'язані дочірні записи з БД.",[3455,11204,11205,11208,11209,11212],{},[3412,11206,11207],{},"delete-orphan",": якщо дочірній об'єкт від'єднати від колекції батьківського (наприклад, видалити зі списку), він вважається «сиротою» та автоматично видаляється з БД, замість того, щоб отримати ",[3412,11210,11211],{},"NULL"," у зовнішньому ключі.",[3455,11214,11215,11218,11219,3461,11222,11225],{},[3412,11216,11217],{},"refresh-expire",": операції ",[3412,11220,11221],{},"session.expire()",[3412,11223,11224],{},"session.refresh()"," на батьківському об'єкті поширюються також і на дочірні.",[3455,11227,11228,11231,11232,11235],{},[3412,11229,11230],{},"expunge",": при виведенні батьківського об'єкта із сесії (",[3412,11233,11234],{},"session.expunge()",") дочірні об'єкти також виводяться з неї.",[3455,11237,11238,11241,11242,11245,11246,3439],{},[3412,11239,11240],{},"all",": швидкий синонім для ",[3412,11243,11244],{},"\"save-update, merge, refresh-expire, expunge, delete\"",". Найбільш популярне поєднання для One-to-Many з жорстким володінням: ",[3412,11247,11248],{},"\"all, delete-orphan\"",[9978,11250,7483,11253,11255,11256,11259],{"name":11251,"type":11252,"default":6238},"passive_deletes","bool | str",[3412,11254,4641],{},", SQLAlchemy не завантажуватиме дочірні записи з БД при видаленні батьківського об'єкта, а делегуватиме каскадне видалення базі даних (вимагає ",[3412,11257,11258],{},"ON DELETE CASCADE"," у зовнішньому ключі).",[9978,11261,11264,11265,11268,11269,11272,11273,11276,11277,11280],{"name":11262,"type":9196,"default":11263},"lazy","select","Стратегія завантаження зв'язку за замовчуванням: ",[3412,11266,11267],{},"\"select\""," (lazy), ",[3412,11270,11271],{},"\"joined\""," (eager via JOIN), ",[3412,11274,11275],{},"\"selectin\""," (eager via IN), або ",[3412,11278,11279],{},"\"raise\""," (заборона lazy load).",[3441,11282],{},[4433,11284,11286,11287],{"id":11285},"зовнішні-ключі-foreignkey","Зовнішні ключі: ",[3412,11288,11148],{},[3385,11290,11291,11292,11294,11295,11299,11300,11303],{},"У той час як ",[3412,11293,3956],{}," створює зв'язок на рівні Python-об'єктів, клас ",[3389,11296,11297],{},[3412,11298,11148],{}," створює справжнє обмеження цілісності зовнішнього ключа (",[3412,11301,11302],{},"FOREIGN KEY constraint",") безпосередньо у схемі бази даних.",[3385,11305,11306,11308,11309,11311],{},[3412,11307,11148],{}," передається як аргумент у ",[3412,11310,3438],{}," для стовпців, що мають посилатися на інші таблиці.",[9975,11313,11314,11335,11370],{},[9978,11315,11318,11319,11322,11323,11326,11327,11330,11331,11334],{"name":11316,"type":11317,"default":9644},"column","str | Column","Перший позиційний аргумент — цільовий стовпець, на який посилається ключ. Зазвичай передається у вигляді рядка у форматі ",[3412,11320,11321],{},"\"ім'я_таблиці.ім'я_стовпця\""," (наприклад, ",[3412,11324,11325],{},"\"users.id\"",").\n",[6344,11328,11329],{},"Зверніть увагу:"," вказується саме фізичне ім'я таблиці з бази даних (",[3412,11332,11333],{},"__tablename__","), а не назва класу ORM-моделі.",[9978,11336,11338,11341],{"name":11337,"type":9196,"default":9644},"ondelete",[3385,11339,11340],{},"Визначає поведінку бази даних при видаленні батьківського запису. Популярні SQL-опції:",[3510,11342,11343,11349,11358,11364],{},[3455,11344,11345,11348],{},[3412,11346,11347],{},"\"CASCADE\"",": автоматично видалити дочірній рядок. Рекомендовано для сильних зв'язків.",[3455,11350,11351,11354,11355,11357],{},[3412,11352,11353],{},"\"SET NULL\"",": встановити значення цього поля в ",[3412,11356,11211],{}," (стовпець має бути nullable).",[3455,11359,11360,11363],{},[3412,11361,11362],{},"\"RESTRICT\"",": заборонити видалення батьківського рядка, якщо на нього посилаються дочірні.",[3455,11365,11366,11369],{},[3412,11367,11368],{},"\"NO ACTION\"",": стандартна поведінка (перевірка обмеження в кінці транзакції).",[9978,11371,11373,11374,11376],{"name":11372,"type":9196,"default":9644},"onupdate","Визначає поведінку бази даних при зміні значення первинного ключа в батьківському рядку. Зазвичай використовується ",[3412,11375,11347],{},", щоб оновлені ID автоматично прописувалися у дочірніх таблицях.",[5430,11378,11380],{"id":11379},"приклад-використання","Приклад використання:",[3585,11382,11384],{"className":4445,"code":11383,"language":4448,"meta":3590,"style":3590},"# Посилання на таблицю \"users\" стовпець \"id\" з каскадним видаленням на рівні СУБД\nuser_id: Mapped[int] = mapped_column(\n    ForeignKey(\"users.id\", ondelete=\"CASCADE\")\n)\n",[3412,11385,11386,11391,11400,11417],{"__ignoreMap":3590},[3594,11387,11388],{"class":3596,"line":3597},[3594,11389,11390],{"class":4482},"# Посилання на таблицю \"users\" стовпець \"id\" з каскадним видаленням на рівні СУБД\n",[3594,11392,11393,11396,11398],{"class":3596,"line":3603},[3594,11394,11395],{"class":4459},"user_id: Mapped[",[3594,11397,6930],{"class":4793},[3594,11399,11023],{"class":4459},[3594,11401,11402,11405,11407,11409,11411,11413,11415],{"class":3596,"line":3609},[3594,11403,11404],{"class":4459},"    ForeignKey(",[3594,11406,11325],{"class":4500},[3594,11408,3488],{"class":4459},[3594,11410,11337],{"class":4493},[3594,11412,4497],{"class":4459},[3594,11414,11347],{"class":4500},[3594,11416,4558],{"class":4459},[3594,11418,11419],{"class":3596,"line":3615},[3594,11420,4558],{"class":4459},[3441,11422],{},[3845,11424,11426],{"id":11425},"one-to-many-один-до-багатьох","One-to-Many (Один до Багатьох)",[3385,11428,11429,11430,11433,11434,11437,11438,11440,11441,11444],{},"Найпоширеніший тип зв'язку. Один запис у таблиці ",[3412,11431,11432],{},"A"," може мати декілька пов'язаних записів у таблиці ",[3412,11435,11436],{},"B",". Наприклад: один ",[3412,11439,9451],{}," може мати багато ",[3412,11442,11443],{},"Post","-ів.",[3585,11446,11449],{"className":4445,"code":11447,"filename":11448,"language":4448,"meta":3590,"style":3590},"from __future__ import annotations\nfrom typing import TYPE_CHECKING\nfrom sqlalchemy import String, Text, ForeignKey\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship\n\nclass Base(DeclarativeBase):\n    pass\n\nclass User(Base):\n    __tablename__ = \"users\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    username: Mapped[str] = mapped_column(String(50), unique=True)\n\n    # \"Один до Багатьох\": один User має багато Post-ів\n    # back_populates=\"author\" зв'язує цю сторону зі стороною Post.author\n    posts: Mapped[list[\"Post\"]] = relationship(\n        \"Post\",\n        back_populates=\"author\",\n        cascade=\"all, delete-orphan\",  # При видаленні User — видати всі його Post-и\n    )\n\nclass Post(Base):\n    __tablename__ = \"posts\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    title: Mapped[str] = mapped_column(String(200))\n    content: Mapped[str | None] = mapped_column(Text)\n\n    # Зовнішній ключ — посилання на users.id\n    author_id: Mapped[int] = mapped_column(ForeignKey(\"users.id\"))\n\n    # \"Зворотний\" зв'язок: Post посилається на свого User (автора)\n    author: Mapped[\"User\"] = relationship(\"User\", back_populates=\"posts\")\n","models\u002Fblog.py",[3412,11450,11451,11464,11476,11487,11498,11502,11514,11518,11522,11534,11540,11544,11562,11582,11586,11591,11596,11606,11613,11625,11639,11643,11647,11660,11667,11671,11689,11703,11716,11720,11725,11739,11743,11748],{"__ignoreMap":3590},[3594,11452,11453,11455,11458,11461],{"class":3596,"line":3597},[3594,11454,4465],{"class":4455},[3594,11456,11457],{"class":4493}," __future__",[3594,11459,11460],{"class":4455}," import",[3594,11462,11463],{"class":4459}," annotations\n",[3594,11465,11466,11468,11471,11473],{"class":3596,"line":3603},[3594,11467,4465],{"class":4455},[3594,11469,11470],{"class":4459}," typing ",[3594,11472,4456],{"class":4455},[3594,11474,11475],{"class":4459}," TYPE_CHECKING\n",[3594,11477,11478,11480,11482,11484],{"class":3596,"line":3609},[3594,11479,4465],{"class":4455},[3594,11481,5996],{"class":4459},[3594,11483,4456],{"class":4455},[3594,11485,11486],{"class":4459}," String, Text, ForeignKey\n",[3594,11488,11489,11491,11493,11495],{"class":3596,"line":3615},[3594,11490,4465],{"class":4455},[3594,11492,6383],{"class":4459},[3594,11494,4456],{"class":4455},[3594,11496,11497],{"class":4459}," DeclarativeBase, Mapped, mapped_column, relationship\n",[3594,11499,11500],{"class":3596,"line":3621},[3594,11501,3631],{"emptyLinePlaceholder":3630},[3594,11503,11504,11506,11508,11510,11512],{"class":3596,"line":3627},[3594,11505,8575],{"class":4622},[3594,11507,9127],{"class":4793},[3594,11509,7763],{"class":4459},[3594,11511,3931],{"class":4793},[3594,11513,8585],{"class":4459},[3594,11515,11516],{"class":3596,"line":3634},[3594,11517,9142],{"class":4455},[3594,11519,11520],{"class":3596,"line":3640},[3594,11521,3631],{"emptyLinePlaceholder":3630},[3594,11523,11524,11526,11528,11530,11532],{"class":3596,"line":3646},[3594,11525,8575],{"class":4622},[3594,11527,8960],{"class":4793},[3594,11529,7763],{"class":4459},[3594,11531,8965],{"class":4793},[3594,11533,8585],{"class":4459},[3594,11535,11536,11538],{"class":3596,"line":3652},[3594,11537,8972],{"class":4459},[3594,11539,8975],{"class":4500},[3594,11541,11542],{"class":3596,"line":3658},[3594,11543,3631],{"emptyLinePlaceholder":3630},[3594,11545,11546,11548,11550,11552,11554,11556,11558,11560],{"class":3596,"line":3664},[3594,11547,8984],{"class":4690},[3594,11549,9175],{"class":4459},[3594,11551,6930],{"class":4793},[3594,11553,9180],{"class":4459},[3594,11555,8990],{"class":4493},[3594,11557,4497],{"class":4459},[3594,11559,4641],{"class":4622},[3594,11561,4558],{"class":4459},[3594,11563,11564,11566,11568,11570,11572,11574,11576,11578,11580],{"class":3596,"line":3670},[3594,11565,9193],{"class":4459},[3594,11567,9196],{"class":4793},[3594,11569,9199],{"class":4459},[3594,11571,9013],{"class":4514},[3594,11573,9016],{"class":4459},[3594,11575,9019],{"class":4493},[3594,11577,4497],{"class":4459},[3594,11579,4641],{"class":4622},[3594,11581,4558],{"class":4459},[3594,11583,11584],{"class":3596,"line":3676},[3594,11585,3631],{"emptyLinePlaceholder":3630},[3594,11587,11588],{"class":3596,"line":3681},[3594,11589,11590],{"class":4482},"    # \"Один до Багатьох\": один User має багато Post-ів\n",[3594,11592,11593],{"class":3596,"line":3687},[3594,11594,11595],{"class":4482},"    # back_populates=\"author\" зв'язує цю сторону зі стороною Post.author\n",[3594,11597,11598,11601,11603],{"class":3596,"line":3693},[3594,11599,11600],{"class":4459},"    posts: Mapped[list[",[3594,11602,11159],{"class":4500},[3594,11604,11605],{"class":4459},"]] = relationship(\n",[3594,11607,11608,11611],{"class":3596,"line":3699},[3594,11609,11610],{"class":4500},"        \"Post\"",[3594,11612,4504],{"class":4459},[3594,11614,11615,11618,11620,11623],{"class":3596,"line":3705},[3594,11616,11617],{"class":4493},"        back_populates",[3594,11619,4497],{"class":4459},[3594,11621,11622],{"class":4500},"\"author\"",[3594,11624,4504],{"class":4459},[3594,11626,11627,11630,11632,11634,11636],{"class":3596,"line":3711},[3594,11628,11629],{"class":4493},"        cascade",[3594,11631,4497],{"class":4459},[3594,11633,11248],{"class":4500},[3594,11635,4976],{"class":4459},[3594,11637,11638],{"class":4482},"# При видаленні User — видати всі його Post-и\n",[3594,11640,11641],{"class":3596,"line":3716},[3594,11642,4935],{"class":4459},[3594,11644,11645],{"class":3596,"line":3721},[3594,11646,3631],{"emptyLinePlaceholder":3630},[3594,11648,11649,11651,11654,11656,11658],{"class":3596,"line":3727},[3594,11650,8575],{"class":4622},[3594,11652,11653],{"class":4793}," Post",[3594,11655,7763],{"class":4459},[3594,11657,8965],{"class":4793},[3594,11659,8585],{"class":4459},[3594,11661,11662,11664],{"class":3596,"line":3733},[3594,11663,8972],{"class":4459},[3594,11665,11666],{"class":4500},"\"posts\"\n",[3594,11668,11669],{"class":3596,"line":3739},[3594,11670,3631],{"emptyLinePlaceholder":3630},[3594,11672,11673,11675,11677,11679,11681,11683,11685,11687],{"class":3596,"line":3744},[3594,11674,8984],{"class":4690},[3594,11676,9175],{"class":4459},[3594,11678,6930],{"class":4793},[3594,11680,9180],{"class":4459},[3594,11682,8990],{"class":4493},[3594,11684,4497],{"class":4459},[3594,11686,4641],{"class":4622},[3594,11688,4558],{"class":4459},[3594,11690,11691,11694,11696,11698,11701],{"class":3596,"line":3749},[3594,11692,11693],{"class":4459},"    title: Mapped[",[3594,11695,9196],{"class":4793},[3594,11697,9199],{"class":4459},[3594,11699,11700],{"class":4514},"200",[3594,11702,7851],{"class":4459},[3594,11704,11705,11708,11710,11712,11714],{"class":3596,"line":3755},[3594,11706,11707],{"class":4459},"    content: Mapped[",[3594,11709,9196],{"class":4793},[3594,11711,9641],{"class":4459},[3594,11713,9644],{"class":4622},[3594,11715,10145],{"class":4459},[3594,11717,11718],{"class":3596,"line":3761},[3594,11719,3631],{"emptyLinePlaceholder":3630},[3594,11721,11722],{"class":3596,"line":3766},[3594,11723,11724],{"class":4482},"    # Зовнішній ключ — посилання на users.id\n",[3594,11726,11727,11730,11732,11735,11737],{"class":3596,"line":3772},[3594,11728,11729],{"class":4459},"    author_id: Mapped[",[3594,11731,6930],{"class":4793},[3594,11733,11734],{"class":4459},"] = mapped_column(ForeignKey(",[3594,11736,11325],{"class":4500},[3594,11738,7851],{"class":4459},[3594,11740,11741],{"class":3596,"line":3778},[3594,11742,3631],{"emptyLinePlaceholder":3630},[3594,11744,11745],{"class":3596,"line":3784},[3594,11746,11747],{"class":4482},"    # \"Зворотний\" зв'язок: Post посилається на свого User (автора)\n",[3594,11749,11750,11753,11756,11759,11761,11763,11765,11767,11770],{"class":3596,"line":3790},[3594,11751,11752],{"class":4459},"    author: Mapped[",[3594,11754,11755],{"class":4500},"\"User\"",[3594,11757,11758],{"class":4459},"] = relationship(",[3594,11760,11755],{"class":4500},[3594,11762,3488],{"class":4459},[3594,11764,11163],{"class":4493},[3594,11766,4497],{"class":4459},[3594,11768,11769],{"class":4500},"\"posts\"",[3594,11771,4558],{"class":4459},[3385,11773,11774],{},"Зверніть на кілька важливих деталей:",[3452,11776,11777,11790,11809],{},[3455,11778,11779,11784,11785,3488,11787,11789],{},[3389,11780,11781],{},[3412,11782,11783],{},"from __future__ import annotations"," — це директива, що дозволяє використовувати рядкові посилання на класи (",[3412,11786,11159],{},[3412,11788,11755],{},") без циклічного імпорту. Вона змушує Python відкладати обчислення анотацій і є стандартною практикою у файлах із взаємними посиланнями.",[3455,11791,11792,11796,11797,11800,11801,11804,11805,11808],{},[3389,11793,11794],{},[3412,11795,11163],{}," — параметр, що встановлює ",[3389,11798,11799],{},"двонаправлений зв'язок",": коли ви змінюєте ",[3412,11802,11803],{},"user.posts",", SQLAlchemy автоматично оновлює ",[3412,11806,11807],{},"post.author",", і навпаки. Це синхронізація в оперативній пам'яті, а не в базі даних.",[3455,11810,11811,11816,11817,11819,11820,11822,11823,11825],{},[3389,11812,11813],{},[3412,11814,11815],{},"cascade=\"all, delete-orphan\""," — правило каскадної поведінки: якщо об'єкт ",[3412,11818,9451],{}," видаляється, всі його пов'язані ",[3412,11821,11443],{},"-и також будуть видалені (аналог ",[3412,11824,11258],{}," у SQL, але реалізований на рівні ORM).",[3845,11827,11829],{"id":11828},"порівняння-з-ef-core-navigation-properties","Порівняння з EF Core Navigation Properties",[3385,11831,11832],{},"У EF Core аналогічна структура виглядала б так:",[5448,11834,11835,12061],{},[3585,11836,11839],{"className":10766,"code":11837,"filename":11838,"language":10769,"meta":3590,"style":3590},"public class User\n{\n    public int Id { get; set; }\n    public string Username { get; set; } = default!;\n\n    \u002F\u002F Navigation Property: колекція пов'язаних Post-ів\n    public ICollection\u003CPost> Posts { get; set; } = new List\u003CPost>();\n}\n\npublic class Post\n{\n    public int Id { get; set; }\n    public string Title { get; set; } = default!;\n\n    \u002F\u002F Foreign Key + Navigation Property\n    public int AuthorId { get; set; }\n    public User Author { get; set; } = default!;\n}\n","EF Core — Navigation Properties",[3412,11840,11841,11850,11854,11872,11894,11898,11903,11944,11948,11952,11961,11965,11983,12006,12010,12015,12034,12057],{"__ignoreMap":3590},[3594,11842,11843,11845,11847],{"class":3596,"line":3597},[3594,11844,10776],{"class":4622},[3594,11846,10782],{"class":4622},[3594,11848,11849],{"class":4793}," User\n",[3594,11851,11852],{"class":3596,"line":3603},[3594,11853,10790],{"class":4459},[3594,11855,11856,11858,11860,11862,11864,11866,11868,11870],{"class":3596,"line":3609},[3594,11857,10795],{"class":4622},[3594,11859,10798],{"class":4622},[3594,11861,10801],{"class":4493},[3594,11863,10804],{"class":4459},[3594,11865,10807],{"class":4622},[3594,11867,10810],{"class":4459},[3594,11869,10813],{"class":4622},[3594,11871,10816],{"class":4459},[3594,11873,11874,11876,11878,11880,11882,11884,11886,11888,11890,11892],{"class":3596,"line":3615},[3594,11875,10795],{"class":4622},[3594,11877,10902],{"class":4622},[3594,11879,10905],{"class":4493},[3594,11881,10804],{"class":4459},[3594,11883,10807],{"class":4622},[3594,11885,10810],{"class":4459},[3594,11887,10813],{"class":4622},[3594,11889,10837],{"class":4459},[3594,11891,9068],{"class":4622},[3594,11893,10920],{"class":4459},[3594,11895,11896],{"class":3596,"line":3621},[3594,11897,3631],{"emptyLinePlaceholder":3630},[3594,11899,11900],{"class":3596,"line":3627},[3594,11901,11902],{"class":4482},"    \u002F\u002F Navigation Property: колекція пов'язаних Post-ів\n",[3594,11904,11905,11907,11910,11913,11915,11918,11921,11923,11925,11927,11929,11931,11934,11937,11939,11941],{"class":3596,"line":3634},[3594,11906,10795],{"class":4622},[3594,11908,11909],{"class":4793}," ICollection",[3594,11911,11912],{"class":4459},"\u003C",[3594,11914,11443],{"class":4793},[3594,11916,11917],{"class":4459},"> ",[3594,11919,11920],{"class":4493},"Posts",[3594,11922,10804],{"class":4459},[3594,11924,10807],{"class":4622},[3594,11926,10810],{"class":4459},[3594,11928,10813],{"class":4622},[3594,11930,10837],{"class":4459},[3594,11932,11933],{"class":4622},"new",[3594,11935,11936],{"class":4793}," List",[3594,11938,11912],{"class":4459},[3594,11940,11443],{"class":4793},[3594,11942,11943],{"class":4459},">();\n",[3594,11945,11946],{"class":3596,"line":3640},[3594,11947,3758],{"class":4459},[3594,11949,11950],{"class":3596,"line":3646},[3594,11951,3631],{"emptyLinePlaceholder":3630},[3594,11953,11954,11956,11958],{"class":3596,"line":3652},[3594,11955,10776],{"class":4622},[3594,11957,10782],{"class":4622},[3594,11959,11960],{"class":4793}," Post\n",[3594,11962,11963],{"class":3596,"line":3658},[3594,11964,10790],{"class":4459},[3594,11966,11967,11969,11971,11973,11975,11977,11979,11981],{"class":3596,"line":3664},[3594,11968,10795],{"class":4622},[3594,11970,10798],{"class":4622},[3594,11972,10801],{"class":4493},[3594,11974,10804],{"class":4459},[3594,11976,10807],{"class":4622},[3594,11978,10810],{"class":4459},[3594,11980,10813],{"class":4622},[3594,11982,10816],{"class":4459},[3594,11984,11985,11987,11989,11992,11994,11996,11998,12000,12002,12004],{"class":3596,"line":3670},[3594,11986,10795],{"class":4622},[3594,11988,10902],{"class":4622},[3594,11990,11991],{"class":4493}," Title",[3594,11993,10804],{"class":4459},[3594,11995,10807],{"class":4622},[3594,11997,10810],{"class":4459},[3594,11999,10813],{"class":4622},[3594,12001,10837],{"class":4459},[3594,12003,9068],{"class":4622},[3594,12005,10920],{"class":4459},[3594,12007,12008],{"class":3596,"line":3676},[3594,12009,3631],{"emptyLinePlaceholder":3630},[3594,12011,12012],{"class":3596,"line":3681},[3594,12013,12014],{"class":4482},"    \u002F\u002F Foreign Key + Navigation Property\n",[3594,12016,12017,12019,12021,12024,12026,12028,12030,12032],{"class":3596,"line":3687},[3594,12018,10795],{"class":4622},[3594,12020,10798],{"class":4622},[3594,12022,12023],{"class":4493}," AuthorId",[3594,12025,10804],{"class":4459},[3594,12027,10807],{"class":4622},[3594,12029,10810],{"class":4459},[3594,12031,10813],{"class":4622},[3594,12033,10816],{"class":4459},[3594,12035,12036,12038,12040,12043,12045,12047,12049,12051,12053,12055],{"class":3596,"line":3693},[3594,12037,10795],{"class":4622},[3594,12039,8960],{"class":4793},[3594,12041,12042],{"class":4493}," Author",[3594,12044,10804],{"class":4459},[3594,12046,10807],{"class":4622},[3594,12048,10810],{"class":4459},[3594,12050,10813],{"class":4622},[3594,12052,10837],{"class":4459},[3594,12054,9068],{"class":4622},[3594,12056,10920],{"class":4459},[3594,12058,12059],{"class":3596,"line":3699},[3594,12060,3758],{"class":4459},[3585,12062,12065],{"className":4445,"code":12063,"filename":12064,"language":4448,"meta":3590,"style":3590},"class User(Base):\n    __tablename__ = \"users\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    username: Mapped[str] = mapped_column(String(50))\n\n    # Аналог Navigation Property з колекцією\n    posts: Mapped[list[\"Post\"]] = relationship(back_populates=\"author\")\n\nclass Post(Base):\n    __tablename__ = \"posts\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    title: Mapped[str] = mapped_column(String(200))\n\n    # Аналог Foreign Key + Navigation Property\n    author_id: Mapped[int] = mapped_column(ForeignKey(\"users.id\"))\n    author: Mapped[\"User\"] = relationship(back_populates=\"posts\")\n","SQLAlchemy 2.0 — relationship()",[3412,12066,12067,12079,12085,12103,12115,12119,12124,12141,12145,12157,12163,12181,12193,12197,12202,12214],{"__ignoreMap":3590},[3594,12068,12069,12071,12073,12075,12077],{"class":3596,"line":3597},[3594,12070,8575],{"class":4622},[3594,12072,8960],{"class":4793},[3594,12074,7763],{"class":4459},[3594,12076,8965],{"class":4793},[3594,12078,8585],{"class":4459},[3594,12080,12081,12083],{"class":3596,"line":3603},[3594,12082,8972],{"class":4459},[3594,12084,8975],{"class":4500},[3594,12086,12087,12089,12091,12093,12095,12097,12099,12101],{"class":3596,"line":3609},[3594,12088,8984],{"class":4690},[3594,12090,9175],{"class":4459},[3594,12092,6930],{"class":4793},[3594,12094,9180],{"class":4459},[3594,12096,8990],{"class":4493},[3594,12098,4497],{"class":4459},[3594,12100,4641],{"class":4622},[3594,12102,4558],{"class":4459},[3594,12104,12105,12107,12109,12111,12113],{"class":3596,"line":3615},[3594,12106,9193],{"class":4459},[3594,12108,9196],{"class":4793},[3594,12110,9199],{"class":4459},[3594,12112,9013],{"class":4514},[3594,12114,7851],{"class":4459},[3594,12116,12117],{"class":3596,"line":3621},[3594,12118,3631],{"emptyLinePlaceholder":3630},[3594,12120,12121],{"class":3596,"line":3627},[3594,12122,12123],{"class":4482},"    # Аналог Navigation Property з колекцією\n",[3594,12125,12126,12128,12130,12133,12135,12137,12139],{"class":3596,"line":3634},[3594,12127,11600],{"class":4459},[3594,12129,11159],{"class":4500},[3594,12131,12132],{"class":4459},"]] = relationship(",[3594,12134,11163],{"class":4493},[3594,12136,4497],{"class":4459},[3594,12138,11622],{"class":4500},[3594,12140,4558],{"class":4459},[3594,12142,12143],{"class":3596,"line":3640},[3594,12144,3631],{"emptyLinePlaceholder":3630},[3594,12146,12147,12149,12151,12153,12155],{"class":3596,"line":3646},[3594,12148,8575],{"class":4622},[3594,12150,11653],{"class":4793},[3594,12152,7763],{"class":4459},[3594,12154,8965],{"class":4793},[3594,12156,8585],{"class":4459},[3594,12158,12159,12161],{"class":3596,"line":3652},[3594,12160,8972],{"class":4459},[3594,12162,11666],{"class":4500},[3594,12164,12165,12167,12169,12171,12173,12175,12177,12179],{"class":3596,"line":3658},[3594,12166,8984],{"class":4690},[3594,12168,9175],{"class":4459},[3594,12170,6930],{"class":4793},[3594,12172,9180],{"class":4459},[3594,12174,8990],{"class":4493},[3594,12176,4497],{"class":4459},[3594,12178,4641],{"class":4622},[3594,12180,4558],{"class":4459},[3594,12182,12183,12185,12187,12189,12191],{"class":3596,"line":3664},[3594,12184,11693],{"class":4459},[3594,12186,9196],{"class":4793},[3594,12188,9199],{"class":4459},[3594,12190,11700],{"class":4514},[3594,12192,7851],{"class":4459},[3594,12194,12195],{"class":3596,"line":3670},[3594,12196,3631],{"emptyLinePlaceholder":3630},[3594,12198,12199],{"class":3596,"line":3676},[3594,12200,12201],{"class":4482},"    # Аналог Foreign Key + Navigation Property\n",[3594,12203,12204,12206,12208,12210,12212],{"class":3596,"line":3681},[3594,12205,11729],{"class":4459},[3594,12207,6930],{"class":4793},[3594,12209,11734],{"class":4459},[3594,12211,11325],{"class":4500},[3594,12213,7851],{"class":4459},[3594,12215,12216,12218,12220,12222,12224,12226,12228],{"class":3596,"line":3687},[3594,12217,11752],{"class":4459},[3594,12219,11755],{"class":4500},[3594,12221,11758],{"class":4459},[3594,12223,11163],{"class":4493},[3594,12225,4497],{"class":4459},[3594,12227,11769],{"class":4500},[3594,12229,4558],{"class":4459},[3845,12231,12233],{"id":12232},"many-to-many-багато-до-багатьох","Many-to-Many (Багато до Багатьох)",[3385,12235,12236,12237,11437,12240,11440,12242,12245,12246,12248,12249,11444],{},"Зв'язок «багато до багатьох» реалізується через ",[3389,12238,12239],{},"асоціативну (проміжну) таблицю",[3412,12241,11443],{},[3412,12243,12244],{},"Tag","-ів, і один ",[3412,12247,12244],{}," може належати до багатьох ",[3412,12250,11443],{},[3385,12252,12253],{},"У SQLAlchemy 2.0 є два підходи для реалізації зв'язку «багато до багатьох».",[4433,12255,12257,12258,3435,12260],{"id":12256},"конструювання-таблиць-на-рівні-core-table-та-column","Конструювання таблиць на рівні Core: ",[3412,12259,3882],{},[3412,12261,12262],{},"Column",[3385,12264,12265,12266,12270,12271,12275,12276,12278],{},"Для створення найпростішої проміжної таблиці (без додаткових полів) у SQLAlchemy використовується низькорівневий конструктор ",[3389,12267,12268],{},[3412,12269,3882],{}," та клас ",[3389,12272,12273],{},[3412,12274,12262],{}," з пакету ",[3412,12277,5258],{}," (Core-рівень). Оскільки ця таблиця потрібна лише для зв'язку між об'єктами і ми не будемо створювати для неї окремий Python-клас моделі, вона оголошується як об'єкт.",[9975,12280,12281,12289,12299],{},[9978,12282,12285,12286,3465],{"name":12283,"type":9196,"default":12284},"name","Required","Перший позиційний аргумент — фізичне ім'я таблиці у базі даних (наприклад, ",[3412,12287,12288],{},"\"post_tags\"",[9978,12290,12292,12293,12295,12296,12298],{"name":12291,"type":3877,"default":12284},"metadata","Другий позиційний аргумент — об'єкт ",[3412,12294,3877],{},", у якому реєструється таблиця (зазвичай передається ",[3412,12297,9397],{},"). Це необхідно, щоб SQLAlchemy та інструмент міграцій Alembic знали про існування цієї таблиці та могли згенерувати її в БД.",[9978,12300,12303,12304,12307,12308,12310,12311,3465],{"name":12301,"type":12302,"default":12284},"args (Column \u002F Constraint)","Column | Constraint","Усі наступні позиційні аргументи (",[3412,12305,12306],{},"*args",") — це стовпці таблиці (об'єкти ",[3412,12309,12262],{},") або обмеження цілісності (наприклад, ",[3412,12312,12313],{},"UniqueConstraint",[3385,12315,12316,12317,12321,12322,12324],{},"Також у цих таблицях використовуються класи ",[3389,12318,12319],{},[3412,12320,12262],{}," (старий Core-еквівалент сучасного ORM-івського ",[3412,12323,3438],{},"):",[9975,12326,12327,12335,12348],{},[9978,12328,12330,12331,12334],{"name":12283,"type":9196,"default":12329},"Optional","Рядкове ім'я стовпця у базі даних. Якщо стовпець створюється всередині ",[3412,12332,12333],{},"Table()",", це ім'я є обов'язковим першим аргументом.",[9978,12336,12339,12340,11322,12343,3488,12345,3465],{"name":12337,"type":12338,"default":12329},"type","TypeEngine","Тип даних SQL ",[3412,12341,12342],{},"type_",[3412,12344,9809],{},[3412,12346,12347],{},"String(50)",[9978,12349,12352,12353,4025,12355,12358,12359,3488,12361,3488,12363,3488,12366,12369],{"name":12350,"type":12351,"default":9644},"args \u002F kwargs","Any","Додаткові опції конфігурації (",[3412,12354,12306],{},[3412,12356,12357],{},"**kwargs","): ",[3412,12360,11148],{},[3412,12362,5585],{},[3412,12364,12365],{},"nullable=False",[3412,12367,12368],{},"index=True"," тощо.",[3441,12371],{},[3385,12373,12374,12377],{},[3389,12375,12376],{},"Підхід 1: Чиста асоціативна таблиця"," (якщо таблиця не має власних атрибутів):",[3585,12379,12382],{"className":4445,"code":12380,"filename":12381,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import Table, Column, ForeignKey\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship\n\nclass Base(DeclarativeBase):\n    pass\n\n# Проміжна таблиця (без власного класу)\npost_tags = Table(\n    \"post_tags\",\n    Base.metadata,\n    Column(\"post_id\", ForeignKey(\"posts.id\"), primary_key=True),\n    Column(\"tag_id\", ForeignKey(\"tags.id\"), primary_key=True),\n)\n\nclass Post(Base):\n    __tablename__ = \"posts\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    title: Mapped[str] = mapped_column(String(200))\n\n    # Many-to-Many через secondary (проміжну таблицю)\n    tags: Mapped[list[\"Tag\"]] = relationship(\n        \"Tag\",\n        secondary=post_tags,\n        back_populates=\"posts\"\n    )\n\nclass Tag(Base):\n    __tablename__ = \"tags\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    name: Mapped[str] = mapped_column(String(50), unique=True)\n\n    posts: Mapped[list[\"Post\"]] = relationship(\n        \"Post\",\n        secondary=post_tags,\n        back_populates=\"tags\"\n    )\n","models\u002Ftags.py",[3412,12383,12384,12395,12405,12409,12421,12425,12429,12434,12439,12446,12451,12475,12497,12501,12505,12517,12523,12541,12553,12557,12562,12572,12579,12587,12595,12599,12603,12616,12623,12641,12662,12666,12674,12680,12686,12694],{"__ignoreMap":3590},[3594,12385,12386,12388,12390,12392],{"class":3596,"line":3597},[3594,12387,4465],{"class":4455},[3594,12389,5996],{"class":4459},[3594,12391,4456],{"class":4455},[3594,12393,12394],{"class":4459}," Table, Column, ForeignKey\n",[3594,12396,12397,12399,12401,12403],{"class":3596,"line":3603},[3594,12398,4465],{"class":4455},[3594,12400,6383],{"class":4459},[3594,12402,4456],{"class":4455},[3594,12404,11497],{"class":4459},[3594,12406,12407],{"class":3596,"line":3609},[3594,12408,3631],{"emptyLinePlaceholder":3630},[3594,12410,12411,12413,12415,12417,12419],{"class":3596,"line":3615},[3594,12412,8575],{"class":4622},[3594,12414,9127],{"class":4793},[3594,12416,7763],{"class":4459},[3594,12418,3931],{"class":4793},[3594,12420,8585],{"class":4459},[3594,12422,12423],{"class":3596,"line":3621},[3594,12424,9142],{"class":4455},[3594,12426,12427],{"class":3596,"line":3627},[3594,12428,3631],{"emptyLinePlaceholder":3630},[3594,12430,12431],{"class":3596,"line":3634},[3594,12432,12433],{"class":4482},"# Проміжна таблиця (без власного класу)\n",[3594,12435,12436],{"class":3596,"line":3640},[3594,12437,12438],{"class":4459},"post_tags = Table(\n",[3594,12440,12441,12444],{"class":3596,"line":3646},[3594,12442,12443],{"class":4500},"    \"post_tags\"",[3594,12445,4504],{"class":4459},[3594,12447,12448],{"class":3596,"line":3652},[3594,12449,12450],{"class":4459},"    Base.metadata,\n",[3594,12452,12453,12456,12459,12462,12465,12467,12469,12471,12473],{"class":3596,"line":3658},[3594,12454,12455],{"class":4459},"    Column(",[3594,12457,12458],{"class":4500},"\"post_id\"",[3594,12460,12461],{"class":4459},", ForeignKey(",[3594,12463,12464],{"class":4500},"\"posts.id\"",[3594,12466,9016],{"class":4459},[3594,12468,8990],{"class":4493},[3594,12470,4497],{"class":4459},[3594,12472,4641],{"class":4622},[3594,12474,4753],{"class":4459},[3594,12476,12477,12479,12482,12484,12487,12489,12491,12493,12495],{"class":3596,"line":3664},[3594,12478,12455],{"class":4459},[3594,12480,12481],{"class":4500},"\"tag_id\"",[3594,12483,12461],{"class":4459},[3594,12485,12486],{"class":4500},"\"tags.id\"",[3594,12488,9016],{"class":4459},[3594,12490,8990],{"class":4493},[3594,12492,4497],{"class":4459},[3594,12494,4641],{"class":4622},[3594,12496,4753],{"class":4459},[3594,12498,12499],{"class":3596,"line":3670},[3594,12500,4558],{"class":4459},[3594,12502,12503],{"class":3596,"line":3676},[3594,12504,3631],{"emptyLinePlaceholder":3630},[3594,12506,12507,12509,12511,12513,12515],{"class":3596,"line":3681},[3594,12508,8575],{"class":4622},[3594,12510,11653],{"class":4793},[3594,12512,7763],{"class":4459},[3594,12514,8965],{"class":4793},[3594,12516,8585],{"class":4459},[3594,12518,12519,12521],{"class":3596,"line":3687},[3594,12520,8972],{"class":4459},[3594,12522,11666],{"class":4500},[3594,12524,12525,12527,12529,12531,12533,12535,12537,12539],{"class":3596,"line":3693},[3594,12526,8984],{"class":4690},[3594,12528,9175],{"class":4459},[3594,12530,6930],{"class":4793},[3594,12532,9180],{"class":4459},[3594,12534,8990],{"class":4493},[3594,12536,4497],{"class":4459},[3594,12538,4641],{"class":4622},[3594,12540,4558],{"class":4459},[3594,12542,12543,12545,12547,12549,12551],{"class":3596,"line":3699},[3594,12544,11693],{"class":4459},[3594,12546,9196],{"class":4793},[3594,12548,9199],{"class":4459},[3594,12550,11700],{"class":4514},[3594,12552,7851],{"class":4459},[3594,12554,12555],{"class":3596,"line":3705},[3594,12556,3631],{"emptyLinePlaceholder":3630},[3594,12558,12559],{"class":3596,"line":3711},[3594,12560,12561],{"class":4482},"    # Many-to-Many через secondary (проміжну таблицю)\n",[3594,12563,12564,12567,12570],{"class":3596,"line":3716},[3594,12565,12566],{"class":4459},"    tags: Mapped[list[",[3594,12568,12569],{"class":4500},"\"Tag\"",[3594,12571,11605],{"class":4459},[3594,12573,12574,12577],{"class":3596,"line":3721},[3594,12575,12576],{"class":4500},"        \"Tag\"",[3594,12578,4504],{"class":4459},[3594,12580,12581,12584],{"class":3596,"line":3727},[3594,12582,12583],{"class":4493},"        secondary",[3594,12585,12586],{"class":4459},"=post_tags,\n",[3594,12588,12589,12591,12593],{"class":3596,"line":3733},[3594,12590,11617],{"class":4493},[3594,12592,4497],{"class":4459},[3594,12594,11666],{"class":4500},[3594,12596,12597],{"class":3596,"line":3739},[3594,12598,4935],{"class":4459},[3594,12600,12601],{"class":3596,"line":3744},[3594,12602,3631],{"emptyLinePlaceholder":3630},[3594,12604,12605,12607,12610,12612,12614],{"class":3596,"line":3749},[3594,12606,8575],{"class":4622},[3594,12608,12609],{"class":4793}," Tag",[3594,12611,7763],{"class":4459},[3594,12613,8965],{"class":4793},[3594,12615,8585],{"class":4459},[3594,12617,12618,12620],{"class":3596,"line":3755},[3594,12619,8972],{"class":4459},[3594,12621,12622],{"class":4500},"\"tags\"\n",[3594,12624,12625,12627,12629,12631,12633,12635,12637,12639],{"class":3596,"line":3761},[3594,12626,8984],{"class":4690},[3594,12628,9175],{"class":4459},[3594,12630,6930],{"class":4793},[3594,12632,9180],{"class":4459},[3594,12634,8990],{"class":4493},[3594,12636,4497],{"class":4459},[3594,12638,4641],{"class":4622},[3594,12640,4558],{"class":4459},[3594,12642,12643,12646,12648,12650,12652,12654,12656,12658,12660],{"class":3596,"line":3766},[3594,12644,12645],{"class":4459},"    name: Mapped[",[3594,12647,9196],{"class":4793},[3594,12649,9199],{"class":4459},[3594,12651,9013],{"class":4514},[3594,12653,9016],{"class":4459},[3594,12655,9019],{"class":4493},[3594,12657,4497],{"class":4459},[3594,12659,4641],{"class":4622},[3594,12661,4558],{"class":4459},[3594,12663,12664],{"class":3596,"line":3772},[3594,12665,3631],{"emptyLinePlaceholder":3630},[3594,12667,12668,12670,12672],{"class":3596,"line":3778},[3594,12669,11600],{"class":4459},[3594,12671,11159],{"class":4500},[3594,12673,11605],{"class":4459},[3594,12675,12676,12678],{"class":3596,"line":3784},[3594,12677,11610],{"class":4500},[3594,12679,4504],{"class":4459},[3594,12681,12682,12684],{"class":3596,"line":3790},[3594,12683,12583],{"class":4493},[3594,12685,12586],{"class":4459},[3594,12687,12688,12690,12692],{"class":3596,"line":3796},[3594,12689,11617],{"class":4493},[3594,12691,4497],{"class":4459},[3594,12693,12622],{"class":4500},[3594,12695,12696],{"class":3596,"line":3802},[3594,12697,4935],{"class":4459},[3385,12699,12700,12703,12704,11116,12707,12324],{},[3389,12701,12702],{},"Підхід 2: Асоціативний клас"," (якщо проміжна таблиця має власні атрибути, наприклад, ",[3412,12705,12706],{},"role",[3412,12708,12709],{},"ProjectMember",[3585,12711,12714],{"className":4445,"code":12712,"filename":12713,"language":4448,"meta":3590,"style":3590},"from datetime import datetime\nfrom sqlalchemy import String, ForeignKey, Enum, func\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship\nimport enum\n\nclass Base(DeclarativeBase):\n    pass\n\nclass MemberRole(enum.Enum):\n    OWNER = \"owner\"\n    EDITOR = \"editor\"\n    VIEWER = \"viewer\"\n\nclass ProjectMember(Base):\n    \"\"\"Асоціативна таблиця з власними атрибутами.\"\"\"\n    __tablename__ = \"project_members\"\n\n    project_id: Mapped[int] = mapped_column(\n        ForeignKey(\"projects.id\"), primary_key=True\n    )\n    user_id: Mapped[int] = mapped_column(\n        ForeignKey(\"users.id\"), primary_key=True\n    )\n    role: Mapped[MemberRole] = mapped_column(\n        Enum(MemberRole), default=MemberRole.VIEWER\n    )\n    joined_at: Mapped[datetime] = mapped_column(server_default=func.now())\n\n    # Navigation properties\n    project: Mapped[\"Project\"] = relationship(back_populates=\"memberships\")\n    user: Mapped[\"User\"] = relationship(back_populates=\"memberships\")\n\nclass Project(Base):\n    __tablename__ = \"projects\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    name: Mapped[str] = mapped_column(String(100))\n\n    memberships: Mapped[list[\"ProjectMember\"]] = relationship(\n        back_populates=\"project\"\n    )\n","models\u002Fproject.py",[3412,12715,12716,12726,12737,12747,12753,12757,12769,12773,12777,12794,12802,12810,12818,12822,12835,12840,12847,12851,12860,12877,12881,12890,12904,12908,12913,12923,12927,12936,12940,12945,12964,12981,12985,12997,13003,13021,13033,13037,13047,13056],{"__ignoreMap":3590},[3594,12717,12718,12720,12722,12724],{"class":3596,"line":3597},[3594,12719,4465],{"class":4455},[3594,12721,9465],{"class":4459},[3594,12723,4456],{"class":4455},[3594,12725,9470],{"class":4459},[3594,12727,12728,12730,12732,12734],{"class":3596,"line":3603},[3594,12729,4465],{"class":4455},[3594,12731,5996],{"class":4459},[3594,12733,4456],{"class":4455},[3594,12735,12736],{"class":4459}," String, ForeignKey, Enum, func\n",[3594,12738,12739,12741,12743,12745],{"class":3596,"line":3609},[3594,12740,4465],{"class":4455},[3594,12742,6383],{"class":4459},[3594,12744,4456],{"class":4455},[3594,12746,11497],{"class":4459},[3594,12748,12749,12751],{"class":3596,"line":3615},[3594,12750,4456],{"class":4455},[3594,12752,10592],{"class":4459},[3594,12754,12755],{"class":3596,"line":3621},[3594,12756,3631],{"emptyLinePlaceholder":3630},[3594,12758,12759,12761,12763,12765,12767],{"class":3596,"line":3627},[3594,12760,8575],{"class":4622},[3594,12762,9127],{"class":4793},[3594,12764,7763],{"class":4459},[3594,12766,3931],{"class":4793},[3594,12768,8585],{"class":4459},[3594,12770,12771],{"class":3596,"line":3634},[3594,12772,9142],{"class":4455},[3594,12774,12775],{"class":3596,"line":3640},[3594,12776,3631],{"emptyLinePlaceholder":3630},[3594,12778,12779,12781,12784,12786,12788,12790,12792],{"class":3596,"line":3646},[3594,12780,8575],{"class":4622},[3594,12782,12783],{"class":4793}," MemberRole",[3594,12785,7763],{"class":4459},[3594,12787,10619],{"class":4793},[3594,12789,3439],{"class":4459},[3594,12791,10192],{"class":4793},[3594,12793,8585],{"class":4459},[3594,12795,12796,12799],{"class":3596,"line":3652},[3594,12797,12798],{"class":4459},"    OWNER = ",[3594,12800,12801],{"class":4500},"\"owner\"\n",[3594,12803,12804,12807],{"class":3596,"line":3658},[3594,12805,12806],{"class":4459},"    EDITOR = ",[3594,12808,12809],{"class":4500},"\"editor\"\n",[3594,12811,12812,12815],{"class":3596,"line":3664},[3594,12813,12814],{"class":4459},"    VIEWER = ",[3594,12816,12817],{"class":4500},"\"viewer\"\n",[3594,12819,12820],{"class":3596,"line":3670},[3594,12821,3631],{"emptyLinePlaceholder":3630},[3594,12823,12824,12826,12829,12831,12833],{"class":3596,"line":3676},[3594,12825,8575],{"class":4622},[3594,12827,12828],{"class":4793}," ProjectMember",[3594,12830,7763],{"class":4459},[3594,12832,8965],{"class":4793},[3594,12834,8585],{"class":4459},[3594,12836,12837],{"class":3596,"line":3681},[3594,12838,12839],{"class":4500},"    \"\"\"Асоціативна таблиця з власними атрибутами.\"\"\"\n",[3594,12841,12842,12844],{"class":3596,"line":3687},[3594,12843,8972],{"class":4459},[3594,12845,12846],{"class":4500},"\"project_members\"\n",[3594,12848,12849],{"class":3596,"line":3693},[3594,12850,3631],{"emptyLinePlaceholder":3630},[3594,12852,12853,12856,12858],{"class":3596,"line":3699},[3594,12854,12855],{"class":4459},"    project_id: Mapped[",[3594,12857,6930],{"class":4793},[3594,12859,11023],{"class":4459},[3594,12861,12862,12865,12868,12870,12872,12874],{"class":3596,"line":3705},[3594,12863,12864],{"class":4459},"        ForeignKey(",[3594,12866,12867],{"class":4500},"\"projects.id\"",[3594,12869,9016],{"class":4459},[3594,12871,8990],{"class":4493},[3594,12873,4497],{"class":4459},[3594,12875,12876],{"class":4622},"True\n",[3594,12878,12879],{"class":3596,"line":3711},[3594,12880,4935],{"class":4459},[3594,12882,12883,12886,12888],{"class":3596,"line":3716},[3594,12884,12885],{"class":4459},"    user_id: Mapped[",[3594,12887,6930],{"class":4793},[3594,12889,11023],{"class":4459},[3594,12891,12892,12894,12896,12898,12900,12902],{"class":3596,"line":3721},[3594,12893,12864],{"class":4459},[3594,12895,11325],{"class":4500},[3594,12897,9016],{"class":4459},[3594,12899,8990],{"class":4493},[3594,12901,4497],{"class":4459},[3594,12903,12876],{"class":4622},[3594,12905,12906],{"class":3596,"line":3727},[3594,12907,4935],{"class":4459},[3594,12909,12910],{"class":3596,"line":3733},[3594,12911,12912],{"class":4459},"    role: Mapped[MemberRole] = mapped_column(\n",[3594,12914,12915,12918,12920],{"class":3596,"line":3739},[3594,12916,12917],{"class":4459},"        Enum(MemberRole), ",[3594,12919,9068],{"class":4493},[3594,12921,12922],{"class":4459},"=MemberRole.VIEWER\n",[3594,12924,12925],{"class":3596,"line":3744},[3594,12926,4935],{"class":4459},[3594,12928,12929,12932,12934],{"class":3596,"line":3749},[3594,12930,12931],{"class":4459},"    joined_at: Mapped[datetime] = mapped_column(",[3594,12933,10398],{"class":4493},[3594,12935,10401],{"class":4459},[3594,12937,12938],{"class":3596,"line":3755},[3594,12939,3631],{"emptyLinePlaceholder":3630},[3594,12941,12942],{"class":3596,"line":3761},[3594,12943,12944],{"class":4482},"    # Navigation properties\n",[3594,12946,12947,12950,12953,12955,12957,12959,12962],{"class":3596,"line":3766},[3594,12948,12949],{"class":4459},"    project: Mapped[",[3594,12951,12952],{"class":4500},"\"Project\"",[3594,12954,11758],{"class":4459},[3594,12956,11163],{"class":4493},[3594,12958,4497],{"class":4459},[3594,12960,12961],{"class":4500},"\"memberships\"",[3594,12963,4558],{"class":4459},[3594,12965,12966,12969,12971,12973,12975,12977,12979],{"class":3596,"line":3772},[3594,12967,12968],{"class":4459},"    user: Mapped[",[3594,12970,11755],{"class":4500},[3594,12972,11758],{"class":4459},[3594,12974,11163],{"class":4493},[3594,12976,4497],{"class":4459},[3594,12978,12961],{"class":4500},[3594,12980,4558],{"class":4459},[3594,12982,12983],{"class":3596,"line":3778},[3594,12984,3631],{"emptyLinePlaceholder":3630},[3594,12986,12987,12989,12991,12993,12995],{"class":3596,"line":3784},[3594,12988,8575],{"class":4622},[3594,12990,10710],{"class":4793},[3594,12992,7763],{"class":4459},[3594,12994,8965],{"class":4793},[3594,12996,8585],{"class":4459},[3594,12998,12999,13001],{"class":3596,"line":3790},[3594,13000,8972],{"class":4459},[3594,13002,10723],{"class":4500},[3594,13004,13005,13007,13009,13011,13013,13015,13017,13019],{"class":3596,"line":3796},[3594,13006,8984],{"class":4690},[3594,13008,9175],{"class":4459},[3594,13010,6930],{"class":4793},[3594,13012,9180],{"class":4459},[3594,13014,8990],{"class":4493},[3594,13016,4497],{"class":4459},[3594,13018,4641],{"class":4622},[3594,13020,4558],{"class":4459},[3594,13022,13023,13025,13027,13029,13031],{"class":3596,"line":3802},[3594,13024,12645],{"class":4459},[3594,13026,9196],{"class":4793},[3594,13028,9199],{"class":4459},[3594,13030,9042],{"class":4514},[3594,13032,7851],{"class":4459},[3594,13034,13035],{"class":3596,"line":3808},[3594,13036,3631],{"emptyLinePlaceholder":3630},[3594,13038,13039,13042,13045],{"class":3596,"line":3814},[3594,13040,13041],{"class":4459},"    memberships: Mapped[list[",[3594,13043,13044],{"class":4500},"\"ProjectMember\"",[3594,13046,11605],{"class":4459},[3594,13048,13049,13051,13053],{"class":3596,"line":3820},[3594,13050,11617],{"class":4493},[3594,13052,4497],{"class":4459},[3594,13054,13055],{"class":4500},"\"project\"\n",[3594,13057,13058],{"class":3596,"line":3826},[3594,13059,4935],{"class":4459},[3441,13061],{},[4433,13063,13065,13066],{"id":13064},"параметри-конфігурації-relationship","Параметри конфігурації ",[3412,13067,3956],{},[3385,13069,5980,13070,13072],{},[3412,13071,3956],{}," керує об'єднанням моделей на рівні Python. Вона приймає низку критично важливих параметрів, які визначають поведінку зв'язків:",[3510,13074,13075,13142,13181,13232],{},[3455,13076,13077,6927,13081,6931,13083,13086,13087],{},[3389,13078,13079],{},[3412,13080,11167],{},[3412,13082,9196],{},[3412,13084,13085],{},"\"save-update, merge\"",")\nКерує тим, як операції над батьківським об'єктом (наприклад, додавання, оновлення, видалення) поширюються на пов'язані дочірні об'єкти.",[3510,13088,13089,13098,13109,13121],{},[3455,13090,13091,13093,13094,13097],{},[3412,13092,11178],{},": якщо додати батьківський об'єкт до сесії (",[3412,13095,13096],{},"session.add(parent)","), усі його дочірні об'єкти автоматично додадуться до сесії.",[3455,13099,13100,13102,13103,13105,13106,13108],{},[3412,13101,11197],{},": якщо видалити батьківський об'єкт (",[3412,13104,11201],{},"), автоматично видаляться всі пов'язані дочірні об'єкти (аналог ",[3412,13107,11258],{}," у БД, але виконується силами Python).",[3455,13110,13111,13113,13114,13117,13118,3465],{},[3412,13112,11207],{}," (критично важливий): якщо дочірній об'єкт від'єднати від батьківського (наприклад, видалити його зі списку ",[3412,13115,13116],{},"parent.children.remove(child)","), цей дочірній об'єкт автоматично буде видалено з БД (інакше він перетвориться на «сироту» — запис у БД з ",[3412,13119,13120],{},"parent_id = NULL",[3455,13122,13123,13126,13127,13129,13130,13132,13133,13135,13136,13138,13139,3465],{},[6344,13124,13125],{},"Рекомендований набір:"," для One-to-Many зв'язків із жорстким володінням (наприклад, ",[3412,13128,9451],{}," -> ",[3412,13131,11920],{},") зазвичай ставлять ",[3412,13134,11815],{}," (",[3412,13137,11240],{}," є синонімом для ",[3412,13140,13141],{},"save-update, merge, refresh-expire, expunge, delete",[3455,13143,13144,6927,13148,3461,13150,6931,13153,13155,13156],{},[3389,13145,13146],{},[3412,13147,11251],{},[3412,13149,7038],{},[3412,13151,13152],{},"\"all\"",[3412,13154,6238],{},")\nВпливає на поведінку каскадного видалення.",[3510,13157,13158,13170],{},[3455,13159,7483,13160,13162,13163,13165,13166,13169],{},[3412,13161,6238],{},", то при видаленні батьківського об'єкта SQLAlchemy виконає окремий ",[3412,13164,7918],{},"-запит, щоб завантажити всі дочірні об'єкти в пам'ять, а потім згенерує окремі ",[3412,13167,13168],{},"DELETE","-запити для кожного з них. Це створює величезний оверхед.",[3455,13171,7483,13172,13174,13175,11322,13177,13180],{},[3412,13173,4641],{},", SQLAlchemy НЕ завантажуватиме дочірні об'єкти, а просто видалить батьківський об'єкт. При цьому вона покладатиметься на те, що на рівні самої бази даних у зовнішньому ключі прописано ",[3412,13176,11258],{},[3412,13178,13179],{},"ForeignKey(\"parent.id\", ondelete=\"CASCADE\")","). Це в рази швидше й ефективніше.",[3455,13182,13183,6927,13187,6931,13189,13191,13192],{},[3389,13184,13185],{},[3412,13186,11262],{},[3412,13188,9196],{},[3412,13190,11267],{},")\nВизначає стратегію завантаження пов'язаних об'єктів за замовчуванням, якщо її не перевизначено в запиті:",[3510,13193,13194,13201,13212,13223],{},[3455,13195,13196,10243,13198,13200],{},[3412,13197,11267],{},[3412,13199,4641],{},"): ліниве завантаження (lazy loading).",[3455,13202,13203,10243,13205,13207,13208,13211],{},[3412,13204,11271],{},[3412,13206,6238],{},"): жадібне завантаження через ",[3412,13209,13210],{},"LEFT OUTER JOIN"," (eager loading).",[3455,13213,13214,13216,13217,13219,13220,13211],{},[3412,13215,11275],{},": жадібне завантаження окремим ",[3412,13218,7918],{}," з ",[3412,13221,13222],{},"IN",[3455,13224,13225,13227,13228,13231],{},[3412,13226,11279],{},": кидає помилку при спробі lazy loading. Дуже корисний режим для асинхронного коду, щоб гарантувати, що розробник не забув зробити eager loading і не отримав ",[3412,13229,13230],{},"MissingGreenlet"," у невідповідний момент.",[3455,13233,13234,13238,13239,13244],{},[3389,13235,13236],{},[3412,13237,11163],{}," vs ",[3389,13240,13241],{},[3412,13242,13243],{},"backref",[3510,13245,13246,13258],{},[3455,13247,13248,13250,13251,13253,13254,13257],{},[3412,13249,11163],{}," (рекомендовано в 2.0) вимагає явного визначення ",[3412,13252,3956],{}," на ",[6344,13255,13256],{},"обох"," моделях із взаємним посиланням одна на одну. Це забезпечує повну сумісність із type hints та автодоповненням IDE.",[3455,13259,13260,13262],{},[3412,13261,13243],{}," (застарілий стиль) визначає зв'язок лише на одній моделі, а зворотний зв'язок на іншій створюється динамічно «магічним» шляхом. Це унеможливлює статичну перевірку типів через mypy та призводить до прихованих помилок.",[5430,13264,13266],{"id":13265},"приклад-звязку-з-оптимізованим-каскадним-видаленням-та-type-safety","Приклад зв'язку з оптимізованим каскадним видаленням та type safety:",[3585,13268,13270],{"className":4445,"code":13269,"language":4448,"meta":3590,"style":3590},"class Project(Base):\n    __tablename__ = \"projects\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n\n    # Каскадне видалення дочірніх завдань оптимізовано через passive_deletes\n    tasks: Mapped[list[\"Task\"]] = relationship(\n        \"Task\",\n        back_populates=\"project\",\n        cascade=\"all, delete-orphan\",\n        passive_deletes=True,         # SQLAlchemy не буде робити SELECT перед DELETE\n    )\n\nclass Task(Base):\n    __tablename__ = \"tasks\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n\n    # ON DELETE CASCADE на рівні бази даних для роботи passive_deletes\n    project_id: Mapped[int] = mapped_column(\n        ForeignKey(\"projects.id\", ondelete=\"CASCADE\")\n    )\n    project: Mapped[\"Project\"] = relationship(\n        \"Project\",\n        back_populates=\"tasks\"\n    )\n",[3412,13271,13272,13284,13290,13308,13312,13317,13327,13334,13345,13355,13370,13374,13378,13391,13398,13416,13420,13425,13433,13449,13453,13462,13469,13477],{"__ignoreMap":3590},[3594,13273,13274,13276,13278,13280,13282],{"class":3596,"line":3597},[3594,13275,8575],{"class":4622},[3594,13277,10710],{"class":4793},[3594,13279,7763],{"class":4459},[3594,13281,8965],{"class":4793},[3594,13283,8585],{"class":4459},[3594,13285,13286,13288],{"class":3596,"line":3603},[3594,13287,8972],{"class":4459},[3594,13289,10723],{"class":4500},[3594,13291,13292,13294,13296,13298,13300,13302,13304,13306],{"class":3596,"line":3609},[3594,13293,8984],{"class":4690},[3594,13295,9175],{"class":4459},[3594,13297,6930],{"class":4793},[3594,13299,9180],{"class":4459},[3594,13301,8990],{"class":4493},[3594,13303,4497],{"class":4459},[3594,13305,4641],{"class":4622},[3594,13307,4558],{"class":4459},[3594,13309,13310],{"class":3596,"line":3615},[3594,13311,3631],{"emptyLinePlaceholder":3630},[3594,13313,13314],{"class":3596,"line":3621},[3594,13315,13316],{"class":4482},"    # Каскадне видалення дочірніх завдань оптимізовано через passive_deletes\n",[3594,13318,13319,13322,13325],{"class":3596,"line":3627},[3594,13320,13321],{"class":4459},"    tasks: Mapped[list[",[3594,13323,13324],{"class":4500},"\"Task\"",[3594,13326,11605],{"class":4459},[3594,13328,13329,13332],{"class":3596,"line":3634},[3594,13330,13331],{"class":4500},"        \"Task\"",[3594,13333,4504],{"class":4459},[3594,13335,13336,13338,13340,13343],{"class":3596,"line":3640},[3594,13337,11617],{"class":4493},[3594,13339,4497],{"class":4459},[3594,13341,13342],{"class":4500},"\"project\"",[3594,13344,4504],{"class":4459},[3594,13346,13347,13349,13351,13353],{"class":3596,"line":3646},[3594,13348,11629],{"class":4493},[3594,13350,4497],{"class":4459},[3594,13352,11248],{"class":4500},[3594,13354,4504],{"class":4459},[3594,13356,13357,13360,13362,13364,13367],{"class":3596,"line":3652},[3594,13358,13359],{"class":4493},"        passive_deletes",[3594,13361,4497],{"class":4459},[3594,13363,4641],{"class":4622},[3594,13365,13366],{"class":4459},",         ",[3594,13368,13369],{"class":4482},"# SQLAlchemy не буде робити SELECT перед DELETE\n",[3594,13371,13372],{"class":3596,"line":3658},[3594,13373,4935],{"class":4459},[3594,13375,13376],{"class":3596,"line":3664},[3594,13377,3631],{"emptyLinePlaceholder":3630},[3594,13379,13380,13382,13385,13387,13389],{"class":3596,"line":3670},[3594,13381,8575],{"class":4622},[3594,13383,13384],{"class":4793}," Task",[3594,13386,7763],{"class":4459},[3594,13388,8965],{"class":4793},[3594,13390,8585],{"class":4459},[3594,13392,13393,13395],{"class":3596,"line":3676},[3594,13394,8972],{"class":4459},[3594,13396,13397],{"class":4500},"\"tasks\"\n",[3594,13399,13400,13402,13404,13406,13408,13410,13412,13414],{"class":3596,"line":3681},[3594,13401,8984],{"class":4690},[3594,13403,9175],{"class":4459},[3594,13405,6930],{"class":4793},[3594,13407,9180],{"class":4459},[3594,13409,8990],{"class":4493},[3594,13411,4497],{"class":4459},[3594,13413,4641],{"class":4622},[3594,13415,4558],{"class":4459},[3594,13417,13418],{"class":3596,"line":3687},[3594,13419,3631],{"emptyLinePlaceholder":3630},[3594,13421,13422],{"class":3596,"line":3693},[3594,13423,13424],{"class":4482},"    # ON DELETE CASCADE на рівні бази даних для роботи passive_deletes\n",[3594,13426,13427,13429,13431],{"class":3596,"line":3699},[3594,13428,12855],{"class":4459},[3594,13430,6930],{"class":4793},[3594,13432,11023],{"class":4459},[3594,13434,13435,13437,13439,13441,13443,13445,13447],{"class":3596,"line":3705},[3594,13436,12864],{"class":4459},[3594,13438,12867],{"class":4500},[3594,13440,3488],{"class":4459},[3594,13442,11337],{"class":4493},[3594,13444,4497],{"class":4459},[3594,13446,11347],{"class":4500},[3594,13448,4558],{"class":4459},[3594,13450,13451],{"class":3596,"line":3711},[3594,13452,4935],{"class":4459},[3594,13454,13455,13457,13459],{"class":3596,"line":3716},[3594,13456,12949],{"class":4459},[3594,13458,12952],{"class":4500},[3594,13460,13461],{"class":4459},"] = relationship(\n",[3594,13463,13464,13467],{"class":3596,"line":3721},[3594,13465,13466],{"class":4500},"        \"Project\"",[3594,13468,4504],{"class":4459},[3594,13470,13471,13473,13475],{"class":3596,"line":3727},[3594,13472,11617],{"class":4493},[3594,13474,4497],{"class":4459},[3594,13476,13397],{"class":4500},[3594,13478,13479],{"class":3596,"line":3733},[3594,13480,4935],{"class":4459},[3441,13482],{},[3444,13484,13486],{"id":13485},"lazy-loading-vs-eager-loading-та-проблема-n1","Lazy Loading vs Eager Loading та Проблема N+1",[3385,13488,13489,13490,13493,13494,13496],{},"Одна з найбільш підступних проблем у роботі з ORM — це ",[3389,13491,13492],{},"N+1 query problem"," (проблема N+1 запитів). Вона виникає через поведінку ",[3389,13495,4163],{}," і є однією з найпоширеніших причин повільної роботи вебдодатків.",[3845,13498,13500],{"id":13499},"що-таке-lazy-loading-і-чому-він-небезпечний","Що таке Lazy Loading і чому він небезпечний?",[3385,13502,13503,13505,13506,13509],{},[3389,13504,4163],{}," (ліниве завантаження) — це стратегія, при якій пов'язані об'єкти ",[3389,13507,13508],{},"не завантажуються одразу"," разом із головним об'єктом. Натомість вони завантажуються «на вимогу» — лише тоді, коли ви вперше звертаєтеся до атрибута зв'язку.",[3385,13511,13512,13513,13516,13517,13520],{},"У EF Core Lazy Loading увімкнено ",[3389,13514,13515],{},"за замовчуванням"," і відбувається автоматично. У SQLAlchemy він також існує, але ",[3389,13518,13519],{},"вимкнений за замовчуванням"," (особливо у async-режимі, де він взагалі не підтримується без спеціального налаштування).",[3385,13522,13523,13524,13526,13527,13529,13530,11444],{},"Уявімо сценарій: отримати список 100 ",[3412,13525,9451],{},"-ів та для кожного — його ",[3412,13528,7736],{}," і кількість ",[3412,13531,11443],{},[3385,13533,13534],{},[3389,13535,13536],{},"З Lazy Loading (Погано ❌):",[3585,13538,13541],{"className":4445,"code":13539,"filename":13540,"language":4448,"meta":3590,"style":3590},"# 1 запит: SELECT * FROM users LIMIT 100\nusers = session.execute(select(User).limit(100)).scalars().all()\n\nfor user in users:\n    # +1 запит для КОЖНОГО користувача:\n    # SELECT * FROM posts WHERE author_id = \u003Cuser.id>\n    print(f\"{user.username}: {len(user.posts)} posts\")\n\n# Разом: 1 + 100 = 101 запит до бази даних!\n# При 1000 користувачів — 1001 запит.\n","ANTI-PATTERN: N+1 проблема",[3412,13542,13543,13548,13558,13562,13575,13580,13585,13618,13622,13627],{"__ignoreMap":3590},[3594,13544,13545],{"class":3596,"line":3597},[3594,13546,13547],{"class":4482},"# 1 запит: SELECT * FROM users LIMIT 100\n",[3594,13549,13550,13553,13555],{"class":3596,"line":3603},[3594,13551,13552],{"class":4459},"users = session.execute(select(User).limit(",[3594,13554,9042],{"class":4514},[3594,13556,13557],{"class":4459},")).scalars().all()\n",[3594,13559,13560],{"class":3596,"line":3609},[3594,13561,3631],{"emptyLinePlaceholder":3630},[3594,13563,13564,13567,13570,13572],{"class":3596,"line":3615},[3594,13565,13566],{"class":4455},"for",[3594,13568,13569],{"class":4459}," user ",[3594,13571,4682],{"class":4455},[3594,13573,13574],{"class":4459}," users:\n",[3594,13576,13577],{"class":3596,"line":3621},[3594,13578,13579],{"class":4482},"    # +1 запит для КОЖНОГО користувача:\n",[3594,13581,13582],{"class":3596,"line":3627},[3594,13583,13584],{"class":4482},"    # SELECT * FROM posts WHERE author_id = \u003Cuser.id>\n",[3594,13586,13587,13589,13591,13593,13595,13597,13599,13601,13603,13605,13608,13611,13613,13616],{"class":3596,"line":3634},[3594,13588,8171],{"class":4690},[3594,13590,7763],{"class":4459},[3594,13592,8617],{"class":4622},[3594,13594,4631],{"class":4500},[3594,13596,8623],{"class":4622},[3594,13598,3553],{"class":4459},[3594,13600,8634],{"class":4622},[3594,13602,9991],{"class":4500},[3594,13604,8623],{"class":4622},[3594,13606,13607],{"class":4690},"len",[3594,13609,13610],{"class":4459},"(user.posts)",[3594,13612,8634],{"class":4622},[3594,13614,13615],{"class":4500}," posts\"",[3594,13617,4558],{"class":4459},[3594,13619,13620],{"class":3596,"line":3640},[3594,13621,3631],{"emptyLinePlaceholder":3630},[3594,13623,13624],{"class":3596,"line":3646},[3594,13625,13626],{"class":4482},"# Разом: 1 + 100 = 101 запит до бази даних!\n",[3594,13628,13629],{"class":3596,"line":3652},[3594,13630,13631],{"class":4482},"# При 1000 користувачів — 1001 запит.\n",[3385,13633,13634,13635,13638],{},"Це класична ",[3389,13636,13637],{},"N+1 проблема",": спочатку виконується 1 запит для отримання головних об'єктів, а потім для кожного з N об'єктів виконується додатковий запит для отримання пов'язаних даних.",[3845,13640,13642,13643,3435,13646],{"id":13641},"вирішення-eager-loading-через-selectinload-та-joinedload","Вирішення: Eager Loading через ",[3412,13644,13645],{},"selectinload()",[3412,13647,13648],{},"joinedload()",[3385,13650,13651,13653,13654,13657],{},[3389,13652,4146],{}," (жадібне завантаження) вирішує цю проблему, завантажуючи всі необхідні дані ",[3389,13655,13656],{},"одним або двома SQL-запитами",". SQLAlchemy надає кілька стратегій:",[4433,13659,13661,13663],{"id":13660},"selectinload-рекомендований-підхід",[3412,13662,13645],{}," — рекомендований підхід",[3385,13665,13666,13667,13670,13671,13674],{},"Виконує ",[3389,13668,13669],{},"два SQL-запити",": перший — для головних об'єктів, другий — один ",[3412,13672,13673],{},"SELECT ... WHERE author_id IN (1, 2, 3, ...)"," для всіх пов'язаних об'єктів одночасно.",[3585,13676,13679],{"className":4445,"code":13677,"filename":13678,"language":4448,"meta":3590,"style":3590},"from sqlalchemy.orm import selectinload\n\n# Два запити замість N+1:\n# 1: SELECT * FROM users LIMIT 100\n# 2: SELECT * FROM posts WHERE author_id IN (1, 2, 3, ..., 100)\nusers = session.execute(\n    select(User)\n    .options(selectinload(User.posts))\n    .limit(100)\n).scalars().all()\n\nfor user in users:\n    print(f\"{user.username}: {len(user.posts)} posts\")\n    # user.posts вже завантажені — жодного додаткового запиту!\n","PATTERN: selectinload (рекомендовано)",[3412,13680,13681,13692,13696,13701,13706,13711,13716,13721,13726,13735,13740,13744,13754,13784],{"__ignoreMap":3590},[3594,13682,13683,13685,13687,13689],{"class":3596,"line":3597},[3594,13684,4465],{"class":4455},[3594,13686,6383],{"class":4459},[3594,13688,4456],{"class":4455},[3594,13690,13691],{"class":4459}," selectinload\n",[3594,13693,13694],{"class":3596,"line":3603},[3594,13695,3631],{"emptyLinePlaceholder":3630},[3594,13697,13698],{"class":3596,"line":3609},[3594,13699,13700],{"class":4482},"# Два запити замість N+1:\n",[3594,13702,13703],{"class":3596,"line":3615},[3594,13704,13705],{"class":4482},"# 1: SELECT * FROM users LIMIT 100\n",[3594,13707,13708],{"class":3596,"line":3621},[3594,13709,13710],{"class":4482},"# 2: SELECT * FROM posts WHERE author_id IN (1, 2, 3, ..., 100)\n",[3594,13712,13713],{"class":3596,"line":3627},[3594,13714,13715],{"class":4459},"users = session.execute(\n",[3594,13717,13718],{"class":3596,"line":3634},[3594,13719,13720],{"class":4459},"    select(User)\n",[3594,13722,13723],{"class":3596,"line":3640},[3594,13724,13725],{"class":4459},"    .options(selectinload(User.posts))\n",[3594,13727,13728,13731,13733],{"class":3596,"line":3646},[3594,13729,13730],{"class":4459},"    .limit(",[3594,13732,9042],{"class":4514},[3594,13734,4558],{"class":4459},[3594,13736,13737],{"class":3596,"line":3652},[3594,13738,13739],{"class":4459},").scalars().all()\n",[3594,13741,13742],{"class":3596,"line":3658},[3594,13743,3631],{"emptyLinePlaceholder":3630},[3594,13745,13746,13748,13750,13752],{"class":3596,"line":3664},[3594,13747,13566],{"class":4455},[3594,13749,13569],{"class":4459},[3594,13751,4682],{"class":4455},[3594,13753,13574],{"class":4459},[3594,13755,13756,13758,13760,13762,13764,13766,13768,13770,13772,13774,13776,13778,13780,13782],{"class":3596,"line":3670},[3594,13757,8171],{"class":4690},[3594,13759,7763],{"class":4459},[3594,13761,8617],{"class":4622},[3594,13763,4631],{"class":4500},[3594,13765,8623],{"class":4622},[3594,13767,3553],{"class":4459},[3594,13769,8634],{"class":4622},[3594,13771,9991],{"class":4500},[3594,13773,8623],{"class":4622},[3594,13775,13607],{"class":4690},[3594,13777,13610],{"class":4459},[3594,13779,8634],{"class":4622},[3594,13781,13615],{"class":4500},[3594,13783,4558],{"class":4459},[3594,13785,13786],{"class":3596,"line":3676},[3594,13787,13788],{"class":4482},"    # user.posts вже завантажені — жодного додаткового запиту!\n",[4433,13790,13792,13794],{"id":13791},"joinedload-для-одиничних-обєктів",[3412,13793,13648],{}," — для одиничних об'єктів",[3385,13796,13666,13797,13219,13800,13802],{},[3389,13798,13799],{},"один SQL-запит",[3412,13801,13210],{},". Підходить для завантаження одного пов'язаного об'єкта (many-to-one або one-to-one), але може бути неефективним для колекцій:",[3585,13804,13807],{"className":4445,"code":13805,"filename":13806,"language":4448,"meta":3590,"style":3590},"from sqlalchemy.orm import joinedload\n\n# Один запит з JOIN:\n# SELECT posts.*, users.* FROM posts\n# LEFT OUTER JOIN users ON users.id = posts.author_id\n# WHERE posts.id = 42\npost = session.execute(\n    select(Post)\n    .options(joinedload(Post.author))\n    .where(Post.id == 42)\n).scalar_one()\n\nprint(f\"Post by: {post.author.username}\")  # Жодного додаткового запиту\n","PATTERN: joinedload (для single-object relations)",[3412,13808,13809,13820,13824,13829,13834,13839,13844,13849,13854,13859,13869,13874,13878],{"__ignoreMap":3590},[3594,13810,13811,13813,13815,13817],{"class":3596,"line":3597},[3594,13812,4465],{"class":4455},[3594,13814,6383],{"class":4459},[3594,13816,4456],{"class":4455},[3594,13818,13819],{"class":4459}," joinedload\n",[3594,13821,13822],{"class":3596,"line":3603},[3594,13823,3631],{"emptyLinePlaceholder":3630},[3594,13825,13826],{"class":3596,"line":3609},[3594,13827,13828],{"class":4482},"# Один запит з JOIN:\n",[3594,13830,13831],{"class":3596,"line":3615},[3594,13832,13833],{"class":4482},"# SELECT posts.*, users.* FROM posts\n",[3594,13835,13836],{"class":3596,"line":3621},[3594,13837,13838],{"class":4482},"# LEFT OUTER JOIN users ON users.id = posts.author_id\n",[3594,13840,13841],{"class":3596,"line":3627},[3594,13842,13843],{"class":4482},"# WHERE posts.id = 42\n",[3594,13845,13846],{"class":3596,"line":3634},[3594,13847,13848],{"class":4459},"post = session.execute(\n",[3594,13850,13851],{"class":3596,"line":3640},[3594,13852,13853],{"class":4459},"    select(Post)\n",[3594,13855,13856],{"class":3596,"line":3646},[3594,13857,13858],{"class":4459},"    .options(joinedload(Post.author))\n",[3594,13860,13861,13864,13867],{"class":3596,"line":3652},[3594,13862,13863],{"class":4459},"    .where(Post.id == ",[3594,13865,13866],{"class":4514},"42",[3594,13868,4558],{"class":4459},[3594,13870,13871],{"class":3596,"line":3658},[3594,13872,13873],{"class":4459},").scalar_one()\n",[3594,13875,13876],{"class":3596,"line":3664},[3594,13877,3631],{"emptyLinePlaceholder":3630},[3594,13879,13880,13883,13885,13887,13890,13892,13895,13897,13899,13902],{"class":3596,"line":3670},[3594,13881,13882],{"class":4690},"print",[3594,13884,7763],{"class":4459},[3594,13886,8617],{"class":4622},[3594,13888,13889],{"class":4500},"\"Post by: ",[3594,13891,8623],{"class":4622},[3594,13893,13894],{"class":4459},"post.author.username",[3594,13896,8634],{"class":4622},[3594,13898,4631],{"class":4500},[3594,13900,13901],{"class":4459},")  ",[3594,13903,13904],{"class":4482},"# Жодного додаткового запиту\n",[3845,13906,13908,13909,13912,13913,13915],{"id":13907},"порівняння-include-у-ef-core-selectinload-у-sqlalchemy","Порівняння: ",[3412,13910,13911],{},".Include()"," у EF Core ↔ ",[3412,13914,13645],{}," у SQLAlchemy",[5448,13917,13918,14080],{},[3585,13919,13922],{"className":10766,"code":13920,"filename":13921,"language":10769,"meta":3590,"style":3590},"\u002F\u002F EF Core: .Include() для завантаження Navigation Property\nvar users = await context.Users\n    .Include(u => u.Posts)  \u002F\u002F Аналог selectinload(User.posts)\n    .Take(100)\n    .ToListAsync();\n\nforeach (var user in users)\n{\n    Console.WriteLine($\"{user.Username}: {user.Posts.Count} posts\");\n}\n","EF Core — .Include() (Eager Loading)",[3412,13923,13924,13929,13950,13977,13990,14000,14004,14023,14027,14076],{"__ignoreMap":3590},[3594,13925,13926],{"class":3596,"line":3597},[3594,13927,13928],{"class":4482},"\u002F\u002F EF Core: .Include() для завантаження Navigation Property\n",[3594,13930,13931,13934,13937,13940,13942,13945,13947],{"class":3596,"line":3603},[3594,13932,13933],{"class":4622},"var",[3594,13935,13936],{"class":4493}," users",[3594,13938,13939],{"class":4459}," = ",[3594,13941,4878],{"class":4622},[3594,13943,13944],{"class":4493}," context",[3594,13946,3439],{"class":4459},[3594,13948,13949],{"class":4493},"Users\n",[3594,13951,13952,13955,13958,13960,13963,13966,13968,13970,13972,13974],{"class":3596,"line":3609},[3594,13953,13954],{"class":4459},"    .",[3594,13956,13957],{"class":4690},"Include",[3594,13959,7763],{"class":4459},[3594,13961,13962],{"class":4493},"u",[3594,13964,13965],{"class":4459}," => ",[3594,13967,13962],{"class":4493},[3594,13969,3439],{"class":4459},[3594,13971,11920],{"class":4493},[3594,13973,13901],{"class":4459},[3594,13975,13976],{"class":4482},"\u002F\u002F Аналог selectinload(User.posts)\n",[3594,13978,13979,13981,13984,13986,13988],{"class":3596,"line":3615},[3594,13980,13954],{"class":4459},[3594,13982,13983],{"class":4690},"Take",[3594,13985,7763],{"class":4459},[3594,13987,9042],{"class":4514},[3594,13989,4558],{"class":4459},[3594,13991,13992,13994,13997],{"class":3596,"line":3621},[3594,13993,13954],{"class":4459},[3594,13995,13996],{"class":4690},"ToListAsync",[3594,13998,13999],{"class":4459},"();\n",[3594,14001,14002],{"class":3596,"line":3627},[3594,14003,3631],{"emptyLinePlaceholder":3630},[3594,14005,14006,14009,14011,14013,14016,14019,14021],{"class":3596,"line":3634},[3594,14007,14008],{"class":4455},"foreach",[3594,14010,13135],{"class":4459},[3594,14012,13933],{"class":4622},[3594,14014,14015],{"class":4493}," user",[3594,14017,14018],{"class":4455}," in",[3594,14020,13936],{"class":4493},[3594,14022,4558],{"class":4459},[3594,14024,14025],{"class":3596,"line":3640},[3594,14026,10790],{"class":4459},[3594,14028,14029,14032,14034,14037,14039,14042,14045,14047,14049,14052,14054,14056,14058,14060,14062,14064,14066,14069,14071,14073],{"class":3596,"line":3646},[3594,14030,14031],{"class":4493},"    Console",[3594,14033,3439],{"class":4459},[3594,14035,14036],{"class":4690},"WriteLine",[3594,14038,7763],{"class":4459},[3594,14040,14041],{"class":4500},"$\"",[3594,14043,8623],{"class":14044},"sD7JJ",[3594,14046,4915],{"class":4493},[3594,14048,3439],{"class":14044},[3594,14050,14051],{"class":4493},"Username",[3594,14053,8634],{"class":14044},[3594,14055,9991],{"class":4500},[3594,14057,8623],{"class":14044},[3594,14059,4915],{"class":4493},[3594,14061,3439],{"class":14044},[3594,14063,11920],{"class":4493},[3594,14065,3439],{"class":14044},[3594,14067,14068],{"class":4493},"Count",[3594,14070,8634],{"class":14044},[3594,14072,13615],{"class":4500},[3594,14074,14075],{"class":4459},");\n",[3594,14077,14078],{"class":3596,"line":3652},[3594,14079,3758],{"class":4459},[3585,14081,14084],{"className":4445,"code":14082,"filename":14083,"language":4448,"meta":3590,"style":3590},"# SQLAlchemy: selectinload() для завантаження relationship\nusers = (await session.execute(\n    select(User)\n    .options(selectinload(User.posts))\n    .limit(100)\n)).scalars().all()\n\nfor user in users:\n    print(f\"{user.username}: {len(user.posts)} posts\")\n","SQLAlchemy — selectinload() (Eager Loading)",[3412,14085,14086,14091,14101,14105,14109,14117,14121,14125,14135],{"__ignoreMap":3590},[3594,14087,14088],{"class":3596,"line":3597},[3594,14089,14090],{"class":4482},"# SQLAlchemy: selectinload() для завантаження relationship\n",[3594,14092,14093,14096,14098],{"class":3596,"line":3603},[3594,14094,14095],{"class":4459},"users = (",[3594,14097,4878],{"class":4455},[3594,14099,14100],{"class":4459}," session.execute(\n",[3594,14102,14103],{"class":3596,"line":3609},[3594,14104,13720],{"class":4459},[3594,14106,14107],{"class":3596,"line":3615},[3594,14108,13725],{"class":4459},[3594,14110,14111,14113,14115],{"class":3596,"line":3621},[3594,14112,13730],{"class":4459},[3594,14114,9042],{"class":4514},[3594,14116,4558],{"class":4459},[3594,14118,14119],{"class":3596,"line":3627},[3594,14120,13557],{"class":4459},[3594,14122,14123],{"class":3596,"line":3634},[3594,14124,3631],{"emptyLinePlaceholder":3630},[3594,14126,14127,14129,14131,14133],{"class":3596,"line":3640},[3594,14128,13566],{"class":4455},[3594,14130,13569],{"class":4459},[3594,14132,4682],{"class":4455},[3594,14134,13574],{"class":4459},[3594,14136,14137,14139,14141,14143,14145,14147,14149,14151,14153,14155,14157,14159,14161,14163],{"class":3596,"line":3646},[3594,14138,8171],{"class":4690},[3594,14140,7763],{"class":4459},[3594,14142,8617],{"class":4622},[3594,14144,4631],{"class":4500},[3594,14146,8623],{"class":4622},[3594,14148,3553],{"class":4459},[3594,14150,8634],{"class":4622},[3594,14152,9991],{"class":4500},[3594,14154,8623],{"class":4622},[3594,14156,13607],{"class":4690},[3594,14158,13610],{"class":4459},[3594,14160,8634],{"class":4622},[3594,14162,13615],{"class":4500},[3594,14164,4558],{"class":4459},[5216,14166,14167,14170,14171,14173,14174,14176,14177,14179],{},[3389,14168,14169],{},"Практичне правило",": завжди використовуйте ",[3412,14172,13645],{}," для колекцій (one-to-many, many-to-many) та ",[3412,14175,13648],{}," для одиничних зв'язків (many-to-one, one-to-one). Увімкніть ",[3412,14178,6132],{}," на Engine під час розробки, щоб бачити реальні SQL-запити та вчасно помічати N+1 проблеми.",[5540,14181,14182,14183,13135,14186,14188,14189,14192,14193,14195,14196,3461,14198,14200],{},"У ",[3389,14184,14185],{},"асинхронному режимі",[3412,14187,3426],{},") Lazy Loading ",[3389,14190,14191],{},"повністю не підтримується"," і призведе до ",[3412,14194,13230],{},"-помилки. Це є корисним обмеженням: async-контекст змушує розробника явно вказувати стратегію завантаження через ",[3412,14197,13645],{},[3412,14199,13648],{},", роблячи поведінку передбачуваною.",[3441,14202],{},[4433,14204,14206],{"id":14205},"просунуті-стратегії-завантаження-звязків","Просунуті стратегії завантаження зв'язків",[3385,14208,14209,14210,3435,14212,14214],{},"Окрім базових ",[3412,14211,13645],{},[3412,14213,13648],{},", SQLAlchemy надає інструменти для вирішення більш складних архітектурних завдань при завантаженні даних:",[3510,14216,14217,14320,14372],{},[3455,14218,14219,14222,14223,14226,14227,14230,14231,14234,14235,14237,14238,3461,14240,14242,14243],{},[3389,14220,14221],{},"Вкладене завантаження (Nested Eager Loading)","\nЧасто потрібно завантажити дерево зв'язків (наприклад, завантажити ",[3412,14224,14225],{},"Project",", для нього — всі ",[3412,14228,14229],{},"Task",", а для кожного завдання — список ",[3412,14232,14233],{},"Comment"," та автора коментаря ",[3412,14236,9451],{},"). Для цього використовується ланцюжок методів ",[3412,14239,13645],{},[3412,14241,13648],{}," через крапку:",[3585,14244,14247],{"className":4445,"code":14245,"filename":14246,"language":4448,"meta":3590,"style":3590},"from sqlalchemy.orm import selectinload\n\nstmt = (\n    select(Project)\n    .options(\n        selectinload(Project.tasks)           # Крок 1: Завантажити завдання для проекту\n        .selectinload(Task.comments)          # Крок 2: Завантажити коментарі для кожного завдання\n        .joinedload(Comment.author)           # Крок 3: Жадібно підтягнути автора коментаря (many-to-one)\n    )\n)\nprojects = (await session.execute(stmt)).scalars().all()\n","nested_loading.py",[3412,14248,14249,14259,14263,14268,14273,14278,14286,14294,14302,14306,14310],{"__ignoreMap":3590},[3594,14250,14251,14253,14255,14257],{"class":3596,"line":3597},[3594,14252,4465],{"class":4455},[3594,14254,6383],{"class":4459},[3594,14256,4456],{"class":4455},[3594,14258,13691],{"class":4459},[3594,14260,14261],{"class":3596,"line":3603},[3594,14262,3631],{"emptyLinePlaceholder":3630},[3594,14264,14265],{"class":3596,"line":3609},[3594,14266,14267],{"class":4459},"stmt = (\n",[3594,14269,14270],{"class":3596,"line":3615},[3594,14271,14272],{"class":4459},"    select(Project)\n",[3594,14274,14275],{"class":3596,"line":3621},[3594,14276,14277],{"class":4459},"    .options(\n",[3594,14279,14280,14283],{"class":3596,"line":3627},[3594,14281,14282],{"class":4459},"        selectinload(Project.tasks)           ",[3594,14284,14285],{"class":4482},"# Крок 1: Завантажити завдання для проекту\n",[3594,14287,14288,14291],{"class":3596,"line":3634},[3594,14289,14290],{"class":4459},"        .selectinload(Task.comments)          ",[3594,14292,14293],{"class":4482},"# Крок 2: Завантажити коментарі для кожного завдання\n",[3594,14295,14296,14299],{"class":3596,"line":3640},[3594,14297,14298],{"class":4459},"        .joinedload(Comment.author)           ",[3594,14300,14301],{"class":4482},"# Крок 3: Жадібно підтягнути автора коментаря (many-to-one)\n",[3594,14303,14304],{"class":3596,"line":3646},[3594,14305,4935],{"class":4459},[3594,14307,14308],{"class":3596,"line":3652},[3594,14309,4558],{"class":4459},[3594,14311,14312,14315,14317],{"class":3596,"line":3658},[3594,14313,14314],{"class":4459},"projects = (",[3594,14316,4878],{"class":4455},[3594,14318,14319],{"class":4459}," session.execute(stmt)).scalars().all()\n",[3455,14321,14322,14329,14330,3439,14332],{},[3389,14323,14324,13238,14326],{},[3412,14325,13645],{},[3412,14327,14328],{},"subqueryload()","\nУ старих версіях SQLAlchemy часто використовували ",[3412,14331,14328],{},[3510,14333,14334,14353,14362],{},[3455,14335,14336,14338,14339,14341,14342,14344,14345,14348,14349,14352],{},[3412,14337,14328],{}," повторює вихідний ",[3412,14340,7918],{},"-запит у підзапиті (subquery) для отримання дочірніх об'єктів. Це може бути вкрай неефективно, якщо вихідний запит мав складні ",[3412,14343,10433],{}," або великі значення ",[3412,14346,14347],{},"LIMIT","\u002F",[3412,14350,14351],{},"OFFSET"," (базі даних доводиться виконувати всю логіку вихідного запиту двічі).",[3455,14354,14355,14357,14358,14361],{},[3412,14356,13645],{}," замість цього бере первинні ключі (ID) вже завантажених батьківських об'єктів і робить простий, швидкий запит ",[3412,14359,14360],{},"SELECT ... WHERE parent_id IN (1, 2, 3...)",". Це набагато легше для планувальника запитів PostgreSQL.",[3455,14363,14364,14366,14367,14369,14370,3439],{},[6344,14365,7060],{}," У 2.0 повністю відмовтеся від ",[3412,14368,14328],{}," на користь ",[3412,14371,13645],{},[3455,14373,14374,14380,14381,14384,14385,14388,14389,14392,14393,14396,14397,14401,14402],{},[3389,14375,14376,14379],{},[3412,14377,14378],{},"contains_eager()",": Завантаження зв'язків при ручному JOIN","\nУявіть ситуацію: вам потрібно відфільтрувати список проектів за статусом їхніх завдань (наприклад, знайти проекти, які мають хоча б одне критичне завдання), і водночас ви хочете, щоб ці завдання були одразу завантажені у властивість ",[3412,14382,14383],{},"project.tasks",".\nЯкщо ви напишете ",[3412,14386,14387],{},".join(Project.tasks).options(selectinload(Project.tasks))",", SQLAlchemy зробить ",[3389,14390,14391],{},"два окремих JOIN",": один для фільтрації, а другий (в окремому запиті) — для завантаження колекції. Це подвійна робота.",[14394,14395],"br",{},"Правильне рішення — ",[3389,14398,14399],{},[3412,14400,14378],{},". Воно вказує SQLAlchemy: «Я вже зробив JOIN вручну для фільтрації, візьми ці дані з результату вихідного запиту та поклади у властивість зв'язку»:",[3585,14403,14406],{"className":4445,"code":14404,"filename":14405,"language":4448,"meta":3590,"style":3590},"from sqlalchemy.orm import contains_eager\n\nstmt = (\n    select(Project)\n    .join(Project.tasks)                                   # Ручний JOIN для фільтрації\n    .where(Task.priority == TaskPriority.CRITICAL)         # Умова фільтрації\n    .options(contains_eager(Project.tasks))                # Завантажити ці ж дані в об'єкт\n)\n# Виконається лише ОДИН SQL-запит із INNER JOIN,\n# і об'єкти project.tasks будуть повністю заповнені!\nprojects = (await session.execute(stmt)).scalars().unique().all()\n","contains_eager_demo.py",[3412,14407,14408,14419,14423,14427,14431,14439,14447,14455,14459,14464,14469],{"__ignoreMap":3590},[3594,14409,14410,14412,14414,14416],{"class":3596,"line":3597},[3594,14411,4465],{"class":4455},[3594,14413,6383],{"class":4459},[3594,14415,4456],{"class":4455},[3594,14417,14418],{"class":4459}," contains_eager\n",[3594,14420,14421],{"class":3596,"line":3603},[3594,14422,3631],{"emptyLinePlaceholder":3630},[3594,14424,14425],{"class":3596,"line":3609},[3594,14426,14267],{"class":4459},[3594,14428,14429],{"class":3596,"line":3615},[3594,14430,14272],{"class":4459},[3594,14432,14433,14436],{"class":3596,"line":3621},[3594,14434,14435],{"class":4459},"    .join(Project.tasks)                                   ",[3594,14437,14438],{"class":4482},"# Ручний JOIN для фільтрації\n",[3594,14440,14441,14444],{"class":3596,"line":3627},[3594,14442,14443],{"class":4459},"    .where(Task.priority == TaskPriority.CRITICAL)         ",[3594,14445,14446],{"class":4482},"# Умова фільтрації\n",[3594,14448,14449,14452],{"class":3596,"line":3634},[3594,14450,14451],{"class":4459},"    .options(contains_eager(Project.tasks))                ",[3594,14453,14454],{"class":4482},"# Завантажити ці ж дані в об'єкт\n",[3594,14456,14457],{"class":3596,"line":3640},[3594,14458,4558],{"class":4459},[3594,14460,14461],{"class":3596,"line":3646},[3594,14462,14463],{"class":4482},"# Виконається лише ОДИН SQL-запит із INNER JOIN,\n",[3594,14465,14466],{"class":3596,"line":3652},[3594,14467,14468],{"class":4482},"# і об'єкти project.tasks будуть повністю заповнені!\n",[3594,14470,14471,14473,14475],{"class":3596,"line":3658},[3594,14472,14314],{"class":4459},[3594,14474,4878],{"class":4455},[3594,14476,14477],{"class":4459}," session.execute(stmt)).scalars().unique().all()\n",[14479,14480,14481,14482,14484,14485,14488,14489,14494,14495,14498,14499,14501],"important",{},"При використанні ",[3412,14483,14378],{}," разом із ",[3412,14486,14487],{},"join()"," на зв'язки типу \"один-до-багатьох\" (one-to-many) обов'язково викликайте метод ",[3389,14490,14491],{},[3412,14492,14493],{},".unique()"," на результаті виконання запиту перед ",[3412,14496,14497],{},".all()",". Оскільки JOIN створює дублікати рядків для батьківської таблиці, ",[3412,14500,14493],{}," гарантує, що SQLAlchemy згорне дублікати в унікальні об'єкти в пам'яті Python.",[3441,14503],{},[3444,14505,14507],{"id":14506},"запити-у-sqlalchemy-20-core-dml-api","Запити у SQLAlchemy 2.0: Core DML API",[3385,14509,14510,14511,14514,14515,3439],{},"У SQLAlchemy 1.x робота з запитами на рівні Core (конструктор SQL) та на рівні ORM (робота з об'єктами) була розділена: Core використовував ",[3412,14512,14513],{},"select(users_table)",", а ORM — ",[3412,14516,14517],{},"session.query(User)",[3385,14519,14520,14521,14524,14525,14528,14529,3488,14531,3488,14533,3435,14535,14537],{},"Починаючи з ",[3389,14522,14523],{},"SQLAlchemy 2.0",", цей бар'єр повністю стерто. Тепер для будь-яких запитів використовується єдиний уніфікований ",[3389,14526,14527],{},"Core DML (Data Manipulation Language) API",". Ми конструюємо запит як об'єкт абстрактного синтаксичного дерева (AST) за допомогою функцій ",[3412,14530,3892],{},[3412,14532,3895],{},[3412,14534,3898],{},[3412,14536,3901],{},", а потім виконуємо його через сесію.",[3845,14539,14541],{"id":14540},"компіляція-та-інспектування-sql-запитів","Компіляція та інспектування SQL-запитів",[3385,14543,14544,14545,14548,14549,14552],{},"Коли ви викликаєте ",[3412,14546,14547],{},"select(User)",", ви не робите запит до бази даних і навіть не створюєте SQL-рядок. Ви створюєте Python-об'єкт типу ",[3412,14550,14551],{},"Select",". SQLAlchemy компілює цей об'єкт у SQL безпосередньо перед виконанням.",[3385,14554,14555],{},"Ви можете переглянути згенерований SQL у будь-який момент:",[3585,14557,14559],{"className":4445,"code":14558,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import select\nfrom sqlalchemy.dialects import postgresql\n\nstmt = select(User).where(User.username == \"arakviel\")\n\n# 1. Швидкий перегляд загального SQL (буде використано базовий діалект)\nprint(stmt)\n# Виведе: SELECT users.id, users.username, users.email, users.is_active, users.created_at FROM users WHERE users.username = :username_1\n\n# 2. Перегляд SQL для конкретної СУБД (наприклад, PostgreSQL)\ncompiled = stmt.compile(dialect=postgresql.dialect())\nprint(compiled)\n# Виведе те саме, але орієнтоване під синтаксис PG\n\n# 3. Перегляд параметрів, які будуть передані з запитом\nprint(compiled.params)\n# Виведе: {'username_1': 'arakviel'}\n",[3412,14560,14561,14572,14584,14588,14598,14602,14607,14614,14619,14623,14628,14639,14646,14651,14655,14660,14667],{"__ignoreMap":3590},[3594,14562,14563,14565,14567,14569],{"class":3596,"line":3597},[3594,14564,4465],{"class":4455},[3594,14566,5996],{"class":4459},[3594,14568,4456],{"class":4455},[3594,14570,14571],{"class":4459}," select\n",[3594,14573,14574,14576,14579,14581],{"class":3596,"line":3603},[3594,14575,4465],{"class":4455},[3594,14577,14578],{"class":4459}," sqlalchemy.dialects ",[3594,14580,4456],{"class":4455},[3594,14582,14583],{"class":4459}," postgresql\n",[3594,14585,14586],{"class":3596,"line":3609},[3594,14587,3631],{"emptyLinePlaceholder":3630},[3594,14589,14590,14593,14596],{"class":3596,"line":3615},[3594,14591,14592],{"class":4459},"stmt = select(User).where(User.username == ",[3594,14594,14595],{"class":4500},"\"arakviel\"",[3594,14597,4558],{"class":4459},[3594,14599,14600],{"class":3596,"line":3621},[3594,14601,3631],{"emptyLinePlaceholder":3630},[3594,14603,14604],{"class":3596,"line":3627},[3594,14605,14606],{"class":4482},"# 1. Швидкий перегляд загального SQL (буде використано базовий діалект)\n",[3594,14608,14609,14611],{"class":3596,"line":3634},[3594,14610,13882],{"class":4690},[3594,14612,14613],{"class":4459},"(stmt)\n",[3594,14615,14616],{"class":3596,"line":3640},[3594,14617,14618],{"class":4482},"# Виведе: SELECT users.id, users.username, users.email, users.is_active, users.created_at FROM users WHERE users.username = :username_1\n",[3594,14620,14621],{"class":3596,"line":3646},[3594,14622,3631],{"emptyLinePlaceholder":3630},[3594,14624,14625],{"class":3596,"line":3652},[3594,14626,14627],{"class":4482},"# 2. Перегляд SQL для конкретної СУБД (наприклад, PostgreSQL)\n",[3594,14629,14630,14633,14636],{"class":3596,"line":3658},[3594,14631,14632],{"class":4459},"compiled = stmt.compile(",[3594,14634,14635],{"class":4493},"dialect",[3594,14637,14638],{"class":4459},"=postgresql.dialect())\n",[3594,14640,14641,14643],{"class":3596,"line":3664},[3594,14642,13882],{"class":4690},[3594,14644,14645],{"class":4459},"(compiled)\n",[3594,14647,14648],{"class":3596,"line":3670},[3594,14649,14650],{"class":4482},"# Виведе те саме, але орієнтоване під синтаксис PG\n",[3594,14652,14653],{"class":3596,"line":3676},[3594,14654,3631],{"emptyLinePlaceholder":3630},[3594,14656,14657],{"class":3596,"line":3681},[3594,14658,14659],{"class":4482},"# 3. Перегляд параметрів, які будуть передані з запитом\n",[3594,14661,14662,14664],{"class":3596,"line":3687},[3594,14663,13882],{"class":4690},[3594,14665,14666],{"class":4459},"(compiled.params)\n",[3594,14668,14669],{"class":3596,"line":3693},[3594,14670,14671],{"class":4482},"# Виведе: {'username_1': 'arakviel'}\n",[3385,14673,14674,14675,14678],{},"Завдяки параметризації (використанню плейсхолдерів типу ",[3412,14676,14677],{},":username_1","), SQLAlchemy повністю захищає ваш додаток від SQL-ін'єкцій «з коробки».",[3441,14680],{},[3845,14682,14684,14685],{"id":14683},"читання-даних-select","Читання даних: ",[3412,14686,3892],{},[3385,14688,5980,14689,14691,14692,3465],{},[3412,14690,3892],{}," будує SQL-запити вибірки (",[3412,14693,7918],{},[4433,14695,14697],{"id":14696},"сигнатура","Сигнатура",[3585,14699,14701],{"className":4445,"code":14700,"language":4448,"meta":3590,"style":3590},"def select(*entities: _ColumnsClauseArgument[Any]) -> Select\n",[3412,14702,14703],{"__ignoreMap":3590},[3594,14704,14705,14708,14711,14714,14717],{"class":3596,"line":3597},[3594,14706,14707],{"class":4622},"def",[3594,14709,14710],{"class":4690}," select",[3594,14712,14713],{"class":4459},"(*",[3594,14715,14716],{"class":4493},"entities",[3594,14718,14719],{"class":4459},": _ColumnsClauseArgument[Any]) -> Select\n",[3385,14721,14722,14723,14725,14726,14728,14729,14732,14733,3465],{},"Аргументами ",[3412,14724,14716],{}," можуть бути класи ORM-моделей (наприклад, ",[3412,14727,9451],{},"), окремі стовпці (",[3412,14730,14731],{},"User.username","), або SQL-функції (",[3412,14734,14735],{},"func.count(User.id)",[4433,14737,14739,14740,14742],{"id":14738},"ключові-методи-обєкта-select-chaining-api","Ключові методи об'єкта ",[3412,14741,14551],{}," (Chaining API)",[3385,14744,14745,14746,14748],{},"Кожен метод повертає новий об'єкт ",[3412,14747,14551],{},", що дозволяє будувати ланцюжки викликів:",[9975,14750,14751,14883,14959,15033,15098,15170,15264,15332,15410],{},[9978,14752,14754,14772],{"name":14753,"type":14551},"where()",[3385,14755,14756,14757,14760,14761,14764,14765,14768,14769,3439],{},"Додає умову ",[3412,14758,14759],{},"WHERE"," до запиту. Приймає позиційні аргументи умов (",[3412,14762,14763],{},"*whereclauses","). Кілька аргументів або послідовні виклики ",[3412,14766,14767],{},".where()"," об'єднуються через логічне ",[3412,14770,14771],{},"AND",[3585,14773,14775],{"className":4445,"code":14774,"language":4448,"meta":3590,"style":3590},"# Базовий: фільтрація за значенням (рівність або порівняння)\nselect(User).where(User.is_active == True)\n# SQL: SELECT ... FROM users WHERE users.is_active = true\n\nselect(User).where(User.id > 10)\n# SQL: SELECT ... FROM users WHERE users.id > 10\n\n# Просунутий: комбінація AND, фільтрація NULL, пошук IN та LIKE\nselect(User).where(\n    User.is_active == True,\n    User.email.is_not(None),\n    User.id.in_([1, 5, 10]),\n    User.username.like(\"ara%\")\n)\n# SQL: SELECT ... FROM users WHERE users.is_active = true AND users.email IS NOT NULL AND users.id IN (1, 5, 10) AND users.username LIKE 'ara%'\n",[3412,14776,14777,14782,14791,14796,14800,14809,14814,14818,14823,14828,14837,14846,14864,14874,14878],{"__ignoreMap":3590},[3594,14778,14779],{"class":3596,"line":3597},[3594,14780,14781],{"class":4482},"# Базовий: фільтрація за значенням (рівність або порівняння)\n",[3594,14783,14784,14787,14789],{"class":3596,"line":3603},[3594,14785,14786],{"class":4459},"select(User).where(User.is_active == ",[3594,14788,4641],{"class":4622},[3594,14790,4558],{"class":4459},[3594,14792,14793],{"class":3596,"line":3609},[3594,14794,14795],{"class":4482},"# SQL: SELECT ... FROM users WHERE users.is_active = true\n",[3594,14797,14798],{"class":3596,"line":3615},[3594,14799,3631],{"emptyLinePlaceholder":3630},[3594,14801,14802,14805,14807],{"class":3596,"line":3621},[3594,14803,14804],{"class":4459},"select(User).where(User.id > ",[3594,14806,4646],{"class":4514},[3594,14808,4558],{"class":4459},[3594,14810,14811],{"class":3596,"line":3627},[3594,14812,14813],{"class":4482},"# SQL: SELECT ... FROM users WHERE users.id > 10\n",[3594,14815,14816],{"class":3596,"line":3634},[3594,14817,3631],{"emptyLinePlaceholder":3630},[3594,14819,14820],{"class":3596,"line":3640},[3594,14821,14822],{"class":4482},"# Просунутий: комбінація AND, фільтрація NULL, пошук IN та LIKE\n",[3594,14824,14825],{"class":3596,"line":3646},[3594,14826,14827],{"class":4459},"select(User).where(\n",[3594,14829,14830,14833,14835],{"class":3596,"line":3652},[3594,14831,14832],{"class":4459},"    User.is_active == ",[3594,14834,4641],{"class":4622},[3594,14836,4504],{"class":4459},[3594,14838,14839,14842,14844],{"class":3596,"line":3658},[3594,14840,14841],{"class":4459},"    User.email.is_not(",[3594,14843,9644],{"class":4622},[3594,14845,4753],{"class":4459},[3594,14847,14848,14851,14853,14855,14857,14859,14861],{"class":3596,"line":3664},[3594,14849,14850],{"class":4459},"    User.id.in_([",[3594,14852,6486],{"class":4514},[3594,14854,3488],{"class":4459},[3594,14856,6934],{"class":4514},[3594,14858,3488],{"class":4459},[3594,14860,4646],{"class":4514},[3594,14862,14863],{"class":4459},"]),\n",[3594,14865,14866,14869,14872],{"class":3596,"line":3670},[3594,14867,14868],{"class":4459},"    User.username.like(",[3594,14870,14871],{"class":4500},"\"ara%\"",[3594,14873,4558],{"class":4459},[3594,14875,14876],{"class":3596,"line":3676},[3594,14877,4558],{"class":4459},[3594,14879,14880],{"class":3596,"line":3681},[3594,14881,14882],{"class":4482},"# SQL: SELECT ... FROM users WHERE users.is_active = true AND users.email IS NOT NULL AND users.id IN (1, 5, 10) AND users.username LIKE 'ara%'\n",[9978,14884,14886,14898],{"name":14885,"type":14551},"filter_by()",[3385,14887,14888,14889,14891,14892,14894,14895,3439],{},"Спрощений варіант фільтрації ",[3412,14890,14759],{}," для порівняння на рівність за назвами полів за допомогою іменованих аргументів (",[3412,14893,12357],{},"). Наприклад: ",[3412,14896,14897],{},".filter_by(is_active=True)",[3585,14899,14901],{"className":4445,"code":14900,"language":4448,"meta":3590,"style":3590},"# Базовий: фільтрація за одним полем\nselect(User).filter_by(is_active=True)\n# SQL: SELECT ... FROM users WHERE users.is_active = true\n\n# Просунутий: фільтрація за кількома полями одночасно (діє як AND)\nselect(User).filter_by(is_active=True, username=\"arakviel\")\n# SQL: SELECT ... FROM users WHERE users.is_active = true AND users.username = 'arakviel'\n",[3412,14902,14903,14908,14921,14925,14929,14934,14954],{"__ignoreMap":3590},[3594,14904,14905],{"class":3596,"line":3597},[3594,14906,14907],{"class":4482},"# Базовий: фільтрація за одним полем\n",[3594,14909,14910,14913,14915,14917,14919],{"class":3596,"line":3603},[3594,14911,14912],{"class":4459},"select(User).filter_by(",[3594,14914,10165],{"class":4493},[3594,14916,4497],{"class":4459},[3594,14918,4641],{"class":4622},[3594,14920,4558],{"class":4459},[3594,14922,14923],{"class":3596,"line":3609},[3594,14924,14795],{"class":4482},[3594,14926,14927],{"class":3596,"line":3615},[3594,14928,3631],{"emptyLinePlaceholder":3630},[3594,14930,14931],{"class":3596,"line":3621},[3594,14932,14933],{"class":4482},"# Просунутий: фільтрація за кількома полями одночасно (діє як AND)\n",[3594,14935,14936,14938,14940,14942,14944,14946,14948,14950,14952],{"class":3596,"line":3627},[3594,14937,14912],{"class":4459},[3594,14939,10165],{"class":4493},[3594,14941,4497],{"class":4459},[3594,14943,4641],{"class":4622},[3594,14945,3488],{"class":4459},[3594,14947,7736],{"class":4493},[3594,14949,4497],{"class":4459},[3594,14951,14595],{"class":4500},[3594,14953,4558],{"class":4459},[3594,14955,14956],{"class":3596,"line":3634},[3594,14957,14958],{"class":4482},"# SQL: SELECT ... FROM users WHERE users.is_active = true AND users.username = 'arakviel'\n",[9978,14960,14962,14980],{"name":14961,"type":14551},"order_by()",[3385,14963,14964,14965,14968,14969,14972,14973,14976,14977,3439],{},"Сортування результатів запиту (",[3412,14966,14967],{},"ORDER BY","). Приймає стовпці для сортування (",[3412,14970,14971],{},"*clauses","). Для зворотного сортування викликається метод ",[3412,14974,14975],{},".desc()"," на стовпці, наприклад ",[3412,14978,14979],{},"User.created_at.desc()",[3585,14981,14983],{"className":4445,"code":14982,"language":4448,"meta":3590,"style":3590},"# Базовий: сортування за зростанням (за замовчуванням)\nselect(User).order_by(User.username)\n# SQL: SELECT ... FROM users ORDER BY users.username ASC\n\n# Просунутий: сортування за кількома полями, спадання (desc) та NULL в кінці\nselect(User).order_by(\n    User.is_active.desc(),\n    User.email.asc().nulls_last()\n)\n# SQL: SELECT ... FROM users ORDER BY users.is_active DESC, users.email ASC NULLS LAST\n",[3412,14984,14985,14990,14995,15000,15004,15009,15014,15019,15024,15028],{"__ignoreMap":3590},[3594,14986,14987],{"class":3596,"line":3597},[3594,14988,14989],{"class":4482},"# Базовий: сортування за зростанням (за замовчуванням)\n",[3594,14991,14992],{"class":3596,"line":3603},[3594,14993,14994],{"class":4459},"select(User).order_by(User.username)\n",[3594,14996,14997],{"class":3596,"line":3609},[3594,14998,14999],{"class":4482},"# SQL: SELECT ... FROM users ORDER BY users.username ASC\n",[3594,15001,15002],{"class":3596,"line":3615},[3594,15003,3631],{"emptyLinePlaceholder":3630},[3594,15005,15006],{"class":3596,"line":3621},[3594,15007,15008],{"class":4482},"# Просунутий: сортування за кількома полями, спадання (desc) та NULL в кінці\n",[3594,15010,15011],{"class":3596,"line":3627},[3594,15012,15013],{"class":4459},"select(User).order_by(\n",[3594,15015,15016],{"class":3596,"line":3634},[3594,15017,15018],{"class":4459},"    User.is_active.desc(),\n",[3594,15020,15021],{"class":3596,"line":3640},[3594,15022,15023],{"class":4459},"    User.email.asc().nulls_last()\n",[3594,15025,15026],{"class":3596,"line":3646},[3594,15027,4558],{"class":4459},[3594,15029,15030],{"class":3596,"line":3652},[3594,15031,15032],{"class":4482},"# SQL: SELECT ... FROM users ORDER BY users.is_active DESC, users.email ASC NULLS LAST\n",[9978,15034,15036,15045],{"name":15035,"type":14551},"limit()",[3385,15037,15038,15039,15041,15042,3439],{},"Обмежує кількість рядків у результаті запиту (",[3412,15040,14347],{},"). Приймає ціле число ",[3412,15043,15044],{},"limit",[3585,15046,15048],{"className":4445,"code":15047,"language":4448,"meta":3590,"style":3590},"# Базовий: обмеження кількості результатів (перші 10 записів)\nselect(User).limit(10)\n# SQL: SELECT ... FROM users LIMIT 10\n\n# Просунутий: ліміт у підзапиті для фільтрації пов'язаних таблиць\nsubq = select(User.id).order_by(User.created_at.desc()).limit(5).subquery()\nselect(Post).where(Post.author_id.in_(select(subq)))\n# SQL: SELECT ... FROM posts WHERE posts.author_id IN (SELECT users.id FROM users ORDER BY users.created_at DESC LIMIT 5)\n",[3412,15049,15050,15055,15064,15069,15073,15078,15088,15093],{"__ignoreMap":3590},[3594,15051,15052],{"class":3596,"line":3597},[3594,15053,15054],{"class":4482},"# Базовий: обмеження кількості результатів (перші 10 записів)\n",[3594,15056,15057,15060,15062],{"class":3596,"line":3603},[3594,15058,15059],{"class":4459},"select(User).limit(",[3594,15061,4646],{"class":4514},[3594,15063,4558],{"class":4459},[3594,15065,15066],{"class":3596,"line":3609},[3594,15067,15068],{"class":4482},"# SQL: SELECT ... FROM users LIMIT 10\n",[3594,15070,15071],{"class":3596,"line":3615},[3594,15072,3631],{"emptyLinePlaceholder":3630},[3594,15074,15075],{"class":3596,"line":3621},[3594,15076,15077],{"class":4482},"# Просунутий: ліміт у підзапиті для фільтрації пов'язаних таблиць\n",[3594,15079,15080,15083,15085],{"class":3596,"line":3627},[3594,15081,15082],{"class":4459},"subq = select(User.id).order_by(User.created_at.desc()).limit(",[3594,15084,6934],{"class":4514},[3594,15086,15087],{"class":4459},").subquery()\n",[3594,15089,15090],{"class":3596,"line":3634},[3594,15091,15092],{"class":4459},"select(Post).where(Post.author_id.in_(select(subq)))\n",[3594,15094,15095],{"class":3596,"line":3640},[3594,15096,15097],{"class":4482},"# SQL: SELECT ... FROM posts WHERE posts.author_id IN (SELECT users.id FROM users ORDER BY users.created_at DESC LIMIT 5)\n",[9978,15099,15101,15109],{"name":15100,"type":14551},"offset()",[3385,15102,15103,15104,15041,15106,3439],{},"Пропускає вказану кількість перших рядків у результаті запиту (",[3412,15105,14351],{},[3412,15107,15108],{},"offset",[3585,15110,15112],{"className":4445,"code":15111,"language":4448,"meta":3590,"style":3590},"# Базовий: пропуск перших 10 записів\nselect(User).offset(10)\n# SQL: SELECT ... FROM users OFFSET 10\n\n# Просунутий: використання разом з .limit() для класичної пагінації\npage, limit = 3, 20\nselect(User).limit(limit).offset((page - 1) * limit)\n# SQL: SELECT ... FROM users LIMIT 20 OFFSET 40\n",[3412,15113,15114,15119,15128,15133,15137,15142,15155,15165],{"__ignoreMap":3590},[3594,15115,15116],{"class":3596,"line":3597},[3594,15117,15118],{"class":4482},"# Базовий: пропуск перших 10 записів\n",[3594,15120,15121,15124,15126],{"class":3596,"line":3603},[3594,15122,15123],{"class":4459},"select(User).offset(",[3594,15125,4646],{"class":4514},[3594,15127,4558],{"class":4459},[3594,15129,15130],{"class":3596,"line":3609},[3594,15131,15132],{"class":4482},"# SQL: SELECT ... FROM users OFFSET 10\n",[3594,15134,15135],{"class":3596,"line":3615},[3594,15136,3631],{"emptyLinePlaceholder":3630},[3594,15138,15139],{"class":3596,"line":3621},[3594,15140,15141],{"class":4482},"# Просунутий: використання разом з .limit() для класичної пагінації\n",[3594,15143,15144,15147,15150,15152],{"class":3596,"line":3627},[3594,15145,15146],{"class":4459},"page, limit = ",[3594,15148,15149],{"class":4514},"3",[3594,15151,3488],{"class":4459},[3594,15153,15154],{"class":4514},"20\n",[3594,15156,15157,15160,15162],{"class":3596,"line":3634},[3594,15158,15159],{"class":4459},"select(User).limit(limit).offset((page - ",[3594,15161,6486],{"class":4514},[3594,15163,15164],{"class":4459},") * limit)\n",[3594,15166,15167],{"class":3596,"line":3640},[3594,15168,15169],{"class":4482},"# SQL: SELECT ... FROM users LIMIT 20 OFFSET 40\n",[9978,15171,15173,15182],{"name":15172,"type":14551},"group_by()",[3385,15174,15175,15176,15179,15180,3465],{},"Групує результати запиту (",[3412,15177,15178],{},"GROUP BY",") за вказаними стовпцями (",[3412,15181,14971],{},[3585,15183,15185],{"className":4445,"code":15184,"language":4448,"meta":3590,"style":3590},"# Базовий: групування постів за ID автора\nselect(Post.author_id, func.count(Post.id)).group_by(Post.author_id)\n# SQL: SELECT posts.author_id, count(posts.id) FROM posts GROUP BY posts.author_id\n\n# Просунутий: групування за кількома полями та виразами (екстракція року з дати)\nselect(\n    Post.author_id,\n    func.extract(\"year\", Post.created_at),\n    func.count(Post.id)\n).group_by(\n    Post.author_id,\n    func.extract(\"year\", Post.created_at)\n)\n# SQL: SELECT posts.author_id, EXTRACT(year FROM posts.created_at), count(posts.id) FROM posts GROUP BY posts.author_id, EXTRACT(year FROM posts.created_at)\n",[3412,15186,15187,15192,15197,15202,15206,15211,15216,15221,15232,15237,15242,15246,15255,15259],{"__ignoreMap":3590},[3594,15188,15189],{"class":3596,"line":3597},[3594,15190,15191],{"class":4482},"# Базовий: групування постів за ID автора\n",[3594,15193,15194],{"class":3596,"line":3603},[3594,15195,15196],{"class":4459},"select(Post.author_id, func.count(Post.id)).group_by(Post.author_id)\n",[3594,15198,15199],{"class":3596,"line":3609},[3594,15200,15201],{"class":4482},"# SQL: SELECT posts.author_id, count(posts.id) FROM posts GROUP BY posts.author_id\n",[3594,15203,15204],{"class":3596,"line":3615},[3594,15205,3631],{"emptyLinePlaceholder":3630},[3594,15207,15208],{"class":3596,"line":3621},[3594,15209,15210],{"class":4482},"# Просунутий: групування за кількома полями та виразами (екстракція року з дати)\n",[3594,15212,15213],{"class":3596,"line":3627},[3594,15214,15215],{"class":4459},"select(\n",[3594,15217,15218],{"class":3596,"line":3634},[3594,15219,15220],{"class":4459},"    Post.author_id,\n",[3594,15222,15223,15226,15229],{"class":3596,"line":3640},[3594,15224,15225],{"class":4459},"    func.extract(",[3594,15227,15228],{"class":4500},"\"year\"",[3594,15230,15231],{"class":4459},", Post.created_at),\n",[3594,15233,15234],{"class":3596,"line":3646},[3594,15235,15236],{"class":4459},"    func.count(Post.id)\n",[3594,15238,15239],{"class":3596,"line":3652},[3594,15240,15241],{"class":4459},").group_by(\n",[3594,15243,15244],{"class":3596,"line":3658},[3594,15245,15220],{"class":4459},[3594,15247,15248,15250,15252],{"class":3596,"line":3664},[3594,15249,15225],{"class":4459},[3594,15251,15228],{"class":4500},[3594,15253,15254],{"class":4459},", Post.created_at)\n",[3594,15256,15257],{"class":3596,"line":3670},[3594,15258,4558],{"class":4459},[3594,15260,15261],{"class":3596,"line":3676},[3594,15262,15263],{"class":4482},"# SQL: SELECT posts.author_id, EXTRACT(year FROM posts.created_at), count(posts.id) FROM posts GROUP BY posts.author_id, EXTRACT(year FROM posts.created_at)\n",[9978,15265,15267,15277],{"name":15266,"type":14551},"having()",[3385,15268,15269,15270,15273,15274,3439],{},"Умови фільтрації для згрупованих результатів (",[3412,15271,15272],{},"HAVING","). Приймає умову фільтрації ",[3412,15275,15276],{},"clause",[3585,15278,15280],{"className":4445,"code":15279,"language":4448,"meta":3590,"style":3590},"# Базовий: фільтрація груп з кількістю елементів більше 5\nselect(Post.author_id).group_by(Post.author_id).having(func.count(Post.id) > 5)\n# SQL: SELECT posts.author_id FROM posts GROUP BY posts.author_id HAVING count(posts.id) > 5\n\n# Просунутий: фільтрація з перевіркою середньої довжини заголовків постів у групі\nselect(Post.author_id).group_by(Post.author_id).having(\n    func.avg(func.length(Post.title)) > 50\n)\n# SQL: SELECT posts.author_id FROM posts GROUP BY posts.author_id HAVING avg(length(posts.title)) > 50\n",[3412,15281,15282,15287,15296,15301,15305,15310,15315,15323,15327],{"__ignoreMap":3590},[3594,15283,15284],{"class":3596,"line":3597},[3594,15285,15286],{"class":4482},"# Базовий: фільтрація груп з кількістю елементів більше 5\n",[3594,15288,15289,15292,15294],{"class":3596,"line":3603},[3594,15290,15291],{"class":4459},"select(Post.author_id).group_by(Post.author_id).having(func.count(Post.id) > ",[3594,15293,6934],{"class":4514},[3594,15295,4558],{"class":4459},[3594,15297,15298],{"class":3596,"line":3609},[3594,15299,15300],{"class":4482},"# SQL: SELECT posts.author_id FROM posts GROUP BY posts.author_id HAVING count(posts.id) > 5\n",[3594,15302,15303],{"class":3596,"line":3615},[3594,15304,3631],{"emptyLinePlaceholder":3630},[3594,15306,15307],{"class":3596,"line":3621},[3594,15308,15309],{"class":4482},"# Просунутий: фільтрація з перевіркою середньої довжини заголовків постів у групі\n",[3594,15311,15312],{"class":3596,"line":3627},[3594,15313,15314],{"class":4459},"select(Post.author_id).group_by(Post.author_id).having(\n",[3594,15316,15317,15320],{"class":3596,"line":3634},[3594,15318,15319],{"class":4459},"    func.avg(func.length(Post.title)) > ",[3594,15321,15322],{"class":4514},"50\n",[3594,15324,15325],{"class":3596,"line":3640},[3594,15326,4558],{"class":4459},[3594,15328,15329],{"class":3596,"line":3646},[3594,15330,15331],{"class":4482},"# SQL: SELECT posts.author_id FROM posts GROUP BY posts.author_id HAVING avg(length(posts.title)) > 50\n",[9978,15333,15334,15352],{"name":14487,"type":14551},[3385,15335,15336,15337,15340,15341,15344,15345,15348,15349,15351],{},"Об'єднує запит з іншою таблицею або моделлю через ",[3412,15338,15339],{},"INNER JOIN",". Приймає цільову модель ",[3412,15342,15343],{},"target"," та необов'язкову умову об'єднання ",[3412,15346,15347],{},"onclause",". Якщо зв'язок налаштовано через ",[3412,15350,3956],{},", SQLAlchemy автоматично виведе умову об'єднання.",[3585,15353,15355],{"className":4445,"code":15354,"language":4448,"meta":3590,"style":3590},"# Базовий: INNER JOIN через зв'язок (relationship)\nselect(User).join(User.posts)\n# SQL: SELECT users.id, ... FROM users JOIN posts ON users.id = posts.author_id\n\n# Просунутий: INNER JOIN за явною умовою ON та подвійний JOIN\nselect(User.username, Tag.name).join(\n    Post, User.id == Post.author_id\n).join(\n    Post.tags\n)\n# SQL: SELECT users.username, tags.name FROM users JOIN posts ON users.id = posts.author_id JOIN post_tags ON posts.id = post_tags.post_id JOIN tags ON tags.id = post_tags.tag_id\n",[3412,15356,15357,15362,15367,15372,15376,15381,15386,15391,15396,15401,15405],{"__ignoreMap":3590},[3594,15358,15359],{"class":3596,"line":3597},[3594,15360,15361],{"class":4482},"# Базовий: INNER JOIN через зв'язок (relationship)\n",[3594,15363,15364],{"class":3596,"line":3603},[3594,15365,15366],{"class":4459},"select(User).join(User.posts)\n",[3594,15368,15369],{"class":3596,"line":3609},[3594,15370,15371],{"class":4482},"# SQL: SELECT users.id, ... FROM users JOIN posts ON users.id = posts.author_id\n",[3594,15373,15374],{"class":3596,"line":3615},[3594,15375,3631],{"emptyLinePlaceholder":3630},[3594,15377,15378],{"class":3596,"line":3621},[3594,15379,15380],{"class":4482},"# Просунутий: INNER JOIN за явною умовою ON та подвійний JOIN\n",[3594,15382,15383],{"class":3596,"line":3627},[3594,15384,15385],{"class":4459},"select(User.username, Tag.name).join(\n",[3594,15387,15388],{"class":3596,"line":3634},[3594,15389,15390],{"class":4459},"    Post, User.id == Post.author_id\n",[3594,15392,15393],{"class":3596,"line":3640},[3594,15394,15395],{"class":4459},").join(\n",[3594,15397,15398],{"class":3596,"line":3646},[3594,15399,15400],{"class":4459},"    Post.tags\n",[3594,15402,15403],{"class":3596,"line":3652},[3594,15404,4558],{"class":4459},[3594,15406,15407],{"class":3596,"line":3658},[3594,15408,15409],{"class":4482},"# SQL: SELECT users.username, tags.name FROM users JOIN posts ON users.id = posts.author_id JOIN post_tags ON posts.id = post_tags.post_id JOIN tags ON tags.id = post_tags.tag_id\n",[9978,15411,15413,15424],{"name":15412,"type":14551},"outerjoin()",[3385,15414,15336,15415,13135,15417,15420,15421,3439],{},[3412,15416,13210],{},[3412,15418,15419],{},"LEFT JOIN","). Працює аналогічно до ",[3412,15422,15423],{},".join()",[3585,15425,15427],{"className":4445,"code":15426,"language":4448,"meta":3590,"style":3590},"# Базовий: LEFT JOIN користувачів та їхніх постів\nselect(User, Post).outerjoin(User.posts)\n# SQL: SELECT users.id, ... posts.id, ... FROM users LEFT OUTER JOIN posts ON users.id = posts.author_id\n\n# Просунутий: Anti-Join (знайти користувачів, у яких взагалі немає постів)\nselect(User).outerjoin(User.posts).where(Post.id.is_(None))\n# SQL: SELECT users.id, ... FROM users LEFT OUTER JOIN posts ON users.id = posts.author_id WHERE posts.id IS NULL\n",[3412,15428,15429,15434,15439,15444,15448,15453,15462],{"__ignoreMap":3590},[3594,15430,15431],{"class":3596,"line":3597},[3594,15432,15433],{"class":4482},"# Базовий: LEFT JOIN користувачів та їхніх постів\n",[3594,15435,15436],{"class":3596,"line":3603},[3594,15437,15438],{"class":4459},"select(User, Post).outerjoin(User.posts)\n",[3594,15440,15441],{"class":3596,"line":3609},[3594,15442,15443],{"class":4482},"# SQL: SELECT users.id, ... posts.id, ... FROM users LEFT OUTER JOIN posts ON users.id = posts.author_id\n",[3594,15445,15446],{"class":3596,"line":3615},[3594,15447,3631],{"emptyLinePlaceholder":3630},[3594,15449,15450],{"class":3596,"line":3621},[3594,15451,15452],{"class":4482},"# Просунутий: Anti-Join (знайти користувачів, у яких взагалі немає постів)\n",[3594,15454,15455,15458,15460],{"class":3596,"line":3627},[3594,15456,15457],{"class":4459},"select(User).outerjoin(User.posts).where(Post.id.is_(",[3594,15459,9644],{"class":4622},[3594,15461,7851],{"class":4459},[3594,15463,15464],{"class":3596,"line":3634},[3594,15465,15466],{"class":4482},"# SQL: SELECT users.id, ... FROM users LEFT OUTER JOIN posts ON users.id = posts.author_id WHERE posts.id IS NULL\n",[4433,15468,15470],{"id":15469},"оператори-та-модифікатори-стовпців-column-operators-modifiers","Оператори та модифікатори стовпців (Column Operators & Modifiers)",[3385,15472,15473,15474,3488,15476,3488,15479,3488,15482,3488,15485,15488,15489,15492],{},"Оскільки Python має власні зарезервовані ключові слова (наприклад, ",[3412,15475,4682],{},[3412,15477,15478],{},"is",[3412,15480,15481],{},"not",[3412,15483,15484],{},"and",[3412,15486,15487],{},"or","), ми не можемо перевантажити їх безпосередньо у виразах на кшталт ",[3412,15490,15491],{},"User.id in [1, 2]",". Python-інтерпретатор просто видасть синтаксичну помилку.",[3385,15494,15495,15496,15499],{},"Для обходу цього обмеження SQLAlchemy надає спеціальні ",[3389,15497,15498],{},"методи об'єктів стовпців"," (Column Operators), які генерують відповідні SQL-конструкції:",[9975,15501,15502,15555,15659,15746,15832,15884,15921,15962,16009],{},[9978,15503,15506,15512,15527],{"name":15504,"type":15505},"in\\_()","Column Operators",[3385,15507,15508,15509,15511],{},"Еквівалент SQL-оператора ",[3412,15510,13222],{},". Перевіряє, чи належить значення стовпця до вказаного списку.",[3510,15513,15514,15519],{},[3455,15515,15516,15518],{},[3389,15517,5103],{},": Для фільтрації за множиною значень (наприклад, статуси, списки ID).",[3455,15520,15521,15523,15524,15526],{},[3389,15522,10007],{},": З великими списками (тисячі елементів), оскільки це сильно сповільнює розбір запиту базою даних. У таких випадках краще використовувати ",[3412,15525,10433],{}," або підзапит.",[3585,15528,15530],{"className":4445,"code":15529,"language":4448,"meta":3590,"style":3590},"select(User).where(User.id.in_([1, 2, 3]))\n# SQL: SELECT ... FROM users WHERE users.id IN (1, 2, 3)\n",[3412,15531,15532,15550],{"__ignoreMap":3590},[3594,15533,15534,15537,15539,15541,15543,15545,15547],{"class":3596,"line":3597},[3594,15535,15536],{"class":4459},"select(User).where(User.id.in_([",[3594,15538,6486],{"class":4514},[3594,15540,3488],{"class":4459},[3594,15542,6282],{"class":4514},[3594,15544,3488],{"class":4459},[3594,15546,15149],{"class":4514},[3594,15548,15549],{"class":4459},"]))\n",[3594,15551,15552],{"class":3596,"line":3603},[3594,15553,15554],{"class":4482},"# SQL: SELECT ... FROM users WHERE users.id IN (1, 2, 3)\n",[9978,15556,15558,15570,15622],{"name":15557,"type":15505},"is\\_() \u002F is_not()",[3385,15559,15560,15561,3435,15564,15567,15568,3439],{},"Еквіваленти SQL-операторів ",[3412,15562,15563],{},"IS",[3412,15565,15566],{},"IS NOT",". Найчастіше використовуються для роботи з ",[3412,15569,11211],{},[3510,15571,15572,15590],{},[3455,15573,15574,15576,15577,13135,15580,15583,15584,13135,15587,3465],{},[3389,15575,5103],{},": Для фільтрації ",[3412,15578,15579],{},"IS NULL",[3412,15581,15582],{},"is_(None)",") або ",[3412,15585,15586],{},"IS NOT NULL",[3412,15588,15589],{},"is_not(None)",[3455,15591,15592,15594,15595,3461,15598,15601,15602,3435,15605,15608,15609,15611,15612,3435,15615,15618,15619,15621],{},[3389,15593,10007],{},": Уникайте порівнянь ",[3412,15596,15597],{},"== None",[3412,15599,15600],{},"!= None",". Хоча SQLAlchemy перевантажує оператори ",[3412,15603,15604],{},"==",[3412,15606,15607],{},"!="," для генерації ",[3412,15610,15579],{},", використання ",[3412,15613,15614],{},"is_()",[3412,15616,15617],{},"is_not()"," є стандартом PEP8 для порівняння з ",[3412,15620,9644],{}," та робить намір коду очевидним.",[3585,15623,15625],{"className":4445,"code":15624,"language":4448,"meta":3590,"style":3590},"select(User).where(User.email.is_(None))\n# SQL: SELECT ... FROM users WHERE users.email IS NULL\n\nselect(User).where(User.email.is_not(None))\n# SQL: SELECT ... FROM users WHERE users.email IS NOT NULL\n",[3412,15626,15627,15636,15641,15645,15654],{"__ignoreMap":3590},[3594,15628,15629,15632,15634],{"class":3596,"line":3597},[3594,15630,15631],{"class":4459},"select(User).where(User.email.is_(",[3594,15633,9644],{"class":4622},[3594,15635,7851],{"class":4459},[3594,15637,15638],{"class":3596,"line":3603},[3594,15639,15640],{"class":4482},"# SQL: SELECT ... FROM users WHERE users.email IS NULL\n",[3594,15642,15643],{"class":3596,"line":3609},[3594,15644,3631],{"emptyLinePlaceholder":3630},[3594,15646,15647,15650,15652],{"class":3596,"line":3615},[3594,15648,15649],{"class":4459},"select(User).where(User.email.is_not(",[3594,15651,9644],{"class":4622},[3594,15653,7851],{"class":4459},[3594,15655,15656],{"class":3596,"line":3621},[3594,15657,15658],{"class":4482},"# SQL: SELECT ... FROM users WHERE users.email IS NOT NULL\n",[9978,15660,15662,15684,15703],{"name":15661,"type":15505},"like() \u002F ilike()",[3385,15663,15664,15665,15668,15669,5072,15672,15675,15676,15679,15680,15683],{},"Пошук за шаблоном. ",[3412,15666,15667],{},"like()"," є регістрозалежним (SQL ",[3412,15670,15671],{},"LIKE",[3412,15673,15674],{},"ilike()"," — регістронезалежний (SQL ",[3412,15677,15678],{},"ILIKE"," у PostgreSQL, або ",[3412,15681,15682],{},"LOWER(col) LIKE LOWER(val)"," в інших СУБД).",[3510,15685,15686,15694],{},[3455,15687,15688,15690,15691,3439],{},[3389,15689,5103],{},": Для пошуку підрядків за допомогою символу маски ",[3412,15692,15693],{},"%",[3455,15695,15696,15698,15699,15702],{},[3389,15697,10007],{},": Для великих текстових полів (наприклад, статей) — замість цього краще використовувати повнотекстовий пошук (Full-Text Search) бази даних, оскільки ",[3412,15700,15701],{},"LIKE \"%текст%\""," не використовує стандартні B-Tree індекси та виконує повне сканування таблиці (Full Table Scan).",[3585,15704,15706],{"className":4445,"code":15705,"language":4448,"meta":3590,"style":3590},"select(User).where(User.username.like(\"ara%\"))\n# SQL: SELECT ... FROM users WHERE users.username LIKE 'ara%'\n\nselect(User).where(User.username.ilike(\"%doe%\"))\n# SQL: SELECT ... FROM users WHERE users.username ILIKE '%doe%'\n",[3412,15707,15708,15717,15722,15726,15741],{"__ignoreMap":3590},[3594,15709,15710,15713,15715],{"class":3596,"line":3597},[3594,15711,15712],{"class":4459},"select(User).where(User.username.like(",[3594,15714,14871],{"class":4500},[3594,15716,7851],{"class":4459},[3594,15718,15719],{"class":3596,"line":3603},[3594,15720,15721],{"class":4482},"# SQL: SELECT ... FROM users WHERE users.username LIKE 'ara%'\n",[3594,15723,15724],{"class":3596,"line":3609},[3594,15725,3631],{"emptyLinePlaceholder":3630},[3594,15727,15728,15731,15733,15736,15739],{"class":3596,"line":3615},[3594,15729,15730],{"class":4459},"select(User).where(User.username.ilike(",[3594,15732,4631],{"class":4500},[3594,15734,15735],{"class":4622},"%d",[3594,15737,15738],{"class":4500},"oe%\"",[3594,15740,7851],{"class":4459},[3594,15742,15743],{"class":3596,"line":3621},[3594,15744,15745],{"class":4482},"# SQL: SELECT ... FROM users WHERE users.username ILIKE '%doe%'\n",[9978,15747,15749,15760,15775],{"name":15748,"type":15505},"startswith() \u002F endswith() \u002F contains()",[3385,15750,15751,15752,4025,15754,15756,15757,15759],{},"Спрощені методи-обгортки поверх ",[3412,15753,15667],{},[3412,15755,15674],{},", які автоматично додають символ ",[3412,15758,15693],{}," у потрібні місця.",[3510,15761,15762],{},[3455,15763,15764,15766,15767,15770,15771,15774],{},[3389,15765,5103],{},": Для очевидного та простого текстового пошуку. За замовчуванням вони є регістрозалежними, але приймають параметр ",[3412,15768,15769],{},"autoescape=True"," або можуть комбінуватися через ",[3412,15772,15773],{},".ilike()"," за потреби.",[3585,15776,15778],{"className":4445,"code":15777,"language":4448,"meta":3590,"style":3590},"select(User).where(User.username.startswith(\"ara\"))\n# SQL: SELECT ... FROM users WHERE users.username LIKE 'ara%'\n\nselect(User).where(User.email.endswith(\".com\"))\n# SQL: SELECT ... FROM users WHERE users.email LIKE '%.com'\n\nselect(User).where(User.username.contains(\"admin\"))\n# SQL: SELECT ... FROM users WHERE users.username LIKE '%admin%'\n",[3412,15779,15780,15790,15794,15798,15808,15813,15817,15827],{"__ignoreMap":3590},[3594,15781,15782,15785,15788],{"class":3596,"line":3597},[3594,15783,15784],{"class":4459},"select(User).where(User.username.startswith(",[3594,15786,15787],{"class":4500},"\"ara\"",[3594,15789,7851],{"class":4459},[3594,15791,15792],{"class":3596,"line":3603},[3594,15793,15721],{"class":4482},[3594,15795,15796],{"class":3596,"line":3609},[3594,15797,3631],{"emptyLinePlaceholder":3630},[3594,15799,15800,15803,15806],{"class":3596,"line":3615},[3594,15801,15802],{"class":4459},"select(User).where(User.email.endswith(",[3594,15804,15805],{"class":4500},"\".com\"",[3594,15807,7851],{"class":4459},[3594,15809,15810],{"class":3596,"line":3621},[3594,15811,15812],{"class":4482},"# SQL: SELECT ... FROM users WHERE users.email LIKE '%.com'\n",[3594,15814,15815],{"class":3596,"line":3627},[3594,15816,3631],{"emptyLinePlaceholder":3630},[3594,15818,15819,15822,15825],{"class":3596,"line":3634},[3594,15820,15821],{"class":4459},"select(User).where(User.username.contains(",[3594,15823,15824],{"class":4500},"\"admin\"",[3594,15826,7851],{"class":4459},[3594,15828,15829],{"class":3596,"line":3640},[3594,15830,15831],{"class":4482},"# SQL: SELECT ... FROM users WHERE users.username LIKE '%admin%'\n",[9978,15833,15835,15840,15861],{"name":15834,"type":15505},"between()",[3385,15836,15508,15837,3439],{},[3412,15838,15839],{},"BETWEEN ... AND ...",[3510,15841,15842,15847],{},[3455,15843,15844,15846],{},[3389,15845,5103],{},": Для фільтрації діапазонів чисел, дат або цін (включаючи межі).",[3455,15848,15849,15851,15852,15855,15856,3435,15859,3439],{},[3389,15850,10007],{},": Для діапазонів дат, якщо ви не впевнені щодо часу (оскільки ",[3412,15853,15854],{},"BETWEEN"," включає кінцеву дату до 00:00:00, що може призвести до пропуску записів за останній день). Для дат часто надійніше використовувати явні порівняння ",[3412,15857,15858],{},">=",[3412,15860,11912],{},[3585,15862,15864],{"className":4445,"code":15863,"language":4448,"meta":3590,"style":3590},"select(User).where(User.id.between(10, 20))\n# SQL: SELECT ... FROM users WHERE users.id BETWEEN 10 AND 20\n",[3412,15865,15866,15879],{"__ignoreMap":3590},[3594,15867,15868,15871,15873,15875,15877],{"class":3596,"line":3597},[3594,15869,15870],{"class":4459},"select(User).where(User.id.between(",[3594,15872,4646],{"class":4514},[3594,15874,3488],{"class":4459},[3594,15876,6061],{"class":4514},[3594,15878,7851],{"class":4459},[3594,15880,15881],{"class":3596,"line":3603},[3594,15882,15883],{"class":4482},"# SQL: SELECT ... FROM users WHERE users.id BETWEEN 10 AND 20\n",[9978,15885,15888,15894,15906],{"name":15886,"type":15887},"desc() \u002F asc()","Column Modifiers",[3385,15889,15890,15891,3439],{},"Визначає напрямок сортування стовпця (спадання \u002F зростання). Зазвичай передається всередину ",[3412,15892,15893],{},".order_by()",[3510,15895,15896,15901],{},[3455,15897,15898,15900],{},[3389,15899,5103],{},": Для явного контролю порядку сортування у звітах, списках та пагінації.",[3455,15902,15903,15905],{},[3389,15904,10007],{},": Унікальні індекси вже відсортовані за зростанням за замовчуванням СУБД.",[3585,15907,15909],{"className":4445,"code":15908,"language":4448,"meta":3590,"style":3590},"select(User).order_by(User.created_at.desc())\n# SQL: SELECT ... FROM users ORDER BY users.created_at DESC\n",[3412,15910,15911,15916],{"__ignoreMap":3590},[3594,15912,15913],{"class":3596,"line":3597},[3594,15914,15915],{"class":4459},"select(User).order_by(User.created_at.desc())\n",[3594,15917,15918],{"class":3596,"line":3603},[3594,15919,15920],{"class":4482},"# SQL: SELECT ... FROM users ORDER BY users.created_at DESC\n",[9978,15922,15924,15936,15947],{"name":15923,"type":15887},"nulls_first() \u002F nulls_last()",[3385,15925,15926,15927,15929,15930,4025,15933,3439],{},"Визначає, де розташовувати порожні (",[3412,15928,11211],{},") значення під час сортування — на початку чи в кінці результату. Працює як доповнення до ",[3412,15931,15932],{},"desc()",[3412,15934,15935],{},"asc()",[3510,15937,15938],{},[3455,15939,15940,15942,15943,15946],{},[3389,15941,5103],{},": Коли сортуєте за опціональними полями (наприклад, дата завершення завдання ",[3412,15944,15945],{},"due_date","), щоб порожні значення не з'являлися на початку списку.",[3585,15948,15950],{"className":4445,"code":15949,"language":4448,"meta":3590,"style":3590},"select(User).order_by(User.email.asc().nulls_last())\n# SQL: SELECT ... FROM users ORDER BY users.email ASC NULLS LAST\n",[3412,15951,15952,15957],{"__ignoreMap":3590},[3594,15953,15954],{"class":3596,"line":3597},[3594,15955,15956],{"class":4459},"select(User).order_by(User.email.asc().nulls_last())\n",[3594,15958,15959],{"class":3596,"line":3603},[3594,15960,15961],{"class":4482},"# SQL: SELECT ... FROM users ORDER BY users.email ASC NULLS LAST\n",[9978,15963,15965,15971,15988],{"name":15964,"type":15887},"label()",[3385,15966,15967,15968,3465],{},"Призначає аліас (псевдонім) стовпцю або виразу (SQL ",[3412,15969,15970],{},"AS",[3510,15972,15973],{},[3455,15974,15975,15977,15978,3488,15981,15984,15985,3465],{},[3389,15976,5103],{},": При агрегаціях (",[3412,15979,15980],{},"func.count()",[3412,15982,15983],{},"func.sum()",") або математичних операціях, щоб результат мав зручне ім'я у NamedTuple-рядках (",[3412,15986,15987],{},"Row.alias_name",[3585,15989,15991],{"className":4445,"code":15990,"language":4448,"meta":3590,"style":3590},"select(func.count(User.id).label(\"total\")).scalar_one()\n# SQL: SELECT count(users.id) AS total FROM users\n",[3412,15992,15993,16004],{"__ignoreMap":3590},[3594,15994,15995,15998,16001],{"class":3596,"line":3597},[3594,15996,15997],{"class":4459},"select(func.count(User.id).label(",[3594,15999,16000],{"class":4500},"\"total\"",[3594,16002,16003],{"class":4459},")).scalar_one()\n",[3594,16005,16006],{"class":3596,"line":3603},[3594,16007,16008],{"class":4482},"# SQL: SELECT count(users.id) AS total FROM users\n",[9978,16010,16012,16022,16029],{"name":16011,"type":15887},"cast()",[3385,16013,16014,16015,16018,16019,3439],{},"Приводить значення стовпця до іншого типу даних на рівні бази даних (SQL ",[3412,16016,16017],{},"CAST(col AS type)","). Імпортується з головного модуля ",[3412,16020,16021],{},"from sqlalchemy import cast",[3510,16023,16024],{},[3455,16025,16026,16028],{},[3389,16027,5103],{},": Коли потрібно порівняти стовпець типу UUID з рядком, або перетворити ціле число на рядок для конкатенації чи форматування.",[3585,16030,16032],{"className":4445,"code":16031,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import cast, String\nselect(cast(User.id, String))\n# SQL: SELECT CAST(users.id AS VARCHAR) FROM users\n",[3412,16033,16034,16045,16050],{"__ignoreMap":3590},[3594,16035,16036,16038,16040,16042],{"class":3596,"line":3597},[3594,16037,4465],{"class":4455},[3594,16039,5996],{"class":4459},[3594,16041,4456],{"class":4455},[3594,16043,16044],{"class":4459}," cast, String\n",[3594,16046,16047],{"class":3596,"line":3603},[3594,16048,16049],{"class":4459},"select(cast(User.id, String))\n",[3594,16051,16052],{"class":3596,"line":3609},[3594,16053,16054],{"class":4482},"# SQL: SELECT CAST(users.id AS VARCHAR) FROM users\n",[4433,16056,16058],{"id":16057},"як-працює-магія-умов-перевантаження-операторів","Як працює «магія» умов (Перевантаження операторів)",[3385,16060,16061,16062,11116,16065,16067,16068,16070,16071,3461,16073,16075,16076,3461,16079,16082],{},"Для розробників, які вперше бачать код ",[3412,16063,16064],{},"User.id > 10",[3412,16066,14753],{},", це виглядає як магія. За логікою Python, вираз ",[3412,16069,16064],{}," мав би негайно обчислитися в логічне ",[3412,16072,4641],{},[3412,16074,6238],{}," (що передало б у ",[3412,16077,16078],{},"where(True)",[3412,16080,16081],{},"where(False)"," і зламало б запит).",[3385,16084,16085,16086,16089],{},"Секрет криється в ",[3389,16087,16088],{},"перевантаженні операторів"," (Operator Overloading) у Python:",[3452,16091,16092,16109],{},[3455,16093,16094,16097,16098,16101,16102,16104,16105,16108],{},[3389,16095,16096],{},"Стовпці — це не просто значення",": Атрибут ",[3412,16099,16100],{},"User.id"," у вашому коді — це не ціле число ",[3412,16103,6930],{},". Це об'єкт класу ",[3412,16106,16107],{},"InstrumentedAttribute"," (дескриптор, наданий SQLAlchemy).",[3455,16110,16111,16114,16115],{},[3389,16112,16113],{},"Магічні методи",": SQLAlchemy перевизначає стандартні магічні методи порівнянь Python на цих об'єктах:\n",[3510,16116,16117,16125,16132,16140,16147],{},[3455,16118,16119,16122,16123,3544],{},[3412,16120,16121],{},"__eq__"," (для ",[3412,16124,15604],{},[3455,16126,16127,16122,16130,3544],{},[3412,16128,16129],{},"__ne__",[3412,16131,15607],{},[3455,16133,16134,16122,16137,3544],{},[3412,16135,16136],{},"__gt__",[3412,16138,16139],{},">",[3455,16141,16142,16122,16145,3544],{},[3412,16143,16144],{},"__lt__",[3412,16146,11912],{},[3455,16148,16149],{},"тощо.",[3385,16151,16152,16153,16155],{},"Коли ви пишете ",[3412,16154,16064],{},", Python під капотом викликає:",[3585,16157,16159],{"className":4445,"code":16158,"language":4448,"meta":3590,"style":3590},"User.id.__gt__(10)\n",[3412,16160,16161],{"__ignoreMap":3590},[3594,16162,16163,16166,16168,16170,16172],{"class":3596,"line":3597},[3594,16164,16165],{"class":4459},"User.id.",[3594,16167,16136],{"class":4690},[3594,16169,7763],{"class":4459},[3594,16171,4646],{"class":4514},[3594,16173,4558],{"class":4459},[3385,16175,16176,16177,3461,16179,16181,16182,16184,16185,16190],{},"Замість повернення ",[3412,16178,4641],{},[3412,16180,6238],{},", реалізація ",[3412,16183,16136],{}," в SQLAlchemy повертає об'єкт ",[3389,16186,16187],{},[3412,16188,16189],{},"BinaryExpression"," (бінарний вираз).",[3385,16192,16193],{},"Цей об'єкт є вузлом абстрактного синтаксичного дерева (AST) і містить інформацію:",[3510,16195,16196,16204,16212],{},[3455,16197,16198,16201,16202],{},[3389,16199,16200],{},"Ліва частина",": посилання на стовпець ",[3412,16203,16100],{},[3455,16205,16206,9991,16209,16211],{},[3389,16207,16208],{},"Оператор",[3412,16210,16139],{}," (більше)",[3455,16213,16214,16217,16218],{},[3389,16215,16216],{},"Права частина",": константа ",[3412,16219,4646],{},[3385,16221,16222,16223,16225,16226,16228,16229,16232,16233,3465],{},"Коли ви передаєте цей ",[3412,16224,16189],{}," у метод ",[3412,16227,14767],{},", SQLAlchemy додає його до списку фільтрів. Під час виконання запиту компилятор діалекту (наприклад, PostgreSQL) обходить це дерево і трансформує вузол у текстовий SQL: ",[3412,16230,16231],{},"users.id > 10"," (а точніше, у параметризований вигляд ",[3412,16234,16235],{},"users.id > :id_1",[3385,16237,16238],{},"Зіставлення операторів Python та SQL у SQLAlchemy:",[3986,16240,16241,16254],{},[3989,16242,16243],{},[3992,16244,16245,16248,16251],{},[3995,16246,16247],{"align":3997},"Вираз у SQLAlchemy",[3995,16249,16250],{"align":3997},"Магічний метод Python",[3995,16252,16253],{"align":3997},"Результат у SQL",[4006,16255,16256,16272,16288,16302,16319,16335,16352,16370],{},[3992,16257,16258,16263,16267],{},[4011,16259,16260],{"align":3997},[3412,16261,16262],{},"User.username == \"admin\"",[4011,16264,16265],{"align":3997},[3412,16266,16121],{},[4011,16268,16269],{"align":3997},[3412,16270,16271],{},"users.username = 'admin'",[3992,16273,16274,16279,16283],{},[4011,16275,16276],{"align":3997},[3412,16277,16278],{},"User.id != 5",[4011,16280,16281],{"align":3997},[3412,16282,16129],{},[4011,16284,16285],{"align":3997},[3412,16286,16287],{},"users.id \u003C> 5",[3992,16289,16290,16294,16298],{},[4011,16291,16292],{"align":3997},[3412,16293,16064],{},[4011,16295,16296],{"align":3997},[3412,16297,16136],{},[4011,16299,16300],{"align":3997},[3412,16301,16231],{},[3992,16303,16304,16309,16314],{},[4011,16305,16306],{"align":3997},[3412,16307,16308],{},"User.id >= 10",[4011,16310,16311],{"align":3997},[3412,16312,16313],{},"__ge__",[4011,16315,16316],{"align":3997},[3412,16317,16318],{},"users.id >= 10",[3992,16320,16321,16326,16330],{},[4011,16322,16323],{"align":3997},[3412,16324,16325],{},"User.id \u003C 10",[4011,16327,16328],{"align":3997},[3412,16329,16144],{},[4011,16331,16332],{"align":3997},[3412,16333,16334],{},"users.id \u003C 10",[3992,16336,16337,16342,16347],{},[4011,16338,16339],{"align":3997},[3412,16340,16341],{},"User.id \u003C= 10",[4011,16343,16344],{"align":3997},[3412,16345,16346],{},"__le__",[4011,16348,16349],{"align":3997},[3412,16350,16351],{},"users.id \u003C= 10",[3992,16353,16354,16359,16365],{},[4011,16355,16356],{"align":3997},[3412,16357,16358],{},"User.email == None",[4011,16360,16361,13219,16363],{"align":3997},[3412,16362,16121],{},[3412,16364,9644],{},[4011,16366,16367],{"align":3997},[3412,16368,16369],{},"users.email IS NULL",[3992,16371,16372,16377,16383],{},[4011,16373,16374],{"align":3997},[3412,16375,16376],{},"User.email != None",[4011,16378,16379,13219,16381],{"align":3997},[3412,16380,16129],{},[3412,16382,9644],{},[4011,16384,16385],{"align":3997},[3412,16386,16387],{},"users.email IS NOT NULL",[4433,16389,16391,16392],{"id":16390},"використання-sql-функцій-func","Використання SQL-функцій: ",[3412,16393,16394],{},"func",[3385,16396,16397,16398,16401,16402,16405,16406,16408],{},"У багатьох SQL-запитах виникає потреба використовувати вбудовані функції СУБД: порахувати кількість рядків (",[3412,16399,16400],{},"COUNT","), звести текст до нижнього регістру (",[3412,16403,16404],{},"LOWER","), отримати поточний час (",[3412,16407,5272],{},") або виконати складнішу агрегацію.",[3385,16410,16411,16412,16416,16417,3465],{},"У SQLAlchemy для цього призначений спеціальний об'єкт ",[3389,16413,16414],{},[3412,16415,16394],{}," (імпортується як ",[3412,16418,16419],{},"from sqlalchemy import func",[5430,16421,16423,16424,7645],{"id":16422},"як-працює-динамічна-магія-func","Як працює «динамічна магія» ",[3412,16425,16394],{},[3385,16427,16428,16429,16431,16432,3439],{},"На відміну від більшості бібліотек, у ",[3412,16430,16394],{}," немає жорстко прописаного списку методів у коді SQLAlchemy. Замість цього він використовує магічний метод Python ",[3389,16433,16434],{},[3412,16435,16436],{},"__getattr__",[3385,16438,16439,16440,11322,16442,16445,16446,16449],{},"Коли ви звертаєтеся до будь-якого атрибута на ",[3412,16441,16394],{},[3412,16443,16444],{},"func.any_custom_function()","), SQLAlchemy «на льоту» створює об'єкт функції з цим ім'ям. Це означає, що ви можете викликати ",[3389,16447,16448],{},"будь-яку"," функцію, яку підтримує ваша конкретна база даних, навіть якщо розробники SQLAlchemy про неї не знали!",[3585,16451,16453],{"className":4445,"code":16452,"language":4448,"meta":3590,"style":3590},"# Будь-яке ім'я динамічно трансформується в SQL-функцію:\nstmt = select(func.my_custom_db_function(User.id))\n# SQL: SELECT my_custom_db_function(users.id) FROM users\n",[3412,16454,16455,16460,16465],{"__ignoreMap":3590},[3594,16456,16457],{"class":3596,"line":3597},[3594,16458,16459],{"class":4482},"# Будь-яке ім'я динамічно трансформується в SQL-функцію:\n",[3594,16461,16462],{"class":3596,"line":3603},[3594,16463,16464],{"class":4459},"stmt = select(func.my_custom_db_function(User.id))\n",[3594,16466,16467],{"class":3596,"line":3609},[3594,16468,16469],{"class":4482},"# SQL: SELECT my_custom_db_function(users.id) FROM users\n",[5430,16471,16473],{"id":16472},"найпопулярніші-стандартні-функції-субд","Найпопулярніші стандартні функції СУБД",[9975,16475,16476,16506,16535,16572,16605],{},[9978,16477,16479,16486],{"name":15980,"type":16478},"SQL Function",[3385,16480,16481,16482,16485],{},"Агрегатна функція ",[3412,16483,16484],{},"COUNT()",". Рахує кількість записів або не-NULL значень у стовпці.",[3585,16487,16489],{"className":4445,"code":16488,"language":4448,"meta":3590,"style":3590},"# Рахуємо загальну кількість користувачів\nstmt = select(func.count(User.id))\n# SQL: SELECT count(users.id) AS count_1 FROM users\n",[3412,16490,16491,16496,16501],{"__ignoreMap":3590},[3594,16492,16493],{"class":3596,"line":3597},[3594,16494,16495],{"class":4482},"# Рахуємо загальну кількість користувачів\n",[3594,16497,16498],{"class":3596,"line":3603},[3594,16499,16500],{"class":4459},"stmt = select(func.count(User.id))\n",[3594,16502,16503],{"class":3596,"line":3609},[3594,16504,16505],{"class":4482},"# SQL: SELECT count(users.id) AS count_1 FROM users\n",[9978,16507,16509,16515],{"name":16508,"type":16478},"func.now() \u002F func.current_timestamp()",[3385,16510,16511,16512,3465],{},"Повертає поточну дату та час транзакції. Зазвичай використовується як дефолтне значення для дат створення запису (",[3412,16513,16514],{},"server_default=func.now()",[3585,16516,16518],{"className":4445,"code":16517,"language":4448,"meta":3590,"style":3590},"# Отримання поточного часу з серверу БД\nstmt = select(func.now())\n# SQL: SELECT now() AS now_1\n",[3412,16519,16520,16525,16530],{"__ignoreMap":3590},[3594,16521,16522],{"class":3596,"line":3597},[3594,16523,16524],{"class":4482},"# Отримання поточного часу з серверу БД\n",[3594,16526,16527],{"class":3596,"line":3603},[3594,16528,16529],{"class":4459},"stmt = select(func.now())\n",[3594,16531,16532],{"class":3596,"line":3609},[3594,16533,16534],{"class":4482},"# SQL: SELECT now() AS now_1\n",[9978,16536,16538,16547],{"name":16537,"type":16478},"func.coalesce()",[3385,16539,16540,16541,16544,16545,3439],{},"Еквівалент SQL-функції ",[3412,16542,16543],{},"COALESCE(...)",". Приймає кілька аргументів і повертає перший з них, який не є ",[3412,16546,11211],{},[3585,16548,16550],{"className":4445,"code":16549,"language":4448,"meta":3590,"style":3590},"# Якщо bio порожнє (NULL), повернути рядок за замовчуванням\nstmt = select(func.coalesce(User.bio, \"Біографія відсутня\"))\n# SQL: SELECT coalesce(users.bio, 'Біографія відсутня') FROM users\n",[3412,16551,16552,16557,16567],{"__ignoreMap":3590},[3594,16553,16554],{"class":3596,"line":3597},[3594,16555,16556],{"class":4482},"# Якщо bio порожнє (NULL), повернути рядок за замовчуванням\n",[3594,16558,16559,16562,16565],{"class":3596,"line":3603},[3594,16560,16561],{"class":4459},"stmt = select(func.coalesce(User.bio, ",[3594,16563,16564],{"class":4500},"\"Біографія відсутня\"",[3594,16566,7851],{"class":4459},[3594,16568,16569],{"class":3596,"line":3609},[3594,16570,16571],{"class":4482},"# SQL: SELECT coalesce(users.bio, 'Біографія відсутня') FROM users\n",[9978,16573,16575,16580],{"name":16574,"type":16478},"func.lower() \u002F func.upper()",[3385,16576,16577,16578,3465],{},"Переведення тексту в нижній або верхній регістр. Часто використовується для валідації або пошуку без урахування регістру (якщо СУБД не підтримує ",[3412,16579,15678],{},[3585,16581,16583],{"className":4445,"code":16582,"language":4448,"meta":3590,"style":3590},"# Порівняння email у нижньому регістрі\nstmt = select(User).where(func.lower(User.email) == \"user@example.com\")\n# SQL: SELECT ... WHERE lower(users.email) = 'user@example.com'\n",[3412,16584,16585,16590,16600],{"__ignoreMap":3590},[3594,16586,16587],{"class":3596,"line":3597},[3594,16588,16589],{"class":4482},"# Порівняння email у нижньому регістрі\n",[3594,16591,16592,16595,16598],{"class":3596,"line":3603},[3594,16593,16594],{"class":4459},"stmt = select(User).where(func.lower(User.email) == ",[3594,16596,16597],{"class":4500},"\"user@example.com\"",[3594,16599,4558],{"class":4459},[3594,16601,16602],{"class":3596,"line":3609},[3594,16603,16604],{"class":4482},"# SQL: SELECT ... WHERE lower(users.email) = 'user@example.com'\n",[9978,16606,16608,16611],{"name":16607,"type":16478},"func.concat()",[3385,16609,16610],{},"Конкатенація (об'єднання) рядків.",[3585,16612,16614],{"className":4445,"code":16613,"language":4448,"meta":3590,"style":3590},"# Об'єднання імені та прізвища через пробіл\nstmt = select(func.concat(User.first_name, \" \", User.last_name))\n# SQL: SELECT concat(users.first_name, ' ', users.last_name) FROM users\n",[3412,16615,16616,16621,16632],{"__ignoreMap":3590},[3594,16617,16618],{"class":3596,"line":3597},[3594,16619,16620],{"class":4482},"# Об'єднання імені та прізвища через пробіл\n",[3594,16622,16623,16626,16629],{"class":3596,"line":3603},[3594,16624,16625],{"class":4459},"stmt = select(func.concat(User.first_name, ",[3594,16627,16628],{"class":4500},"\" \"",[3594,16630,16631],{"class":4459},", User.last_name))\n",[3594,16633,16634],{"class":3596,"line":3609},[3594,16635,16636],{"class":4482},"# SQL: SELECT concat(users.first_name, ' ', users.last_name) FROM users\n",[5430,16638,16640],{"id":16639},"специфічні-функції-субд-на-прикладі-postgresql","Специфічні функції СУБД (на прикладі PostgreSQL)",[3385,16642,6339,16643,16645],{},[3412,16644,16394],{}," динамічний, ви можете викликати специфічні для PostgreSQL функції:",[3585,16647,16649],{"className":4445,"code":16648,"language":4448,"meta":3590,"style":3590},"# date_trunc — округлення дати до дня (прибирає години\u002Fхвилини)\nstmt = select(func.date_trunc(\"day\", User.created_at))\n# SQL: SELECT date_trunc('day', users.created_at) FROM users\n\n# age — розрахунок віку на основі дати народження\nstmt = select(func.age(User.birth_date))\n# SQL: SELECT age(users.birth_date) FROM users\n",[3412,16650,16651,16656,16667,16672,16676,16681,16686],{"__ignoreMap":3590},[3594,16652,16653],{"class":3596,"line":3597},[3594,16654,16655],{"class":4482},"# date_trunc — округлення дати до дня (прибирає години\u002Fхвилини)\n",[3594,16657,16658,16661,16664],{"class":3596,"line":3603},[3594,16659,16660],{"class":4459},"stmt = select(func.date_trunc(",[3594,16662,16663],{"class":4500},"\"day\"",[3594,16665,16666],{"class":4459},", User.created_at))\n",[3594,16668,16669],{"class":3596,"line":3609},[3594,16670,16671],{"class":4482},"# SQL: SELECT date_trunc('day', users.created_at) FROM users\n",[3594,16673,16674],{"class":3596,"line":3615},[3594,16675,3631],{"emptyLinePlaceholder":3630},[3594,16677,16678],{"class":3596,"line":3621},[3594,16679,16680],{"class":4482},"# age — розрахунок віку на основі дати народження\n",[3594,16682,16683],{"class":3596,"line":3627},[3594,16684,16685],{"class":4459},"stmt = select(func.age(User.birth_date))\n",[3594,16687,16688],{"class":3596,"line":3634},[3594,16689,16690],{"class":4482},"# SQL: SELECT age(users.birth_date) FROM users\n",[5430,16692,16694,16695,3544],{"id":16693},"явне-зазначення-типу-повернення-type_","Явне зазначення типу повернення (",[3412,16696,12342],{},[3385,16698,16699,16700,16702,16703,3465],{},"Зазвичай SQLAlchemy намагається автоматично визначити тип даних, який поверне функція (наприклад, ",[3412,16701,15980],{}," поверне ціле число ",[3412,16704,9809],{},[3385,16706,16707,16708,16711,16712,3465],{},"Проте для специфічних або кастомних СУБД-функцій SQLAlchemy не знає заздалегідь тип повернення. Через це вона не зможе виконати автоматичну конвертацію результату в об'єкти Python (наприклад, перетворити результат ",[3412,16709,16710],{},"date_trunc"," у Python-об'єкт ",[3412,16713,16714],{},"datetime.date",[3385,16716,16717,16718,4442],{},"Для вирішення цього використовується параметр ",[3389,16719,16720],{},[3412,16721,12342],{},[3585,16723,16725],{"className":4445,"code":16724,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import Date\n\n# Явно вказуємо, що функція date_trunc повертає дату\nstmt = select(func.date_trunc(\"day\", User.created_at, type_=Date))\n\n# Тепер результат буде автоматично розпарсено у datetime.date в Python:\nday = session.execute(stmt).scalar_one()  # поверне datetime.date(2026, 7, 4)\n",[3412,16726,16727,16738,16742,16747,16761,16765,16770],{"__ignoreMap":3590},[3594,16728,16729,16731,16733,16735],{"class":3596,"line":3597},[3594,16730,4465],{"class":4455},[3594,16732,5996],{"class":4459},[3594,16734,4456],{"class":4455},[3594,16736,16737],{"class":4459}," Date\n",[3594,16739,16740],{"class":3596,"line":3603},[3594,16741,3631],{"emptyLinePlaceholder":3630},[3594,16743,16744],{"class":3596,"line":3609},[3594,16745,16746],{"class":4482},"# Явно вказуємо, що функція date_trunc повертає дату\n",[3594,16748,16749,16751,16753,16756,16758],{"class":3596,"line":3615},[3594,16750,16660],{"class":4459},[3594,16752,16663],{"class":4500},[3594,16754,16755],{"class":4459},", User.created_at, ",[3594,16757,12342],{"class":4493},[3594,16759,16760],{"class":4459},"=Date))\n",[3594,16762,16763],{"class":3596,"line":3621},[3594,16764,3631],{"emptyLinePlaceholder":3630},[3594,16766,16767],{"class":3596,"line":3627},[3594,16768,16769],{"class":4482},"# Тепер результат буде автоматично розпарсено у datetime.date в Python:\n",[3594,16771,16772,16775],{"class":3596,"line":3634},[3594,16773,16774],{"class":4459},"day = session.execute(stmt).scalar_one()  ",[3594,16776,16777],{"class":4482},"# поверне datetime.date(2026, 7, 4)\n",[4433,16779,16781],{"id":16780},"виконання-та-отримання-результатів","Виконання та отримання результатів",[3385,16783,16784,16785,16787,16788,4442],{},"Для виконання запиту об'єкт ",[3412,16786,14551],{}," передається в ",[3412,16789,16790],{},"session.execute()",[3585,16792,16794],{"className":4445,"code":16793,"language":4448,"meta":3590,"style":3590},"result = session.execute(stmt)\n",[3412,16795,16796],{"__ignoreMap":3590},[3594,16797,16798],{"class":3596,"line":3597},[3594,16799,16793],{"class":4459},[3385,16801,16802,16803,16806,16807,16812,16813,16818,16819,16822,16823,16826],{},"Метод ",[3412,16804,16805],{},"execute()"," повертає об'єкт ",[3389,16808,16809],{},[3412,16810,16811],{},"Result",". Це ітератор, який повертає рядки типу ",[3389,16814,16815],{},[3412,16816,16817],{},"Row"," (схожі на ",[3412,16820,16821],{},"NamedTuple"," в Python). Оскільки при виборі повних ORM-моделей кожен рядок повертається як кортеж з одним елементом (наприклад, ",[3412,16824,16825],{},"(User,)","), SQLAlchemy надає допоміжні методи для розпакування цих даних:",[9975,16828,16829,16962,17013,17048,17089,17125,17161],{},[9978,16830,16833,16839,16848,16885],{"name":16831,"type":16832},"scalars()","ScalarResult",[3385,16834,16835,16836,16838],{},"Розпаковує одноелементні кортежі рядків результату, повертаючи чисті об'єкти замість об'єктів ",[3412,16837,16817],{}," (кортежів).",[3385,16840,16841,16844,16845,16847],{},[3389,16842,16843],{},"Чому це потрібно?","\nБаза даних завжди повертає результати у вигляді таблиці (матриці). Навіть коли ви вибираєте всю модель ",[3412,16846,14547],{},", SQLAlchemy отримує з бази набір рядків і загортає кожний результат у кортеж з одним елементом:",[3510,16849,16850,16870],{},[3455,16851,16852,16858,16859,16862,16863,16866,16867,3439],{},[3389,16853,16854,16855],{},"Без ",[3412,16856,16857],{},".scalars()"," результат виконання ",[3412,16860,16861],{},"session.execute(select(User)).all()"," виглядає так:\n",[3412,16864,16865],{},"[(User_1,), (User_2,), (User_3,)]"," — список кортежів довжиною 1. Щоб отримати користувача, довелося б писати ",[3412,16868,16869],{},"row[0]",[3455,16871,16872,16877,16878,16862,16881,16884],{},[3389,16873,16874,16875],{},"З ",[3412,16876,16857],{}," SQLAlchemy автоматично витягує перший елемент із кожного кортежу. Результат виконання ",[3412,16879,16880],{},"session.execute(select(User)).scalars().all()",[3412,16882,16883],{},"[User_1, User_2, User_3]"," — чистий список об'єктів.",[3585,16886,16888],{"className":4445,"code":16887,"language":4448,"meta":3590,"style":3590},"# Без scalars():\nrows = session.execute(select(User)).all()\nfor row in rows:\n    user = row[0]  # доводиться вручну брати перший елемент кортежу\n    print(user.username)\n\n# З scalars():\nusers = session.execute(select(User)).scalars().all()\nfor user in users:\n    print(user.username)  # працюємо безпосередньо з об'єктом User\n",[3412,16889,16890,16895,16900,16910,16923,16929,16933,16938,16943,16953],{"__ignoreMap":3590},[3594,16891,16892],{"class":3596,"line":3597},[3594,16893,16894],{"class":4482},"# Без scalars():\n",[3594,16896,16897],{"class":3596,"line":3603},[3594,16898,16899],{"class":4459},"rows = session.execute(select(User)).all()\n",[3594,16901,16902,16904,16906,16908],{"class":3596,"line":3609},[3594,16903,13566],{"class":4455},[3594,16905,4679],{"class":4459},[3594,16907,4682],{"class":4455},[3594,16909,4685],{"class":4459},[3594,16911,16912,16915,16917,16920],{"class":3596,"line":3615},[3594,16913,16914],{"class":4459},"    user = row[",[3594,16916,10061],{"class":4514},[3594,16918,16919],{"class":4459},"]  ",[3594,16921,16922],{"class":4482},"# доводиться вручну брати перший елемент кортежу\n",[3594,16924,16925,16927],{"class":3596,"line":3621},[3594,16926,8171],{"class":4690},[3594,16928,8320],{"class":4459},[3594,16930,16931],{"class":3596,"line":3627},[3594,16932,3631],{"emptyLinePlaceholder":3630},[3594,16934,16935],{"class":3596,"line":3634},[3594,16936,16937],{"class":4482},"# З scalars():\n",[3594,16939,16940],{"class":3596,"line":3640},[3594,16941,16942],{"class":4459},"users = session.execute(select(User)).scalars().all()\n",[3594,16944,16945,16947,16949,16951],{"class":3596,"line":3646},[3594,16946,13566],{"class":4455},[3594,16948,13569],{"class":4459},[3594,16950,4682],{"class":4455},[3594,16952,13574],{"class":4459},[3594,16954,16955,16957,16959],{"class":3596,"line":3652},[3594,16956,8171],{"class":4690},[3594,16958,8505],{"class":4459},[3594,16960,16961],{"class":4482},"# працюємо безпосередньо з об'єктом User\n",[9978,16963,16966,16975],{"name":16964,"type":16965},"all()","list[Row] \u002F list[T]",[3385,16967,16968,16969,16971,16972,3465],{},"Вичитує всі рядки результату у список і закриває курсор. Якщо викликано після ",[3412,16970,16857],{},", повертає список чистих об'єктів (наприклад, ",[3412,16973,16974],{},"list[User]",[3585,16976,16978],{"className":4445,"code":16977,"language":4448,"meta":3590,"style":3590},"# 1. Без scalars() — список об'єктів Row ( NamedTuple )\nrows = session.execute(select(User.id, User.username)).all()\n# rows: list[Row(id=1, username=\"arakviel\"), ...]\n\n# 2. Зі scalars() — список чистих об'єктів User\nusers = session.execute(select(User)).scalars().all()\n# users: list[User]\n",[3412,16979,16980,16985,16990,16995,16999,17004,17008],{"__ignoreMap":3590},[3594,16981,16982],{"class":3596,"line":3597},[3594,16983,16984],{"class":4482},"# 1. Без scalars() — список об'єктів Row ( NamedTuple )\n",[3594,16986,16987],{"class":3596,"line":3603},[3594,16988,16989],{"class":4459},"rows = session.execute(select(User.id, User.username)).all()\n",[3594,16991,16992],{"class":3596,"line":3609},[3594,16993,16994],{"class":4482},"# rows: list[Row(id=1, username=\"arakviel\"), ...]\n",[3594,16996,16997],{"class":3596,"line":3615},[3594,16998,3631],{"emptyLinePlaceholder":3630},[3594,17000,17001],{"class":3596,"line":3621},[3594,17002,17003],{"class":4482},"# 2. Зі scalars() — список чистих об'єктів User\n",[3594,17005,17006],{"class":3596,"line":3627},[3594,17007,16942],{"class":4459},[3594,17009,17010],{"class":3596,"line":3634},[3594,17011,17012],{"class":4482},"# users: list[User]\n",[9978,17014,17017,17023],{"name":17015,"type":17016},"first()","Row \u002F T \u002F None",[3385,17018,17019,17020,17022],{},"Повертає перший рядок результату або ",[3412,17021,9644],{},", якщо результат порожній. Закриває з'єднання.",[3585,17024,17026],{"className":4445,"code":17025,"language":4448,"meta":3590,"style":3590},"# Отримання першого знайденого користувача або None\nuser = session.execute(\n    select(User).order_by(User.created_at.asc())\n).scalars().first()\n",[3412,17027,17028,17033,17038,17043],{"__ignoreMap":3590},[3594,17029,17030],{"class":3596,"line":3597},[3594,17031,17032],{"class":4482},"# Отримання першого знайденого користувача або None\n",[3594,17034,17035],{"class":3596,"line":3603},[3594,17036,17037],{"class":4459},"user = session.execute(\n",[3594,17039,17040],{"class":3596,"line":3609},[3594,17041,17042],{"class":4459},"    select(User).order_by(User.created_at.asc())\n",[3594,17044,17045],{"class":3596,"line":3615},[3594,17046,17047],{"class":4459},").scalars().first()\n",[9978,17049,17052,17061],{"name":17050,"type":17051},"one()","Row \u002F T",[3385,17053,17054,17055,3461,17058,3465],{},"Повертає рівно один рядок. Якщо база даних повернула 0 рядків або більше 1, кидає виняток (",[3412,17056,17057],{},"NoResultFound",[3412,17059,17060],{},"MultipleResultsFound",[3585,17062,17064],{"className":4445,"code":17063,"language":4448,"meta":3590,"style":3590},"# Очікуємо отримати рівно одного користувача за унікальним username\nstmt = select(User).where(User.username == \"admin\")\nadmin = session.execute(stmt).scalars().one()\n# Увага: якщо admin немає — кидає NoResultFound\n",[3412,17065,17066,17071,17079,17084],{"__ignoreMap":3590},[3594,17067,17068],{"class":3596,"line":3597},[3594,17069,17070],{"class":4482},"# Очікуємо отримати рівно одного користувача за унікальним username\n",[3594,17072,17073,17075,17077],{"class":3596,"line":3603},[3594,17074,14592],{"class":4459},[3594,17076,15824],{"class":4500},[3594,17078,4558],{"class":4459},[3594,17080,17081],{"class":3596,"line":3609},[3594,17082,17083],{"class":4459},"admin = session.execute(stmt).scalars().one()\n",[3594,17085,17086],{"class":3596,"line":3615},[3594,17087,17088],{"class":4482},"# Увага: якщо admin немає — кидає NoResultFound\n",[9978,17090,17092,17100],{"name":17091,"type":17016},"one_or_none()",[3385,17093,17094,17095,17097,17098,3439],{},"Повертає один рядок або ",[3412,17096,9644],{},", якщо записів немає. Якщо знайдено більше 1 запису, кидає виняток ",[3412,17099,17060],{},[3585,17101,17103],{"className":4445,"code":17102,"language":4448,"meta":3590,"style":3590},"# Безпечний пошук за унікальним email (поверне об'єкт або None)\nstmt = select(User).where(User.email == \"notfound@example.com\")\nuser = session.execute(stmt).scalars().one_or_none()\n",[3412,17104,17105,17110,17120],{"__ignoreMap":3590},[3594,17106,17107],{"class":3596,"line":3597},[3594,17108,17109],{"class":4482},"# Безпечний пошук за унікальним email (поверне об'єкт або None)\n",[3594,17111,17112,17115,17118],{"class":3596,"line":3603},[3594,17113,17114],{"class":4459},"stmt = select(User).where(User.email == ",[3594,17116,17117],{"class":4500},"\"notfound@example.com\"",[3594,17119,4558],{"class":4459},[3594,17121,17122],{"class":3596,"line":3609},[3594,17123,17124],{"class":4459},"user = session.execute(stmt).scalars().one_or_none()\n",[9978,17126,17128,17137],{"name":17127,"type":12351},"scalar_one()",[3385,17129,17130,17131,17134,17135,3439],{},"Швидкий аналог ",[3412,17132,17133],{},".scalars().one()",". Повертає значення першого стовпця першого рядка. Корисний для агрегацій типу ",[3412,17136,16400],{},[3585,17138,17140],{"className":4445,"code":17139,"language":4448,"meta":3590,"style":3590},"# Отримання кількості користувачів (повертає одне число int)\ncount = session.execute(\n    select(func.count(User.id))\n).scalar_one()\n",[3412,17141,17142,17147,17152,17157],{"__ignoreMap":3590},[3594,17143,17144],{"class":3596,"line":3597},[3594,17145,17146],{"class":4482},"# Отримання кількості користувачів (повертає одне число int)\n",[3594,17148,17149],{"class":3596,"line":3603},[3594,17150,17151],{"class":4459},"count = session.execute(\n",[3594,17153,17154],{"class":3596,"line":3609},[3594,17155,17156],{"class":4459},"    select(func.count(User.id))\n",[3594,17158,17159],{"class":3596,"line":3615},[3594,17160,13873],{"class":4459},[9978,17162,17165,17173],{"name":17163,"type":17164},"scalar_one_or_none()","Any | None",[3385,17166,17130,17167,17170,17171,3439],{},[3412,17168,17169],{},".scalars().one_or_none()",". Повертає одне скалярне значення або ",[3412,17172,9644],{},[3585,17174,17176],{"className":4445,"code":17175,"language":4448,"meta":3590,"style":3590},"# Отримання email користувача за ID (поверне рядок або None, якщо ID не існує)\nstmt = select(User.email).where(User.id == 999)\nemail = session.execute(stmt).scalar_one_or_none()\n",[3412,17177,17178,17183,17193],{"__ignoreMap":3590},[3594,17179,17180],{"class":3596,"line":3597},[3594,17181,17182],{"class":4482},"# Отримання email користувача за ID (поверне рядок або None, якщо ID не існує)\n",[3594,17184,17185,17188,17191],{"class":3596,"line":3603},[3594,17186,17187],{"class":4459},"stmt = select(User.email).where(User.id == ",[3594,17189,17190],{"class":4514},"999",[3594,17192,4558],{"class":4459},[3594,17194,17195],{"class":3596,"line":3609},[3594,17196,17197],{"class":4459},"email = session.execute(stmt).scalar_one_or_none()\n",[4433,17199,17201],{"id":17200},"практичні-приклади-з-виводом-результатів","Практичні приклади з виводом результатів",[5430,17203,17205],{"id":17204},"приклад-1-отримання-списку-активних-користувачів-orm-стиль","Приклад 1: Отримання списку активних користувачів (ORM-стиль)",[3585,17207,17209],{"className":4445,"code":17208,"language":4448,"meta":3590,"style":3590},"stmt = select(User).where(User.is_active == True).order_by(User.username.asc())\n\n# Компіляція SQL:\n# SELECT users.id, users.username, users.email, users.is_active, users.created_at\n# FROM users\n# WHERE users.is_active = true ORDER BY users.username ASC\n\nusers = session.execute(stmt).scalars().all()\n\nfor user in users:\n    print(f\"User: {user.username} (ID: {user.id})\")\n",[3412,17210,17211,17221,17225,17230,17235,17240,17245,17249,17254,17258,17268],{"__ignoreMap":3590},[3594,17212,17213,17216,17218],{"class":3596,"line":3597},[3594,17214,17215],{"class":4459},"stmt = select(User).where(User.is_active == ",[3594,17217,4641],{"class":4622},[3594,17219,17220],{"class":4459},").order_by(User.username.asc())\n",[3594,17222,17223],{"class":3596,"line":3603},[3594,17224,3631],{"emptyLinePlaceholder":3630},[3594,17226,17227],{"class":3596,"line":3609},[3594,17228,17229],{"class":4482},"# Компіляція SQL:\n",[3594,17231,17232],{"class":3596,"line":3615},[3594,17233,17234],{"class":4482},"# SELECT users.id, users.username, users.email, users.is_active, users.created_at\n",[3594,17236,17237],{"class":3596,"line":3621},[3594,17238,17239],{"class":4482},"# FROM users\n",[3594,17241,17242],{"class":3596,"line":3627},[3594,17243,17244],{"class":4482},"# WHERE users.is_active = true ORDER BY users.username ASC\n",[3594,17246,17247],{"class":3596,"line":3634},[3594,17248,3631],{"emptyLinePlaceholder":3630},[3594,17250,17251],{"class":3596,"line":3640},[3594,17252,17253],{"class":4459},"users = session.execute(stmt).scalars().all()\n",[3594,17255,17256],{"class":3596,"line":3646},[3594,17257,3631],{"emptyLinePlaceholder":3630},[3594,17259,17260,17262,17264,17266],{"class":3596,"line":3652},[3594,17261,13566],{"class":4455},[3594,17263,13569],{"class":4459},[3594,17265,4682],{"class":4455},[3594,17267,13574],{"class":4459},[3594,17269,17270,17272,17274,17276,17279,17281,17283,17285,17288,17290,17293,17295,17298],{"class":3596,"line":3658},[3594,17271,8171],{"class":4690},[3594,17273,7763],{"class":4459},[3594,17275,8617],{"class":4622},[3594,17277,17278],{"class":4500},"\"User: ",[3594,17280,8623],{"class":4622},[3594,17282,3553],{"class":4459},[3594,17284,8634],{"class":4622},[3594,17286,17287],{"class":4500}," (ID: ",[3594,17289,8623],{"class":4622},[3594,17291,17292],{"class":4459},"user.id",[3594,17294,8634],{"class":4622},[3594,17296,17297],{"class":4500},")\"",[3594,17299,4558],{"class":4459},[3385,17301,17302],{},[3389,17303,17304],{},"Вивід у консоль:",[3585,17306,17311],{"className":17307,"code":17309,"language":17310,"meta":3590},[17308],"language-text","User: alice (ID: 2)\nUser: arakviel (ID: 1)\nUser: bob (ID: 3)\n","text",[3412,17312,17309],{"__ignoreMap":3590},[5430,17314,17316],{"id":17315},"приклад-2-вибірка-конкретних-стовпців-tuple-стиль","Приклад 2: Вибірка конкретних стовпців (Tuple-стиль)",[3385,17318,17319],{},"Коли нам потрібні лише окремі поля, завантажувати повні ORM-об'єкти неефективно (вони займають пам'ять та відстежуються сесією). Краще вибрати лише потрібні стовпці:",[3585,17321,17323],{"className":4445,"code":17322,"language":4448,"meta":3590,"style":3590},"stmt = select(User.id, User.username).where(User.id > 1)\n\n# Компіляція SQL:\n# SELECT users.id, users.username FROM users WHERE users.id > 1\n\nrows = session.execute(stmt).all()\n# rows містить список об'єктів Row, які поводяться як кортежі та іменовані об'єкти\n\nfor row in rows:\n    # Доступ можливий двома шляхами:\n    print(f\"ID: {row[0]}, Username: {row[1]}\")          # за індексом\n    print(f\"ID: {row.id}, Username: {row.username}\")    # за атрибутом\n",[3412,17324,17325,17334,17338,17342,17347,17351,17356,17361,17365,17375,17380,17424],{"__ignoreMap":3590},[3594,17326,17327,17330,17332],{"class":3596,"line":3597},[3594,17328,17329],{"class":4459},"stmt = select(User.id, User.username).where(User.id > ",[3594,17331,6486],{"class":4514},[3594,17333,4558],{"class":4459},[3594,17335,17336],{"class":3596,"line":3603},[3594,17337,3631],{"emptyLinePlaceholder":3630},[3594,17339,17340],{"class":3596,"line":3609},[3594,17341,17229],{"class":4482},[3594,17343,17344],{"class":3596,"line":3615},[3594,17345,17346],{"class":4482},"# SELECT users.id, users.username FROM users WHERE users.id > 1\n",[3594,17348,17349],{"class":3596,"line":3621},[3594,17350,3631],{"emptyLinePlaceholder":3630},[3594,17352,17353],{"class":3596,"line":3627},[3594,17354,17355],{"class":4459},"rows = session.execute(stmt).all()\n",[3594,17357,17358],{"class":3596,"line":3634},[3594,17359,17360],{"class":4482},"# rows містить список об'єктів Row, які поводяться як кортежі та іменовані об'єкти\n",[3594,17362,17363],{"class":3596,"line":3640},[3594,17364,3631],{"emptyLinePlaceholder":3630},[3594,17366,17367,17369,17371,17373],{"class":3596,"line":3646},[3594,17368,13566],{"class":4455},[3594,17370,4679],{"class":4459},[3594,17372,4682],{"class":4455},[3594,17374,4685],{"class":4459},[3594,17376,17377],{"class":3596,"line":3652},[3594,17378,17379],{"class":4482},"    # Доступ можливий двома шляхами:\n",[3594,17381,17382,17384,17386,17388,17391,17393,17396,17398,17401,17403,17406,17408,17410,17412,17414,17416,17418,17421],{"class":3596,"line":3658},[3594,17383,8171],{"class":4690},[3594,17385,7763],{"class":4459},[3594,17387,8617],{"class":4622},[3594,17389,17390],{"class":4500},"\"ID: ",[3594,17392,8623],{"class":4622},[3594,17394,17395],{"class":4459},"row[",[3594,17397,10061],{"class":4514},[3594,17399,17400],{"class":4459},"]",[3594,17402,8634],{"class":4622},[3594,17404,17405],{"class":4500},", Username: ",[3594,17407,8623],{"class":4622},[3594,17409,17395],{"class":4459},[3594,17411,6486],{"class":4514},[3594,17413,17400],{"class":4459},[3594,17415,8634],{"class":4622},[3594,17417,4631],{"class":4500},[3594,17419,17420],{"class":4459},")          ",[3594,17422,17423],{"class":4482},"# за індексом\n",[3594,17425,17426,17428,17430,17432,17434,17436,17439,17441,17443,17445,17448,17450,17452,17455],{"class":3596,"line":3664},[3594,17427,8171],{"class":4690},[3594,17429,7763],{"class":4459},[3594,17431,8617],{"class":4622},[3594,17433,17390],{"class":4500},[3594,17435,8623],{"class":4622},[3594,17437,17438],{"class":4459},"row.id",[3594,17440,8634],{"class":4622},[3594,17442,17405],{"class":4500},[3594,17444,8623],{"class":4622},[3594,17446,17447],{"class":4459},"row.username",[3594,17449,8634],{"class":4622},[3594,17451,4631],{"class":4500},[3594,17453,17454],{"class":4459},")    ",[3594,17456,17457],{"class":4482},"# за атрибутом\n",[3385,17459,17460],{},[3389,17461,17304],{},[3585,17463,17466],{"className":17464,"code":17465,"language":17310,"meta":3590},[17308],"ID: 2, Username: alice\nID: 3, Username: bob\n",[3412,17467,17465],{"__ignoreMap":3590},[5430,17469,17471],{"id":17470},"приклад-3-складні-логічні-умови-and-or-not","Приклад 3: Складні логічні умови (AND, OR, NOT)",[3385,17473,17474,17475,17477,17478,17480,17481,3435,17484,17487,17488,3435,17491,4442],{},"Умови ",[3412,17476,14771],{}," поєднуються автоматично при передачі кількох аргументів у ",[3412,17479,14753],{},". Для ",[3412,17482,17483],{},"OR",[3412,17485,17486],{},"NOT"," використовуються спеціальні функції ",[3412,17489,17490],{},"or_",[3412,17492,17493],{},"not_",[3585,17495,17497],{"className":4445,"code":17496,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import or_, not_\n\nstmt = (\n    select(User)\n    .where(\n        User.is_active == True,\n        or_(\n            User.email.like(\"%@gmail.com\"),\n            not_(User.username == \"admin\")\n        )\n    )\n)\n\n# Компіляція SQL:\n# SELECT users.id, users.username, users.email ...\n# FROM users\n# WHERE users.is_active = true AND (users.email LIKE '%@gmail.com' OR users.username != 'admin')\n",[3412,17498,17499,17510,17514,17518,17522,17527,17536,17541,17551,17560,17564,17568,17572,17576,17580,17585,17589],{"__ignoreMap":3590},[3594,17500,17501,17503,17505,17507],{"class":3596,"line":3597},[3594,17502,4465],{"class":4455},[3594,17504,5996],{"class":4459},[3594,17506,4456],{"class":4455},[3594,17508,17509],{"class":4459}," or_, not_\n",[3594,17511,17512],{"class":3596,"line":3603},[3594,17513,3631],{"emptyLinePlaceholder":3630},[3594,17515,17516],{"class":3596,"line":3609},[3594,17517,14267],{"class":4459},[3594,17519,17520],{"class":3596,"line":3615},[3594,17521,13720],{"class":4459},[3594,17523,17524],{"class":3596,"line":3621},[3594,17525,17526],{"class":4459},"    .where(\n",[3594,17528,17529,17532,17534],{"class":3596,"line":3627},[3594,17530,17531],{"class":4459},"        User.is_active == ",[3594,17533,4641],{"class":4622},[3594,17535,4504],{"class":4459},[3594,17537,17538],{"class":3596,"line":3634},[3594,17539,17540],{"class":4459},"        or_(\n",[3594,17542,17543,17546,17549],{"class":3596,"line":3640},[3594,17544,17545],{"class":4459},"            User.email.like(",[3594,17547,17548],{"class":4500},"\"%@gmail.com\"",[3594,17550,4753],{"class":4459},[3594,17552,17553,17556,17558],{"class":3596,"line":3646},[3594,17554,17555],{"class":4459},"            not_(User.username == ",[3594,17557,15824],{"class":4500},[3594,17559,4558],{"class":4459},[3594,17561,17562],{"class":3596,"line":3652},[3594,17563,4657],{"class":4459},[3594,17565,17566],{"class":3596,"line":3658},[3594,17567,4935],{"class":4459},[3594,17569,17570],{"class":3596,"line":3664},[3594,17571,4558],{"class":4459},[3594,17573,17574],{"class":3596,"line":3670},[3594,17575,3631],{"emptyLinePlaceholder":3630},[3594,17577,17578],{"class":3596,"line":3676},[3594,17579,17229],{"class":4482},[3594,17581,17582],{"class":3596,"line":3681},[3594,17583,17584],{"class":4482},"# SELECT users.id, users.username, users.email ...\n",[3594,17586,17587],{"class":3596,"line":3687},[3594,17588,17239],{"class":4482},[3594,17590,17591],{"class":3596,"line":3693},[3594,17592,17593],{"class":4482},"# WHERE users.is_active = true AND (users.email LIKE '%@gmail.com' OR users.username != 'admin')\n",[5430,17595,17597],{"id":17596},"приклад-4-inner-join-та-агрегація-групування","Приклад 4: INNER JOIN та агрегація (Групування)",[3385,17599,17600],{},"Знайдемо кількість постів для кожного користувача, що має хоча б один пост, та відсортуємо за спаданням:",[3585,17602,17604],{"className":4445,"code":17603,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import func\n\nstmt = (\n    select(User.username, func.count(Post.id).label(\"total_posts\"))\n    .join(Post, User.id == Post.author_id)\n    .group_by(User.username)\n    .having(func.count(Post.id) > 0)\n    .order_by(func.count(Post.id).desc())\n)\n\n# Компіляція SQL:\n# SELECT users.username, count(posts.id) AS total_posts\n# FROM users JOIN posts ON users.id = posts.author_id\n# GROUP BY users.username HAVING count(posts.id) > 0\n# ORDER BY count(posts.id) DESC\n\nresults = session.execute(stmt).all()\nfor row in results:\n    print(f\"{row.username}: {row.total_posts} posts\")\n",[3412,17605,17606,17617,17621,17625,17635,17640,17645,17654,17659,17663,17667,17671,17676,17681,17686,17691,17695,17700,17711],{"__ignoreMap":3590},[3594,17607,17608,17610,17612,17614],{"class":3596,"line":3597},[3594,17609,4465],{"class":4455},[3594,17611,5996],{"class":4459},[3594,17613,4456],{"class":4455},[3594,17615,17616],{"class":4459}," func\n",[3594,17618,17619],{"class":3596,"line":3603},[3594,17620,3631],{"emptyLinePlaceholder":3630},[3594,17622,17623],{"class":3596,"line":3609},[3594,17624,14267],{"class":4459},[3594,17626,17627,17630,17633],{"class":3596,"line":3615},[3594,17628,17629],{"class":4459},"    select(User.username, func.count(Post.id).label(",[3594,17631,17632],{"class":4500},"\"total_posts\"",[3594,17634,7851],{"class":4459},[3594,17636,17637],{"class":3596,"line":3621},[3594,17638,17639],{"class":4459},"    .join(Post, User.id == Post.author_id)\n",[3594,17641,17642],{"class":3596,"line":3627},[3594,17643,17644],{"class":4459},"    .group_by(User.username)\n",[3594,17646,17647,17650,17652],{"class":3596,"line":3634},[3594,17648,17649],{"class":4459},"    .having(func.count(Post.id) > ",[3594,17651,10061],{"class":4514},[3594,17653,4558],{"class":4459},[3594,17655,17656],{"class":3596,"line":3640},[3594,17657,17658],{"class":4459},"    .order_by(func.count(Post.id).desc())\n",[3594,17660,17661],{"class":3596,"line":3646},[3594,17662,4558],{"class":4459},[3594,17664,17665],{"class":3596,"line":3652},[3594,17666,3631],{"emptyLinePlaceholder":3630},[3594,17668,17669],{"class":3596,"line":3658},[3594,17670,17229],{"class":4482},[3594,17672,17673],{"class":3596,"line":3664},[3594,17674,17675],{"class":4482},"# SELECT users.username, count(posts.id) AS total_posts\n",[3594,17677,17678],{"class":3596,"line":3670},[3594,17679,17680],{"class":4482},"# FROM users JOIN posts ON users.id = posts.author_id\n",[3594,17682,17683],{"class":3596,"line":3676},[3594,17684,17685],{"class":4482},"# GROUP BY users.username HAVING count(posts.id) > 0\n",[3594,17687,17688],{"class":3596,"line":3681},[3594,17689,17690],{"class":4482},"# ORDER BY count(posts.id) DESC\n",[3594,17692,17693],{"class":3596,"line":3687},[3594,17694,3631],{"emptyLinePlaceholder":3630},[3594,17696,17697],{"class":3596,"line":3693},[3594,17698,17699],{"class":4459},"results = session.execute(stmt).all()\n",[3594,17701,17702,17704,17706,17708],{"class":3596,"line":3699},[3594,17703,13566],{"class":4455},[3594,17705,4679],{"class":4459},[3594,17707,4682],{"class":4455},[3594,17709,17710],{"class":4459}," results:\n",[3594,17712,17713,17715,17717,17719,17721,17723,17725,17727,17729,17731,17734,17736,17738],{"class":3596,"line":3705},[3594,17714,8171],{"class":4690},[3594,17716,7763],{"class":4459},[3594,17718,8617],{"class":4622},[3594,17720,4631],{"class":4500},[3594,17722,8623],{"class":4622},[3594,17724,17447],{"class":4459},[3594,17726,8634],{"class":4622},[3594,17728,9991],{"class":4500},[3594,17730,8623],{"class":4622},[3594,17732,17733],{"class":4459},"row.total_posts",[3594,17735,8634],{"class":4622},[3594,17737,13615],{"class":4500},[3594,17739,4558],{"class":4459},[3385,17741,17742],{},[3389,17743,17304],{},[3585,17745,17748],{"className":17746,"code":17747,"language":17310,"meta":3590},[17308],"arakviel: 12 posts\nalice: 3 posts\n",[3412,17749,17747],{"__ignoreMap":3590},[3441,17751],{},[3845,17753,17755,17756],{"id":17754},"створення-даних-insert","Створення даних: ",[3412,17757,3895],{},[3385,17759,5980,17760,17762,17763,3465],{},[3412,17761,3895],{}," генерує SQL-запити вставки записів (",[3412,17764,7659],{},[4433,17766,14697],{"id":17767},"сигнатура-1",[3585,17769,17771],{"className":4445,"code":17770,"language":4448,"meta":3590,"style":3590},"def insert(table: _DMLTableArgument) -> Insert\n",[3412,17772,17773],{"__ignoreMap":3590},[3594,17774,17775,17777,17780,17782,17784],{"class":3596,"line":3597},[3594,17776,14707],{"class":4622},[3594,17778,17779],{"class":4690}," insert",[3594,17781,7763],{"class":4459},[3594,17783,3986],{"class":4493},[3594,17785,17786],{"class":4459},": _DMLTableArgument) -> Insert\n",[3385,17788,17789,17790,3439],{},"Аргументом є ORM-клас моделі або об'єкт ",[3412,17791,3882],{},[4433,17793,17795,17796],{"id":17794},"методи-обєкта-insert","Методи об'єкта ",[3412,17797,17798],{},"Insert",[3510,17800,17801,17808],{},[3455,17802,17803,17804,17807],{},"**",[3412,17805,17806],{},".values(\\*args, **kwargs)","** — визначає дані для вставки. Може приймати словник, список словників (для масової вставки) або keyword-аргументи.",[3455,17809,17810,17815,17816,17819],{},[3389,17811,17812],{},[3412,17813,17814],{},".returning(*cols)"," — додає секцію ",[3412,17817,17818],{},"RETURNING"," для отримання згенерованих базою даних значень (наприклад, ID або значень за замовчуванням) в результаті виконання запиту.",[4433,17821,17823],{"id":17822},"orm-vs-core-insertion","ORM vs Core Insertion",[3385,17825,17826],{},"У SQLAlchemy є два шляхи створення записів:",[3503,17828,17829,17853],{},[3506,17830,17833,17839],{"icon":17831,"title":17832},"i-lucide-user-plus","ORM-стиль (Instance-based)",[3385,17834,17835,17836,17838],{},"Ви створюєте екземпляр класу моделі, додаєте його до сесії через ",[3412,17837,4122],{}," та робите коміт.",[3510,17840,17841,17847],{},[3455,17842,17843,17846],{},[3389,17844,17845],{},"Плюси",": Об'єкт автоматично стає частиною Unit of Work, відстежуються його зв'язки, після вставки об'єкт готовий до подальшого використання.",[3455,17848,17849,17852],{},[3389,17850,17851],{},"Мінуси",": Повільний для великої кількості записів (створює накладні витрати на рівні Python-об'єктів).",[3506,17854,17856,17865],{"icon":943,"title":17855},"Core-стиль (Bulk DML)",[3385,17857,17858,17859,17862,17863,3439],{},"Ви викликаєте ",[3412,17860,17861],{},"insert(Model).values(...)"," та виконуєте через ",[3412,17864,16790],{},[3510,17866,17867,17872],{},[3455,17868,17869,17871],{},[3389,17870,17845],{},": Надзвичайно швидкий. Виконує пряму вставку сирих словників даних без створення об'єктів моделей в пам'яті.",[3455,17873,17874,17876],{},[3389,17875,17851],{},": Не відстежує стан об'єктів у сесії, не синхронізує Identity Map автоматично.",[4433,17878,17201],{"id":17879},"практичні-приклади-з-виводом-результатів-1",[5430,17881,17883],{"id":17882},"приклад-1-одиночна-вставка-з-поверненням-згенерованого-id","Приклад 1: Одиночна вставка з поверненням згенерованого ID",[3585,17885,17887],{"className":4445,"code":17886,"language":4448,"meta":3590,"style":3590},"stmt = (\n    insert(User)\n    .values(username=\"new_user\", email=\"new@example.com\")\n    .returning(User.id, User.created_at)\n)\n\n# Компіляція SQL (PostgreSQL):\n# INSERT INTO users (username, email) VALUES ('new_user', 'new@example.com')\n# RETURNING users.id, users.created_at\n\nresult = session.execute(stmt)\nrow = result.fetchone()  # Оскільки ми чекаємо один рядок з RETURNING\n\nprint(f\"Inserted ID: {row.id}\")\nprint(f\"Created At: {row.created_at}\")\nsession.commit()\n",[3412,17888,17889,17893,17898,17920,17925,17929,17933,17938,17943,17948,17952,17956,17964,17968,17989,18011],{"__ignoreMap":3590},[3594,17890,17891],{"class":3596,"line":3597},[3594,17892,14267],{"class":4459},[3594,17894,17895],{"class":3596,"line":3603},[3594,17896,17897],{"class":4459},"    insert(User)\n",[3594,17899,17900,17903,17905,17907,17909,17911,17914,17916,17918],{"class":3596,"line":3609},[3594,17901,17902],{"class":4459},"    .values(",[3594,17904,7736],{"class":4493},[3594,17906,4497],{"class":4459},[3594,17908,4745],{"class":4500},[3594,17910,3488],{"class":4459},[3594,17912,17913],{"class":4493},"email",[3594,17915,4497],{"class":4459},[3594,17917,4750],{"class":4500},[3594,17919,4558],{"class":4459},[3594,17921,17922],{"class":3596,"line":3615},[3594,17923,17924],{"class":4459},"    .returning(User.id, User.created_at)\n",[3594,17926,17927],{"class":3596,"line":3621},[3594,17928,4558],{"class":4459},[3594,17930,17931],{"class":3596,"line":3627},[3594,17932,3631],{"emptyLinePlaceholder":3630},[3594,17934,17935],{"class":3596,"line":3634},[3594,17936,17937],{"class":4482},"# Компіляція SQL (PostgreSQL):\n",[3594,17939,17940],{"class":3596,"line":3640},[3594,17941,17942],{"class":4482},"# INSERT INTO users (username, email) VALUES ('new_user', 'new@example.com')\n",[3594,17944,17945],{"class":3596,"line":3646},[3594,17946,17947],{"class":4482},"# RETURNING users.id, users.created_at\n",[3594,17949,17950],{"class":3596,"line":3652},[3594,17951,3631],{"emptyLinePlaceholder":3630},[3594,17953,17954],{"class":3596,"line":3658},[3594,17955,16793],{"class":4459},[3594,17957,17958,17961],{"class":3596,"line":3664},[3594,17959,17960],{"class":4459},"row = result.fetchone()  ",[3594,17962,17963],{"class":4482},"# Оскільки ми чекаємо один рядок з RETURNING\n",[3594,17965,17966],{"class":3596,"line":3670},[3594,17967,3631],{"emptyLinePlaceholder":3630},[3594,17969,17970,17972,17974,17976,17979,17981,17983,17985,17987],{"class":3596,"line":3676},[3594,17971,13882],{"class":4690},[3594,17973,7763],{"class":4459},[3594,17975,8617],{"class":4622},[3594,17977,17978],{"class":4500},"\"Inserted ID: ",[3594,17980,8623],{"class":4622},[3594,17982,17438],{"class":4459},[3594,17984,8634],{"class":4622},[3594,17986,4631],{"class":4500},[3594,17988,4558],{"class":4459},[3594,17990,17991,17993,17995,17997,18000,18002,18005,18007,18009],{"class":3596,"line":3681},[3594,17992,13882],{"class":4690},[3594,17994,7763],{"class":4459},[3594,17996,8617],{"class":4622},[3594,17998,17999],{"class":4500},"\"Created At: ",[3594,18001,8623],{"class":4622},[3594,18003,18004],{"class":4459},"row.created_at",[3594,18006,8634],{"class":4622},[3594,18008,4631],{"class":4500},[3594,18010,4558],{"class":4459},[3594,18012,18013],{"class":3596,"line":3687},[3594,18014,18015],{"class":4459},"session.commit()\n",[3385,18017,18018],{},[3389,18019,17304],{},[3585,18021,18024],{"className":18022,"code":18023,"language":17310,"meta":3590},[17308],"Inserted ID: 42\nCreated At: 2026-07-04 18:00:00+00:00\n",[3412,18025,18023],{"__ignoreMap":3590},[5430,18027,18029],{"id":18028},"приклад-2-масова-вставка-bulk-insert","Приклад 2: Масова вставка (Bulk Insert)",[3385,18031,18032],{},"Для оптимізації продуктивності масової вставки передається список словників:",[3585,18034,18036],{"className":4445,"code":18035,"language":4448,"meta":3590,"style":3590},"data = [\n    {\"username\": \"user_a\", \"email\": \"a@example.com\"},\n    {\"username\": \"user_b\", \"email\": \"b@example.com\"},\n    {\"username\": \"user_c\", \"email\": \"c@example.com\"},\n]\n\n# Створюємо стейтмент без виклику .values() — передамо дані під час виконання\nstmt = insert(User)\n\n# Компіляція SQL:\n# INSERT INTO users (username, email) VALUES (:username, :email)\n\nresult = session.execute(stmt, data)\n# SQLAlchemy автоматично групує це в один оптимізований батч-запит\n\nprint(f\"Rows inserted: {result.rowcount}\")\nsession.commit()\n",[3412,18037,18038,18043,18068,18090,18112,18116,18120,18125,18130,18134,18138,18143,18147,18152,18157,18161,18183],{"__ignoreMap":3590},[3594,18039,18040],{"class":3596,"line":3597},[3594,18041,18042],{"class":4459},"data = [\n",[3594,18044,18045,18048,18050,18052,18055,18057,18060,18062,18065],{"class":3596,"line":3603},[3594,18046,18047],{"class":4459},"    {",[3594,18049,4703],{"class":4500},[3594,18051,9991],{"class":4459},[3594,18053,18054],{"class":4500},"\"user_a\"",[3594,18056,3488],{"class":4459},[3594,18058,18059],{"class":4500},"\"email\"",[3594,18061,9991],{"class":4459},[3594,18063,18064],{"class":4500},"\"a@example.com\"",[3594,18066,18067],{"class":4459},"},\n",[3594,18069,18070,18072,18074,18076,18079,18081,18083,18085,18088],{"class":3596,"line":3609},[3594,18071,18047],{"class":4459},[3594,18073,4703],{"class":4500},[3594,18075,9991],{"class":4459},[3594,18077,18078],{"class":4500},"\"user_b\"",[3594,18080,3488],{"class":4459},[3594,18082,18059],{"class":4500},[3594,18084,9991],{"class":4459},[3594,18086,18087],{"class":4500},"\"b@example.com\"",[3594,18089,18067],{"class":4459},[3594,18091,18092,18094,18096,18098,18101,18103,18105,18107,18110],{"class":3596,"line":3615},[3594,18093,18047],{"class":4459},[3594,18095,4703],{"class":4500},[3594,18097,9991],{"class":4459},[3594,18099,18100],{"class":4500},"\"user_c\"",[3594,18102,3488],{"class":4459},[3594,18104,18059],{"class":4500},[3594,18106,9991],{"class":4459},[3594,18108,18109],{"class":4500},"\"c@example.com\"",[3594,18111,18067],{"class":4459},[3594,18113,18114],{"class":3596,"line":3621},[3594,18115,4767],{"class":4459},[3594,18117,18118],{"class":3596,"line":3627},[3594,18119,3631],{"emptyLinePlaceholder":3630},[3594,18121,18122],{"class":3596,"line":3634},[3594,18123,18124],{"class":4482},"# Створюємо стейтмент без виклику .values() — передамо дані під час виконання\n",[3594,18126,18127],{"class":3596,"line":3640},[3594,18128,18129],{"class":4459},"stmt = insert(User)\n",[3594,18131,18132],{"class":3596,"line":3646},[3594,18133,3631],{"emptyLinePlaceholder":3630},[3594,18135,18136],{"class":3596,"line":3652},[3594,18137,17229],{"class":4482},[3594,18139,18140],{"class":3596,"line":3658},[3594,18141,18142],{"class":4482},"# INSERT INTO users (username, email) VALUES (:username, :email)\n",[3594,18144,18145],{"class":3596,"line":3664},[3594,18146,3631],{"emptyLinePlaceholder":3630},[3594,18148,18149],{"class":3596,"line":3670},[3594,18150,18151],{"class":4459},"result = session.execute(stmt, data)\n",[3594,18153,18154],{"class":3596,"line":3676},[3594,18155,18156],{"class":4482},"# SQLAlchemy автоматично групує це в один оптимізований батч-запит\n",[3594,18158,18159],{"class":3596,"line":3681},[3594,18160,3631],{"emptyLinePlaceholder":3630},[3594,18162,18163,18165,18167,18169,18172,18174,18177,18179,18181],{"class":3596,"line":3687},[3594,18164,13882],{"class":4690},[3594,18166,7763],{"class":4459},[3594,18168,8617],{"class":4622},[3594,18170,18171],{"class":4500},"\"Rows inserted: ",[3594,18173,8623],{"class":4622},[3594,18175,18176],{"class":4459},"result.rowcount",[3594,18178,8634],{"class":4622},[3594,18180,4631],{"class":4500},[3594,18182,4558],{"class":4459},[3594,18184,18185],{"class":3596,"line":3693},[3594,18186,18015],{"class":4459},[3385,18188,18189],{},[3389,18190,17304],{},[3585,18192,18195],{"className":18193,"code":18194,"language":17310,"meta":3590},[17308],"Rows inserted: 3\n",[3412,18196,18194],{"__ignoreMap":3590},[3441,18198],{},[3845,18200,18202,18203],{"id":18201},"оновлення-даних-update","Оновлення даних: ",[3412,18204,3898],{},[3385,18206,5980,18207,18209,18210,3465],{},[3412,18208,3898],{}," будує SQL-запити оновлення (",[3412,18211,3480],{},[4433,18213,14697],{"id":18214},"сигнатура-2",[3585,18216,18218],{"className":4445,"code":18217,"language":4448,"meta":3590,"style":3590},"def update(table: _DMLTableArgument) -> Update\n",[3412,18219,18220],{"__ignoreMap":3590},[3594,18221,18222,18224,18227,18229,18231],{"class":3596,"line":3597},[3594,18223,14707],{"class":4622},[3594,18225,18226],{"class":4690}," update",[3594,18228,7763],{"class":4459},[3594,18230,3986],{"class":4493},[3594,18232,18233],{"class":4459},": _DMLTableArgument) -> Update\n",[4433,18235,17795,18237],{"id":18236},"методи-обєкта-update",[3412,18238,18239],{},"Update",[3510,18241,18242,18250,18262],{},[3455,18243,18244,18249],{},[3389,18245,18246],{},[3412,18247,18248],{},".where(*clauses)"," — обмежує вибірку рядків, які будуть оновлені (критично важливо, інакше оновляться всі рядки в таблиці!).",[3455,18251,18252,18257,18258,18261],{},[3389,18253,18254],{},[3412,18255,18256],{},".values(**kwargs)"," або **",[3412,18259,18260],{},".values(dict)","** — встановлює нові значення для стовпців.",[3455,18263,18264,18268],{},[3389,18265,18266],{},[3412,18267,17814],{}," — повертає оновлені значення.",[4433,18270,18272],{"id":18271},"orm-vs-core-update","ORM vs Core Update",[3510,18274,18275,18295],{},[3455,18276,18277,18280,18281,18284,18285,18288,18289,18291,18292,18294],{},[3389,18278,18279],{},"ORM-стиль",": Отримуємо об'єкт через ",[3412,18282,18283],{},"session.get(User, id)",", міняємо його атрибут (",[3412,18286,18287],{},"user.email = \"new@mail.com\"","), і робимо ",[3412,18290,6327],{},". SQLAlchemy автоматично згенерує ",[3412,18293,3480],{}," під час комміту завдяки Change Tracking (Unit of Work).",[3455,18296,18297,18300,18301,18304],{},[3389,18298,18299],{},"Core-стиль",": Виконуємо ",[3412,18302,18303],{},"update(User).where(...).values(...)",". Застосовується для масових оновлень або коли потрібно оновити запис без попереднього завантаження його в пам'ять.",[4433,18306,17201],{"id":18307},"практичні-приклади-з-виводом-результатів-2",[5430,18309,18311],{"id":18310},"приклад-1-масове-оновлення-з-фільтрацією","Приклад 1: Масове оновлення з фільтрацією",[3385,18313,18314],{},"Заблокуємо всіх користувачів, які не заходили на сайт дуже давно (наприклад, у яких пошта на старому домені):",[3585,18316,18318],{"className":4445,"code":18317,"language":4448,"meta":3590,"style":3590},"stmt = (\n    update(User)\n    .where(User.email.like(\"%@old-domain.com\"))\n    .values(is_active=False)\n)\n\n# Компіляція SQL:\n# UPDATE users SET is_active = false WHERE users.email LIKE '%@old-domain.com'\n\nresult = session.execute(stmt)\nprint(f\"Matched and updated rows: {result.rowcount}\")\nsession.commit()\n",[3412,18319,18320,18324,18329,18339,18351,18355,18359,18363,18368,18372,18376,18397],{"__ignoreMap":3590},[3594,18321,18322],{"class":3596,"line":3597},[3594,18323,14267],{"class":4459},[3594,18325,18326],{"class":3596,"line":3603},[3594,18327,18328],{"class":4459},"    update(User)\n",[3594,18330,18331,18334,18337],{"class":3596,"line":3609},[3594,18332,18333],{"class":4459},"    .where(User.email.like(",[3594,18335,18336],{"class":4500},"\"%@old-domain.com\"",[3594,18338,7851],{"class":4459},[3594,18340,18341,18343,18345,18347,18349],{"class":3596,"line":3615},[3594,18342,17902],{"class":4459},[3594,18344,10165],{"class":4493},[3594,18346,4497],{"class":4459},[3594,18348,6238],{"class":4622},[3594,18350,4558],{"class":4459},[3594,18352,18353],{"class":3596,"line":3621},[3594,18354,4558],{"class":4459},[3594,18356,18357],{"class":3596,"line":3627},[3594,18358,3631],{"emptyLinePlaceholder":3630},[3594,18360,18361],{"class":3596,"line":3634},[3594,18362,17229],{"class":4482},[3594,18364,18365],{"class":3596,"line":3640},[3594,18366,18367],{"class":4482},"# UPDATE users SET is_active = false WHERE users.email LIKE '%@old-domain.com'\n",[3594,18369,18370],{"class":3596,"line":3646},[3594,18371,3631],{"emptyLinePlaceholder":3630},[3594,18373,18374],{"class":3596,"line":3652},[3594,18375,16793],{"class":4459},[3594,18377,18378,18380,18382,18384,18387,18389,18391,18393,18395],{"class":3596,"line":3658},[3594,18379,13882],{"class":4690},[3594,18381,7763],{"class":4459},[3594,18383,8617],{"class":4622},[3594,18385,18386],{"class":4500},"\"Matched and updated rows: ",[3594,18388,8623],{"class":4622},[3594,18390,18176],{"class":4459},[3594,18392,8634],{"class":4622},[3594,18394,4631],{"class":4500},[3594,18396,4558],{"class":4459},[3594,18398,18399],{"class":3596,"line":3664},[3594,18400,18015],{"class":4459},[3385,18402,18403],{},[3389,18404,17304],{},[3585,18406,18409],{"className":18407,"code":18408,"language":17310,"meta":3590},[17308],"Matched and updated rows: 14\n",[3412,18410,18408],{"__ignoreMap":3590},[5430,18412,18414],{"id":18413},"приклад-2-динамічне-оновлення-на-основі-існуючих-значень-стовпця","Приклад 2: Динамічне оновлення на основі існуючих значень стовпця",[3385,18416,18417],{},"Оновимо лічильник переглядів постів, збільшивши його на 1 directly на рівні бази даних (без завантаження у Python):",[3585,18419,18421],{"className":4445,"code":18420,"language":4448,"meta":3590,"style":3590},"stmt = (\n    update(Post)\n    .where(Post.id == 5)\n    .values(views_count=Post.views_count + 1)\n    .returning(Post.views_count)\n)\n\n# Компіляція SQL:\n# UPDATE posts SET views_count = posts.views_count + 1 WHERE posts.id = 5 RETURNING posts.views_count\n\nresult = session.execute(stmt)\nnew_views = result.scalar_one()\n\nprint(f\"Updated post views count. New value: {new_views}\")\nsession.commit()\n",[3412,18422,18423,18427,18432,18440,18454,18459,18463,18467,18471,18476,18480,18484,18489,18493,18515],{"__ignoreMap":3590},[3594,18424,18425],{"class":3596,"line":3597},[3594,18426,14267],{"class":4459},[3594,18428,18429],{"class":3596,"line":3603},[3594,18430,18431],{"class":4459},"    update(Post)\n",[3594,18433,18434,18436,18438],{"class":3596,"line":3609},[3594,18435,13863],{"class":4459},[3594,18437,6934],{"class":4514},[3594,18439,4558],{"class":4459},[3594,18441,18442,18444,18447,18450,18452],{"class":3596,"line":3615},[3594,18443,17902],{"class":4459},[3594,18445,18446],{"class":4493},"views_count",[3594,18448,18449],{"class":4459},"=Post.views_count + ",[3594,18451,6486],{"class":4514},[3594,18453,4558],{"class":4459},[3594,18455,18456],{"class":3596,"line":3621},[3594,18457,18458],{"class":4459},"    .returning(Post.views_count)\n",[3594,18460,18461],{"class":3596,"line":3627},[3594,18462,4558],{"class":4459},[3594,18464,18465],{"class":3596,"line":3634},[3594,18466,3631],{"emptyLinePlaceholder":3630},[3594,18468,18469],{"class":3596,"line":3640},[3594,18470,17229],{"class":4482},[3594,18472,18473],{"class":3596,"line":3646},[3594,18474,18475],{"class":4482},"# UPDATE posts SET views_count = posts.views_count + 1 WHERE posts.id = 5 RETURNING posts.views_count\n",[3594,18477,18478],{"class":3596,"line":3652},[3594,18479,3631],{"emptyLinePlaceholder":3630},[3594,18481,18482],{"class":3596,"line":3658},[3594,18483,16793],{"class":4459},[3594,18485,18486],{"class":3596,"line":3664},[3594,18487,18488],{"class":4459},"new_views = result.scalar_one()\n",[3594,18490,18491],{"class":3596,"line":3670},[3594,18492,3631],{"emptyLinePlaceholder":3630},[3594,18494,18495,18497,18499,18501,18504,18506,18509,18511,18513],{"class":3596,"line":3676},[3594,18496,13882],{"class":4690},[3594,18498,7763],{"class":4459},[3594,18500,8617],{"class":4622},[3594,18502,18503],{"class":4500},"\"Updated post views count. New value: ",[3594,18505,8623],{"class":4622},[3594,18507,18508],{"class":4459},"new_views",[3594,18510,8634],{"class":4622},[3594,18512,4631],{"class":4500},[3594,18514,4558],{"class":4459},[3594,18516,18517],{"class":3596,"line":3681},[3594,18518,18015],{"class":4459},[3385,18520,18521],{},[3389,18522,17304],{},[3585,18524,18527],{"className":18525,"code":18526,"language":17310,"meta":3590},[17308],"Updated post views count. New value: 101\n",[3412,18528,18526],{"__ignoreMap":3590},[3441,18530],{},[3845,18532,18534,18535],{"id":18533},"видалення-даних-delete","Видалення даних: ",[3412,18536,3901],{},[3385,18538,5980,18539,18541,18542,3465],{},[3412,18540,3901],{}," створює SQL-запити видалення записів (",[3412,18543,13168],{},[4433,18545,14697],{"id":18546},"сигнатура-3",[3585,18548,18550],{"className":4445,"code":18549,"language":4448,"meta":3590,"style":3590},"def delete(table: _DMLTableArgument) -> Delete\n",[3412,18551,18552],{"__ignoreMap":3590},[3594,18553,18554,18556,18559,18561,18563],{"class":3596,"line":3597},[3594,18555,14707],{"class":4622},[3594,18557,18558],{"class":4690}," delete",[3594,18560,7763],{"class":4459},[3594,18562,3986],{"class":4493},[3594,18564,18565],{"class":4459},": _DMLTableArgument) -> Delete\n",[4433,18567,17795,18569],{"id":18568},"методи-обєкта-delete",[3412,18570,18571],{},"Delete",[3510,18573,18574,18584],{},[3455,18575,18576,18580,18581,18583],{},[3389,18577,18578],{},[3412,18579,18248],{}," — визначає умови фільтрації для видалення (якщо не вказати ",[3412,18582,14767],{},", база даних очистить усю таблицю!).",[3455,18585,18586,18590],{},[3389,18587,18588],{},[3412,18589,17814],{}," — повертає дані видалених рядків перед їх остаточним знищенням.",[4433,18592,17201],{"id":18593},"практичні-приклади-з-виводом-результатів-3",[5430,18595,18597],{"id":18596},"приклад-1-видалення-неактивних-користувачів","Приклад 1: Видалення неактивних користувачів",[3585,18599,18601],{"className":4445,"code":18600,"language":4448,"meta":3590,"style":3590},"stmt = (\n    delete(User)\n    .where(User.is_active == False)\n)\n\n# Компіляція SQL:\n# DELETE FROM users WHERE users.is_active = false\n\nresult = session.execute(stmt)\nprint(f\"Deleted users count: {result.rowcount}\")\nsession.commit()\n",[3412,18602,18603,18607,18612,18621,18625,18629,18633,18638,18642,18646,18667],{"__ignoreMap":3590},[3594,18604,18605],{"class":3596,"line":3597},[3594,18606,14267],{"class":4459},[3594,18608,18609],{"class":3596,"line":3603},[3594,18610,18611],{"class":4459},"    delete(User)\n",[3594,18613,18614,18617,18619],{"class":3596,"line":3609},[3594,18615,18616],{"class":4459},"    .where(User.is_active == ",[3594,18618,6238],{"class":4622},[3594,18620,4558],{"class":4459},[3594,18622,18623],{"class":3596,"line":3615},[3594,18624,4558],{"class":4459},[3594,18626,18627],{"class":3596,"line":3621},[3594,18628,3631],{"emptyLinePlaceholder":3630},[3594,18630,18631],{"class":3596,"line":3627},[3594,18632,17229],{"class":4482},[3594,18634,18635],{"class":3596,"line":3634},[3594,18636,18637],{"class":4482},"# DELETE FROM users WHERE users.is_active = false\n",[3594,18639,18640],{"class":3596,"line":3640},[3594,18641,3631],{"emptyLinePlaceholder":3630},[3594,18643,18644],{"class":3596,"line":3646},[3594,18645,16793],{"class":4459},[3594,18647,18648,18650,18652,18654,18657,18659,18661,18663,18665],{"class":3596,"line":3652},[3594,18649,13882],{"class":4690},[3594,18651,7763],{"class":4459},[3594,18653,8617],{"class":4622},[3594,18655,18656],{"class":4500},"\"Deleted users count: ",[3594,18658,8623],{"class":4622},[3594,18660,18176],{"class":4459},[3594,18662,8634],{"class":4622},[3594,18664,4631],{"class":4500},[3594,18666,4558],{"class":4459},[3594,18668,18669],{"class":3596,"line":3658},[3594,18670,18015],{"class":4459},[3385,18672,18673],{},[3389,18674,17304],{},[3585,18676,18679],{"className":18677,"code":18678,"language":17310,"meta":3590},[17308],"Deleted users count: 5\n",[3412,18680,18678],{"__ignoreMap":3590},[5430,18682,18684],{"id":18683},"приклад-2-видалення-з-поверненням-видалених-даних-returning","Приклад 2: Видалення з поверненням видалених даних (RETURNING)",[3385,18686,18687],{},"Корисно, якщо перед видаленням нам потрібно залогувати або зберегти видалені дані:",[3585,18689,18691],{"className":4445,"code":18690,"language":4448,"meta":3590,"style":3590},"stmt = (\n    delete(Post)\n    .where(Post.created_at \u003C datetime(2020, 1, 1))\n    .returning(Post.id, Post.title)\n)\n\n# Компіляція SQL:\n# DELETE FROM posts WHERE posts.created_at \u003C '2020-01-01 00:00:00'\n# RETURNING posts.id, posts.title\n\ndeleted_posts = session.execute(stmt).all()\n\nfor post in deleted_posts:\n    print(f\"Logged deleted post: [ID: {post.id}] {post.title}\")\n\nsession.commit()\n",[3412,18692,18693,18697,18702,18720,18725,18729,18733,18737,18742,18747,18751,18756,18760,18772,18804,18808],{"__ignoreMap":3590},[3594,18694,18695],{"class":3596,"line":3597},[3594,18696,14267],{"class":4459},[3594,18698,18699],{"class":3596,"line":3603},[3594,18700,18701],{"class":4459},"    delete(Post)\n",[3594,18703,18704,18707,18710,18712,18714,18716,18718],{"class":3596,"line":3609},[3594,18705,18706],{"class":4459},"    .where(Post.created_at \u003C datetime(",[3594,18708,18709],{"class":4514},"2020",[3594,18711,3488],{"class":4459},[3594,18713,6486],{"class":4514},[3594,18715,3488],{"class":4459},[3594,18717,6486],{"class":4514},[3594,18719,7851],{"class":4459},[3594,18721,18722],{"class":3596,"line":3615},[3594,18723,18724],{"class":4459},"    .returning(Post.id, Post.title)\n",[3594,18726,18727],{"class":3596,"line":3621},[3594,18728,4558],{"class":4459},[3594,18730,18731],{"class":3596,"line":3627},[3594,18732,3631],{"emptyLinePlaceholder":3630},[3594,18734,18735],{"class":3596,"line":3634},[3594,18736,17229],{"class":4482},[3594,18738,18739],{"class":3596,"line":3640},[3594,18740,18741],{"class":4482},"# DELETE FROM posts WHERE posts.created_at \u003C '2020-01-01 00:00:00'\n",[3594,18743,18744],{"class":3596,"line":3646},[3594,18745,18746],{"class":4482},"# RETURNING posts.id, posts.title\n",[3594,18748,18749],{"class":3596,"line":3652},[3594,18750,3631],{"emptyLinePlaceholder":3630},[3594,18752,18753],{"class":3596,"line":3658},[3594,18754,18755],{"class":4459},"deleted_posts = session.execute(stmt).all()\n",[3594,18757,18758],{"class":3596,"line":3664},[3594,18759,3631],{"emptyLinePlaceholder":3630},[3594,18761,18762,18764,18767,18769],{"class":3596,"line":3670},[3594,18763,13566],{"class":4455},[3594,18765,18766],{"class":4459}," post ",[3594,18768,4682],{"class":4455},[3594,18770,18771],{"class":4459}," deleted_posts:\n",[3594,18773,18774,18776,18778,18780,18783,18785,18788,18790,18793,18795,18798,18800,18802],{"class":3596,"line":3676},[3594,18775,8171],{"class":4690},[3594,18777,7763],{"class":4459},[3594,18779,8617],{"class":4622},[3594,18781,18782],{"class":4500},"\"Logged deleted post: [ID: ",[3594,18784,8623],{"class":4622},[3594,18786,18787],{"class":4459},"post.id",[3594,18789,8634],{"class":4622},[3594,18791,18792],{"class":4500},"] ",[3594,18794,8623],{"class":4622},[3594,18796,18797],{"class":4459},"post.title",[3594,18799,8634],{"class":4622},[3594,18801,4631],{"class":4500},[3594,18803,4558],{"class":4459},[3594,18805,18806],{"class":3596,"line":3681},[3594,18807,3631],{"emptyLinePlaceholder":3630},[3594,18809,18810],{"class":3596,"line":3687},[3594,18811,18015],{"class":4459},[3845,18813,18815],{"id":18814},"порівняння-linq-ef-core-sqlalchemy-query-api","Порівняння: LINQ (EF Core) ↔ SQLAlchemy Query API",[5448,18817,18818,19036],{},[3585,18819,18822],{"className":10766,"code":18820,"filename":18821,"language":10769,"meta":3590,"style":3590},"\u002F\u002F Фільтрація + сортування + пагінація\nvar users = await context.Users\n    .Where(u => u.IsActive && u.Username.StartsWith(\"ara\"))\n    .OrderByDescending(u => u.CreatedAt)\n    .Skip((page - 1) * perPage)\n    .Take(perPage)\n    .Include(u => u.Posts)\n    .ToListAsync();\n\n\u002F\u002F Агрегація\nvar postCount = await context.Posts\n    .Where(p => p.AuthorId == userId)\n    .CountAsync();\n","EF Core — LINQ",[3412,18823,18824,18829,18845,18885,18907,18933,18945,18965,18973,18977,18982,19000,19027],{"__ignoreMap":3590},[3594,18825,18826],{"class":3596,"line":3597},[3594,18827,18828],{"class":4482},"\u002F\u002F Фільтрація + сортування + пагінація\n",[3594,18830,18831,18833,18835,18837,18839,18841,18843],{"class":3596,"line":3603},[3594,18832,13933],{"class":4622},[3594,18834,13936],{"class":4493},[3594,18836,13939],{"class":4459},[3594,18838,4878],{"class":4622},[3594,18840,13944],{"class":4493},[3594,18842,3439],{"class":4459},[3594,18844,13949],{"class":4493},[3594,18846,18847,18849,18852,18854,18856,18858,18860,18862,18865,18868,18870,18872,18874,18876,18879,18881,18883],{"class":3596,"line":3609},[3594,18848,13954],{"class":4459},[3594,18850,18851],{"class":4690},"Where",[3594,18853,7763],{"class":4459},[3594,18855,13962],{"class":4493},[3594,18857,13965],{"class":4459},[3594,18859,13962],{"class":4493},[3594,18861,3439],{"class":4459},[3594,18863,18864],{"class":4493},"IsActive",[3594,18866,18867],{"class":4459}," && ",[3594,18869,13962],{"class":4493},[3594,18871,3439],{"class":4459},[3594,18873,14051],{"class":4493},[3594,18875,3439],{"class":4459},[3594,18877,18878],{"class":4690},"StartsWith",[3594,18880,7763],{"class":4459},[3594,18882,15787],{"class":4500},[3594,18884,7851],{"class":4459},[3594,18886,18887,18889,18892,18894,18896,18898,18900,18902,18905],{"class":3596,"line":3615},[3594,18888,13954],{"class":4459},[3594,18890,18891],{"class":4690},"OrderByDescending",[3594,18893,7763],{"class":4459},[3594,18895,13962],{"class":4493},[3594,18897,13965],{"class":4459},[3594,18899,13962],{"class":4493},[3594,18901,3439],{"class":4459},[3594,18903,18904],{"class":4493},"CreatedAt",[3594,18906,4558],{"class":4459},[3594,18908,18909,18911,18914,18917,18920,18923,18925,18928,18931],{"class":3596,"line":3621},[3594,18910,13954],{"class":4459},[3594,18912,18913],{"class":4690},"Skip",[3594,18915,18916],{"class":4459},"((",[3594,18918,18919],{"class":4493},"page",[3594,18921,18922],{"class":4459}," - ",[3594,18924,6486],{"class":4514},[3594,18926,18927],{"class":4459},") * ",[3594,18929,18930],{"class":4493},"perPage",[3594,18932,4558],{"class":4459},[3594,18934,18935,18937,18939,18941,18943],{"class":3596,"line":3627},[3594,18936,13954],{"class":4459},[3594,18938,13983],{"class":4690},[3594,18940,7763],{"class":4459},[3594,18942,18930],{"class":4493},[3594,18944,4558],{"class":4459},[3594,18946,18947,18949,18951,18953,18955,18957,18959,18961,18963],{"class":3596,"line":3634},[3594,18948,13954],{"class":4459},[3594,18950,13957],{"class":4690},[3594,18952,7763],{"class":4459},[3594,18954,13962],{"class":4493},[3594,18956,13965],{"class":4459},[3594,18958,13962],{"class":4493},[3594,18960,3439],{"class":4459},[3594,18962,11920],{"class":4493},[3594,18964,4558],{"class":4459},[3594,18966,18967,18969,18971],{"class":3596,"line":3640},[3594,18968,13954],{"class":4459},[3594,18970,13996],{"class":4690},[3594,18972,13999],{"class":4459},[3594,18974,18975],{"class":3596,"line":3646},[3594,18976,3631],{"emptyLinePlaceholder":3630},[3594,18978,18979],{"class":3596,"line":3652},[3594,18980,18981],{"class":4482},"\u002F\u002F Агрегація\n",[3594,18983,18984,18986,18989,18991,18993,18995,18997],{"class":3596,"line":3658},[3594,18985,13933],{"class":4622},[3594,18987,18988],{"class":4493}," postCount",[3594,18990,13939],{"class":4459},[3594,18992,4878],{"class":4622},[3594,18994,13944],{"class":4493},[3594,18996,3439],{"class":4459},[3594,18998,18999],{"class":4493},"Posts\n",[3594,19001,19002,19004,19006,19008,19010,19012,19014,19016,19019,19022,19025],{"class":3596,"line":3664},[3594,19003,13954],{"class":4459},[3594,19005,18851],{"class":4690},[3594,19007,7763],{"class":4459},[3594,19009,3385],{"class":4493},[3594,19011,13965],{"class":4459},[3594,19013,3385],{"class":4493},[3594,19015,3439],{"class":4459},[3594,19017,19018],{"class":4493},"AuthorId",[3594,19020,19021],{"class":4459}," == ",[3594,19023,19024],{"class":4493},"userId",[3594,19026,4558],{"class":4459},[3594,19028,19029,19031,19034],{"class":3596,"line":3670},[3594,19030,13954],{"class":4459},[3594,19032,19033],{"class":4690},"CountAsync",[3594,19035,13999],{"class":4459},[3585,19037,19040],{"className":4445,"code":19038,"filename":19039,"language":4448,"meta":3590,"style":3590},"# Фільтрація + сортування + пагінація\nusers = (await session.execute(\n    select(User)\n    .where(User.is_active == True, User.username.like(\"ara%\"))\n    .order_by(User.created_at.desc())\n    .offset((page - 1) * per_page)\n    .limit(per_page)\n    .options(selectinload(User.posts))\n)).scalars().all()\n\n# Агрегація\nfrom sqlalchemy import func\npost_count = (await session.execute(\n    select(func.count(Post.id))\n    .where(Post.author_id == user_id)\n)).scalar_one()\n","SQLAlchemy — select() API",[3412,19041,19042,19047,19055,19059,19072,19077,19087,19092,19096,19100,19104,19109,19119,19128,19133,19138],{"__ignoreMap":3590},[3594,19043,19044],{"class":3596,"line":3597},[3594,19045,19046],{"class":4482},"# Фільтрація + сортування + пагінація\n",[3594,19048,19049,19051,19053],{"class":3596,"line":3603},[3594,19050,14095],{"class":4459},[3594,19052,4878],{"class":4455},[3594,19054,14100],{"class":4459},[3594,19056,19057],{"class":3596,"line":3609},[3594,19058,13720],{"class":4459},[3594,19060,19061,19063,19065,19068,19070],{"class":3596,"line":3615},[3594,19062,18616],{"class":4459},[3594,19064,4641],{"class":4622},[3594,19066,19067],{"class":4459},", User.username.like(",[3594,19069,14871],{"class":4500},[3594,19071,7851],{"class":4459},[3594,19073,19074],{"class":3596,"line":3621},[3594,19075,19076],{"class":4459},"    .order_by(User.created_at.desc())\n",[3594,19078,19079,19082,19084],{"class":3596,"line":3627},[3594,19080,19081],{"class":4459},"    .offset((page - ",[3594,19083,6486],{"class":4514},[3594,19085,19086],{"class":4459},") * per_page)\n",[3594,19088,19089],{"class":3596,"line":3634},[3594,19090,19091],{"class":4459},"    .limit(per_page)\n",[3594,19093,19094],{"class":3596,"line":3640},[3594,19095,13725],{"class":4459},[3594,19097,19098],{"class":3596,"line":3646},[3594,19099,13557],{"class":4459},[3594,19101,19102],{"class":3596,"line":3652},[3594,19103,3631],{"emptyLinePlaceholder":3630},[3594,19105,19106],{"class":3596,"line":3658},[3594,19107,19108],{"class":4482},"# Агрегація\n",[3594,19110,19111,19113,19115,19117],{"class":3596,"line":3664},[3594,19112,4465],{"class":4455},[3594,19114,5996],{"class":4459},[3594,19116,4456],{"class":4455},[3594,19118,17616],{"class":4459},[3594,19120,19121,19124,19126],{"class":3596,"line":3670},[3594,19122,19123],{"class":4459},"post_count = (",[3594,19125,4878],{"class":4455},[3594,19127,14100],{"class":4459},[3594,19129,19130],{"class":3596,"line":3676},[3594,19131,19132],{"class":4459},"    select(func.count(Post.id))\n",[3594,19134,19135],{"class":3596,"line":3681},[3594,19136,19137],{"class":4459},"    .where(Post.author_id == user_id)\n",[3594,19139,19140],{"class":3596,"line":3687},[3594,19141,16003],{"class":4459},[3441,19143],{},[4433,19145,19147,19148],{"id":19146},"просунуті-операції-з-даними-в-session","Просунуті операції з даними в ",[3412,19149,3944],{},[3385,19151,19152,19153,4442],{},"Для ефективного написання репозиторіїв та оптимізації роботи з базою даних важливо розрізняти тонкощі поведінки внутрішніх методів ",[3412,19154,3944],{},[19156,19157,19158,19208,19285,19344,19520],"tabs",{},[19159,19160,19162,19171],"tabs-item",{"label":19161},"get() vs select()",[4433,19163,19165,19168,19169],{"id":19164},"sessionget-проти-select",[3412,19166,19167],{},"session.get()"," проти ",[3412,19170,3892],{},[3510,19172,19173,19185,19200],{},[3455,19174,19175,19180,19181,19184],{},[3389,19176,19177],{},[3412,19178,19179],{},"session.get(Model, pk_value)"," шукає об'єкт за його первинним ключем. Його головна перевага — ",[3389,19182,19183],{},"використання Identity Map",". Перед тим як генерувати SQL-запит і відправляти його в базу даних, SQLAlchemy перевіряє, чи цей об'єкт уже завантажений у поточну сесію. Якщо так — він одразу повертає об'єкт із пам'яті Python, повністю уникаючи мережевого запиту до БД.",[3455,19186,19187,19188,7939,19193,19196,19197,19199],{},"Конструкція ",[3389,19189,19190],{},[3412,19191,19192],{},"select(Model).where(Model.id == pk_value)",[3389,19194,19195],{},"завжди"," генерує та виконує ",[3412,19198,7918],{},"-запит до бази даних, ігноруючи те, що об'єкт уже може бути завантажений.",[3455,19201,19202,19204,19205,19207],{},[6344,19203,7060],{}," Для пошуку за ID завжди віддавайте перевагу ",[3412,19206,19167],{}," — це суттєво заощаджує ресурси бази даних.",[19159,19209,19211,19218],{"label":19210},"add() vs merge()",[4433,19212,19214,19168,19216],{"id":19213},"sessionadd-проти-sessionmerge",[3412,19215,4122],{},[3412,19217,11191],{},[3510,19219,19220,19232,19247],{},[3455,19221,19222,19227,19228,19231],{},[3389,19223,19224],{},[3412,19225,19226],{},"session.add(obj)"," призначений для додавання нового (",[3412,19229,19230],{},"transient",") об'єкта до сесії. Якщо ви спробуєте додати об'єкт, який має первинний ключ, що вже існує в базі даних, SQLAlchemy може видати помилку при збереженні (через обмеження унікальності PK).",[3455,19233,19234,19239,19240,19243,19244,19246],{},[3389,19235,19236],{},[3412,19237,19238],{},"session.merge(obj)"," використовується для роботи з від'єднаними (",[3412,19241,19242],{},"detached",") об'єктами (наприклад, об'єкт прийшов із кешу Redis або був десеріалізований, і він уже має ",[3412,19245,7949],{},", але поточна сесія про него нічого не знає).",[3455,19248,16802,19249,19252,19253],{},[3412,19250,19251],{},"merge()"," робить наступне:\n",[3452,19254,19255,19258,19265,19268],{},[3455,19256,19257],{},"Перевіряє Identity Map та базу даних на наявність об'єкта з таким самим ID.",[3455,19259,19260,19261,19264],{},"Якщо об'єкт знайдено у БД, він завантажує його в сесію та ",[3389,19262,19263],{},"копіює стан"," вашого від'єднаного об'єкта в цей завантажений екземпляр.",[3455,19266,19267],{},"Якщо об'єкта немає в БД, він створює новий об'єкт.",[3455,19269,19270,19271,19274,19275,19278,19279,19281,19282,3439],{},"Повертає ",[6344,19272,19273],{},"новий"," відстежуваний екземпляр. Важливо пам'ятати: сам переданий в ",[3412,19276,19277],{},"merge(obj)"," об'єкт залишається невідстежуваним (",[3412,19280,19242],{},"), роботу слід продовжувати з об'єктом, який повернув метод: ",[3412,19283,19284],{},"tracked_obj = await session.merge(obj)",[19159,19286,19288,19298],{"label":19287},"flush \u002F commit \u002F refresh",[4433,19289,19291,19168,19293,19168,19295],{"id":19290},"flush-проти-commit-проти-refresh",[3412,19292,7911],{},[3412,19294,7672],{},[3412,19296,19297],{},"refresh()",[3510,19299,19300,19316,19329],{},[3455,19301,19302,19306,19307,19309,19310,13135,19313,19315],{},[3389,19303,19304],{},[3412,19305,6324],{}," — відправляє накопичені в пам'яті операції (INSERT, UPDATE, DELETE) до бази даних у межах поточної транзакції. СУБД виконує ці команди та повертає згенеровані значення (як-от автоінкрементні ",[3412,19308,7949],{},"), але ці зміни ",[3389,19311,19312],{},"можна відкотити",[3412,19314,7675],{},"). Вони не стають видимими іншим підключенням до завершення транзакції.",[3455,19317,19318,19322,19323,19325,19326,19328],{},[3389,19319,19320],{},[3412,19321,6327],{}," — фіксує поточну транзакцію бази даних, роблячи всі зміни перманентними та видимими для всіх. Під капотом ",[3412,19324,7672],{}," завжди автоматично викликає ",[3412,19327,7911],{}," перед збереженням.",[3455,19330,19331,19336,19337,19339,19340,19343],{},[3389,19332,19333],{},[3412,19334,19335],{},"session.refresh(obj)"," — виконує примусовий ",[3412,19338,7918],{},"-запит до бази даних, щоб оновити стан конкретного об'єкта ",[3412,19341,19342],{},"obj"," (наприклад, для отримання значень, розрахованих на стороні бази даних). Також це анулює локальний кеш Identity Map для даного об'єкта.",[19159,19345,19347,19355,19383],{"label":19346},"begin_nested() (Savepoints)",[4433,19348,19350,19351,19354],{"id":19349},"вкладені-транзакції-begin_nested-savepoints","Вкладені транзакції: ",[3412,19352,19353],{},"begin_nested()"," (Savepoints)",[3510,19356,19357,19367,19370],{},[3455,19358,19359,19360,19363,19364,3439],{},"У багатьох реляційних СУБД (зокрема, PostgreSQL) будь-яка помилка під час транзакції (наприклад, спроба вставити дублікат унікального ключа) робить усю транзакцію ",[3389,19361,19362],{},"недійсною",". Подальші запити у ній будуть викидати помилку ",[3412,19365,19366],{},"InFailedSqlTransactionError",[3455,19368,19369],{},"Якщо ви імпортуєте дані у циклі, один некоректний запис за замовчуванням завалить весь імпорт.",[3455,19371,19372,19373,19376,19377,19382],{},"Для обходу цього використовується ",[3412,19374,19375],{},"session.begin_nested()",", який створює SQL ",[3389,19378,19379],{},[3412,19380,19381],{},"SAVEPOINT",". Якщо операція всередині вкладеної транзакції падає з помилкою, вона відкочується лише до цієї контрольної точки, дозволяючи продовжити роботу з основною транзакцією.",[3585,19384,19386],{"className":4445,"code":19385,"language":4448,"meta":3590,"style":3590},"# Масовий імпорт з ігноруванням помилкових записів\nfor user_data in users_to_import:\n    # begin_nested() створює Savepoint і автоматично відкочує його у разі винятку\n    async with session.begin_nested():\n        try:\n            new_user = User(**user_data)\n            session.add(new_user)\n            await session.flush()  # Спроба вставки конкретного запису\n        except Exception as e:\n            # Цей запис відкотиться, але сесія залишиться робочою\n            print(f\"Помилка імпорту {user_data['username']}: {e}\")\n            continue\n\nawait session.commit()  # Зберігаємо всі успішно імпортовані записи\n",[3412,19387,19388,19393,19405,19410,19419,19426,19431,19436,19447,19460,19465,19501,19506,19510],{"__ignoreMap":3590},[3594,19389,19390],{"class":3596,"line":3597},[3594,19391,19392],{"class":4482},"# Масовий імпорт з ігноруванням помилкових записів\n",[3594,19394,19395,19397,19400,19402],{"class":3596,"line":3603},[3594,19396,13566],{"class":4455},[3594,19398,19399],{"class":4459}," user_data ",[3594,19401,4682],{"class":4455},[3594,19403,19404],{"class":4459}," users_to_import:\n",[3594,19406,19407],{"class":3596,"line":3609},[3594,19408,19409],{"class":4482},"    # begin_nested() створює Savepoint і автоматично відкочує його у разі винятку\n",[3594,19411,19412,19414,19416],{"class":3596,"line":3615},[3594,19413,8595],{"class":4455},[3594,19415,6606],{"class":4455},[3594,19417,19418],{"class":4459}," session.begin_nested():\n",[3594,19420,19421,19424],{"class":3596,"line":3621},[3594,19422,19423],{"class":4455},"        try",[3594,19425,4570],{"class":4459},[3594,19427,19428],{"class":3596,"line":3627},[3594,19429,19430],{"class":4459},"            new_user = User(**user_data)\n",[3594,19432,19433],{"class":3596,"line":3634},[3594,19434,19435],{"class":4459},"            session.add(new_user)\n",[3594,19437,19438,19441,19444],{"class":3596,"line":3640},[3594,19439,19440],{"class":4455},"            await",[3594,19442,19443],{"class":4459}," session.flush()  ",[3594,19445,19446],{"class":4482},"# Спроба вставки конкретного запису\n",[3594,19448,19449,19452,19454,19457],{"class":3596,"line":3646},[3594,19450,19451],{"class":4455},"        except",[3594,19453,4794],{"class":4793},[3594,19455,19456],{"class":4455}," as",[3594,19458,19459],{"class":4459}," e:\n",[3594,19461,19462],{"class":3596,"line":3652},[3594,19463,19464],{"class":4482},"            # Цей запис відкотиться, але сесія залишиться робочою\n",[3594,19466,19467,19469,19471,19473,19476,19478,19481,19484,19486,19488,19490,19492,19495,19497,19499],{"class":3596,"line":3658},[3594,19468,4691],{"class":4690},[3594,19470,7763],{"class":4459},[3594,19472,8617],{"class":4622},[3594,19474,19475],{"class":4500},"\"Помилка імпорту ",[3594,19477,8623],{"class":4622},[3594,19479,19480],{"class":4459},"user_data[",[3594,19482,19483],{"class":4500},"'username'",[3594,19485,17400],{"class":4459},[3594,19487,8634],{"class":4622},[3594,19489,9991],{"class":4500},[3594,19491,8623],{"class":4622},[3594,19493,19494],{"class":4459},"e",[3594,19496,8634],{"class":4622},[3594,19498,4631],{"class":4500},[3594,19500,4558],{"class":4459},[3594,19502,19503],{"class":3596,"line":3664},[3594,19504,19505],{"class":4455},"            continue\n",[3594,19507,19508],{"class":3596,"line":3670},[3594,19509,3631],{"emptyLinePlaceholder":3630},[3594,19511,19512,19514,19517],{"class":3596,"line":3676},[3594,19513,4878],{"class":4455},[3594,19515,19516],{"class":4459}," session.commit()  ",[3594,19518,19519],{"class":4482},"# Зберігаємо всі успішно імпортовані записи\n",[19159,19521,19523,19531,19588],{"label":19522},"execute() vs scalars()",[4433,19524,19526,19168,19528,19530],{"id":19525},"execute-проти-scalars-та-розпаковка-кортежів",[3412,19527,16805],{},[3412,19529,16831],{}," та розпаковка кортежів",[3510,19532,19533,19550,19562,19575],{},[3455,19534,19535,19536,19541,19542,19544,19545,19547,19548,3465],{},"Виклик ",[3389,19537,19538],{},[3412,19539,19540],{},"session.execute(stmt)"," повертає об'єкт класу ",[3412,19543,16811],{},". За своєю суттю це ітератор над рядками результату, де кожен рядок є об'єктом ",[3412,19546,16817],{}," (поводиться як ",[3412,19549,16821],{},[3455,19551,19552,19553,19555,19556,19558,19559,3439],{},"Якщо ви робите ",[3412,19554,14547],{},", кожен ",[3412,19557,16817],{}," містить кортеж із одного елемента: ",[3412,19560,19561],{},"(UserObject,)",[3455,19563,16802,19564,19568,19569,19572,19573,3439],{},[3389,19565,19566],{},[3412,19567,16857],{}," перетворює цей результат, витягуючи перший елемент із кожного кортежу. Тобто ",[3412,19570,19571],{},"execute(select(User)).scalars().all()"," повертає ",[3412,19574,16974],{},[3455,19576,19577,19578,3461,19581,19584,19585,19587],{},"Якщо ви робите мульти-вибір (наприклад, ",[3412,19579,19580],{},"select(User, Post)",[3412,19582,19583],{},"select(User.username, Post.title)","), вам потрібен повний ",[3412,19586,16805],{},", і ви можете розпакувати кортежі в циклі:",[3585,19589,19591],{"className":4445,"code":19590,"language":4448,"meta":3590,"style":3590},"result = await session.execute(select(User, Post).join(User.posts))\nfor user, post in result:  # Автоматична розпаковка кортежу Row\n    print(f\"Користувач {user.username} написав пост {post.title}\")\n",[3412,19592,19593,19603,19618],{"__ignoreMap":3590},[3594,19594,19595,19598,19600],{"class":3596,"line":3597},[3594,19596,19597],{"class":4459},"result = ",[3594,19599,4878],{"class":4455},[3594,19601,19602],{"class":4459}," session.execute(select(User, Post).join(User.posts))\n",[3594,19604,19605,19607,19610,19612,19615],{"class":3596,"line":3603},[3594,19606,13566],{"class":4455},[3594,19608,19609],{"class":4459}," user, post ",[3594,19611,4682],{"class":4455},[3594,19613,19614],{"class":4459}," result:  ",[3594,19616,19617],{"class":4482},"# Автоматична розпаковка кортежу Row\n",[3594,19619,19620,19622,19624,19626,19629,19631,19633,19635,19638,19640,19642,19644,19646],{"class":3596,"line":3609},[3594,19621,8171],{"class":4690},[3594,19623,7763],{"class":4459},[3594,19625,8617],{"class":4622},[3594,19627,19628],{"class":4500},"\"Користувач ",[3594,19630,8623],{"class":4622},[3594,19632,3553],{"class":4459},[3594,19634,8634],{"class":4622},[3594,19636,19637],{"class":4500}," написав пост ",[3594,19639,8623],{"class":4622},[3594,19641,18797],{"class":4459},[3594,19643,8634],{"class":4622},[3594,19645,4631],{"class":4500},[3594,19647,4558],{"class":4459},[5430,19649,19651],{"id":19650},"схематичне-порівняння-методів-session","Схематичне порівняння методів Session:",[3986,19653,19654,19670],{},[3989,19655,19656],{},[3992,19657,19658,19661,19664,19667],{},[3995,19659,19660],{"align":3997},"Метод",[3995,19662,19663],{"align":3997},"Об'єкт до виклику",[3995,19665,19666],{"align":3997},"Стан після виклику",[3995,19668,19669],{"align":3997},"Чи робить запит до БД?",[4006,19671,19672,19689,19707,19727,19744,19761,19777],{},[3992,19673,19674,19680,19683,19686],{},[4011,19675,19676],{"align":3997},[3389,19677,19678],{},[3412,19679,19226],{},[4011,19681,19682],{"align":3997},"Transient (новий)",[4011,19684,19685],{"align":3997},"Persistent (відстежується)",[4011,19687,19688],{"align":3997},"Ні (запит буде при flush\u002Fcommit)",[3992,19690,19691,19698,19701,19704],{},[4011,19692,19693],{"align":3997},[3389,19694,19695],{},[3412,19696,19697],{},"session.get(Model, id)",[4011,19699,19700],{"align":3997},"Не існує у сесії",[4011,19702,19703],{"align":3997},"Persistent (завантажений)",[4011,19705,19706],{"align":3997},"Лише якщо об'єкта немає в Identity Map",[3992,19708,19709,19715,19718,19721],{},[4011,19710,19711],{"align":3997},[3389,19712,19713],{},[3412,19714,19238],{},[4011,19716,19717],{"align":3997},"Detached (ззовні)",[4011,19719,19720],{"align":3997},"Detached (оригінал), повернутий копіюється у Persistent",[4011,19722,19723,19724,19726],{"align":3997},"Так, робить ",[3412,19725,7918],{}," для перевірки стану в БД",[3992,19728,19729,19736,19739,19742],{},[4011,19730,19731],{"align":3997},[3389,19732,19733],{},[3412,19734,19735],{},"session.delete(obj)",[4011,19737,19738],{"align":3997},"Persistent",[4011,19740,19741],{"align":3997},"Pending Delete (видалення при commit)",[4011,19743,19688],{"align":3997},[3992,19745,19746,19752,19755,19758],{},[4011,19747,19748],{"align":3997},[3389,19749,19750],{},[3412,19751,6324],{},[4011,19753,19754],{"align":3997},"Persistent\u002FDirty",[4011,19756,19757],{"align":3997},"Persistent (зміни відправлені до БД)",[4011,19759,19760],{"align":3997},"Так, виконує накопичені операції запису",[3992,19762,19763,19769,19771,19774],{},[4011,19764,19765],{"align":3997},[3389,19766,19767],{},[3412,19768,6327],{},[4011,19770,19754],{"align":3997},[4011,19772,19773],{"align":3997},"Persistent (або Expired \u002F Detached при закритті)",[4011,19775,19776],{"align":3997},"Так, фіксує поточну транзакцію",[3992,19778,19779,19785,19787,19790],{},[4011,19780,19781],{"align":3997},[3389,19782,19783],{},[3412,19784,19335],{},[4011,19786,19738],{"align":3997},[4011,19788,19789],{"align":3997},"Persistent (дані оновлено з БД)",[4011,19791,19792,19793,19795],{"align":3997},"Так, виконує ",[3412,19794,7918],{}," для оновлення полів",[3441,19797],{},[3444,19799,19801],{"id":19800},"під-капотом-unit-of-work-identity-map-та-change-tracking","Під капотом: Unit of Work, Identity Map та Change Tracking",[3385,19803,19804,19805,9991,19807,3488,19809,3435,19811,3439],{},"Щоб дійсно розуміти SQLAlchemy і передбачати її поведінку, недостатньо просто знати синтаксис. Потрібно зрозуміти три ключові патерни, що лежать в основі ",[3412,19806,3944],{},[3389,19808,6303],{},[3389,19810,3962],{},[3389,19812,19813],{},"Change Tracking",[3845,19815,19817],{"id":19816},"патерн-unit-of-work-одиниця-роботи","Патерн Unit of Work (Одиниця роботи)",[3385,19819,19820,19822],{},[3389,19821,6303],{}," — це архітектурний патерн (описаний Мартіном Фаулером у книзі «Patterns of Enterprise Application Architecture»), який відстежує всі зміни, що відбуваються з об'єктами під час однієї «одиниці роботи» (зазвичай — одного HTTP-запиту), і потім атомарно відправляє їх до бази даних у вигляді однієї транзакції.",[3385,19824,19825,19826,19828,19829,19832,19833,19835],{},"Аналогія: уявіть, що ",[3412,19827,3944],{}," — це ",[3389,19830,19831],{},"кошик для покупок",". Ви кладете товари (об'єкти) у кошик, берете звідти, змінюєте вміст — і лише коли ви підходите до каси (",[3412,19834,6327],{},"), всі операції виконуються разом як єдина атомарна дія.",[19837,19838,19839],"mermaid",{},[3585,19840,19843],{"className":19841,"code":19842,"language":19837,"meta":3590,"style":3590},"language-mermaid shiki shiki-themes light-plus dark-plus dark-plus","sequenceDiagram\n    participant Code as \"Python Code\"\n    participant Session as \"Session (UoW)\"\n    participant DB as \"PostgreSQL\"\n\n    Code->>Session: session.add(new_user)\n    Note over Session: Додає об'єкт у стан \"pending\"\n    Code->>Session: user.is_active = False\n    Note over Session: Відмічає об'єкт як \"dirty\"\n    Code->>Session: session.delete(old_post)\n    Note over Session: Позначає об'єкт як \"deleted\"\n    Code->>Session: await session.commit()\n    Session->>DB: BEGIN TRANSACTION\n    Session->>DB: INSERT INTO users VALUES (...)\n    Session->>DB: UPDATE users SET is_active=false WHERE id=...\n    Session->>DB: DELETE FROM posts WHERE id=...\n    Session->>DB: COMMIT\n    Note over Session: Об'єкти стають \"persistent\"\n",[3412,19844,19845,19850,19855,19860,19865,19869,19874,19879,19884,19889,19894,19899,19904,19909,19914,19919,19924,19929],{"__ignoreMap":3590},[3594,19846,19847],{"class":3596,"line":3597},[3594,19848,19849],{},"sequenceDiagram\n",[3594,19851,19852],{"class":3596,"line":3603},[3594,19853,19854],{},"    participant Code as \"Python Code\"\n",[3594,19856,19857],{"class":3596,"line":3609},[3594,19858,19859],{},"    participant Session as \"Session (UoW)\"\n",[3594,19861,19862],{"class":3596,"line":3615},[3594,19863,19864],{},"    participant DB as \"PostgreSQL\"\n",[3594,19866,19867],{"class":3596,"line":3621},[3594,19868,3631],{"emptyLinePlaceholder":3630},[3594,19870,19871],{"class":3596,"line":3627},[3594,19872,19873],{},"    Code->>Session: session.add(new_user)\n",[3594,19875,19876],{"class":3596,"line":3634},[3594,19877,19878],{},"    Note over Session: Додає об'єкт у стан \"pending\"\n",[3594,19880,19881],{"class":3596,"line":3640},[3594,19882,19883],{},"    Code->>Session: user.is_active = False\n",[3594,19885,19886],{"class":3596,"line":3646},[3594,19887,19888],{},"    Note over Session: Відмічає об'єкт як \"dirty\"\n",[3594,19890,19891],{"class":3596,"line":3652},[3594,19892,19893],{},"    Code->>Session: session.delete(old_post)\n",[3594,19895,19896],{"class":3596,"line":3658},[3594,19897,19898],{},"    Note over Session: Позначає об'єкт як \"deleted\"\n",[3594,19900,19901],{"class":3596,"line":3664},[3594,19902,19903],{},"    Code->>Session: await session.commit()\n",[3594,19905,19906],{"class":3596,"line":3670},[3594,19907,19908],{},"    Session->>DB: BEGIN TRANSACTION\n",[3594,19910,19911],{"class":3596,"line":3676},[3594,19912,19913],{},"    Session->>DB: INSERT INTO users VALUES (...)\n",[3594,19915,19916],{"class":3596,"line":3681},[3594,19917,19918],{},"    Session->>DB: UPDATE users SET is_active=false WHERE id=...\n",[3594,19920,19921],{"class":3596,"line":3687},[3594,19922,19923],{},"    Session->>DB: DELETE FROM posts WHERE id=...\n",[3594,19925,19926],{"class":3596,"line":3693},[3594,19927,19928],{},"    Session->>DB: COMMIT\n",[3594,19930,19931],{"class":3596,"line":3699},[3594,19932,19933],{},"    Note over Session: Об'єкти стають \"persistent\"\n",[3385,19935,19936,19938],{},[3412,19937,3944],{}," відстежує об'єкти через чотири можливі стани:",[3510,19940,19941,19951,19963,19968],{},[3455,19942,19943,19946,19947,19950],{},[3389,19944,19945],{},"Transient"," (перехідний) — об'єкт створений (",[3412,19948,19949],{},"User()","), але ще не доданий до сесії.",[3455,19952,19953,19956,19957,19959,19960,19962],{},[3389,19954,19955],{},"Pending"," (очікуваний) — об'єкт доданий через ",[3412,19958,4122],{},", але ще не збережений до БД (",[3412,19961,7659],{}," ще не виконано).",[3455,19964,19965,19967],{},[3389,19966,19738],{}," (постійний) — об'єкт має відповідний запис у БД та відстежується сесією.",[3455,19969,19970,19973],{},[3389,19971,19972],{},"Detached"," (від'єднаний) — об'єкт був persistent, але сесія закрита або він був видалений зі сесії.",[3845,19975,19977],{"id":19976},"identity-map-гарантія-унікальності","Identity Map: Гарантія унікальності",[3385,19979,19980,19982,19983,19985],{},[3389,19981,3962],{}," (карта ідентичностей) — це внутрішній словник у ",[3412,19984,3944],{},", що відображає первинний ключ бази даних на Python-об'єкт.",[3385,19987,19988,19989,19996],{},"Ключова гарантія Identity Map: ",[3389,19990,19991,19992,19995],{},"у межах однієї сесії ",[3412,19993,19994],{},"session.get(User, 1)"," завжди повертає той самий Python-об'єкт",", незалежно від кількості викликів.",[3585,19998,20001],{"className":4445,"code":19999,"filename":20000,"language":4448,"meta":3590,"style":3590},"with Session(engine) as session:\n    # Перший виклик: SELECT * FROM users WHERE id = 1\n    user_a = session.get(User, 1)\n\n    # Другий виклик: SQL-запиту НЕ виконується!\n    # SQLAlchemy знаходить об'єкт у Identity Map і повертає той самий\n    user_b = session.get(User, 1)\n\n    print(user_a is user_b)  # True — це буквально один і той самий об'єкт!\n\n    # Якщо ми змінимо user_a — user_b теж «зміниться»\n    user_a.is_active = False\n    print(user_b.is_active)  # False\n","identity_map_demo.py",[3412,20002,20003,20014,20019,20028,20032,20037,20042,20051,20055,20070,20074,20079,20086],{"__ignoreMap":3590},[3594,20004,20005,20007,20010,20012],{"class":3596,"line":3597},[3594,20006,6470],{"class":4455},[3594,20008,20009],{"class":4459}," Session(engine) ",[3594,20011,4597],{"class":4455},[3594,20013,6478],{"class":4459},[3594,20015,20016],{"class":3596,"line":3603},[3594,20017,20018],{"class":4482},"    # Перший виклик: SELECT * FROM users WHERE id = 1\n",[3594,20020,20021,20024,20026],{"class":3596,"line":3609},[3594,20022,20023],{"class":4459},"    user_a = session.get(User, ",[3594,20025,6486],{"class":4514},[3594,20027,4558],{"class":4459},[3594,20029,20030],{"class":3596,"line":3615},[3594,20031,3631],{"emptyLinePlaceholder":3630},[3594,20033,20034],{"class":3596,"line":3621},[3594,20035,20036],{"class":4482},"    # Другий виклик: SQL-запиту НЕ виконується!\n",[3594,20038,20039],{"class":3596,"line":3627},[3594,20040,20041],{"class":4482},"    # SQLAlchemy знаходить об'єкт у Identity Map і повертає той самий\n",[3594,20043,20044,20047,20049],{"class":3596,"line":3634},[3594,20045,20046],{"class":4459},"    user_b = session.get(User, ",[3594,20048,6486],{"class":4514},[3594,20050,4558],{"class":4459},[3594,20052,20053],{"class":3596,"line":3640},[3594,20054,3631],{"emptyLinePlaceholder":3630},[3594,20056,20057,20059,20062,20064,20067],{"class":3596,"line":3646},[3594,20058,8171],{"class":4690},[3594,20060,20061],{"class":4459},"(user_a ",[3594,20063,15478],{"class":4455},[3594,20065,20066],{"class":4459}," user_b)  ",[3594,20068,20069],{"class":4482},"# True — це буквально один і той самий об'єкт!\n",[3594,20071,20072],{"class":3596,"line":3652},[3594,20073,3631],{"emptyLinePlaceholder":3630},[3594,20075,20076],{"class":3596,"line":3658},[3594,20077,20078],{"class":4482},"    # Якщо ми змінимо user_a — user_b теж «зміниться»\n",[3594,20080,20081,20084],{"class":3596,"line":3664},[3594,20082,20083],{"class":4459},"    user_a.is_active = ",[3594,20085,6496],{"class":4622},[3594,20087,20088,20090,20093],{"class":3596,"line":3670},[3594,20089,8171],{"class":4690},[3594,20091,20092],{"class":4459},"(user_b.is_active)  ",[3594,20094,20095],{"class":4482},"# False\n",[3385,20097,20098,20099,20102],{},"Це принципово відрізняється від простого кешування запитів. Identity Map гарантує, що ваша програма завжди працює з ",[3389,20100,20101],{},"консистентним станом об'єктів"," протягом однієї транзакції.",[3385,20104,20105,20106,20108,20109,20112],{},"Порівняння з EF Core: ",[3412,20107,4094],{}," є прямим аналогом Identity Map + Change Tracking у SQLAlchemy. Якщо ви двічі запитуєте один і той самий запис через ",[3412,20110,20111],{},"context.Users.Find(1)"," в EF Core — ви також отримуєте той самий об'єкт з кешу.",[3845,20114,20116],{"id":20115},"change-tracking-як-sqlalchemy-знає-що-змінилося","Change Tracking: Як SQLAlchemy «знає», що змінилося",[3385,20118,20119,20121,20122,20124,20125,3481],{},[3389,20120,19813],{}," (відстеження змін) — це механізм, завдяки якому ",[3412,20123,3944],{}," автоматично знає, які саме атрибути об'єктів були змінені і генерує мінімально необхідний ",[3412,20126,3480],{},[3385,20128,20129,20130,20133,20134,3488,20136,20138,20139,20142,20143,3439],{},"Цей механізм реалізований через ",[3389,20131,20132],{},"Python-дескриптори"," (instrumented attributes) — при реєстрації моделі SQLAlchemy замінює звичайні атрибути класу (",[3412,20135,7736],{},[3412,20137,17913],{}," тощо) на спеціальні об'єкти-дескриптори, що перехоплюють операції ",[3412,20140,20141],{},"__get__"," і ",[3412,20144,20145],{},"__set__",[3585,20147,20150],{"className":4445,"code":20148,"filename":20149,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import inspect\n\nwith Session(engine) as session:\n    user = session.get(User, 1)  # Завантажуємо об'єкт\n\n    # Змінюємо кілька атрибутів:\n    user.username = \"new_username\"\n    user.bio = \"Updated bio\"\n\n    # Можна перевірити стан об'єкта ПЕРЕД commit():\n    inspector = inspect(user)\n    for attr in inspector.attrs:\n        history = attr.history\n        if history.has_changes():\n            print(f\"  {attr.key}: {history.deleted} → {history.added}\")\n    # Виведе:\n    # username: ['old_username'] → ['new_username']\n    # bio: [None] → ['Updated bio']\n\n    session.commit()\n    # SQLAlchemy генерує МІНІМАЛЬНИЙ запит:\n    # UPDATE users SET username='new_username', bio='Updated bio' WHERE id=1\n    # (лише змінені поля, не всі!)\n","change_tracking_demo.py",[3412,20151,20152,20163,20167,20177,20188,20192,20197,20204,20212,20216,20221,20226,20239,20244,20252,20292,20297,20302,20307,20311,20315,20320,20325],{"__ignoreMap":3590},[3594,20153,20154,20156,20158,20160],{"class":3596,"line":3597},[3594,20155,4465],{"class":4455},[3594,20157,5996],{"class":4459},[3594,20159,4456],{"class":4455},[3594,20161,20162],{"class":4459}," inspect\n",[3594,20164,20165],{"class":3596,"line":3603},[3594,20166,3631],{"emptyLinePlaceholder":3630},[3594,20168,20169,20171,20173,20175],{"class":3596,"line":3609},[3594,20170,6470],{"class":4455},[3594,20172,20009],{"class":4459},[3594,20174,4597],{"class":4455},[3594,20176,6478],{"class":4459},[3594,20178,20179,20181,20183,20185],{"class":3596,"line":3615},[3594,20180,6483],{"class":4459},[3594,20182,6486],{"class":4514},[3594,20184,13901],{"class":4459},[3594,20186,20187],{"class":4482},"# Завантажуємо об'єкт\n",[3594,20189,20190],{"class":3596,"line":3621},[3594,20191,3631],{"emptyLinePlaceholder":3630},[3594,20193,20194],{"class":3596,"line":3627},[3594,20195,20196],{"class":4482},"    # Змінюємо кілька атрибутів:\n",[3594,20198,20199,20201],{"class":3596,"line":3634},[3594,20200,8292],{"class":4459},[3594,20202,20203],{"class":4500},"\"new_username\"\n",[3594,20205,20206,20209],{"class":3596,"line":3640},[3594,20207,20208],{"class":4459},"    user.bio = ",[3594,20210,20211],{"class":4500},"\"Updated bio\"\n",[3594,20213,20214],{"class":3596,"line":3646},[3594,20215,3631],{"emptyLinePlaceholder":3630},[3594,20217,20218],{"class":3596,"line":3652},[3594,20219,20220],{"class":4482},"    # Можна перевірити стан об'єкта ПЕРЕД commit():\n",[3594,20222,20223],{"class":3596,"line":3658},[3594,20224,20225],{"class":4459},"    inspector = inspect(user)\n",[3594,20227,20228,20231,20234,20236],{"class":3596,"line":3664},[3594,20229,20230],{"class":4455},"    for",[3594,20232,20233],{"class":4459}," attr ",[3594,20235,4682],{"class":4455},[3594,20237,20238],{"class":4459}," inspector.attrs:\n",[3594,20240,20241],{"class":3596,"line":3670},[3594,20242,20243],{"class":4459},"        history = attr.history\n",[3594,20245,20246,20249],{"class":3596,"line":3676},[3594,20247,20248],{"class":4455},"        if",[3594,20250,20251],{"class":4459}," history.has_changes():\n",[3594,20253,20254,20256,20258,20260,20263,20265,20268,20270,20272,20274,20277,20279,20281,20283,20286,20288,20290],{"class":3596,"line":3681},[3594,20255,4691],{"class":4690},[3594,20257,7763],{"class":4459},[3594,20259,8617],{"class":4622},[3594,20261,20262],{"class":4500},"\"  ",[3594,20264,8623],{"class":4622},[3594,20266,20267],{"class":4459},"attr.key",[3594,20269,8634],{"class":4622},[3594,20271,9991],{"class":4500},[3594,20273,8623],{"class":4622},[3594,20275,20276],{"class":4459},"history.deleted",[3594,20278,8634],{"class":4622},[3594,20280,9277],{"class":4500},[3594,20282,8623],{"class":4622},[3594,20284,20285],{"class":4459},"history.added",[3594,20287,8634],{"class":4622},[3594,20289,4631],{"class":4500},[3594,20291,4558],{"class":4459},[3594,20293,20294],{"class":3596,"line":3687},[3594,20295,20296],{"class":4482},"    # Виведе:\n",[3594,20298,20299],{"class":3596,"line":3693},[3594,20300,20301],{"class":4482},"    # username: ['old_username'] → ['new_username']\n",[3594,20303,20304],{"class":3596,"line":3699},[3594,20305,20306],{"class":4482},"    # bio: [None] → ['Updated bio']\n",[3594,20308,20309],{"class":3596,"line":3705},[3594,20310,3631],{"emptyLinePlaceholder":3630},[3594,20312,20313],{"class":3596,"line":3711},[3594,20314,6501],{"class":4459},[3594,20316,20317],{"class":3596,"line":3716},[3594,20318,20319],{"class":4482},"    # SQLAlchemy генерує МІНІМАЛЬНИЙ запит:\n",[3594,20321,20322],{"class":3596,"line":3721},[3594,20323,20324],{"class":4482},"    # UPDATE users SET username='new_username', bio='Updated bio' WHERE id=1\n",[3594,20326,20327],{"class":3596,"line":3727},[3594,20328,20329],{"class":4482},"    # (лише змінені поля, не всі!)\n",[3385,20331,20332,20333,20335],{},"Саме завдяки Change Tracking SQLAlchemy генерує ефективні ",[3412,20334,3480],{},"-запити — оновлюються лише ті стовпці, що дійсно змінилися, а не всі поля об'єкта.",[3441,20337],{},[3444,20339,20341,20342,20345],{"id":20340},"інтеграція-з-fastapi-get_async_session-через-dependency-injection","Інтеграція з FastAPI: ",[3412,20343,20344],{},"get_async_session"," через Dependency Injection",[3385,20347,20348,20349,20354],{},"Тепер об'єднаємо все, що ми вивчили, у повноцінний production-ready файл конфігурації бази даних для FastAPI-додатку. Ключовим питанням є: ",[3389,20350,20351,20352],{},"як правильно управляти життєвим циклом ",[3412,20353,3426],{}," — коли її створювати і коли закривати?",[3385,20356,20357,20358,20361],{},"Відповідь стандартна для FastAPI: через ",[3389,20359,20360],{},"Dependency Injection"," (детально розібраний у статті 20).",[3585,20363,20365],{"className":4445,"code":20364,"filename":5987,"language":4448,"meta":3590,"style":3590},"from typing import AsyncGenerator\nfrom sqlalchemy.ext.asyncio import (\n    create_async_engine,\n    async_sessionmaker,\n    AsyncSession,\n)\nfrom sqlalchemy.orm import DeclarativeBase\n\n# 1. Базовий клас для всіх моделей\nclass Base(DeclarativeBase):\n    pass\n\n# 2. Async Engine — singleton, живе весь час роботи додатку\nDATABASE_URL = \"postgresql+asyncpg:\u002F\u002Fuser:password@localhost\u002Ftaskforge_db\"\n\nasync_engine = create_async_engine(\n    DATABASE_URL,\n    pool_size=10,\n    max_overflow=20,\n    pool_recycle=1800,\n    echo=False,\n)\n\n# 3. Фабрика AsyncSession\nAsyncSessionLocal = async_sessionmaker(\n    bind=async_engine,\n    class_=AsyncSession,\n    autocommit=False,\n    autoflush=False,\n    expire_on_commit=False,\n)\n\n# 4. Dependency для FastAPI — генератор сесії\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    \"\"\"\n    FastAPI Dependency: надає AsyncSession для одного HTTP-запиту.\n    Сесія автоматично закривається після завершення запиту.\n    У разі помилки — автоматичний rollback.\n    \"\"\"\n    async with AsyncSessionLocal() as session:\n        try:\n            yield session\n            await session.commit()\n        except Exception:\n            await session.rollback()\n            raise\n",[3412,20366,20367,20378,20388,20392,20396,20400,20404,20414,20418,20423,20435,20439,20443,20448,20455,20459,20463,20467,20477,20487,20497,20507,20511,20515,20520,20524,20530,20536,20546,20556,20566,20570,20574,20579,20596,20600,20605,20610,20615,20619,20631,20637,20645,20651,20660,20668],{"__ignoreMap":3590},[3594,20368,20369,20371,20373,20375],{"class":3596,"line":3597},[3594,20370,4465],{"class":4455},[3594,20372,11470],{"class":4459},[3594,20374,4456],{"class":4455},[3594,20376,20377],{"class":4459}," AsyncGenerator\n",[3594,20379,20380,20382,20384,20386],{"class":3596,"line":3603},[3594,20381,4465],{"class":4455},[3594,20383,5782],{"class":4459},[3594,20385,4456],{"class":4455},[3594,20387,8749],{"class":4459},[3594,20389,20390],{"class":3596,"line":3609},[3594,20391,8754],{"class":4459},[3594,20393,20394],{"class":3596,"line":3615},[3594,20395,8759],{"class":4459},[3594,20397,20398],{"class":3596,"line":3621},[3594,20399,8764],{"class":4459},[3594,20401,20402],{"class":3596,"line":3627},[3594,20403,4558],{"class":4459},[3594,20405,20406,20408,20410,20412],{"class":3596,"line":3634},[3594,20407,4465],{"class":4455},[3594,20409,6383],{"class":4459},[3594,20411,4456],{"class":4455},[3594,20413,9341],{"class":4459},[3594,20415,20416],{"class":3596,"line":3640},[3594,20417,3631],{"emptyLinePlaceholder":3630},[3594,20419,20420],{"class":3596,"line":3646},[3594,20421,20422],{"class":4482},"# 1. Базовий клас для всіх моделей\n",[3594,20424,20425,20427,20429,20431,20433],{"class":3596,"line":3652},[3594,20426,8575],{"class":4622},[3594,20428,9127],{"class":4793},[3594,20430,7763],{"class":4459},[3594,20432,3931],{"class":4793},[3594,20434,8585],{"class":4459},[3594,20436,20437],{"class":3596,"line":3658},[3594,20438,9142],{"class":4455},[3594,20440,20441],{"class":3596,"line":3664},[3594,20442,3631],{"emptyLinePlaceholder":3630},[3594,20444,20445],{"class":3596,"line":3670},[3594,20446,20447],{"class":4482},"# 2. Async Engine — singleton, живе весь час роботи додатку\n",[3594,20449,20450,20452],{"class":3596,"line":3676},[3594,20451,6015],{"class":4459},[3594,20453,20454],{"class":4500},"\"postgresql+asyncpg:\u002F\u002Fuser:password@localhost\u002Ftaskforge_db\"\n",[3594,20456,20457],{"class":3596,"line":3681},[3594,20458,3631],{"emptyLinePlaceholder":3630},[3594,20460,20461],{"class":3596,"line":3687},[3594,20462,5500],{"class":4459},[3594,20464,20465],{"class":3596,"line":3693},[3594,20466,6031],{"class":4459},[3594,20468,20469,20471,20473,20475],{"class":3596,"line":3699},[3594,20470,6041],{"class":4493},[3594,20472,4497],{"class":4459},[3594,20474,4646],{"class":4514},[3594,20476,4504],{"class":4459},[3594,20478,20479,20481,20483,20485],{"class":3596,"line":3705},[3594,20480,6056],{"class":4493},[3594,20482,4497],{"class":4459},[3594,20484,6061],{"class":4514},[3594,20486,4504],{"class":4459},[3594,20488,20489,20491,20493,20495],{"class":3596,"line":3711},[3594,20490,6087],{"class":4493},[3594,20492,4497],{"class":4459},[3594,20494,6092],{"class":4514},[3594,20496,4504],{"class":4459},[3594,20498,20499,20501,20503,20505],{"class":3596,"line":3716},[3594,20500,6112],{"class":4493},[3594,20502,4497],{"class":4459},[3594,20504,6238],{"class":4622},[3594,20506,4504],{"class":4459},[3594,20508,20509],{"class":3596,"line":3721},[3594,20510,4558],{"class":4459},[3594,20512,20513],{"class":3596,"line":3727},[3594,20514,3631],{"emptyLinePlaceholder":3630},[3594,20516,20517],{"class":3596,"line":3733},[3594,20518,20519],{"class":4482},"# 3. Фабрика AsyncSession\n",[3594,20521,20522],{"class":3596,"line":3739},[3594,20523,6541],{"class":4459},[3594,20525,20526,20528],{"class":3596,"line":3744},[3594,20527,6416],{"class":4493},[3594,20529,6548],{"class":4459},[3594,20531,20532,20534],{"class":3596,"line":3749},[3594,20533,6553],{"class":4493},[3594,20535,6556],{"class":4459},[3594,20537,20538,20540,20542,20544],{"class":3596,"line":3755},[3594,20539,6424],{"class":4493},[3594,20541,4497],{"class":4459},[3594,20543,6238],{"class":4622},[3594,20545,4504],{"class":4459},[3594,20547,20548,20550,20552,20554],{"class":3596,"line":3761},[3594,20549,6435],{"class":4493},[3594,20551,4497],{"class":4459},[3594,20553,6238],{"class":4622},[3594,20555,4504],{"class":4459},[3594,20557,20558,20560,20562,20564],{"class":3596,"line":3766},[3594,20559,6446],{"class":4493},[3594,20561,4497],{"class":4459},[3594,20563,6238],{"class":4622},[3594,20565,4504],{"class":4459},[3594,20567,20568],{"class":3596,"line":3772},[3594,20569,4558],{"class":4459},[3594,20571,20572],{"class":3596,"line":3778},[3594,20573,3631],{"emptyLinePlaceholder":3630},[3594,20575,20576],{"class":3596,"line":3784},[3594,20577,20578],{"class":4482},"# 4. Dependency для FastAPI — генератор сесії\n",[3594,20580,20581,20583,20585,20588,20591,20593],{"class":3596,"line":3790},[3594,20582,4856],{"class":4622},[3594,20584,4859],{"class":4622},[3594,20586,20587],{"class":4690}," get_async_session",[3594,20589,20590],{"class":4459},"() -> AsyncGenerator[AsyncSession, ",[3594,20592,9644],{"class":4622},[3594,20594,20595],{"class":4459},"]:\n",[3594,20597,20598],{"class":3596,"line":3796},[3594,20599,9362],{"class":4500},[3594,20601,20602],{"class":3596,"line":3802},[3594,20603,20604],{"class":4500},"    FastAPI Dependency: надає AsyncSession для одного HTTP-запиту.\n",[3594,20606,20607],{"class":3596,"line":3808},[3594,20608,20609],{"class":4500},"    Сесія автоматично закривається після завершення запиту.\n",[3594,20611,20612],{"class":3596,"line":3814},[3594,20613,20614],{"class":4500},"    У разі помилки — автоматичний rollback.\n",[3594,20616,20617],{"class":3596,"line":3820},[3594,20618,9362],{"class":4500},[3594,20620,20621,20623,20625,20627,20629],{"class":3596,"line":3826},[3594,20622,8595],{"class":4455},[3594,20624,6606],{"class":4455},[3594,20626,6609],{"class":4459},[3594,20628,4597],{"class":4455},[3594,20630,6478],{"class":4459},[3594,20632,20633,20635],{"class":3596,"line":3832},[3594,20634,19423],{"class":4455},[3594,20636,4570],{"class":4459},[3594,20638,20639,20642],{"class":3596,"line":3837},[3594,20640,20641],{"class":4455},"            yield",[3594,20643,20644],{"class":4459}," session\n",[3594,20646,20647,20649],{"class":3596,"line":4819},[3594,20648,19440],{"class":4455},[3594,20650,6641],{"class":4459},[3594,20652,20654,20656,20658],{"class":3596,"line":20653},44,[3594,20655,19451],{"class":4455},[3594,20657,4794],{"class":4793},[3594,20659,4570],{"class":4459},[3594,20661,20663,20665],{"class":3596,"line":20662},45,[3594,20664,19440],{"class":4455},[3594,20666,20667],{"class":4459}," session.rollback()\n",[3594,20669,20671],{"class":3596,"line":20670},46,[3594,20672,20673],{"class":4455},"            raise\n",[3385,20675,20676],{},"Тепер використаємо цей dependency у FastAPI-роутері:",[3585,20678,20681],{"className":4445,"code":20679,"filename":20680,"language":4448,"meta":3590,"style":3590},"from typing import Annotated\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom sqlalchemy import select\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\nfrom app.core.database import get_async_session\nfrom app.models.user import User\n# Схеми Pydantic для користувачів\nfrom app.schemas.user import UserCreate, UserRead\n\nrouter = APIRouter(prefix=\"\u002Fusers\", tags=[\"Users\"])\n\n# Зручний alias для типізованого параметра залежності\nSessionDep = Annotated[AsyncSession, Depends(get_async_session)]\n\n@router.get(\"\u002F{user_id}\", response_model=UserRead)\nasync def get_user(user_id: int, session: SessionDep):\n    \"\"\"Отримати користувача за ID.\"\"\"\n    user = await session.get(User, user_id)\n    if user is None:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=f\"User with id={user_id} not found\"\n        )\n    return user\n\n@router.get(\"\u002F\", response_model=list[UserRead])\nasync def list_users(\n    session: SessionDep,\n    skip: int = 0,\n    limit: int = 20,\n):\n    \"\"\"Отримати список користувачів з пагінацією.\"\"\"\n    users = (await session.execute(\n        select(User)\n        .offset(skip)\n        .limit(limit)\n        .order_by(User.created_at.desc())\n    )).scalars().all()\n    return users\n\n@router.post(\"\u002F\", response_model=UserRead, status_code=status.HTTP_201_CREATED)\nasync def create_user(user_in: UserCreate, session: SessionDep):\n    \"\"\"Створити нового користувача.\"\"\"\n    # Перевірка унікальності username\n    existing = (await session.execute(\n        select(User).where(User.username == user_in.username)\n    )).scalar_one_or_none()\n\n    if existing:\n        raise HTTPException(\n            status_code=status.HTTP_409_CONFLICT,\n            detail=f\"Username '{user_in.username}' already taken\"\n        )\n\n    user = User(**user_in.model_dump())\n    session.add(user)\n    await session.flush()  # Виконуємо INSERT, щоб отримати згенерований id\n    return user\n","routers\u002Fusers.py",[3412,20682,20683,20694,20706,20716,20727,20731,20743,20754,20759,20771,20775,20801,20805,20810,20815,20819,20842,20868,20873,20882,20896,20904,20912,20933,20937,20945,20949,20965,20977,20985,21000,21015,21019,21024,21033,21038,21043,21048,21053,21058,21065,21069,21091,21112,21117,21122,21131,21137,21143,21148,21156,21163,21171,21193,21198,21203,21209,21215,21225],{"__ignoreMap":3590},[3594,20684,20685,20687,20689,20691],{"class":3596,"line":3597},[3594,20686,4465],{"class":4455},[3594,20688,11470],{"class":4459},[3594,20690,4456],{"class":4455},[3594,20692,20693],{"class":4459}," Annotated\n",[3594,20695,20696,20698,20701,20703],{"class":3596,"line":3603},[3594,20697,4465],{"class":4455},[3594,20699,20700],{"class":4459}," fastapi ",[3594,20702,4456],{"class":4455},[3594,20704,20705],{"class":4459}," APIRouter, Depends, HTTPException, status\n",[3594,20707,20708,20710,20712,20714],{"class":3596,"line":3609},[3594,20709,4465],{"class":4455},[3594,20711,5996],{"class":4459},[3594,20713,4456],{"class":4455},[3594,20715,14571],{"class":4459},[3594,20717,20718,20720,20722,20724],{"class":3596,"line":3615},[3594,20719,4465],{"class":4455},[3594,20721,5782],{"class":4459},[3594,20723,4456],{"class":4455},[3594,20725,20726],{"class":4459}," AsyncSession\n",[3594,20728,20729],{"class":3596,"line":3621},[3594,20730,3631],{"emptyLinePlaceholder":3630},[3594,20732,20733,20735,20738,20740],{"class":3596,"line":3627},[3594,20734,4465],{"class":4455},[3594,20736,20737],{"class":4459}," app.core.database ",[3594,20739,4456],{"class":4455},[3594,20741,20742],{"class":4459}," get_async_session\n",[3594,20744,20745,20747,20750,20752],{"class":3596,"line":3634},[3594,20746,4465],{"class":4455},[3594,20748,20749],{"class":4459}," app.models.user ",[3594,20751,4456],{"class":4455},[3594,20753,11849],{"class":4459},[3594,20755,20756],{"class":3596,"line":3640},[3594,20757,20758],{"class":4482},"# Схеми Pydantic для користувачів\n",[3594,20760,20761,20763,20766,20768],{"class":3596,"line":3646},[3594,20762,4465],{"class":4455},[3594,20764,20765],{"class":4459}," app.schemas.user ",[3594,20767,4456],{"class":4455},[3594,20769,20770],{"class":4459}," UserCreate, UserRead\n",[3594,20772,20773],{"class":3596,"line":3652},[3594,20774,3631],{"emptyLinePlaceholder":3630},[3594,20776,20777,20780,20783,20785,20788,20790,20793,20796,20799],{"class":3596,"line":3658},[3594,20778,20779],{"class":4459},"router = APIRouter(",[3594,20781,20782],{"class":4493},"prefix",[3594,20784,4497],{"class":4459},[3594,20786,20787],{"class":4500},"\"\u002Fusers\"",[3594,20789,3488],{"class":4459},[3594,20791,20792],{"class":4493},"tags",[3594,20794,20795],{"class":4459},"=[",[3594,20797,20798],{"class":4500},"\"Users\"",[3594,20800,5008],{"class":4459},[3594,20802,20803],{"class":3596,"line":3664},[3594,20804,3631],{"emptyLinePlaceholder":3630},[3594,20806,20807],{"class":3596,"line":3670},[3594,20808,20809],{"class":4482},"# Зручний alias для типізованого параметра залежності\n",[3594,20811,20812],{"class":3596,"line":3676},[3594,20813,20814],{"class":4459},"SessionDep = Annotated[AsyncSession, Depends(get_async_session)]\n",[3594,20816,20817],{"class":3596,"line":3681},[3594,20818,3631],{"emptyLinePlaceholder":3630},[3594,20820,20821,20824,20826,20829,20832,20834,20836,20839],{"class":3596,"line":3687},[3594,20822,20823],{"class":4690},"@router.get",[3594,20825,7763],{"class":4459},[3594,20827,20828],{"class":4500},"\"\u002F",[3594,20830,20831],{"class":4622},"{user_id}",[3594,20833,4631],{"class":4500},[3594,20835,3488],{"class":4459},[3594,20837,20838],{"class":4493},"response_model",[3594,20840,20841],{"class":4459},"=UserRead)\n",[3594,20843,20844,20846,20848,20851,20853,20856,20858,20860,20862,20865],{"class":3596,"line":3693},[3594,20845,4856],{"class":4622},[3594,20847,4859],{"class":4622},[3594,20849,20850],{"class":4690}," get_user",[3594,20852,7763],{"class":4459},[3594,20854,20855],{"class":4493},"user_id",[3594,20857,9991],{"class":4459},[3594,20859,6930],{"class":4793},[3594,20861,3488],{"class":4459},[3594,20863,20864],{"class":4493},"session",[3594,20866,20867],{"class":4459},": SessionDep):\n",[3594,20869,20870],{"class":3596,"line":3699},[3594,20871,20872],{"class":4500},"    \"\"\"Отримати користувача за ID.\"\"\"\n",[3594,20874,20875,20877,20879],{"class":3596,"line":3705},[3594,20876,6618],{"class":4459},[3594,20878,4878],{"class":4455},[3594,20880,20881],{"class":4459}," session.get(User, user_id)\n",[3594,20883,20884,20887,20889,20891,20894],{"class":3596,"line":3711},[3594,20885,20886],{"class":4455},"    if",[3594,20888,13569],{"class":4459},[3594,20890,15478],{"class":4622},[3594,20892,20893],{"class":4622}," None",[3594,20895,4570],{"class":4459},[3594,20897,20898,20901],{"class":3596,"line":3716},[3594,20899,20900],{"class":4455},"        raise",[3594,20902,20903],{"class":4459}," HTTPException(\n",[3594,20905,20906,20909],{"class":3596,"line":3721},[3594,20907,20908],{"class":4493},"            status_code",[3594,20910,20911],{"class":4459},"=status.HTTP_404_NOT_FOUND,\n",[3594,20913,20914,20917,20919,20921,20924,20926,20928,20930],{"class":3596,"line":3727},[3594,20915,20916],{"class":4493},"            detail",[3594,20918,4497],{"class":4459},[3594,20920,8617],{"class":4622},[3594,20922,20923],{"class":4500},"\"User with id=",[3594,20925,8623],{"class":4622},[3594,20927,20855],{"class":4459},[3594,20929,8634],{"class":4622},[3594,20931,20932],{"class":4500}," not found\"\n",[3594,20934,20935],{"class":3596,"line":3733},[3594,20936,4657],{"class":4459},[3594,20938,20939,20942],{"class":3596,"line":3739},[3594,20940,20941],{"class":4455},"    return",[3594,20943,20944],{"class":4459}," user\n",[3594,20946,20947],{"class":3596,"line":3744},[3594,20948,3631],{"emptyLinePlaceholder":3630},[3594,20950,20951,20953,20955,20958,20960,20962],{"class":3596,"line":3749},[3594,20952,20823],{"class":4690},[3594,20954,7763],{"class":4459},[3594,20956,20957],{"class":4500},"\"\u002F\"",[3594,20959,3488],{"class":4459},[3594,20961,20838],{"class":4493},[3594,20963,20964],{"class":4459},"=list[UserRead])\n",[3594,20966,20967,20969,20971,20974],{"class":3596,"line":3755},[3594,20968,4856],{"class":4622},[3594,20970,4859],{"class":4622},[3594,20972,20973],{"class":4690}," list_users",[3594,20975,20976],{"class":4459},"(\n",[3594,20978,20979,20982],{"class":3596,"line":3761},[3594,20980,20981],{"class":4493},"    session",[3594,20983,20984],{"class":4459},": SessionDep,\n",[3594,20986,20987,20990,20992,20994,20996,20998],{"class":3596,"line":3766},[3594,20988,20989],{"class":4493},"    skip",[3594,20991,9991],{"class":4459},[3594,20993,6930],{"class":4793},[3594,20995,13939],{"class":4459},[3594,20997,10061],{"class":4514},[3594,20999,4504],{"class":4459},[3594,21001,21002,21005,21007,21009,21011,21013],{"class":3596,"line":3772},[3594,21003,21004],{"class":4493},"    limit",[3594,21006,9991],{"class":4459},[3594,21008,6930],{"class":4793},[3594,21010,13939],{"class":4459},[3594,21012,6061],{"class":4514},[3594,21014,4504],{"class":4459},[3594,21016,21017],{"class":3596,"line":3778},[3594,21018,8585],{"class":4459},[3594,21020,21021],{"class":3596,"line":3784},[3594,21022,21023],{"class":4500},"    \"\"\"Отримати список користувачів з пагінацією.\"\"\"\n",[3594,21025,21026,21029,21031],{"class":3596,"line":3790},[3594,21027,21028],{"class":4459},"    users = (",[3594,21030,4878],{"class":4455},[3594,21032,14100],{"class":4459},[3594,21034,21035],{"class":3596,"line":3796},[3594,21036,21037],{"class":4459},"        select(User)\n",[3594,21039,21040],{"class":3596,"line":3802},[3594,21041,21042],{"class":4459},"        .offset(skip)\n",[3594,21044,21045],{"class":3596,"line":3808},[3594,21046,21047],{"class":4459},"        .limit(limit)\n",[3594,21049,21050],{"class":3596,"line":3814},[3594,21051,21052],{"class":4459},"        .order_by(User.created_at.desc())\n",[3594,21054,21055],{"class":3596,"line":3820},[3594,21056,21057],{"class":4459},"    )).scalars().all()\n",[3594,21059,21060,21062],{"class":3596,"line":3826},[3594,21061,20941],{"class":4455},[3594,21063,21064],{"class":4459}," users\n",[3594,21066,21067],{"class":3596,"line":3832},[3594,21068,3631],{"emptyLinePlaceholder":3630},[3594,21070,21071,21074,21076,21078,21080,21082,21085,21088],{"class":3596,"line":3837},[3594,21072,21073],{"class":4690},"@router.post",[3594,21075,7763],{"class":4459},[3594,21077,20957],{"class":4500},[3594,21079,3488],{"class":4459},[3594,21081,20838],{"class":4493},[3594,21083,21084],{"class":4459},"=UserRead, ",[3594,21086,21087],{"class":4493},"status_code",[3594,21089,21090],{"class":4459},"=status.HTTP_201_CREATED)\n",[3594,21092,21093,21095,21097,21100,21102,21105,21108,21110],{"class":3596,"line":4819},[3594,21094,4856],{"class":4622},[3594,21096,4859],{"class":4622},[3594,21098,21099],{"class":4690}," create_user",[3594,21101,7763],{"class":4459},[3594,21103,21104],{"class":4493},"user_in",[3594,21106,21107],{"class":4459},": UserCreate, ",[3594,21109,20864],{"class":4493},[3594,21111,20867],{"class":4459},[3594,21113,21114],{"class":3596,"line":20653},[3594,21115,21116],{"class":4500},"    \"\"\"Створити нового користувача.\"\"\"\n",[3594,21118,21119],{"class":3596,"line":20662},[3594,21120,21121],{"class":4482},"    # Перевірка унікальності username\n",[3594,21123,21124,21127,21129],{"class":3596,"line":20670},[3594,21125,21126],{"class":4459},"    existing = (",[3594,21128,4878],{"class":4455},[3594,21130,14100],{"class":4459},[3594,21132,21134],{"class":3596,"line":21133},47,[3594,21135,21136],{"class":4459},"        select(User).where(User.username == user_in.username)\n",[3594,21138,21140],{"class":3596,"line":21139},48,[3594,21141,21142],{"class":4459},"    )).scalar_one_or_none()\n",[3594,21144,21146],{"class":3596,"line":21145},49,[3594,21147,3631],{"emptyLinePlaceholder":3630},[3594,21149,21151,21153],{"class":3596,"line":21150},50,[3594,21152,20886],{"class":4455},[3594,21154,21155],{"class":4459}," existing:\n",[3594,21157,21159,21161],{"class":3596,"line":21158},51,[3594,21160,20900],{"class":4455},[3594,21162,20903],{"class":4459},[3594,21164,21166,21168],{"class":3596,"line":21165},52,[3594,21167,20908],{"class":4493},[3594,21169,21170],{"class":4459},"=status.HTTP_409_CONFLICT,\n",[3594,21172,21174,21176,21178,21180,21183,21185,21188,21190],{"class":3596,"line":21173},53,[3594,21175,20916],{"class":4493},[3594,21177,4497],{"class":4459},[3594,21179,8617],{"class":4622},[3594,21181,21182],{"class":4500},"\"Username '",[3594,21184,8623],{"class":4622},[3594,21186,21187],{"class":4459},"user_in.username",[3594,21189,8634],{"class":4622},[3594,21191,21192],{"class":4500},"' already taken\"\n",[3594,21194,21196],{"class":3596,"line":21195},54,[3594,21197,4657],{"class":4459},[3594,21199,21201],{"class":3596,"line":21200},55,[3594,21202,3631],{"emptyLinePlaceholder":3630},[3594,21204,21206],{"class":3596,"line":21205},56,[3594,21207,21208],{"class":4459},"    user = User(**user_in.model_dump())\n",[3594,21210,21212],{"class":3596,"line":21211},57,[3594,21213,21214],{"class":4459},"    session.add(user)\n",[3594,21216,21218,21220,21222],{"class":3596,"line":21217},58,[3594,21219,6638],{"class":4455},[3594,21221,19443],{"class":4459},[3594,21223,21224],{"class":4482},"# Виконуємо INSERT, щоб отримати згенерований id\n",[3594,21226,21228,21230],{"class":3596,"line":21227},59,[3594,21229,20941],{"class":4455},[3594,21231,20944],{"class":4459},[5216,21233,21234,21235,21238,21239,21241,21242,21244,21245,21247,21248,21251,21252,21254,21255,21257,21258,21261],{},"Зверніть на ",[3412,21236,21237],{},"await session.flush()"," перед поверненням ",[3412,21240,4915],{},". Виклик ",[3412,21243,7911],{}," відправляє ",[3412,21246,7659],{}," до PostgreSQL у межах поточної транзакції (але ",[3389,21249,21250],{},"не комітить"," її), що дозволяє отримати згенерований автоінкрементний ",[3412,21253,7949],{},". Сам ",[3412,21256,7672],{}," відбудеться автоматично у dependency-генераторі ",[3412,21259,21260],{},"get_async_session()"," при успішному завершенні запиту.",[3845,21263,21265],{"id":21264},"ініціалізація-таблиць-при-старті-додатку","Ініціалізація таблиць при старті додатку",[3385,21267,21268],{},"Для розробки та тестування зручно автоматично створювати всі таблиці при старті FastAPI:",[3585,21270,21273],{"className":4445,"code":21271,"filename":21272,"language":4448,"meta":3590,"style":3590},"from contextlib import asynccontextmanager\nfrom fastapi import FastAPI\nfrom app.core.database import async_engine, Base\n\n# Імпортуємо всі моделі, щоб Base.metadata їх «знала»\nfrom app.models import user, post, project  # noqa: F401\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    \"\"\"Lifecycle-хук FastAPI: виконується при старті та зупинці.\"\"\"\n    # Старт: створюємо всі таблиці (лише для розробки!)\n    async with async_engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n    yield\n    # Зупинка: закриваємо пул підключень\n    await async_engine.dispose()\n\napp = FastAPI(title=\"TaskForge API\", lifespan=lifespan)\n","main.py",[3412,21274,21275,21287,21298,21309,21313,21318,21333,21337,21342,21359,21364,21369,21383,21390,21395,21400,21407,21411],{"__ignoreMap":3590},[3594,21276,21277,21279,21282,21284],{"class":3596,"line":3597},[3594,21278,4465],{"class":4455},[3594,21280,21281],{"class":4459}," contextlib ",[3594,21283,4456],{"class":4455},[3594,21285,21286],{"class":4459}," asynccontextmanager\n",[3594,21288,21289,21291,21293,21295],{"class":3596,"line":3603},[3594,21290,4465],{"class":4455},[3594,21292,20700],{"class":4459},[3594,21294,4456],{"class":4455},[3594,21296,21297],{"class":4459}," FastAPI\n",[3594,21299,21300,21302,21304,21306],{"class":3596,"line":3609},[3594,21301,4465],{"class":4455},[3594,21303,20737],{"class":4459},[3594,21305,4456],{"class":4455},[3594,21307,21308],{"class":4459}," async_engine, Base\n",[3594,21310,21311],{"class":3596,"line":3615},[3594,21312,3631],{"emptyLinePlaceholder":3630},[3594,21314,21315],{"class":3596,"line":3621},[3594,21316,21317],{"class":4482},"# Імпортуємо всі моделі, щоб Base.metadata їх «знала»\n",[3594,21319,21320,21322,21325,21327,21330],{"class":3596,"line":3627},[3594,21321,4465],{"class":4455},[3594,21323,21324],{"class":4459}," app.models ",[3594,21326,4456],{"class":4455},[3594,21328,21329],{"class":4459}," user, post, project  ",[3594,21331,21332],{"class":4482},"# noqa: F401\n",[3594,21334,21335],{"class":3596,"line":3634},[3594,21336,3631],{"emptyLinePlaceholder":3630},[3594,21338,21339],{"class":3596,"line":3640},[3594,21340,21341],{"class":4690},"@asynccontextmanager\n",[3594,21343,21344,21346,21348,21351,21353,21356],{"class":3596,"line":3646},[3594,21345,4856],{"class":4622},[3594,21347,4859],{"class":4622},[3594,21349,21350],{"class":4690}," lifespan",[3594,21352,7763],{"class":4459},[3594,21354,21355],{"class":4493},"app",[3594,21357,21358],{"class":4459},": FastAPI):\n",[3594,21360,21361],{"class":3596,"line":3652},[3594,21362,21363],{"class":4500},"    \"\"\"Lifecycle-хук FastAPI: виконується при старті та зупинці.\"\"\"\n",[3594,21365,21366],{"class":3596,"line":3658},[3594,21367,21368],{"class":4482},"    # Старт: створюємо всі таблиці (лише для розробки!)\n",[3594,21370,21371,21373,21375,21378,21380],{"class":3596,"line":3664},[3594,21372,8595],{"class":4455},[3594,21374,6606],{"class":4455},[3594,21376,21377],{"class":4459}," async_engine.begin() ",[3594,21379,4597],{"class":4455},[3594,21381,21382],{"class":4459}," conn:\n",[3594,21384,21385,21387],{"class":3596,"line":3670},[3594,21386,5056],{"class":4455},[3594,21388,21389],{"class":4459}," conn.run_sync(Base.metadata.create_all)\n",[3594,21391,21392],{"class":3596,"line":3676},[3594,21393,21394],{"class":4455},"    yield\n",[3594,21396,21397],{"class":3596,"line":3681},[3594,21398,21399],{"class":4482},"    # Зупинка: закриваємо пул підключень\n",[3594,21401,21402,21404],{"class":3596,"line":3687},[3594,21403,6638],{"class":4455},[3594,21405,21406],{"class":4459}," async_engine.dispose()\n",[3594,21408,21409],{"class":3596,"line":3693},[3594,21410,3631],{"emptyLinePlaceholder":3630},[3594,21412,21413,21416,21419,21421,21424,21426,21429],{"class":3596,"line":3699},[3594,21414,21415],{"class":4459},"app = FastAPI(",[3594,21417,21418],{"class":4493},"title",[3594,21420,4497],{"class":4459},[3594,21422,21423],{"class":4500},"\"TaskForge API\"",[3594,21425,3488],{"class":4459},[3594,21427,21428],{"class":4493},"lifespan",[3594,21430,21431],{"class":4459},"=lifespan)\n",[21433,21434,21435,21438,21439,21442,21443,21446],"caution",{},[3412,21436,21437],{},"Base.metadata.create_all()"," — зручний інструмент для локальної розробки та тестів, але він ",[3389,21440,21441],{},"категорично не підходить для production",". Він лише створює таблиці, що не існують, але не вміє змінювати існуючі (додавати стовпці, змінювати типи, видаляти поля). Для управління схемою бази даних у production використовується ",[3389,21444,21445],{},"Alembic"," — інструмент міграцій, якому присвячена наступна стаття 23.",[3444,21448,21450],{"id":21449},"практичний-приклад-від-а-до-я-самодостатній-скрипт","Практичний приклад від А до Я: Самодостатній скрипт",[3385,21452,21453],{},"Для того щоб ви могли швидко запустити та протестувати всі концепції async SQLAlchemy 2.0 в одному місці без потреби встановлювати PostgreSQL чи конфігурувати FastAPI, нижче наведено повністю самодостатній, працездатний асинхронний скрипт.",[3385,21455,21456,21457,21459,21460,21463],{},"Він використовує вбудовану в Python базу даних ",[3389,21458,5360],{}," в оперативній пам'яті (",[3412,21461,21462],{},":memory:","), створює таблиці, виконує CRUD-операції, оптимізує завантаження зв'язків та виводить результати у консоль.",[3385,21465,21466,21467,21469,21470,4442],{},"Для запуску вам знадобляться встановлені бібліотеки ",[3412,21468,5258],{}," та асинхронний драйвер ",[3412,21471,5376],{},[3585,21473,21475],{"className":5172,"code":21474,"language":5174,"meta":3590,"style":3590},"pip install sqlalchemy aiosqlite\n",[3412,21476,21477],{"__ignoreMap":3590},[3594,21478,21479,21481,21483,21485],{"class":3596,"line":3597},[3594,21480,5181],{"class":4690},[3594,21482,5184],{"class":4500},[3594,21484,5234],{"class":4500},[3594,21486,21487],{"class":4500}," aiosqlite\n",[5430,21489,21491],{"id":21490},"mainpy",[3412,21492,21272],{},[3585,21494,21496],{"className":4445,"code":21495,"language":4448,"meta":3590,"style":3590},"import asyncio\nfrom datetime import datetime\nfrom sqlalchemy import String, ForeignKey, select\nfrom sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, selectinload\n\n# 1. Створюємо базовий клас\nclass Base(DeclarativeBase):\n    pass\n\n# 2. Визначаємо моделі (зв'язок Один до Багатьох: User -> Post)\nclass User(Base):\n    __tablename__ = \"users\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    username: Mapped[str] = mapped_column(String(50), unique=True)\n    email: Mapped[str] = mapped_column(String(100), unique=True)\n\n    posts: Mapped[list[\"Post\"]] = relationship(\n        back_populates=\"author\", cascade=\"all, delete-orphan\"\n    )\n\n    def __repr__(self) -> str:\n        return f\"\u003CUser id={self.id} username={self.username!r}>\"\n\nclass Post(Base):\n    __tablename__ = \"posts\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    title: Mapped[str] = mapped_column(String(200))\n    content: Mapped[str] = mapped_column(String)\n    created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)\n    author_id: Mapped[int] = mapped_column(ForeignKey(\"users.id\"))\n\n    author: Mapped[\"User\"] = relationship(back_populates=\"posts\")\n\n    def __repr__(self) -> str:\n        return f\"\u003CPost id={self.id} title={self.title!r}>\"\n\n# 3. Налаштовуємо Async Engine та Session local фабрику\n# Використовуємо SQLite в пам'яті з асинхронним драйвером aiosqlite\nDATABASE_URL = \"sqlite+aiosqlite:\u002F\u002F\u002F:memory:\"\n\nasync_engine = create_async_engine(DATABASE_URL, echo=True)  # echo=True покаже згенерований SQL\nAsyncSessionLocal = async_sessionmaker(\n    bind=async_engine, class_=AsyncSession, expire_on_commit=False\n)\n\nasync def main():\n    # 4. Створюємо таблиці у базі даних\n    async with async_engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n    async with AsyncSessionLocal() as session:\n        print(\"\\n--- 1. Створення записів (INSERT) ---\")\n        # Створюємо користувача та його пости\n        user = User(\n            username=\"arakviel\",\n            email=\"arakviel@example.com\",\n            posts=[\n                Post(title=\"Перший async пост\", content=\"Вміст першого асинхронного посту\"),\n                Post(title=\"Другий async пост\", content=\"Вміст другого асинхронного посту\")\n            ]\n        )\n        session.add(user)\n        await session.commit()\n        print(f\"Збережено користувача: {user} (ID: {user.id})\")\n\n        print(\"\\n--- 2. Читання записів з Eager Loading (SELECT з selectinload) ---\")\n        # selectinload(User.posts) запобігає N+1 проблемі для завантаження списку постів\n        stmt = select(User).where(User.username == \"arakviel\").options(selectinload(User.posts))\n        db_user = (await session.execute(stmt)).scalars().one()\n\n        print(f\"Знайдено користувача: {db_user.username}\")\n        for post in db_user.posts:\n            print(f\" - Пост: {post.title}\")\n\n        print(\"\\n--- 3. Оновлення запису (UPDATE) ---\")\n        # Оновимо заголовок першого посту\n        first_post = db_user.posts[0]\n        first_post.title = \"Оновлена назва async посту\"\n        await session.commit()\n        print(f\"Нова назва посту в БД: {first_post.title}\")\n\n        print(\"\\n--- 4. Видалення запису (DELETE) ---\")\n        # Видалення користувача призведе до каскадного видалення всіх його постів\n        await session.delete(db_user)\n        await session.commit()\n\n        # Перевіряємо, чи залишились пости у базі\n        posts_stmt = select(Post)\n        remaining_posts = (await session.execute(posts_stmt)).scalars().all()\n        print(f\"Пости в базі даних після видалення користувача: {remaining_posts}\")\n\n    # Закриваємо з'єднання з БД\n    await async_engine.dispose()\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n",[3412,21497,21498,21505,21515,21526,21536,21547,21551,21556,21568,21572,21576,21581,21593,21599,21603,21621,21641,21661,21665,21673,21690,21694,21698,21714,21738,21742,21754,21760,21764,21782,21794,21803,21813,21825,21829,21845,21849,21865,21892,21896,21901,21906,21913,21917,21934,21938,21955,21959,21963,21973,21978,21990,21996,22000,22012,22029,22034,22039,22050,22062,22071,22096,22119,22125,22130,22136,22143,22173,22178,22194,22200,22211,22222,22227,22250,22262,22284,22289,22305,22311,22321,22330,22337,22360,22365,22381,22387,22395,22402,22407,22413,22419,22430,22453,22458,22464,22471,22476,22492],{"__ignoreMap":3590},[3594,21499,21500,21502],{"class":3596,"line":3597},[3594,21501,4456],{"class":4455},[3594,21503,21504],{"class":4459}," asyncio\n",[3594,21506,21507,21509,21511,21513],{"class":3596,"line":3603},[3594,21508,4465],{"class":4455},[3594,21510,9465],{"class":4459},[3594,21512,4456],{"class":4455},[3594,21514,9470],{"class":4459},[3594,21516,21517,21519,21521,21523],{"class":3596,"line":3609},[3594,21518,4465],{"class":4455},[3594,21520,5996],{"class":4459},[3594,21522,4456],{"class":4455},[3594,21524,21525],{"class":4459}," String, ForeignKey, select\n",[3594,21527,21528,21530,21532,21534],{"class":3596,"line":3615},[3594,21529,4465],{"class":4455},[3594,21531,5782],{"class":4459},[3594,21533,4456],{"class":4455},[3594,21535,6518],{"class":4459},[3594,21537,21538,21540,21542,21544],{"class":3596,"line":3621},[3594,21539,4465],{"class":4455},[3594,21541,6383],{"class":4459},[3594,21543,4456],{"class":4455},[3594,21545,21546],{"class":4459}," DeclarativeBase, Mapped, mapped_column, relationship, selectinload\n",[3594,21548,21549],{"class":3596,"line":3627},[3594,21550,3631],{"emptyLinePlaceholder":3630},[3594,21552,21553],{"class":3596,"line":3634},[3594,21554,21555],{"class":4482},"# 1. Створюємо базовий клас\n",[3594,21557,21558,21560,21562,21564,21566],{"class":3596,"line":3640},[3594,21559,8575],{"class":4622},[3594,21561,9127],{"class":4793},[3594,21563,7763],{"class":4459},[3594,21565,3931],{"class":4793},[3594,21567,8585],{"class":4459},[3594,21569,21570],{"class":3596,"line":3646},[3594,21571,9142],{"class":4455},[3594,21573,21574],{"class":3596,"line":3652},[3594,21575,3631],{"emptyLinePlaceholder":3630},[3594,21577,21578],{"class":3596,"line":3658},[3594,21579,21580],{"class":4482},"# 2. Визначаємо моделі (зв'язок Один до Багатьох: User -> Post)\n",[3594,21582,21583,21585,21587,21589,21591],{"class":3596,"line":3664},[3594,21584,8575],{"class":4622},[3594,21586,8960],{"class":4793},[3594,21588,7763],{"class":4459},[3594,21590,8965],{"class":4793},[3594,21592,8585],{"class":4459},[3594,21594,21595,21597],{"class":3596,"line":3670},[3594,21596,8972],{"class":4459},[3594,21598,8975],{"class":4500},[3594,21600,21601],{"class":3596,"line":3676},[3594,21602,3631],{"emptyLinePlaceholder":3630},[3594,21604,21605,21607,21609,21611,21613,21615,21617,21619],{"class":3596,"line":3681},[3594,21606,8984],{"class":4690},[3594,21608,9175],{"class":4459},[3594,21610,6930],{"class":4793},[3594,21612,9180],{"class":4459},[3594,21614,8990],{"class":4493},[3594,21616,4497],{"class":4459},[3594,21618,4641],{"class":4622},[3594,21620,4558],{"class":4459},[3594,21622,21623,21625,21627,21629,21631,21633,21635,21637,21639],{"class":3596,"line":3687},[3594,21624,9193],{"class":4459},[3594,21626,9196],{"class":4793},[3594,21628,9199],{"class":4459},[3594,21630,9013],{"class":4514},[3594,21632,9016],{"class":4459},[3594,21634,9019],{"class":4493},[3594,21636,4497],{"class":4459},[3594,21638,4641],{"class":4622},[3594,21640,4558],{"class":4459},[3594,21642,21643,21645,21647,21649,21651,21653,21655,21657,21659],{"class":3596,"line":3693},[3594,21644,9216],{"class":4459},[3594,21646,9196],{"class":4793},[3594,21648,9199],{"class":4459},[3594,21650,9042],{"class":4514},[3594,21652,9016],{"class":4459},[3594,21654,9019],{"class":4493},[3594,21656,4497],{"class":4459},[3594,21658,4641],{"class":4622},[3594,21660,4558],{"class":4459},[3594,21662,21663],{"class":3596,"line":3699},[3594,21664,3631],{"emptyLinePlaceholder":3630},[3594,21666,21667,21669,21671],{"class":3596,"line":3705},[3594,21668,11600],{"class":4459},[3594,21670,11159],{"class":4500},[3594,21672,11605],{"class":4459},[3594,21674,21675,21677,21679,21681,21683,21685,21687],{"class":3596,"line":3711},[3594,21676,11617],{"class":4493},[3594,21678,4497],{"class":4459},[3594,21680,11622],{"class":4500},[3594,21682,3488],{"class":4459},[3594,21684,11167],{"class":4493},[3594,21686,4497],{"class":4459},[3594,21688,21689],{"class":4500},"\"all, delete-orphan\"\n",[3594,21691,21692],{"class":3596,"line":3716},[3594,21693,4935],{"class":4459},[3594,21695,21696],{"class":3596,"line":3721},[3594,21697,3631],{"emptyLinePlaceholder":3630},[3594,21699,21700,21702,21704,21706,21708,21710,21712],{"class":3596,"line":3727},[3594,21701,9725],{"class":4622},[3594,21703,9728],{"class":4690},[3594,21705,7763],{"class":4459},[3594,21707,8605],{"class":4493},[3594,21709,9735],{"class":4459},[3594,21711,9196],{"class":4793},[3594,21713,4570],{"class":4459},[3594,21715,21716,21718,21720,21722,21724,21726,21728,21730,21732,21734,21736],{"class":3596,"line":3733},[3594,21717,9744],{"class":4455},[3594,21719,9747],{"class":4622},[3594,21721,9750],{"class":4500},[3594,21723,9753],{"class":4622},[3594,21725,9756],{"class":4459},[3594,21727,8634],{"class":4622},[3594,21729,9761],{"class":4500},[3594,21731,9753],{"class":4622},[3594,21733,9766],{"class":4459},[3594,21735,9769],{"class":4622},[3594,21737,9772],{"class":4500},[3594,21739,21740],{"class":3596,"line":3739},[3594,21741,3631],{"emptyLinePlaceholder":3630},[3594,21743,21744,21746,21748,21750,21752],{"class":3596,"line":3744},[3594,21745,8575],{"class":4622},[3594,21747,11653],{"class":4793},[3594,21749,7763],{"class":4459},[3594,21751,8965],{"class":4793},[3594,21753,8585],{"class":4459},[3594,21755,21756,21758],{"class":3596,"line":3749},[3594,21757,8972],{"class":4459},[3594,21759,11666],{"class":4500},[3594,21761,21762],{"class":3596,"line":3755},[3594,21763,3631],{"emptyLinePlaceholder":3630},[3594,21765,21766,21768,21770,21772,21774,21776,21778,21780],{"class":3596,"line":3761},[3594,21767,8984],{"class":4690},[3594,21769,9175],{"class":4459},[3594,21771,6930],{"class":4793},[3594,21773,9180],{"class":4459},[3594,21775,8990],{"class":4493},[3594,21777,4497],{"class":4459},[3594,21779,4641],{"class":4622},[3594,21781,4558],{"class":4459},[3594,21783,21784,21786,21788,21790,21792],{"class":3596,"line":3766},[3594,21785,11693],{"class":4459},[3594,21787,9196],{"class":4793},[3594,21789,9199],{"class":4459},[3594,21791,11700],{"class":4514},[3594,21793,7851],{"class":4459},[3594,21795,21796,21798,21800],{"class":3596,"line":3772},[3594,21797,11707],{"class":4459},[3594,21799,9196],{"class":4793},[3594,21801,21802],{"class":4459},"] = mapped_column(String)\n",[3594,21804,21805,21808,21810],{"class":3596,"line":3778},[3594,21806,21807],{"class":4459},"    created_at: Mapped[datetime] = mapped_column(",[3594,21809,9068],{"class":4493},[3594,21811,21812],{"class":4459},"=datetime.utcnow)\n",[3594,21814,21815,21817,21819,21821,21823],{"class":3596,"line":3784},[3594,21816,11729],{"class":4459},[3594,21818,6930],{"class":4793},[3594,21820,11734],{"class":4459},[3594,21822,11325],{"class":4500},[3594,21824,7851],{"class":4459},[3594,21826,21827],{"class":3596,"line":3790},[3594,21828,3631],{"emptyLinePlaceholder":3630},[3594,21830,21831,21833,21835,21837,21839,21841,21843],{"class":3596,"line":3796},[3594,21832,11752],{"class":4459},[3594,21834,11755],{"class":4500},[3594,21836,11758],{"class":4459},[3594,21838,11163],{"class":4493},[3594,21840,4497],{"class":4459},[3594,21842,11769],{"class":4500},[3594,21844,4558],{"class":4459},[3594,21846,21847],{"class":3596,"line":3802},[3594,21848,3631],{"emptyLinePlaceholder":3630},[3594,21850,21851,21853,21855,21857,21859,21861,21863],{"class":3596,"line":3808},[3594,21852,9725],{"class":4622},[3594,21854,9728],{"class":4690},[3594,21856,7763],{"class":4459},[3594,21858,8605],{"class":4493},[3594,21860,9735],{"class":4459},[3594,21862,9196],{"class":4793},[3594,21864,4570],{"class":4459},[3594,21866,21867,21869,21871,21874,21876,21878,21880,21883,21885,21888,21890],{"class":3596,"line":3814},[3594,21868,9744],{"class":4455},[3594,21870,9747],{"class":4622},[3594,21872,21873],{"class":4500},"\"\u003CPost id=",[3594,21875,9753],{"class":4622},[3594,21877,9756],{"class":4459},[3594,21879,8634],{"class":4622},[3594,21881,21882],{"class":4500}," title=",[3594,21884,9753],{"class":4622},[3594,21886,21887],{"class":4459},".title",[3594,21889,9769],{"class":4622},[3594,21891,9772],{"class":4500},[3594,21893,21894],{"class":3596,"line":3820},[3594,21895,3631],{"emptyLinePlaceholder":3630},[3594,21897,21898],{"class":3596,"line":3826},[3594,21899,21900],{"class":4482},"# 3. Налаштовуємо Async Engine та Session local фабрику\n",[3594,21902,21903],{"class":3596,"line":3832},[3594,21904,21905],{"class":4482},"# Використовуємо SQLite в пам'яті з асинхронним драйвером aiosqlite\n",[3594,21907,21908,21910],{"class":3596,"line":3837},[3594,21909,6015],{"class":4459},[3594,21911,21912],{"class":4500},"\"sqlite+aiosqlite:\u002F\u002F\u002F:memory:\"\n",[3594,21914,21915],{"class":3596,"line":4819},[3594,21916,3631],{"emptyLinePlaceholder":3630},[3594,21918,21919,21922,21925,21927,21929,21931],{"class":3596,"line":20653},[3594,21920,21921],{"class":4459},"async_engine = create_async_engine(DATABASE_URL, ",[3594,21923,21924],{"class":4493},"echo",[3594,21926,4497],{"class":4459},[3594,21928,4641],{"class":4622},[3594,21930,13901],{"class":4459},[3594,21932,21933],{"class":4482},"# echo=True покаже згенерований SQL\n",[3594,21935,21936],{"class":3596,"line":20662},[3594,21937,6541],{"class":4459},[3594,21939,21940,21942,21944,21946,21949,21951,21953],{"class":3596,"line":20670},[3594,21941,6416],{"class":4493},[3594,21943,8349],{"class":4459},[3594,21945,8533],{"class":4493},[3594,21947,21948],{"class":4459},"=AsyncSession, ",[3594,21950,8190],{"class":4493},[3594,21952,4497],{"class":4459},[3594,21954,6496],{"class":4622},[3594,21956,21957],{"class":3596,"line":21133},[3594,21958,4558],{"class":4459},[3594,21960,21961],{"class":3596,"line":21139},[3594,21962,3631],{"emptyLinePlaceholder":3630},[3594,21964,21965,21967,21969,21971],{"class":3596,"line":21145},[3594,21966,4856],{"class":4622},[3594,21968,4859],{"class":4622},[3594,21970,4862],{"class":4690},[3594,21972,4865],{"class":4459},[3594,21974,21975],{"class":3596,"line":21150},[3594,21976,21977],{"class":4482},"    # 4. Створюємо таблиці у базі даних\n",[3594,21979,21980,21982,21984,21986,21988],{"class":3596,"line":21158},[3594,21981,8595],{"class":4455},[3594,21983,6606],{"class":4455},[3594,21985,21377],{"class":4459},[3594,21987,4597],{"class":4455},[3594,21989,21382],{"class":4459},[3594,21991,21992,21994],{"class":3596,"line":21165},[3594,21993,5056],{"class":4455},[3594,21995,21389],{"class":4459},[3594,21997,21998],{"class":3596,"line":21173},[3594,21999,3631],{"emptyLinePlaceholder":3630},[3594,22001,22002,22004,22006,22008,22010],{"class":3596,"line":21195},[3594,22003,8595],{"class":4455},[3594,22005,6606],{"class":4455},[3594,22007,6609],{"class":4459},[3594,22009,4597],{"class":4455},[3594,22011,6478],{"class":4459},[3594,22013,22014,22016,22018,22020,22024,22027],{"class":3596,"line":21200},[3594,22015,8612],{"class":4690},[3594,22017,7763],{"class":4459},[3594,22019,4631],{"class":4500},[3594,22021,22023],{"class":22022},"sjcCO","\\n",[3594,22025,22026],{"class":4500},"--- 1. Створення записів (INSERT) ---\"",[3594,22028,4558],{"class":4459},[3594,22030,22031],{"class":3596,"line":21205},[3594,22032,22033],{"class":4482},"        # Створюємо користувача та його пости\n",[3594,22035,22036],{"class":3596,"line":21211},[3594,22037,22038],{"class":4459},"        user = User(\n",[3594,22040,22041,22044,22046,22048],{"class":3596,"line":21217},[3594,22042,22043],{"class":4493},"            username",[3594,22045,4497],{"class":4459},[3594,22047,14595],{"class":4500},[3594,22049,4504],{"class":4459},[3594,22051,22052,22055,22057,22060],{"class":3596,"line":21227},[3594,22053,22054],{"class":4493},"            email",[3594,22056,4497],{"class":4459},[3594,22058,22059],{"class":4500},"\"arakviel@example.com\"",[3594,22061,4504],{"class":4459},[3594,22063,22065,22068],{"class":3596,"line":22064},60,[3594,22066,22067],{"class":4493},"            posts",[3594,22069,22070],{"class":4459},"=[\n",[3594,22072,22074,22077,22079,22081,22084,22086,22089,22091,22094],{"class":3596,"line":22073},61,[3594,22075,22076],{"class":4459},"                Post(",[3594,22078,21418],{"class":4493},[3594,22080,4497],{"class":4459},[3594,22082,22083],{"class":4500},"\"Перший async пост\"",[3594,22085,3488],{"class":4459},[3594,22087,22088],{"class":4493},"content",[3594,22090,4497],{"class":4459},[3594,22092,22093],{"class":4500},"\"Вміст першого асинхронного посту\"",[3594,22095,4753],{"class":4459},[3594,22097,22099,22101,22103,22105,22108,22110,22112,22114,22117],{"class":3596,"line":22098},62,[3594,22100,22076],{"class":4459},[3594,22102,21418],{"class":4493},[3594,22104,4497],{"class":4459},[3594,22106,22107],{"class":4500},"\"Другий async пост\"",[3594,22109,3488],{"class":4459},[3594,22111,22088],{"class":4493},[3594,22113,4497],{"class":4459},[3594,22115,22116],{"class":4500},"\"Вміст другого асинхронного посту\"",[3594,22118,4558],{"class":4459},[3594,22120,22122],{"class":3596,"line":22121},63,[3594,22123,22124],{"class":4459},"            ]\n",[3594,22126,22128],{"class":3596,"line":22127},64,[3594,22129,4657],{"class":4459},[3594,22131,22133],{"class":3596,"line":22132},65,[3594,22134,22135],{"class":4459},"        session.add(user)\n",[3594,22137,22139,22141],{"class":3596,"line":22138},66,[3594,22140,5056],{"class":4455},[3594,22142,6641],{"class":4459},[3594,22144,22146,22148,22150,22152,22155,22157,22159,22161,22163,22165,22167,22169,22171],{"class":3596,"line":22145},67,[3594,22147,8612],{"class":4690},[3594,22149,7763],{"class":4459},[3594,22151,8617],{"class":4622},[3594,22153,22154],{"class":4500},"\"Збережено користувача: ",[3594,22156,8623],{"class":4622},[3594,22158,4915],{"class":4459},[3594,22160,8634],{"class":4622},[3594,22162,17287],{"class":4500},[3594,22164,8623],{"class":4622},[3594,22166,17292],{"class":4459},[3594,22168,8634],{"class":4622},[3594,22170,17297],{"class":4500},[3594,22172,4558],{"class":4459},[3594,22174,22176],{"class":3596,"line":22175},68,[3594,22177,3631],{"emptyLinePlaceholder":3630},[3594,22179,22181,22183,22185,22187,22189,22192],{"class":3596,"line":22180},69,[3594,22182,8612],{"class":4690},[3594,22184,7763],{"class":4459},[3594,22186,4631],{"class":4500},[3594,22188,22023],{"class":22022},[3594,22190,22191],{"class":4500},"--- 2. Читання записів з Eager Loading (SELECT з selectinload) ---\"",[3594,22193,4558],{"class":4459},[3594,22195,22197],{"class":3596,"line":22196},70,[3594,22198,22199],{"class":4482},"        # selectinload(User.posts) запобігає N+1 проблемі для завантаження списку постів\n",[3594,22201,22203,22206,22208],{"class":3596,"line":22202},71,[3594,22204,22205],{"class":4459},"        stmt = select(User).where(User.username == ",[3594,22207,14595],{"class":4500},[3594,22209,22210],{"class":4459},").options(selectinload(User.posts))\n",[3594,22212,22214,22217,22219],{"class":3596,"line":22213},72,[3594,22215,22216],{"class":4459},"        db_user = (",[3594,22218,4878],{"class":4455},[3594,22220,22221],{"class":4459}," session.execute(stmt)).scalars().one()\n",[3594,22223,22225],{"class":3596,"line":22224},73,[3594,22226,3631],{"emptyLinePlaceholder":3630},[3594,22228,22230,22232,22234,22236,22239,22241,22244,22246,22248],{"class":3596,"line":22229},74,[3594,22231,8612],{"class":4690},[3594,22233,7763],{"class":4459},[3594,22235,8617],{"class":4622},[3594,22237,22238],{"class":4500},"\"Знайдено користувача: ",[3594,22240,8623],{"class":4622},[3594,22242,22243],{"class":4459},"db_user.username",[3594,22245,8634],{"class":4622},[3594,22247,4631],{"class":4500},[3594,22249,4558],{"class":4459},[3594,22251,22253,22255,22257,22259],{"class":3596,"line":22252},75,[3594,22254,4676],{"class":4455},[3594,22256,18766],{"class":4459},[3594,22258,4682],{"class":4455},[3594,22260,22261],{"class":4459}," db_user.posts:\n",[3594,22263,22265,22267,22269,22271,22274,22276,22278,22280,22282],{"class":3596,"line":22264},76,[3594,22266,4691],{"class":4690},[3594,22268,7763],{"class":4459},[3594,22270,8617],{"class":4622},[3594,22272,22273],{"class":4500},"\" - Пост: ",[3594,22275,8623],{"class":4622},[3594,22277,18797],{"class":4459},[3594,22279,8634],{"class":4622},[3594,22281,4631],{"class":4500},[3594,22283,4558],{"class":4459},[3594,22285,22287],{"class":3596,"line":22286},77,[3594,22288,3631],{"emptyLinePlaceholder":3630},[3594,22290,22292,22294,22296,22298,22300,22303],{"class":3596,"line":22291},78,[3594,22293,8612],{"class":4690},[3594,22295,7763],{"class":4459},[3594,22297,4631],{"class":4500},[3594,22299,22023],{"class":22022},[3594,22301,22302],{"class":4500},"--- 3. Оновлення запису (UPDATE) ---\"",[3594,22304,4558],{"class":4459},[3594,22306,22308],{"class":3596,"line":22307},79,[3594,22309,22310],{"class":4482},"        # Оновимо заголовок першого посту\n",[3594,22312,22314,22317,22319],{"class":3596,"line":22313},80,[3594,22315,22316],{"class":4459},"        first_post = db_user.posts[",[3594,22318,10061],{"class":4514},[3594,22320,4767],{"class":4459},[3594,22322,22324,22327],{"class":3596,"line":22323},81,[3594,22325,22326],{"class":4459},"        first_post.title = ",[3594,22328,22329],{"class":4500},"\"Оновлена назва async посту\"\n",[3594,22331,22333,22335],{"class":3596,"line":22332},82,[3594,22334,5056],{"class":4455},[3594,22336,6641],{"class":4459},[3594,22338,22340,22342,22344,22346,22349,22351,22354,22356,22358],{"class":3596,"line":22339},83,[3594,22341,8612],{"class":4690},[3594,22343,7763],{"class":4459},[3594,22345,8617],{"class":4622},[3594,22347,22348],{"class":4500},"\"Нова назва посту в БД: ",[3594,22350,8623],{"class":4622},[3594,22352,22353],{"class":4459},"first_post.title",[3594,22355,8634],{"class":4622},[3594,22357,4631],{"class":4500},[3594,22359,4558],{"class":4459},[3594,22361,22363],{"class":3596,"line":22362},84,[3594,22364,3631],{"emptyLinePlaceholder":3630},[3594,22366,22368,22370,22372,22374,22376,22379],{"class":3596,"line":22367},85,[3594,22369,8612],{"class":4690},[3594,22371,7763],{"class":4459},[3594,22373,4631],{"class":4500},[3594,22375,22023],{"class":22022},[3594,22377,22378],{"class":4500},"--- 4. Видалення запису (DELETE) ---\"",[3594,22380,4558],{"class":4459},[3594,22382,22384],{"class":3596,"line":22383},86,[3594,22385,22386],{"class":4482},"        # Видалення користувача призведе до каскадного видалення всіх його постів\n",[3594,22388,22390,22392],{"class":3596,"line":22389},87,[3594,22391,5056],{"class":4455},[3594,22393,22394],{"class":4459}," session.delete(db_user)\n",[3594,22396,22398,22400],{"class":3596,"line":22397},88,[3594,22399,5056],{"class":4455},[3594,22401,6641],{"class":4459},[3594,22403,22405],{"class":3596,"line":22404},89,[3594,22406,3631],{"emptyLinePlaceholder":3630},[3594,22408,22410],{"class":3596,"line":22409},90,[3594,22411,22412],{"class":4482},"        # Перевіряємо, чи залишились пости у базі\n",[3594,22414,22416],{"class":3596,"line":22415},91,[3594,22417,22418],{"class":4459},"        posts_stmt = select(Post)\n",[3594,22420,22422,22425,22427],{"class":3596,"line":22421},92,[3594,22423,22424],{"class":4459},"        remaining_posts = (",[3594,22426,4878],{"class":4455},[3594,22428,22429],{"class":4459}," session.execute(posts_stmt)).scalars().all()\n",[3594,22431,22433,22435,22437,22439,22442,22444,22447,22449,22451],{"class":3596,"line":22432},93,[3594,22434,8612],{"class":4690},[3594,22436,7763],{"class":4459},[3594,22438,8617],{"class":4622},[3594,22440,22441],{"class":4500},"\"Пости в базі даних після видалення користувача: ",[3594,22443,8623],{"class":4622},[3594,22445,22446],{"class":4459},"remaining_posts",[3594,22448,8634],{"class":4622},[3594,22450,4631],{"class":4500},[3594,22452,4558],{"class":4459},[3594,22454,22456],{"class":3596,"line":22455},94,[3594,22457,3631],{"emptyLinePlaceholder":3630},[3594,22459,22461],{"class":3596,"line":22460},95,[3594,22462,22463],{"class":4482},"    # Закриваємо з'єднання з БД\n",[3594,22465,22467,22469],{"class":3596,"line":22466},96,[3594,22468,6638],{"class":4455},[3594,22470,21406],{"class":4459},[3594,22472,22474],{"class":3596,"line":22473},97,[3594,22475,3631],{"emptyLinePlaceholder":3630},[3594,22477,22479,22482,22485,22487,22490],{"class":3596,"line":22478},98,[3594,22480,22481],{"class":4455},"if",[3594,22483,22484],{"class":4493}," __name__",[3594,22486,19021],{"class":4459},[3594,22488,22489],{"class":4500},"\"__main__\"",[3594,22491,4570],{"class":4459},[3594,22493,22495],{"class":3596,"line":22494},99,[3594,22496,22497],{"class":4459},"    asyncio.run(main())\n",[22499,22500,22503,22516,22520,22524,22528,22532,22536,22540,22543,22547,22551,22555,22559,22563,22567,22570,22574,22578,22581,22585,22588,22592,22596,22600,22603],"terminal-preview",{":cursor":22501,"title":22502},"true","python main.py",[22504,22505,22507,7939,22512],"div",{"className":22506},[3596],[3594,22508,22511],{"className":22509},[22510],"opacity-40","$",[3389,22513,22502],{"className":22514},[22515],"font-bold",[22504,22517,22519],{"className":22518},[3596],"--- 1. Створення записів (INSERT) ---",[22504,22521,22523],{"className":22522},[3596],"INFO:sqlalchemy.engine.Engine:BEGIN (implicit)",[22504,22525,22527],{"className":22526},[3596],"INFO:sqlalchemy.engine.Engine:INSERT INTO users (username, email) VALUES (?, ?)",[22504,22529,22531],{"className":22530},[3596],"INFO:sqlalchemy.engine.Engine:INSERT INTO posts (title, content, created_at, author_id) VALUES (?, ?, ?, ?)",[22504,22533,22535],{"className":22534},[3596],"INFO:sqlalchemy.engine.Engine:COMMIT",[22504,22537,22539],{"className":22538},[3596],"Збережено користувача: \u003CUser id=1 username='arakviel'> (ID: 1)",[22504,22541],{"className":22542},[3596],[22504,22544,22546],{"className":22545},[3596],"--- 2. Читання записів з Eager Loading ---",[22504,22548,22550],{"className":22549},[3596],"INFO:sqlalchemy.engine.Engine:SELECT users.id, users.username, users.email FROM users WHERE users.username = ?",[22504,22552,22554],{"className":22553},[3596],"INFO:sqlalchemy.engine.Engine:SELECT posts.author_id AS posts_author_id, posts.id AS posts_id, posts.title AS posts_title ... FROM posts WHERE posts.author_id IN (?)",[22504,22556,22558],{"className":22557},[3596],"Знайдено користувача: arakviel",[22504,22560,22562],{"className":22561},[3596]," - Пост: Перший async пост",[22504,22564,22566],{"className":22565},[3596]," - Пост: Другий async пост",[22504,22568],{"className":22569},[3596],[22504,22571,22573],{"className":22572},[3596],"--- 3. Оновлення запису (UPDATE) ---",[22504,22575,22577],{"className":22576},[3596],"INFO:sqlalchemy.engine.Engine:UPDATE posts SET title=? WHERE posts.id = ?",[22504,22579,22535],{"className":22580},[3596],[22504,22582,22584],{"className":22583},[3596],"Нова назва посту в БД: Оновлена назва async посту",[22504,22586],{"className":22587},[3596],[22504,22589,22591],{"className":22590},[3596],"--- 4. Видалення запису (DELETE) ---",[22504,22593,22595],{"className":22594},[3596],"INFO:sqlalchemy.engine.Engine:DELETE FROM posts WHERE posts.id = ?",[22504,22597,22599],{"className":22598},[3596],"INFO:sqlalchemy.engine.Engine:DELETE FROM users WHERE users.id = ?",[22504,22601,22535],{"className":22602},[3596],[22504,22604,22606],{"className":22605},[3596],"Пости в базі даних після видалення користувача: []",[3441,22608],{},[3444,22610,22612],{"id":22611},"практика-будуємо-шар-моделей-для-taskforge","Практика: Будуємо шар моделей для TaskForge",[3385,22614,22615,22616,22619,22620,3439],{},"На основі всього вивченого побудуємо повноцінний шар моделей для проєкту TaskForge. Усі моделі розміщуватимуться у директорії ",[3412,22617,22618],{},"models\u002F"," і успадковуватимуться від спільного ",[3412,22621,8965],{},[3845,22623,22625],{"id":22624},"структура-проєкту","Структура проєкту",[22627,22628,22629,22636,22746,23059,23463,23869,24028,24280,24985,25698],"code-tree",{},[3585,22630,22634],{"className":22631,"code":22632,"filename":22633,"language":17310,"meta":3590},[17308],"# Реекспорт усіх моделей для зручного імпорту\nfrom .base import Base\nfrom .user import User\nfrom .project import Project, ProjectMember, MemberRole\nfrom .task import Task, TaskStatus, TaskPriority\nfrom .comment import Comment\n\n__all__ = [\n    \"Base\",\n    \"User\",\n    \"Project\", \"ProjectMember\", \"MemberRole\",\n    \"Task\", \"TaskStatus\", \"TaskPriority\",\n    \"Comment\",\n]\n","models\u002F__init__.py",[3412,22635,22632],{"__ignoreMap":3590},[3585,22637,22639],{"className":4445,"code":22638,"filename":9328,"language":4448,"meta":3590,"style":3590},"from datetime import datetime\nfrom sqlalchemy import func\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column\n\nclass Base(DeclarativeBase):\n    \"\"\"Базовий клас для всіх ORM-моделей.\"\"\"\n    pass\n\nclass TimestampMixin:\n    \"\"\"Аудитні поля: created_at та updated_at.\"\"\"\n    created_at: Mapped[datetime] = mapped_column(server_default=func.now())\n    updated_at: Mapped[datetime | None] = mapped_column(\n        onupdate=func.now(), default=None\n    )\n",[3412,22640,22641,22651,22661,22671,22675,22687,22692,22696,22700,22708,22713,22721,22729,22742],{"__ignoreMap":3590},[3594,22642,22643,22645,22647,22649],{"class":3596,"line":3597},[3594,22644,4465],{"class":4455},[3594,22646,9465],{"class":4459},[3594,22648,4456],{"class":4455},[3594,22650,9470],{"class":4459},[3594,22652,22653,22655,22657,22659],{"class":3596,"line":3603},[3594,22654,4465],{"class":4455},[3594,22656,5996],{"class":4459},[3594,22658,4456],{"class":4455},[3594,22660,17616],{"class":4459},[3594,22662,22663,22665,22667,22669],{"class":3596,"line":3609},[3594,22664,4465],{"class":4455},[3594,22666,6383],{"class":4459},[3594,22668,4456],{"class":4455},[3594,22670,9116],{"class":4459},[3594,22672,22673],{"class":3596,"line":3615},[3594,22674,3631],{"emptyLinePlaceholder":3630},[3594,22676,22677,22679,22681,22683,22685],{"class":3596,"line":3621},[3594,22678,8575],{"class":4622},[3594,22680,9127],{"class":4793},[3594,22682,7763],{"class":4459},[3594,22684,3931],{"class":4793},[3594,22686,8585],{"class":4459},[3594,22688,22689],{"class":3596,"line":3627},[3594,22690,22691],{"class":4500},"    \"\"\"Базовий клас для всіх ORM-моделей.\"\"\"\n",[3594,22693,22694],{"class":3596,"line":3634},[3594,22695,9142],{"class":4455},[3594,22697,22698],{"class":3596,"line":3640},[3594,22699,3631],{"emptyLinePlaceholder":3630},[3594,22701,22702,22704,22706],{"class":3596,"line":3646},[3594,22703,8575],{"class":4622},[3594,22705,10992],{"class":4793},[3594,22707,4570],{"class":4459},[3594,22709,22710],{"class":3596,"line":3652},[3594,22711,22712],{"class":4500},"    \"\"\"Аудитні поля: created_at та updated_at.\"\"\"\n",[3594,22714,22715,22717,22719],{"class":3596,"line":3658},[3594,22716,21807],{"class":4459},[3594,22718,10398],{"class":4493},[3594,22720,10401],{"class":4459},[3594,22722,22723,22725,22727],{"class":3596,"line":3664},[3594,22724,11018],{"class":4459},[3594,22726,9644],{"class":4622},[3594,22728,11023],{"class":4459},[3594,22730,22731,22733,22736,22738,22740],{"class":3596,"line":3670},[3594,22732,11028],{"class":4493},[3594,22734,22735],{"class":4459},"=func.now(), ",[3594,22737,9068],{"class":4493},[3594,22739,4497],{"class":4459},[3594,22741,11041],{"class":4622},[3594,22743,22744],{"class":3596,"line":3676},[3594,22745,4935],{"class":4459},[3585,22747,22749],{"className":4445,"code":22748,"filename":9456,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import String\nfrom sqlalchemy.orm import Mapped, mapped_column, relationship\nfrom .base import Base, TimestampMixin\n\nclass User(TimestampMixin, Base):\n    __tablename__ = \"users\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    username: Mapped[str] = mapped_column(String(50), unique=True, index=True)\n    email: Mapped[str] = mapped_column(String(100), unique=True, index=True)\n    hashed_password: Mapped[str] = mapped_column(String(255))\n    is_active: Mapped[bool] = mapped_column(default=True)\n\n    # Relationships\n    memberships: Mapped[list[\"ProjectMember\"]] = relationship(\n        back_populates=\"user\", cascade=\"all, delete-orphan\"\n    )\n    tasks: Mapped[list[\"Task\"]] = relationship(\n        back_populates=\"assignee\", foreign_keys=\"Task.assignee_id\"\n    )\n    comments: Mapped[list[\"Comment\"]] = relationship(\n        back_populates=\"author\", cascade=\"all, delete-orphan\"\n    )\n\n    def __repr__(self) -> str:\n        return f\"\u003CUser id={self.id} username={self.username!r}>\"\n",[3412,22750,22751,22761,22772,22784,22788,22804,22810,22814,22832,22860,22888,22901,22917,22921,22926,22934,22950,22954,22962,22981,22985,22995,23011,23015,23019,23035],{"__ignoreMap":3590},[3594,22752,22753,22755,22757,22759],{"class":3596,"line":3597},[3594,22754,4465],{"class":4455},[3594,22756,5996],{"class":4459},[3594,22758,4456],{"class":4455},[3594,22760,9105],{"class":4459},[3594,22762,22763,22765,22767,22769],{"class":3596,"line":3603},[3594,22764,4465],{"class":4455},[3594,22766,6383],{"class":4459},[3594,22768,4456],{"class":4455},[3594,22770,22771],{"class":4459}," Mapped, mapped_column, relationship\n",[3594,22773,22774,22776,22779,22781],{"class":3596,"line":3609},[3594,22775,4465],{"class":4455},[3594,22777,22778],{"class":4459}," .base ",[3594,22780,4456],{"class":4455},[3594,22782,22783],{"class":4459}," Base, TimestampMixin\n",[3594,22785,22786],{"class":3596,"line":3615},[3594,22787,3631],{"emptyLinePlaceholder":3630},[3594,22789,22790,22792,22794,22796,22798,22800,22802],{"class":3596,"line":3621},[3594,22791,8575],{"class":4622},[3594,22793,8960],{"class":4793},[3594,22795,7763],{"class":4459},[3594,22797,11060],{"class":4793},[3594,22799,3488],{"class":4459},[3594,22801,8965],{"class":4793},[3594,22803,8585],{"class":4459},[3594,22805,22806,22808],{"class":3596,"line":3627},[3594,22807,8972],{"class":4459},[3594,22809,8975],{"class":4500},[3594,22811,22812],{"class":3596,"line":3634},[3594,22813,3631],{"emptyLinePlaceholder":3630},[3594,22815,22816,22818,22820,22822,22824,22826,22828,22830],{"class":3596,"line":3640},[3594,22817,8984],{"class":4690},[3594,22819,9175],{"class":4459},[3594,22821,6930],{"class":4793},[3594,22823,9180],{"class":4459},[3594,22825,8990],{"class":4493},[3594,22827,4497],{"class":4459},[3594,22829,4641],{"class":4622},[3594,22831,4558],{"class":4459},[3594,22833,22834,22836,22838,22840,22842,22844,22846,22848,22850,22852,22854,22856,22858],{"class":3596,"line":3646},[3594,22835,9193],{"class":4459},[3594,22837,9196],{"class":4793},[3594,22839,9199],{"class":4459},[3594,22841,9013],{"class":4514},[3594,22843,9016],{"class":4459},[3594,22845,9019],{"class":4493},[3594,22847,4497],{"class":4459},[3594,22849,4641],{"class":4622},[3594,22851,3488],{"class":4459},[3594,22853,8999],{"class":4493},[3594,22855,4497],{"class":4459},[3594,22857,4641],{"class":4622},[3594,22859,4558],{"class":4459},[3594,22861,22862,22864,22866,22868,22870,22872,22874,22876,22878,22880,22882,22884,22886],{"class":3596,"line":3652},[3594,22863,9216],{"class":4459},[3594,22865,9196],{"class":4793},[3594,22867,9199],{"class":4459},[3594,22869,9042],{"class":4514},[3594,22871,9016],{"class":4459},[3594,22873,9019],{"class":4493},[3594,22875,4497],{"class":4459},[3594,22877,4641],{"class":4622},[3594,22879,3488],{"class":4459},[3594,22881,8999],{"class":4493},[3594,22883,4497],{"class":4459},[3594,22885,4641],{"class":4622},[3594,22887,4558],{"class":4459},[3594,22889,22890,22893,22895,22897,22899],{"class":3596,"line":3658},[3594,22891,22892],{"class":4459},"    hashed_password: Mapped[",[3594,22894,9196],{"class":4793},[3594,22896,9199],{"class":4459},[3594,22898,10125],{"class":4514},[3594,22900,7851],{"class":4459},[3594,22902,22903,22905,22907,22909,22911,22913,22915],{"class":3596,"line":3664},[3594,22904,9237],{"class":4459},[3594,22906,7038],{"class":4793},[3594,22908,9180],{"class":4459},[3594,22910,9068],{"class":4493},[3594,22912,4497],{"class":4459},[3594,22914,4641],{"class":4622},[3594,22916,4558],{"class":4459},[3594,22918,22919],{"class":3596,"line":3670},[3594,22920,3631],{"emptyLinePlaceholder":3630},[3594,22922,22923],{"class":3596,"line":3676},[3594,22924,22925],{"class":4482},"    # Relationships\n",[3594,22927,22928,22930,22932],{"class":3596,"line":3681},[3594,22929,13041],{"class":4459},[3594,22931,13044],{"class":4500},[3594,22933,11605],{"class":4459},[3594,22935,22936,22938,22940,22942,22944,22946,22948],{"class":3596,"line":3687},[3594,22937,11617],{"class":4493},[3594,22939,4497],{"class":4459},[3594,22941,4539],{"class":4500},[3594,22943,3488],{"class":4459},[3594,22945,11167],{"class":4493},[3594,22947,4497],{"class":4459},[3594,22949,21689],{"class":4500},[3594,22951,22952],{"class":3596,"line":3693},[3594,22953,4935],{"class":4459},[3594,22955,22956,22958,22960],{"class":3596,"line":3699},[3594,22957,13321],{"class":4459},[3594,22959,13324],{"class":4500},[3594,22961,11605],{"class":4459},[3594,22963,22964,22966,22968,22971,22973,22976,22978],{"class":3596,"line":3705},[3594,22965,11617],{"class":4493},[3594,22967,4497],{"class":4459},[3594,22969,22970],{"class":4500},"\"assignee\"",[3594,22972,3488],{"class":4459},[3594,22974,22975],{"class":4493},"foreign_keys",[3594,22977,4497],{"class":4459},[3594,22979,22980],{"class":4500},"\"Task.assignee_id\"\n",[3594,22982,22983],{"class":3596,"line":3711},[3594,22984,4935],{"class":4459},[3594,22986,22987,22990,22993],{"class":3596,"line":3716},[3594,22988,22989],{"class":4459},"    comments: Mapped[list[",[3594,22991,22992],{"class":4500},"\"Comment\"",[3594,22994,11605],{"class":4459},[3594,22996,22997,22999,23001,23003,23005,23007,23009],{"class":3596,"line":3721},[3594,22998,11617],{"class":4493},[3594,23000,4497],{"class":4459},[3594,23002,11622],{"class":4500},[3594,23004,3488],{"class":4459},[3594,23006,11167],{"class":4493},[3594,23008,4497],{"class":4459},[3594,23010,21689],{"class":4500},[3594,23012,23013],{"class":3596,"line":3727},[3594,23014,4935],{"class":4459},[3594,23016,23017],{"class":3596,"line":3733},[3594,23018,3631],{"emptyLinePlaceholder":3630},[3594,23020,23021,23023,23025,23027,23029,23031,23033],{"class":3596,"line":3739},[3594,23022,9725],{"class":4622},[3594,23024,9728],{"class":4690},[3594,23026,7763],{"class":4459},[3594,23028,8605],{"class":4493},[3594,23030,9735],{"class":4459},[3594,23032,9196],{"class":4793},[3594,23034,4570],{"class":4459},[3594,23036,23037,23039,23041,23043,23045,23047,23049,23051,23053,23055,23057],{"class":3596,"line":3744},[3594,23038,9744],{"class":4455},[3594,23040,9747],{"class":4622},[3594,23042,9750],{"class":4500},[3594,23044,9753],{"class":4622},[3594,23046,9756],{"class":4459},[3594,23048,8634],{"class":4622},[3594,23050,9761],{"class":4500},[3594,23052,9753],{"class":4622},[3594,23054,9766],{"class":4459},[3594,23056,9769],{"class":4622},[3594,23058,9772],{"class":4500},[3585,23060,23062],{"className":4445,"code":23061,"filename":12713,"language":4448,"meta":3590,"style":3590},"import enum\nfrom datetime import datetime\nfrom sqlalchemy import String, Text, ForeignKey, Enum, func\nfrom sqlalchemy.orm import Mapped, mapped_column, relationship\nfrom .base import Base, TimestampMixin\n\nclass MemberRole(enum.Enum):\n    OWNER = \"owner\"\n    EDITOR = \"editor\"\n    VIEWER = \"viewer\"\n\nclass Project(TimestampMixin, Base):\n    __tablename__ = \"projects\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    name: Mapped[str] = mapped_column(String(100), index=True)\n    description: Mapped[str | None] = mapped_column(Text)\n    owner_id: Mapped[int] = mapped_column(ForeignKey(\"users.id\"))\n\n    # Relationships\n    owner: Mapped[\"User\"] = relationship(\"User\", foreign_keys=[owner_id])\n    memberships: Mapped[list[\"ProjectMember\"]] = relationship(\n        back_populates=\"project\", cascade=\"all, delete-orphan\"\n    )\n    tasks: Mapped[list[\"Task\"]] = relationship(\n        back_populates=\"project\", cascade=\"all, delete-orphan\"\n    )\n\nclass ProjectMember(Base):\n    __tablename__ = \"project_members\"\n\n    project_id: Mapped[int] = mapped_column(\n        ForeignKey(\"projects.id\"), primary_key=True\n    )\n    user_id: Mapped[int] = mapped_column(\n        ForeignKey(\"users.id\"), primary_key=True\n    )\n    role: Mapped[MemberRole] = mapped_column(\n        Enum(MemberRole), default=MemberRole.VIEWER\n    )\n    joined_at: Mapped[datetime] = mapped_column(server_default=func.now())\n\n    project: Mapped[\"Project\"] = relationship(back_populates=\"memberships\")\n    user: Mapped[\"User\"] = relationship(back_populates=\"memberships\")\n",[3412,23063,23064,23070,23080,23091,23101,23111,23115,23131,23137,23143,23149,23153,23169,23175,23179,23197,23217,23230,23243,23247,23251,23269,23277,23293,23297,23305,23321,23325,23329,23341,23347,23351,23359,23373,23377,23385,23399,23403,23407,23415,23419,23427,23431,23447],{"__ignoreMap":3590},[3594,23065,23066,23068],{"class":3596,"line":3597},[3594,23067,4456],{"class":4455},[3594,23069,10592],{"class":4459},[3594,23071,23072,23074,23076,23078],{"class":3596,"line":3603},[3594,23073,4465],{"class":4455},[3594,23075,9465],{"class":4459},[3594,23077,4456],{"class":4455},[3594,23079,9470],{"class":4459},[3594,23081,23082,23084,23086,23088],{"class":3596,"line":3609},[3594,23083,4465],{"class":4455},[3594,23085,5996],{"class":4459},[3594,23087,4456],{"class":4455},[3594,23089,23090],{"class":4459}," String, Text, ForeignKey, Enum, func\n",[3594,23092,23093,23095,23097,23099],{"class":3596,"line":3615},[3594,23094,4465],{"class":4455},[3594,23096,6383],{"class":4459},[3594,23098,4456],{"class":4455},[3594,23100,22771],{"class":4459},[3594,23102,23103,23105,23107,23109],{"class":3596,"line":3621},[3594,23104,4465],{"class":4455},[3594,23106,22778],{"class":4459},[3594,23108,4456],{"class":4455},[3594,23110,22783],{"class":4459},[3594,23112,23113],{"class":3596,"line":3627},[3594,23114,3631],{"emptyLinePlaceholder":3630},[3594,23116,23117,23119,23121,23123,23125,23127,23129],{"class":3596,"line":3634},[3594,23118,8575],{"class":4622},[3594,23120,12783],{"class":4793},[3594,23122,7763],{"class":4459},[3594,23124,10619],{"class":4793},[3594,23126,3439],{"class":4459},[3594,23128,10192],{"class":4793},[3594,23130,8585],{"class":4459},[3594,23132,23133,23135],{"class":3596,"line":3640},[3594,23134,12798],{"class":4459},[3594,23136,12801],{"class":4500},[3594,23138,23139,23141],{"class":3596,"line":3646},[3594,23140,12806],{"class":4459},[3594,23142,12809],{"class":4500},[3594,23144,23145,23147],{"class":3596,"line":3652},[3594,23146,12814],{"class":4459},[3594,23148,12817],{"class":4500},[3594,23150,23151],{"class":3596,"line":3658},[3594,23152,3631],{"emptyLinePlaceholder":3630},[3594,23154,23155,23157,23159,23161,23163,23165,23167],{"class":3596,"line":3664},[3594,23156,8575],{"class":4622},[3594,23158,10710],{"class":4793},[3594,23160,7763],{"class":4459},[3594,23162,11060],{"class":4793},[3594,23164,3488],{"class":4459},[3594,23166,8965],{"class":4793},[3594,23168,8585],{"class":4459},[3594,23170,23171,23173],{"class":3596,"line":3670},[3594,23172,8972],{"class":4459},[3594,23174,10723],{"class":4500},[3594,23176,23177],{"class":3596,"line":3676},[3594,23178,3631],{"emptyLinePlaceholder":3630},[3594,23180,23181,23183,23185,23187,23189,23191,23193,23195],{"class":3596,"line":3681},[3594,23182,8984],{"class":4690},[3594,23184,9175],{"class":4459},[3594,23186,6930],{"class":4793},[3594,23188,9180],{"class":4459},[3594,23190,8990],{"class":4493},[3594,23192,4497],{"class":4459},[3594,23194,4641],{"class":4622},[3594,23196,4558],{"class":4459},[3594,23198,23199,23201,23203,23205,23207,23209,23211,23213,23215],{"class":3596,"line":3687},[3594,23200,12645],{"class":4459},[3594,23202,9196],{"class":4793},[3594,23204,9199],{"class":4459},[3594,23206,9042],{"class":4514},[3594,23208,9016],{"class":4459},[3594,23210,8999],{"class":4493},[3594,23212,4497],{"class":4459},[3594,23214,4641],{"class":4622},[3594,23216,4558],{"class":4459},[3594,23218,23219,23222,23224,23226,23228],{"class":3596,"line":3693},[3594,23220,23221],{"class":4459},"    description: Mapped[",[3594,23223,9196],{"class":4793},[3594,23225,9641],{"class":4459},[3594,23227,9644],{"class":4622},[3594,23229,10145],{"class":4459},[3594,23231,23232,23235,23237,23239,23241],{"class":3596,"line":3699},[3594,23233,23234],{"class":4459},"    owner_id: Mapped[",[3594,23236,6930],{"class":4793},[3594,23238,11734],{"class":4459},[3594,23240,11325],{"class":4500},[3594,23242,7851],{"class":4459},[3594,23244,23245],{"class":3596,"line":3705},[3594,23246,3631],{"emptyLinePlaceholder":3630},[3594,23248,23249],{"class":3596,"line":3711},[3594,23250,22925],{"class":4482},[3594,23252,23253,23256,23258,23260,23262,23264,23266],{"class":3596,"line":3716},[3594,23254,23255],{"class":4459},"    owner: Mapped[",[3594,23257,11755],{"class":4500},[3594,23259,11758],{"class":4459},[3594,23261,11755],{"class":4500},[3594,23263,3488],{"class":4459},[3594,23265,22975],{"class":4493},[3594,23267,23268],{"class":4459},"=[owner_id])\n",[3594,23270,23271,23273,23275],{"class":3596,"line":3721},[3594,23272,13041],{"class":4459},[3594,23274,13044],{"class":4500},[3594,23276,11605],{"class":4459},[3594,23278,23279,23281,23283,23285,23287,23289,23291],{"class":3596,"line":3727},[3594,23280,11617],{"class":4493},[3594,23282,4497],{"class":4459},[3594,23284,13342],{"class":4500},[3594,23286,3488],{"class":4459},[3594,23288,11167],{"class":4493},[3594,23290,4497],{"class":4459},[3594,23292,21689],{"class":4500},[3594,23294,23295],{"class":3596,"line":3733},[3594,23296,4935],{"class":4459},[3594,23298,23299,23301,23303],{"class":3596,"line":3739},[3594,23300,13321],{"class":4459},[3594,23302,13324],{"class":4500},[3594,23304,11605],{"class":4459},[3594,23306,23307,23309,23311,23313,23315,23317,23319],{"class":3596,"line":3744},[3594,23308,11617],{"class":4493},[3594,23310,4497],{"class":4459},[3594,23312,13342],{"class":4500},[3594,23314,3488],{"class":4459},[3594,23316,11167],{"class":4493},[3594,23318,4497],{"class":4459},[3594,23320,21689],{"class":4500},[3594,23322,23323],{"class":3596,"line":3749},[3594,23324,4935],{"class":4459},[3594,23326,23327],{"class":3596,"line":3755},[3594,23328,3631],{"emptyLinePlaceholder":3630},[3594,23330,23331,23333,23335,23337,23339],{"class":3596,"line":3761},[3594,23332,8575],{"class":4622},[3594,23334,12828],{"class":4793},[3594,23336,7763],{"class":4459},[3594,23338,8965],{"class":4793},[3594,23340,8585],{"class":4459},[3594,23342,23343,23345],{"class":3596,"line":3766},[3594,23344,8972],{"class":4459},[3594,23346,12846],{"class":4500},[3594,23348,23349],{"class":3596,"line":3772},[3594,23350,3631],{"emptyLinePlaceholder":3630},[3594,23352,23353,23355,23357],{"class":3596,"line":3778},[3594,23354,12855],{"class":4459},[3594,23356,6930],{"class":4793},[3594,23358,11023],{"class":4459},[3594,23360,23361,23363,23365,23367,23369,23371],{"class":3596,"line":3784},[3594,23362,12864],{"class":4459},[3594,23364,12867],{"class":4500},[3594,23366,9016],{"class":4459},[3594,23368,8990],{"class":4493},[3594,23370,4497],{"class":4459},[3594,23372,12876],{"class":4622},[3594,23374,23375],{"class":3596,"line":3790},[3594,23376,4935],{"class":4459},[3594,23378,23379,23381,23383],{"class":3596,"line":3796},[3594,23380,12885],{"class":4459},[3594,23382,6930],{"class":4793},[3594,23384,11023],{"class":4459},[3594,23386,23387,23389,23391,23393,23395,23397],{"class":3596,"line":3802},[3594,23388,12864],{"class":4459},[3594,23390,11325],{"class":4500},[3594,23392,9016],{"class":4459},[3594,23394,8990],{"class":4493},[3594,23396,4497],{"class":4459},[3594,23398,12876],{"class":4622},[3594,23400,23401],{"class":3596,"line":3808},[3594,23402,4935],{"class":4459},[3594,23404,23405],{"class":3596,"line":3814},[3594,23406,12912],{"class":4459},[3594,23408,23409,23411,23413],{"class":3596,"line":3820},[3594,23410,12917],{"class":4459},[3594,23412,9068],{"class":4493},[3594,23414,12922],{"class":4459},[3594,23416,23417],{"class":3596,"line":3826},[3594,23418,4935],{"class":4459},[3594,23420,23421,23423,23425],{"class":3596,"line":3832},[3594,23422,12931],{"class":4459},[3594,23424,10398],{"class":4493},[3594,23426,10401],{"class":4459},[3594,23428,23429],{"class":3596,"line":3837},[3594,23430,3631],{"emptyLinePlaceholder":3630},[3594,23432,23433,23435,23437,23439,23441,23443,23445],{"class":3596,"line":4819},[3594,23434,12949],{"class":4459},[3594,23436,12952],{"class":4500},[3594,23438,11758],{"class":4459},[3594,23440,11163],{"class":4493},[3594,23442,4497],{"class":4459},[3594,23444,12961],{"class":4500},[3594,23446,4558],{"class":4459},[3594,23448,23449,23451,23453,23455,23457,23459,23461],{"class":3596,"line":20653},[3594,23450,12968],{"class":4459},[3594,23452,11755],{"class":4500},[3594,23454,11758],{"class":4459},[3594,23456,11163],{"class":4493},[3594,23458,4497],{"class":4459},[3594,23460,12961],{"class":4500},[3594,23462,4558],{"class":4459},[3585,23464,23467],{"className":4445,"code":23465,"filename":23466,"language":4448,"meta":3590,"style":3590},"import enum\nfrom sqlalchemy import String, Text, ForeignKey, Enum, Date\nfrom sqlalchemy.orm import Mapped, mapped_column, relationship\nfrom datetime import date\nfrom .base import Base, TimestampMixin\n\nclass TaskStatus(enum.Enum):\n    TODO = \"todo\"\n    IN_PROGRESS = \"in_progress\"\n    DONE = \"done\"\n    CANCELLED = \"cancelled\"\n\nclass TaskPriority(enum.Enum):\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n    CRITICAL = \"critical\"\n\nclass Task(TimestampMixin, Base):\n    __tablename__ = \"tasks\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    title: Mapped[str] = mapped_column(String(200))\n    description: Mapped[str | None] = mapped_column(Text)\n    status: Mapped[TaskStatus] = mapped_column(\n        Enum(TaskStatus), default=TaskStatus.TODO\n    )\n    priority: Mapped[TaskPriority] = mapped_column(\n        Enum(TaskPriority), default=TaskPriority.MEDIUM\n    )\n    due_date: Mapped[date | None] = mapped_column(Date)\n\n    project_id: Mapped[int] = mapped_column(ForeignKey(\"projects.id\"))\n    assignee_id: Mapped[int | None] = mapped_column(\n        ForeignKey(\"users.id\"), nullable=True\n    )\n\n    project: Mapped[\"Project\"] = relationship(back_populates=\"tasks\")\n    assignee: Mapped[\"User | None\"] = relationship(\n        back_populates=\"tasks\", foreign_keys=[assignee_id]\n    )\n    comments: Mapped[list[\"Comment\"]] = relationship(\n        back_populates=\"task\", cascade=\"all, delete-orphan\"\n    )\n","models\u002Ftask.py",[3412,23468,23469,23475,23486,23496,23507,23517,23521,23538,23546,23554,23562,23570,23574,23591,23599,23607,23615,23623,23627,23643,23649,23653,23671,23683,23695,23700,23710,23714,23719,23729,23733,23743,23747,23759,23772,23786,23790,23794,23811,23821,23836,23840,23848,23865],{"__ignoreMap":3590},[3594,23470,23471,23473],{"class":3596,"line":3597},[3594,23472,4456],{"class":4455},[3594,23474,10592],{"class":4459},[3594,23476,23477,23479,23481,23483],{"class":3596,"line":3603},[3594,23478,4465],{"class":4455},[3594,23480,5996],{"class":4459},[3594,23482,4456],{"class":4455},[3594,23484,23485],{"class":4459}," String, Text, ForeignKey, Enum, Date\n",[3594,23487,23488,23490,23492,23494],{"class":3596,"line":3609},[3594,23489,4465],{"class":4455},[3594,23491,6383],{"class":4459},[3594,23493,4456],{"class":4455},[3594,23495,22771],{"class":4459},[3594,23497,23498,23500,23502,23504],{"class":3596,"line":3615},[3594,23499,4465],{"class":4455},[3594,23501,9465],{"class":4459},[3594,23503,4456],{"class":4455},[3594,23505,23506],{"class":4459}," date\n",[3594,23508,23509,23511,23513,23515],{"class":3596,"line":3621},[3594,23510,4465],{"class":4455},[3594,23512,22778],{"class":4459},[3594,23514,4456],{"class":4455},[3594,23516,22783],{"class":4459},[3594,23518,23519],{"class":3596,"line":3627},[3594,23520,3631],{"emptyLinePlaceholder":3630},[3594,23522,23523,23525,23528,23530,23532,23534,23536],{"class":3596,"line":3634},[3594,23524,8575],{"class":4622},[3594,23526,23527],{"class":4793}," TaskStatus",[3594,23529,7763],{"class":4459},[3594,23531,10619],{"class":4793},[3594,23533,3439],{"class":4459},[3594,23535,10192],{"class":4793},[3594,23537,8585],{"class":4459},[3594,23539,23540,23543],{"class":3596,"line":3640},[3594,23541,23542],{"class":4459},"    TODO = ",[3594,23544,23545],{"class":4500},"\"todo\"\n",[3594,23547,23548,23551],{"class":3596,"line":3646},[3594,23549,23550],{"class":4459},"    IN_PROGRESS = ",[3594,23552,23553],{"class":4500},"\"in_progress\"\n",[3594,23555,23556,23559],{"class":3596,"line":3652},[3594,23557,23558],{"class":4459},"    DONE = ",[3594,23560,23561],{"class":4500},"\"done\"\n",[3594,23563,23564,23567],{"class":3596,"line":3658},[3594,23565,23566],{"class":4459},"    CANCELLED = ",[3594,23568,23569],{"class":4500},"\"cancelled\"\n",[3594,23571,23572],{"class":3596,"line":3664},[3594,23573,3631],{"emptyLinePlaceholder":3630},[3594,23575,23576,23578,23581,23583,23585,23587,23589],{"class":3596,"line":3670},[3594,23577,8575],{"class":4622},[3594,23579,23580],{"class":4793}," TaskPriority",[3594,23582,7763],{"class":4459},[3594,23584,10619],{"class":4793},[3594,23586,3439],{"class":4459},[3594,23588,10192],{"class":4793},[3594,23590,8585],{"class":4459},[3594,23592,23593,23596],{"class":3596,"line":3676},[3594,23594,23595],{"class":4459},"    LOW = ",[3594,23597,23598],{"class":4500},"\"low\"\n",[3594,23600,23601,23604],{"class":3596,"line":3681},[3594,23602,23603],{"class":4459},"    MEDIUM = ",[3594,23605,23606],{"class":4500},"\"medium\"\n",[3594,23608,23609,23612],{"class":3596,"line":3687},[3594,23610,23611],{"class":4459},"    HIGH = ",[3594,23613,23614],{"class":4500},"\"high\"\n",[3594,23616,23617,23620],{"class":3596,"line":3693},[3594,23618,23619],{"class":4459},"    CRITICAL = ",[3594,23621,23622],{"class":4500},"\"critical\"\n",[3594,23624,23625],{"class":3596,"line":3699},[3594,23626,3631],{"emptyLinePlaceholder":3630},[3594,23628,23629,23631,23633,23635,23637,23639,23641],{"class":3596,"line":3705},[3594,23630,8575],{"class":4622},[3594,23632,13384],{"class":4793},[3594,23634,7763],{"class":4459},[3594,23636,11060],{"class":4793},[3594,23638,3488],{"class":4459},[3594,23640,8965],{"class":4793},[3594,23642,8585],{"class":4459},[3594,23644,23645,23647],{"class":3596,"line":3711},[3594,23646,8972],{"class":4459},[3594,23648,13397],{"class":4500},[3594,23650,23651],{"class":3596,"line":3716},[3594,23652,3631],{"emptyLinePlaceholder":3630},[3594,23654,23655,23657,23659,23661,23663,23665,23667,23669],{"class":3596,"line":3721},[3594,23656,8984],{"class":4690},[3594,23658,9175],{"class":4459},[3594,23660,6930],{"class":4793},[3594,23662,9180],{"class":4459},[3594,23664,8990],{"class":4493},[3594,23666,4497],{"class":4459},[3594,23668,4641],{"class":4622},[3594,23670,4558],{"class":4459},[3594,23672,23673,23675,23677,23679,23681],{"class":3596,"line":3727},[3594,23674,11693],{"class":4459},[3594,23676,9196],{"class":4793},[3594,23678,9199],{"class":4459},[3594,23680,11700],{"class":4514},[3594,23682,7851],{"class":4459},[3594,23684,23685,23687,23689,23691,23693],{"class":3596,"line":3733},[3594,23686,23221],{"class":4459},[3594,23688,9196],{"class":4793},[3594,23690,9641],{"class":4459},[3594,23692,9644],{"class":4622},[3594,23694,10145],{"class":4459},[3594,23696,23697],{"class":3596,"line":3739},[3594,23698,23699],{"class":4459},"    status: Mapped[TaskStatus] = mapped_column(\n",[3594,23701,23702,23705,23707],{"class":3596,"line":3744},[3594,23703,23704],{"class":4459},"        Enum(TaskStatus), ",[3594,23706,9068],{"class":4493},[3594,23708,23709],{"class":4459},"=TaskStatus.TODO\n",[3594,23711,23712],{"class":3596,"line":3749},[3594,23713,4935],{"class":4459},[3594,23715,23716],{"class":3596,"line":3755},[3594,23717,23718],{"class":4459},"    priority: Mapped[TaskPriority] = mapped_column(\n",[3594,23720,23721,23724,23726],{"class":3596,"line":3761},[3594,23722,23723],{"class":4459},"        Enum(TaskPriority), ",[3594,23725,9068],{"class":4493},[3594,23727,23728],{"class":4459},"=TaskPriority.MEDIUM\n",[3594,23730,23731],{"class":3596,"line":3766},[3594,23732,4935],{"class":4459},[3594,23734,23735,23738,23740],{"class":3596,"line":3772},[3594,23736,23737],{"class":4459},"    due_date: Mapped[date | ",[3594,23739,9644],{"class":4622},[3594,23741,23742],{"class":4459},"] = mapped_column(Date)\n",[3594,23744,23745],{"class":3596,"line":3778},[3594,23746,3631],{"emptyLinePlaceholder":3630},[3594,23748,23749,23751,23753,23755,23757],{"class":3596,"line":3784},[3594,23750,12855],{"class":4459},[3594,23752,6930],{"class":4793},[3594,23754,11734],{"class":4459},[3594,23756,12867],{"class":4500},[3594,23758,7851],{"class":4459},[3594,23760,23761,23764,23766,23768,23770],{"class":3596,"line":3790},[3594,23762,23763],{"class":4459},"    assignee_id: Mapped[",[3594,23765,6930],{"class":4793},[3594,23767,9641],{"class":4459},[3594,23769,9644],{"class":4622},[3594,23771,11023],{"class":4459},[3594,23773,23774,23776,23778,23780,23782,23784],{"class":3596,"line":3796},[3594,23775,12864],{"class":4459},[3594,23777,11325],{"class":4500},[3594,23779,9016],{"class":4459},[3594,23781,9028],{"class":4493},[3594,23783,4497],{"class":4459},[3594,23785,12876],{"class":4622},[3594,23787,23788],{"class":3596,"line":3802},[3594,23789,4935],{"class":4459},[3594,23791,23792],{"class":3596,"line":3808},[3594,23793,3631],{"emptyLinePlaceholder":3630},[3594,23795,23796,23798,23800,23802,23804,23806,23809],{"class":3596,"line":3814},[3594,23797,12949],{"class":4459},[3594,23799,12952],{"class":4500},[3594,23801,11758],{"class":4459},[3594,23803,11163],{"class":4493},[3594,23805,4497],{"class":4459},[3594,23807,23808],{"class":4500},"\"tasks\"",[3594,23810,4558],{"class":4459},[3594,23812,23813,23816,23819],{"class":3596,"line":3820},[3594,23814,23815],{"class":4459},"    assignee: Mapped[",[3594,23817,23818],{"class":4500},"\"User | None\"",[3594,23820,13461],{"class":4459},[3594,23822,23823,23825,23827,23829,23831,23833],{"class":3596,"line":3826},[3594,23824,11617],{"class":4493},[3594,23826,4497],{"class":4459},[3594,23828,23808],{"class":4500},[3594,23830,3488],{"class":4459},[3594,23832,22975],{"class":4493},[3594,23834,23835],{"class":4459},"=[assignee_id]\n",[3594,23837,23838],{"class":3596,"line":3832},[3594,23839,4935],{"class":4459},[3594,23841,23842,23844,23846],{"class":3596,"line":3837},[3594,23843,22989],{"class":4459},[3594,23845,22992],{"class":4500},[3594,23847,11605],{"class":4459},[3594,23849,23850,23852,23854,23857,23859,23861,23863],{"class":3596,"line":4819},[3594,23851,11617],{"class":4493},[3594,23853,4497],{"class":4459},[3594,23855,23856],{"class":4500},"\"task\"",[3594,23858,3488],{"class":4459},[3594,23860,11167],{"class":4493},[3594,23862,4497],{"class":4459},[3594,23864,21689],{"class":4500},[3594,23866,23867],{"class":3596,"line":20653},[3594,23868,4935],{"class":4459},[3585,23870,23873],{"className":4445,"code":23871,"filename":23872,"language":4448,"meta":3590,"style":3590},"from sqlalchemy import Text, ForeignKey\nfrom sqlalchemy.orm import Mapped, mapped_column, relationship\nfrom .base import Base, TimestampMixin\n\nclass Comment(TimestampMixin, Base):\n    __tablename__ = \"comments\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    content: Mapped[str] = mapped_column(Text)\n    task_id: Mapped[int] = mapped_column(ForeignKey(\"tasks.id\"))\n    author_id: Mapped[int] = mapped_column(ForeignKey(\"users.id\"))\n\n    task: Mapped[\"Task\"] = relationship(back_populates=\"comments\")\n    author: Mapped[\"User\"] = relationship(back_populates=\"comments\")\n","models\u002Fcomment.py",[3412,23874,23875,23886,23896,23906,23910,23927,23934,23938,23956,23964,23978,23990,23994,24012],{"__ignoreMap":3590},[3594,23876,23877,23879,23881,23883],{"class":3596,"line":3597},[3594,23878,4465],{"class":4455},[3594,23880,5996],{"class":4459},[3594,23882,4456],{"class":4455},[3594,23884,23885],{"class":4459}," Text, ForeignKey\n",[3594,23887,23888,23890,23892,23894],{"class":3596,"line":3603},[3594,23889,4465],{"class":4455},[3594,23891,6383],{"class":4459},[3594,23893,4456],{"class":4455},[3594,23895,22771],{"class":4459},[3594,23897,23898,23900,23902,23904],{"class":3596,"line":3609},[3594,23899,4465],{"class":4455},[3594,23901,22778],{"class":4459},[3594,23903,4456],{"class":4455},[3594,23905,22783],{"class":4459},[3594,23907,23908],{"class":3596,"line":3615},[3594,23909,3631],{"emptyLinePlaceholder":3630},[3594,23911,23912,23914,23917,23919,23921,23923,23925],{"class":3596,"line":3621},[3594,23913,8575],{"class":4622},[3594,23915,23916],{"class":4793}," Comment",[3594,23918,7763],{"class":4459},[3594,23920,11060],{"class":4793},[3594,23922,3488],{"class":4459},[3594,23924,8965],{"class":4793},[3594,23926,8585],{"class":4459},[3594,23928,23929,23931],{"class":3596,"line":3627},[3594,23930,8972],{"class":4459},[3594,23932,23933],{"class":4500},"\"comments\"\n",[3594,23935,23936],{"class":3596,"line":3634},[3594,23937,3631],{"emptyLinePlaceholder":3630},[3594,23939,23940,23942,23944,23946,23948,23950,23952,23954],{"class":3596,"line":3640},[3594,23941,8984],{"class":4690},[3594,23943,9175],{"class":4459},[3594,23945,6930],{"class":4793},[3594,23947,9180],{"class":4459},[3594,23949,8990],{"class":4493},[3594,23951,4497],{"class":4459},[3594,23953,4641],{"class":4622},[3594,23955,4558],{"class":4459},[3594,23957,23958,23960,23962],{"class":3596,"line":3646},[3594,23959,11707],{"class":4459},[3594,23961,9196],{"class":4793},[3594,23963,10145],{"class":4459},[3594,23965,23966,23969,23971,23973,23976],{"class":3596,"line":3652},[3594,23967,23968],{"class":4459},"    task_id: Mapped[",[3594,23970,6930],{"class":4793},[3594,23972,11734],{"class":4459},[3594,23974,23975],{"class":4500},"\"tasks.id\"",[3594,23977,7851],{"class":4459},[3594,23979,23980,23982,23984,23986,23988],{"class":3596,"line":3658},[3594,23981,11729],{"class":4459},[3594,23983,6930],{"class":4793},[3594,23985,11734],{"class":4459},[3594,23987,11325],{"class":4500},[3594,23989,7851],{"class":4459},[3594,23991,23992],{"class":3596,"line":3664},[3594,23993,3631],{"emptyLinePlaceholder":3630},[3594,23995,23996,23999,24001,24003,24005,24007,24010],{"class":3596,"line":3670},[3594,23997,23998],{"class":4459},"    task: Mapped[",[3594,24000,13324],{"class":4500},[3594,24002,11758],{"class":4459},[3594,24004,11163],{"class":4493},[3594,24006,4497],{"class":4459},[3594,24008,24009],{"class":4500},"\"comments\"",[3594,24011,4558],{"class":4459},[3594,24013,24014,24016,24018,24020,24022,24024,24026],{"class":3596,"line":3676},[3594,24015,11752],{"class":4459},[3594,24017,11755],{"class":4500},[3594,24019,11758],{"class":4459},[3594,24021,11163],{"class":4493},[3594,24023,4497],{"class":4459},[3594,24025,24009],{"class":4500},[3594,24027,4558],{"class":4459},[3585,24029,24031],{"className":4445,"code":24030,"filename":5987,"language":4448,"meta":3590,"style":3590},"from typing import AsyncGenerator\nfrom sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession\nfrom sqlalchemy.orm import DeclarativeBase\n\nclass Base(DeclarativeBase):\n    \"\"\"Базовий клас для схем, що імпортується іншими модулями.\"\"\"\n    pass\n\nDATABASE_URL = \"postgresql+asyncpg:\u002F\u002Fpostgres:postgres@localhost:5432\u002Ftaskforge\"\n\n# Async Engine для PostgreSQL\nasync_engine = create_async_engine(\n    DATABASE_URL,\n    pool_size=10,\n    max_overflow=20,\n    pool_recycle=1800,\n)\n\n# Async Session Local фабрика\nAsyncSessionLocal = async_sessionmaker(\n    bind=async_engine,\n    class_=AsyncSession,\n    autocommit=False,\n    autoflush=False,\n    expire_on_commit=False,\n)\n\n# DI-функція для отримання сесії на кожен HTTP-запит\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with AsyncSessionLocal() as session:\n        try:\n            yield session\n            await session.commit()\n        except Exception:\n            await session.rollback()\n            raise\n",[3412,24032,24033,24043,24053,24063,24067,24079,24084,24088,24092,24099,24103,24108,24112,24116,24126,24136,24146,24150,24154,24159,24163,24169,24175,24185,24195,24205,24209,24213,24218,24232,24244,24250,24256,24262,24270,24276],{"__ignoreMap":3590},[3594,24034,24035,24037,24039,24041],{"class":3596,"line":3597},[3594,24036,4465],{"class":4455},[3594,24038,11470],{"class":4459},[3594,24040,4456],{"class":4455},[3594,24042,20377],{"class":4459},[3594,24044,24045,24047,24049,24051],{"class":3596,"line":3603},[3594,24046,4465],{"class":4455},[3594,24048,5782],{"class":4459},[3594,24050,4456],{"class":4455},[3594,24052,6518],{"class":4459},[3594,24054,24055,24057,24059,24061],{"class":3596,"line":3609},[3594,24056,4465],{"class":4455},[3594,24058,6383],{"class":4459},[3594,24060,4456],{"class":4455},[3594,24062,9341],{"class":4459},[3594,24064,24065],{"class":3596,"line":3615},[3594,24066,3631],{"emptyLinePlaceholder":3630},[3594,24068,24069,24071,24073,24075,24077],{"class":3596,"line":3621},[3594,24070,8575],{"class":4622},[3594,24072,9127],{"class":4793},[3594,24074,7763],{"class":4459},[3594,24076,3931],{"class":4793},[3594,24078,8585],{"class":4459},[3594,24080,24081],{"class":3596,"line":3627},[3594,24082,24083],{"class":4500},"    \"\"\"Базовий клас для схем, що імпортується іншими модулями.\"\"\"\n",[3594,24085,24086],{"class":3596,"line":3634},[3594,24087,9142],{"class":4455},[3594,24089,24090],{"class":3596,"line":3640},[3594,24091,3631],{"emptyLinePlaceholder":3630},[3594,24093,24094,24096],{"class":3596,"line":3646},[3594,24095,6015],{"class":4459},[3594,24097,24098],{"class":4500},"\"postgresql+asyncpg:\u002F\u002Fpostgres:postgres@localhost:5432\u002Ftaskforge\"\n",[3594,24100,24101],{"class":3596,"line":3652},[3594,24102,3631],{"emptyLinePlaceholder":3630},[3594,24104,24105],{"class":3596,"line":3658},[3594,24106,24107],{"class":4482},"# Async Engine для PostgreSQL\n",[3594,24109,24110],{"class":3596,"line":3664},[3594,24111,5500],{"class":4459},[3594,24113,24114],{"class":3596,"line":3670},[3594,24115,6031],{"class":4459},[3594,24117,24118,24120,24122,24124],{"class":3596,"line":3676},[3594,24119,6041],{"class":4493},[3594,24121,4497],{"class":4459},[3594,24123,4646],{"class":4514},[3594,24125,4504],{"class":4459},[3594,24127,24128,24130,24132,24134],{"class":3596,"line":3681},[3594,24129,6056],{"class":4493},[3594,24131,4497],{"class":4459},[3594,24133,6061],{"class":4514},[3594,24135,4504],{"class":4459},[3594,24137,24138,24140,24142,24144],{"class":3596,"line":3687},[3594,24139,6087],{"class":4493},[3594,24141,4497],{"class":4459},[3594,24143,6092],{"class":4514},[3594,24145,4504],{"class":4459},[3594,24147,24148],{"class":3596,"line":3693},[3594,24149,4558],{"class":4459},[3594,24151,24152],{"class":3596,"line":3699},[3594,24153,3631],{"emptyLinePlaceholder":3630},[3594,24155,24156],{"class":3596,"line":3705},[3594,24157,24158],{"class":4482},"# Async Session Local фабрика\n",[3594,24160,24161],{"class":3596,"line":3711},[3594,24162,6541],{"class":4459},[3594,24164,24165,24167],{"class":3596,"line":3716},[3594,24166,6416],{"class":4493},[3594,24168,6548],{"class":4459},[3594,24170,24171,24173],{"class":3596,"line":3721},[3594,24172,6553],{"class":4493},[3594,24174,6556],{"class":4459},[3594,24176,24177,24179,24181,24183],{"class":3596,"line":3727},[3594,24178,6424],{"class":4493},[3594,24180,4497],{"class":4459},[3594,24182,6238],{"class":4622},[3594,24184,4504],{"class":4459},[3594,24186,24187,24189,24191,24193],{"class":3596,"line":3733},[3594,24188,6435],{"class":4493},[3594,24190,4497],{"class":4459},[3594,24192,6238],{"class":4622},[3594,24194,4504],{"class":4459},[3594,24196,24197,24199,24201,24203],{"class":3596,"line":3739},[3594,24198,6446],{"class":4493},[3594,24200,4497],{"class":4459},[3594,24202,6238],{"class":4622},[3594,24204,4504],{"class":4459},[3594,24206,24207],{"class":3596,"line":3744},[3594,24208,4558],{"class":4459},[3594,24210,24211],{"class":3596,"line":3749},[3594,24212,3631],{"emptyLinePlaceholder":3630},[3594,24214,24215],{"class":3596,"line":3755},[3594,24216,24217],{"class":4482},"# DI-функція для отримання сесії на кожен HTTP-запит\n",[3594,24219,24220,24222,24224,24226,24228,24230],{"class":3596,"line":3761},[3594,24221,4856],{"class":4622},[3594,24223,4859],{"class":4622},[3594,24225,20587],{"class":4690},[3594,24227,20590],{"class":4459},[3594,24229,9644],{"class":4622},[3594,24231,20595],{"class":4459},[3594,24233,24234,24236,24238,24240,24242],{"class":3596,"line":3766},[3594,24235,8595],{"class":4455},[3594,24237,6606],{"class":4455},[3594,24239,6609],{"class":4459},[3594,24241,4597],{"class":4455},[3594,24243,6478],{"class":4459},[3594,24245,24246,24248],{"class":3596,"line":3772},[3594,24247,19423],{"class":4455},[3594,24249,4570],{"class":4459},[3594,24251,24252,24254],{"class":3596,"line":3778},[3594,24253,20641],{"class":4455},[3594,24255,20644],{"class":4459},[3594,24257,24258,24260],{"class":3596,"line":3784},[3594,24259,19440],{"class":4455},[3594,24261,6641],{"class":4459},[3594,24263,24264,24266,24268],{"class":3596,"line":3790},[3594,24265,19451],{"class":4455},[3594,24267,4794],{"class":4793},[3594,24269,4570],{"class":4459},[3594,24271,24272,24274],{"class":3596,"line":3796},[3594,24273,19440],{"class":4455},[3594,24275,20667],{"class":4459},[3594,24277,24278],{"class":3596,"line":3802},[3594,24279,20673],{"class":4455},[3585,24281,24284],{"className":4445,"code":24282,"filename":24283,"language":4448,"meta":3590,"style":3590},"from typing import Annotated\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom sqlalchemy import select\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom sqlalchemy.orm import selectinload\n\nfrom app.core.database import get_async_session\nfrom app.models.project import Project\n# Припускаємо, що схеми Pydantic (ProjectCreate, ProjectRead, ProjectUpdate) вже створені в Статті 19\nfrom app.schemas.project import ProjectCreate, ProjectRead, ProjectUpdate\n\nrouter = APIRouter(prefix=\"\u002Fprojects\", tags=[\"Projects\"])\n\nSessionDep = Annotated[AsyncSession, Depends(get_async_session)]\n\n@router.post(\"\u002F\", response_model=ProjectRead, status_code=status.HTTP_201_CREATED)\nasync def create_project(project_in: ProjectCreate, session: SessionDep):\n    # У реальному додатку owner_id буде братися з авторизованого користувача (сесії JWT)\n    project = Project(**project_in.model_dump())\n    session.add(project)\n    await session.flush()\n    return project\n\n@router.get(\"\u002F\", response_model=list[ProjectRead])\nasync def list_projects(session: SessionDep, skip: int = 0, limit: int = 20):\n    stmt = select(Project).offset(skip).limit(limit).order_by(Project.id)\n    projects = (await session.execute(stmt)).scalars().all()\n    return projects\n\n@router.get(\"\u002F{project_id}\", response_model=ProjectRead)\nasync def get_project(project_id: int, session: SessionDep):\n    # Завантажуємо проект разом із його зв'язками\n    stmt = (\n        select(Project)\n        .where(Project.id == project_id)\n        .options(selectinload(Project.memberships))\n    )\n    project = (await session.execute(stmt)).scalars().one_or_none()\n    if project is None:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=f\"Project with id={project_id} not found\"\n        )\n    return project\n\n@router.put(\"\u002F{project_id}\", response_model=ProjectRead)\nasync def update_project(\n    project_id: int, project_in: ProjectUpdate, session: SessionDep\n):\n    project = await session.get(Project, project_id)\n    if project is None:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=f\"Project with id={project_id} not found\"\n        )\n\n    update_data = project_in.model_dump(exclude_unset=True)\n    for key, value in update_data.items():\n        setattr(project, key, value)\n\n    await session.flush()\n    return project\n\n@router.delete(\"\u002F{project_id}\", status_code=status.HTTP_204_NO_CONTENT)\nasync def delete_project(project_id: int, session: SessionDep):\n    project = await session.get(Project, project_id)\n    if project is None:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=f\"Project with id={project_id} not found\"\n        )\n    await session.delete(project)\n","routers\u002Fprojects.py",[3412,24285,24286,24296,24306,24316,24326,24336,24340,24350,24362,24367,24379,24383,24405,24409,24413,24417,24436,24457,24462,24467,24472,24479,24486,24490,24505,24546,24551,24560,24567,24571,24591,24615,24620,24625,24630,24635,24640,24644,24654,24667,24673,24679,24698,24702,24708,24712,24731,24742,24763,24767,24777,24789,24795,24801,24819,24823,24827,24841,24853,24861,24865,24871,24877,24881,24901,24924,24932,24944,24950,24956,24974,24978],{"__ignoreMap":3590},[3594,24287,24288,24290,24292,24294],{"class":3596,"line":3597},[3594,24289,4465],{"class":4455},[3594,24291,11470],{"class":4459},[3594,24293,4456],{"class":4455},[3594,24295,20693],{"class":4459},[3594,24297,24298,24300,24302,24304],{"class":3596,"line":3603},[3594,24299,4465],{"class":4455},[3594,24301,20700],{"class":4459},[3594,24303,4456],{"class":4455},[3594,24305,20705],{"class":4459},[3594,24307,24308,24310,24312,24314],{"class":3596,"line":3609},[3594,24309,4465],{"class":4455},[3594,24311,5996],{"class":4459},[3594,24313,4456],{"class":4455},[3594,24315,14571],{"class":4459},[3594,24317,24318,24320,24322,24324],{"class":3596,"line":3615},[3594,24319,4465],{"class":4455},[3594,24321,5782],{"class":4459},[3594,24323,4456],{"class":4455},[3594,24325,20726],{"class":4459},[3594,24327,24328,24330,24332,24334],{"class":3596,"line":3621},[3594,24329,4465],{"class":4455},[3594,24331,6383],{"class":4459},[3594,24333,4456],{"class":4455},[3594,24335,13691],{"class":4459},[3594,24337,24338],{"class":3596,"line":3627},[3594,24339,3631],{"emptyLinePlaceholder":3630},[3594,24341,24342,24344,24346,24348],{"class":3596,"line":3634},[3594,24343,4465],{"class":4455},[3594,24345,20737],{"class":4459},[3594,24347,4456],{"class":4455},[3594,24349,20742],{"class":4459},[3594,24351,24352,24354,24357,24359],{"class":3596,"line":3640},[3594,24353,4465],{"class":4455},[3594,24355,24356],{"class":4459}," app.models.project ",[3594,24358,4456],{"class":4455},[3594,24360,24361],{"class":4459}," Project\n",[3594,24363,24364],{"class":3596,"line":3646},[3594,24365,24366],{"class":4482},"# Припускаємо, що схеми Pydantic (ProjectCreate, ProjectRead, ProjectUpdate) вже створені в Статті 19\n",[3594,24368,24369,24371,24374,24376],{"class":3596,"line":3652},[3594,24370,4465],{"class":4455},[3594,24372,24373],{"class":4459}," app.schemas.project ",[3594,24375,4456],{"class":4455},[3594,24377,24378],{"class":4459}," ProjectCreate, ProjectRead, ProjectUpdate\n",[3594,24380,24381],{"class":3596,"line":3658},[3594,24382,3631],{"emptyLinePlaceholder":3630},[3594,24384,24385,24387,24389,24391,24394,24396,24398,24400,24403],{"class":3596,"line":3664},[3594,24386,20779],{"class":4459},[3594,24388,20782],{"class":4493},[3594,24390,4497],{"class":4459},[3594,24392,24393],{"class":4500},"\"\u002Fprojects\"",[3594,24395,3488],{"class":4459},[3594,24397,20792],{"class":4493},[3594,24399,20795],{"class":4459},[3594,24401,24402],{"class":4500},"\"Projects\"",[3594,24404,5008],{"class":4459},[3594,24406,24407],{"class":3596,"line":3670},[3594,24408,3631],{"emptyLinePlaceholder":3630},[3594,24410,24411],{"class":3596,"line":3676},[3594,24412,20814],{"class":4459},[3594,24414,24415],{"class":3596,"line":3681},[3594,24416,3631],{"emptyLinePlaceholder":3630},[3594,24418,24419,24421,24423,24425,24427,24429,24432,24434],{"class":3596,"line":3687},[3594,24420,21073],{"class":4690},[3594,24422,7763],{"class":4459},[3594,24424,20957],{"class":4500},[3594,24426,3488],{"class":4459},[3594,24428,20838],{"class":4493},[3594,24430,24431],{"class":4459},"=ProjectRead, ",[3594,24433,21087],{"class":4493},[3594,24435,21090],{"class":4459},[3594,24437,24438,24440,24442,24445,24447,24450,24453,24455],{"class":3596,"line":3693},[3594,24439,4856],{"class":4622},[3594,24441,4859],{"class":4622},[3594,24443,24444],{"class":4690}," create_project",[3594,24446,7763],{"class":4459},[3594,24448,24449],{"class":4493},"project_in",[3594,24451,24452],{"class":4459},": ProjectCreate, ",[3594,24454,20864],{"class":4493},[3594,24456,20867],{"class":4459},[3594,24458,24459],{"class":3596,"line":3699},[3594,24460,24461],{"class":4482},"    # У реальному додатку owner_id буде братися з авторизованого користувача (сесії JWT)\n",[3594,24463,24464],{"class":3596,"line":3705},[3594,24465,24466],{"class":4459},"    project = Project(**project_in.model_dump())\n",[3594,24468,24469],{"class":3596,"line":3711},[3594,24470,24471],{"class":4459},"    session.add(project)\n",[3594,24473,24474,24476],{"class":3596,"line":3716},[3594,24475,6638],{"class":4455},[3594,24477,24478],{"class":4459}," session.flush()\n",[3594,24480,24481,24483],{"class":3596,"line":3721},[3594,24482,20941],{"class":4455},[3594,24484,24485],{"class":4459}," project\n",[3594,24487,24488],{"class":3596,"line":3727},[3594,24489,3631],{"emptyLinePlaceholder":3630},[3594,24491,24492,24494,24496,24498,24500,24502],{"class":3596,"line":3733},[3594,24493,20823],{"class":4690},[3594,24495,7763],{"class":4459},[3594,24497,20957],{"class":4500},[3594,24499,3488],{"class":4459},[3594,24501,20838],{"class":4493},[3594,24503,24504],{"class":4459},"=list[ProjectRead])\n",[3594,24506,24507,24509,24511,24514,24516,24518,24521,24524,24526,24528,24530,24532,24534,24536,24538,24540,24542,24544],{"class":3596,"line":3739},[3594,24508,4856],{"class":4622},[3594,24510,4859],{"class":4622},[3594,24512,24513],{"class":4690}," list_projects",[3594,24515,7763],{"class":4459},[3594,24517,20864],{"class":4493},[3594,24519,24520],{"class":4459},": SessionDep, ",[3594,24522,24523],{"class":4493},"skip",[3594,24525,9991],{"class":4459},[3594,24527,6930],{"class":4793},[3594,24529,13939],{"class":4459},[3594,24531,10061],{"class":4514},[3594,24533,3488],{"class":4459},[3594,24535,15044],{"class":4493},[3594,24537,9991],{"class":4459},[3594,24539,6930],{"class":4793},[3594,24541,13939],{"class":4459},[3594,24543,6061],{"class":4514},[3594,24545,8585],{"class":4459},[3594,24547,24548],{"class":3596,"line":3744},[3594,24549,24550],{"class":4459},"    stmt = select(Project).offset(skip).limit(limit).order_by(Project.id)\n",[3594,24552,24553,24556,24558],{"class":3596,"line":3749},[3594,24554,24555],{"class":4459},"    projects = (",[3594,24557,4878],{"class":4455},[3594,24559,14319],{"class":4459},[3594,24561,24562,24564],{"class":3596,"line":3755},[3594,24563,20941],{"class":4455},[3594,24565,24566],{"class":4459}," projects\n",[3594,24568,24569],{"class":3596,"line":3761},[3594,24570,3631],{"emptyLinePlaceholder":3630},[3594,24572,24573,24575,24577,24579,24582,24584,24586,24588],{"class":3596,"line":3766},[3594,24574,20823],{"class":4690},[3594,24576,7763],{"class":4459},[3594,24578,20828],{"class":4500},[3594,24580,24581],{"class":4622},"{project_id}",[3594,24583,4631],{"class":4500},[3594,24585,3488],{"class":4459},[3594,24587,20838],{"class":4493},[3594,24589,24590],{"class":4459},"=ProjectRead)\n",[3594,24592,24593,24595,24597,24600,24602,24605,24607,24609,24611,24613],{"class":3596,"line":3772},[3594,24594,4856],{"class":4622},[3594,24596,4859],{"class":4622},[3594,24598,24599],{"class":4690}," get_project",[3594,24601,7763],{"class":4459},[3594,24603,24604],{"class":4493},"project_id",[3594,24606,9991],{"class":4459},[3594,24608,6930],{"class":4793},[3594,24610,3488],{"class":4459},[3594,24612,20864],{"class":4493},[3594,24614,20867],{"class":4459},[3594,24616,24617],{"class":3596,"line":3778},[3594,24618,24619],{"class":4482},"    # Завантажуємо проект разом із його зв'язками\n",[3594,24621,24622],{"class":3596,"line":3784},[3594,24623,24624],{"class":4459},"    stmt = (\n",[3594,24626,24627],{"class":3596,"line":3790},[3594,24628,24629],{"class":4459},"        select(Project)\n",[3594,24631,24632],{"class":3596,"line":3796},[3594,24633,24634],{"class":4459},"        .where(Project.id == project_id)\n",[3594,24636,24637],{"class":3596,"line":3802},[3594,24638,24639],{"class":4459},"        .options(selectinload(Project.memberships))\n",[3594,24641,24642],{"class":3596,"line":3808},[3594,24643,4935],{"class":4459},[3594,24645,24646,24649,24651],{"class":3596,"line":3814},[3594,24647,24648],{"class":4459},"    project = (",[3594,24650,4878],{"class":4455},[3594,24652,24653],{"class":4459}," session.execute(stmt)).scalars().one_or_none()\n",[3594,24655,24656,24658,24661,24663,24665],{"class":3596,"line":3820},[3594,24657,20886],{"class":4455},[3594,24659,24660],{"class":4459}," project ",[3594,24662,15478],{"class":4622},[3594,24664,20893],{"class":4622},[3594,24666,4570],{"class":4459},[3594,24668,24669,24671],{"class":3596,"line":3826},[3594,24670,20900],{"class":4455},[3594,24672,20903],{"class":4459},[3594,24674,24675,24677],{"class":3596,"line":3832},[3594,24676,20908],{"class":4493},[3594,24678,20911],{"class":4459},[3594,24680,24681,24683,24685,24687,24690,24692,24694,24696],{"class":3596,"line":3837},[3594,24682,20916],{"class":4493},[3594,24684,4497],{"class":4459},[3594,24686,8617],{"class":4622},[3594,24688,24689],{"class":4500},"\"Project with id=",[3594,24691,8623],{"class":4622},[3594,24693,24604],{"class":4459},[3594,24695,8634],{"class":4622},[3594,24697,20932],{"class":4500},[3594,24699,24700],{"class":3596,"line":4819},[3594,24701,4657],{"class":4459},[3594,24703,24704,24706],{"class":3596,"line":20653},[3594,24705,20941],{"class":4455},[3594,24707,24485],{"class":4459},[3594,24709,24710],{"class":3596,"line":20662},[3594,24711,3631],{"emptyLinePlaceholder":3630},[3594,24713,24714,24717,24719,24721,24723,24725,24727,24729],{"class":3596,"line":20670},[3594,24715,24716],{"class":4690},"@router.put",[3594,24718,7763],{"class":4459},[3594,24720,20828],{"class":4500},[3594,24722,24581],{"class":4622},[3594,24724,4631],{"class":4500},[3594,24726,3488],{"class":4459},[3594,24728,20838],{"class":4493},[3594,24730,24590],{"class":4459},[3594,24732,24733,24735,24737,24740],{"class":3596,"line":21133},[3594,24734,4856],{"class":4622},[3594,24736,4859],{"class":4622},[3594,24738,24739],{"class":4690}," update_project",[3594,24741,20976],{"class":4459},[3594,24743,24744,24747,24749,24751,24753,24755,24758,24760],{"class":3596,"line":21139},[3594,24745,24746],{"class":4493},"    project_id",[3594,24748,9991],{"class":4459},[3594,24750,6930],{"class":4793},[3594,24752,3488],{"class":4459},[3594,24754,24449],{"class":4493},[3594,24756,24757],{"class":4459},": ProjectUpdate, ",[3594,24759,20864],{"class":4493},[3594,24761,24762],{"class":4459},": SessionDep\n",[3594,24764,24765],{"class":3596,"line":21145},[3594,24766,8585],{"class":4459},[3594,24768,24769,24772,24774],{"class":3596,"line":21150},[3594,24770,24771],{"class":4459},"    project = ",[3594,24773,4878],{"class":4455},[3594,24775,24776],{"class":4459}," session.get(Project, project_id)\n",[3594,24778,24779,24781,24783,24785,24787],{"class":3596,"line":21158},[3594,24780,20886],{"class":4455},[3594,24782,24660],{"class":4459},[3594,24784,15478],{"class":4622},[3594,24786,20893],{"class":4622},[3594,24788,4570],{"class":4459},[3594,24790,24791,24793],{"class":3596,"line":21165},[3594,24792,20900],{"class":4455},[3594,24794,20903],{"class":4459},[3594,24796,24797,24799],{"class":3596,"line":21173},[3594,24798,20908],{"class":4493},[3594,24800,20911],{"class":4459},[3594,24802,24803,24805,24807,24809,24811,24813,24815,24817],{"class":3596,"line":21195},[3594,24804,20916],{"class":4493},[3594,24806,4497],{"class":4459},[3594,24808,8617],{"class":4622},[3594,24810,24689],{"class":4500},[3594,24812,8623],{"class":4622},[3594,24814,24604],{"class":4459},[3594,24816,8634],{"class":4622},[3594,24818,20932],{"class":4500},[3594,24820,24821],{"class":3596,"line":21200},[3594,24822,4657],{"class":4459},[3594,24824,24825],{"class":3596,"line":21205},[3594,24826,3631],{"emptyLinePlaceholder":3630},[3594,24828,24829,24832,24835,24837,24839],{"class":3596,"line":21211},[3594,24830,24831],{"class":4459},"    update_data = project_in.model_dump(",[3594,24833,24834],{"class":4493},"exclude_unset",[3594,24836,4497],{"class":4459},[3594,24838,4641],{"class":4622},[3594,24840,4558],{"class":4459},[3594,24842,24843,24845,24848,24850],{"class":3596,"line":21217},[3594,24844,20230],{"class":4455},[3594,24846,24847],{"class":4459}," key, value ",[3594,24849,4682],{"class":4455},[3594,24851,24852],{"class":4459}," update_data.items():\n",[3594,24854,24855,24858],{"class":3596,"line":21227},[3594,24856,24857],{"class":4690},"        setattr",[3594,24859,24860],{"class":4459},"(project, key, value)\n",[3594,24862,24863],{"class":3596,"line":22064},[3594,24864,3631],{"emptyLinePlaceholder":3630},[3594,24866,24867,24869],{"class":3596,"line":22073},[3594,24868,6638],{"class":4455},[3594,24870,24478],{"class":4459},[3594,24872,24873,24875],{"class":3596,"line":22098},[3594,24874,20941],{"class":4455},[3594,24876,24485],{"class":4459},[3594,24878,24879],{"class":3596,"line":22121},[3594,24880,3631],{"emptyLinePlaceholder":3630},[3594,24882,24883,24886,24888,24890,24892,24894,24896,24898],{"class":3596,"line":22127},[3594,24884,24885],{"class":4690},"@router.delete",[3594,24887,7763],{"class":4459},[3594,24889,20828],{"class":4500},[3594,24891,24581],{"class":4622},[3594,24893,4631],{"class":4500},[3594,24895,3488],{"class":4459},[3594,24897,21087],{"class":4493},[3594,24899,24900],{"class":4459},"=status.HTTP_204_NO_CONTENT)\n",[3594,24902,24903,24905,24907,24910,24912,24914,24916,24918,24920,24922],{"class":3596,"line":22132},[3594,24904,4856],{"class":4622},[3594,24906,4859],{"class":4622},[3594,24908,24909],{"class":4690}," delete_project",[3594,24911,7763],{"class":4459},[3594,24913,24604],{"class":4493},[3594,24915,9991],{"class":4459},[3594,24917,6930],{"class":4793},[3594,24919,3488],{"class":4459},[3594,24921,20864],{"class":4493},[3594,24923,20867],{"class":4459},[3594,24925,24926,24928,24930],{"class":3596,"line":22138},[3594,24927,24771],{"class":4459},[3594,24929,4878],{"class":4455},[3594,24931,24776],{"class":4459},[3594,24933,24934,24936,24938,24940,24942],{"class":3596,"line":22145},[3594,24935,20886],{"class":4455},[3594,24937,24660],{"class":4459},[3594,24939,15478],{"class":4622},[3594,24941,20893],{"class":4622},[3594,24943,4570],{"class":4459},[3594,24945,24946,24948],{"class":3596,"line":22175},[3594,24947,20900],{"class":4455},[3594,24949,20903],{"class":4459},[3594,24951,24952,24954],{"class":3596,"line":22180},[3594,24953,20908],{"class":4493},[3594,24955,20911],{"class":4459},[3594,24957,24958,24960,24962,24964,24966,24968,24970,24972],{"class":3596,"line":22196},[3594,24959,20916],{"class":4493},[3594,24961,4497],{"class":4459},[3594,24963,8617],{"class":4622},[3594,24965,24689],{"class":4500},[3594,24967,8623],{"class":4622},[3594,24969,24604],{"class":4459},[3594,24971,8634],{"class":4622},[3594,24973,20932],{"class":4500},[3594,24975,24976],{"class":3596,"line":22202},[3594,24977,4657],{"class":4459},[3594,24979,24980,24982],{"class":3596,"line":22213},[3594,24981,6638],{"class":4455},[3594,24983,24984],{"class":4459}," session.delete(project)\n",[3585,24986,24989],{"className":4445,"code":24987,"filename":24988,"language":4448,"meta":3590,"style":3590},"from typing import Annotated\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom sqlalchemy import select\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom sqlalchemy.orm import selectinload\n\nfrom app.core.database import get_async_session\nfrom app.models.task import Task\n# Схеми Pydantic (TaskCreate, TaskRead, TaskUpdate)\nfrom app.schemas.task import TaskCreate, TaskRead, TaskUpdate\n\nrouter = APIRouter(prefix=\"\u002Ftasks\", tags=[\"Tasks\"])\n\nSessionDep = Annotated[AsyncSession, Depends(get_async_session)]\n\n@router.post(\"\u002F\", response_model=TaskRead, status_code=status.HTTP_201_CREATED)\nasync def create_task(task_in: TaskCreate, session: SessionDep):\n    task = Task(**task_in.model_dump())\n    session.add(task)\n    await session.flush()\n    return task\n\n@router.get(\"\u002F\", response_model=list[TaskRead])\nasync def list_tasks(\n    session: SessionDep,\n    project_id: int | None = None,\n    skip: int = 0,\n    limit: int = 20,\n):\n    stmt = select(Task).offset(skip).limit(limit).order_by(Task.id)\n    if project_id:\n        stmt = stmt.where(Task.project_id == project_id)\n\n    tasks = (await session.execute(stmt)).scalars().all()\n    return tasks\n\n@router.get(\"\u002F{task_id}\", response_model=TaskRead)\nasync def get_task(task_id: int, session: SessionDep):\n    # Eager loading коментарів для конкретної задачі\n    stmt = select(Task).where(Task.id == task_id).options(selectinload(Task.comments))\n    task = (await session.execute(stmt)).scalars().one_or_none()\n    if task is None:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=f\"Task with id={task_id} not found\"\n        )\n    return task\n\n@router.put(\"\u002F{task_id}\", response_model=TaskRead)\nasync def update_task(\n    task_id: int, task_in: TaskUpdate, session: SessionDep\n):\n    task = await session.get(Task, task_id)\n    if task is None:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=f\"Task with id={task_id} not found\"\n        )\n\n    update_data = task_in.model_dump(exclude_unset=True)\n    for key, value in update_data.items():\n        setattr(task, key, value)\n\n    await session.flush()\n    return task\n\n@router.delete(\"\u002F{task_id}\", status_code=status.HTTP_204_NO_CONTENT)\nasync def delete_task(task_id: int, session: SessionDep):\n    task = await session.get(Task, task_id)\n    if task is None:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=f\"Task with id={task_id} not found\"\n        )\n    await session.delete(task)\n","routers\u002Ftasks.py",[3412,24990,24991,25001,25011,25021,25031,25041,25045,25055,25067,25072,25084,25088,25110,25114,25118,25122,25141,25162,25167,25172,25178,25185,25189,25204,25215,25221,25239,25253,25267,25271,25276,25283,25288,25292,25301,25308,25312,25332,25356,25361,25366,25375,25388,25394,25400,25419,25423,25429,25433,25451,25462,25482,25486,25496,25508,25514,25520,25538,25542,25546,25559,25569,25576,25580,25586,25592,25596,25614,25637,25645,25657,25663,25669,25687,25691],{"__ignoreMap":3590},[3594,24992,24993,24995,24997,24999],{"class":3596,"line":3597},[3594,24994,4465],{"class":4455},[3594,24996,11470],{"class":4459},[3594,24998,4456],{"class":4455},[3594,25000,20693],{"class":4459},[3594,25002,25003,25005,25007,25009],{"class":3596,"line":3603},[3594,25004,4465],{"class":4455},[3594,25006,20700],{"class":4459},[3594,25008,4456],{"class":4455},[3594,25010,20705],{"class":4459},[3594,25012,25013,25015,25017,25019],{"class":3596,"line":3609},[3594,25014,4465],{"class":4455},[3594,25016,5996],{"class":4459},[3594,25018,4456],{"class":4455},[3594,25020,14571],{"class":4459},[3594,25022,25023,25025,25027,25029],{"class":3596,"line":3615},[3594,25024,4465],{"class":4455},[3594,25026,5782],{"class":4459},[3594,25028,4456],{"class":4455},[3594,25030,20726],{"class":4459},[3594,25032,25033,25035,25037,25039],{"class":3596,"line":3621},[3594,25034,4465],{"class":4455},[3594,25036,6383],{"class":4459},[3594,25038,4456],{"class":4455},[3594,25040,13691],{"class":4459},[3594,25042,25043],{"class":3596,"line":3627},[3594,25044,3631],{"emptyLinePlaceholder":3630},[3594,25046,25047,25049,25051,25053],{"class":3596,"line":3634},[3594,25048,4465],{"class":4455},[3594,25050,20737],{"class":4459},[3594,25052,4456],{"class":4455},[3594,25054,20742],{"class":4459},[3594,25056,25057,25059,25062,25064],{"class":3596,"line":3640},[3594,25058,4465],{"class":4455},[3594,25060,25061],{"class":4459}," app.models.task ",[3594,25063,4456],{"class":4455},[3594,25065,25066],{"class":4459}," Task\n",[3594,25068,25069],{"class":3596,"line":3646},[3594,25070,25071],{"class":4482},"# Схеми Pydantic (TaskCreate, TaskRead, TaskUpdate)\n",[3594,25073,25074,25076,25079,25081],{"class":3596,"line":3652},[3594,25075,4465],{"class":4455},[3594,25077,25078],{"class":4459}," app.schemas.task ",[3594,25080,4456],{"class":4455},[3594,25082,25083],{"class":4459}," TaskCreate, TaskRead, TaskUpdate\n",[3594,25085,25086],{"class":3596,"line":3658},[3594,25087,3631],{"emptyLinePlaceholder":3630},[3594,25089,25090,25092,25094,25096,25099,25101,25103,25105,25108],{"class":3596,"line":3664},[3594,25091,20779],{"class":4459},[3594,25093,20782],{"class":4493},[3594,25095,4497],{"class":4459},[3594,25097,25098],{"class":4500},"\"\u002Ftasks\"",[3594,25100,3488],{"class":4459},[3594,25102,20792],{"class":4493},[3594,25104,20795],{"class":4459},[3594,25106,25107],{"class":4500},"\"Tasks\"",[3594,25109,5008],{"class":4459},[3594,25111,25112],{"class":3596,"line":3670},[3594,25113,3631],{"emptyLinePlaceholder":3630},[3594,25115,25116],{"class":3596,"line":3676},[3594,25117,20814],{"class":4459},[3594,25119,25120],{"class":3596,"line":3681},[3594,25121,3631],{"emptyLinePlaceholder":3630},[3594,25123,25124,25126,25128,25130,25132,25134,25137,25139],{"class":3596,"line":3687},[3594,25125,21073],{"class":4690},[3594,25127,7763],{"class":4459},[3594,25129,20957],{"class":4500},[3594,25131,3488],{"class":4459},[3594,25133,20838],{"class":4493},[3594,25135,25136],{"class":4459},"=TaskRead, ",[3594,25138,21087],{"class":4493},[3594,25140,21090],{"class":4459},[3594,25142,25143,25145,25147,25150,25152,25155,25158,25160],{"class":3596,"line":3693},[3594,25144,4856],{"class":4622},[3594,25146,4859],{"class":4622},[3594,25148,25149],{"class":4690}," create_task",[3594,25151,7763],{"class":4459},[3594,25153,25154],{"class":4493},"task_in",[3594,25156,25157],{"class":4459},": TaskCreate, ",[3594,25159,20864],{"class":4493},[3594,25161,20867],{"class":4459},[3594,25163,25164],{"class":3596,"line":3699},[3594,25165,25166],{"class":4459},"    task = Task(**task_in.model_dump())\n",[3594,25168,25169],{"class":3596,"line":3705},[3594,25170,25171],{"class":4459},"    session.add(task)\n",[3594,25173,25174,25176],{"class":3596,"line":3711},[3594,25175,6638],{"class":4455},[3594,25177,24478],{"class":4459},[3594,25179,25180,25182],{"class":3596,"line":3716},[3594,25181,20941],{"class":4455},[3594,25183,25184],{"class":4459}," task\n",[3594,25186,25187],{"class":3596,"line":3721},[3594,25188,3631],{"emptyLinePlaceholder":3630},[3594,25190,25191,25193,25195,25197,25199,25201],{"class":3596,"line":3727},[3594,25192,20823],{"class":4690},[3594,25194,7763],{"class":4459},[3594,25196,20957],{"class":4500},[3594,25198,3488],{"class":4459},[3594,25200,20838],{"class":4493},[3594,25202,25203],{"class":4459},"=list[TaskRead])\n",[3594,25205,25206,25208,25210,25213],{"class":3596,"line":3733},[3594,25207,4856],{"class":4622},[3594,25209,4859],{"class":4622},[3594,25211,25212],{"class":4690}," list_tasks",[3594,25214,20976],{"class":4459},[3594,25216,25217,25219],{"class":3596,"line":3739},[3594,25218,20981],{"class":4493},[3594,25220,20984],{"class":4459},[3594,25222,25223,25225,25227,25229,25231,25233,25235,25237],{"class":3596,"line":3744},[3594,25224,24746],{"class":4493},[3594,25226,9991],{"class":4459},[3594,25228,6930],{"class":4793},[3594,25230,9641],{"class":4459},[3594,25232,9644],{"class":4622},[3594,25234,13939],{"class":4459},[3594,25236,9644],{"class":4622},[3594,25238,4504],{"class":4459},[3594,25240,25241,25243,25245,25247,25249,25251],{"class":3596,"line":3749},[3594,25242,20989],{"class":4493},[3594,25244,9991],{"class":4459},[3594,25246,6930],{"class":4793},[3594,25248,13939],{"class":4459},[3594,25250,10061],{"class":4514},[3594,25252,4504],{"class":4459},[3594,25254,25255,25257,25259,25261,25263,25265],{"class":3596,"line":3755},[3594,25256,21004],{"class":4493},[3594,25258,9991],{"class":4459},[3594,25260,6930],{"class":4793},[3594,25262,13939],{"class":4459},[3594,25264,6061],{"class":4514},[3594,25266,4504],{"class":4459},[3594,25268,25269],{"class":3596,"line":3761},[3594,25270,8585],{"class":4459},[3594,25272,25273],{"class":3596,"line":3766},[3594,25274,25275],{"class":4459},"    stmt = select(Task).offset(skip).limit(limit).order_by(Task.id)\n",[3594,25277,25278,25280],{"class":3596,"line":3772},[3594,25279,20886],{"class":4455},[3594,25281,25282],{"class":4459}," project_id:\n",[3594,25284,25285],{"class":3596,"line":3778},[3594,25286,25287],{"class":4459},"        stmt = stmt.where(Task.project_id == project_id)\n",[3594,25289,25290],{"class":3596,"line":3784},[3594,25291,3631],{"emptyLinePlaceholder":3630},[3594,25293,25294,25297,25299],{"class":3596,"line":3790},[3594,25295,25296],{"class":4459},"    tasks = (",[3594,25298,4878],{"class":4455},[3594,25300,14319],{"class":4459},[3594,25302,25303,25305],{"class":3596,"line":3796},[3594,25304,20941],{"class":4455},[3594,25306,25307],{"class":4459}," tasks\n",[3594,25309,25310],{"class":3596,"line":3802},[3594,25311,3631],{"emptyLinePlaceholder":3630},[3594,25313,25314,25316,25318,25320,25323,25325,25327,25329],{"class":3596,"line":3808},[3594,25315,20823],{"class":4690},[3594,25317,7763],{"class":4459},[3594,25319,20828],{"class":4500},[3594,25321,25322],{"class":4622},"{task_id}",[3594,25324,4631],{"class":4500},[3594,25326,3488],{"class":4459},[3594,25328,20838],{"class":4493},[3594,25330,25331],{"class":4459},"=TaskRead)\n",[3594,25333,25334,25336,25338,25341,25343,25346,25348,25350,25352,25354],{"class":3596,"line":3814},[3594,25335,4856],{"class":4622},[3594,25337,4859],{"class":4622},[3594,25339,25340],{"class":4690}," get_task",[3594,25342,7763],{"class":4459},[3594,25344,25345],{"class":4493},"task_id",[3594,25347,9991],{"class":4459},[3594,25349,6930],{"class":4793},[3594,25351,3488],{"class":4459},[3594,25353,20864],{"class":4493},[3594,25355,20867],{"class":4459},[3594,25357,25358],{"class":3596,"line":3820},[3594,25359,25360],{"class":4482},"    # Eager loading коментарів для конкретної задачі\n",[3594,25362,25363],{"class":3596,"line":3826},[3594,25364,25365],{"class":4459},"    stmt = select(Task).where(Task.id == task_id).options(selectinload(Task.comments))\n",[3594,25367,25368,25371,25373],{"class":3596,"line":3832},[3594,25369,25370],{"class":4459},"    task = (",[3594,25372,4878],{"class":4455},[3594,25374,24653],{"class":4459},[3594,25376,25377,25379,25382,25384,25386],{"class":3596,"line":3837},[3594,25378,20886],{"class":4455},[3594,25380,25381],{"class":4459}," task ",[3594,25383,15478],{"class":4622},[3594,25385,20893],{"class":4622},[3594,25387,4570],{"class":4459},[3594,25389,25390,25392],{"class":3596,"line":4819},[3594,25391,20900],{"class":4455},[3594,25393,20903],{"class":4459},[3594,25395,25396,25398],{"class":3596,"line":20653},[3594,25397,20908],{"class":4493},[3594,25399,20911],{"class":4459},[3594,25401,25402,25404,25406,25408,25411,25413,25415,25417],{"class":3596,"line":20662},[3594,25403,20916],{"class":4493},[3594,25405,4497],{"class":4459},[3594,25407,8617],{"class":4622},[3594,25409,25410],{"class":4500},"\"Task with id=",[3594,25412,8623],{"class":4622},[3594,25414,25345],{"class":4459},[3594,25416,8634],{"class":4622},[3594,25418,20932],{"class":4500},[3594,25420,25421],{"class":3596,"line":20670},[3594,25422,4657],{"class":4459},[3594,25424,25425,25427],{"class":3596,"line":21133},[3594,25426,20941],{"class":4455},[3594,25428,25184],{"class":4459},[3594,25430,25431],{"class":3596,"line":21139},[3594,25432,3631],{"emptyLinePlaceholder":3630},[3594,25434,25435,25437,25439,25441,25443,25445,25447,25449],{"class":3596,"line":21145},[3594,25436,24716],{"class":4690},[3594,25438,7763],{"class":4459},[3594,25440,20828],{"class":4500},[3594,25442,25322],{"class":4622},[3594,25444,4631],{"class":4500},[3594,25446,3488],{"class":4459},[3594,25448,20838],{"class":4493},[3594,25450,25331],{"class":4459},[3594,25452,25453,25455,25457,25460],{"class":3596,"line":21150},[3594,25454,4856],{"class":4622},[3594,25456,4859],{"class":4622},[3594,25458,25459],{"class":4690}," update_task",[3594,25461,20976],{"class":4459},[3594,25463,25464,25467,25469,25471,25473,25475,25478,25480],{"class":3596,"line":21158},[3594,25465,25466],{"class":4493},"    task_id",[3594,25468,9991],{"class":4459},[3594,25470,6930],{"class":4793},[3594,25472,3488],{"class":4459},[3594,25474,25154],{"class":4493},[3594,25476,25477],{"class":4459},": TaskUpdate, ",[3594,25479,20864],{"class":4493},[3594,25481,24762],{"class":4459},[3594,25483,25484],{"class":3596,"line":21165},[3594,25485,8585],{"class":4459},[3594,25487,25488,25491,25493],{"class":3596,"line":21173},[3594,25489,25490],{"class":4459},"    task = ",[3594,25492,4878],{"class":4455},[3594,25494,25495],{"class":4459}," session.get(Task, task_id)\n",[3594,25497,25498,25500,25502,25504,25506],{"class":3596,"line":21195},[3594,25499,20886],{"class":4455},[3594,25501,25381],{"class":4459},[3594,25503,15478],{"class":4622},[3594,25505,20893],{"class":4622},[3594,25507,4570],{"class":4459},[3594,25509,25510,25512],{"class":3596,"line":21200},[3594,25511,20900],{"class":4455},[3594,25513,20903],{"class":4459},[3594,25515,25516,25518],{"class":3596,"line":21205},[3594,25517,20908],{"class":4493},[3594,25519,20911],{"class":4459},[3594,25521,25522,25524,25526,25528,25530,25532,25534,25536],{"class":3596,"line":21211},[3594,25523,20916],{"class":4493},[3594,25525,4497],{"class":4459},[3594,25527,8617],{"class":4622},[3594,25529,25410],{"class":4500},[3594,25531,8623],{"class":4622},[3594,25533,25345],{"class":4459},[3594,25535,8634],{"class":4622},[3594,25537,20932],{"class":4500},[3594,25539,25540],{"class":3596,"line":21217},[3594,25541,4657],{"class":4459},[3594,25543,25544],{"class":3596,"line":21227},[3594,25545,3631],{"emptyLinePlaceholder":3630},[3594,25547,25548,25551,25553,25555,25557],{"class":3596,"line":22064},[3594,25549,25550],{"class":4459},"    update_data = task_in.model_dump(",[3594,25552,24834],{"class":4493},[3594,25554,4497],{"class":4459},[3594,25556,4641],{"class":4622},[3594,25558,4558],{"class":4459},[3594,25560,25561,25563,25565,25567],{"class":3596,"line":22073},[3594,25562,20230],{"class":4455},[3594,25564,24847],{"class":4459},[3594,25566,4682],{"class":4455},[3594,25568,24852],{"class":4459},[3594,25570,25571,25573],{"class":3596,"line":22098},[3594,25572,24857],{"class":4690},[3594,25574,25575],{"class":4459},"(task, key, value)\n",[3594,25577,25578],{"class":3596,"line":22121},[3594,25579,3631],{"emptyLinePlaceholder":3630},[3594,25581,25582,25584],{"class":3596,"line":22127},[3594,25583,6638],{"class":4455},[3594,25585,24478],{"class":4459},[3594,25587,25588,25590],{"class":3596,"line":22132},[3594,25589,20941],{"class":4455},[3594,25591,25184],{"class":4459},[3594,25593,25594],{"class":3596,"line":22138},[3594,25595,3631],{"emptyLinePlaceholder":3630},[3594,25597,25598,25600,25602,25604,25606,25608,25610,25612],{"class":3596,"line":22145},[3594,25599,24885],{"class":4690},[3594,25601,7763],{"class":4459},[3594,25603,20828],{"class":4500},[3594,25605,25322],{"class":4622},[3594,25607,4631],{"class":4500},[3594,25609,3488],{"class":4459},[3594,25611,21087],{"class":4493},[3594,25613,24900],{"class":4459},[3594,25615,25616,25618,25620,25623,25625,25627,25629,25631,25633,25635],{"class":3596,"line":22175},[3594,25617,4856],{"class":4622},[3594,25619,4859],{"class":4622},[3594,25621,25622],{"class":4690}," delete_task",[3594,25624,7763],{"class":4459},[3594,25626,25345],{"class":4493},[3594,25628,9991],{"class":4459},[3594,25630,6930],{"class":4793},[3594,25632,3488],{"class":4459},[3594,25634,20864],{"class":4493},[3594,25636,20867],{"class":4459},[3594,25638,25639,25641,25643],{"class":3596,"line":22180},[3594,25640,25490],{"class":4459},[3594,25642,4878],{"class":4455},[3594,25644,25495],{"class":4459},[3594,25646,25647,25649,25651,25653,25655],{"class":3596,"line":22196},[3594,25648,20886],{"class":4455},[3594,25650,25381],{"class":4459},[3594,25652,15478],{"class":4622},[3594,25654,20893],{"class":4622},[3594,25656,4570],{"class":4459},[3594,25658,25659,25661],{"class":3596,"line":22202},[3594,25660,20900],{"class":4455},[3594,25662,20903],{"class":4459},[3594,25664,25665,25667],{"class":3596,"line":22213},[3594,25666,20908],{"class":4493},[3594,25668,20911],{"class":4459},[3594,25670,25671,25673,25675,25677,25679,25681,25683,25685],{"class":3596,"line":22224},[3594,25672,20916],{"class":4493},[3594,25674,4497],{"class":4459},[3594,25676,8617],{"class":4622},[3594,25678,25410],{"class":4500},[3594,25680,8623],{"class":4622},[3594,25682,25345],{"class":4459},[3594,25684,8634],{"class":4622},[3594,25686,20932],{"class":4500},[3594,25688,25689],{"class":3596,"line":22229},[3594,25690,4657],{"class":4459},[3594,25692,25693,25695],{"class":3596,"line":22252},[3594,25694,6638],{"class":4455},[3594,25696,25697],{"class":4459}," session.delete(task)\n",[3585,25699,25702],{"className":4445,"code":25700,"filename":25701,"language":4448,"meta":3590,"style":3590},"import logging\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI\nfrom fastapi.middleware.cors import CORSMiddleware\n\nfrom app.core.database import async_engine\nfrom app.core.exceptions import DomainException\nfrom app.routers import projects, tasks\n\nlogger = logging.getLogger(\"TaskForgeLogger\")\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    logger.info(\"Initializing TaskForge API...\")\n    yield\n    logger.info(\"Shutting down TaskForge API...\")\n    await async_engine.dispose()\n\napp = FastAPI(\n    title=\"TaskForge API\",\n    description=\"Система управління проєктами та тасками на базі PostgreSQL та SQLAlchemy 2.0\",\n    lifespan=lifespan,\n)\n\n# Реєстрація оновлених роутерів\napp.include_router(projects.router)\napp.include_router(tasks.router)\n\n# Глобальний обробник доменних помилок (імпортується з core.exceptions)\n@app.exception_handler(DomainException)\nasync def domain_exception_handler(request, exc):\n    from fastapi.responses import JSONResponse\n    from fastapi import status\n    status_code = status.HTTP_400_BAD_REQUEST\n    if \"NOT_FOUND\" in exc.code:\n        status_code = status.HTTP_404_NOT_FOUND\n    elif \"ACCESS_FORBIDDEN\" in exc.code:\n        status_code = status.HTTP_403_FORBIDDEN\n    return JSONResponse(\n        status_code=status_code,\n        content={\"error\": exc.code, \"message\": exc.message, \"details\": None}\n    )\n\n# Налаштування CORS\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=[\"http:\u002F\u002Flocalhost:3000\"],\n    allow_credentials=True,\n    allow_methods=[\"*\"],\n    allow_headers=[\"*\"],\n)\n","app\u002Fmain.py",[3412,25703,25704,25711,25721,25731,25743,25747,25758,25770,25782,25786,25796,25800,25804,25818,25828,25832,25841,25847,25851,25856,25867,25879,25887,25891,25895,25900,25905,25910,25914,25919,25927,25948,25961,25972,25977,25989,25994,26006,26011,26018,26026,26055,26059,26063,26068,26073,26078,26091,26102,26114,26125],{"__ignoreMap":3590},[3594,25705,25706,25708],{"class":3596,"line":3597},[3594,25707,4456],{"class":4455},[3594,25709,25710],{"class":4459}," logging\n",[3594,25712,25713,25715,25717,25719],{"class":3596,"line":3603},[3594,25714,4465],{"class":4455},[3594,25716,21281],{"class":4459},[3594,25718,4456],{"class":4455},[3594,25720,21286],{"class":4459},[3594,25722,25723,25725,25727,25729],{"class":3596,"line":3609},[3594,25724,4465],{"class":4455},[3594,25726,20700],{"class":4459},[3594,25728,4456],{"class":4455},[3594,25730,21297],{"class":4459},[3594,25732,25733,25735,25738,25740],{"class":3596,"line":3615},[3594,25734,4465],{"class":4455},[3594,25736,25737],{"class":4459}," fastapi.middleware.cors ",[3594,25739,4456],{"class":4455},[3594,25741,25742],{"class":4459}," CORSMiddleware\n",[3594,25744,25745],{"class":3596,"line":3621},[3594,25746,3631],{"emptyLinePlaceholder":3630},[3594,25748,25749,25751,25753,25755],{"class":3596,"line":3627},[3594,25750,4465],{"class":4455},[3594,25752,20737],{"class":4459},[3594,25754,4456],{"class":4455},[3594,25756,25757],{"class":4459}," async_engine\n",[3594,25759,25760,25762,25765,25767],{"class":3596,"line":3634},[3594,25761,4465],{"class":4455},[3594,25763,25764],{"class":4459}," app.core.exceptions ",[3594,25766,4456],{"class":4455},[3594,25768,25769],{"class":4459}," DomainException\n",[3594,25771,25772,25774,25777,25779],{"class":3596,"line":3640},[3594,25773,4465],{"class":4455},[3594,25775,25776],{"class":4459}," app.routers ",[3594,25778,4456],{"class":4455},[3594,25780,25781],{"class":4459}," projects, tasks\n",[3594,25783,25784],{"class":3596,"line":3646},[3594,25785,3631],{"emptyLinePlaceholder":3630},[3594,25787,25788,25791,25794],{"class":3596,"line":3652},[3594,25789,25790],{"class":4459},"logger = logging.getLogger(",[3594,25792,25793],{"class":4500},"\"TaskForgeLogger\"",[3594,25795,4558],{"class":4459},[3594,25797,25798],{"class":3596,"line":3658},[3594,25799,3631],{"emptyLinePlaceholder":3630},[3594,25801,25802],{"class":3596,"line":3664},[3594,25803,21341],{"class":4690},[3594,25805,25806,25808,25810,25812,25814,25816],{"class":3596,"line":3670},[3594,25807,4856],{"class":4622},[3594,25809,4859],{"class":4622},[3594,25811,21350],{"class":4690},[3594,25813,7763],{"class":4459},[3594,25815,21355],{"class":4493},[3594,25817,21358],{"class":4459},[3594,25819,25820,25823,25826],{"class":3596,"line":3676},[3594,25821,25822],{"class":4459},"    logger.info(",[3594,25824,25825],{"class":4500},"\"Initializing TaskForge API...\"",[3594,25827,4558],{"class":4459},[3594,25829,25830],{"class":3596,"line":3681},[3594,25831,21394],{"class":4455},[3594,25833,25834,25836,25839],{"class":3596,"line":3687},[3594,25835,25822],{"class":4459},[3594,25837,25838],{"class":4500},"\"Shutting down TaskForge API...\"",[3594,25840,4558],{"class":4459},[3594,25842,25843,25845],{"class":3596,"line":3693},[3594,25844,6638],{"class":4455},[3594,25846,21406],{"class":4459},[3594,25848,25849],{"class":3596,"line":3699},[3594,25850,3631],{"emptyLinePlaceholder":3630},[3594,25852,25853],{"class":3596,"line":3705},[3594,25854,25855],{"class":4459},"app = FastAPI(\n",[3594,25857,25858,25861,25863,25865],{"class":3596,"line":3711},[3594,25859,25860],{"class":4493},"    title",[3594,25862,4497],{"class":4459},[3594,25864,21423],{"class":4500},[3594,25866,4504],{"class":4459},[3594,25868,25869,25872,25874,25877],{"class":3596,"line":3716},[3594,25870,25871],{"class":4493},"    description",[3594,25873,4497],{"class":4459},[3594,25875,25876],{"class":4500},"\"Система управління проєктами та тасками на базі PostgreSQL та SQLAlchemy 2.0\"",[3594,25878,4504],{"class":4459},[3594,25880,25881,25884],{"class":3596,"line":3721},[3594,25882,25883],{"class":4493},"    lifespan",[3594,25885,25886],{"class":4459},"=lifespan,\n",[3594,25888,25889],{"class":3596,"line":3727},[3594,25890,4558],{"class":4459},[3594,25892,25893],{"class":3596,"line":3733},[3594,25894,3631],{"emptyLinePlaceholder":3630},[3594,25896,25897],{"class":3596,"line":3739},[3594,25898,25899],{"class":4482},"# Реєстрація оновлених роутерів\n",[3594,25901,25902],{"class":3596,"line":3744},[3594,25903,25904],{"class":4459},"app.include_router(projects.router)\n",[3594,25906,25907],{"class":3596,"line":3749},[3594,25908,25909],{"class":4459},"app.include_router(tasks.router)\n",[3594,25911,25912],{"class":3596,"line":3755},[3594,25913,3631],{"emptyLinePlaceholder":3630},[3594,25915,25916],{"class":3596,"line":3761},[3594,25917,25918],{"class":4482},"# Глобальний обробник доменних помилок (імпортується з core.exceptions)\n",[3594,25920,25921,25924],{"class":3596,"line":3766},[3594,25922,25923],{"class":4690},"@app.exception_handler",[3594,25925,25926],{"class":4459},"(DomainException)\n",[3594,25928,25929,25931,25933,25936,25938,25941,25943,25946],{"class":3596,"line":3772},[3594,25930,4856],{"class":4622},[3594,25932,4859],{"class":4622},[3594,25934,25935],{"class":4690}," domain_exception_handler",[3594,25937,7763],{"class":4459},[3594,25939,25940],{"class":4493},"request",[3594,25942,3488],{"class":4459},[3594,25944,25945],{"class":4493},"exc",[3594,25947,8585],{"class":4459},[3594,25949,25950,25953,25956,25958],{"class":3596,"line":3778},[3594,25951,25952],{"class":4455},"    from",[3594,25954,25955],{"class":4459}," fastapi.responses ",[3594,25957,4456],{"class":4455},[3594,25959,25960],{"class":4459}," JSONResponse\n",[3594,25962,25963,25965,25967,25969],{"class":3596,"line":3784},[3594,25964,25952],{"class":4455},[3594,25966,20700],{"class":4459},[3594,25968,4456],{"class":4455},[3594,25970,25971],{"class":4459}," status\n",[3594,25973,25974],{"class":3596,"line":3790},[3594,25975,25976],{"class":4459},"    status_code = status.HTTP_400_BAD_REQUEST\n",[3594,25978,25979,25981,25984,25986],{"class":3596,"line":3796},[3594,25980,20886],{"class":4455},[3594,25982,25983],{"class":4500}," \"NOT_FOUND\"",[3594,25985,14018],{"class":4622},[3594,25987,25988],{"class":4459}," exc.code:\n",[3594,25990,25991],{"class":3596,"line":3802},[3594,25992,25993],{"class":4459},"        status_code = status.HTTP_404_NOT_FOUND\n",[3594,25995,25996,25999,26002,26004],{"class":3596,"line":3808},[3594,25997,25998],{"class":4455},"    elif",[3594,26000,26001],{"class":4500}," \"ACCESS_FORBIDDEN\"",[3594,26003,14018],{"class":4622},[3594,26005,25988],{"class":4459},[3594,26007,26008],{"class":3596,"line":3814},[3594,26009,26010],{"class":4459},"        status_code = status.HTTP_403_FORBIDDEN\n",[3594,26012,26013,26015],{"class":3596,"line":3820},[3594,26014,20941],{"class":4455},[3594,26016,26017],{"class":4459}," JSONResponse(\n",[3594,26019,26020,26023],{"class":3596,"line":3826},[3594,26021,26022],{"class":4493},"        status_code",[3594,26024,26025],{"class":4459},"=status_code,\n",[3594,26027,26028,26031,26034,26037,26040,26043,26046,26049,26051,26053],{"class":3596,"line":3832},[3594,26029,26030],{"class":4493},"        content",[3594,26032,26033],{"class":4459},"={",[3594,26035,26036],{"class":4500},"\"error\"",[3594,26038,26039],{"class":4459},": exc.code, ",[3594,26041,26042],{"class":4500},"\"message\"",[3594,26044,26045],{"class":4459},": exc.message, ",[3594,26047,26048],{"class":4500},"\"details\"",[3594,26050,9991],{"class":4459},[3594,26052,9644],{"class":4622},[3594,26054,3758],{"class":4459},[3594,26056,26057],{"class":3596,"line":3837},[3594,26058,4935],{"class":4459},[3594,26060,26061],{"class":3596,"line":4819},[3594,26062,3631],{"emptyLinePlaceholder":3630},[3594,26064,26065],{"class":3596,"line":20653},[3594,26066,26067],{"class":4482},"# Налаштування CORS\n",[3594,26069,26070],{"class":3596,"line":20662},[3594,26071,26072],{"class":4459},"app.add_middleware(\n",[3594,26074,26075],{"class":3596,"line":20670},[3594,26076,26077],{"class":4459},"    CORSMiddleware,\n",[3594,26079,26080,26083,26085,26088],{"class":3596,"line":21133},[3594,26081,26082],{"class":4493},"    allow_origins",[3594,26084,20795],{"class":4459},[3594,26086,26087],{"class":4500},"\"http:\u002F\u002Flocalhost:3000\"",[3594,26089,26090],{"class":4459},"],\n",[3594,26092,26093,26096,26098,26100],{"class":3596,"line":21139},[3594,26094,26095],{"class":4493},"    allow_credentials",[3594,26097,4497],{"class":4459},[3594,26099,4641],{"class":4622},[3594,26101,4504],{"class":4459},[3594,26103,26104,26107,26109,26112],{"class":3596,"line":21145},[3594,26105,26106],{"class":4493},"    allow_methods",[3594,26108,20795],{"class":4459},[3594,26110,26111],{"class":4500},"\"*\"",[3594,26113,26090],{"class":4459},[3594,26115,26116,26119,26121,26123],{"class":3596,"line":21150},[3594,26117,26118],{"class":4493},"    allow_headers",[3594,26120,20795],{"class":4459},[3594,26122,26111],{"class":4500},[3594,26124,26090],{"class":4459},[3594,26126,26127],{"class":3596,"line":21158},[3594,26128,4558],{"class":4459},[4433,26130,26132],{"id":26131},"крок-4-фіксація-змін-у-репозиторії","Крок 4: Фіксація змін у репозиторії",[3385,26134,26135],{},"Збережіть усі внесені зміни та виконайте фіксацію у вашому сховищі Git:",[3585,26137,26140],{"className":5172,"code":26138,"filename":26139,"language":5174,"meta":3590,"style":3590},"git add .\ngit commit -m \"feat: add PostgreSQL with SQLAlchemy 2.0 async models\"\n","terminal",[3412,26141,26142,26153],{"__ignoreMap":3590},[3594,26143,26144,26147,26150],{"class":3596,"line":3597},[3594,26145,26146],{"class":4690},"git",[3594,26148,26149],{"class":4500}," add",[3594,26151,26152],{"class":4500}," .\n",[3594,26154,26155,26157,26159,26162],{"class":3596,"line":3603},[3594,26156,26146],{"class":4690},[3594,26158,8600],{"class":4500},[3594,26160,26161],{"class":4622}," -m",[3594,26163,26164],{"class":4500}," \"feat: add PostgreSQL with SQLAlchemy 2.0 async models\"\n",[3441,26166],{},[3845,26168,26170],{"id":26169},"практичні-вправи","Практичні вправи",[3385,26172,26173],{},"Виконайте завдання для перевірки та закріплення матеріалу статті.",[4433,26175,26177,26178,26180],{"id":26176},"вправа-1-отримання-обєкта-та-перевірка-на-none-базовий-рівень","Вправа 1: Отримання об'єкта та перевірка на ",[3412,26179,9644],{}," (Базовий рівень)",[3385,26182,26183,26186,26187,26190],{},[3389,26184,26185],{},"Завдання",": Напишіть асинхронний роут ",[3412,26188,26189],{},"GET \u002Fapi\u002Fv1\u002Fprojects\u002F{project_id}",", який:",[3510,26192,26193,26202,26208,26217],{},[3455,26194,26195,26196,3435,26199,3439],{},"Приймає ",[3412,26197,26198],{},"project_id: int",[3412,26200,26201],{},"session: SessionDep",[3455,26203,26204,26205,3439],{},"Використовує асинхронний метод ",[3412,26206,26207],{},"session.get(Project, project_id)",[3455,26209,26210,26211,26213,26214,3439],{},"Якщо проєкт не знайдено в базі даних (метод повернув ",[3412,26212,9644],{},"), викидає ",[3412,26215,26216],{},"HTTPException(status_code=404, detail=\"Project not found\")",[3455,26218,26219],{},"Якщо проєкт знайдено, повертає його.",[4433,26221,26223],{"id":26222},"вправа-2-каскадне-створення-сутностей-в-одній-транзакції-середній-рівень","Вправа 2: Каскадне створення сутностей в одній транзакції (Середній рівень)",[3385,26225,26226,26186,26228,26231],{},[3389,26227,26185],{},[3412,26229,26230],{},"POST \u002Fapi\u002Fv1\u002Fprojects",", який створює новий проєкт та автоматично робить його творця учасником проєкту:",[3510,26233,26234,26241,26251,26257,26269],{},[3455,26235,26195,26236,3435,26239,3439],{},[3412,26237,26238],{},"project_in: ProjectCreate",[3412,26240,26201],{},[3455,26242,26243,26244,26247,26248,26250],{},"Припускається, що ",[3412,26245,26246],{},"owner_id"," дорівнює ",[3412,26249,6486],{}," (для спрощення, поки немає аутентифікації).",[3455,26252,26253,26254,26256],{},"Створює об'єкт ",[3412,26255,14225],{}," та додає його до сесії.",[3455,26258,26253,26259,26261,26262,26265,26266,3439],{},[3412,26260,12709],{}," із роллю ",[3412,26263,26264],{},"MemberRole.OWNER"," для цього проєкту та користувача з ",[3412,26267,26268],{},"id=1",[3455,26270,26271,26272,3439],{},"Зберігає обидві сутності в базу даних за допомогою ",[3412,26273,8231],{},[4433,26275,26277],{"id":26276},"вправа-3-агрегація-та-оптимізація-n1-запитів-професійний-рівень","Вправа 3: Агрегація та оптимізація N+1 запитів (Професійний рівень)",[3385,26279,26280,26282],{},[3389,26281,26185],{},": Напишіть асинхронний запит для отримання списку користувачів разом із кількістю їхніх завдань:",[3510,26284,26285,26298,26301],{},[3455,26286,26287,26288,26291,26292,3435,26295,3439],{},"Напишіть запит ",[3412,26289,26290],{},"select(User, func.count(Task.id))"," із використанням ",[3412,26293,26294],{},"outerjoin(User.tasks)",[3412,26296,26297],{},"group_by(User.id)",[3455,26299,26300],{},"Виконайте запит у сесії та отримайте результати.",[3455,26302,26303,26304,26306],{},"Переконайтеся за допомогою логів (",[3412,26305,6132],{},"), що виконується лише один SQL-запит до бази даних.",[3441,26308],{},[3444,26310,26312],{"id":26311},"підсумок","Підсумок",[3385,26314,26315],{},"У цій статті ми пройшли від перших принципів до побудови повноцінного шару моделей для FastAPI-додатку. Виокремимо ключові ідеї:",[3503,26317,26318,26332,26349,26363,26376,26382],{},[3506,26319,26322,26323,26325,26326,26328,26329,26331],{"icon":26320,"title":26321},"i-lucide-layers-3","Дворівнева архітектура","SQLAlchemy — це екосистема з двох рівнів: ",[3389,26324,3402],{}," (SQL-будівник) і ",[3389,26327,3406],{}," (об'єктне відображення). У версії 2.0 вони об'єднані в єдиний ",[3412,26330,3892],{},"-based API.",[3506,26333,26336,3488,26338,20142,26340,26342,26343,26345,26346,26348],{"icon":26334,"title":26335},"i-lucide-code","Новий Python-first синтаксис",[3412,26337,3931],{},[3412,26339,3434],{},[3412,26341,3438],{}," дають повну type-safety. ",[3412,26344,9276],{}," — NOT NULL, ",[3412,26347,9285],{}," — NULLABLE. IDE тепер розуміє типи атрибутів моделей.",[3506,26350,26352,26354,26355,4025,26357,26359,26360,26362],{"icon":2651,"title":26351},"Engine та Session",[3412,26353,3869],{}," управляє пулом підключень (singleton). ",[3412,26356,3944],{},[3412,26358,3426],{}," — короткоживучий «workspace» для роботи з об'єктами. ",[3412,26361,8513],{}," — обов'язково для async.",[3506,26364,26366,26367,26369,26370,26372,26373,26375],{"icon":92,"title":26365},"N+1 проблема та Eager Loading","Lazy Loading у async не підтримується. Завжди явно вказуйте ",[3412,26368,13645],{}," для колекцій і ",[3412,26371,13648],{}," для одиничних зв'язків. Увімкніть ",[3412,26374,6132],{}," для аудиту запитів.",[3506,26377,26379,26381],{"icon":793,"title":26378},"Unit of Work під капотом",[3412,26380,3944],{}," відстежує об'єкти (Transient → Pending → Persistent → Detached), гарантує унікальність через Identity Map і генерує мінімальні UPDATE-запити через Change Tracking.",[3506,26383,26386,26404],{"icon":26384,"title":26385},"i-lucide-plug","Інтеграція з FastAPI",[3385,26387,26388,26390,26391,26393,26394,26396,26397,26399,26400,26403],{},[3412,26389,21260],{}," — dependency-генератор: один HTTP-запит = одна ",[3412,26392,3426],{},". Автоматичний ",[3412,26395,7672],{}," при успіху та ",[3412,26398,7675],{}," при помилці. ",[3412,26401,26402],{},"create_all()"," — лише для dev\u002Fтестів.",[3417,26405,26406,26407,26410],{},"Наступна стаття ",[3389,26408,26409],{},"23: Alembic — міграції бази даних"," розкриє тему управління схемою БД у production: автогенерацію міграцій, їх застосування та відкат. Alembic є обов'язковим інструментом для будь-якого серйозного проєкту, що використовує SQLAlchemy.",[26412,26413,26414],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s8xlr, html code.shiki .s8xlr{--shiki-light:#AF00DB;--shiki-default:#C586C0;--shiki-dark:#C586C0}html pre.shiki code .sHH4Y, html code.shiki .sHH4Y{--shiki-light:#000000;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .spJ8K, html code.shiki .spJ8K{--shiki-light:#008000;--shiki-default:#6A9955;--shiki-dark:#6A9955}html pre.shiki code .siwwj, html code.shiki .siwwj{--shiki-light:#001080;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .sbdoH, html code.shiki .sbdoH{--shiki-light:#A31515;--shiki-default:#CE9178;--shiki-dark:#CE9178}html pre.shiki code .sJj4R, html code.shiki .sJj4R{--shiki-light:#098658;--shiki-default:#B5CEA8;--shiki-dark:#B5CEA8}html pre.shiki code .su1O8, html code.shiki .su1O8{--shiki-light:#0000FF;--shiki-default:#569CD6;--shiki-dark:#569CD6}html pre.shiki code .s8Opu, html code.shiki .s8Opu{--shiki-light:#795E26;--shiki-default:#DCDCAA;--shiki-dark:#DCDCAA}html pre.shiki code .sN1BT, html code.shiki .sN1BT{--shiki-light:#267F99;--shiki-default:#4EC9B0;--shiki-dark:#4EC9B0}html pre.shiki code .sD7JJ, html code.shiki .sD7JJ{--shiki-light:#000000FF;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .sjcCO, html code.shiki .sjcCO{--shiki-light:#EE0000;--shiki-default:#D7BA7D;--shiki-dark:#D7BA7D}",{"title":3590,"searchDepth":3603,"depth":3603,"links":26416},[26417,26421,26422,26430,26437,26442,26451,26456,26463,26475,26480,26484,26485,26489],{"id":3446,"depth":3603,"text":3447,"children":26418},[26419,26420],{"id":3847,"depth":3609,"text":3848},{"id":3913,"depth":3609,"text":3914},{"id":3980,"depth":3603,"text":3981},{"id":4211,"depth":3603,"text":4212,"children":26423},[26424,26425,26426,26428],{"id":4226,"depth":3609,"text":4227},{"id":4399,"depth":3609,"text":4400},{"id":5974,"depth":3609,"text":26427},"Синхронний Engine: create_engine()",{"id":6140,"depth":3609,"text":26429},"Асинхронний Engine: create_async_engine()",{"id":6288,"depth":3603,"text":26431,"children":26432},"Session та sessionmaker: Одиниця роботи",[26433,26435,26436],{"id":6331,"depth":3609,"text":26434},"sessionmaker та async_sessionmaker",{"id":6646,"depth":3609,"text":26427},{"id":6777,"depth":3609,"text":26429},{"id":7148,"depth":3603,"text":26431,"children":26438},[26439,26440],{"id":7177,"depth":3609,"text":26434},{"id":7446,"depth":3609,"text":26441},"Параметри sessionmaker та async_sessionmaker: повний розбір",{"id":8895,"depth":3603,"text":8896,"children":26443},[26444,26445,26447,26449,26450],{"id":8902,"depth":3609,"text":8903},{"id":9310,"depth":3609,"text":26446},"DeclarativeBase: Базовий клас нового покоління",{"id":9424,"depth":3609,"text":26448},"mapped_column(): Детальна конфігурація стовпців",{"id":9775,"depth":3609,"text":9776},{"id":10745,"depth":3609,"text":10746},{"id":11131,"depth":3603,"text":11132,"children":26452},[26453,26454,26455],{"id":11425,"depth":3609,"text":11426},{"id":11828,"depth":3609,"text":11829},{"id":12232,"depth":3609,"text":12233},{"id":13485,"depth":3603,"text":13486,"children":26457},[26458,26459,26461],{"id":13499,"depth":3609,"text":13500},{"id":13641,"depth":3609,"text":26460},"Вирішення: Eager Loading через selectinload() та joinedload()",{"id":13907,"depth":3609,"text":26462},"Порівняння: .Include() у EF Core ↔ selectinload() у SQLAlchemy",{"id":14506,"depth":3603,"text":14507,"children":26464},[26465,26466,26468,26470,26472,26474],{"id":14540,"depth":3609,"text":14541},{"id":14683,"depth":3609,"text":26467},"Читання даних: select()",{"id":17754,"depth":3609,"text":26469},"Створення даних: insert()",{"id":18201,"depth":3609,"text":26471},"Оновлення даних: update()",{"id":18533,"depth":3609,"text":26473},"Видалення даних: delete()",{"id":18814,"depth":3609,"text":18815},{"id":19800,"depth":3603,"text":19801,"children":26476},[26477,26478,26479],{"id":19816,"depth":3609,"text":19817},{"id":19976,"depth":3609,"text":19977},{"id":20115,"depth":3609,"text":20116},{"id":20340,"depth":3603,"text":26481,"children":26482},"Інтеграція з FastAPI: get_async_session через Dependency Injection",[26483],{"id":21264,"depth":3609,"text":21265},{"id":21449,"depth":3603,"text":21450},{"id":22611,"depth":3603,"text":22612,"children":26486},[26487,26488],{"id":22624,"depth":3609,"text":22625},{"id":26169,"depth":3609,"text":26170},{"id":26311,"depth":3603,"text":26312},"Глибоке занурення в SQLAlchemy 2.0: DeclarativeBase, Mapped[T], Engine, AsyncSession, relationships, N+1 проблема та патерн Unit of Work під капотом. Порівняння з EF Core з ASP.NET.","md",null,{},{"title":2642,"description":26490},"Fb9zHC9Z0XSLd3Mgg8IUN8UVB-43DU5yXt0YyqH6njE",[26497,26499],{"title":2638,"path":2639,"stem":2640,"description":26498,"children":-1},"Глибоке занурення в архітектуру обробки помилок, конвеєр ASGI-middleware та налаштування безпеки CORS у FastAPI. Порівняння з підходами в ASP.NET Core.",{"title":2646,"path":2647,"stem":2648,"description":3590,"children":-1},1783248268930]