[{"data":1,"prerenderedAt":13342},["ShallowReactive",2],{"navigation_docs":3,"-python-fastapi-fastapi-dependency-injection":3379,"-python-fastapi-fastapi-dependency-injection-surround":13337},[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":2634,"body":3381,"description":13331,"extension":13332,"links":13333,"meta":13334,"navigation":3559,"path":2635,"seo":13335,"stem":2636,"__hash__":13336},"docs\u002F05.python\u002Ffastapi\u002F20.fastapi-dependency-injection.md",{"type":3382,"value":3383,"toc":13290},"minimark",[3384,3388,3399,3405,3408,3413,3416,3421,3428,3478,3482,3491,3519,3522,4113,4117,4229,4231,4238,4245,4252,4259,4262,4287,4290,4293,4652,4655,4671,4697,4701,4704,4711,4714,4848,4851,4870,4872,4878,4881,4890,4911,4916,4930,4936,4969,4977,4985,4995,5019,5026,5030,5037,5039,5045,5057,5061,5066,5106,5117,5120,5527,5531,5537,5603,5612,5615,5667,5669,5680,5697,5708,5711,5832,5834,5838,5841,5845,5855,6010,6025,6031,6037,6042,6457,6461,6464,6494,6500,6504,6507,6557,6579,6583,6586,6628,6649,6651,6655,6658,6660,6664,6673,6682,6686,6689,6725,7214,7216,7220,7223,7232,7246,7250,7256,7263,7274,7284,7287,7620,7624,7627,7651,7654,7658,7664,7745,7764,7767,7778,7782,7785,7857,7875,7881,7891,7893,7897,7900,7904,7916,7920,7923,7938,7955,7957,7966,7969,8068,8071,8111,8113,8117,8124,8135,8138,8146,8153,8164,8190,8200,8202,8206,8212,8518,8520,8524,8527,8551,8557,8606,8609,8651,8670,8675,8682,8717,8720,8756,8758,8762,8765,8767,8771,8778,8781,8795,8801,8808,8812,8815,9246,9249,9262,9297,9299,9308,9321,9324,9500,9509,9511,9515,9527,9530,10145,10149,10151,10175,10179,10182,10216,10235,10238,10257,10261,10264,10296,10316,10319,10336,10338,10342,10345,10347,10351,10355,10378,10592,10596,10601,10631,10950,10954,10969,11323,11325,11329,11332,11336,11383,11386,12351,12355,12358,12382,12388,12447,12466,12469,12484,12487,12543,12562,12565,12567,12571,12581,12585,12592,12783,13009,13013,13023,13246,13255,13258,13286],[3385,3386,3387],"p",{},"Впровадження залежностей (Dependency Injection, DI) — це один із фундаментальних патернів проєктування сучасного програмного забезпечення. Він забезпечує слабку пов'язаність (loose coupling) компонентів системи, полегшує тестування та дозволяє дотримуватися принципу інверсії залежностей (Dependency Inversion Principle) із набору SOLID.",[3385,3389,3390,3391,3398],{},"У багатьох вебфреймворках (наприклад, ASP.NET Core у C# або Spring Boot у Java) DI-контейнери є складними інфраструктурними компонентами, що вимагають попередньої конфігурації. У FastAPI розробники пішли іншим шляхом: впровадження залежностей є лаконічним, декларативним і вбудованим безпосередньо у сигнатури функцій обробників маршрутів (ендпоінтів) за допомогою спеціальної функції ",[3392,3393,3394],"strong",{},[3395,3396,3397],"code",{},"Depends()",".",[3385,3400,3401,3402,3404],{},"У цій статті ми детально розглянемо теоретичні підвалини DI, порівняємо підхід FastAPI із IoC-контейнерами в ASP.NET Core, заглянемо під капот роботи ",[3395,3403,3397],{}," та розберемося, як будується і вирішується граф залежностей під час обробки HTTP-запитів.",[3406,3407],"hr",{},[3409,3410,3412],"h2",{"id":3411},"філософські-відмінності-ioc-контейнери-aspnet-core-depends-у-fastapi","Філософські відмінності: IoC-контейнери ASP.NET Core ↔ Depends() у FastAPI",[3385,3414,3415],{},"Щоб зрозуміти унікальність підходу FastAPI, порівняємо його з класичною та знайомою багатьом студентам моделлю DI в ASP.NET Core.",[3417,3418,3420],"h3",{"id":3419},"_1-aspnet-core-централізований-ioc-контейнер","1. ASP.NET Core: Централізований IoC-контейнер",[3385,3422,3423,3424,3427],{},"В екосистемі .NET впровадження залежностей базується на ",[3392,3425,3426],{},"IoC-контейнері"," (Inversion of Control).",[3429,3430,3431,3468],"ul",{},[3432,3433,3434,3437,3438,3441,3442],"li",{},[3392,3435,3436],{},"Реєстрація",": Усі сервіси обов'язково реєструються на старті програми (у файлі ",[3395,3439,3440],{},"Program.cs",") із вказанням їхнього часу життя (Service Lifetime):\n",[3429,3443,3444,3452,3460],{},[3432,3445,3446,3451],{},[3392,3447,3448],{},[3395,3449,3450],{},"Transient"," — новий екземпляр сервісу створюється при кожному запиті до контейнера.",[3432,3453,3454,3459],{},[3392,3455,3456],{},[3395,3457,3458],{},"Scoped"," — один екземпляр створюється на весь життєвий цикл одного HTTP-запиту.",[3432,3461,3462,3467],{},[3392,3463,3464],{},[3395,3465,3466],{},"Singleton"," — один єдиний екземпляр створюється на весь час роботи додатку.",[3432,3469,3470,3473,3474,3477],{},[3392,3471,3472],{},"Впровадження",": Зазвичай реалізується через ",[3392,3475,3476],{},"Constructor Injection"," (впровадження через конструктор класу). Контейнер автоматично аналізує конструктор контролера та підставляє туди зареєстровані реалізації інтерфейсів.",[3417,3479,3481],{"id":3480},"_2-fastapi-декларативний-граф-залежностей","2. FastAPI: Декларативний граф залежностей",[3385,3483,3484,3485,3488,3489,3398],{},"У FastAPI концепція центрального IoC-контейнера з конфігураційними файлами або глобальною реєстрацією типів ",[3392,3486,3487],{},"відсутня",". Замість цього DI працює локально та декларативно безпосередньо в ендпоінтах через функцію ",[3395,3490,3397],{},[3429,3492,3493,3503,3509],{},[3432,3494,3495,3498,3499,3502],{},[3392,3496,3497],{},"Немає інтерфейсної реєстрації",": Залежністю може бути будь-який об'єкт, що викликається (Callable): звичайна функція, клас, генератор або об'єкт із методом ",[3395,3500,3501],{},"__call__",". Вам не потрібно реєструвати їх на старті програми — ви просто вказуєте посилання на них у сигнатурі роута.",[3432,3504,3505,3508],{},[3392,3506,3507],{},"Впровадження через параметри (Parameter Injection)",": Залежності оголошуються як значення за замовчуванням для параметрів функції-ендпоінту.",[3432,3510,3511,3514,3515,3518],{},[3392,3512,3513],{},"Динамічний граф",": Замість зберігання стану сервісів у глобальному контейнері, FastAPI будує ",[3392,3516,3517],{},"граф залежностей"," безпосередньо під час аналізу коду і вирішує його «на льоту» для кожного вхідного HTTP-запиту.",[3385,3520,3521],{},"Порівняємо опис впровадження сесії бази даних та сервісу авторизації в обох екосистемах:",[3523,3524,3525,3760],"code-group",{},[3526,3527,3533],"pre",{"className":3528,"code":3529,"filename":3530,"language":3531,"meta":3532,"style":3532},"language-python shiki shiki-themes light-plus dark-plus dark-plus","from fastapi import FastAPI, Depends\n\napp = FastAPI()\n\n# 1. Визначаємо функції-залежності\ndef get_db():\n    db = DatabaseSession()\n    try:\n        yield db\n    finally:\n        db.close()\n\ndef get_current_user(db = Depends(get_db)):\n    # Залежність get_current_user сама залежить від get_db\n    return TokenService.verify_user(db)\n\n# 2. Впроваджуємо залежності безпосередньо в параметри ендпоінту\n@app.get(\"\u002Fprofile\")\nasync def read_profile(\n    db = Depends(get_db),\n    user = Depends(get_current_user)\n):\n    return {\"user\": user, \"status\": \"active\"}\n","FastAPI (Python)","python","",[3395,3534,3535,3554,3561,3567,3572,3579,3593,3599,3608,3617,3625,3631,3636,3654,3660,3669,3674,3680,3695,3710,3719,3728,3734],{"__ignoreMap":3532},[3536,3537,3540,3544,3548,3551],"span",{"class":3538,"line":3539},"line",1,[3536,3541,3543],{"class":3542},"s8xlr","from",[3536,3545,3547],{"class":3546},"sHH4Y"," fastapi ",[3536,3549,3550],{"class":3542},"import",[3536,3552,3553],{"class":3546}," FastAPI, Depends\n",[3536,3555,3557],{"class":3538,"line":3556},2,[3536,3558,3560],{"emptyLinePlaceholder":3559},true,"\n",[3536,3562,3564],{"class":3538,"line":3563},3,[3536,3565,3566],{"class":3546},"app = FastAPI()\n",[3536,3568,3570],{"class":3538,"line":3569},4,[3536,3571,3560],{"emptyLinePlaceholder":3559},[3536,3573,3575],{"class":3538,"line":3574},5,[3536,3576,3578],{"class":3577},"spJ8K","# 1. Визначаємо функції-залежності\n",[3536,3580,3582,3586,3590],{"class":3538,"line":3581},6,[3536,3583,3585],{"class":3584},"su1O8","def",[3536,3587,3589],{"class":3588},"s8Opu"," get_db",[3536,3591,3592],{"class":3546},"():\n",[3536,3594,3596],{"class":3538,"line":3595},7,[3536,3597,3598],{"class":3546},"    db = DatabaseSession()\n",[3536,3600,3602,3605],{"class":3538,"line":3601},8,[3536,3603,3604],{"class":3542},"    try",[3536,3606,3607],{"class":3546},":\n",[3536,3609,3611,3614],{"class":3538,"line":3610},9,[3536,3612,3613],{"class":3542},"        yield",[3536,3615,3616],{"class":3546}," db\n",[3536,3618,3620,3623],{"class":3538,"line":3619},10,[3536,3621,3622],{"class":3542},"    finally",[3536,3624,3607],{"class":3546},[3536,3626,3628],{"class":3538,"line":3627},11,[3536,3629,3630],{"class":3546},"        db.close()\n",[3536,3632,3634],{"class":3538,"line":3633},12,[3536,3635,3560],{"emptyLinePlaceholder":3559},[3536,3637,3639,3641,3644,3647,3651],{"class":3538,"line":3638},13,[3536,3640,3585],{"class":3584},[3536,3642,3643],{"class":3588}," get_current_user",[3536,3645,3646],{"class":3546},"(",[3536,3648,3650],{"class":3649},"siwwj","db",[3536,3652,3653],{"class":3546}," = Depends(get_db)):\n",[3536,3655,3657],{"class":3538,"line":3656},14,[3536,3658,3659],{"class":3577},"    # Залежність get_current_user сама залежить від get_db\n",[3536,3661,3663,3666],{"class":3538,"line":3662},15,[3536,3664,3665],{"class":3542},"    return",[3536,3667,3668],{"class":3546}," TokenService.verify_user(db)\n",[3536,3670,3672],{"class":3538,"line":3671},16,[3536,3673,3560],{"emptyLinePlaceholder":3559},[3536,3675,3677],{"class":3538,"line":3676},17,[3536,3678,3679],{"class":3577},"# 2. Впроваджуємо залежності безпосередньо в параметри ендпоінту\n",[3536,3681,3683,3686,3688,3692],{"class":3538,"line":3682},18,[3536,3684,3685],{"class":3588},"@app.get",[3536,3687,3646],{"class":3546},[3536,3689,3691],{"class":3690},"sbdoH","\"\u002Fprofile\"",[3536,3693,3694],{"class":3546},")\n",[3536,3696,3698,3701,3704,3707],{"class":3538,"line":3697},19,[3536,3699,3700],{"class":3584},"async",[3536,3702,3703],{"class":3584}," def",[3536,3705,3706],{"class":3588}," read_profile",[3536,3708,3709],{"class":3546},"(\n",[3536,3711,3713,3716],{"class":3538,"line":3712},20,[3536,3714,3715],{"class":3649},"    db",[3536,3717,3718],{"class":3546}," = Depends(get_db),\n",[3536,3720,3722,3725],{"class":3538,"line":3721},21,[3536,3723,3724],{"class":3649},"    user",[3536,3726,3727],{"class":3546}," = Depends(get_current_user)\n",[3536,3729,3731],{"class":3538,"line":3730},22,[3536,3732,3733],{"class":3546},"):\n",[3536,3735,3737,3739,3742,3745,3748,3751,3754,3757],{"class":3538,"line":3736},23,[3536,3738,3665],{"class":3542},[3536,3740,3741],{"class":3546}," {",[3536,3743,3744],{"class":3690},"\"user\"",[3536,3746,3747],{"class":3546},": user, ",[3536,3749,3750],{"class":3690},"\"status\"",[3536,3752,3753],{"class":3546},": ",[3536,3755,3756],{"class":3690},"\"active\"",[3536,3758,3759],{"class":3546},"}\n",[3526,3761,3766],{"className":3762,"code":3763,"filename":3764,"language":3765,"meta":3532,"style":3532},"language-csharp shiki shiki-themes light-plus dark-plus dark-plus","\u002F\u002F 1. Реєстрація в Program.cs\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddScoped\u003CIDbSession, DatabaseSession>();\nbuilder.Services.AddScoped\u003CIUserService, UserService>();\nbuilder.Services.AddControllers();\n\n\u002F\u002F 2. Впровадження через конструктор контролера\n[ApiController]\n[Route(\"[controller]\")]\npublic class ProfileController : ControllerBase\n{\n    private readonly IDbSession _db;\n    private readonly IUserService _userService;\n\n    public ProfileController(IDbSession db, IUserService userService)\n    {\n        _db = db;\n        _userService = userService;\n    }\n\n    [HttpGet]\n    public IActionResult GetProfile()\n    {\n        var user = _userService.GetCurrentUser();\n        return Ok(new { user, status = \"active\" });\n    }\n}\n","ASP.NET Core (C#)","csharp",[3395,3767,3768,3773,3800,3831,3855,3871,3875,3880,3891,3906,3923,3928,3945,3959,3963,3986,3991,4002,4014,4019,4023,4033,4046,4050,4071,4103,4108],{"__ignoreMap":3532},[3536,3769,3770],{"class":3538,"line":3539},[3536,3771,3772],{"class":3577},"\u002F\u002F 1. Реєстрація в Program.cs\n",[3536,3774,3775,3778,3781,3784,3787,3789,3792,3794,3797],{"class":3538,"line":3556},[3536,3776,3777],{"class":3584},"var",[3536,3779,3780],{"class":3649}," builder",[3536,3782,3783],{"class":3546}," = ",[3536,3785,3786],{"class":3649},"WebApplication",[3536,3788,3398],{"class":3546},[3536,3790,3791],{"class":3588},"CreateBuilder",[3536,3793,3646],{"class":3546},[3536,3795,3796],{"class":3649},"args",[3536,3798,3799],{"class":3546},");\n",[3536,3801,3802,3805,3807,3810,3812,3815,3818,3822,3825,3828],{"class":3538,"line":3563},[3536,3803,3804],{"class":3649},"builder",[3536,3806,3398],{"class":3546},[3536,3808,3809],{"class":3649},"Services",[3536,3811,3398],{"class":3546},[3536,3813,3814],{"class":3588},"AddScoped",[3536,3816,3817],{"class":3546},"\u003C",[3536,3819,3821],{"class":3820},"sN1BT","IDbSession",[3536,3823,3824],{"class":3546},", ",[3536,3826,3827],{"class":3820},"DatabaseSession",[3536,3829,3830],{"class":3546},">();\n",[3536,3832,3833,3835,3837,3839,3841,3843,3845,3848,3850,3853],{"class":3538,"line":3569},[3536,3834,3804],{"class":3649},[3536,3836,3398],{"class":3546},[3536,3838,3809],{"class":3649},[3536,3840,3398],{"class":3546},[3536,3842,3814],{"class":3588},[3536,3844,3817],{"class":3546},[3536,3846,3847],{"class":3820},"IUserService",[3536,3849,3824],{"class":3546},[3536,3851,3852],{"class":3820},"UserService",[3536,3854,3830],{"class":3546},[3536,3856,3857,3859,3861,3863,3865,3868],{"class":3538,"line":3574},[3536,3858,3804],{"class":3649},[3536,3860,3398],{"class":3546},[3536,3862,3809],{"class":3649},[3536,3864,3398],{"class":3546},[3536,3866,3867],{"class":3588},"AddControllers",[3536,3869,3870],{"class":3546},"();\n",[3536,3872,3873],{"class":3538,"line":3581},[3536,3874,3560],{"emptyLinePlaceholder":3559},[3536,3876,3877],{"class":3538,"line":3595},[3536,3878,3879],{"class":3577},"\u002F\u002F 2. Впровадження через конструктор контролера\n",[3536,3881,3882,3885,3888],{"class":3538,"line":3601},[3536,3883,3884],{"class":3546},"[",[3536,3886,3887],{"class":3820},"ApiController",[3536,3889,3890],{"class":3546},"]\n",[3536,3892,3893,3895,3898,3900,3903],{"class":3538,"line":3610},[3536,3894,3884],{"class":3546},[3536,3896,3897],{"class":3820},"Route",[3536,3899,3646],{"class":3546},[3536,3901,3902],{"class":3690},"\"[controller]\"",[3536,3904,3905],{"class":3546},")]\n",[3536,3907,3908,3911,3914,3917,3920],{"class":3538,"line":3619},[3536,3909,3910],{"class":3584},"public",[3536,3912,3913],{"class":3584}," class",[3536,3915,3916],{"class":3820}," ProfileController",[3536,3918,3919],{"class":3546}," : ",[3536,3921,3922],{"class":3820},"ControllerBase\n",[3536,3924,3925],{"class":3538,"line":3627},[3536,3926,3927],{"class":3546},"{\n",[3536,3929,3930,3933,3936,3939,3942],{"class":3538,"line":3633},[3536,3931,3932],{"class":3584},"    private",[3536,3934,3935],{"class":3584}," readonly",[3536,3937,3938],{"class":3820}," IDbSession",[3536,3940,3941],{"class":3649}," _db",[3536,3943,3944],{"class":3546},";\n",[3536,3946,3947,3949,3951,3954,3957],{"class":3538,"line":3638},[3536,3948,3932],{"class":3584},[3536,3950,3935],{"class":3584},[3536,3952,3953],{"class":3820}," IUserService",[3536,3955,3956],{"class":3649}," _userService",[3536,3958,3944],{"class":3546},[3536,3960,3961],{"class":3538,"line":3656},[3536,3962,3560],{"emptyLinePlaceholder":3559},[3536,3964,3965,3968,3970,3972,3974,3977,3979,3981,3984],{"class":3538,"line":3662},[3536,3966,3967],{"class":3584},"    public",[3536,3969,3916],{"class":3588},[3536,3971,3646],{"class":3546},[3536,3973,3821],{"class":3820},[3536,3975,3976],{"class":3649}," db",[3536,3978,3824],{"class":3546},[3536,3980,3847],{"class":3820},[3536,3982,3983],{"class":3649}," userService",[3536,3985,3694],{"class":3546},[3536,3987,3988],{"class":3538,"line":3671},[3536,3989,3990],{"class":3546},"    {\n",[3536,3992,3993,3996,3998,4000],{"class":3538,"line":3676},[3536,3994,3995],{"class":3649},"        _db",[3536,3997,3783],{"class":3546},[3536,3999,3650],{"class":3649},[3536,4001,3944],{"class":3546},[3536,4003,4004,4007,4009,4012],{"class":3538,"line":3682},[3536,4005,4006],{"class":3649},"        _userService",[3536,4008,3783],{"class":3546},[3536,4010,4011],{"class":3649},"userService",[3536,4013,3944],{"class":3546},[3536,4015,4016],{"class":3538,"line":3697},[3536,4017,4018],{"class":3546},"    }\n",[3536,4020,4021],{"class":3538,"line":3712},[3536,4022,3560],{"emptyLinePlaceholder":3559},[3536,4024,4025,4028,4031],{"class":3538,"line":3721},[3536,4026,4027],{"class":3546},"    [",[3536,4029,4030],{"class":3820},"HttpGet",[3536,4032,3890],{"class":3546},[3536,4034,4035,4037,4040,4043],{"class":3538,"line":3730},[3536,4036,3967],{"class":3584},[3536,4038,4039],{"class":3820}," IActionResult",[3536,4041,4042],{"class":3588}," GetProfile",[3536,4044,4045],{"class":3546},"()\n",[3536,4047,4048],{"class":3538,"line":3736},[3536,4049,3990],{"class":3546},[3536,4051,4053,4056,4059,4061,4064,4066,4069],{"class":3538,"line":4052},24,[3536,4054,4055],{"class":3584},"        var",[3536,4057,4058],{"class":3649}," user",[3536,4060,3783],{"class":3546},[3536,4062,4063],{"class":3649},"_userService",[3536,4065,3398],{"class":3546},[3536,4067,4068],{"class":3588},"GetCurrentUser",[3536,4070,3870],{"class":3546},[3536,4072,4074,4077,4080,4082,4085,4088,4091,4093,4096,4098,4100],{"class":3538,"line":4073},25,[3536,4075,4076],{"class":3542},"        return",[3536,4078,4079],{"class":3588}," Ok",[3536,4081,3646],{"class":3546},[3536,4083,4084],{"class":3584},"new",[3536,4086,4087],{"class":3546}," { ",[3536,4089,4090],{"class":3649},"user",[3536,4092,3824],{"class":3546},[3536,4094,4095],{"class":3649},"status",[3536,4097,3783],{"class":3546},[3536,4099,3756],{"class":3690},[3536,4101,4102],{"class":3546}," });\n",[3536,4104,4106],{"class":3538,"line":4105},26,[3536,4107,4018],{"class":3546},[3536,4109,4111],{"class":3538,"line":4110},27,[3536,4112,3759],{"class":3546},[3417,4114,4116],{"id":4115},"порівняльна-таблиця-концепцій-di","Порівняльна таблиця концепцій DI",[4118,4119,4120,4136],"table",{},[4121,4122,4123],"thead",{},[4124,4125,4126,4131,4134],"tr",{},[4127,4128,4130],"th",{"align":4129},"left","Характеристика",[4127,4132,4133],{"align":4129},"ASP.NET Core",[4127,4135,2609],{"align":4129},[4137,4138,4139,4160,4173,4186,4210],"tbody",{},[4124,4140,4141,4147,4157],{},[4142,4143,4144],"td",{"align":4129},[3392,4145,4146],{},"Місце реєстрації",[4142,4148,4149,4150,4152,4153,4156],{"align":4129},"Глобально в ",[3395,4151,3440],{}," (",[3395,4154,4155],{},"builder.Services",")",[4142,4158,4159],{"align":4129},"Локально в місці використання (у функціях або роутерах)",[4124,4161,4162,4167,4170],{},[4142,4163,4164],{"align":4129},[3392,4165,4166],{},"Спосіб впровадження",[4142,4168,4169],{"align":4129},"Constructor Injection (через конструктор класу)",[4142,4171,4172],{"align":4129},"Parameter Injection (через аргументи функції обробника)",[4124,4174,4175,4180,4183],{},[4142,4176,4177],{"align":4129},[3392,4178,4179],{},"Головний механізм",[4142,4181,4182],{"align":4129},"IoC-контейнер (резолюція типів)",[4142,4184,4185],{"align":4129},"Граф викликів (DAG - Directed Acyclic Graph)",[4124,4187,4188,4193,4203],{},[4142,4189,4190],{"align":4129},[3392,4191,4192],{},"Життєвий цикл (Lifetime)",[4142,4194,4195,4196,4198,4199,4198,4201,4156],{"align":4129},"Керований контейнером (",[3395,4197,3450],{},"\u002F",[3395,4200,3458],{},[3395,4202,3466],{},[4142,4204,4205,4206,4209],{"align":4129},"Обмежений HTTP-запитом (кешування ",[3395,4207,4208],{},"use_cache=True"," або lifespan)",[4124,4211,4212,4217,4223],{},[4142,4213,4214],{"align":4129},[3392,4215,4216],{},"Тестування\u002FMocking",[4142,4218,4219,4220,4156],{"align":4129},"Перевизначення в контейнері (",[3395,4221,4222],{},"ConfigureTestServices",[4142,4224,4225,4226,4156],{"align":4129},"Словник перевизначень (",[3395,4227,4228],{},"app.dependency_overrides",[3406,4230],{},[3409,4232,4234,4235,4237],{"id":4233},"механіка-depends-під-капотом","Механіка ",[3395,4236,3397],{}," під капотом",[3385,4239,4240,4241,4244],{},"Як саме FastAPI дізнається, що потрібно викликати функцію ",[3395,4242,4243],{},"get_db"," та підставити її результат у параметри ендпоінту?",[3417,4246,4248,4249,4156],{"id":4247},"_1-аналіз-за-допомогою-рефлексії-inspect","1. Аналіз за допомогою рефлексії (",[3395,4250,4251],{},"inspect",[3385,4253,4254,4255,3398],{},"Коли ви запускаєте FastAPI-додаток, під час старту фреймворк сканує всі зареєстровані маршрути за допомогою модуля стандартної бібліотеки Python ",[3392,4256,4257],{},[3395,4258,4251],{},[3385,4260,4261],{},"Він вивчає сигнатуру кожної функції-ендпоінту:",[3429,4263,4264,4275,4282],{},[3432,4265,4266,4267,4270,4271,4274],{},"Знаходить параметри, які мають значення за замовчуванням типу ",[3395,4268,4269],{},"Depends(...)"," (об'єкт класу ",[3395,4272,4273],{},"fastapi.params.Depends",").",[3432,4276,4277,4278,4281],{},"Витягує з об'єкта ",[3395,4279,4280],{},"Depends"," внутрішню функцію (callable), яку потрібно викликати.",[3432,4283,4284,4285,3398],{},"Рекурсивно перевіряє сигнатуру цієї внутрішньої функції на наявність інших ",[3395,4286,4280],{},[3385,4288,4289],{},"На основі цього сканування будується статична карта зв'язків.",[3385,4291,4292],{},"Щоб краще зрозуміти цей процес, розглянемо спрощену чисту модель того, як FastAPI сканує функції за допомогою рефлексії Python та витягує з них залежності:",[3526,4294,4297],{"className":3528,"code":4295,"filename":4296,"language":3531,"meta":3532,"style":3532},"# inspect_di_demo.py\nimport inspect\n\nclass MockDepends:\n    \"\"\"Спрощена імітація класу Depends у FastAPI.\"\"\"\n    def __init__(self, dependency):\n        self.dependency = dependency\n\n# --- НАШІ ЗАЛЕЖНОСТІ ---\n\ndef get_db():\n    return \"DatabaseConnection\"\n\ndef get_current_user(db = MockDepends(get_db)):\n    return \"UserEntity\"\n\n# --- ЕНДПОІНТ (ОБРОБНИК МАРШРУТУ) ---\n\ndef read_tasks_endpoint(user = MockDepends(get_current_user)):\n    pass\n\n# --- ІМІТАЦІЯ RESOLVER ---\n\ndef inspect_dependencies(func, depth=0):\n    # Отримуємо сигнатуру функції за допомогою модуля inspect\n    signature = inspect.signature(func)\n    indent = \"  \" * depth\n\n    for param_name, param in signature.parameters.items():\n        # Перевіряємо, чи є значення за замовчуванням об'єктом MockDepends\n        if isinstance(param.default, MockDepends):\n            dep_func = param.default.dependency\n            print(f\"{indent}↳ Параметр '{param_name}' потребує виклику: {dep_func.__name__}()\")\n            # Рекурсивно скануємо знайдену залежність\n            inspect_dependencies(dep_func, depth + 1)\n\nprint(f\"Аналіз сигнатури ендпоінту '{read_tasks_endpoint.__name__}':\")\ninspect_dependencies(read_tasks_endpoint)\n","inspect_di_demo.py",[3395,4298,4299,4304,4311,4315,4325,4330,4350,4358,4362,4367,4371,4379,4386,4390,4403,4410,4414,4419,4423,4437,4442,4446,4451,4455,4481,4486,4491,4502,4507,4522,4528,4540,4546,4597,4603,4614,4619,4646],{"__ignoreMap":3532},[3536,4300,4301],{"class":3538,"line":3539},[3536,4302,4303],{"class":3577},"# inspect_di_demo.py\n",[3536,4305,4306,4308],{"class":3538,"line":3556},[3536,4307,3550],{"class":3542},[3536,4309,4310],{"class":3546}," inspect\n",[3536,4312,4313],{"class":3538,"line":3563},[3536,4314,3560],{"emptyLinePlaceholder":3559},[3536,4316,4317,4320,4323],{"class":3538,"line":3569},[3536,4318,4319],{"class":3584},"class",[3536,4321,4322],{"class":3820}," MockDepends",[3536,4324,3607],{"class":3546},[3536,4326,4327],{"class":3538,"line":3574},[3536,4328,4329],{"class":3690},"    \"\"\"Спрощена імітація класу Depends у FastAPI.\"\"\"\n",[3536,4331,4332,4335,4338,4340,4343,4345,4348],{"class":3538,"line":3581},[3536,4333,4334],{"class":3584},"    def",[3536,4336,4337],{"class":3588}," __init__",[3536,4339,3646],{"class":3546},[3536,4341,4342],{"class":3649},"self",[3536,4344,3824],{"class":3546},[3536,4346,4347],{"class":3649},"dependency",[3536,4349,3733],{"class":3546},[3536,4351,4352,4355],{"class":3538,"line":3595},[3536,4353,4354],{"class":3584},"        self",[3536,4356,4357],{"class":3546},".dependency = dependency\n",[3536,4359,4360],{"class":3538,"line":3601},[3536,4361,3560],{"emptyLinePlaceholder":3559},[3536,4363,4364],{"class":3538,"line":3610},[3536,4365,4366],{"class":3577},"# --- НАШІ ЗАЛЕЖНОСТІ ---\n",[3536,4368,4369],{"class":3538,"line":3619},[3536,4370,3560],{"emptyLinePlaceholder":3559},[3536,4372,4373,4375,4377],{"class":3538,"line":3627},[3536,4374,3585],{"class":3584},[3536,4376,3589],{"class":3588},[3536,4378,3592],{"class":3546},[3536,4380,4381,4383],{"class":3538,"line":3633},[3536,4382,3665],{"class":3542},[3536,4384,4385],{"class":3690}," \"DatabaseConnection\"\n",[3536,4387,4388],{"class":3538,"line":3638},[3536,4389,3560],{"emptyLinePlaceholder":3559},[3536,4391,4392,4394,4396,4398,4400],{"class":3538,"line":3656},[3536,4393,3585],{"class":3584},[3536,4395,3643],{"class":3588},[3536,4397,3646],{"class":3546},[3536,4399,3650],{"class":3649},[3536,4401,4402],{"class":3546}," = MockDepends(get_db)):\n",[3536,4404,4405,4407],{"class":3538,"line":3662},[3536,4406,3665],{"class":3542},[3536,4408,4409],{"class":3690}," \"UserEntity\"\n",[3536,4411,4412],{"class":3538,"line":3671},[3536,4413,3560],{"emptyLinePlaceholder":3559},[3536,4415,4416],{"class":3538,"line":3676},[3536,4417,4418],{"class":3577},"# --- ЕНДПОІНТ (ОБРОБНИК МАРШРУТУ) ---\n",[3536,4420,4421],{"class":3538,"line":3682},[3536,4422,3560],{"emptyLinePlaceholder":3559},[3536,4424,4425,4427,4430,4432,4434],{"class":3538,"line":3697},[3536,4426,3585],{"class":3584},[3536,4428,4429],{"class":3588}," read_tasks_endpoint",[3536,4431,3646],{"class":3546},[3536,4433,4090],{"class":3649},[3536,4435,4436],{"class":3546}," = MockDepends(get_current_user)):\n",[3536,4438,4439],{"class":3538,"line":3712},[3536,4440,4441],{"class":3542},"    pass\n",[3536,4443,4444],{"class":3538,"line":3721},[3536,4445,3560],{"emptyLinePlaceholder":3559},[3536,4447,4448],{"class":3538,"line":3730},[3536,4449,4450],{"class":3577},"# --- ІМІТАЦІЯ RESOLVER ---\n",[3536,4452,4453],{"class":3538,"line":3736},[3536,4454,3560],{"emptyLinePlaceholder":3559},[3536,4456,4457,4459,4462,4464,4467,4469,4472,4475,4479],{"class":3538,"line":4052},[3536,4458,3585],{"class":3584},[3536,4460,4461],{"class":3588}," inspect_dependencies",[3536,4463,3646],{"class":3546},[3536,4465,4466],{"class":3649},"func",[3536,4468,3824],{"class":3546},[3536,4470,4471],{"class":3649},"depth",[3536,4473,4474],{"class":3546},"=",[3536,4476,4478],{"class":4477},"sJj4R","0",[3536,4480,3733],{"class":3546},[3536,4482,4483],{"class":3538,"line":4073},[3536,4484,4485],{"class":3577},"    # Отримуємо сигнатуру функції за допомогою модуля inspect\n",[3536,4487,4488],{"class":3538,"line":4105},[3536,4489,4490],{"class":3546},"    signature = inspect.signature(func)\n",[3536,4492,4493,4496,4499],{"class":3538,"line":4110},[3536,4494,4495],{"class":3546},"    indent = ",[3536,4497,4498],{"class":3690},"\"  \"",[3536,4500,4501],{"class":3546}," * depth\n",[3536,4503,4505],{"class":3538,"line":4504},28,[3536,4506,3560],{"emptyLinePlaceholder":3559},[3536,4508,4510,4513,4516,4519],{"class":3538,"line":4509},29,[3536,4511,4512],{"class":3542},"    for",[3536,4514,4515],{"class":3546}," param_name, param ",[3536,4517,4518],{"class":3542},"in",[3536,4520,4521],{"class":3546}," signature.parameters.items():\n",[3536,4523,4525],{"class":3538,"line":4524},30,[3536,4526,4527],{"class":3577},"        # Перевіряємо, чи є значення за замовчуванням об'єктом MockDepends\n",[3536,4529,4531,4534,4537],{"class":3538,"line":4530},31,[3536,4532,4533],{"class":3542},"        if",[3536,4535,4536],{"class":3588}," isinstance",[3536,4538,4539],{"class":3546},"(param.default, MockDepends):\n",[3536,4541,4543],{"class":3538,"line":4542},32,[3536,4544,4545],{"class":3546},"            dep_func = param.default.dependency\n",[3536,4547,4549,4552,4554,4557,4560,4563,4566,4569,4572,4574,4577,4579,4582,4584,4587,4590,4592,4595],{"class":3538,"line":4548},33,[3536,4550,4551],{"class":3588},"            print",[3536,4553,3646],{"class":3546},[3536,4555,4556],{"class":3584},"f",[3536,4558,4559],{"class":3690},"\"",[3536,4561,4562],{"class":3584},"{",[3536,4564,4565],{"class":3546},"indent",[3536,4567,4568],{"class":3584},"}",[3536,4570,4571],{"class":3690},"↳ Параметр '",[3536,4573,4562],{"class":3584},[3536,4575,4576],{"class":3546},"param_name",[3536,4578,4568],{"class":3584},[3536,4580,4581],{"class":3690},"' потребує виклику: ",[3536,4583,4562],{"class":3584},[3536,4585,4586],{"class":3546},"dep_func.",[3536,4588,4589],{"class":3649},"__name__",[3536,4591,4568],{"class":3584},[3536,4593,4594],{"class":3690},"()\"",[3536,4596,3694],{"class":3546},[3536,4598,4600],{"class":3538,"line":4599},34,[3536,4601,4602],{"class":3577},"            # Рекурсивно скануємо знайдену залежність\n",[3536,4604,4606,4609,4612],{"class":3538,"line":4605},35,[3536,4607,4608],{"class":3546},"            inspect_dependencies(dep_func, depth + ",[3536,4610,4611],{"class":4477},"1",[3536,4613,3694],{"class":3546},[3536,4615,4617],{"class":3538,"line":4616},36,[3536,4618,3560],{"emptyLinePlaceholder":3559},[3536,4620,4622,4625,4627,4629,4632,4634,4637,4639,4641,4644],{"class":3538,"line":4621},37,[3536,4623,4624],{"class":3588},"print",[3536,4626,3646],{"class":3546},[3536,4628,4556],{"class":3584},[3536,4630,4631],{"class":3690},"\"Аналіз сигнатури ендпоінту '",[3536,4633,4562],{"class":3584},[3536,4635,4636],{"class":3546},"read_tasks_endpoint.",[3536,4638,4589],{"class":3649},[3536,4640,4568],{"class":3584},[3536,4642,4643],{"class":3690},"':\"",[3536,4645,3694],{"class":3546},[3536,4647,4649],{"class":3538,"line":4648},38,[3536,4650,4651],{"class":3546},"inspect_dependencies(read_tasks_endpoint)\n",[3385,4653,4654],{},"Для запуску цього демонстраційного скрипту виконайте наступну команду:",[3526,4656,4661],{"className":4657,"code":4658,"filename":4659,"language":4660,"meta":3532,"style":3532},"language-bash shiki shiki-themes light-plus dark-plus dark-plus","python3 inspect_di_demo.py\n","terminal","bash",[3395,4662,4663],{"__ignoreMap":3532},[3536,4664,4665,4668],{"class":3538,"line":3539},[3536,4666,4667],{"class":3588},"python3",[3536,4669,4670],{"class":3690}," inspect_di_demo.py\n",[4672,4673,4676,4681,4690],"terminal-preview",{":cursor":4674,"title":4675},"false","Вивід роботи рефлексії inspect",[4677,4678,4680],"div",{"className":4679},[3538],"Аналіз сигнатури ендпоінту 'read_tasks_endpoint':",[4677,4682,4684],{"className":4683},[3538],[3536,4685,4689],{"className":4686},[4687,4688],"text-blue-400","font-bold","↳ Параметр 'user' потребує виклику: get_current_user()",[4677,4691,4693],{"className":4692},[3538],[3536,4694,4696],{"className":4695},[4687,4688],"  ↳ Параметр 'db' потребує виклику: get_db()",[3417,4698,4700],{"id":4699},"_2-побудова-та-вирішення-графа-залежностей-dag","2. Побудова та вирішення графа залежностей (DAG)",[3385,4702,4703],{},"Залежності можуть залежати від інших залежностей. Наприклад, обробник маршруту потребує сервіс користувача, який потребує сесію бази даних, яка потребує підключення до пулу з'єднань.",[3385,4705,4706,4707,4710],{},"FastAPI моделює цей ланцюжок як ",[3392,4708,4709],{},"Орієнтований ациклічний граф (Directed Acyclic Graph, DAG)",". Напрямок ребер у графі вказує черговість виконання залежностей.",[3385,4712,4713],{},"Розглянемо візуалізацію вирішення залежностей для типового HTTP-запиту:",[4715,4716,4717],"plant-uml",{},[3526,4718,4722],{"className":4719,"code":4720,"language":4721,"meta":3532,"style":3532},"language-plantuml shiki shiki-themes light-plus dark-plus dark-plus","@startuml\nskinparam style plain\nskinparam monochrome false\nskinparam roundCorner 8\nskinparam DefaultFontName \"Courier New\"\n\npackage \"HTTP Request Lifecycle\" {\n    [HTTP Client Request] as Req\n\n    node \"FastAPI Dependency Resolver (DAG)\" {\n        [Route Handler: get_tasks] as Endpoint\n        [Dependency: get_current_user] as Auth\n        [Dependency: get_db_session] as DB\n        [Dependency: get_token] as Token\n        [Dependency: get_db_engine] as Engine\n\n        Endpoint --> Auth : Depends()\n        Endpoint --> DB : Depends()\n        Auth --> Token : Depends()\n        Auth --> DB : Depends()\n        DB --> Engine : Depends()\n    }\n}\n\nReq --> Endpoint : triggers validation & execution\n@enduml\n","plantuml",[3395,4723,4724,4729,4734,4739,4744,4749,4753,4758,4763,4767,4772,4777,4782,4787,4792,4797,4801,4806,4811,4816,4821,4826,4830,4834,4838,4843],{"__ignoreMap":3532},[3536,4725,4726],{"class":3538,"line":3539},[3536,4727,4728],{},"@startuml\n",[3536,4730,4731],{"class":3538,"line":3556},[3536,4732,4733],{},"skinparam style plain\n",[3536,4735,4736],{"class":3538,"line":3563},[3536,4737,4738],{},"skinparam monochrome false\n",[3536,4740,4741],{"class":3538,"line":3569},[3536,4742,4743],{},"skinparam roundCorner 8\n",[3536,4745,4746],{"class":3538,"line":3574},[3536,4747,4748],{},"skinparam DefaultFontName \"Courier New\"\n",[3536,4750,4751],{"class":3538,"line":3581},[3536,4752,3560],{"emptyLinePlaceholder":3559},[3536,4754,4755],{"class":3538,"line":3595},[3536,4756,4757],{},"package \"HTTP Request Lifecycle\" {\n",[3536,4759,4760],{"class":3538,"line":3601},[3536,4761,4762],{},"    [HTTP Client Request] as Req\n",[3536,4764,4765],{"class":3538,"line":3610},[3536,4766,3560],{"emptyLinePlaceholder":3559},[3536,4768,4769],{"class":3538,"line":3619},[3536,4770,4771],{},"    node \"FastAPI Dependency Resolver (DAG)\" {\n",[3536,4773,4774],{"class":3538,"line":3627},[3536,4775,4776],{},"        [Route Handler: get_tasks] as Endpoint\n",[3536,4778,4779],{"class":3538,"line":3633},[3536,4780,4781],{},"        [Dependency: get_current_user] as Auth\n",[3536,4783,4784],{"class":3538,"line":3638},[3536,4785,4786],{},"        [Dependency: get_db_session] as DB\n",[3536,4788,4789],{"class":3538,"line":3656},[3536,4790,4791],{},"        [Dependency: get_token] as Token\n",[3536,4793,4794],{"class":3538,"line":3662},[3536,4795,4796],{},"        [Dependency: get_db_engine] as Engine\n",[3536,4798,4799],{"class":3538,"line":3671},[3536,4800,3560],{"emptyLinePlaceholder":3559},[3536,4802,4803],{"class":3538,"line":3676},[3536,4804,4805],{},"        Endpoint --> Auth : Depends()\n",[3536,4807,4808],{"class":3538,"line":3682},[3536,4809,4810],{},"        Endpoint --> DB : Depends()\n",[3536,4812,4813],{"class":3538,"line":3697},[3536,4814,4815],{},"        Auth --> Token : Depends()\n",[3536,4817,4818],{"class":3538,"line":3712},[3536,4819,4820],{},"        Auth --> DB : Depends()\n",[3536,4822,4823],{"class":3538,"line":3721},[3536,4824,4825],{},"        DB --> Engine : Depends()\n",[3536,4827,4828],{"class":3538,"line":3730},[3536,4829,4018],{},[3536,4831,4832],{"class":3538,"line":3736},[3536,4833,3759],{},[3536,4835,4836],{"class":3538,"line":4052},[3536,4837,3560],{"emptyLinePlaceholder":3559},[3536,4839,4840],{"class":3538,"line":4073},[3536,4841,4842],{},"Req --> Endpoint : triggers validation & execution\n",[3536,4844,4845],{"class":3538,"line":4105},[3536,4846,4847],{},"@enduml\n",[3385,4849,4850],{},"Коли надходить HTTP-запит:",[4852,4853,4854,4860,4863],"ol",{},[3432,4855,4856,4857,4274],{},"FastAPI починає вирішувати граф з найглибших вузлів (листя), які не мають власних залежностей (наприклад, ",[3395,4858,4859],{},"get_db_engine",[3432,4861,4862],{},"Результати піднімаються вгору графом.",[3432,4864,4865,4866,4869],{},"Коли всі залежності вищого рівня успішно виконані, їхні результати підставляються у параметри обробника маршруту (",[3395,4867,4868],{},"Endpoint","), і виконується сам ендпоінт.",[3406,4871],{},[3417,4873,4875,4876,4156],{"id":4874},"кешування-результатів-залежностей-use_cachetrue","Кешування результатів залежностей (",[3395,4877,4208],{},[3385,4879,4880],{},"Важливою деталлю роботи резолвера FastAPI є керування часом життя об'єктів у межах одного HTTP-запиту.",[3385,4882,4883,4884,4886,4887,4889],{},"За замовчуванням параметр ",[3395,4885,4280],{}," приймає конфігурацію ",[3395,4888,4208],{},":",[3526,4891,4894],{"className":3528,"code":4892,"filename":4893,"language":3531,"meta":3532,"style":3532},"db = Depends(get_db, use_cache=True)\n","app.py",[3395,4895,4896],{"__ignoreMap":3532},[3536,4897,4898,4901,4904,4906,4909],{"class":3538,"line":3539},[3536,4899,4900],{"class":3546},"db = Depends(get_db, ",[3536,4902,4903],{"class":3649},"use_cache",[3536,4905,4474],{"class":3546},[3536,4907,4908],{"class":3584},"True",[3536,4910,3694],{"class":3546},[4912,4913,4915],"h4",{"id":4914},"як-працює-кешування","Як працює кешування?",[3385,4917,4918,4919,4922,4923,4926,4927,3398],{},"Повернемося до нашого графу: і ендпоінт ",[3395,4920,4921],{},"get_tasks"," безпосередньо, і залежність ",[3395,4924,4925],{},"get_current_user"," потребують сесії бази даних ",[3395,4928,4929],{},"get_db_session",[3385,4931,4932,4933,4935],{},"Якщо ",[3395,4934,4208],{}," (поведінка за замовчуванням):",[4852,4937,4938,4950,4966],{},[3432,4939,4940,4941,4943,4944,4946,4947,3398],{},"Коли FastAPI вперше зустрічає ",[3395,4942,4929],{}," (наприклад, під час валідації ",[3395,4945,4925],{},"), він викликає цю функцію, отримує сесію бази даних і зберігає її у внутрішньому кеші ",[3392,4948,4949],{},"поточного HTTP-запиту",[3432,4951,4952,4953,4955,4956,4958,4959,4962,4963,4965],{},"Коли FastAPI переходить до резолюції ",[3395,4954,3650],{}," безпосередньо для ендпоінту ",[3395,4957,4921],{},", він ",[3392,4960,4961],{},"не викликає"," ",[3395,4964,4929],{}," вдруге. Замість цього він бере вже створену сесію з кешу та інжектує її.",[3432,4967,4968],{},"Після закінчення обробки запиту (Response надіслано клієнту) внутрішній кеш повністю очищається, а ресурси (наприклад, з'єднання з БД) закриваються.",[3385,4970,4971,4972,4976],{},"Це архітектурний аналог часу життя ",[3392,4973,4974],{},[3395,4975,3458],{}," в ASP.NET Core — один об'єкт створюється рівно один раз на запит та розділяється між усіма споживачами в межах цього запиту.",[4912,4978,4980,4981,4984],{"id":4979},"коли-варто-вимикати-кешування-use_cachefalse","Коли варто вимикати кешування (",[3395,4982,4983],{},"use_cache=False",")?",[3385,4986,4987,4988,4992,4993,4889],{},"Якщо ви хочете, щоб залежність створювала новий унікальний екземпляр щоразу, коли її потребує інша функція (аналог ",[3392,4989,4990],{},[3395,4991,3450],{}," у межах одного запиту), встановіть ",[3395,4994,4983],{},[3526,4996,4998],{"className":3528,"code":4997,"filename":4893,"language":3531,"meta":3532,"style":3532},"# Отримаємо новий генератор випадкових чисел або логер для кожного виклику\nlogger = Depends(get_fresh_logger, use_cache=False)\n",[3395,4999,5000,5005],{"__ignoreMap":3532},[3536,5001,5002],{"class":3538,"line":3539},[3536,5003,5004],{"class":3577},"# Отримаємо новий генератор випадкових чисел або логер для кожного виклику\n",[3536,5006,5007,5010,5012,5014,5017],{"class":3538,"line":3556},[3536,5008,5009],{"class":3546},"logger = Depends(get_fresh_logger, ",[3536,5011,4903],{"class":3649},[3536,5013,4474],{"class":3546},[3536,5015,5016],{"class":3584},"False",[3536,5018,3694],{"class":3546},[3385,5020,5021,5022,5025],{},"У такому разі, скільки б разів логер не вимагався в дереві залежностей поточного запиту, функція ",[3395,5023,5024],{},"get_fresh_logger"," буде викликана заново для кожного параметра.",[3409,5027,5029],{"id":5028},"частина-ii-функції-генератори-та-класи-залежності","Частина II: Функції-генератори та класи-залежності",[3385,5031,5032,5033,5036],{},"Впровадження залежностей не обмежується простим поверненням статичних значень або сервісів. Залежності часто вимагають управління життєвим циклом своїх ресурсів (наприклад, відкриття та обов'язкове закриття файлів або з'єднань із базою даних) або збереження стану конфігурації. Для вирішення цих завдань FastAPI пропонує використання функцій-генераторів із ключовим словом ",[3395,5034,5035],{},"yield"," та класів (зокрема, об'єктів, що викликаються — Callable classes).",[3406,5038],{},[3417,5040,5042,5043],{"id":5041},"управління-ресурсами-функції-залежності-з-yield","Управління ресурсами: Функції-залежності з ",[3395,5044,5035],{},[3385,5046,5047,5048,5051,5052,5056],{},"Коли залежність створює ресурс, який потребує очищення після завершення обробки запиту, замість оператора ",[3395,5049,5050],{},"return"," використовується ",[3392,5053,5054],{},[3395,5055,5035],{}," (створюючи функцію-генератор).",[4912,5058,5060],{"id":5059},"механізм-виконання-yield-залежності","Механізм виконання Yield-залежності",[3385,5062,5063,5064,4889],{},"Коли FastAPI зустрічає залежність із ",[3395,5065,5035],{},[4852,5067,5068,5080,5089,5095],{},[3432,5069,5070,5073,5074,5077,5078,3398],{},[3392,5071,5072],{},"Фаза підготовки (Setup)",": Виконується весь код, розташований ",[3392,5075,5076],{},"до"," оператора ",[3395,5079,5035],{},[3432,5081,5082,5085,5086,5088],{},[3392,5083,5084],{},"Фаза передачі контролю",": Виконання призупиняється на рядку ",[3395,5087,5035],{},", а значення після нього (наприклад, сесія бази даних) інжектується в обробник маршруту.",[3432,5090,5091,5094],{},[3392,5092,5093],{},"Фаза виконання роуту",": Виконується код ендпоінту та інших залежностей.",[3432,5096,5097,5100,5101,4962,5104,3398],{},[3392,5098,5099],{},"Фаза очищення (Cleanup)",": Після того, як відповідь клієнту сформована та відправлена, FastAPI повертається до генератора та виконує весь код, розташований ",[3392,5102,5103],{},"після",[3395,5105,5035],{},[5107,5108,5109,5110,5112,5113,5116],"important",{},"Якщо під час виконання ендпоінту або інших залежностей виникає виключення (Exception), FastAPI автоматично прокидає його назад всередину генератора в точку виклику ",[3395,5111,5035],{},". Тому код очищення обов'язково має бути обгорнутий у блок ",[3395,5114,5115],{},"try...except...finally",", щоб гарантувати вивільнення ресурсів навіть при критичних збоях додатку.",[3385,5118,5119],{},"Розглянемо приклад управління асинхронною сесією бази даних:",[3526,5121,5124],{"className":3528,"code":5122,"filename":5123,"language":3531,"meta":3532,"style":3532},"# yield_di_demo.py\nfrom typing import AsyncGenerator\nfrom fastapi import FastAPI, Depends, HTTPException, status\n\napp = FastAPI()\n\nclass DatabaseSession:\n    \"\"\"Імітація з'єднання з базою даних.\"\"\"\n    def __init__(self):\n        self.is_connected = True\n        print(\"[DB Connection] З'єднання відкрито\")\n\n    def commit(self):\n        print(\"[DB Connection] Зміни збережено (Commit)\")\n\n    def rollback(self):\n        print(\"[DB Connection] Помилка! Зміни скасовано (Rollback)\")\n\n    def close(self):\n        self.is_connected = False\n        print(\"[DB Connection] З'єднання закрито\")\n\n# Функція-залежність з yield\nasync def get_db() -> AsyncGenerator[DatabaseSession, None]:\n    db = DatabaseSession()\n    try:\n        yield db  # Передаємо сесію в ендпоінт\n        db.commit()  # Якщо помилок не було, фіксуємо зміни\n    except Exception as e:\n        db.rollback()  # У разі виключення — відкочуємо транзакцію\n        raise e  # Прокидаємо помилку далі для обробки системою\n    finally:\n        db.close()  # Гарантовано закриваємо з'єднання в будь-якому випадку\n\n@app.get(\"\u002Fitems\u002F{item_id}\")\nasync def read_item(item_id: int, db: DatabaseSession = Depends(get_db)):\n    if item_id \u003C 1:\n        # Спричинить виклик db.rollback() та db.close()\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=\"Невалідний ID товару\"\n        )\n    return {\"item_id\": item_id, \"status\": \"processed\"}\n","yield_di_demo.py",[3395,5125,5126,5131,5143,5154,5158,5162,5166,5175,5180,5192,5202,5214,5218,5231,5242,5246,5259,5270,5274,5287,5296,5307,5311,5316,5333,5337,5343,5353,5361,5375,5383,5394,5400,5408,5412,5428,5454,5466,5471,5479,5488,5499,5505],{"__ignoreMap":3532},[3536,5127,5128],{"class":3538,"line":3539},[3536,5129,5130],{"class":3577},"# yield_di_demo.py\n",[3536,5132,5133,5135,5138,5140],{"class":3538,"line":3556},[3536,5134,3543],{"class":3542},[3536,5136,5137],{"class":3546}," typing ",[3536,5139,3550],{"class":3542},[3536,5141,5142],{"class":3546}," AsyncGenerator\n",[3536,5144,5145,5147,5149,5151],{"class":3538,"line":3563},[3536,5146,3543],{"class":3542},[3536,5148,3547],{"class":3546},[3536,5150,3550],{"class":3542},[3536,5152,5153],{"class":3546}," FastAPI, Depends, HTTPException, status\n",[3536,5155,5156],{"class":3538,"line":3569},[3536,5157,3560],{"emptyLinePlaceholder":3559},[3536,5159,5160],{"class":3538,"line":3574},[3536,5161,3566],{"class":3546},[3536,5163,5164],{"class":3538,"line":3581},[3536,5165,3560],{"emptyLinePlaceholder":3559},[3536,5167,5168,5170,5173],{"class":3538,"line":3595},[3536,5169,4319],{"class":3584},[3536,5171,5172],{"class":3820}," DatabaseSession",[3536,5174,3607],{"class":3546},[3536,5176,5177],{"class":3538,"line":3601},[3536,5178,5179],{"class":3690},"    \"\"\"Імітація з'єднання з базою даних.\"\"\"\n",[3536,5181,5182,5184,5186,5188,5190],{"class":3538,"line":3610},[3536,5183,4334],{"class":3584},[3536,5185,4337],{"class":3588},[3536,5187,3646],{"class":3546},[3536,5189,4342],{"class":3649},[3536,5191,3733],{"class":3546},[3536,5193,5194,5196,5199],{"class":3538,"line":3619},[3536,5195,4354],{"class":3584},[3536,5197,5198],{"class":3546},".is_connected = ",[3536,5200,5201],{"class":3584},"True\n",[3536,5203,5204,5207,5209,5212],{"class":3538,"line":3627},[3536,5205,5206],{"class":3588},"        print",[3536,5208,3646],{"class":3546},[3536,5210,5211],{"class":3690},"\"[DB Connection] З'єднання відкрито\"",[3536,5213,3694],{"class":3546},[3536,5215,5216],{"class":3538,"line":3633},[3536,5217,3560],{"emptyLinePlaceholder":3559},[3536,5219,5220,5222,5225,5227,5229],{"class":3538,"line":3638},[3536,5221,4334],{"class":3584},[3536,5223,5224],{"class":3588}," commit",[3536,5226,3646],{"class":3546},[3536,5228,4342],{"class":3649},[3536,5230,3733],{"class":3546},[3536,5232,5233,5235,5237,5240],{"class":3538,"line":3656},[3536,5234,5206],{"class":3588},[3536,5236,3646],{"class":3546},[3536,5238,5239],{"class":3690},"\"[DB Connection] Зміни збережено (Commit)\"",[3536,5241,3694],{"class":3546},[3536,5243,5244],{"class":3538,"line":3662},[3536,5245,3560],{"emptyLinePlaceholder":3559},[3536,5247,5248,5250,5253,5255,5257],{"class":3538,"line":3671},[3536,5249,4334],{"class":3584},[3536,5251,5252],{"class":3588}," rollback",[3536,5254,3646],{"class":3546},[3536,5256,4342],{"class":3649},[3536,5258,3733],{"class":3546},[3536,5260,5261,5263,5265,5268],{"class":3538,"line":3676},[3536,5262,5206],{"class":3588},[3536,5264,3646],{"class":3546},[3536,5266,5267],{"class":3690},"\"[DB Connection] Помилка! Зміни скасовано (Rollback)\"",[3536,5269,3694],{"class":3546},[3536,5271,5272],{"class":3538,"line":3682},[3536,5273,3560],{"emptyLinePlaceholder":3559},[3536,5275,5276,5278,5281,5283,5285],{"class":3538,"line":3697},[3536,5277,4334],{"class":3584},[3536,5279,5280],{"class":3588}," close",[3536,5282,3646],{"class":3546},[3536,5284,4342],{"class":3649},[3536,5286,3733],{"class":3546},[3536,5288,5289,5291,5293],{"class":3538,"line":3712},[3536,5290,4354],{"class":3584},[3536,5292,5198],{"class":3546},[3536,5294,5295],{"class":3584},"False\n",[3536,5297,5298,5300,5302,5305],{"class":3538,"line":3721},[3536,5299,5206],{"class":3588},[3536,5301,3646],{"class":3546},[3536,5303,5304],{"class":3690},"\"[DB Connection] З'єднання закрито\"",[3536,5306,3694],{"class":3546},[3536,5308,5309],{"class":3538,"line":3730},[3536,5310,3560],{"emptyLinePlaceholder":3559},[3536,5312,5313],{"class":3538,"line":3736},[3536,5314,5315],{"class":3577},"# Функція-залежність з yield\n",[3536,5317,5318,5320,5322,5324,5327,5330],{"class":3538,"line":4052},[3536,5319,3700],{"class":3584},[3536,5321,3703],{"class":3584},[3536,5323,3589],{"class":3588},[3536,5325,5326],{"class":3546},"() -> AsyncGenerator[DatabaseSession, ",[3536,5328,5329],{"class":3584},"None",[3536,5331,5332],{"class":3546},"]:\n",[3536,5334,5335],{"class":3538,"line":4073},[3536,5336,3598],{"class":3546},[3536,5338,5339,5341],{"class":3538,"line":4105},[3536,5340,3604],{"class":3542},[3536,5342,3607],{"class":3546},[3536,5344,5345,5347,5350],{"class":3538,"line":4110},[3536,5346,3613],{"class":3542},[3536,5348,5349],{"class":3546}," db  ",[3536,5351,5352],{"class":3577},"# Передаємо сесію в ендпоінт\n",[3536,5354,5355,5358],{"class":3538,"line":4504},[3536,5356,5357],{"class":3546},"        db.commit()  ",[3536,5359,5360],{"class":3577},"# Якщо помилок не було, фіксуємо зміни\n",[3536,5362,5363,5366,5369,5372],{"class":3538,"line":4509},[3536,5364,5365],{"class":3542},"    except",[3536,5367,5368],{"class":3820}," Exception",[3536,5370,5371],{"class":3542}," as",[3536,5373,5374],{"class":3546}," e:\n",[3536,5376,5377,5380],{"class":3538,"line":4524},[3536,5378,5379],{"class":3546},"        db.rollback()  ",[3536,5381,5382],{"class":3577},"# У разі виключення — відкочуємо транзакцію\n",[3536,5384,5385,5388,5391],{"class":3538,"line":4530},[3536,5386,5387],{"class":3542},"        raise",[3536,5389,5390],{"class":3546}," e  ",[3536,5392,5393],{"class":3577},"# Прокидаємо помилку далі для обробки системою\n",[3536,5395,5396,5398],{"class":3538,"line":4542},[3536,5397,3622],{"class":3542},[3536,5399,3607],{"class":3546},[3536,5401,5402,5405],{"class":3538,"line":4548},[3536,5403,5404],{"class":3546},"        db.close()  ",[3536,5406,5407],{"class":3577},"# Гарантовано закриваємо з'єднання в будь-якому випадку\n",[3536,5409,5410],{"class":3538,"line":4599},[3536,5411,3560],{"emptyLinePlaceholder":3559},[3536,5413,5414,5416,5418,5421,5424,5426],{"class":3538,"line":4605},[3536,5415,3685],{"class":3588},[3536,5417,3646],{"class":3546},[3536,5419,5420],{"class":3690},"\"\u002Fitems\u002F",[3536,5422,5423],{"class":3584},"{item_id}",[3536,5425,4559],{"class":3690},[3536,5427,3694],{"class":3546},[3536,5429,5430,5432,5434,5437,5439,5442,5444,5447,5449,5451],{"class":3538,"line":4616},[3536,5431,3700],{"class":3584},[3536,5433,3703],{"class":3584},[3536,5435,5436],{"class":3588}," read_item",[3536,5438,3646],{"class":3546},[3536,5440,5441],{"class":3649},"item_id",[3536,5443,3753],{"class":3546},[3536,5445,5446],{"class":3820},"int",[3536,5448,3824],{"class":3546},[3536,5450,3650],{"class":3649},[3536,5452,5453],{"class":3546},": DatabaseSession = Depends(get_db)):\n",[3536,5455,5456,5459,5462,5464],{"class":3538,"line":4621},[3536,5457,5458],{"class":3542},"    if",[3536,5460,5461],{"class":3546}," item_id \u003C ",[3536,5463,4611],{"class":4477},[3536,5465,3607],{"class":3546},[3536,5467,5468],{"class":3538,"line":4648},[3536,5469,5470],{"class":3577},"        # Спричинить виклик db.rollback() та db.close()\n",[3536,5472,5474,5476],{"class":3538,"line":5473},39,[3536,5475,5387],{"class":3542},[3536,5477,5478],{"class":3546}," HTTPException(\n",[3536,5480,5482,5485],{"class":3538,"line":5481},40,[3536,5483,5484],{"class":3649},"            status_code",[3536,5486,5487],{"class":3546},"=status.HTTP_400_BAD_REQUEST,\n",[3536,5489,5491,5494,5496],{"class":3538,"line":5490},41,[3536,5492,5493],{"class":3649},"            detail",[3536,5495,4474],{"class":3546},[3536,5497,5498],{"class":3690},"\"Невалідний ID товару\"\n",[3536,5500,5502],{"class":3538,"line":5501},42,[3536,5503,5504],{"class":3546},"        )\n",[3536,5506,5508,5510,5512,5515,5518,5520,5522,5525],{"class":3538,"line":5507},43,[3536,5509,3665],{"class":3542},[3536,5511,3741],{"class":3546},[3536,5513,5514],{"class":3690},"\"item_id\"",[3536,5516,5517],{"class":3546},": item_id, ",[3536,5519,3750],{"class":3690},[3536,5521,3753],{"class":3546},[3536,5523,5524],{"class":3690},"\"processed\"",[3536,5526,3759],{"class":3546},[4912,5528,5530],{"id":5529},"під-капотом-генератори-та-контекстні-менеджери","Під капотом: Генератори та контекстні менеджери",[3385,5532,5533,5534,5536],{},"Щоб зрозуміти, чому ",[3395,5535,5035],{}," працює саме так, необхідно звернутися до концепцій генераторів та контекстних менеджерів у Python.",[4852,5538,5539,5559],{},[3432,5540,5541,5544,5545,5547,5548,5551,5552,5555,5556,5558],{},[3392,5542,5543],{},"Генератори (Generators)",": У Python функція, яка містить ключове слово ",[3395,5546,5035],{},", повертає об'єкт-генератор. Замість повного виконання коду та повернення результату, генератор дозволяє призупиняти виконання функції, зберігаючи весь її локальний стан (значення змінних, точку виконання). Виклик вбудованої функції ",[3395,5549,5550],{},"next()"," (або ",[3395,5553,5554],{},"anext()"," для асинхронних генераторів) змушує Python продовжити виконання генератора до наступного ",[3395,5557,5035],{}," або до кінця функції.",[3432,5560,5561,5564,5565,5568,5569,5572,5573,5576,5577,5580,5581,5584,5585,5588,5589,5591,5592,5594,5595,5597,5598,5600,5601,3398],{},[3392,5562,5563],{},"Контекстні менеджери (Context Managers)",": Протокол контекстного менеджера (методи ",[3395,5566,5567],{},"__enter__"," та ",[3395,5570,5571],{},"__exit__",") використовується для безпечного управління ресурсами через блок ",[3395,5574,5575],{},"with",". Стандартний модуль ",[3395,5578,5579],{},"contextlib"," містить декоратор ",[3395,5582,5583],{},"@contextmanager"," (та ",[3395,5586,5587],{},"@asynccontextmanager","), який автоматично перетворює будь-яку функцію-генератор з одним ",[3395,5590,5035],{}," на повноцінний контекстний менеджер. Код до ",[3395,5593,5035],{}," виконується в методі ",[3395,5596,5567],{},", а код після ",[3395,5599,5035],{}," — у методі ",[3395,5602,5571],{},[3385,5604,5605,5606,5608,5609,5611],{},"Коли FastAPI розпізнає залежність із ",[3395,5607,5035],{},", він під капотом адаптує її як асинхронний контекстний менеджер за допомогою бібліотеки ",[3395,5610,5579],{}," або аналогічних внутрішніх ASGI-інструментів.",[3385,5613,5614],{},"Спрощено, алгоритм обробки виглядає так:",[3429,5616,5617,5628,5634,5646],{},[3432,5618,5619,5620,5551,5622,5625,5626,3398],{},"Перед викликом ендпоінту FastAPI створює контекст: викликає метод ",[3395,5621,5567],{},[3395,5623,5624],{},"__aenter__"," для асинхронних залежностей). Виконання генератора доходить до оператора ",[3395,5627,5035],{},[3432,5629,5630,5631,5633],{},"Значення, яке повернув ",[3395,5632,5035],{}," (наприклад, з'єднання з базою даних), передається в аргументи функції-ендпоінту.",[3432,5635,5636,5637,5551,5639,5642,5643,5645],{},"Після надсилання HTTP-відповіді клієнту, FastAPI викликає метод ",[3395,5638,5571],{},[3395,5640,5641],{},"__aexit__","), який відновлює роботу генератора після ",[3395,5644,5035],{}," та виконує фазу очищення.",[3432,5647,5648,5649,5651,5652,5655,5656,5659,5660,5663,5664,3398],{},"Якщо в ендпоінті виникла помилка, вона передається в метод ",[3395,5650,5571],{}," через аргументи виключення (тип помилки, значення, traceback). Це дозволяє блоку ",[3395,5653,5654],{},"try...except"," всередині вашої залежності перехопити цю помилку, виконати ",[3395,5657,5658],{},"rollback"," транзакції та знову підняти виключення (",[3395,5661,5662],{},"raise e",") або виконати фінальні дії в блоці ",[3395,5665,5666],{},"finally",[3406,5668],{},[3417,5670,5672,5673,5676,5677,5679],{"id":5671},"порівняння-архітектурних-концепцій-idisposable-в-c-генератори-yield-в-python","Порівняння архітектурних концепцій: ",[3395,5674,5675],{},"IDisposable"," в C# ↔ Генератори (",[3395,5678,5035],{},") в Python",[3385,5681,5682,5683,5685,5686,5689,5690,5692,5693,5696],{},"C# розробники звикли контролювати очищення ресурсів за допомогою інтерфейсу ",[3395,5684,5675],{}," та конструкції ",[3395,5687,5688],{},"using",". Коли сфера дії блоку ",[3395,5691,5688],{}," завершується, середовище виконання .NET (CLR) автоматично викликає метод ",[3395,5694,5695],{},"Dispose()"," для очищення некерованих ресурсів.",[3385,5698,5699,5700,5702,5703,5705,5706,4274],{},"У Python та FastAPI концепт генераторів з ",[3395,5701,5035],{}," дозволяє досягти аналогічного ефекту без написання інтерфейсів та обгортання коду ендпоінтів у додаткові конструкції ",[3395,5704,5575],{}," (аналог ",[3395,5707,5688],{},[3385,5709,5710],{},"Порівняємо обидва підходи side-by-side:",[3523,5712,5713,5764],{},[3526,5714,5716],{"className":3528,"code":5715,"filename":3530,"language":3531,"meta":3532,"style":3532},"# Залежність керує очищенням самостійно\ndef get_resource():\n    resource = Resource()\n    try:\n        yield resource\n    finally:\n        resource.close() # Автоматично виконається після завершення роуту\n",[3395,5717,5718,5723,5732,5737,5743,5750,5756],{"__ignoreMap":3532},[3536,5719,5720],{"class":3538,"line":3539},[3536,5721,5722],{"class":3577},"# Залежність керує очищенням самостійно\n",[3536,5724,5725,5727,5730],{"class":3538,"line":3556},[3536,5726,3585],{"class":3584},[3536,5728,5729],{"class":3588}," get_resource",[3536,5731,3592],{"class":3546},[3536,5733,5734],{"class":3538,"line":3563},[3536,5735,5736],{"class":3546},"    resource = Resource()\n",[3536,5738,5739,5741],{"class":3538,"line":3569},[3536,5740,3604],{"class":3542},[3536,5742,3607],{"class":3546},[3536,5744,5745,5747],{"class":3538,"line":3574},[3536,5746,3613],{"class":3542},[3536,5748,5749],{"class":3546}," resource\n",[3536,5751,5752,5754],{"class":3538,"line":3581},[3536,5753,3622],{"class":3542},[3536,5755,3607],{"class":3546},[3536,5757,5758,5761],{"class":3538,"line":3595},[3536,5759,5760],{"class":3546},"        resource.close() ",[3536,5762,5763],{"class":3577},"# Автоматично виконається після завершення роуту\n",[3526,5765,5767],{"className":3762,"code":5766,"filename":3764,"language":3765,"meta":3532,"style":3532},"\u002F\u002F Ресурс має реалізувати IDisposable\npublic class Resource : IDisposable\n{\n    public void Dispose()\n    {\n        this.Close(); \u002F\u002F Викликається контейнером автоматично\n    }\n}\n",[3395,5768,5769,5774,5788,5792,5804,5808,5824,5828],{"__ignoreMap":3532},[3536,5770,5771],{"class":3538,"line":3539},[3536,5772,5773],{"class":3577},"\u002F\u002F Ресурс має реалізувати IDisposable\n",[3536,5775,5776,5778,5780,5783,5785],{"class":3538,"line":3556},[3536,5777,3910],{"class":3584},[3536,5779,3913],{"class":3584},[3536,5781,5782],{"class":3820}," Resource",[3536,5784,3919],{"class":3546},[3536,5786,5787],{"class":3820},"IDisposable\n",[3536,5789,5790],{"class":3538,"line":3563},[3536,5791,3927],{"class":3546},[3536,5793,5794,5796,5799,5802],{"class":3538,"line":3569},[3536,5795,3967],{"class":3584},[3536,5797,5798],{"class":3584}," void",[3536,5800,5801],{"class":3588}," Dispose",[3536,5803,4045],{"class":3546},[3536,5805,5806],{"class":3538,"line":3574},[3536,5807,3990],{"class":3546},[3536,5809,5810,5813,5815,5818,5821],{"class":3538,"line":3581},[3536,5811,5812],{"class":3584},"        this",[3536,5814,3398],{"class":3546},[3536,5816,5817],{"class":3588},"Close",[3536,5819,5820],{"class":3546},"(); ",[3536,5822,5823],{"class":3577},"\u002F\u002F Викликається контейнером автоматично\n",[3536,5825,5826],{"class":3538,"line":3595},[3536,5827,4018],{"class":3546},[3536,5829,5830],{"class":3538,"line":3601},[3536,5831,3759],{"class":3546},[3406,5833],{},[3417,5835,5837],{"id":5836},"класи-залежності-class-based-dependencies","Класи-залежності (Class-based Dependencies)",[3385,5839,5840],{},"Функції є простим та зручним способом опису залежностей, але іноді виникає потреба групувати логіку валідації або параметризувати поведінку залежності. Для цього FastAPI підтримує класи як залежності.",[4912,5842,5844],{"id":5843},"_1-клас-як-контейнер-параметрів","1. Клас як контейнер параметрів",[3385,5846,5847,5848,5850,5851,5854],{},"Якщо передати клас у ",[3395,5849,4280],{},", FastAPI проаналізує параметри конструктора цього класу (",[3395,5852,5853],{},"__init__",") і підставить їх як Query або Path параметри запиту.",[3526,5856,5859],{"className":3528,"code":5857,"filename":5858,"language":3531,"meta":3532,"style":3532},"# class_di_params.py\nfrom fastapi import FastAPI, Depends\n\napp = FastAPI()\n\nclass PaginationParams:\n    def __init__(self, skip: int = 0, limit: int = 10):\n        self.skip = skip\n        self.limit = limit\n\n@app.get(\"\u002Fitems\")\nasync def get_items(params: PaginationParams = Depends(PaginationParams)):\n    # FastAPI зчитав skip та limit з URL-запиту, створив екземпляр класу\n    # та підставив його в параметр params\n    return {\"skip\": params.skip, \"limit\": params.limit}\n","class_di_params.py",[3395,5860,5861,5866,5876,5880,5884,5888,5897,5936,5943,5950,5954,5965,5982,5987,5992],{"__ignoreMap":3532},[3536,5862,5863],{"class":3538,"line":3539},[3536,5864,5865],{"class":3577},"# class_di_params.py\n",[3536,5867,5868,5870,5872,5874],{"class":3538,"line":3556},[3536,5869,3543],{"class":3542},[3536,5871,3547],{"class":3546},[3536,5873,3550],{"class":3542},[3536,5875,3553],{"class":3546},[3536,5877,5878],{"class":3538,"line":3563},[3536,5879,3560],{"emptyLinePlaceholder":3559},[3536,5881,5882],{"class":3538,"line":3569},[3536,5883,3566],{"class":3546},[3536,5885,5886],{"class":3538,"line":3574},[3536,5887,3560],{"emptyLinePlaceholder":3559},[3536,5889,5890,5892,5895],{"class":3538,"line":3581},[3536,5891,4319],{"class":3584},[3536,5893,5894],{"class":3820}," PaginationParams",[3536,5896,3607],{"class":3546},[3536,5898,5899,5901,5903,5905,5907,5909,5912,5914,5916,5918,5920,5922,5925,5927,5929,5931,5934],{"class":3538,"line":3595},[3536,5900,4334],{"class":3584},[3536,5902,4337],{"class":3588},[3536,5904,3646],{"class":3546},[3536,5906,4342],{"class":3649},[3536,5908,3824],{"class":3546},[3536,5910,5911],{"class":3649},"skip",[3536,5913,3753],{"class":3546},[3536,5915,5446],{"class":3820},[3536,5917,3783],{"class":3546},[3536,5919,4478],{"class":4477},[3536,5921,3824],{"class":3546},[3536,5923,5924],{"class":3649},"limit",[3536,5926,3753],{"class":3546},[3536,5928,5446],{"class":3820},[3536,5930,3783],{"class":3546},[3536,5932,5933],{"class":4477},"10",[3536,5935,3733],{"class":3546},[3536,5937,5938,5940],{"class":3538,"line":3601},[3536,5939,4354],{"class":3584},[3536,5941,5942],{"class":3546},".skip = skip\n",[3536,5944,5945,5947],{"class":3538,"line":3610},[3536,5946,4354],{"class":3584},[3536,5948,5949],{"class":3546},".limit = limit\n",[3536,5951,5952],{"class":3538,"line":3619},[3536,5953,3560],{"emptyLinePlaceholder":3559},[3536,5955,5956,5958,5960,5963],{"class":3538,"line":3627},[3536,5957,3685],{"class":3588},[3536,5959,3646],{"class":3546},[3536,5961,5962],{"class":3690},"\"\u002Fitems\"",[3536,5964,3694],{"class":3546},[3536,5966,5967,5969,5971,5974,5976,5979],{"class":3538,"line":3633},[3536,5968,3700],{"class":3584},[3536,5970,3703],{"class":3584},[3536,5972,5973],{"class":3588}," get_items",[3536,5975,3646],{"class":3546},[3536,5977,5978],{"class":3649},"params",[3536,5980,5981],{"class":3546},": PaginationParams = Depends(PaginationParams)):\n",[3536,5983,5984],{"class":3538,"line":3638},[3536,5985,5986],{"class":3577},"    # FastAPI зчитав skip та limit з URL-запиту, створив екземпляр класу\n",[3536,5988,5989],{"class":3538,"line":3656},[3536,5990,5991],{"class":3577},"    # та підставив його в параметр params\n",[3536,5993,5994,5996,5998,6001,6004,6007],{"class":3538,"line":3662},[3536,5995,3665],{"class":3542},[3536,5997,3741],{"class":3546},[3536,5999,6000],{"class":3690},"\"skip\"",[3536,6002,6003],{"class":3546},": params.skip, ",[3536,6005,6006],{"class":3690},"\"limit\"",[3536,6008,6009],{"class":3546},": params.limit}\n",[6011,6012,6013,6014,6017,6018,6020,6021,6024],"tip",{},"Завдяки підтримці анотації типів у FastAPI ви можете скоротити запис:\n",[3395,6015,6016],{},"params: PaginationParams = Depends()","\nЯкщо дужки ",[3395,6019,3397],{}," залишаються порожніми, фреймворк автоматично візьме тип параметра з лівої частини (",[3395,6022,6023],{},"PaginationParams",") як залежність для виклику.",[4912,6026,6028,6029,4156],{"id":6027},"_2-callable-класи-для-параметризації-залежностей-__call__","2. Callable класи для параметризації залежностей (",[3395,6030,3501],{},[3385,6032,6033,6034,6036],{},"Якщо клас реалізує магічний метод ",[3395,6035,3501],{},", його екземпляри стають об'єктами, які можна викликати як звичайні функції. Це дозволяє створювати налаштовувані залежності.",[3385,6038,6039,6040,4889],{},"Найбільш поширений приклад — перевірка ролей користувача. Ми створюємо об'єкт класу, конфігуруючи його списком дозволених ролей, а потім використовуємо цей екземпляр у ",[3395,6041,3397],{},[3526,6043,6046],{"className":3528,"code":6044,"filename":6045,"language":3531,"meta":3532,"style":3532},"# class_di_callable.py\nfrom fastapi import FastAPI, Depends, HTTPException, status\n\napp = FastAPI()\n\n# Проста імітація авторизованого користувача\nclass User:\n    def __init__(self, username: str, role: str):\n        self.username = username\n        self.role = role\n\ndef get_current_user() -> User:\n    # В реальному коді тут буде декодування токена та читання з БД\n    return User(username=\"manager_alex\", role=\"manager\")\n\nclass RoleChecker:\n    \"\"\"Клас-валідатор ролей.\"\"\"\n    def __init__(self, allowed_roles: list[str]):\n        # Конфігуруємо список дозволених ролей при створенні об'єкта\n        self.allowed_roles = allowed_roles\n\n    def __call__(self, user: User = Depends(get_current_user)) -> User:\n        # Магічний метод __call__ виконує саму перевірку\n        if user.role not in self.allowed_roles:\n            raise HTTPException(\n                status_code=status.HTTP_403_FORBIDDEN,\n                detail=f\"Доступ обмежено. Необхідна одна з ролей: {self.allowed_roles}\"\n            )\n        return user\n\n# Створюємо налаштовані екземпляри-перевірки\nallow_admin = RoleChecker([\"admin\"])\nallow_staff = RoleChecker([\"admin\", \"manager\"])\n\n@app.get(\"\u002Fadmin\u002Fdashboard\")\nasync def admin_dashboard(user: User = Depends(allow_admin)):\n    return {\"message\": \"Вітаємо в панелі адміністратора\"}\n\n@app.get(\"\u002Fstaff\u002Freports\")\nasync def staff_reports(user: User = Depends(allow_staff)):\n    return {\"message\": \"Звіти завантажено\", \"user\": user.username}\n","class_di_callable.py",[3395,6047,6048,6053,6063,6067,6071,6075,6080,6089,6120,6127,6134,6138,6147,6152,6177,6181,6190,6195,6218,6223,6230,6234,6252,6257,6276,6283,6291,6314,6319,6326,6330,6335,6346,6359,6363,6374,6390,6406,6410,6421,6437],{"__ignoreMap":3532},[3536,6049,6050],{"class":3538,"line":3539},[3536,6051,6052],{"class":3577},"# class_di_callable.py\n",[3536,6054,6055,6057,6059,6061],{"class":3538,"line":3556},[3536,6056,3543],{"class":3542},[3536,6058,3547],{"class":3546},[3536,6060,3550],{"class":3542},[3536,6062,5153],{"class":3546},[3536,6064,6065],{"class":3538,"line":3563},[3536,6066,3560],{"emptyLinePlaceholder":3559},[3536,6068,6069],{"class":3538,"line":3569},[3536,6070,3566],{"class":3546},[3536,6072,6073],{"class":3538,"line":3574},[3536,6074,3560],{"emptyLinePlaceholder":3559},[3536,6076,6077],{"class":3538,"line":3581},[3536,6078,6079],{"class":3577},"# Проста імітація авторизованого користувача\n",[3536,6081,6082,6084,6087],{"class":3538,"line":3595},[3536,6083,4319],{"class":3584},[3536,6085,6086],{"class":3820}," User",[3536,6088,3607],{"class":3546},[3536,6090,6091,6093,6095,6097,6099,6101,6104,6106,6109,6111,6114,6116,6118],{"class":3538,"line":3601},[3536,6092,4334],{"class":3584},[3536,6094,4337],{"class":3588},[3536,6096,3646],{"class":3546},[3536,6098,4342],{"class":3649},[3536,6100,3824],{"class":3546},[3536,6102,6103],{"class":3649},"username",[3536,6105,3753],{"class":3546},[3536,6107,6108],{"class":3820},"str",[3536,6110,3824],{"class":3546},[3536,6112,6113],{"class":3649},"role",[3536,6115,3753],{"class":3546},[3536,6117,6108],{"class":3820},[3536,6119,3733],{"class":3546},[3536,6121,6122,6124],{"class":3538,"line":3610},[3536,6123,4354],{"class":3584},[3536,6125,6126],{"class":3546},".username = username\n",[3536,6128,6129,6131],{"class":3538,"line":3619},[3536,6130,4354],{"class":3584},[3536,6132,6133],{"class":3546},".role = role\n",[3536,6135,6136],{"class":3538,"line":3627},[3536,6137,3560],{"emptyLinePlaceholder":3559},[3536,6139,6140,6142,6144],{"class":3538,"line":3633},[3536,6141,3585],{"class":3584},[3536,6143,3643],{"class":3588},[3536,6145,6146],{"class":3546},"() -> User:\n",[3536,6148,6149],{"class":3538,"line":3638},[3536,6150,6151],{"class":3577},"    # В реальному коді тут буде декодування токена та читання з БД\n",[3536,6153,6154,6156,6159,6161,6163,6166,6168,6170,6172,6175],{"class":3538,"line":3656},[3536,6155,3665],{"class":3542},[3536,6157,6158],{"class":3546}," User(",[3536,6160,6103],{"class":3649},[3536,6162,4474],{"class":3546},[3536,6164,6165],{"class":3690},"\"manager_alex\"",[3536,6167,3824],{"class":3546},[3536,6169,6113],{"class":3649},[3536,6171,4474],{"class":3546},[3536,6173,6174],{"class":3690},"\"manager\"",[3536,6176,3694],{"class":3546},[3536,6178,6179],{"class":3538,"line":3662},[3536,6180,3560],{"emptyLinePlaceholder":3559},[3536,6182,6183,6185,6188],{"class":3538,"line":3671},[3536,6184,4319],{"class":3584},[3536,6186,6187],{"class":3820}," RoleChecker",[3536,6189,3607],{"class":3546},[3536,6191,6192],{"class":3538,"line":3676},[3536,6193,6194],{"class":3690},"    \"\"\"Клас-валідатор ролей.\"\"\"\n",[3536,6196,6197,6199,6201,6203,6205,6207,6210,6213,6215],{"class":3538,"line":3682},[3536,6198,4334],{"class":3584},[3536,6200,4337],{"class":3588},[3536,6202,3646],{"class":3546},[3536,6204,4342],{"class":3649},[3536,6206,3824],{"class":3546},[3536,6208,6209],{"class":3649},"allowed_roles",[3536,6211,6212],{"class":3546},": list[",[3536,6214,6108],{"class":3820},[3536,6216,6217],{"class":3546},"]):\n",[3536,6219,6220],{"class":3538,"line":3697},[3536,6221,6222],{"class":3577},"        # Конфігуруємо список дозволених ролей при створенні об'єкта\n",[3536,6224,6225,6227],{"class":3538,"line":3712},[3536,6226,4354],{"class":3584},[3536,6228,6229],{"class":3546},".allowed_roles = allowed_roles\n",[3536,6231,6232],{"class":3538,"line":3721},[3536,6233,3560],{"emptyLinePlaceholder":3559},[3536,6235,6236,6238,6241,6243,6245,6247,6249],{"class":3538,"line":3730},[3536,6237,4334],{"class":3584},[3536,6239,6240],{"class":3588}," __call__",[3536,6242,3646],{"class":3546},[3536,6244,4342],{"class":3649},[3536,6246,3824],{"class":3546},[3536,6248,4090],{"class":3649},[3536,6250,6251],{"class":3546},": User = Depends(get_current_user)) -> User:\n",[3536,6253,6254],{"class":3538,"line":3736},[3536,6255,6256],{"class":3577},"        # Магічний метод __call__ виконує саму перевірку\n",[3536,6258,6259,6261,6264,6267,6270,6273],{"class":3538,"line":4052},[3536,6260,4533],{"class":3542},[3536,6262,6263],{"class":3546}," user.role ",[3536,6265,6266],{"class":3584},"not",[3536,6268,6269],{"class":3584}," in",[3536,6271,6272],{"class":3584}," self",[3536,6274,6275],{"class":3546},".allowed_roles:\n",[3536,6277,6278,6281],{"class":3538,"line":4073},[3536,6279,6280],{"class":3542},"            raise",[3536,6282,5478],{"class":3546},[3536,6284,6285,6288],{"class":3538,"line":4105},[3536,6286,6287],{"class":3649},"                status_code",[3536,6289,6290],{"class":3546},"=status.HTTP_403_FORBIDDEN,\n",[3536,6292,6293,6296,6298,6300,6303,6306,6309,6311],{"class":3538,"line":4110},[3536,6294,6295],{"class":3649},"                detail",[3536,6297,4474],{"class":3546},[3536,6299,4556],{"class":3584},[3536,6301,6302],{"class":3690},"\"Доступ обмежено. Необхідна одна з ролей: ",[3536,6304,6305],{"class":3584},"{self",[3536,6307,6308],{"class":3546},".allowed_roles",[3536,6310,4568],{"class":3584},[3536,6312,6313],{"class":3690},"\"\n",[3536,6315,6316],{"class":3538,"line":4504},[3536,6317,6318],{"class":3546},"            )\n",[3536,6320,6321,6323],{"class":3538,"line":4509},[3536,6322,4076],{"class":3542},[3536,6324,6325],{"class":3546}," user\n",[3536,6327,6328],{"class":3538,"line":4524},[3536,6329,3560],{"emptyLinePlaceholder":3559},[3536,6331,6332],{"class":3538,"line":4530},[3536,6333,6334],{"class":3577},"# Створюємо налаштовані екземпляри-перевірки\n",[3536,6336,6337,6340,6343],{"class":3538,"line":4542},[3536,6338,6339],{"class":3546},"allow_admin = RoleChecker([",[3536,6341,6342],{"class":3690},"\"admin\"",[3536,6344,6345],{"class":3546},"])\n",[3536,6347,6348,6351,6353,6355,6357],{"class":3538,"line":4548},[3536,6349,6350],{"class":3546},"allow_staff = RoleChecker([",[3536,6352,6342],{"class":3690},[3536,6354,3824],{"class":3546},[3536,6356,6174],{"class":3690},[3536,6358,6345],{"class":3546},[3536,6360,6361],{"class":3538,"line":4599},[3536,6362,3560],{"emptyLinePlaceholder":3559},[3536,6364,6365,6367,6369,6372],{"class":3538,"line":4605},[3536,6366,3685],{"class":3588},[3536,6368,3646],{"class":3546},[3536,6370,6371],{"class":3690},"\"\u002Fadmin\u002Fdashboard\"",[3536,6373,3694],{"class":3546},[3536,6375,6376,6378,6380,6383,6385,6387],{"class":3538,"line":4616},[3536,6377,3700],{"class":3584},[3536,6379,3703],{"class":3584},[3536,6381,6382],{"class":3588}," admin_dashboard",[3536,6384,3646],{"class":3546},[3536,6386,4090],{"class":3649},[3536,6388,6389],{"class":3546},": User = Depends(allow_admin)):\n",[3536,6391,6392,6394,6396,6399,6401,6404],{"class":3538,"line":4621},[3536,6393,3665],{"class":3542},[3536,6395,3741],{"class":3546},[3536,6397,6398],{"class":3690},"\"message\"",[3536,6400,3753],{"class":3546},[3536,6402,6403],{"class":3690},"\"Вітаємо в панелі адміністратора\"",[3536,6405,3759],{"class":3546},[3536,6407,6408],{"class":3538,"line":4648},[3536,6409,3560],{"emptyLinePlaceholder":3559},[3536,6411,6412,6414,6416,6419],{"class":3538,"line":5473},[3536,6413,3685],{"class":3588},[3536,6415,3646],{"class":3546},[3536,6417,6418],{"class":3690},"\"\u002Fstaff\u002Freports\"",[3536,6420,3694],{"class":3546},[3536,6422,6423,6425,6427,6430,6432,6434],{"class":3538,"line":5481},[3536,6424,3700],{"class":3584},[3536,6426,3703],{"class":3584},[3536,6428,6429],{"class":3588}," staff_reports",[3536,6431,3646],{"class":3546},[3536,6433,4090],{"class":3649},[3536,6435,6436],{"class":3546},": User = Depends(allow_staff)):\n",[3536,6438,6439,6441,6443,6445,6447,6450,6452,6454],{"class":3538,"line":5490},[3536,6440,3665],{"class":3542},[3536,6442,3741],{"class":3546},[3536,6444,6398],{"class":3690},[3536,6446,3753],{"class":3546},[3536,6448,6449],{"class":3690},"\"Звіти завантажено\"",[3536,6451,3824],{"class":3546},[3536,6453,3744],{"class":3690},[3536,6455,6456],{"class":3546},": user.username}\n",[3417,6458,6460],{"id":6459},"запуск-та-тестування-ендпоінтів-з-ролями","Запуск та тестування ендпоінтів з ролями",[3385,6462,6463],{},"Запустимо веб-сайт за допомогою Uvicorn:",[3523,6465,6466,6483],{},[3526,6467,6470],{"className":4657,"code":6468,"filename":6469,"language":4660,"meta":3532,"style":3532},"uvicorn class_di_callable:app --reload\n","Linux \u002F macOS",[3395,6471,6472],{"__ignoreMap":3532},[3536,6473,6474,6477,6480],{"class":3538,"line":3539},[3536,6475,6476],{"class":3588},"uvicorn",[3536,6478,6479],{"class":3690}," class_di_callable:app",[3536,6481,6482],{"class":3584}," --reload\n",[3526,6484,6488],{"className":6485,"code":6468,"filename":6486,"language":6487,"meta":3532,"style":3532},"language-powershell shiki shiki-themes light-plus dark-plus dark-plus","PowerShell","powershell",[3395,6489,6490],{"__ignoreMap":3532},[3536,6491,6492],{"class":3538,"line":3539},[3536,6493,6468],{"class":3546},[3385,6495,6496,6497,3398],{},"Тепер перевіримо доступ до роутерів через cURL-запити. Наш імітований користувач має роль ",[3395,6498,6499],{},"manager",[4912,6501,6503],{"id":6502},"сценарій-1-успішний-доступ-роль-manager-входить-у-дозволений-список-allow_staff","Сценарій 1: Успішний доступ (роль manager входить у дозволений список allow_staff)",[3385,6505,6506],{},"Надішлемо запит на перегляд звітів:",[3523,6508,6509,6528],{},[3526,6510,6512],{"className":4657,"code":6511,"filename":6469,"language":4660,"meta":3532,"style":3532},"curl -X GET http:\u002F\u002F127.0.0.1:8000\u002Fstaff\u002Freports\n",[3395,6513,6514],{"__ignoreMap":3532},[3536,6515,6516,6519,6522,6525],{"class":3538,"line":3539},[3536,6517,6518],{"class":3588},"curl",[3536,6520,6521],{"class":3584}," -X",[3536,6523,6524],{"class":3690}," GET",[3536,6526,6527],{"class":3690}," http:\u002F\u002F127.0.0.1:8000\u002Fstaff\u002Freports\n",[3526,6529,6531],{"className":6485,"code":6530,"filename":6486,"language":6487,"meta":3532,"style":3532},"curl.exe -X GET http:\u002F\u002F127.0.0.1:8000\u002Fstaff\u002Freports\n",[3395,6532,6533],{"__ignoreMap":3532},[3536,6534,6535,6538,6541,6544,6546,6549,6551,6554],{"class":3538,"line":3539},[3536,6536,6537],{"class":3588},"curl.exe",[3536,6539,6540],{"class":3546}," -X GET http:\u002F\u002F",[3536,6542,6543],{"class":4477},"127.0",[3536,6545,3398],{"class":3546},[3536,6547,6548],{"class":4477},"0.1",[3536,6550,4889],{"class":3546},[3536,6552,6553],{"class":4477},"8000",[3536,6555,6556],{"class":3546},"\u002Fstaff\u002Freports\n",[4672,6558,6560,6568,6572,6575],{":cursor":4674,"title":6559},"Консольний вивід доступу staff",[4677,6561,6563],{"className":6562},[3538],[3536,6564,6567],{"className":6565},[6566,4688],"text-green-400","HTTP\u002F1.1 200 OK",[4677,6569,6571],{"className":6570},[3538],"Content-Type: application\u002Fjson",[4677,6573],{"className":6574},[3538],[4677,6576,6578],{"className":6577},[3538],"{\"message\":\"Звіти завантажено\",\"user\":\"manager_alex\"}",[4912,6580,6582],{"id":6581},"сценарій-2-відхилення-доступу-користувач-не-є-admin","Сценарій 2: Відхилення доступу (користувач не є admin)",[3385,6584,6585],{},"Спробуємо отримати доступ до адмін-панелі:",[3523,6587,6588,6604],{},[3526,6589,6591],{"className":4657,"code":6590,"filename":6469,"language":4660,"meta":3532,"style":3532},"curl -X GET http:\u002F\u002F127.0.0.1:8000\u002Fadmin\u002Fdashboard\n",[3395,6592,6593],{"__ignoreMap":3532},[3536,6594,6595,6597,6599,6601],{"class":3538,"line":3539},[3536,6596,6518],{"class":3588},[3536,6598,6521],{"class":3584},[3536,6600,6524],{"class":3690},[3536,6602,6603],{"class":3690}," http:\u002F\u002F127.0.0.1:8000\u002Fadmin\u002Fdashboard\n",[3526,6605,6607],{"className":6485,"code":6606,"filename":6486,"language":6487,"meta":3532,"style":3532},"curl.exe -X GET http:\u002F\u002F127.0.0.1:8000\u002Fadmin\u002Fdashboard\n",[3395,6608,6609],{"__ignoreMap":3532},[3536,6610,6611,6613,6615,6617,6619,6621,6623,6625],{"class":3538,"line":3539},[3536,6612,6537],{"class":3588},[3536,6614,6540],{"class":3546},[3536,6616,6543],{"class":4477},[3536,6618,3398],{"class":3546},[3536,6620,6548],{"class":4477},[3536,6622,4889],{"class":3546},[3536,6624,6553],{"class":4477},[3536,6626,6627],{"class":3546},"\u002Fadmin\u002Fdashboard\n",[4672,6629,6631,6639,6642,6645],{":cursor":4674,"title":6630},"Консольний вивід відхилення",[4677,6632,6634],{"className":6633},[3538],[3536,6635,6638],{"className":6636},[6637,4688],"text-rose-400","HTTP\u002F1.1 403 Forbidden",[4677,6640,6571],{"className":6641},[3538],[4677,6643],{"className":6644},[3538],[4677,6646,6648],{"className":6647},[3538],"{\"detail\":\"Доступ обмежено. Необхідна одна з ролей: ['admin']\"}",[3406,6650],{},[3409,6652,6654],{"id":6653},"частина-iii-ланцюжки-залежностей-глобальні-залежності-та-області-видимості","Частина III: Ланцюжки залежностей, глобальні залежності та області видимості",[3385,6656,6657],{},"У реальних виробничих проектах структури залежностей рідко складаються з одного рівня. Вони утворюють глибокі ієрархічні структури. Крім того, для забезпечення наскрізної безпеки (Cross-Cutting Concerns) часто необхідно застосовувати залежності глобально для всього додатку або для цілих груп маршрутів. У цій частині ми розберемо побудову складних ланцюжків залежностей, конфігурацію глобальних обмежень та концепцію часу життя ресурсів (Scopes).",[3406,6659],{},[3417,6661,6663],{"id":6662},"ланцюжки-залежностей-dependency-chains","Ланцюжки залежностей (Dependency Chains)",[3385,6665,6666,6667,6670,6671,3398],{},"FastAPI дозволяє створювати ",[3392,6668,6669],{},"ланцюжки залежностей",": будь-яка функція, яка сама є залежністю, може приймати інші залежності за допомогою ",[3395,6672,3397],{},[3385,6674,6675,6676,6679,6680,3398],{},"При вирішенні запиту FastAPI рекурсивно проходить по всьому дереву, починаючи з найглибших вузлів, і передає результати знизу вгору. Якщо будь-яка залежність у ланцюжку генерує помилку (",[3395,6677,6678],{},"HTTPException","), виконання запиту негайно переривається, а всі створені на попередніх кроках ресурси (наприклад, з'єднання з БД) вивільняються через механізми ",[3395,6681,5035],{},[4912,6683,6685],{"id":6684},"практичний-сценарій-перевірка-клієнтського-доступу-за-api-ключем","Практичний сценарій: Перевірка клієнтського доступу за API-ключем",[3385,6687,6688],{},"Створимо ланцюжок:",[4852,6690,6691,6700,6705,6716],{},[3432,6692,6693,6696,6697,3398],{},[3395,6694,6695],{},"get_api_key",": Отримує та валідує заголовок ",[3395,6698,6699],{},"X-API-Key",[3432,6701,6702,6704],{},[3395,6703,4243],{},": Надає підключення до бази даних.",[3432,6706,6707,6710,6711,5568,6713,6715],{},[3395,6708,6709],{},"get_active_client",": Залежить від ",[3395,6712,6695],{},[3395,6714,4243],{},". Вона шукає клієнта в БД та перевіряє статус його активності.",[3432,6717,6718,6719,6710,6722,6724],{},"Ендпоінт ",[3395,6720,6721],{},"\u002Fdata",[3395,6723,6709],{}," і виконує корисну роботу.",[3526,6726,6729],{"className":3528,"code":6727,"filename":6728,"language":3531,"meta":3532,"style":3532},"# dependency_chains.py\nfrom fastapi import FastAPI, Depends, Header, HTTPException, status\n\napp = FastAPI()\n\n# Симульована БД клієнтів\nDB_CLIENTS = {\n    \"key_prod_12345\": {\"name\": \"Alpha Corp\", \"is_active\": True},\n    \"key_temp_67890\": {\"name\": \"Beta Startup\", \"is_active\": False},\n}\n\n# 1. Перша залежність — витягуємо API-ключ із заголовків\ndef get_api_key(x_api_key: str = Header(..., description=\"Ключ доступу API\")) -> str:\n    if not x_api_key.startswith(\"key_\"):\n        raise HTTPException(\n            status_code=status.HTTP_401_UNAUTHORIZED,\n            detail=\"Некоректний формат API-ключа\"\n        )\n    return x_api_key\n\n# 2. Друга залежність — імітація підключення до БД\ndef get_db():\n    print(\"[Chain DB] Підключення відкрито\")\n    try:\n        yield DB_CLIENTS\n    finally:\n        print(\"[Chain DB] Підключення закрито\")\n\n# 3. Третя залежність (Ланцюжок) — залежить від перших двох\ndef get_active_client(\n    api_key: str = Depends(get_api_key),\n    db: dict = Depends(get_db)\n) -> dict:\n    if api_key not in db:\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=\"Невалідний API-ключ\"\n        )\n    client = db[api_key]\n    if not client[\"is_active\"]:\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=\"Акаунт клієнта деактивовано\"\n        )\n    return client\n\n# 4. Обробник роуту приймає кінцевий результат ланцюжка\n@app.get(\"\u002Fclient\u002Fdata\")\nasync def get_secure_data(client: dict = Depends(get_active_client)):\n    return {\n        \"status\": \"success\",\n        \"client_name\": client[\"name\"],\n        \"payload\": \"Секретні комерційні дані\"\n    }\n","dependency_chains.py",[3395,6730,6731,6736,6747,6751,6755,6759,6764,6769,6797,6821,6825,6829,6834,6868,6883,6889,6896,6905,6909,6916,6920,6925,6933,6945,6951,6958,6964,6975,6979,6984,6993,7005,7017,7026,7040,7046,7052,7061,7065,7070,7083,7089,7095,7104,7109,7117,7122,7128,7140,7162,7170,7184,7198,7209],{"__ignoreMap":3532},[3536,6732,6733],{"class":3538,"line":3539},[3536,6734,6735],{"class":3577},"# dependency_chains.py\n",[3536,6737,6738,6740,6742,6744],{"class":3538,"line":3556},[3536,6739,3543],{"class":3542},[3536,6741,3547],{"class":3546},[3536,6743,3550],{"class":3542},[3536,6745,6746],{"class":3546}," FastAPI, Depends, Header, HTTPException, status\n",[3536,6748,6749],{"class":3538,"line":3563},[3536,6750,3560],{"emptyLinePlaceholder":3559},[3536,6752,6753],{"class":3538,"line":3569},[3536,6754,3566],{"class":3546},[3536,6756,6757],{"class":3538,"line":3574},[3536,6758,3560],{"emptyLinePlaceholder":3559},[3536,6760,6761],{"class":3538,"line":3581},[3536,6762,6763],{"class":3577},"# Симульована БД клієнтів\n",[3536,6765,6766],{"class":3538,"line":3595},[3536,6767,6768],{"class":3546},"DB_CLIENTS = {\n",[3536,6770,6771,6774,6777,6780,6782,6785,6787,6790,6792,6794],{"class":3538,"line":3601},[3536,6772,6773],{"class":3690},"    \"key_prod_12345\"",[3536,6775,6776],{"class":3546},": {",[3536,6778,6779],{"class":3690},"\"name\"",[3536,6781,3753],{"class":3546},[3536,6783,6784],{"class":3690},"\"Alpha Corp\"",[3536,6786,3824],{"class":3546},[3536,6788,6789],{"class":3690},"\"is_active\"",[3536,6791,3753],{"class":3546},[3536,6793,4908],{"class":3584},[3536,6795,6796],{"class":3546},"},\n",[3536,6798,6799,6802,6804,6806,6808,6811,6813,6815,6817,6819],{"class":3538,"line":3610},[3536,6800,6801],{"class":3690},"    \"key_temp_67890\"",[3536,6803,6776],{"class":3546},[3536,6805,6779],{"class":3690},[3536,6807,3753],{"class":3546},[3536,6809,6810],{"class":3690},"\"Beta Startup\"",[3536,6812,3824],{"class":3546},[3536,6814,6789],{"class":3690},[3536,6816,3753],{"class":3546},[3536,6818,5016],{"class":3584},[3536,6820,6796],{"class":3546},[3536,6822,6823],{"class":3538,"line":3619},[3536,6824,3759],{"class":3546},[3536,6826,6827],{"class":3538,"line":3627},[3536,6828,3560],{"emptyLinePlaceholder":3559},[3536,6830,6831],{"class":3538,"line":3633},[3536,6832,6833],{"class":3577},"# 1. Перша залежність — витягуємо API-ключ із заголовків\n",[3536,6835,6836,6838,6841,6843,6846,6848,6850,6853,6856,6858,6861,6864,6866],{"class":3538,"line":3638},[3536,6837,3585],{"class":3584},[3536,6839,6840],{"class":3588}," get_api_key",[3536,6842,3646],{"class":3546},[3536,6844,6845],{"class":3649},"x_api_key",[3536,6847,3753],{"class":3546},[3536,6849,6108],{"class":3820},[3536,6851,6852],{"class":3546}," = Header(..., ",[3536,6854,6855],{"class":3649},"description",[3536,6857,4474],{"class":3546},[3536,6859,6860],{"class":3690},"\"Ключ доступу API\"",[3536,6862,6863],{"class":3546},")) -> ",[3536,6865,6108],{"class":3820},[3536,6867,3607],{"class":3546},[3536,6869,6870,6872,6875,6878,6881],{"class":3538,"line":3656},[3536,6871,5458],{"class":3542},[3536,6873,6874],{"class":3584}," not",[3536,6876,6877],{"class":3546}," x_api_key.startswith(",[3536,6879,6880],{"class":3690},"\"key_\"",[3536,6882,3733],{"class":3546},[3536,6884,6885,6887],{"class":3538,"line":3662},[3536,6886,5387],{"class":3542},[3536,6888,5478],{"class":3546},[3536,6890,6891,6893],{"class":3538,"line":3671},[3536,6892,5484],{"class":3649},[3536,6894,6895],{"class":3546},"=status.HTTP_401_UNAUTHORIZED,\n",[3536,6897,6898,6900,6902],{"class":3538,"line":3676},[3536,6899,5493],{"class":3649},[3536,6901,4474],{"class":3546},[3536,6903,6904],{"class":3690},"\"Некоректний формат API-ключа\"\n",[3536,6906,6907],{"class":3538,"line":3682},[3536,6908,5504],{"class":3546},[3536,6910,6911,6913],{"class":3538,"line":3697},[3536,6912,3665],{"class":3542},[3536,6914,6915],{"class":3546}," x_api_key\n",[3536,6917,6918],{"class":3538,"line":3712},[3536,6919,3560],{"emptyLinePlaceholder":3559},[3536,6921,6922],{"class":3538,"line":3721},[3536,6923,6924],{"class":3577},"# 2. Друга залежність — імітація підключення до БД\n",[3536,6926,6927,6929,6931],{"class":3538,"line":3730},[3536,6928,3585],{"class":3584},[3536,6930,3589],{"class":3588},[3536,6932,3592],{"class":3546},[3536,6934,6935,6938,6940,6943],{"class":3538,"line":3736},[3536,6936,6937],{"class":3588},"    print",[3536,6939,3646],{"class":3546},[3536,6941,6942],{"class":3690},"\"[Chain DB] Підключення відкрито\"",[3536,6944,3694],{"class":3546},[3536,6946,6947,6949],{"class":3538,"line":4052},[3536,6948,3604],{"class":3542},[3536,6950,3607],{"class":3546},[3536,6952,6953,6955],{"class":3538,"line":4073},[3536,6954,3613],{"class":3542},[3536,6956,6957],{"class":3546}," DB_CLIENTS\n",[3536,6959,6960,6962],{"class":3538,"line":4105},[3536,6961,3622],{"class":3542},[3536,6963,3607],{"class":3546},[3536,6965,6966,6968,6970,6973],{"class":3538,"line":4110},[3536,6967,5206],{"class":3588},[3536,6969,3646],{"class":3546},[3536,6971,6972],{"class":3690},"\"[Chain DB] Підключення закрито\"",[3536,6974,3694],{"class":3546},[3536,6976,6977],{"class":3538,"line":4504},[3536,6978,3560],{"emptyLinePlaceholder":3559},[3536,6980,6981],{"class":3538,"line":4509},[3536,6982,6983],{"class":3577},"# 3. Третя залежність (Ланцюжок) — залежить від перших двох\n",[3536,6985,6986,6988,6991],{"class":3538,"line":4524},[3536,6987,3585],{"class":3584},[3536,6989,6990],{"class":3588}," get_active_client",[3536,6992,3709],{"class":3546},[3536,6994,6995,6998,7000,7002],{"class":3538,"line":4530},[3536,6996,6997],{"class":3649},"    api_key",[3536,6999,3753],{"class":3546},[3536,7001,6108],{"class":3820},[3536,7003,7004],{"class":3546}," = Depends(get_api_key),\n",[3536,7006,7007,7009,7011,7014],{"class":3538,"line":4542},[3536,7008,3715],{"class":3649},[3536,7010,3753],{"class":3546},[3536,7012,7013],{"class":3820},"dict",[3536,7015,7016],{"class":3546}," = Depends(get_db)\n",[3536,7018,7019,7022,7024],{"class":3538,"line":4548},[3536,7020,7021],{"class":3546},") -> ",[3536,7023,7013],{"class":3820},[3536,7025,3607],{"class":3546},[3536,7027,7028,7030,7033,7035,7037],{"class":3538,"line":4599},[3536,7029,5458],{"class":3542},[3536,7031,7032],{"class":3546}," api_key ",[3536,7034,6266],{"class":3584},[3536,7036,6269],{"class":3584},[3536,7038,7039],{"class":3546}," db:\n",[3536,7041,7042,7044],{"class":3538,"line":4605},[3536,7043,5387],{"class":3542},[3536,7045,5478],{"class":3546},[3536,7047,7048,7050],{"class":3538,"line":4616},[3536,7049,5484],{"class":3649},[3536,7051,6290],{"class":3546},[3536,7053,7054,7056,7058],{"class":3538,"line":4621},[3536,7055,5493],{"class":3649},[3536,7057,4474],{"class":3546},[3536,7059,7060],{"class":3690},"\"Невалідний API-ключ\"\n",[3536,7062,7063],{"class":3538,"line":4648},[3536,7064,5504],{"class":3546},[3536,7066,7067],{"class":3538,"line":5473},[3536,7068,7069],{"class":3546},"    client = db[api_key]\n",[3536,7071,7072,7074,7076,7079,7081],{"class":3538,"line":5481},[3536,7073,5458],{"class":3542},[3536,7075,6874],{"class":3584},[3536,7077,7078],{"class":3546}," client[",[3536,7080,6789],{"class":3690},[3536,7082,5332],{"class":3546},[3536,7084,7085,7087],{"class":3538,"line":5490},[3536,7086,5387],{"class":3542},[3536,7088,5478],{"class":3546},[3536,7090,7091,7093],{"class":3538,"line":5501},[3536,7092,5484],{"class":3649},[3536,7094,6290],{"class":3546},[3536,7096,7097,7099,7101],{"class":3538,"line":5507},[3536,7098,5493],{"class":3649},[3536,7100,4474],{"class":3546},[3536,7102,7103],{"class":3690},"\"Акаунт клієнта деактивовано\"\n",[3536,7105,7107],{"class":3538,"line":7106},44,[3536,7108,5504],{"class":3546},[3536,7110,7112,7114],{"class":3538,"line":7111},45,[3536,7113,3665],{"class":3542},[3536,7115,7116],{"class":3546}," client\n",[3536,7118,7120],{"class":3538,"line":7119},46,[3536,7121,3560],{"emptyLinePlaceholder":3559},[3536,7123,7125],{"class":3538,"line":7124},47,[3536,7126,7127],{"class":3577},"# 4. Обробник роуту приймає кінцевий результат ланцюжка\n",[3536,7129,7131,7133,7135,7138],{"class":3538,"line":7130},48,[3536,7132,3685],{"class":3588},[3536,7134,3646],{"class":3546},[3536,7136,7137],{"class":3690},"\"\u002Fclient\u002Fdata\"",[3536,7139,3694],{"class":3546},[3536,7141,7143,7145,7147,7150,7152,7155,7157,7159],{"class":3538,"line":7142},49,[3536,7144,3700],{"class":3584},[3536,7146,3703],{"class":3584},[3536,7148,7149],{"class":3588}," get_secure_data",[3536,7151,3646],{"class":3546},[3536,7153,7154],{"class":3649},"client",[3536,7156,3753],{"class":3546},[3536,7158,7013],{"class":3820},[3536,7160,7161],{"class":3546}," = Depends(get_active_client)):\n",[3536,7163,7165,7167],{"class":3538,"line":7164},50,[3536,7166,3665],{"class":3542},[3536,7168,7169],{"class":3546}," {\n",[3536,7171,7173,7176,7178,7181],{"class":3538,"line":7172},51,[3536,7174,7175],{"class":3690},"        \"status\"",[3536,7177,3753],{"class":3546},[3536,7179,7180],{"class":3690},"\"success\"",[3536,7182,7183],{"class":3546},",\n",[3536,7185,7187,7190,7193,7195],{"class":3538,"line":7186},52,[3536,7188,7189],{"class":3690},"        \"client_name\"",[3536,7191,7192],{"class":3546},": client[",[3536,7194,6779],{"class":3690},[3536,7196,7197],{"class":3546},"],\n",[3536,7199,7201,7204,7206],{"class":3538,"line":7200},53,[3536,7202,7203],{"class":3690},"        \"payload\"",[3536,7205,3753],{"class":3546},[3536,7207,7208],{"class":3690},"\"Секретні комерційні дані\"\n",[3536,7210,7212],{"class":3538,"line":7211},54,[3536,7213,4018],{"class":3546},[3406,7215],{},[3417,7217,7219],{"id":7218},"глобальні-та-групові-залежності-global-router-dependencies","Глобальні та групові залежності (Global & Router Dependencies)",[3385,7221,7222],{},"Іноді необхідно застосувати правила (наприклад, логування, авторизацію, перевірку IP-адрес) до всіх ендпоінтів додатку або до окремого модуля (роутера).",[3385,7224,7225,7226,7228,7229,4889],{},"У FastAPI для цього не потрібно прописувати ",[3395,7227,4280],{}," у параметрах кожного методу. Залежності можна передати списком у параметрі ",[3395,7230,7231],{},"dependencies",[3429,7233,7234,7240],{},[3432,7235,7236,7237,3398],{},"На рівні всього додатку при створенні ",[3395,7238,7239],{},"FastAPI()",[3432,7241,7242,7243,3398],{},"На рівні модуля при створенні ",[3395,7244,7245],{},"APIRouter()",[4912,7247,7249],{"id":7248},"важливе-обмеження-групових-залежностей","Важливе обмеження групових залежностей",[3385,7251,7252,7253,3398],{},"Залежності, які оголошуються на рівні додатку чи роутера, виконуються перед запуском ендпоінтів, але ",[3392,7254,7255],{},"їхні результати не передаються в параметри функцій-ендпоінтів",[3385,7257,7258,7262],{},[7259,7260,7261],"em",{},"Для чого вони використовуються?"," Для перевірок, які створюють side-effects (побічні ефекти), наприклад:",[3429,7264,7265,7268,7271],{},[3432,7266,7267],{},"Валідація токенів (перервати запит 401 помилкою, якщо токен невалідний).",[3432,7269,7270],{},"Логування метаданих запиту.",[3432,7272,7273],{},"Rate Limiting (обмеження частоти запитів).",[3385,7275,7276,7277,7280,7281,7283],{},"Якщо вам потрібен об'єкт, який повертає залежність (наприклад, модель користувача), ви ",[3392,7278,7279],{},"зобов'язані"," явно прописати ",[3395,7282,4280],{}," у параметрах вашої функції-ендпоінту.",[3385,7285,7286],{},"Приклад конфігурації глобальних та групових залежностей:",[3526,7288,7291],{"className":3528,"code":7289,"filename":7290,"language":3531,"meta":3532,"style":3532},"# global_di_demo.py\nfrom fastapi import FastAPI, APIRouter, Depends, Header, HTTPException, status\n\n# Залежність 1: Глобальне логування для всього додатку\ndef global_request_logger(user_agent: str | None = Header(None)):\n    print(f\"[Global Logger] Запит від клієнта: {user_agent}\")\n\n# Залежність 2: Перевірка прав для адміністративного роутера\ndef verify_admin_token(x_admin_token: str = Header(...)):\n    if x_admin_token != \"superadmin123\":\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=\"Невалідний токен адміністратора\"\n        )\n\n# Реєструємо глобальну залежність при ініціалізації додатка\napp = FastAPI(dependencies=[Depends(global_request_logger)])\n\n# Створюємо роутер та реєструємо залежність для всієї групи його маршрутів\nadmin_router = APIRouter(\n    prefix=\"\u002Fadmin\", \n    dependencies=[Depends(verify_admin_token)]\n)\n\n@admin_router.get(\"\u002Fusers\")\nasync def list_users():\n    return [{\"id\": 1, \"username\": \"alice\"}, {\"id\": 2, \"username\": \"bob\"}]\n\n@admin_router.post(\"\u002Fshutdown\")\nasync def shutdown_system():\n    return {\"status\": \"system shutting down\"}\n\n# Підключаємо роутер до додатку\napp.include_router(admin_router)\n","global_di_demo.py",[3395,7292,7293,7298,7309,7313,7318,7347,7368,7372,7377,7396,7408,7414,7420,7429,7433,7437,7442,7452,7456,7461,7466,7479,7487,7491,7495,7507,7518,7564,7568,7580,7591,7606,7610,7615],{"__ignoreMap":3532},[3536,7294,7295],{"class":3538,"line":3539},[3536,7296,7297],{"class":3577},"# global_di_demo.py\n",[3536,7299,7300,7302,7304,7306],{"class":3538,"line":3556},[3536,7301,3543],{"class":3542},[3536,7303,3547],{"class":3546},[3536,7305,3550],{"class":3542},[3536,7307,7308],{"class":3546}," FastAPI, APIRouter, Depends, Header, HTTPException, status\n",[3536,7310,7311],{"class":3538,"line":3563},[3536,7312,3560],{"emptyLinePlaceholder":3559},[3536,7314,7315],{"class":3538,"line":3569},[3536,7316,7317],{"class":3577},"# Залежність 1: Глобальне логування для всього додатку\n",[3536,7319,7320,7322,7325,7327,7330,7332,7334,7337,7339,7342,7344],{"class":3538,"line":3574},[3536,7321,3585],{"class":3584},[3536,7323,7324],{"class":3588}," global_request_logger",[3536,7326,3646],{"class":3546},[3536,7328,7329],{"class":3649},"user_agent",[3536,7331,3753],{"class":3546},[3536,7333,6108],{"class":3820},[3536,7335,7336],{"class":3546}," | ",[3536,7338,5329],{"class":3584},[3536,7340,7341],{"class":3546}," = Header(",[3536,7343,5329],{"class":3584},[3536,7345,7346],{"class":3546},")):\n",[3536,7348,7349,7351,7353,7355,7358,7360,7362,7364,7366],{"class":3538,"line":3581},[3536,7350,6937],{"class":3588},[3536,7352,3646],{"class":3546},[3536,7354,4556],{"class":3584},[3536,7356,7357],{"class":3690},"\"[Global Logger] Запит від клієнта: ",[3536,7359,4562],{"class":3584},[3536,7361,7329],{"class":3546},[3536,7363,4568],{"class":3584},[3536,7365,4559],{"class":3690},[3536,7367,3694],{"class":3546},[3536,7369,7370],{"class":3538,"line":3595},[3536,7371,3560],{"emptyLinePlaceholder":3559},[3536,7373,7374],{"class":3538,"line":3601},[3536,7375,7376],{"class":3577},"# Залежність 2: Перевірка прав для адміністративного роутера\n",[3536,7378,7379,7381,7384,7386,7389,7391,7393],{"class":3538,"line":3610},[3536,7380,3585],{"class":3584},[3536,7382,7383],{"class":3588}," verify_admin_token",[3536,7385,3646],{"class":3546},[3536,7387,7388],{"class":3649},"x_admin_token",[3536,7390,3753],{"class":3546},[3536,7392,6108],{"class":3820},[3536,7394,7395],{"class":3546}," = Header(...)):\n",[3536,7397,7398,7400,7403,7406],{"class":3538,"line":3619},[3536,7399,5458],{"class":3542},[3536,7401,7402],{"class":3546}," x_admin_token != ",[3536,7404,7405],{"class":3690},"\"superadmin123\"",[3536,7407,3607],{"class":3546},[3536,7409,7410,7412],{"class":3538,"line":3627},[3536,7411,5387],{"class":3542},[3536,7413,5478],{"class":3546},[3536,7415,7416,7418],{"class":3538,"line":3633},[3536,7417,5484],{"class":3649},[3536,7419,6290],{"class":3546},[3536,7421,7422,7424,7426],{"class":3538,"line":3638},[3536,7423,5493],{"class":3649},[3536,7425,4474],{"class":3546},[3536,7427,7428],{"class":3690},"\"Невалідний токен адміністратора\"\n",[3536,7430,7431],{"class":3538,"line":3656},[3536,7432,5504],{"class":3546},[3536,7434,7435],{"class":3538,"line":3662},[3536,7436,3560],{"emptyLinePlaceholder":3559},[3536,7438,7439],{"class":3538,"line":3671},[3536,7440,7441],{"class":3577},"# Реєструємо глобальну залежність при ініціалізації додатка\n",[3536,7443,7444,7447,7449],{"class":3538,"line":3676},[3536,7445,7446],{"class":3546},"app = FastAPI(",[3536,7448,7231],{"class":3649},[3536,7450,7451],{"class":3546},"=[Depends(global_request_logger)])\n",[3536,7453,7454],{"class":3538,"line":3682},[3536,7455,3560],{"emptyLinePlaceholder":3559},[3536,7457,7458],{"class":3538,"line":3697},[3536,7459,7460],{"class":3577},"# Створюємо роутер та реєструємо залежність для всієї групи його маршрутів\n",[3536,7462,7463],{"class":3538,"line":3712},[3536,7464,7465],{"class":3546},"admin_router = APIRouter(\n",[3536,7467,7468,7471,7473,7476],{"class":3538,"line":3721},[3536,7469,7470],{"class":3649},"    prefix",[3536,7472,4474],{"class":3546},[3536,7474,7475],{"class":3690},"\"\u002Fadmin\"",[3536,7477,7478],{"class":3546},", \n",[3536,7480,7481,7484],{"class":3538,"line":3730},[3536,7482,7483],{"class":3649},"    dependencies",[3536,7485,7486],{"class":3546},"=[Depends(verify_admin_token)]\n",[3536,7488,7489],{"class":3538,"line":3736},[3536,7490,3694],{"class":3546},[3536,7492,7493],{"class":3538,"line":4052},[3536,7494,3560],{"emptyLinePlaceholder":3559},[3536,7496,7497,7500,7502,7505],{"class":3538,"line":4073},[3536,7498,7499],{"class":3588},"@admin_router.get",[3536,7501,3646],{"class":3546},[3536,7503,7504],{"class":3690},"\"\u002Fusers\"",[3536,7506,3694],{"class":3546},[3536,7508,7509,7511,7513,7516],{"class":3538,"line":4105},[3536,7510,3700],{"class":3584},[3536,7512,3703],{"class":3584},[3536,7514,7515],{"class":3588}," list_users",[3536,7517,3592],{"class":3546},[3536,7519,7520,7522,7525,7528,7530,7532,7534,7537,7539,7542,7545,7547,7549,7552,7554,7556,7558,7561],{"class":3538,"line":4110},[3536,7521,3665],{"class":3542},[3536,7523,7524],{"class":3546}," [{",[3536,7526,7527],{"class":3690},"\"id\"",[3536,7529,3753],{"class":3546},[3536,7531,4611],{"class":4477},[3536,7533,3824],{"class":3546},[3536,7535,7536],{"class":3690},"\"username\"",[3536,7538,3753],{"class":3546},[3536,7540,7541],{"class":3690},"\"alice\"",[3536,7543,7544],{"class":3546},"}, {",[3536,7546,7527],{"class":3690},[3536,7548,3753],{"class":3546},[3536,7550,7551],{"class":4477},"2",[3536,7553,3824],{"class":3546},[3536,7555,7536],{"class":3690},[3536,7557,3753],{"class":3546},[3536,7559,7560],{"class":3690},"\"bob\"",[3536,7562,7563],{"class":3546},"}]\n",[3536,7565,7566],{"class":3538,"line":4504},[3536,7567,3560],{"emptyLinePlaceholder":3559},[3536,7569,7570,7573,7575,7578],{"class":3538,"line":4509},[3536,7571,7572],{"class":3588},"@admin_router.post",[3536,7574,3646],{"class":3546},[3536,7576,7577],{"class":3690},"\"\u002Fshutdown\"",[3536,7579,3694],{"class":3546},[3536,7581,7582,7584,7586,7589],{"class":3538,"line":4524},[3536,7583,3700],{"class":3584},[3536,7585,3703],{"class":3584},[3536,7587,7588],{"class":3588}," shutdown_system",[3536,7590,3592],{"class":3546},[3536,7592,7593,7595,7597,7599,7601,7604],{"class":3538,"line":4530},[3536,7594,3665],{"class":3542},[3536,7596,3741],{"class":3546},[3536,7598,3750],{"class":3690},[3536,7600,3753],{"class":3546},[3536,7602,7603],{"class":3690},"\"system shutting down\"",[3536,7605,3759],{"class":3546},[3536,7607,7608],{"class":3538,"line":4542},[3536,7609,3560],{"emptyLinePlaceholder":3559},[3536,7611,7612],{"class":3538,"line":4548},[3536,7613,7614],{"class":3577},"# Підключаємо роутер до додатку\n",[3536,7616,7617],{"class":3538,"line":4599},[3536,7618,7619],{"class":3546},"app.include_router(admin_router)\n",[3417,7621,7623],{"id":7622},"запуск-та-тестування-глобальних-і-групових-залежностей","Запуск та тестування глобальних і групових залежностей",[3385,7625,7626],{},"Запустіть веб-сайт додатку за допомогою Uvicorn:",[3523,7628,7629,7643],{},[3526,7630,7632],{"className":4657,"code":7631,"filename":6469,"language":4660,"meta":3532,"style":3532},"uvicorn global_di_demo:app --reload\n",[3395,7633,7634],{"__ignoreMap":3532},[3536,7635,7636,7638,7641],{"class":3538,"line":3539},[3536,7637,6476],{"class":3588},[3536,7639,7640],{"class":3690}," global_di_demo:app",[3536,7642,6482],{"class":3584},[3526,7644,7645],{"className":6485,"code":7631,"filename":6486,"language":6487,"meta":3532,"style":3532},[3395,7646,7647],{"__ignoreMap":3532},[3536,7648,7649],{"class":3538,"line":3539},[3536,7650,7631],{"class":3546},[3385,7652,7653],{},"Тепер перевіримо поведінку глобальних та групових обмежень за допомогою cURL-запитів.",[4912,7655,7657],{"id":7656},"сценарій-1-успішний-доступ-до-адміністративного-роутера-передано-правильний-заголовок","Сценарій 1: Успішний доступ до адміністративного роутера (Передано правильний заголовок)",[3385,7659,7660,7661,4889],{},"Надішлемо запит на перегляд користувачів, надавши валідний заголовок ",[3395,7662,7663],{},"X-Admin-Token",[3523,7665,7666,7703],{},[3526,7667,7669],{"className":4657,"code":7668,"filename":6469,"language":4660,"meta":3532,"style":3532},"curl -X GET http:\u002F\u002F127.0.0.1:8000\u002Fadmin\u002Fusers \\\n  -H \"X-Admin-Token: superadmin123\" \\\n  -H \"User-Agent: CurlClient\"\n",[3395,7670,7671,7686,7696],{"__ignoreMap":3532},[3536,7672,7673,7675,7677,7679,7682],{"class":3538,"line":3539},[3536,7674,6518],{"class":3588},[3536,7676,6521],{"class":3584},[3536,7678,6524],{"class":3690},[3536,7680,7681],{"class":3690}," http:\u002F\u002F127.0.0.1:8000\u002Fadmin\u002Fusers",[3536,7683,7685],{"class":7684},"sjcCO"," \\\n",[3536,7687,7688,7691,7694],{"class":3538,"line":3556},[3536,7689,7690],{"class":3584},"  -H",[3536,7692,7693],{"class":3690}," \"X-Admin-Token: superadmin123\"",[3536,7695,7685],{"class":7684},[3536,7697,7698,7700],{"class":3538,"line":3563},[3536,7699,7690],{"class":3584},[3536,7701,7702],{"class":3690}," \"User-Agent: CurlClient\"\n",[3526,7704,7706],{"className":6485,"code":7705,"filename":6486,"language":6487,"meta":3532,"style":3532},"curl.exe -X GET http:\u002F\u002F127.0.0.1:8000\u002Fadmin\u002Fusers `\n  -H \"X-Admin-Token: superadmin123\" `\n  -H \"User-Agent: CurlClient\"\n",[3395,7707,7708,7727,7738],{"__ignoreMap":3532},[3536,7709,7710,7712,7714,7716,7718,7720,7722,7724],{"class":3538,"line":3539},[3536,7711,6537],{"class":3588},[3536,7713,6540],{"class":3546},[3536,7715,6543],{"class":4477},[3536,7717,3398],{"class":3546},[3536,7719,6548],{"class":4477},[3536,7721,4889],{"class":3546},[3536,7723,6553],{"class":4477},[3536,7725,7726],{"class":3546},"\u002Fadmin\u002Fusers `\n",[3536,7728,7729,7732,7735],{"class":3538,"line":3556},[3536,7730,7731],{"class":3546},"  -H ",[3536,7733,7734],{"class":3690},"\"X-Admin-Token: superadmin123\"",[3536,7736,7737],{"class":3546}," `\n",[3536,7739,7740,7742],{"class":3538,"line":3563},[3536,7741,7731],{"class":3546},[3536,7743,7744],{"class":3690},"\"User-Agent: CurlClient\"\n",[4672,7746,7748,7754,7757,7760],{":cursor":4674,"title":7747},"Консольний вивід успіху роутера",[4677,7749,7751],{"className":7750},[3538],[3536,7752,6567],{"className":7753},[6566,4688],[4677,7755,6571],{"className":7756},[3538],[4677,7758],{"className":7759},[3538],[4677,7761,7763],{"className":7762},[3538],"[{\"id\":1,\"username\":\"alice\"},{\"id\":2,\"username\":\"bob\"}]",[3385,7765,7766],{},"У терміналі сервера з'явиться рядок глобального логера, що підтверджує спрацювання обох залежностей:",[4672,7768,7770,7774],{":cursor":4674,"title":7769},"Логи сервера при успіху",[4677,7771,7773],{"className":7772},[3538],"[Global Logger] Запит від клієнта: CurlClient",[4677,7775,7777],{"className":7776},[3538],"INFO:     127.0.0.1:54321 - \"GET \u002Fadmin\u002Fusers HTTP\u002F1.1\" 200 OK",[4912,7779,7781],{"id":7780},"сценарій-2-відмова-в-доступі-неправильний-або-відсутній-токен","Сценарій 2: Відмова в доступі (Неправильний або відсутній токен)",[3385,7783,7784],{},"Надішлемо запит із невалідним токеном:",[3523,7786,7787,7819],{},[3526,7788,7790],{"className":4657,"code":7789,"filename":6469,"language":4660,"meta":3532,"style":3532},"curl -X GET http:\u002F\u002F127.0.0.1:8000\u002Fadmin\u002Fusers \\\n  -H \"X-Admin-Token: wrongtoken\" \\\n  -H \"User-Agent: CurlClient\"\n",[3395,7791,7792,7804,7813],{"__ignoreMap":3532},[3536,7793,7794,7796,7798,7800,7802],{"class":3538,"line":3539},[3536,7795,6518],{"class":3588},[3536,7797,6521],{"class":3584},[3536,7799,6524],{"class":3690},[3536,7801,7681],{"class":3690},[3536,7803,7685],{"class":7684},[3536,7805,7806,7808,7811],{"class":3538,"line":3556},[3536,7807,7690],{"class":3584},[3536,7809,7810],{"class":3690}," \"X-Admin-Token: wrongtoken\"",[3536,7812,7685],{"class":7684},[3536,7814,7815,7817],{"class":3538,"line":3563},[3536,7816,7690],{"class":3584},[3536,7818,7702],{"class":3690},[3526,7820,7822],{"className":6485,"code":7821,"filename":6486,"language":6487,"meta":3532,"style":3532},"curl.exe -X GET http:\u002F\u002F127.0.0.1:8000\u002Fadmin\u002Fusers `\n  -H \"X-Admin-Token: wrongtoken\" `\n  -H \"User-Agent: CurlClient\"\n",[3395,7823,7824,7842,7851],{"__ignoreMap":3532},[3536,7825,7826,7828,7830,7832,7834,7836,7838,7840],{"class":3538,"line":3539},[3536,7827,6537],{"class":3588},[3536,7829,6540],{"class":3546},[3536,7831,6543],{"class":4477},[3536,7833,3398],{"class":3546},[3536,7835,6548],{"class":4477},[3536,7837,4889],{"class":3546},[3536,7839,6553],{"class":4477},[3536,7841,7726],{"class":3546},[3536,7843,7844,7846,7849],{"class":3538,"line":3556},[3536,7845,7731],{"class":3546},[3536,7847,7848],{"class":3690},"\"X-Admin-Token: wrongtoken\"",[3536,7850,7737],{"class":3546},[3536,7852,7853,7855],{"class":3538,"line":3563},[3536,7854,7731],{"class":3546},[3536,7856,7744],{"class":3690},[4672,7858,7859,7865,7868,7871],{":cursor":4674,"title":6630},[4677,7860,7862],{"className":7861},[3538],[3536,7863,6638],{"className":7864},[6637,4688],[4677,7866,6571],{"className":7867},[3538],[4677,7869],{"className":7870},[3538],[4677,7872,7874],{"className":7873},[3538],"{\"detail\":\"Невалідний токен адміністратора\"}",[3385,7876,7877,7878,7880],{},"А у терміналі сервера ми все одно побачимо виклик глобального логера, оскільки він спрацьовує на рівні всього додатку ",[7259,7879,5076],{}," валідації роутера:",[4672,7882,7884,7887],{":cursor":4674,"title":7883},"Логи сервера при помилці",[4677,7885,7773],{"className":7886},[3538],[4677,7888,7890],{"className":7889},[3538],"INFO:     127.0.0.1:54321 - \"GET \u002Fadmin\u002Fusers HTTP\u002F1.1\" 403 Forbidden",[3406,7892],{},[3417,7894,7896],{"id":7895},"області-видимості-та-час-життя-залежностей-scopes-lifetimes","Області видимості та час життя залежностей (Scopes & Lifetimes)",[3385,7898,7899],{},"В ASP.NET Core розробники мають чіткі межі управління життєвим циклом об'єктів (Transient, Scoped, Singleton). Як ці концепції мапуються на FastAPI?",[4912,7901,7903],{"id":7902},"_1-request-scope-за-замовчуванням","1. Request Scope (За замовчуванням)",[3385,7905,7906,7907,7909,7910,7912,7913,7915],{},"Будь-яка залежність, яку ви впроваджуєте через ",[3395,7908,3397],{},", за замовчуванням має Request Scope (час життя в межах одного HTTP-запиту). Вона ініціалізується під час обробки запиту, кешується (якщо ",[3395,7911,4208],{},"), і повністю знищується (разом із очищенням її ",[3395,7914,5035],{},"-генераторів) після відправки HTTP-відповіді клієнту.",[4912,7917,7919],{"id":7918},"_2-application-lifetime-scope-lifespan-singleton","2. Application-Lifetime Scope (Lifespan \u002F Singleton)",[3385,7921,7922],{},"У професійній розробці існують важкі об'єкти, створення яких під час обробки кожного HTTP-запиту є недопустимим через великі накладні витрати часу та процесорної пам'яті. До них відносяться:",[3429,7924,7925,7928,7935],{},[3432,7926,7927],{},"Пул з'єднань із базою даних (Database Connection Pool) або клієнт Redis.",[3432,7929,7930,7931,7934],{},"Асинхронний HTTP-клієнт (",[3395,7932,7933],{},"httpx.AsyncClient",") для комунікації з іншими мікросервісами.",[3432,7936,7937],{},"Важкі моделі машинного навчання (наприклад, ваги нейромереж, завантажені через PyTorch або TensorFlow).",[3385,7939,7940,7941,7944,7945,7948,7949,5568,7952,3398],{},"Ці об'єкти мають створюватися ",[3392,7942,7943],{},"рівно один раз"," при запуску веб-сервера та існувати в пам'яті протягом усього життєвого циклу програми (Singleton). У FastAPI для цього використовується сучасний протокол ",[3392,7946,7947],{},"Lifespan Events"," (події життєвого циклу), який прийшов на заміну застарілим та небезпечним декораторам ",[3395,7950,7951],{},"@app.on_event(\"startup\")",[3395,7953,7954],{},"@app.on_event(\"shutdown\")",[3406,7956],{},[7958,7959,7961,7962,7965],"h5",{"id":7960},"чому-старі-декоратори-appon_event-тепер-є-антипатерном","Чому старі декоратори ",[3395,7963,7964],{},"@app.on_event"," тепер є антипатерном?",[3385,7967,7968],{},"Раніше старт і зупинка додатку описувалися різними функціями:",[3526,7970,7973],{"className":3528,"code":7971,"filename":7972,"language":3531,"meta":3532,"style":3532},"# ❌ Застарілий підхід (Startup\u002FShutdown декоратори)\ndb_client = None\n\n@app.on_event(\"startup\")\nasync def startup():\n    global db_client\n    db_client = DatabaseClient()\n    await db_client.connect()\n\n@app.on_event(\"shutdown\")\nasync def shutdown():\n    await db_client.disconnect()\n","old_events.py",[3395,7974,7975,7980,7988,7992,8003,8014,8022,8027,8035,8039,8050,8061],{"__ignoreMap":3532},[3536,7976,7977],{"class":3538,"line":3539},[3536,7978,7979],{"class":3577},"# ❌ Застарілий підхід (Startup\u002FShutdown декоратори)\n",[3536,7981,7982,7985],{"class":3538,"line":3556},[3536,7983,7984],{"class":3546},"db_client = ",[3536,7986,7987],{"class":3584},"None\n",[3536,7989,7990],{"class":3538,"line":3563},[3536,7991,3560],{"emptyLinePlaceholder":3559},[3536,7993,7994,7996,7998,8001],{"class":3538,"line":3569},[3536,7995,7964],{"class":3588},[3536,7997,3646],{"class":3546},[3536,7999,8000],{"class":3690},"\"startup\"",[3536,8002,3694],{"class":3546},[3536,8004,8005,8007,8009,8012],{"class":3538,"line":3574},[3536,8006,3700],{"class":3584},[3536,8008,3703],{"class":3584},[3536,8010,8011],{"class":3588}," startup",[3536,8013,3592],{"class":3546},[3536,8015,8016,8019],{"class":3538,"line":3581},[3536,8017,8018],{"class":3584},"    global",[3536,8020,8021],{"class":3546}," db_client\n",[3536,8023,8024],{"class":3538,"line":3595},[3536,8025,8026],{"class":3546},"    db_client = DatabaseClient()\n",[3536,8028,8029,8032],{"class":3538,"line":3601},[3536,8030,8031],{"class":3542},"    await",[3536,8033,8034],{"class":3546}," db_client.connect()\n",[3536,8036,8037],{"class":3538,"line":3610},[3536,8038,3560],{"emptyLinePlaceholder":3559},[3536,8040,8041,8043,8045,8048],{"class":3538,"line":3619},[3536,8042,7964],{"class":3588},[3536,8044,3646],{"class":3546},[3536,8046,8047],{"class":3690},"\"shutdown\"",[3536,8049,3694],{"class":3546},[3536,8051,8052,8054,8056,8059],{"class":3538,"line":3627},[3536,8053,3700],{"class":3584},[3536,8055,3703],{"class":3584},[3536,8057,8058],{"class":3588}," shutdown",[3536,8060,3592],{"class":3546},[3536,8062,8063,8065],{"class":3538,"line":3633},[3536,8064,8031],{"class":3542},[3536,8066,8067],{"class":3546}," db_client.disconnect()\n",[3385,8069,8070],{},"Цей підхід мав три фундаментальні проблеми:",[4852,8072,8073,8087,8101],{},[3432,8074,8075,8078,8079,8082,8083,8086],{},[3392,8076,8077],{},"Глобальний стан",": Змінну ",[3395,8080,8081],{},"db_client"," доводилося робити глобальною (",[3395,8084,8085],{},"global","), щоб передати її з функції ініціалізації у функцію очищення. Це порушує модульність і створює ризик перезапису змінної під час запусків тестів.",[3432,8088,8089,8092,8093,8096,8097,8100],{},[3392,8090,8091],{},"Витоки ресурсів",": Якщо у функції ",[3395,8094,8095],{},"startup"," виникала помилка, сервер завершував роботу, але подія ",[3395,8098,8099],{},"shutdown"," не викликалася. Через це відкриті сокети або файлові дескриптори залишалися заблокованими в системі.",[3432,8102,8103,8106,8107,8110],{},[3392,8104,8105],{},"Проблеми тестування",": При запуску паралельних тестів (наприклад, через ",[3395,8108,8109],{},"pytest-xdist",") глобальні змінні різних тестів конфліктували між собою, викликаючи ефект \"flaky tests\" (нестабільних тестів).",[3406,8112],{},[7958,8114,8116],{"id":8115},"анатомія-lifespan-та-asgi-протоколу-під-капотом","Анатомія Lifespan та ASGI-протоколу під капотом",[3385,8118,8119,8120,8123],{},"Сучасний Lifespan-механізм у FastAPI працює на базі стандарту ",[3392,8121,8122],{},"ASGI (Asynchronous Server Gateway Interface)",". ASGI-сервер (наприклад, Uvicorn) координує старт та зупинку додатка за допомогою спеціального циклу обміну повідомленнями.",[3385,8125,8126,8127,4152,8130,8132,8133,3398],{},"На рівні Python це реалізується за допомогою ",[3392,8128,8129],{},"асинхронного контекстного менеджера",[3395,8131,5587],{},"), який поєднує фазу ініціалізації та очищення в межах однієї функції, розділеної ключовим словом ",[3395,8134,5035],{},[3385,8136,8137],{},"Візуалізуємо схему обміну повідомленнями Lifespan між сервером та додатком:",[3526,8139,8144],{"className":8140,"code":8142,"language":8143},[8141],"language-text","[ Uvicorn Server ]                     [ FastAPI App ]\n       |                                      |\n       | 1. lifespan.startup                  |\n       |------------------------------------->|\n       |                                      | === [Виконується SETUP код до yield]\n       |                                      | === [Створюється пул з'єднань, клієнти]\n       | 2. lifespan.startup.complete         |\n       |\u003C-------------------------------------|\n       |                                      |\n       | === [ СЕРВЕР ПРИЙМАЄ HTTP-ЗАПИТИ ] ===|\n       |                                      |\n       | 3. Зупинка (Ctrl+C \u002F SIGTERM)        |\n       |                                      |\n       | 4. lifespan.shutdown                 |\n       |------------------------------------->|\n       |                                      | === [Виконується CLEANUP код після yield]\n       |                                      | === [Закриваються пули, файли, сесії]\n       | 5. lifespan.shutdown.complete        |\n       |\u003C-------------------------------------|\n       v                                      v\n [Процес завершено]\n","text",[3395,8145,8142],{"__ignoreMap":3532},[7958,8147,8149,8150,4156],{"id":8148},"робота-з-станом-програми-appstate","Робота з станом програми (",[3395,8151,8152],{},"app.state",[3385,8154,8155,8156,8160,8161,4274],{},"Щоб уникнути використання глобальних змінних, FastAPI (наслідуючи Starlette) надає об'єкт ",[3392,8157,8158],{},[3395,8159,8152],{}," (екземпляр класу ",[3395,8162,8163],{},"State",[3429,8165,8166,8173,8179],{},[3432,8167,8168,8170,8171,3398],{},[3395,8169,8152],{}," — це спеціальне динамічне сховище, яке живе стільки ж, скільки й сам екземпляр класу ",[3395,8172,2609],{},[3432,8174,8175,8176,3398],{},"Будь-які об'єкти (пули, http-клієнти, моделі), які створюються в фазі Setup, записуються безпосередньо в атрибути ",[3395,8177,8178],{},"app.state.some_resource = resource",[3432,8180,8181,8182,8185,8186,8189],{},"Оскільки при тестуванні ми створюємо ізольований об'єкт ",[3395,8183,8184],{},"app"," для кожного тестового прогону, кожен тест отримує власне ізольоване сховище ",[3395,8187,8188],{},"state",", що повністю вирішує проблему раси ресурсів у тестах.",[3385,8191,8192,8193,8196,8197,3398],{},"Щоб отримати доступ до цього стану під час HTTP-запиту, ми інжектуємо залежність ",[3395,8194,8195],{},"Request"," (системний об'єкт запиту FastAPI), яка містить посилання на наш додаток: ",[3395,8198,8199],{},"request.app.state",[3406,8201],{},[7958,8203,8205],{"id":8204},"практична-реалізація-lifespan-singleton-через-di","Практична реалізація Lifespan Singleton через DI",[3385,8207,8208,8209,8211],{},"Розглянемо повноцінний приклад, де ми створюємо один екземпляр асинхронного HTTP-клієнта (",[3395,8210,7933],{},") на старті додатка, використовуємо його в роутах через DI та гарантовано закриваємо при зупинці сервера:",[3526,8213,8216],{"className":3528,"code":8214,"filename":8215,"language":3531,"meta":3532,"style":3532},"# lifespan_di_demo.py\nimport httpx\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI, Depends, Request\n\n# 1. Визначаємо асинхронний менеджер життєвого циклу додатка (Lifespan)\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # --- SETUP ФАЗА (виконується при запуску додатка) ---\n    # Створюємо один клієнт на весь час роботи сервера\n    client = httpx.AsyncClient(timeout=5.0)\n    app.state.http_client = client\n    print(\"[Lifespan Setup] Асинхронний HTTP-клієнт створено (Singleton)\")\n    \n    yield  # Тут додаток запускається і приймає HTTP-запити\n    \n    # --- CLEANUP ФАЗА (виконується при вимкненні додатка) ---\n    await client.aclose()\n    print(\"[Lifespan Cleanup] Асинхронний HTTP-клієнт успішно закрито\")\n\n# Передаємо lifespan в додаток при його ініціалізації\napp = FastAPI(lifespan=lifespan)\n\n# 2. Створюємо Request-scoped залежність, яка витягує Singleton із стану додатка\ndef get_http_client(request: Request) -> httpx.AsyncClient:\n    # Звертаємось до app.state через об'єкт HTTP запиту\n    return request.app.state.http_client\n\n# 3. Використовуємо залежність в ендпоінті\n@app.get(\"\u002Fweather\u002F{city}\")\nasync def get_weather(city: str, client: httpx.AsyncClient = Depends(get_http_client)):\n    # Виконуємо асинхронний запит до зовнішнього API\n    response = await client.get(f\"https:\u002F\u002Fapi.weatherapi.com\u002Fv1\u002Fcurrent.json?q={city}\")\n    return {\"city\": city, \"status_code\": response.status_code}\n","lifespan_di_demo.py",[3395,8217,8218,8223,8230,8242,8253,8257,8262,8267,8283,8288,8293,8308,8313,8324,8329,8337,8341,8346,8353,8364,8368,8373,8383,8387,8392,8407,8412,8419,8423,8428,8444,8469,8474,8500],{"__ignoreMap":3532},[3536,8219,8220],{"class":3538,"line":3539},[3536,8221,8222],{"class":3577},"# lifespan_di_demo.py\n",[3536,8224,8225,8227],{"class":3538,"line":3556},[3536,8226,3550],{"class":3542},[3536,8228,8229],{"class":3546}," httpx\n",[3536,8231,8232,8234,8237,8239],{"class":3538,"line":3563},[3536,8233,3543],{"class":3542},[3536,8235,8236],{"class":3546}," contextlib ",[3536,8238,3550],{"class":3542},[3536,8240,8241],{"class":3546}," asynccontextmanager\n",[3536,8243,8244,8246,8248,8250],{"class":3538,"line":3569},[3536,8245,3543],{"class":3542},[3536,8247,3547],{"class":3546},[3536,8249,3550],{"class":3542},[3536,8251,8252],{"class":3546}," FastAPI, Depends, Request\n",[3536,8254,8255],{"class":3538,"line":3574},[3536,8256,3560],{"emptyLinePlaceholder":3559},[3536,8258,8259],{"class":3538,"line":3581},[3536,8260,8261],{"class":3577},"# 1. Визначаємо асинхронний менеджер життєвого циклу додатка (Lifespan)\n",[3536,8263,8264],{"class":3538,"line":3595},[3536,8265,8266],{"class":3588},"@asynccontextmanager\n",[3536,8268,8269,8271,8273,8276,8278,8280],{"class":3538,"line":3601},[3536,8270,3700],{"class":3584},[3536,8272,3703],{"class":3584},[3536,8274,8275],{"class":3588}," lifespan",[3536,8277,3646],{"class":3546},[3536,8279,8184],{"class":3649},[3536,8281,8282],{"class":3546},": FastAPI):\n",[3536,8284,8285],{"class":3538,"line":3610},[3536,8286,8287],{"class":3577},"    # --- SETUP ФАЗА (виконується при запуску додатка) ---\n",[3536,8289,8290],{"class":3538,"line":3619},[3536,8291,8292],{"class":3577},"    # Створюємо один клієнт на весь час роботи сервера\n",[3536,8294,8295,8298,8301,8303,8306],{"class":3538,"line":3627},[3536,8296,8297],{"class":3546},"    client = httpx.AsyncClient(",[3536,8299,8300],{"class":3649},"timeout",[3536,8302,4474],{"class":3546},[3536,8304,8305],{"class":4477},"5.0",[3536,8307,3694],{"class":3546},[3536,8309,8310],{"class":3538,"line":3633},[3536,8311,8312],{"class":3546},"    app.state.http_client = client\n",[3536,8314,8315,8317,8319,8322],{"class":3538,"line":3638},[3536,8316,6937],{"class":3588},[3536,8318,3646],{"class":3546},[3536,8320,8321],{"class":3690},"\"[Lifespan Setup] Асинхронний HTTP-клієнт створено (Singleton)\"",[3536,8323,3694],{"class":3546},[3536,8325,8326],{"class":3538,"line":3656},[3536,8327,8328],{"class":3546},"    \n",[3536,8330,8331,8334],{"class":3538,"line":3662},[3536,8332,8333],{"class":3542},"    yield",[3536,8335,8336],{"class":3577},"  # Тут додаток запускається і приймає HTTP-запити\n",[3536,8338,8339],{"class":3538,"line":3671},[3536,8340,8328],{"class":3546},[3536,8342,8343],{"class":3538,"line":3676},[3536,8344,8345],{"class":3577},"    # --- CLEANUP ФАЗА (виконується при вимкненні додатка) ---\n",[3536,8347,8348,8350],{"class":3538,"line":3682},[3536,8349,8031],{"class":3542},[3536,8351,8352],{"class":3546}," client.aclose()\n",[3536,8354,8355,8357,8359,8362],{"class":3538,"line":3697},[3536,8356,6937],{"class":3588},[3536,8358,3646],{"class":3546},[3536,8360,8361],{"class":3690},"\"[Lifespan Cleanup] Асинхронний HTTP-клієнт успішно закрито\"",[3536,8363,3694],{"class":3546},[3536,8365,8366],{"class":3538,"line":3712},[3536,8367,3560],{"emptyLinePlaceholder":3559},[3536,8369,8370],{"class":3538,"line":3721},[3536,8371,8372],{"class":3577},"# Передаємо lifespan в додаток при його ініціалізації\n",[3536,8374,8375,8377,8380],{"class":3538,"line":3730},[3536,8376,7446],{"class":3546},[3536,8378,8379],{"class":3649},"lifespan",[3536,8381,8382],{"class":3546},"=lifespan)\n",[3536,8384,8385],{"class":3538,"line":3736},[3536,8386,3560],{"emptyLinePlaceholder":3559},[3536,8388,8389],{"class":3538,"line":4052},[3536,8390,8391],{"class":3577},"# 2. Створюємо Request-scoped залежність, яка витягує Singleton із стану додатка\n",[3536,8393,8394,8396,8399,8401,8404],{"class":3538,"line":4073},[3536,8395,3585],{"class":3584},[3536,8397,8398],{"class":3588}," get_http_client",[3536,8400,3646],{"class":3546},[3536,8402,8403],{"class":3649},"request",[3536,8405,8406],{"class":3546},": Request) -> httpx.AsyncClient:\n",[3536,8408,8409],{"class":3538,"line":4105},[3536,8410,8411],{"class":3577},"    # Звертаємось до app.state через об'єкт HTTP запиту\n",[3536,8413,8414,8416],{"class":3538,"line":4110},[3536,8415,3665],{"class":3542},[3536,8417,8418],{"class":3546}," request.app.state.http_client\n",[3536,8420,8421],{"class":3538,"line":4504},[3536,8422,3560],{"emptyLinePlaceholder":3559},[3536,8424,8425],{"class":3538,"line":4509},[3536,8426,8427],{"class":3577},"# 3. Використовуємо залежність в ендпоінті\n",[3536,8429,8430,8432,8434,8437,8440,8442],{"class":3538,"line":4524},[3536,8431,3685],{"class":3588},[3536,8433,3646],{"class":3546},[3536,8435,8436],{"class":3690},"\"\u002Fweather\u002F",[3536,8438,8439],{"class":3584},"{city}",[3536,8441,4559],{"class":3690},[3536,8443,3694],{"class":3546},[3536,8445,8446,8448,8450,8453,8455,8458,8460,8462,8464,8466],{"class":3538,"line":4530},[3536,8447,3700],{"class":3584},[3536,8449,3703],{"class":3584},[3536,8451,8452],{"class":3588}," get_weather",[3536,8454,3646],{"class":3546},[3536,8456,8457],{"class":3649},"city",[3536,8459,3753],{"class":3546},[3536,8461,6108],{"class":3820},[3536,8463,3824],{"class":3546},[3536,8465,7154],{"class":3649},[3536,8467,8468],{"class":3546},": httpx.AsyncClient = Depends(get_http_client)):\n",[3536,8470,8471],{"class":3538,"line":4542},[3536,8472,8473],{"class":3577},"    # Виконуємо асинхронний запит до зовнішнього API\n",[3536,8475,8476,8479,8482,8485,8487,8490,8492,8494,8496,8498],{"class":3538,"line":4548},[3536,8477,8478],{"class":3546},"    response = ",[3536,8480,8481],{"class":3542},"await",[3536,8483,8484],{"class":3546}," client.get(",[3536,8486,4556],{"class":3584},[3536,8488,8489],{"class":3690},"\"https:\u002F\u002Fapi.weatherapi.com\u002Fv1\u002Fcurrent.json?q=",[3536,8491,4562],{"class":3584},[3536,8493,8457],{"class":3546},[3536,8495,4568],{"class":3584},[3536,8497,4559],{"class":3690},[3536,8499,3694],{"class":3546},[3536,8501,8502,8504,8506,8509,8512,8515],{"class":3538,"line":4599},[3536,8503,3665],{"class":3542},[3536,8505,3741],{"class":3546},[3536,8507,8508],{"class":3690},"\"city\"",[3536,8510,8511],{"class":3546},": city, ",[3536,8513,8514],{"class":3690},"\"status_code\"",[3536,8516,8517],{"class":3546},": response.status_code}\n",[3406,8519],{},[7958,8521,8523],{"id":8522},"інструкція-тестування-життєвого-циклу","Інструкція тестування життєвого циклу",[3385,8525,8526],{},"Запустимо цей додаток за допомогою Uvicorn:",[3523,8528,8529,8543],{},[3526,8530,8532],{"className":4657,"code":8531,"filename":6469,"language":4660,"meta":3532,"style":3532},"uvicorn lifespan_di_demo:app --reload\n",[3395,8533,8534],{"__ignoreMap":3532},[3536,8535,8536,8538,8541],{"class":3538,"line":3539},[3536,8537,6476],{"class":3588},[3536,8539,8540],{"class":3690}," lifespan_di_demo:app",[3536,8542,6482],{"class":3584},[3526,8544,8545],{"className":6485,"code":8531,"filename":6486,"language":6487,"meta":3532,"style":3532},[3395,8546,8547],{"__ignoreMap":3532},[3536,8548,8549],{"class":3538,"line":3539},[3536,8550,8531],{"class":3546},[3385,8552,8553,8554,8556],{},"У терміналі Uvicorn ми відразу побачимо вивід нашої Setup фази, який виконується ",[3392,8555,5076],{}," початку прослуховування портів:",[4672,8558,8561,8569,8576,8583,8587,8594],{":cursor":8559,"title":8560},"true","Вивід запуска сервера",[4677,8562,8564,8568],{"className":8563},[3538],[3536,8565,8567],{"className":8566},[4687,4688],"INFO:","     Started reloader process [12400] using StatReload",[4677,8570,8572,8575],{"className":8571},[3538],[3536,8573,8567],{"className":8574},[4687,4688],"     Started server process [12401]",[4677,8577,8579,8582],{"className":8578},[3538],[3536,8580,8567],{"className":8581},[4687,4688],"     Waiting for application startup.",[4677,8584,8586],{"className":8585},[3538],"[Lifespan Setup] Асинхронний HTTP-клієнт створено (Singleton)",[4677,8588,8590,8593],{"className":8589},[3538],[3536,8591,8567],{"className":8592},[4687,4688],"     Application startup complete.",[4677,8595,8597,8600,8601,8605],{"className":8596},[3538],[3536,8598,8567],{"className":8599},[4687,4688],"     Uvicorn running on ",[3392,8602,8604],{"className":8603},[4688],"http:\u002F\u002F127.0.0.1:8000"," (Press CTRL+C to quit)",[3385,8607,8608],{},"Тепер надішлемо тестовий запит для перевірки роботи ендпоінту:",[3523,8610,8611,8627],{},[3526,8612,8614],{"className":4657,"code":8613,"filename":6469,"language":4660,"meta":3532,"style":3532},"curl -X GET http:\u002F\u002F127.0.0.1:8000\u002Fweather\u002FKyiv\n",[3395,8615,8616],{"__ignoreMap":3532},[3536,8617,8618,8620,8622,8624],{"class":3538,"line":3539},[3536,8619,6518],{"class":3588},[3536,8621,6521],{"class":3584},[3536,8623,6524],{"class":3690},[3536,8625,8626],{"class":3690}," http:\u002F\u002F127.0.0.1:8000\u002Fweather\u002FKyiv\n",[3526,8628,8630],{"className":6485,"code":8629,"filename":6486,"language":6487,"meta":3532,"style":3532},"curl.exe -X GET http:\u002F\u002F127.0.0.1:8000\u002Fweather\u002FKyiv\n",[3395,8631,8632],{"__ignoreMap":3532},[3536,8633,8634,8636,8638,8640,8642,8644,8646,8648],{"class":3538,"line":3539},[3536,8635,6537],{"class":3588},[3536,8637,6540],{"class":3546},[3536,8639,6543],{"class":4477},[3536,8641,3398],{"class":3546},[3536,8643,6548],{"class":4477},[3536,8645,4889],{"class":3546},[3536,8647,6553],{"class":4477},[3536,8649,8650],{"class":3546},"\u002Fweather\u002FKyiv\n",[4672,8652,8654,8660,8663,8666],{":cursor":4674,"title":8653},"Консольний вивід відповіді клієнта",[4677,8655,8657],{"className":8656},[3538],[3536,8658,6567],{"className":8659},[6566,4688],[4677,8661,6571],{"className":8662},[3538],[4677,8664],{"className":8665},[3538],[4677,8667,8669],{"className":8668},[3538],"{\"city\":\"Kyiv\",\"status_code\":401}",[3385,8671,8672],{},[7259,8673,8674],{},"(Примітка: статус 401 повертає сторонній сервіс WeatherAPI через відсутність валідного API-ключа в нашому демо-запиті, але це підтверджує, що HTTP-клієнт успішно виконав запит).",[3385,8676,8677,8678,8681],{},"Тепер зупинимо сервер, натиснувши ",[3395,8679,8680],{},"Ctrl+C"," в терміналі. Ми побачимо запуск Cleanup фази перед повним завершенням процесу:",[4672,8683,8685,8692,8699,8703,8710],{":cursor":8559,"title":8684},"Вивід зупинки сервера",[4677,8686,8688,8691],{"className":8687},[3538],[3536,8689,8567],{"className":8690},[4687,4688],"     Shutting down",[4677,8693,8695,8698],{"className":8694},[3538],[3536,8696,8567],{"className":8697},[4687,4688],"     Waiting for application shutdown.",[4677,8700,8702],{"className":8701},[3538],"[Lifespan Cleanup] Асинхронний HTTP-клієнт успішно закрито",[4677,8704,8706,8709],{"className":8705},[3538],[3536,8707,8567],{"className":8708},[4687,4688],"     Application shutdown complete.",[4677,8711,8713,8716],{"className":8712},[3538],[3536,8714,8567],{"className":8715},[4687,4688],"     Finished server process [12401]",[3385,8718,8719],{},"Завдяки такому архітектурному патерну:",[4852,8721,8722,8728,8737],{},[3432,8723,8724,8727],{},[3392,8725,8726],{},"Економічність",": Мережеві сокети для HTTP-клієнта тримаються відкритими протягом усієї роботи програми, уникаючи оверхеду на встановлення TCP\u002FTLS з'єднання (handshake) для кожного HTTP-запиту.",[3432,8729,8730,8733,8734,8736],{},[3392,8731,8732],{},"Гарантія очищення",": Блок після ",[3395,8735,5035],{}," гарантовано закриє сокети навіть у разі падіння додатку через системні збої.",[3432,8738,8739,8742,8743,8745,8746,8748,8749,8752,8753,3398],{},[3392,8740,8741],{},"Сумісність із DI",": Будь-який ендпоінт вимагає ",[3395,8744,7933],{}," декларативно. У тестах нам не потрібно підміняти весь об'єкт ",[3395,8747,2609],{},", достатньо замінити функцію ",[3395,8750,8751],{},"get_http_client"," у словнику ",[3395,8754,8755],{},"dependency_overrides",[3406,8757],{},[3409,8759,8761],{"id":8760},"частина-iv-тестування-перевизначення-залежностей-та-патерн-unit-of-work","Частина IV: Тестування, перевизначення залежностей та патерн Unit of Work",[3385,8763,8764],{},"Однією з головних переваг використання Dependency Injection є значне полегшення процесу тестування додатку. Оскільки всі зовнішні інтеграції (бази даних, сторонні API, черги повідомлень) інжектуються як залежності, їх можна легко підмінити (mock) під час інтеграційного тестування. У цій частині ми розглянемо механізм перевизначення залежностей у FastAPI, порівняємо його з рішеннями в ASP.NET Core та навчимося реалізовувати транзакційний патерн Unit of Work за допомогою DI.",[3406,8766],{},[3417,8768,8770],{"id":8769},"перевизначення-залежностей-для-тестування-dependency-overrides","Перевизначення залежностей для тестування (Dependency Overrides)",[3385,8772,8773,8774,3398],{},"Для інтеграційного тестування FastAPI пропонує простий і лаконічний механізм — словник ",[3392,8775,8776],{},[3395,8777,4228],{},[3385,8779,8780],{},"Цей словник працює як глобальна таблиця перенаправлень:",[3429,8782,8783,8788],{},[3432,8784,8785,8786,4274],{},"Ключем є оригінальна функція-залежність (наприклад, ",[3395,8787,4243],{},[3432,8789,8790,8791,8794],{},"Значенням є тестова залежність (наприклад, ",[3395,8792,8793],{},"get_test_db","), яка повертає підроблене підключення (in-memory SQLite або mock-об'єкт).",[3385,8796,8797,8798,8800],{},"Коли FastAPI будує граф залежностей під час HTTP-запиту, він перевіряє словник ",[3395,8799,4228],{},". Якщо оригінальна залежність присутня в словнику, фреймворк ігнорує оригінальну функцію і викликає тестову.",[3385,8802,8803,8804,8807],{},"Після завершення тестів словник необхідно очистити за допомогою методу ",[3395,8805,8806],{},".clear()",", щоб повернути систему до вихідного стану.",[4912,8809,8811],{"id":8810},"практичний-приклад-тестування-з-перевизначенням","Практичний приклад тестування з перевизначенням",[3385,8813,8814],{},"Розглянемо додаток, який перевіряє статус платіжної транзакції через зовнішній банківський шлюз:",[3526,8816,8819],{"className":3528,"code":8817,"filename":8818,"language":3531,"meta":3532,"style":3532},"# test_overrides_demo.py\nfrom fastapi import FastAPI, Depends, HTTPException, status\nfrom fastapi.testclient import TestClient\n\napp = FastAPI()\n\n# Реальній клієнт банку (важка залежність)\nclass BankApiClient:\n    def get_payment_status(self, tx_id: str) -> str:\n        # У реальності тут виконується HTTP-запит до платіжної системи\n        raise NotImplementedError(\"Реальний мережевий запит заборонений у тестах\")\n\ndef get_bank_client() -> BankApiClient:\n    return BankApiClient()\n\n@app.get(\"\u002Fpayments\u002F{tx_id}\")\nasync def check_payment(tx_id: str, bank: BankApiClient = Depends(get_bank_client)):\n    try:\n        status_code = bank.get_payment_status(tx_id)\n        return {\"tx_id\": tx_id, \"status\": status_code}\n    except Exception:\n        raise HTTPException(\n            status_code=status.HTTP_503_SERVICE_UNAVAILABLE, \n            detail=\"Платіжний сервіс недоступний\"\n        )\n\n# --- ТЕСТУВАННЯ ---\n\n# Створюємо фейковий клієнт банку для тестів\nclass FakeBankApiClient:\n    def get_payment_status(self, tx_id: str) -> str:\n        if tx_id == \"tx_success_123\":\n            return \"SUCCESS\"\n        return \"FAILED\"\n\ndef get_test_bank_client() -> FakeBankApiClient:\n    return FakeBankApiClient()\n\n# Записуємо перевизначення в словник додатку\napp.dependency_overrides[get_bank_client] = get_test_bank_client\n\n# Створюємо TestClient для відправки HTTP запитів у пам'яті\nclient = TestClient(app)\n\ndef test_check_payment_success():\n    response = client.get(\"\u002Fpayments\u002Ftx_success_123\")\n    assert response.status_code == 200\n    assert response.json() == {\"tx_id\": \"tx_success_123\", \"status\": \"SUCCESS\"}\n\n# Після тестів очищаємо перевизначення\napp.dependency_overrides.clear()\n","test_overrides_demo.py",[3395,8820,8821,8826,8836,8848,8852,8856,8860,8865,8874,8900,8905,8919,8923,8933,8940,8944,8960,8985,8991,8996,9013,9021,9027,9034,9043,9047,9051,9056,9060,9065,9074,9098,9110,9118,9125,9129,9139,9146,9150,9155,9160,9164,9169,9174,9178,9187,9197,9208,9232,9236,9241],{"__ignoreMap":3532},[3536,8822,8823],{"class":3538,"line":3539},[3536,8824,8825],{"class":3577},"# test_overrides_demo.py\n",[3536,8827,8828,8830,8832,8834],{"class":3538,"line":3556},[3536,8829,3543],{"class":3542},[3536,8831,3547],{"class":3546},[3536,8833,3550],{"class":3542},[3536,8835,5153],{"class":3546},[3536,8837,8838,8840,8843,8845],{"class":3538,"line":3563},[3536,8839,3543],{"class":3542},[3536,8841,8842],{"class":3546}," fastapi.testclient ",[3536,8844,3550],{"class":3542},[3536,8846,8847],{"class":3546}," TestClient\n",[3536,8849,8850],{"class":3538,"line":3569},[3536,8851,3560],{"emptyLinePlaceholder":3559},[3536,8853,8854],{"class":3538,"line":3574},[3536,8855,3566],{"class":3546},[3536,8857,8858],{"class":3538,"line":3581},[3536,8859,3560],{"emptyLinePlaceholder":3559},[3536,8861,8862],{"class":3538,"line":3595},[3536,8863,8864],{"class":3577},"# Реальній клієнт банку (важка залежність)\n",[3536,8866,8867,8869,8872],{"class":3538,"line":3601},[3536,8868,4319],{"class":3584},[3536,8870,8871],{"class":3820}," BankApiClient",[3536,8873,3607],{"class":3546},[3536,8875,8876,8878,8881,8883,8885,8887,8890,8892,8894,8896,8898],{"class":3538,"line":3610},[3536,8877,4334],{"class":3584},[3536,8879,8880],{"class":3588}," get_payment_status",[3536,8882,3646],{"class":3546},[3536,8884,4342],{"class":3649},[3536,8886,3824],{"class":3546},[3536,8888,8889],{"class":3649},"tx_id",[3536,8891,3753],{"class":3546},[3536,8893,6108],{"class":3820},[3536,8895,7021],{"class":3546},[3536,8897,6108],{"class":3820},[3536,8899,3607],{"class":3546},[3536,8901,8902],{"class":3538,"line":3619},[3536,8903,8904],{"class":3577},"        # У реальності тут виконується HTTP-запит до платіжної системи\n",[3536,8906,8907,8909,8912,8914,8917],{"class":3538,"line":3627},[3536,8908,5387],{"class":3542},[3536,8910,8911],{"class":3820}," NotImplementedError",[3536,8913,3646],{"class":3546},[3536,8915,8916],{"class":3690},"\"Реальний мережевий запит заборонений у тестах\"",[3536,8918,3694],{"class":3546},[3536,8920,8921],{"class":3538,"line":3633},[3536,8922,3560],{"emptyLinePlaceholder":3559},[3536,8924,8925,8927,8930],{"class":3538,"line":3638},[3536,8926,3585],{"class":3584},[3536,8928,8929],{"class":3588}," get_bank_client",[3536,8931,8932],{"class":3546},"() -> BankApiClient:\n",[3536,8934,8935,8937],{"class":3538,"line":3656},[3536,8936,3665],{"class":3542},[3536,8938,8939],{"class":3546}," BankApiClient()\n",[3536,8941,8942],{"class":3538,"line":3662},[3536,8943,3560],{"emptyLinePlaceholder":3559},[3536,8945,8946,8948,8950,8953,8956,8958],{"class":3538,"line":3671},[3536,8947,3685],{"class":3588},[3536,8949,3646],{"class":3546},[3536,8951,8952],{"class":3690},"\"\u002Fpayments\u002F",[3536,8954,8955],{"class":3584},"{tx_id}",[3536,8957,4559],{"class":3690},[3536,8959,3694],{"class":3546},[3536,8961,8962,8964,8966,8969,8971,8973,8975,8977,8979,8982],{"class":3538,"line":3676},[3536,8963,3700],{"class":3584},[3536,8965,3703],{"class":3584},[3536,8967,8968],{"class":3588}," check_payment",[3536,8970,3646],{"class":3546},[3536,8972,8889],{"class":3649},[3536,8974,3753],{"class":3546},[3536,8976,6108],{"class":3820},[3536,8978,3824],{"class":3546},[3536,8980,8981],{"class":3649},"bank",[3536,8983,8984],{"class":3546},": BankApiClient = Depends(get_bank_client)):\n",[3536,8986,8987,8989],{"class":3538,"line":3682},[3536,8988,3604],{"class":3542},[3536,8990,3607],{"class":3546},[3536,8992,8993],{"class":3538,"line":3697},[3536,8994,8995],{"class":3546},"        status_code = bank.get_payment_status(tx_id)\n",[3536,8997,8998,9000,9002,9005,9008,9010],{"class":3538,"line":3712},[3536,8999,4076],{"class":3542},[3536,9001,3741],{"class":3546},[3536,9003,9004],{"class":3690},"\"tx_id\"",[3536,9006,9007],{"class":3546},": tx_id, ",[3536,9009,3750],{"class":3690},[3536,9011,9012],{"class":3546},": status_code}\n",[3536,9014,9015,9017,9019],{"class":3538,"line":3721},[3536,9016,5365],{"class":3542},[3536,9018,5368],{"class":3820},[3536,9020,3607],{"class":3546},[3536,9022,9023,9025],{"class":3538,"line":3730},[3536,9024,5387],{"class":3542},[3536,9026,5478],{"class":3546},[3536,9028,9029,9031],{"class":3538,"line":3736},[3536,9030,5484],{"class":3649},[3536,9032,9033],{"class":3546},"=status.HTTP_503_SERVICE_UNAVAILABLE, \n",[3536,9035,9036,9038,9040],{"class":3538,"line":4052},[3536,9037,5493],{"class":3649},[3536,9039,4474],{"class":3546},[3536,9041,9042],{"class":3690},"\"Платіжний сервіс недоступний\"\n",[3536,9044,9045],{"class":3538,"line":4073},[3536,9046,5504],{"class":3546},[3536,9048,9049],{"class":3538,"line":4105},[3536,9050,3560],{"emptyLinePlaceholder":3559},[3536,9052,9053],{"class":3538,"line":4110},[3536,9054,9055],{"class":3577},"# --- ТЕСТУВАННЯ ---\n",[3536,9057,9058],{"class":3538,"line":4504},[3536,9059,3560],{"emptyLinePlaceholder":3559},[3536,9061,9062],{"class":3538,"line":4509},[3536,9063,9064],{"class":3577},"# Створюємо фейковий клієнт банку для тестів\n",[3536,9066,9067,9069,9072],{"class":3538,"line":4524},[3536,9068,4319],{"class":3584},[3536,9070,9071],{"class":3820}," FakeBankApiClient",[3536,9073,3607],{"class":3546},[3536,9075,9076,9078,9080,9082,9084,9086,9088,9090,9092,9094,9096],{"class":3538,"line":4530},[3536,9077,4334],{"class":3584},[3536,9079,8880],{"class":3588},[3536,9081,3646],{"class":3546},[3536,9083,4342],{"class":3649},[3536,9085,3824],{"class":3546},[3536,9087,8889],{"class":3649},[3536,9089,3753],{"class":3546},[3536,9091,6108],{"class":3820},[3536,9093,7021],{"class":3546},[3536,9095,6108],{"class":3820},[3536,9097,3607],{"class":3546},[3536,9099,9100,9102,9105,9108],{"class":3538,"line":4542},[3536,9101,4533],{"class":3542},[3536,9103,9104],{"class":3546}," tx_id == ",[3536,9106,9107],{"class":3690},"\"tx_success_123\"",[3536,9109,3607],{"class":3546},[3536,9111,9112,9115],{"class":3538,"line":4548},[3536,9113,9114],{"class":3542},"            return",[3536,9116,9117],{"class":3690}," \"SUCCESS\"\n",[3536,9119,9120,9122],{"class":3538,"line":4599},[3536,9121,4076],{"class":3542},[3536,9123,9124],{"class":3690}," \"FAILED\"\n",[3536,9126,9127],{"class":3538,"line":4605},[3536,9128,3560],{"emptyLinePlaceholder":3559},[3536,9130,9131,9133,9136],{"class":3538,"line":4616},[3536,9132,3585],{"class":3584},[3536,9134,9135],{"class":3588}," get_test_bank_client",[3536,9137,9138],{"class":3546},"() -> FakeBankApiClient:\n",[3536,9140,9141,9143],{"class":3538,"line":4621},[3536,9142,3665],{"class":3542},[3536,9144,9145],{"class":3546}," FakeBankApiClient()\n",[3536,9147,9148],{"class":3538,"line":4648},[3536,9149,3560],{"emptyLinePlaceholder":3559},[3536,9151,9152],{"class":3538,"line":5473},[3536,9153,9154],{"class":3577},"# Записуємо перевизначення в словник додатку\n",[3536,9156,9157],{"class":3538,"line":5481},[3536,9158,9159],{"class":3546},"app.dependency_overrides[get_bank_client] = get_test_bank_client\n",[3536,9161,9162],{"class":3538,"line":5490},[3536,9163,3560],{"emptyLinePlaceholder":3559},[3536,9165,9166],{"class":3538,"line":5501},[3536,9167,9168],{"class":3577},"# Створюємо TestClient для відправки HTTP запитів у пам'яті\n",[3536,9170,9171],{"class":3538,"line":5507},[3536,9172,9173],{"class":3546},"client = TestClient(app)\n",[3536,9175,9176],{"class":3538,"line":7106},[3536,9177,3560],{"emptyLinePlaceholder":3559},[3536,9179,9180,9182,9185],{"class":3538,"line":7111},[3536,9181,3585],{"class":3584},[3536,9183,9184],{"class":3588}," test_check_payment_success",[3536,9186,3592],{"class":3546},[3536,9188,9189,9192,9195],{"class":3538,"line":7119},[3536,9190,9191],{"class":3546},"    response = client.get(",[3536,9193,9194],{"class":3690},"\"\u002Fpayments\u002Ftx_success_123\"",[3536,9196,3694],{"class":3546},[3536,9198,9199,9202,9205],{"class":3538,"line":7124},[3536,9200,9201],{"class":3542},"    assert",[3536,9203,9204],{"class":3546}," response.status_code == ",[3536,9206,9207],{"class":4477},"200\n",[3536,9209,9210,9212,9215,9217,9219,9221,9223,9225,9227,9230],{"class":3538,"line":7130},[3536,9211,9201],{"class":3542},[3536,9213,9214],{"class":3546}," response.json() == {",[3536,9216,9004],{"class":3690},[3536,9218,3753],{"class":3546},[3536,9220,9107],{"class":3690},[3536,9222,3824],{"class":3546},[3536,9224,3750],{"class":3690},[3536,9226,3753],{"class":3546},[3536,9228,9229],{"class":3690},"\"SUCCESS\"",[3536,9231,3759],{"class":3546},[3536,9233,9234],{"class":3538,"line":7142},[3536,9235,3560],{"emptyLinePlaceholder":3559},[3536,9237,9238],{"class":3538,"line":7164},[3536,9239,9240],{"class":3577},"# Після тестів очищаємо перевизначення\n",[3536,9242,9243],{"class":3538,"line":7172},[3536,9244,9245],{"class":3546},"app.dependency_overrides.clear()\n",[3385,9247,9248],{},"Для запуску тестів за допомогою pytest використовуйте команду:",[3526,9250,9252],{"className":4657,"code":9251,"filename":4659,"language":4660,"meta":3532,"style":3532},"pytest test_overrides_demo.py\n",[3395,9253,9254],{"__ignoreMap":3532},[3536,9255,9256,9259],{"class":3538,"line":3539},[3536,9257,9258],{"class":3588},"pytest",[3536,9260,9261],{"class":3690}," test_overrides_demo.py\n",[4672,9263,9265,9272,9276,9279,9287,9290],{":cursor":4674,"title":9264},"Вивід результатів тестування pytest",[4677,9266,9268],{"className":9267},[3538],[3536,9269,9271],{"className":9270},[6566,4688],"============================= test session starts ==============================",[4677,9273,9275],{"className":9274},[3538],"collected 1 item",[4677,9277],{"className":9278},[3538],[4677,9280,9282,9283],{"className":9281},[3538],"test_overrides_demo.py ",[3536,9284,9286],{"className":9285},[6566,4688],".                                                 [100%]",[4677,9288],{"className":9289},[3538],[4677,9291,9293],{"className":9292},[3538],[3536,9294,9296],{"className":9295},[6566,4688],"============================== 1 passed in 0.12s ===============================",[3406,9298],{},[3417,9300,5672,9302,9304,9305,9307],{"id":9301},"порівняння-архітектурних-концепцій-configuretestservices-aspnet-dependency_overrides-fastapi",[3395,9303,4222],{}," (ASP.NET) ↔ ",[3395,9306,8755],{}," (FastAPI)",[3385,9309,9310,9311,9314,9315,9318,9319,3398],{},"В ASP.NET Core інтеграційне тестування виконується через клас ",[3395,9312,9313],{},"WebApplicationFactory\u003CTEntryPoint>",". Для заміни реальних сервісів на тестові необхідно перевизначити метод ",[3395,9316,9317],{},"ConfigureWebHost"," та сконфігурувати DI-контейнер через ",[3395,9320,4222],{},[3385,9322,9323],{},"Порівняємо обидва підходи:",[3523,9325,9326,9354],{},[3526,9327,9329],{"className":3528,"code":9328,"filename":3530,"language":3531,"meta":3532,"style":3532},"# Заміна робиться простим присвоєнням у словник\napp.dependency_overrides[get_real_service] = get_test_service\n\n# Очищення після закінчення тесту\napp.dependency_overrides.clear()\n",[3395,9330,9331,9336,9341,9345,9350],{"__ignoreMap":3532},[3536,9332,9333],{"class":3538,"line":3539},[3536,9334,9335],{"class":3577},"# Заміна робиться простим присвоєнням у словник\n",[3536,9337,9338],{"class":3538,"line":3556},[3536,9339,9340],{"class":3546},"app.dependency_overrides[get_real_service] = get_test_service\n",[3536,9342,9343],{"class":3538,"line":3563},[3536,9344,3560],{"emptyLinePlaceholder":3559},[3536,9346,9347],{"class":3538,"line":3569},[3536,9348,9349],{"class":3577},"# Очищення після закінчення тесту\n",[3536,9351,9352],{"class":3538,"line":3574},[3536,9353,9245],{"class":3546},[3526,9355,9357],{"className":3762,"code":9356,"filename":3764,"language":3765,"meta":3532,"style":3532},"\u002F\u002F Потребує створення фабрики додатку та конфігурації контейнера\nvar factory = new WebApplicationFactory\u003CProgram>()\n    .WithWebHostBuilder(builder =>\n    {\n        builder.ConfigureTestServices(services =>\n        {\n            \u002F\u002F Видаляємо реальний сервіс та реєструємо мок\n            services.RemoveAll\u003CIBankApiClient>();\n            services.AddScoped\u003CIBankApiClient, FakeBankApiClient>();\n        });\n    });\n\nvar client = factory.CreateClient();\n",[3395,9358,9359,9364,9386,9401,9405,9421,9426,9431,9448,9467,9472,9477,9481],{"__ignoreMap":3532},[3536,9360,9361],{"class":3538,"line":3539},[3536,9362,9363],{"class":3577},"\u002F\u002F Потребує створення фабрики додатку та конфігурації контейнера\n",[3536,9365,9366,9368,9371,9373,9375,9378,9380,9383],{"class":3538,"line":3556},[3536,9367,3777],{"class":3584},[3536,9369,9370],{"class":3649}," factory",[3536,9372,3783],{"class":3546},[3536,9374,4084],{"class":3584},[3536,9376,9377],{"class":3820}," WebApplicationFactory",[3536,9379,3817],{"class":3546},[3536,9381,9382],{"class":3820},"Program",[3536,9384,9385],{"class":3546},">()\n",[3536,9387,9388,9391,9394,9396,9398],{"class":3538,"line":3563},[3536,9389,9390],{"class":3546},"    .",[3536,9392,9393],{"class":3588},"WithWebHostBuilder",[3536,9395,3646],{"class":3546},[3536,9397,3804],{"class":3649},[3536,9399,9400],{"class":3546}," =>\n",[3536,9402,9403],{"class":3538,"line":3569},[3536,9404,3990],{"class":3546},[3536,9406,9407,9410,9412,9414,9416,9419],{"class":3538,"line":3574},[3536,9408,9409],{"class":3649},"        builder",[3536,9411,3398],{"class":3546},[3536,9413,4222],{"class":3588},[3536,9415,3646],{"class":3546},[3536,9417,9418],{"class":3649},"services",[3536,9420,9400],{"class":3546},[3536,9422,9423],{"class":3538,"line":3581},[3536,9424,9425],{"class":3546},"        {\n",[3536,9427,9428],{"class":3538,"line":3595},[3536,9429,9430],{"class":3577},"            \u002F\u002F Видаляємо реальний сервіс та реєструємо мок\n",[3536,9432,9433,9436,9438,9441,9443,9446],{"class":3538,"line":3601},[3536,9434,9435],{"class":3649},"            services",[3536,9437,3398],{"class":3546},[3536,9439,9440],{"class":3588},"RemoveAll",[3536,9442,3817],{"class":3546},[3536,9444,9445],{"class":3820},"IBankApiClient",[3536,9447,3830],{"class":3546},[3536,9449,9450,9452,9454,9456,9458,9460,9462,9465],{"class":3538,"line":3610},[3536,9451,9435],{"class":3649},[3536,9453,3398],{"class":3546},[3536,9455,3814],{"class":3588},[3536,9457,3817],{"class":3546},[3536,9459,9445],{"class":3820},[3536,9461,3824],{"class":3546},[3536,9463,9464],{"class":3820},"FakeBankApiClient",[3536,9466,3830],{"class":3546},[3536,9468,9469],{"class":3538,"line":3619},[3536,9470,9471],{"class":3546},"        });\n",[3536,9473,9474],{"class":3538,"line":3627},[3536,9475,9476],{"class":3546},"    });\n",[3536,9478,9479],{"class":3538,"line":3633},[3536,9480,3560],{"emptyLinePlaceholder":3559},[3536,9482,9483,9485,9488,9490,9493,9495,9498],{"class":3538,"line":3638},[3536,9484,3777],{"class":3584},[3536,9486,9487],{"class":3649}," client",[3536,9489,3783],{"class":3546},[3536,9491,9492],{"class":3649},"factory",[3536,9494,3398],{"class":3546},[3536,9496,9497],{"class":3588},"CreateClient",[3536,9499,3870],{"class":3546},[3385,9501,9502,9503,9505,9506,4274],{},"Підхід FastAPI є значно простішим через використання стандартних структур даних мови Python (",[3395,9504,7013],{},"), тоді як ASP.NET Core вимагає знання методів конфігурації життєвого циклу контейнера та роботи з метаданими типів (",[3395,9507,9508],{},"IServiceCollection",[3406,9510],{},[3417,9512,9514],{"id":9513},"патерн-unit-of-work-uow-через-dependency-injection","Патерн Unit of Work (UoW) через Dependency Injection",[3385,9516,9517,9520,9521,9524,9525,4274],{},[3392,9518,9519],{},"Unit of Work"," — це патерн проєктування, який групує виконання кількох логічних операцій в одну бізнес-транзакцію. Його головна мета — гарантувати узгодженість даних (принцип ACID): або всі операції завершуються успішно та фіксуються (",[3395,9522,9523],{},"commit","), або, у разі будь-якої помилки, жодна з них не записується в базу даних (",[3395,9526,5658],{},[3385,9528,9529],{},"За допомогою DI у FastAPI ми можемо побудувати елегантний менеджер Unit of Work, який огортає виконання HTTP-запиту в транзакцію.",[3526,9531,9534],{"className":3528,"code":9532,"filename":9533,"language":3531,"meta":3532,"style":3532},"# unit_of_work_demo.py\nfrom fastapi import FastAPI, Depends, HTTPException, status\n\napp = FastAPI()\n\nclass DatabaseConnection:\n    \"\"\"Імітація з'єднання та транзакції бази даних.\"\"\"\n    def __init__(self):\n        print(\"[UoW Connection] З'єднання з БД встановлено\")\n        self.in_transaction = True\n\n    def commit(self):\n        print(\"[UoW Connection] Транзакцію успішно зафіксовано (COMMIT)\")\n        self.in_transaction = False\n\n    def rollback(self):\n        print(\"[UoW Connection] Помилка в транзакції! Зміни відкочено (ROLLBACK)\")\n        self.in_transaction = False\n\n    def close(self):\n        print(\"[UoW Connection] З'єднання з БД закрито\")\n\n# Клас Unit of Work\nclass UnitOfWork:\n    def __init__(self):\n        self.db = DatabaseConnection()\n\n    def save_changes(self):\n        self.db.commit()\n\n# Залежність для створення UoW з управлінням життєвим циклом\ndef get_unit_of_work():\n    uow = UnitOfWork()\n    try:\n        yield uow\n        # Якщо ендпоінт завершився успішно — фіксуємо транзакцію\n        uow.save_changes()\n    except Exception as e:\n        # У разі виникнення будь-якої помилки — відкочуємо зміни\n        uow.db.rollback()\n        raise e\n    finally:\n        # У будь-якому випадку закриваємо з'єднання\n        uow.db.close()\n\n# Сервіс бізнес-логіки\nclass TaskService:\n    def __init__(self, uow: UnitOfWork):\n        self.uow = uow\n\n    def create_task(self, title: str):\n        # Імітуємо запис в БД через об'єкт UoW\n        print(f\"[TaskService] Запис задачі '{title}' в БД...\")\n        if len(title) \u003C 3:\n            raise ValueError(\"Назва задачі занадто коротка\")\n        return {\"title\": title, \"status\": \"saved\"}\n\n# Залежність для створення сервісу\ndef get_task_service(uow: UnitOfWork = Depends(get_unit_of_work)) -> TaskService:\n    return TaskService(uow)\n\n# Ендпоінт інжектує сервіс\n@app.post(\"\u002Ftasks\")\nasync def create_task(title: str, service: TaskService = Depends(get_task_service)):\n    try:\n        return service.create_task(title)\n    except ValueError as e:\n        raise HTTPException(\n            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, \n            detail=str(e)\n        )\n","unit_of_work_demo.py",[3395,9535,9536,9541,9551,9555,9559,9563,9572,9577,9589,9600,9609,9613,9625,9636,9644,9648,9660,9671,9679,9683,9695,9706,9710,9715,9724,9736,9743,9747,9760,9767,9771,9776,9785,9790,9796,9803,9808,9813,9823,9828,9833,9840,9846,9851,9856,9860,9865,9874,9892,9899,9903,9925,9930,9952,9967,9982,10004,10009,10015,10030,10038,10043,10049,10062,10087,10094,10102,10113,10120,10128,10140],{"__ignoreMap":3532},[3536,9537,9538],{"class":3538,"line":3539},[3536,9539,9540],{"class":3577},"# unit_of_work_demo.py\n",[3536,9542,9543,9545,9547,9549],{"class":3538,"line":3556},[3536,9544,3543],{"class":3542},[3536,9546,3547],{"class":3546},[3536,9548,3550],{"class":3542},[3536,9550,5153],{"class":3546},[3536,9552,9553],{"class":3538,"line":3563},[3536,9554,3560],{"emptyLinePlaceholder":3559},[3536,9556,9557],{"class":3538,"line":3569},[3536,9558,3566],{"class":3546},[3536,9560,9561],{"class":3538,"line":3574},[3536,9562,3560],{"emptyLinePlaceholder":3559},[3536,9564,9565,9567,9570],{"class":3538,"line":3581},[3536,9566,4319],{"class":3584},[3536,9568,9569],{"class":3820}," DatabaseConnection",[3536,9571,3607],{"class":3546},[3536,9573,9574],{"class":3538,"line":3595},[3536,9575,9576],{"class":3690},"    \"\"\"Імітація з'єднання та транзакції бази даних.\"\"\"\n",[3536,9578,9579,9581,9583,9585,9587],{"class":3538,"line":3601},[3536,9580,4334],{"class":3584},[3536,9582,4337],{"class":3588},[3536,9584,3646],{"class":3546},[3536,9586,4342],{"class":3649},[3536,9588,3733],{"class":3546},[3536,9590,9591,9593,9595,9598],{"class":3538,"line":3610},[3536,9592,5206],{"class":3588},[3536,9594,3646],{"class":3546},[3536,9596,9597],{"class":3690},"\"[UoW Connection] З'єднання з БД встановлено\"",[3536,9599,3694],{"class":3546},[3536,9601,9602,9604,9607],{"class":3538,"line":3619},[3536,9603,4354],{"class":3584},[3536,9605,9606],{"class":3546},".in_transaction = ",[3536,9608,5201],{"class":3584},[3536,9610,9611],{"class":3538,"line":3627},[3536,9612,3560],{"emptyLinePlaceholder":3559},[3536,9614,9615,9617,9619,9621,9623],{"class":3538,"line":3633},[3536,9616,4334],{"class":3584},[3536,9618,5224],{"class":3588},[3536,9620,3646],{"class":3546},[3536,9622,4342],{"class":3649},[3536,9624,3733],{"class":3546},[3536,9626,9627,9629,9631,9634],{"class":3538,"line":3638},[3536,9628,5206],{"class":3588},[3536,9630,3646],{"class":3546},[3536,9632,9633],{"class":3690},"\"[UoW Connection] Транзакцію успішно зафіксовано (COMMIT)\"",[3536,9635,3694],{"class":3546},[3536,9637,9638,9640,9642],{"class":3538,"line":3656},[3536,9639,4354],{"class":3584},[3536,9641,9606],{"class":3546},[3536,9643,5295],{"class":3584},[3536,9645,9646],{"class":3538,"line":3662},[3536,9647,3560],{"emptyLinePlaceholder":3559},[3536,9649,9650,9652,9654,9656,9658],{"class":3538,"line":3671},[3536,9651,4334],{"class":3584},[3536,9653,5252],{"class":3588},[3536,9655,3646],{"class":3546},[3536,9657,4342],{"class":3649},[3536,9659,3733],{"class":3546},[3536,9661,9662,9664,9666,9669],{"class":3538,"line":3676},[3536,9663,5206],{"class":3588},[3536,9665,3646],{"class":3546},[3536,9667,9668],{"class":3690},"\"[UoW Connection] Помилка в транзакції! Зміни відкочено (ROLLBACK)\"",[3536,9670,3694],{"class":3546},[3536,9672,9673,9675,9677],{"class":3538,"line":3682},[3536,9674,4354],{"class":3584},[3536,9676,9606],{"class":3546},[3536,9678,5295],{"class":3584},[3536,9680,9681],{"class":3538,"line":3697},[3536,9682,3560],{"emptyLinePlaceholder":3559},[3536,9684,9685,9687,9689,9691,9693],{"class":3538,"line":3712},[3536,9686,4334],{"class":3584},[3536,9688,5280],{"class":3588},[3536,9690,3646],{"class":3546},[3536,9692,4342],{"class":3649},[3536,9694,3733],{"class":3546},[3536,9696,9697,9699,9701,9704],{"class":3538,"line":3721},[3536,9698,5206],{"class":3588},[3536,9700,3646],{"class":3546},[3536,9702,9703],{"class":3690},"\"[UoW Connection] З'єднання з БД закрито\"",[3536,9705,3694],{"class":3546},[3536,9707,9708],{"class":3538,"line":3730},[3536,9709,3560],{"emptyLinePlaceholder":3559},[3536,9711,9712],{"class":3538,"line":3736},[3536,9713,9714],{"class":3577},"# Клас Unit of Work\n",[3536,9716,9717,9719,9722],{"class":3538,"line":4052},[3536,9718,4319],{"class":3584},[3536,9720,9721],{"class":3820}," UnitOfWork",[3536,9723,3607],{"class":3546},[3536,9725,9726,9728,9730,9732,9734],{"class":3538,"line":4073},[3536,9727,4334],{"class":3584},[3536,9729,4337],{"class":3588},[3536,9731,3646],{"class":3546},[3536,9733,4342],{"class":3649},[3536,9735,3733],{"class":3546},[3536,9737,9738,9740],{"class":3538,"line":4105},[3536,9739,4354],{"class":3584},[3536,9741,9742],{"class":3546},".db = DatabaseConnection()\n",[3536,9744,9745],{"class":3538,"line":4110},[3536,9746,3560],{"emptyLinePlaceholder":3559},[3536,9748,9749,9751,9754,9756,9758],{"class":3538,"line":4504},[3536,9750,4334],{"class":3584},[3536,9752,9753],{"class":3588}," save_changes",[3536,9755,3646],{"class":3546},[3536,9757,4342],{"class":3649},[3536,9759,3733],{"class":3546},[3536,9761,9762,9764],{"class":3538,"line":4509},[3536,9763,4354],{"class":3584},[3536,9765,9766],{"class":3546},".db.commit()\n",[3536,9768,9769],{"class":3538,"line":4524},[3536,9770,3560],{"emptyLinePlaceholder":3559},[3536,9772,9773],{"class":3538,"line":4530},[3536,9774,9775],{"class":3577},"# Залежність для створення UoW з управлінням життєвим циклом\n",[3536,9777,9778,9780,9783],{"class":3538,"line":4542},[3536,9779,3585],{"class":3584},[3536,9781,9782],{"class":3588}," get_unit_of_work",[3536,9784,3592],{"class":3546},[3536,9786,9787],{"class":3538,"line":4548},[3536,9788,9789],{"class":3546},"    uow = UnitOfWork()\n",[3536,9791,9792,9794],{"class":3538,"line":4599},[3536,9793,3604],{"class":3542},[3536,9795,3607],{"class":3546},[3536,9797,9798,9800],{"class":3538,"line":4605},[3536,9799,3613],{"class":3542},[3536,9801,9802],{"class":3546}," uow\n",[3536,9804,9805],{"class":3538,"line":4616},[3536,9806,9807],{"class":3577},"        # Якщо ендпоінт завершився успішно — фіксуємо транзакцію\n",[3536,9809,9810],{"class":3538,"line":4621},[3536,9811,9812],{"class":3546},"        uow.save_changes()\n",[3536,9814,9815,9817,9819,9821],{"class":3538,"line":4648},[3536,9816,5365],{"class":3542},[3536,9818,5368],{"class":3820},[3536,9820,5371],{"class":3542},[3536,9822,5374],{"class":3546},[3536,9824,9825],{"class":3538,"line":5473},[3536,9826,9827],{"class":3577},"        # У разі виникнення будь-якої помилки — відкочуємо зміни\n",[3536,9829,9830],{"class":3538,"line":5481},[3536,9831,9832],{"class":3546},"        uow.db.rollback()\n",[3536,9834,9835,9837],{"class":3538,"line":5490},[3536,9836,5387],{"class":3542},[3536,9838,9839],{"class":3546}," e\n",[3536,9841,9842,9844],{"class":3538,"line":5501},[3536,9843,3622],{"class":3542},[3536,9845,3607],{"class":3546},[3536,9847,9848],{"class":3538,"line":5507},[3536,9849,9850],{"class":3577},"        # У будь-якому випадку закриваємо з'єднання\n",[3536,9852,9853],{"class":3538,"line":7106},[3536,9854,9855],{"class":3546},"        uow.db.close()\n",[3536,9857,9858],{"class":3538,"line":7111},[3536,9859,3560],{"emptyLinePlaceholder":3559},[3536,9861,9862],{"class":3538,"line":7119},[3536,9863,9864],{"class":3577},"# Сервіс бізнес-логіки\n",[3536,9866,9867,9869,9872],{"class":3538,"line":7124},[3536,9868,4319],{"class":3584},[3536,9870,9871],{"class":3820}," TaskService",[3536,9873,3607],{"class":3546},[3536,9875,9876,9878,9880,9882,9884,9886,9889],{"class":3538,"line":7130},[3536,9877,4334],{"class":3584},[3536,9879,4337],{"class":3588},[3536,9881,3646],{"class":3546},[3536,9883,4342],{"class":3649},[3536,9885,3824],{"class":3546},[3536,9887,9888],{"class":3649},"uow",[3536,9890,9891],{"class":3546},": UnitOfWork):\n",[3536,9893,9894,9896],{"class":3538,"line":7142},[3536,9895,4354],{"class":3584},[3536,9897,9898],{"class":3546},".uow = uow\n",[3536,9900,9901],{"class":3538,"line":7164},[3536,9902,3560],{"emptyLinePlaceholder":3559},[3536,9904,9905,9907,9910,9912,9914,9916,9919,9921,9923],{"class":3538,"line":7172},[3536,9906,4334],{"class":3584},[3536,9908,9909],{"class":3588}," create_task",[3536,9911,3646],{"class":3546},[3536,9913,4342],{"class":3649},[3536,9915,3824],{"class":3546},[3536,9917,9918],{"class":3649},"title",[3536,9920,3753],{"class":3546},[3536,9922,6108],{"class":3820},[3536,9924,3733],{"class":3546},[3536,9926,9927],{"class":3538,"line":7186},[3536,9928,9929],{"class":3577},"        # Імітуємо запис в БД через об'єкт UoW\n",[3536,9931,9932,9934,9936,9938,9941,9943,9945,9947,9950],{"class":3538,"line":7200},[3536,9933,5206],{"class":3588},[3536,9935,3646],{"class":3546},[3536,9937,4556],{"class":3584},[3536,9939,9940],{"class":3690},"\"[TaskService] Запис задачі '",[3536,9942,4562],{"class":3584},[3536,9944,9918],{"class":3546},[3536,9946,4568],{"class":3584},[3536,9948,9949],{"class":3690},"' в БД...\"",[3536,9951,3694],{"class":3546},[3536,9953,9954,9956,9959,9962,9965],{"class":3538,"line":7211},[3536,9955,4533],{"class":3542},[3536,9957,9958],{"class":3588}," len",[3536,9960,9961],{"class":3546},"(title) \u003C ",[3536,9963,9964],{"class":4477},"3",[3536,9966,3607],{"class":3546},[3536,9968,9970,9972,9975,9977,9980],{"class":3538,"line":9969},55,[3536,9971,6280],{"class":3542},[3536,9973,9974],{"class":3820}," ValueError",[3536,9976,3646],{"class":3546},[3536,9978,9979],{"class":3690},"\"Назва задачі занадто коротка\"",[3536,9981,3694],{"class":3546},[3536,9983,9985,9987,9989,9992,9995,9997,9999,10002],{"class":3538,"line":9984},56,[3536,9986,4076],{"class":3542},[3536,9988,3741],{"class":3546},[3536,9990,9991],{"class":3690},"\"title\"",[3536,9993,9994],{"class":3546},": title, ",[3536,9996,3750],{"class":3690},[3536,9998,3753],{"class":3546},[3536,10000,10001],{"class":3690},"\"saved\"",[3536,10003,3759],{"class":3546},[3536,10005,10007],{"class":3538,"line":10006},57,[3536,10008,3560],{"emptyLinePlaceholder":3559},[3536,10010,10012],{"class":3538,"line":10011},58,[3536,10013,10014],{"class":3577},"# Залежність для створення сервісу\n",[3536,10016,10018,10020,10023,10025,10027],{"class":3538,"line":10017},59,[3536,10019,3585],{"class":3584},[3536,10021,10022],{"class":3588}," get_task_service",[3536,10024,3646],{"class":3546},[3536,10026,9888],{"class":3649},[3536,10028,10029],{"class":3546},": UnitOfWork = Depends(get_unit_of_work)) -> TaskService:\n",[3536,10031,10033,10035],{"class":3538,"line":10032},60,[3536,10034,3665],{"class":3542},[3536,10036,10037],{"class":3546}," TaskService(uow)\n",[3536,10039,10041],{"class":3538,"line":10040},61,[3536,10042,3560],{"emptyLinePlaceholder":3559},[3536,10044,10046],{"class":3538,"line":10045},62,[3536,10047,10048],{"class":3577},"# Ендпоінт інжектує сервіс\n",[3536,10050,10052,10055,10057,10060],{"class":3538,"line":10051},63,[3536,10053,10054],{"class":3588},"@app.post",[3536,10056,3646],{"class":3546},[3536,10058,10059],{"class":3690},"\"\u002Ftasks\"",[3536,10061,3694],{"class":3546},[3536,10063,10065,10067,10069,10071,10073,10075,10077,10079,10081,10084],{"class":3538,"line":10064},64,[3536,10066,3700],{"class":3584},[3536,10068,3703],{"class":3584},[3536,10070,9909],{"class":3588},[3536,10072,3646],{"class":3546},[3536,10074,9918],{"class":3649},[3536,10076,3753],{"class":3546},[3536,10078,6108],{"class":3820},[3536,10080,3824],{"class":3546},[3536,10082,10083],{"class":3649},"service",[3536,10085,10086],{"class":3546},": TaskService = Depends(get_task_service)):\n",[3536,10088,10090,10092],{"class":3538,"line":10089},65,[3536,10091,3604],{"class":3542},[3536,10093,3607],{"class":3546},[3536,10095,10097,10099],{"class":3538,"line":10096},66,[3536,10098,4076],{"class":3542},[3536,10100,10101],{"class":3546}," service.create_task(title)\n",[3536,10103,10105,10107,10109,10111],{"class":3538,"line":10104},67,[3536,10106,5365],{"class":3542},[3536,10108,9974],{"class":3820},[3536,10110,5371],{"class":3542},[3536,10112,5374],{"class":3546},[3536,10114,10116,10118],{"class":3538,"line":10115},68,[3536,10117,5387],{"class":3542},[3536,10119,5478],{"class":3546},[3536,10121,10123,10125],{"class":3538,"line":10122},69,[3536,10124,5484],{"class":3649},[3536,10126,10127],{"class":3546},"=status.HTTP_422_UNPROCESSABLE_ENTITY, \n",[3536,10129,10131,10133,10135,10137],{"class":3538,"line":10130},70,[3536,10132,5493],{"class":3649},[3536,10134,4474],{"class":3546},[3536,10136,6108],{"class":3820},[3536,10138,10139],{"class":3546},"(e)\n",[3536,10141,10143],{"class":3538,"line":10142},71,[3536,10144,5504],{"class":3546},[3417,10146,10148],{"id":10147},"запуск-та-тестування-unit-of-work-транзакції","Запуск та тестування Unit of Work транзакції",[3385,10150,6463],{},[3523,10152,10153,10167],{},[3526,10154,10156],{"className":4657,"code":10155,"filename":6469,"language":4660,"meta":3532,"style":3532},"uvicorn unit_of_work_demo:app --reload\n",[3395,10157,10158],{"__ignoreMap":3532},[3536,10159,10160,10162,10165],{"class":3538,"line":3539},[3536,10161,6476],{"class":3588},[3536,10163,10164],{"class":3690}," unit_of_work_demo:app",[3536,10166,6482],{"class":3584},[3526,10168,10169],{"className":6485,"code":10155,"filename":6486,"language":6487,"meta":3532,"style":3532},[3395,10170,10171],{"__ignoreMap":3532},[3536,10172,10173],{"class":3538,"line":3539},[3536,10174,10155],{"class":3546},[4912,10176,10178],{"id":10177},"сценарій-1-успішний-запит-транзакція-підтверджується","Сценарій 1: Успішний запит (Транзакція підтверджується)",[3385,10180,10181],{},"Надішлемо коректну назву задачі:",[3523,10183,10184,10201],{},[3526,10185,10187],{"className":4657,"code":10186,"filename":6469,"language":4660,"meta":3532,"style":3532},"curl -X POST \"http:\u002F\u002F127.0.0.1:8000\u002Ftasks?title=Написати%20тести\"\n",[3395,10188,10189],{"__ignoreMap":3532},[3536,10190,10191,10193,10195,10198],{"class":3538,"line":3539},[3536,10192,6518],{"class":3588},[3536,10194,6521],{"class":3584},[3536,10196,10197],{"class":3690}," POST",[3536,10199,10200],{"class":3690}," \"http:\u002F\u002F127.0.0.1:8000\u002Ftasks?title=Написати%20тести\"\n",[3526,10202,10204],{"className":6485,"code":10203,"filename":6486,"language":6487,"meta":3532,"style":3532},"curl.exe -X POST \"http:\u002F\u002F127.0.0.1:8000\u002Ftasks?title=Написати%20тести\"\n",[3395,10205,10206],{"__ignoreMap":3532},[3536,10207,10208,10210,10213],{"class":3538,"line":3539},[3536,10209,6537],{"class":3588},[3536,10211,10212],{"class":3546}," -X POST ",[3536,10214,10215],{"class":3690},"\"http:\u002F\u002F127.0.0.1:8000\u002Ftasks?title=Написати%20тести\"\n",[4672,10217,10219,10225,10228,10231],{":cursor":4674,"title":10218},"Консольний вивід успішного UoW",[4677,10220,10222],{"className":10221},[3538],[3536,10223,6567],{"className":10224},[6566,4688],[4677,10226,6571],{"className":10227},[3538],[4677,10229],{"className":10230},[3538],[4677,10232,10234],{"className":10233},[3538],"{\"title\":\"Написати тести\",\"status\":\"saved\"}",[3385,10236,10237],{},"А на сервері в терміналі ми побачимо наступні лог-повідомлення, що підтверджують роботу UoW:",[4672,10239,10241,10245,10249,10253],{":cursor":4674,"title":10240},"Логи UoW успіху на сервері",[4677,10242,10244],{"className":10243},[3538],"[UoW Connection] З'єднання з БД встановлено",[4677,10246,10248],{"className":10247},[3538],"[TaskService] Запис задачі 'Написати тести' в БД...",[4677,10250,10252],{"className":10251},[3538],"[UoW Connection] Транзакцію успішно зафіксовано (COMMIT)",[4677,10254,10256],{"className":10255},[3538],"[UoW Connection] З'єднання з БД закрито",[4912,10258,10260],{"id":10259},"сценарій-2-виникнення-помилки-валідації-транзакція-відкочується","Сценарій 2: Виникнення помилки валідації (Транзакція відкочується)",[3385,10262,10263],{},"Надішлемо назву довжиною менше 3 символів:",[3523,10265,10266,10282],{},[3526,10267,10269],{"className":4657,"code":10268,"filename":6469,"language":4660,"meta":3532,"style":3532},"curl -X POST \"http:\u002F\u002F127.0.0.1:8000\u002Ftasks?title=Ok\"\n",[3395,10270,10271],{"__ignoreMap":3532},[3536,10272,10273,10275,10277,10279],{"class":3538,"line":3539},[3536,10274,6518],{"class":3588},[3536,10276,6521],{"class":3584},[3536,10278,10197],{"class":3690},[3536,10280,10281],{"class":3690}," \"http:\u002F\u002F127.0.0.1:8000\u002Ftasks?title=Ok\"\n",[3526,10283,10285],{"className":6485,"code":10284,"filename":6486,"language":6487,"meta":3532,"style":3532},"curl.exe -X POST \"http:\u002F\u002F127.0.0.1:8000\u002Ftasks?title=Ok\"\n",[3395,10286,10287],{"__ignoreMap":3532},[3536,10288,10289,10291,10293],{"class":3538,"line":3539},[3536,10290,6537],{"class":3588},[3536,10292,10212],{"class":3546},[3536,10294,10295],{"class":3690},"\"http:\u002F\u002F127.0.0.1:8000\u002Ftasks?title=Ok\"\n",[4672,10297,10299,10306,10309,10312],{":cursor":4674,"title":10298},"Консольний вивід помилки UoW",[4677,10300,10302],{"className":10301},[3538],[3536,10303,10305],{"className":10304},[6637,4688],"HTTP\u002F1.1 422 Unprocessable Entity",[4677,10307,6571],{"className":10308},[3538],[4677,10310],{"className":10311},[3538],[4677,10313,10315],{"className":10314},[3538],"{\"detail\":\"Назва задачі занадто коротка\"}",[3385,10317,10318],{},"Логи на сервері покажуть автоматичне скасування транзакції:",[4672,10320,10322,10325,10329,10333],{":cursor":4674,"title":10321},"Логи UoW помилки на сервері",[4677,10323,10244],{"className":10324},[3538],[4677,10326,10328],{"className":10327},[3538],"[TaskService] Запис задачі 'Ok' в БД...",[4677,10330,10332],{"className":10331},[3538],"[UoW Connection] Помилка в транзакції! Зміни відкочено (ROLLBACK)",[4677,10334,10256],{"className":10335},[3538],[3406,10337],{},[3409,10339,10341],{"id":10340},"частина-v-практичні-завдання-та-інтеграція-в-taskforge","Частина V: Практичні завдання та інтеграція в TaskForge",[3385,10343,10344],{},"Для закріплення матеріалу розберемо практичні вправи різного рівня складності: від базових залежностей пагінації до побудови багаторівневого DI-контейнера для Clean Architecture, а також розробимо самодостатній мікропроєкт сейфу документів та рефакторинг TaskForge.",[3406,10346],{},[3417,10348,10350],{"id":10349},"практичні-вправи","Практичні вправи",[4912,10352,10354],{"id":10353},"вправа-1-залежність-для-пагінації-pagination-dependency","Вправа 1: Залежність для пагінації (Pagination Dependency)",[3385,10356,10357,10360,10361,10363,10364,5568,10366,10368,10369,10371,10372,10374,10375,3398],{},[3392,10358,10359],{},"Завдання",": Написати клас-залежність ",[3395,10362,6023],{},", яка зчитує необов'язкові query-параметри ",[3395,10365,5911],{},[3395,10367,5924],{},". Додати валідацію: ",[3395,10370,5911],{}," має бути $\\ge 0$, а ",[3395,10373,5924],{}," — у діапазоні від $1$ до $100$. Якщо валідація провалена, повернути помилку ",[3395,10376,10377],{},"400 Bad Request",[3526,10379,10382],{"className":3528,"code":10380,"filename":10381,"language":3531,"meta":3532,"style":3532},"# pagination_dependency.py\nfrom fastapi import FastAPI, Depends, HTTPException, status\n\napp = FastAPI()\n\nclass PaginationParams:\n    def __init__(self, skip: int = 0, limit: int = 10):\n        if skip \u003C 0:\n            raise HTTPException(\n                status_code=status.HTTP_400_BAD_REQUEST,\n                detail=\"Параметр skip не може бути від'ємним\"\n            )\n        if limit \u003C 1 or limit > 100:\n            raise HTTPException(\n                status_code=status.HTTP_400_BAD_REQUEST,\n                detail=\"Параметр limit має бути в межах від 1 до 100\"\n            )\n        self.skip = skip\n        self.limit = limit\n\n@app.get(\"\u002Fitems\")\nasync def read_items(params: PaginationParams = Depends()):\n    return {\"skip\": params.skip, \"limit\": params.limit}\n","pagination_dependency.py",[3395,10383,10384,10389,10399,10403,10407,10411,10419,10455,10466,10472,10478,10487,10491,10511,10517,10523,10532,10536,10542,10548,10552,10562,10578],{"__ignoreMap":3532},[3536,10385,10386],{"class":3538,"line":3539},[3536,10387,10388],{"class":3577},"# pagination_dependency.py\n",[3536,10390,10391,10393,10395,10397],{"class":3538,"line":3556},[3536,10392,3543],{"class":3542},[3536,10394,3547],{"class":3546},[3536,10396,3550],{"class":3542},[3536,10398,5153],{"class":3546},[3536,10400,10401],{"class":3538,"line":3563},[3536,10402,3560],{"emptyLinePlaceholder":3559},[3536,10404,10405],{"class":3538,"line":3569},[3536,10406,3566],{"class":3546},[3536,10408,10409],{"class":3538,"line":3574},[3536,10410,3560],{"emptyLinePlaceholder":3559},[3536,10412,10413,10415,10417],{"class":3538,"line":3581},[3536,10414,4319],{"class":3584},[3536,10416,5894],{"class":3820},[3536,10418,3607],{"class":3546},[3536,10420,10421,10423,10425,10427,10429,10431,10433,10435,10437,10439,10441,10443,10445,10447,10449,10451,10453],{"class":3538,"line":3595},[3536,10422,4334],{"class":3584},[3536,10424,4337],{"class":3588},[3536,10426,3646],{"class":3546},[3536,10428,4342],{"class":3649},[3536,10430,3824],{"class":3546},[3536,10432,5911],{"class":3649},[3536,10434,3753],{"class":3546},[3536,10436,5446],{"class":3820},[3536,10438,3783],{"class":3546},[3536,10440,4478],{"class":4477},[3536,10442,3824],{"class":3546},[3536,10444,5924],{"class":3649},[3536,10446,3753],{"class":3546},[3536,10448,5446],{"class":3820},[3536,10450,3783],{"class":3546},[3536,10452,5933],{"class":4477},[3536,10454,3733],{"class":3546},[3536,10456,10457,10459,10462,10464],{"class":3538,"line":3601},[3536,10458,4533],{"class":3542},[3536,10460,10461],{"class":3546}," skip \u003C ",[3536,10463,4478],{"class":4477},[3536,10465,3607],{"class":3546},[3536,10467,10468,10470],{"class":3538,"line":3610},[3536,10469,6280],{"class":3542},[3536,10471,5478],{"class":3546},[3536,10473,10474,10476],{"class":3538,"line":3619},[3536,10475,6287],{"class":3649},[3536,10477,5487],{"class":3546},[3536,10479,10480,10482,10484],{"class":3538,"line":3627},[3536,10481,6295],{"class":3649},[3536,10483,4474],{"class":3546},[3536,10485,10486],{"class":3690},"\"Параметр skip не може бути від'ємним\"\n",[3536,10488,10489],{"class":3538,"line":3633},[3536,10490,6318],{"class":3546},[3536,10492,10493,10495,10498,10500,10503,10506,10509],{"class":3538,"line":3638},[3536,10494,4533],{"class":3542},[3536,10496,10497],{"class":3546}," limit \u003C ",[3536,10499,4611],{"class":4477},[3536,10501,10502],{"class":3584}," or",[3536,10504,10505],{"class":3546}," limit > ",[3536,10507,10508],{"class":4477},"100",[3536,10510,3607],{"class":3546},[3536,10512,10513,10515],{"class":3538,"line":3656},[3536,10514,6280],{"class":3542},[3536,10516,5478],{"class":3546},[3536,10518,10519,10521],{"class":3538,"line":3662},[3536,10520,6287],{"class":3649},[3536,10522,5487],{"class":3546},[3536,10524,10525,10527,10529],{"class":3538,"line":3671},[3536,10526,6295],{"class":3649},[3536,10528,4474],{"class":3546},[3536,10530,10531],{"class":3690},"\"Параметр limit має бути в межах від 1 до 100\"\n",[3536,10533,10534],{"class":3538,"line":3676},[3536,10535,6318],{"class":3546},[3536,10537,10538,10540],{"class":3538,"line":3682},[3536,10539,4354],{"class":3584},[3536,10541,5942],{"class":3546},[3536,10543,10544,10546],{"class":3538,"line":3697},[3536,10545,4354],{"class":3584},[3536,10547,5949],{"class":3546},[3536,10549,10550],{"class":3538,"line":3712},[3536,10551,3560],{"emptyLinePlaceholder":3559},[3536,10553,10554,10556,10558,10560],{"class":3538,"line":3721},[3536,10555,3685],{"class":3588},[3536,10557,3646],{"class":3546},[3536,10559,5962],{"class":3690},[3536,10561,3694],{"class":3546},[3536,10563,10564,10566,10568,10571,10573,10575],{"class":3538,"line":3730},[3536,10565,3700],{"class":3584},[3536,10567,3703],{"class":3584},[3536,10569,10570],{"class":3588}," read_items",[3536,10572,3646],{"class":3546},[3536,10574,5978],{"class":3649},[3536,10576,10577],{"class":3546},": PaginationParams = Depends()):\n",[3536,10579,10580,10582,10584,10586,10588,10590],{"class":3538,"line":3736},[3536,10581,3665],{"class":3542},[3536,10583,3741],{"class":3546},[3536,10585,6000],{"class":3690},[3536,10587,6003],{"class":3546},[3536,10589,6006],{"class":3690},[3536,10591,6009],{"class":3546},[4912,10593,10595],{"id":10594},"вправа-2-ланцюжок-отримання-поточного-користувача-current-user-resolution","Вправа 2: Ланцюжок отримання поточного користувача (Current User Resolution)",[3385,10597,10598,10600],{},[3392,10599,10359],{},": Побудувати ланцюжок залежностей для авторизації користувача:",[4852,10602,10603,10620],{},[3432,10604,10605,10608,10609,10612,10613,10616,10617,3398],{},[3395,10606,10607],{},"get_token"," — зчитує заголовок ",[3395,10610,10611],{},"Authorization",". Перевіряє формат ",[3395,10614,10615],{},"Bearer \u003Ctoken>",". Якщо формат невірний, повертає ",[3395,10618,10619],{},"401 Unauthorized",[3432,10621,10622,10624,10625,10627,10628,3398],{},[3395,10623,4925],{}," — залежить від ",[3395,10626,10607],{},". Розкодовує токен (імітація) та перевіряє користувача у базі даних. Якщо користувача не знайдено — повертає ",[3395,10629,10630],{},"404 Not Found",[3526,10632,10635],{"className":3528,"code":10633,"filename":10634,"language":3531,"meta":3532,"style":3532},"# user_resolution.py\nfrom fastapi import FastAPI, Depends, Header, HTTPException, status\n\napp = FastAPI()\n\n# База користувачів у пам'яті\nMOCK_USERS = {\n    \"token_user1\": {\"username\": \"developer_ivan\", \"email\": \"ivan@example.com\"},\n    \"token_user2\": {\"username\": \"tester_anna\", \"email\": \"anna@example.com\"},\n}\n\ndef get_token(authorization: str | None = Header(None, description=\"Bearer токен авторизації\")) -> str:\n    if not authorization or not authorization.startswith(\"Bearer \"):\n        raise HTTPException(\n            status_code=status.HTTP_401_UNAUTHORIZED,\n            detail=\"Відсутній або невалідний заголовок Authorization\"\n        )\n    return authorization.split(\" \")[1]\n\ndef get_current_user(token: str = Depends(get_token)) -> dict:\n    if token not in MOCK_USERS:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=\"Користувача за цим токеном не знайдено\"\n        )\n    return MOCK_USERS[token]\n\n@app.get(\"\u002Fusers\u002Fme\")\nasync def read_current_user(user: dict = Depends(get_current_user)):\n    return user\n","user_resolution.py",[3395,10636,10637,10642,10652,10656,10660,10664,10669,10674,10700,10725,10729,10733,10772,10794,10800,10806,10815,10819,10836,10840,10862,10876,10882,10889,10898,10902,10909,10913,10924,10944],{"__ignoreMap":3532},[3536,10638,10639],{"class":3538,"line":3539},[3536,10640,10641],{"class":3577},"# user_resolution.py\n",[3536,10643,10644,10646,10648,10650],{"class":3538,"line":3556},[3536,10645,3543],{"class":3542},[3536,10647,3547],{"class":3546},[3536,10649,3550],{"class":3542},[3536,10651,6746],{"class":3546},[3536,10653,10654],{"class":3538,"line":3563},[3536,10655,3560],{"emptyLinePlaceholder":3559},[3536,10657,10658],{"class":3538,"line":3569},[3536,10659,3566],{"class":3546},[3536,10661,10662],{"class":3538,"line":3574},[3536,10663,3560],{"emptyLinePlaceholder":3559},[3536,10665,10666],{"class":3538,"line":3581},[3536,10667,10668],{"class":3577},"# База користувачів у пам'яті\n",[3536,10670,10671],{"class":3538,"line":3595},[3536,10672,10673],{"class":3546},"MOCK_USERS = {\n",[3536,10675,10676,10679,10681,10683,10685,10688,10690,10693,10695,10698],{"class":3538,"line":3601},[3536,10677,10678],{"class":3690},"    \"token_user1\"",[3536,10680,6776],{"class":3546},[3536,10682,7536],{"class":3690},[3536,10684,3753],{"class":3546},[3536,10686,10687],{"class":3690},"\"developer_ivan\"",[3536,10689,3824],{"class":3546},[3536,10691,10692],{"class":3690},"\"email\"",[3536,10694,3753],{"class":3546},[3536,10696,10697],{"class":3690},"\"ivan@example.com\"",[3536,10699,6796],{"class":3546},[3536,10701,10702,10705,10707,10709,10711,10714,10716,10718,10720,10723],{"class":3538,"line":3610},[3536,10703,10704],{"class":3690},"    \"token_user2\"",[3536,10706,6776],{"class":3546},[3536,10708,7536],{"class":3690},[3536,10710,3753],{"class":3546},[3536,10712,10713],{"class":3690},"\"tester_anna\"",[3536,10715,3824],{"class":3546},[3536,10717,10692],{"class":3690},[3536,10719,3753],{"class":3546},[3536,10721,10722],{"class":3690},"\"anna@example.com\"",[3536,10724,6796],{"class":3546},[3536,10726,10727],{"class":3538,"line":3619},[3536,10728,3759],{"class":3546},[3536,10730,10731],{"class":3538,"line":3627},[3536,10732,3560],{"emptyLinePlaceholder":3559},[3536,10734,10735,10737,10740,10742,10745,10747,10749,10751,10753,10755,10757,10759,10761,10763,10766,10768,10770],{"class":3538,"line":3633},[3536,10736,3585],{"class":3584},[3536,10738,10739],{"class":3588}," get_token",[3536,10741,3646],{"class":3546},[3536,10743,10744],{"class":3649},"authorization",[3536,10746,3753],{"class":3546},[3536,10748,6108],{"class":3820},[3536,10750,7336],{"class":3546},[3536,10752,5329],{"class":3584},[3536,10754,7341],{"class":3546},[3536,10756,5329],{"class":3584},[3536,10758,3824],{"class":3546},[3536,10760,6855],{"class":3649},[3536,10762,4474],{"class":3546},[3536,10764,10765],{"class":3690},"\"Bearer токен авторизації\"",[3536,10767,6863],{"class":3546},[3536,10769,6108],{"class":3820},[3536,10771,3607],{"class":3546},[3536,10773,10774,10776,10778,10781,10784,10786,10789,10792],{"class":3538,"line":3638},[3536,10775,5458],{"class":3542},[3536,10777,6874],{"class":3584},[3536,10779,10780],{"class":3546}," authorization ",[3536,10782,10783],{"class":3584},"or",[3536,10785,6874],{"class":3584},[3536,10787,10788],{"class":3546}," authorization.startswith(",[3536,10790,10791],{"class":3690},"\"Bearer \"",[3536,10793,3733],{"class":3546},[3536,10795,10796,10798],{"class":3538,"line":3656},[3536,10797,5387],{"class":3542},[3536,10799,5478],{"class":3546},[3536,10801,10802,10804],{"class":3538,"line":3662},[3536,10803,5484],{"class":3649},[3536,10805,6895],{"class":3546},[3536,10807,10808,10810,10812],{"class":3538,"line":3671},[3536,10809,5493],{"class":3649},[3536,10811,4474],{"class":3546},[3536,10813,10814],{"class":3690},"\"Відсутній або невалідний заголовок Authorization\"\n",[3536,10816,10817],{"class":3538,"line":3676},[3536,10818,5504],{"class":3546},[3536,10820,10821,10823,10826,10829,10832,10834],{"class":3538,"line":3682},[3536,10822,3665],{"class":3542},[3536,10824,10825],{"class":3546}," authorization.split(",[3536,10827,10828],{"class":3690},"\" \"",[3536,10830,10831],{"class":3546},")[",[3536,10833,4611],{"class":4477},[3536,10835,3890],{"class":3546},[3536,10837,10838],{"class":3538,"line":3697},[3536,10839,3560],{"emptyLinePlaceholder":3559},[3536,10841,10842,10844,10846,10848,10851,10853,10855,10858,10860],{"class":3538,"line":3712},[3536,10843,3585],{"class":3584},[3536,10845,3643],{"class":3588},[3536,10847,3646],{"class":3546},[3536,10849,10850],{"class":3649},"token",[3536,10852,3753],{"class":3546},[3536,10854,6108],{"class":3820},[3536,10856,10857],{"class":3546}," = Depends(get_token)) -> ",[3536,10859,7013],{"class":3820},[3536,10861,3607],{"class":3546},[3536,10863,10864,10866,10869,10871,10873],{"class":3538,"line":3721},[3536,10865,5458],{"class":3542},[3536,10867,10868],{"class":3546}," token ",[3536,10870,6266],{"class":3584},[3536,10872,6269],{"class":3584},[3536,10874,10875],{"class":3546}," MOCK_USERS:\n",[3536,10877,10878,10880],{"class":3538,"line":3730},[3536,10879,5387],{"class":3542},[3536,10881,5478],{"class":3546},[3536,10883,10884,10886],{"class":3538,"line":3736},[3536,10885,5484],{"class":3649},[3536,10887,10888],{"class":3546},"=status.HTTP_404_NOT_FOUND,\n",[3536,10890,10891,10893,10895],{"class":3538,"line":4052},[3536,10892,5493],{"class":3649},[3536,10894,4474],{"class":3546},[3536,10896,10897],{"class":3690},"\"Користувача за цим токеном не знайдено\"\n",[3536,10899,10900],{"class":3538,"line":4073},[3536,10901,5504],{"class":3546},[3536,10903,10904,10906],{"class":3538,"line":4105},[3536,10905,3665],{"class":3542},[3536,10907,10908],{"class":3546}," MOCK_USERS[token]\n",[3536,10910,10911],{"class":3538,"line":4110},[3536,10912,3560],{"emptyLinePlaceholder":3559},[3536,10914,10915,10917,10919,10922],{"class":3538,"line":4504},[3536,10916,3685],{"class":3588},[3536,10918,3646],{"class":3546},[3536,10920,10921],{"class":3690},"\"\u002Fusers\u002Fme\"",[3536,10923,3694],{"class":3546},[3536,10925,10926,10928,10930,10933,10935,10937,10939,10941],{"class":3538,"line":4509},[3536,10927,3700],{"class":3584},[3536,10929,3703],{"class":3584},[3536,10931,10932],{"class":3588}," read_current_user",[3536,10934,3646],{"class":3546},[3536,10936,4090],{"class":3649},[3536,10938,3753],{"class":3546},[3536,10940,7013],{"class":3820},[3536,10942,10943],{"class":3546}," = Depends(get_current_user)):\n",[3536,10945,10946,10948],{"class":3538,"line":4524},[3536,10947,3665],{"class":3542},[3536,10949,6325],{"class":3546},[4912,10951,10953],{"id":10952},"вправа-3-побудова-архітектури-di-контейнера-для-тестування-di-container-architecture","Вправа 3: Побудова архітектури DI-контейнера для тестування (DI-Container Architecture)",[3385,10955,10956,10958,10959,10962,10963,10962,10966,10968],{},[3392,10957,10359],{},": Реалізувати трирівневу Clean Architecture за допомогою DI: ",[3395,10960,10961],{},"DatabaseConnection"," $\\to$ ",[3395,10964,10965],{},"UserRepository",[3395,10967,3852],{},". Забезпечити можливість мокінгу бази даних для тестів без зміни бізнес-логіки.",[3526,10970,10973],{"className":3528,"code":10971,"filename":10972,"language":3531,"meta":3532,"style":3532},"# clean_di_container.py\nfrom fastapi import FastAPI, Depends\n\napp = FastAPI()\n\n# 1. Низький рівень — Підключення до БД\nclass DbConnection:\n    def execute_query(self) -> str:\n        return \"Дані з реальної PostgreSQL бази\"\n\ndef get_db_connection() -> DbConnection:\n    return DbConnection()\n\n# 2. Середній рівень — Репозиторій\nclass UserRepository:\n    def __init__(self, db: DbConnection):\n        self.db = db\n\n    def fetch_user_data(self) -> str:\n        return f\"Репозиторій отримав: {self.db.execute_query()}\"\n\ndef get_user_repository(db: DbConnection = Depends(get_db_connection)) -> UserRepository:\n    return UserRepository(db)\n\n# 3. Високий рівень — Сервіс бізнес-логіки\nclass UserService:\n    def __init__(self, repo: UserRepository):\n        self.repo = repo\n\n    def get_profile(self) -> dict:\n        return {\"profile\": self.repo.fetch_user_data(), \"status\": \"active\"}\n\ndef get_user_service(repo: UserRepository = Depends(get_user_repository)) -> UserService:\n    return UserService(repo)\n\n# Ендпоінт інжектує тільки UserService\n@app.get(\"\u002Fprofile\")\nasync def profile(service: UserService = Depends(get_user_service)):\n    return service.get_profile()\n","clean_di_container.py",[3395,10974,10975,10980,10990,10994,10998,11002,11007,11016,11033,11040,11044,11054,11061,11065,11070,11079,11096,11103,11107,11124,11143,11147,11161,11168,11172,11177,11186,11204,11211,11215,11232,11256,11260,11274,11281,11285,11290,11300,11316],{"__ignoreMap":3532},[3536,10976,10977],{"class":3538,"line":3539},[3536,10978,10979],{"class":3577},"# clean_di_container.py\n",[3536,10981,10982,10984,10986,10988],{"class":3538,"line":3556},[3536,10983,3543],{"class":3542},[3536,10985,3547],{"class":3546},[3536,10987,3550],{"class":3542},[3536,10989,3553],{"class":3546},[3536,10991,10992],{"class":3538,"line":3563},[3536,10993,3560],{"emptyLinePlaceholder":3559},[3536,10995,10996],{"class":3538,"line":3569},[3536,10997,3566],{"class":3546},[3536,10999,11000],{"class":3538,"line":3574},[3536,11001,3560],{"emptyLinePlaceholder":3559},[3536,11003,11004],{"class":3538,"line":3581},[3536,11005,11006],{"class":3577},"# 1. Низький рівень — Підключення до БД\n",[3536,11008,11009,11011,11014],{"class":3538,"line":3595},[3536,11010,4319],{"class":3584},[3536,11012,11013],{"class":3820}," DbConnection",[3536,11015,3607],{"class":3546},[3536,11017,11018,11020,11023,11025,11027,11029,11031],{"class":3538,"line":3601},[3536,11019,4334],{"class":3584},[3536,11021,11022],{"class":3588}," execute_query",[3536,11024,3646],{"class":3546},[3536,11026,4342],{"class":3649},[3536,11028,7021],{"class":3546},[3536,11030,6108],{"class":3820},[3536,11032,3607],{"class":3546},[3536,11034,11035,11037],{"class":3538,"line":3610},[3536,11036,4076],{"class":3542},[3536,11038,11039],{"class":3690}," \"Дані з реальної PostgreSQL бази\"\n",[3536,11041,11042],{"class":3538,"line":3619},[3536,11043,3560],{"emptyLinePlaceholder":3559},[3536,11045,11046,11048,11051],{"class":3538,"line":3627},[3536,11047,3585],{"class":3584},[3536,11049,11050],{"class":3588}," get_db_connection",[3536,11052,11053],{"class":3546},"() -> DbConnection:\n",[3536,11055,11056,11058],{"class":3538,"line":3633},[3536,11057,3665],{"class":3542},[3536,11059,11060],{"class":3546}," DbConnection()\n",[3536,11062,11063],{"class":3538,"line":3638},[3536,11064,3560],{"emptyLinePlaceholder":3559},[3536,11066,11067],{"class":3538,"line":3656},[3536,11068,11069],{"class":3577},"# 2. Середній рівень — Репозиторій\n",[3536,11071,11072,11074,11077],{"class":3538,"line":3662},[3536,11073,4319],{"class":3584},[3536,11075,11076],{"class":3820}," UserRepository",[3536,11078,3607],{"class":3546},[3536,11080,11081,11083,11085,11087,11089,11091,11093],{"class":3538,"line":3671},[3536,11082,4334],{"class":3584},[3536,11084,4337],{"class":3588},[3536,11086,3646],{"class":3546},[3536,11088,4342],{"class":3649},[3536,11090,3824],{"class":3546},[3536,11092,3650],{"class":3649},[3536,11094,11095],{"class":3546},": DbConnection):\n",[3536,11097,11098,11100],{"class":3538,"line":3676},[3536,11099,4354],{"class":3584},[3536,11101,11102],{"class":3546},".db = db\n",[3536,11104,11105],{"class":3538,"line":3682},[3536,11106,3560],{"emptyLinePlaceholder":3559},[3536,11108,11109,11111,11114,11116,11118,11120,11122],{"class":3538,"line":3697},[3536,11110,4334],{"class":3584},[3536,11112,11113],{"class":3588}," fetch_user_data",[3536,11115,3646],{"class":3546},[3536,11117,4342],{"class":3649},[3536,11119,7021],{"class":3546},[3536,11121,6108],{"class":3820},[3536,11123,3607],{"class":3546},[3536,11125,11126,11128,11131,11134,11136,11139,11141],{"class":3538,"line":3712},[3536,11127,4076],{"class":3542},[3536,11129,11130],{"class":3584}," f",[3536,11132,11133],{"class":3690},"\"Репозиторій отримав: ",[3536,11135,6305],{"class":3584},[3536,11137,11138],{"class":3546},".db.execute_query()",[3536,11140,4568],{"class":3584},[3536,11142,6313],{"class":3690},[3536,11144,11145],{"class":3538,"line":3721},[3536,11146,3560],{"emptyLinePlaceholder":3559},[3536,11148,11149,11151,11154,11156,11158],{"class":3538,"line":3730},[3536,11150,3585],{"class":3584},[3536,11152,11153],{"class":3588}," get_user_repository",[3536,11155,3646],{"class":3546},[3536,11157,3650],{"class":3649},[3536,11159,11160],{"class":3546},": DbConnection = Depends(get_db_connection)) -> UserRepository:\n",[3536,11162,11163,11165],{"class":3538,"line":3736},[3536,11164,3665],{"class":3542},[3536,11166,11167],{"class":3546}," UserRepository(db)\n",[3536,11169,11170],{"class":3538,"line":4052},[3536,11171,3560],{"emptyLinePlaceholder":3559},[3536,11173,11174],{"class":3538,"line":4073},[3536,11175,11176],{"class":3577},"# 3. Високий рівень — Сервіс бізнес-логіки\n",[3536,11178,11179,11181,11184],{"class":3538,"line":4105},[3536,11180,4319],{"class":3584},[3536,11182,11183],{"class":3820}," UserService",[3536,11185,3607],{"class":3546},[3536,11187,11188,11190,11192,11194,11196,11198,11201],{"class":3538,"line":4110},[3536,11189,4334],{"class":3584},[3536,11191,4337],{"class":3588},[3536,11193,3646],{"class":3546},[3536,11195,4342],{"class":3649},[3536,11197,3824],{"class":3546},[3536,11199,11200],{"class":3649},"repo",[3536,11202,11203],{"class":3546},": UserRepository):\n",[3536,11205,11206,11208],{"class":3538,"line":4504},[3536,11207,4354],{"class":3584},[3536,11209,11210],{"class":3546},".repo = repo\n",[3536,11212,11213],{"class":3538,"line":4509},[3536,11214,3560],{"emptyLinePlaceholder":3559},[3536,11216,11217,11219,11222,11224,11226,11228,11230],{"class":3538,"line":4524},[3536,11218,4334],{"class":3584},[3536,11220,11221],{"class":3588}," get_profile",[3536,11223,3646],{"class":3546},[3536,11225,4342],{"class":3649},[3536,11227,7021],{"class":3546},[3536,11229,7013],{"class":3820},[3536,11231,3607],{"class":3546},[3536,11233,11234,11236,11238,11241,11243,11245,11248,11250,11252,11254],{"class":3538,"line":4530},[3536,11235,4076],{"class":3542},[3536,11237,3741],{"class":3546},[3536,11239,11240],{"class":3690},"\"profile\"",[3536,11242,3753],{"class":3546},[3536,11244,4342],{"class":3584},[3536,11246,11247],{"class":3546},".repo.fetch_user_data(), ",[3536,11249,3750],{"class":3690},[3536,11251,3753],{"class":3546},[3536,11253,3756],{"class":3690},[3536,11255,3759],{"class":3546},[3536,11257,11258],{"class":3538,"line":4542},[3536,11259,3560],{"emptyLinePlaceholder":3559},[3536,11261,11262,11264,11267,11269,11271],{"class":3538,"line":4548},[3536,11263,3585],{"class":3584},[3536,11265,11266],{"class":3588}," get_user_service",[3536,11268,3646],{"class":3546},[3536,11270,11200],{"class":3649},[3536,11272,11273],{"class":3546},": UserRepository = Depends(get_user_repository)) -> UserService:\n",[3536,11275,11276,11278],{"class":3538,"line":4599},[3536,11277,3665],{"class":3542},[3536,11279,11280],{"class":3546}," UserService(repo)\n",[3536,11282,11283],{"class":3538,"line":4605},[3536,11284,3560],{"emptyLinePlaceholder":3559},[3536,11286,11287],{"class":3538,"line":4616},[3536,11288,11289],{"class":3577},"# Ендпоінт інжектує тільки UserService\n",[3536,11291,11292,11294,11296,11298],{"class":3538,"line":4621},[3536,11293,3685],{"class":3588},[3536,11295,3646],{"class":3546},[3536,11297,3691],{"class":3690},[3536,11299,3694],{"class":3546},[3536,11301,11302,11304,11306,11309,11311,11313],{"class":3538,"line":4648},[3536,11303,3700],{"class":3584},[3536,11305,3703],{"class":3584},[3536,11307,11308],{"class":3588}," profile",[3536,11310,3646],{"class":3546},[3536,11312,10083],{"class":3649},[3536,11314,11315],{"class":3546},": UserService = Depends(get_user_service)):\n",[3536,11317,11318,11320],{"class":3538,"line":5473},[3536,11319,3665],{"class":3542},[3536,11321,11322],{"class":3546}," service.get_profile()\n",[3406,11324],{},[3417,11326,11328],{"id":11327},"самостійний-проект-сейф-секретних-документів-secure-document-vault","Самостійний проект: Сейф Секретних Документів (Secure Document Vault)",[3385,11330,11331],{},"Створимо завершений асинхронний мікросервіс, який виконує роль захищеного сейфу документів із перевіркою API-ключа, логуванням IP-адрес через ланцюжок залежностей та наданням HTML-інтерфейсу для перегляду.",[4912,11333,11335],{"id":11334},"функціонал-проекту","Функціонал проекту:",[4852,11337,11338,11347],{},[3432,11339,11340,11343,11344,11346],{},[3392,11341,11342],{},"HTML-інтерфейс",": GET роут ",[3395,11345,4198],{}," віддає просту сторінку для завантаження документів за ID.",[3432,11348,11349,3607,11352],{},[3392,11350,11351],{},"Ланцюжок DI",[3429,11353,11354,11362,11371,11377],{},[3432,11355,11356,11359,11360,3398],{},[3395,11357,11358],{},"get_client_ip"," — отримує IP-адресу клієнта з об'єкта ",[3395,11361,8195],{},[3432,11363,11364,11367,11368,3398],{},[3395,11365,11366],{},"verify_vault_token"," — зчитує та перевіряє заголовок ",[3395,11369,11370],{},"X-Vault-Token",[3432,11372,11373,11376],{},[3395,11374,11375],{},"audit_access_log"," — залежить від IP та токена. Записує лог доступу на сервері.",[3432,11378,11379,11382],{},[3395,11380,11381],{},"get_vault_service"," — інжектує сервіс документів.",[3385,11384,11385],{},"Створимо та запустимо цей додаток:",[3526,11387,11390],{"className":3528,"code":11388,"filename":11389,"language":3531,"meta":3532,"style":3532},"# secure_vault.py\nfrom typing import Generator\nfrom fastapi import FastAPI, Depends, Request, Header, HTTPException, status\nfrom fastapi.responses import HTMLResponse\n\napp = FastAPI(title=\"Secure Document Vault\")\n\n# Симульовані документи\nVAULT_DOCUMENTS = {\n    \"doc_1\": \"План розвитку компанії на 2026 рік (СЕКРЕТНО)\",\n    \"doc_2\": \"Фінансовий звіт за Q2 (КОНФІДЕНЦІЙНО)\",\n}\n\n# --- СЕРВІСНИЙ ШАР ---\nclass VaultService:\n    def get_document(self, doc_id: str) -> str:\n        if doc_id not in VAULT_DOCUMENTS:\n            raise KeyError(\"Документ не знайдено в сховищі\")\n        return VAULT_DOCUMENTS[doc_id]\n\ndef get_vault_service() -> VaultService:\n    return VaultService()\n\n# --- ЗАЛЕЖНОСТІ DI ---\n\ndef get_client_ip(request: Request) -> str:\n    \"\"\"Витягує IP-адресу відправника.\"\"\"\n    return request.client.host if request.client else \"unknown\"\n\ndef verify_vault_token(x_vault_token: str = Header(..., description=\"Токен доступу до сейфу\")) -> str:\n    \"\"\"Перевіряє наявність секретного токена.\"\"\"\n    if x_vault_token != \"vault_secret_token_123\":\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=\"Невірний токен доступу до сейфу документів\"\n        )\n    return x_vault_token\n\ndef audit_access_log(\n    ip: str = Depends(get_client_ip),\n    token: str = Depends(verify_vault_token)\n) -> Generator[None, None, None]:\n    \"\"\"Ланцюгова залежність для аудиту логів.\"\"\"\n    print(f\"[AUDIT START] Спроба доступу з IP: {ip} за допомогою токена\")\n    try:\n        yield\n        print(f\"[AUDIT SUCCESS] Успішний доступ з IP: {ip}\")\n    except Exception as e:\n        print(f\"[AUDIT FAILED] Збій доступу з IP: {ip} через помилку: {e}\")\n        raise e\n\n# --- МАРШРУТИ ---\n\n@app.get(\"\u002F\", response_class=HTMLResponse)\nasync def index():\n    content = \"\"\"\n    \u003C!DOCTYPE html>\n    \u003Chtml>\n    \u003Chead>\n        \u003Ctitle>Document Vault\u003C\u002Ftitle>\n        \u003Cstyle>\n            body { font-family: sans-serif; background-color: #f3f4f6; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }\n            .card { background: white; padding: 2rem; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); width: 400px; text-align: center; }\n            input { width: 100%; padding: 0.5rem; margin: 0.5rem 0; border: 1px solid #d1d5db; border-radius: 6px; box-sizing: border-box; }\n            button { background: #3b82f6; color: white; border: none; padding: 0.75rem; width: 100%; border-radius: 6px; font-weight: bold; cursor: pointer; }\n            button:hover { background: #2563eb; }\n            #output { margin-top: 1rem; padding: 0.5rem; background: #f9fafb; border-radius: 6px; text-align: left; display: none; white-space: pre-wrap; font-size: 0.9rem; }\n        \u003C\u002Fstyle>\n    \u003C\u002Fhead>\n    \u003Cbody>\n        \u003Cdiv class=\"card\">\n            \u003Ch2>Сейф документів\u003C\u002Fh2>\n            \u003Cinput id=\"token\" type=\"text\" placeholder=\"X-Vault-Token\" value=\"vault_secret_token_123\">\n            \u003Cinput id=\"docId\" type=\"text\" placeholder=\"ID документа (напр. doc_1)\">\n            \u003Cbutton onclick=\"fetchDocument()\">Отримати документ\u003C\u002Fbutton>\n            \u003Cdiv id=\"output\">\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cscript>\n            async function fetchDocument() {\n                const token = document.getElementById('token').value;\n                const docId = document.getElementById('docId').value;\n                const outDiv = document.getElementById('output');\n                outDiv.style.display = 'block';\n                outDiv.innerText = 'Завантаження...';\n                \n                try {\n                    const res = await fetch(`\u002Fdocuments\u002F${docId}`, {\n                        method: 'POST',\n                        headers: { 'X-Vault-Token': token }\n                    });\n                    const data = await res.json();\n                    outDiv.innerText = JSON.stringify(data, null, 2);\n                } catch (err) {\n                    outDiv.innerText = 'Помилка з\\'єднання: ' + err;\n                }\n            }\n        \u003C\u002Fscript>\n    \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n    \"\"\"\n    return HTMLResponse(content=content)\n\n@app.post(\"\u002Fdocuments\u002F{doc_id}\", dependencies=[Depends(audit_access_log)])\nasync def get_document(\n    doc_id: str, \n    service: VaultService = Depends(get_vault_service)\n):\n    try:\n        doc_content = service.get_document(doc_id)\n        return {\"doc_id\": doc_id, \"content\": doc_content}\n    except KeyError as e:\n        raise HTTPException(\n            status_code=status.HTTP_404_NOT_FOUND,\n            detail=str(e).strip(\"'\")\n        )\n","secure_vault.py",[3395,11391,11392,11397,11408,11419,11431,11435,11448,11452,11457,11462,11474,11486,11490,11494,11499,11508,11534,11548,11562,11569,11573,11583,11590,11594,11599,11603,11621,11626,11645,11649,11680,11685,11697,11703,11709,11718,11722,11729,11733,11742,11754,11766,11783,11788,11811,11817,11822,11843,11853,11884,11890,11894,11899,11903,11920,11931,11939,11944,11949,11954,11959,11964,11969,11974,11979,11984,11989,11994,11999,12004,12009,12014,12020,12026,12032,12038,12044,12050,12056,12062,12068,12074,12080,12086,12092,12098,12104,12110,12116,12122,12128,12134,12140,12146,12158,12164,12170,12176,12182,12188,12194,12208,12213,12235,12246,12258,12267,12272,12279,12285,12304,12315,12322,12329,12346],{"__ignoreMap":3532},[3536,11393,11394],{"class":3538,"line":3539},[3536,11395,11396],{"class":3577},"# secure_vault.py\n",[3536,11398,11399,11401,11403,11405],{"class":3538,"line":3556},[3536,11400,3543],{"class":3542},[3536,11402,5137],{"class":3546},[3536,11404,3550],{"class":3542},[3536,11406,11407],{"class":3546}," Generator\n",[3536,11409,11410,11412,11414,11416],{"class":3538,"line":3563},[3536,11411,3543],{"class":3542},[3536,11413,3547],{"class":3546},[3536,11415,3550],{"class":3542},[3536,11417,11418],{"class":3546}," FastAPI, Depends, Request, Header, HTTPException, status\n",[3536,11420,11421,11423,11426,11428],{"class":3538,"line":3569},[3536,11422,3543],{"class":3542},[3536,11424,11425],{"class":3546}," fastapi.responses ",[3536,11427,3550],{"class":3542},[3536,11429,11430],{"class":3546}," HTMLResponse\n",[3536,11432,11433],{"class":3538,"line":3574},[3536,11434,3560],{"emptyLinePlaceholder":3559},[3536,11436,11437,11439,11441,11443,11446],{"class":3538,"line":3581},[3536,11438,7446],{"class":3546},[3536,11440,9918],{"class":3649},[3536,11442,4474],{"class":3546},[3536,11444,11445],{"class":3690},"\"Secure Document Vault\"",[3536,11447,3694],{"class":3546},[3536,11449,11450],{"class":3538,"line":3595},[3536,11451,3560],{"emptyLinePlaceholder":3559},[3536,11453,11454],{"class":3538,"line":3601},[3536,11455,11456],{"class":3577},"# Симульовані документи\n",[3536,11458,11459],{"class":3538,"line":3610},[3536,11460,11461],{"class":3546},"VAULT_DOCUMENTS = {\n",[3536,11463,11464,11467,11469,11472],{"class":3538,"line":3619},[3536,11465,11466],{"class":3690},"    \"doc_1\"",[3536,11468,3753],{"class":3546},[3536,11470,11471],{"class":3690},"\"План розвитку компанії на 2026 рік (СЕКРЕТНО)\"",[3536,11473,7183],{"class":3546},[3536,11475,11476,11479,11481,11484],{"class":3538,"line":3627},[3536,11477,11478],{"class":3690},"    \"doc_2\"",[3536,11480,3753],{"class":3546},[3536,11482,11483],{"class":3690},"\"Фінансовий звіт за Q2 (КОНФІДЕНЦІЙНО)\"",[3536,11485,7183],{"class":3546},[3536,11487,11488],{"class":3538,"line":3633},[3536,11489,3759],{"class":3546},[3536,11491,11492],{"class":3538,"line":3638},[3536,11493,3560],{"emptyLinePlaceholder":3559},[3536,11495,11496],{"class":3538,"line":3656},[3536,11497,11498],{"class":3577},"# --- СЕРВІСНИЙ ШАР ---\n",[3536,11500,11501,11503,11506],{"class":3538,"line":3662},[3536,11502,4319],{"class":3584},[3536,11504,11505],{"class":3820}," VaultService",[3536,11507,3607],{"class":3546},[3536,11509,11510,11512,11515,11517,11519,11521,11524,11526,11528,11530,11532],{"class":3538,"line":3671},[3536,11511,4334],{"class":3584},[3536,11513,11514],{"class":3588}," get_document",[3536,11516,3646],{"class":3546},[3536,11518,4342],{"class":3649},[3536,11520,3824],{"class":3546},[3536,11522,11523],{"class":3649},"doc_id",[3536,11525,3753],{"class":3546},[3536,11527,6108],{"class":3820},[3536,11529,7021],{"class":3546},[3536,11531,6108],{"class":3820},[3536,11533,3607],{"class":3546},[3536,11535,11536,11538,11541,11543,11545],{"class":3538,"line":3676},[3536,11537,4533],{"class":3542},[3536,11539,11540],{"class":3546}," doc_id ",[3536,11542,6266],{"class":3584},[3536,11544,6269],{"class":3584},[3536,11546,11547],{"class":3546}," VAULT_DOCUMENTS:\n",[3536,11549,11550,11552,11555,11557,11560],{"class":3538,"line":3682},[3536,11551,6280],{"class":3542},[3536,11553,11554],{"class":3820}," KeyError",[3536,11556,3646],{"class":3546},[3536,11558,11559],{"class":3690},"\"Документ не знайдено в сховищі\"",[3536,11561,3694],{"class":3546},[3536,11563,11564,11566],{"class":3538,"line":3697},[3536,11565,4076],{"class":3542},[3536,11567,11568],{"class":3546}," VAULT_DOCUMENTS[doc_id]\n",[3536,11570,11571],{"class":3538,"line":3712},[3536,11572,3560],{"emptyLinePlaceholder":3559},[3536,11574,11575,11577,11580],{"class":3538,"line":3721},[3536,11576,3585],{"class":3584},[3536,11578,11579],{"class":3588}," get_vault_service",[3536,11581,11582],{"class":3546},"() -> VaultService:\n",[3536,11584,11585,11587],{"class":3538,"line":3730},[3536,11586,3665],{"class":3542},[3536,11588,11589],{"class":3546}," VaultService()\n",[3536,11591,11592],{"class":3538,"line":3736},[3536,11593,3560],{"emptyLinePlaceholder":3559},[3536,11595,11596],{"class":3538,"line":4052},[3536,11597,11598],{"class":3577},"# --- ЗАЛЕЖНОСТІ DI ---\n",[3536,11600,11601],{"class":3538,"line":4073},[3536,11602,3560],{"emptyLinePlaceholder":3559},[3536,11604,11605,11607,11610,11612,11614,11617,11619],{"class":3538,"line":4105},[3536,11606,3585],{"class":3584},[3536,11608,11609],{"class":3588}," get_client_ip",[3536,11611,3646],{"class":3546},[3536,11613,8403],{"class":3649},[3536,11615,11616],{"class":3546},": Request) -> ",[3536,11618,6108],{"class":3820},[3536,11620,3607],{"class":3546},[3536,11622,11623],{"class":3538,"line":4110},[3536,11624,11625],{"class":3690},"    \"\"\"Витягує IP-адресу відправника.\"\"\"\n",[3536,11627,11628,11630,11633,11636,11639,11642],{"class":3538,"line":4504},[3536,11629,3665],{"class":3542},[3536,11631,11632],{"class":3546}," request.client.host ",[3536,11634,11635],{"class":3542},"if",[3536,11637,11638],{"class":3546}," request.client ",[3536,11640,11641],{"class":3542},"else",[3536,11643,11644],{"class":3690}," \"unknown\"\n",[3536,11646,11647],{"class":3538,"line":4509},[3536,11648,3560],{"emptyLinePlaceholder":3559},[3536,11650,11651,11653,11656,11658,11661,11663,11665,11667,11669,11671,11674,11676,11678],{"class":3538,"line":4524},[3536,11652,3585],{"class":3584},[3536,11654,11655],{"class":3588}," verify_vault_token",[3536,11657,3646],{"class":3546},[3536,11659,11660],{"class":3649},"x_vault_token",[3536,11662,3753],{"class":3546},[3536,11664,6108],{"class":3820},[3536,11666,6852],{"class":3546},[3536,11668,6855],{"class":3649},[3536,11670,4474],{"class":3546},[3536,11672,11673],{"class":3690},"\"Токен доступу до сейфу\"",[3536,11675,6863],{"class":3546},[3536,11677,6108],{"class":3820},[3536,11679,3607],{"class":3546},[3536,11681,11682],{"class":3538,"line":4530},[3536,11683,11684],{"class":3690},"    \"\"\"Перевіряє наявність секретного токена.\"\"\"\n",[3536,11686,11687,11689,11692,11695],{"class":3538,"line":4542},[3536,11688,5458],{"class":3542},[3536,11690,11691],{"class":3546}," x_vault_token != ",[3536,11693,11694],{"class":3690},"\"vault_secret_token_123\"",[3536,11696,3607],{"class":3546},[3536,11698,11699,11701],{"class":3538,"line":4548},[3536,11700,5387],{"class":3542},[3536,11702,5478],{"class":3546},[3536,11704,11705,11707],{"class":3538,"line":4599},[3536,11706,5484],{"class":3649},[3536,11708,6290],{"class":3546},[3536,11710,11711,11713,11715],{"class":3538,"line":4605},[3536,11712,5493],{"class":3649},[3536,11714,4474],{"class":3546},[3536,11716,11717],{"class":3690},"\"Невірний токен доступу до сейфу документів\"\n",[3536,11719,11720],{"class":3538,"line":4616},[3536,11721,5504],{"class":3546},[3536,11723,11724,11726],{"class":3538,"line":4621},[3536,11725,3665],{"class":3542},[3536,11727,11728],{"class":3546}," x_vault_token\n",[3536,11730,11731],{"class":3538,"line":4648},[3536,11732,3560],{"emptyLinePlaceholder":3559},[3536,11734,11735,11737,11740],{"class":3538,"line":5473},[3536,11736,3585],{"class":3584},[3536,11738,11739],{"class":3588}," audit_access_log",[3536,11741,3709],{"class":3546},[3536,11743,11744,11747,11749,11751],{"class":3538,"line":5481},[3536,11745,11746],{"class":3649},"    ip",[3536,11748,3753],{"class":3546},[3536,11750,6108],{"class":3820},[3536,11752,11753],{"class":3546}," = Depends(get_client_ip),\n",[3536,11755,11756,11759,11761,11763],{"class":3538,"line":5490},[3536,11757,11758],{"class":3649},"    token",[3536,11760,3753],{"class":3546},[3536,11762,6108],{"class":3820},[3536,11764,11765],{"class":3546}," = Depends(verify_vault_token)\n",[3536,11767,11768,11771,11773,11775,11777,11779,11781],{"class":3538,"line":5501},[3536,11769,11770],{"class":3546},") -> Generator[",[3536,11772,5329],{"class":3584},[3536,11774,3824],{"class":3546},[3536,11776,5329],{"class":3584},[3536,11778,3824],{"class":3546},[3536,11780,5329],{"class":3584},[3536,11782,5332],{"class":3546},[3536,11784,11785],{"class":3538,"line":5507},[3536,11786,11787],{"class":3690},"    \"\"\"Ланцюгова залежність для аудиту логів.\"\"\"\n",[3536,11789,11790,11792,11794,11796,11799,11801,11804,11806,11809],{"class":3538,"line":7106},[3536,11791,6937],{"class":3588},[3536,11793,3646],{"class":3546},[3536,11795,4556],{"class":3584},[3536,11797,11798],{"class":3690},"\"[AUDIT START] Спроба доступу з IP: ",[3536,11800,4562],{"class":3584},[3536,11802,11803],{"class":3546},"ip",[3536,11805,4568],{"class":3584},[3536,11807,11808],{"class":3690}," за допомогою токена\"",[3536,11810,3694],{"class":3546},[3536,11812,11813,11815],{"class":3538,"line":7111},[3536,11814,3604],{"class":3542},[3536,11816,3607],{"class":3546},[3536,11818,11819],{"class":3538,"line":7119},[3536,11820,11821],{"class":3542},"        yield\n",[3536,11823,11824,11826,11828,11830,11833,11835,11837,11839,11841],{"class":3538,"line":7124},[3536,11825,5206],{"class":3588},[3536,11827,3646],{"class":3546},[3536,11829,4556],{"class":3584},[3536,11831,11832],{"class":3690},"\"[AUDIT SUCCESS] Успішний доступ з IP: ",[3536,11834,4562],{"class":3584},[3536,11836,11803],{"class":3546},[3536,11838,4568],{"class":3584},[3536,11840,4559],{"class":3690},[3536,11842,3694],{"class":3546},[3536,11844,11845,11847,11849,11851],{"class":3538,"line":7130},[3536,11846,5365],{"class":3542},[3536,11848,5368],{"class":3820},[3536,11850,5371],{"class":3542},[3536,11852,5374],{"class":3546},[3536,11854,11855,11857,11859,11861,11864,11866,11868,11870,11873,11875,11878,11880,11882],{"class":3538,"line":7142},[3536,11856,5206],{"class":3588},[3536,11858,3646],{"class":3546},[3536,11860,4556],{"class":3584},[3536,11862,11863],{"class":3690},"\"[AUDIT FAILED] Збій доступу з IP: ",[3536,11865,4562],{"class":3584},[3536,11867,11803],{"class":3546},[3536,11869,4568],{"class":3584},[3536,11871,11872],{"class":3690}," через помилку: ",[3536,11874,4562],{"class":3584},[3536,11876,11877],{"class":3546},"e",[3536,11879,4568],{"class":3584},[3536,11881,4559],{"class":3690},[3536,11883,3694],{"class":3546},[3536,11885,11886,11888],{"class":3538,"line":7164},[3536,11887,5387],{"class":3542},[3536,11889,9839],{"class":3546},[3536,11891,11892],{"class":3538,"line":7172},[3536,11893,3560],{"emptyLinePlaceholder":3559},[3536,11895,11896],{"class":3538,"line":7186},[3536,11897,11898],{"class":3577},"# --- МАРШРУТИ ---\n",[3536,11900,11901],{"class":3538,"line":7200},[3536,11902,3560],{"emptyLinePlaceholder":3559},[3536,11904,11905,11907,11909,11912,11914,11917],{"class":3538,"line":7211},[3536,11906,3685],{"class":3588},[3536,11908,3646],{"class":3546},[3536,11910,11911],{"class":3690},"\"\u002F\"",[3536,11913,3824],{"class":3546},[3536,11915,11916],{"class":3649},"response_class",[3536,11918,11919],{"class":3546},"=HTMLResponse)\n",[3536,11921,11922,11924,11926,11929],{"class":3538,"line":9969},[3536,11923,3700],{"class":3584},[3536,11925,3703],{"class":3584},[3536,11927,11928],{"class":3588}," index",[3536,11930,3592],{"class":3546},[3536,11932,11933,11936],{"class":3538,"line":9984},[3536,11934,11935],{"class":3546},"    content = ",[3536,11937,11938],{"class":3690},"\"\"\"\n",[3536,11940,11941],{"class":3538,"line":10006},[3536,11942,11943],{"class":3690},"    \u003C!DOCTYPE html>\n",[3536,11945,11946],{"class":3538,"line":10011},[3536,11947,11948],{"class":3690},"    \u003Chtml>\n",[3536,11950,11951],{"class":3538,"line":10017},[3536,11952,11953],{"class":3690},"    \u003Chead>\n",[3536,11955,11956],{"class":3538,"line":10032},[3536,11957,11958],{"class":3690},"        \u003Ctitle>Document Vault\u003C\u002Ftitle>\n",[3536,11960,11961],{"class":3538,"line":10040},[3536,11962,11963],{"class":3690},"        \u003Cstyle>\n",[3536,11965,11966],{"class":3538,"line":10045},[3536,11967,11968],{"class":3690},"            body { font-family: sans-serif; background-color: #f3f4f6; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }\n",[3536,11970,11971],{"class":3538,"line":10051},[3536,11972,11973],{"class":3690},"            .card { background: white; padding: 2rem; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); width: 400px; text-align: center; }\n",[3536,11975,11976],{"class":3538,"line":10064},[3536,11977,11978],{"class":3690},"            input { width: 100%; padding: 0.5rem; margin: 0.5rem 0; border: 1px solid #d1d5db; border-radius: 6px; box-sizing: border-box; }\n",[3536,11980,11981],{"class":3538,"line":10089},[3536,11982,11983],{"class":3690},"            button { background: #3b82f6; color: white; border: none; padding: 0.75rem; width: 100%; border-radius: 6px; font-weight: bold; cursor: pointer; }\n",[3536,11985,11986],{"class":3538,"line":10096},[3536,11987,11988],{"class":3690},"            button:hover { background: #2563eb; }\n",[3536,11990,11991],{"class":3538,"line":10104},[3536,11992,11993],{"class":3690},"            #output { margin-top: 1rem; padding: 0.5rem; background: #f9fafb; border-radius: 6px; text-align: left; display: none; white-space: pre-wrap; font-size: 0.9rem; }\n",[3536,11995,11996],{"class":3538,"line":10115},[3536,11997,11998],{"class":3690},"        \u003C\u002Fstyle>\n",[3536,12000,12001],{"class":3538,"line":10122},[3536,12002,12003],{"class":3690},"    \u003C\u002Fhead>\n",[3536,12005,12006],{"class":3538,"line":10130},[3536,12007,12008],{"class":3690},"    \u003Cbody>\n",[3536,12010,12011],{"class":3538,"line":10142},[3536,12012,12013],{"class":3690},"        \u003Cdiv class=\"card\">\n",[3536,12015,12017],{"class":3538,"line":12016},72,[3536,12018,12019],{"class":3690},"            \u003Ch2>Сейф документів\u003C\u002Fh2>\n",[3536,12021,12023],{"class":3538,"line":12022},73,[3536,12024,12025],{"class":3690},"            \u003Cinput id=\"token\" type=\"text\" placeholder=\"X-Vault-Token\" value=\"vault_secret_token_123\">\n",[3536,12027,12029],{"class":3538,"line":12028},74,[3536,12030,12031],{"class":3690},"            \u003Cinput id=\"docId\" type=\"text\" placeholder=\"ID документа (напр. doc_1)\">\n",[3536,12033,12035],{"class":3538,"line":12034},75,[3536,12036,12037],{"class":3690},"            \u003Cbutton onclick=\"fetchDocument()\">Отримати документ\u003C\u002Fbutton>\n",[3536,12039,12041],{"class":3538,"line":12040},76,[3536,12042,12043],{"class":3690},"            \u003Cdiv id=\"output\">\u003C\u002Fdiv>\n",[3536,12045,12047],{"class":3538,"line":12046},77,[3536,12048,12049],{"class":3690},"        \u003C\u002Fdiv>\n",[3536,12051,12053],{"class":3538,"line":12052},78,[3536,12054,12055],{"class":3690},"        \u003Cscript>\n",[3536,12057,12059],{"class":3538,"line":12058},79,[3536,12060,12061],{"class":3690},"            async function fetchDocument() {\n",[3536,12063,12065],{"class":3538,"line":12064},80,[3536,12066,12067],{"class":3690},"                const token = document.getElementById('token').value;\n",[3536,12069,12071],{"class":3538,"line":12070},81,[3536,12072,12073],{"class":3690},"                const docId = document.getElementById('docId').value;\n",[3536,12075,12077],{"class":3538,"line":12076},82,[3536,12078,12079],{"class":3690},"                const outDiv = document.getElementById('output');\n",[3536,12081,12083],{"class":3538,"line":12082},83,[3536,12084,12085],{"class":3690},"                outDiv.style.display = 'block';\n",[3536,12087,12089],{"class":3538,"line":12088},84,[3536,12090,12091],{"class":3690},"                outDiv.innerText = 'Завантаження...';\n",[3536,12093,12095],{"class":3538,"line":12094},85,[3536,12096,12097],{"class":3690},"                \n",[3536,12099,12101],{"class":3538,"line":12100},86,[3536,12102,12103],{"class":3690},"                try {\n",[3536,12105,12107],{"class":3538,"line":12106},87,[3536,12108,12109],{"class":3690},"                    const res = await fetch(`\u002Fdocuments\u002F${docId}`, {\n",[3536,12111,12113],{"class":3538,"line":12112},88,[3536,12114,12115],{"class":3690},"                        method: 'POST',\n",[3536,12117,12119],{"class":3538,"line":12118},89,[3536,12120,12121],{"class":3690},"                        headers: { 'X-Vault-Token': token }\n",[3536,12123,12125],{"class":3538,"line":12124},90,[3536,12126,12127],{"class":3690},"                    });\n",[3536,12129,12131],{"class":3538,"line":12130},91,[3536,12132,12133],{"class":3690},"                    const data = await res.json();\n",[3536,12135,12137],{"class":3538,"line":12136},92,[3536,12138,12139],{"class":3690},"                    outDiv.innerText = JSON.stringify(data, null, 2);\n",[3536,12141,12143],{"class":3538,"line":12142},93,[3536,12144,12145],{"class":3690},"                } catch (err) {\n",[3536,12147,12149,12152,12155],{"class":3538,"line":12148},94,[3536,12150,12151],{"class":3690},"                    outDiv.innerText = 'Помилка з",[3536,12153,12154],{"class":7684},"\\'",[3536,12156,12157],{"class":3690},"єднання: ' + err;\n",[3536,12159,12161],{"class":3538,"line":12160},95,[3536,12162,12163],{"class":3690},"                }\n",[3536,12165,12167],{"class":3538,"line":12166},96,[3536,12168,12169],{"class":3690},"            }\n",[3536,12171,12173],{"class":3538,"line":12172},97,[3536,12174,12175],{"class":3690},"        \u003C\u002Fscript>\n",[3536,12177,12179],{"class":3538,"line":12178},98,[3536,12180,12181],{"class":3690},"    \u003C\u002Fbody>\n",[3536,12183,12185],{"class":3538,"line":12184},99,[3536,12186,12187],{"class":3690},"    \u003C\u002Fhtml>\n",[3536,12189,12191],{"class":3538,"line":12190},100,[3536,12192,12193],{"class":3690},"    \"\"\"\n",[3536,12195,12197,12199,12202,12205],{"class":3538,"line":12196},101,[3536,12198,3665],{"class":3542},[3536,12200,12201],{"class":3546}," HTMLResponse(",[3536,12203,12204],{"class":3649},"content",[3536,12206,12207],{"class":3546},"=content)\n",[3536,12209,12211],{"class":3538,"line":12210},102,[3536,12212,3560],{"emptyLinePlaceholder":3559},[3536,12214,12216,12218,12220,12223,12226,12228,12230,12232],{"class":3538,"line":12215},103,[3536,12217,10054],{"class":3588},[3536,12219,3646],{"class":3546},[3536,12221,12222],{"class":3690},"\"\u002Fdocuments\u002F",[3536,12224,12225],{"class":3584},"{doc_id}",[3536,12227,4559],{"class":3690},[3536,12229,3824],{"class":3546},[3536,12231,7231],{"class":3649},[3536,12233,12234],{"class":3546},"=[Depends(audit_access_log)])\n",[3536,12236,12238,12240,12242,12244],{"class":3538,"line":12237},104,[3536,12239,3700],{"class":3584},[3536,12241,3703],{"class":3584},[3536,12243,11514],{"class":3588},[3536,12245,3709],{"class":3546},[3536,12247,12249,12252,12254,12256],{"class":3538,"line":12248},105,[3536,12250,12251],{"class":3649},"    doc_id",[3536,12253,3753],{"class":3546},[3536,12255,6108],{"class":3820},[3536,12257,7478],{"class":3546},[3536,12259,12261,12264],{"class":3538,"line":12260},106,[3536,12262,12263],{"class":3649},"    service",[3536,12265,12266],{"class":3546},": VaultService = Depends(get_vault_service)\n",[3536,12268,12270],{"class":3538,"line":12269},107,[3536,12271,3733],{"class":3546},[3536,12273,12275,12277],{"class":3538,"line":12274},108,[3536,12276,3604],{"class":3542},[3536,12278,3607],{"class":3546},[3536,12280,12282],{"class":3538,"line":12281},109,[3536,12283,12284],{"class":3546},"        doc_content = service.get_document(doc_id)\n",[3536,12286,12288,12290,12292,12295,12298,12301],{"class":3538,"line":12287},110,[3536,12289,4076],{"class":3542},[3536,12291,3741],{"class":3546},[3536,12293,12294],{"class":3690},"\"doc_id\"",[3536,12296,12297],{"class":3546},": doc_id, ",[3536,12299,12300],{"class":3690},"\"content\"",[3536,12302,12303],{"class":3546},": doc_content}\n",[3536,12305,12307,12309,12311,12313],{"class":3538,"line":12306},111,[3536,12308,5365],{"class":3542},[3536,12310,11554],{"class":3820},[3536,12312,5371],{"class":3542},[3536,12314,5374],{"class":3546},[3536,12316,12318,12320],{"class":3538,"line":12317},112,[3536,12319,5387],{"class":3542},[3536,12321,5478],{"class":3546},[3536,12323,12325,12327],{"class":3538,"line":12324},113,[3536,12326,5484],{"class":3649},[3536,12328,10888],{"class":3546},[3536,12330,12332,12334,12336,12338,12341,12344],{"class":3538,"line":12331},114,[3536,12333,5493],{"class":3649},[3536,12335,4474],{"class":3546},[3536,12337,6108],{"class":3820},[3536,12339,12340],{"class":3546},"(e).strip(",[3536,12342,12343],{"class":3690},"\"'\"",[3536,12345,3694],{"class":3546},[3536,12347,12349],{"class":3538,"line":12348},115,[3536,12350,5504],{"class":3546},[4912,12352,12354],{"id":12353},"запуск-та-тестування-мікросервісу","Запуск та тестування мікросервісу",[3385,12356,12357],{},"Запустіть додаток через Uvicorn:",[3523,12359,12360,12374],{},[3526,12361,12363],{"className":4657,"code":12362,"filename":6469,"language":4660,"meta":3532,"style":3532},"uvicorn secure_vault:app --reload\n",[3395,12364,12365],{"__ignoreMap":3532},[3536,12366,12367,12369,12372],{"class":3538,"line":3539},[3536,12368,6476],{"class":3588},[3536,12370,12371],{"class":3690}," secure_vault:app",[3536,12373,6482],{"class":3584},[3526,12375,12376],{"className":6485,"code":12362,"filename":6486,"language":6487,"meta":3532,"style":3532},[3395,12377,12378],{"__ignoreMap":3532},[3536,12379,12380],{"class":3538,"line":3539},[3536,12381,12362],{"class":3546},[3385,12383,12384,12385,4889],{},"Надішлемо POST запит із валідним ключем для читання документа ",[3395,12386,12387],{},"doc_1",[3523,12389,12390,12415],{},[3526,12391,12393],{"className":4657,"code":12392,"filename":6469,"language":4660,"meta":3532,"style":3532},"curl -X POST http:\u002F\u002F127.0.0.1:8000\u002Fdocuments\u002Fdoc_1 \\\n  -H \"X-Vault-Token: vault_secret_token_123\"\n",[3395,12394,12395,12408],{"__ignoreMap":3532},[3536,12396,12397,12399,12401,12403,12406],{"class":3538,"line":3539},[3536,12398,6518],{"class":3588},[3536,12400,6521],{"class":3584},[3536,12402,10197],{"class":3690},[3536,12404,12405],{"class":3690}," http:\u002F\u002F127.0.0.1:8000\u002Fdocuments\u002Fdoc_1",[3536,12407,7685],{"class":7684},[3536,12409,12410,12412],{"class":3538,"line":3556},[3536,12411,7690],{"class":3584},[3536,12413,12414],{"class":3690}," \"X-Vault-Token: vault_secret_token_123\"\n",[3526,12416,12418],{"className":6485,"code":12417,"filename":6486,"language":6487,"meta":3532,"style":3532},"curl.exe -X POST http:\u002F\u002F127.0.0.1:8000\u002Fdocuments\u002Fdoc_1 `\n  -H \"X-Vault-Token: vault_secret_token_123\"\n",[3395,12419,12420,12440],{"__ignoreMap":3532},[3536,12421,12422,12424,12427,12429,12431,12433,12435,12437],{"class":3538,"line":3539},[3536,12423,6537],{"class":3588},[3536,12425,12426],{"class":3546}," -X POST http:\u002F\u002F",[3536,12428,6543],{"class":4477},[3536,12430,3398],{"class":3546},[3536,12432,6548],{"class":4477},[3536,12434,4889],{"class":3546},[3536,12436,6553],{"class":4477},[3536,12438,12439],{"class":3546},"\u002Fdocuments\u002Fdoc_1 `\n",[3536,12441,12442,12444],{"class":3538,"line":3556},[3536,12443,7731],{"class":3546},[3536,12445,12446],{"class":3690},"\"X-Vault-Token: vault_secret_token_123\"\n",[4672,12448,12450,12456,12459,12462],{":cursor":4674,"title":12449},"Консольний вивід успіху",[4677,12451,12453],{"className":12452},[3538],[3536,12454,6567],{"className":12455},[6566,4688],[4677,12457,6571],{"className":12458},[3538],[4677,12460],{"className":12461},[3538],[4677,12463,12465],{"className":12464},[3538],"{\"doc_id\":\"doc_1\",\"content\":\"План розвитку компанії на 2026 рік (СЕКРЕТНО)\"}",[3385,12467,12468],{},"У логах сервера ми побачимо послідовне виконання ланцюжка DI:",[4672,12470,12472,12476,12480],{":cursor":4674,"title":12471},"Логи аудиту успіху на сервері",[4677,12473,12475],{"className":12474},[3538],"[AUDIT START] Спроба доступу з IP: 127.0.0.1 за допомогою токена",[4677,12477,12479],{"className":12478},[3538],"[AUDIT SUCCESS] Успішний доступ з IP: 127.0.0.1",[4677,12481,12483],{"className":12482},[3538],"INFO:     127.0.0.1:54322 - \"POST \u002Fdocuments\u002Fdoc_1 HTTP\u002F1.1\" 200 OK",[3385,12485,12486],{},"А тепер надішлемо запит із невалідним токеном:",[3523,12488,12489,12513],{},[3526,12490,12492],{"className":4657,"code":12491,"filename":6469,"language":4660,"meta":3532,"style":3532},"curl -X POST http:\u002F\u002F127.0.0.1:8000\u002Fdocuments\u002Fdoc_1 \\\n  -H \"X-Vault-Token: wrong_token\"\n",[3395,12493,12494,12506],{"__ignoreMap":3532},[3536,12495,12496,12498,12500,12502,12504],{"class":3538,"line":3539},[3536,12497,6518],{"class":3588},[3536,12499,6521],{"class":3584},[3536,12501,10197],{"class":3690},[3536,12503,12405],{"class":3690},[3536,12505,7685],{"class":7684},[3536,12507,12508,12510],{"class":3538,"line":3556},[3536,12509,7690],{"class":3584},[3536,12511,12512],{"class":3690}," \"X-Vault-Token: wrong_token\"\n",[3526,12514,12516],{"className":6485,"code":12515,"filename":6486,"language":6487,"meta":3532,"style":3532},"curl.exe -X POST http:\u002F\u002F127.0.0.1:8000\u002Fdocuments\u002Fdoc_1 `\n  -H \"X-Vault-Token: wrong_token\"\n",[3395,12517,12518,12536],{"__ignoreMap":3532},[3536,12519,12520,12522,12524,12526,12528,12530,12532,12534],{"class":3538,"line":3539},[3536,12521,6537],{"class":3588},[3536,12523,12426],{"class":3546},[3536,12525,6543],{"class":4477},[3536,12527,3398],{"class":3546},[3536,12529,6548],{"class":4477},[3536,12531,4889],{"class":3546},[3536,12533,6553],{"class":4477},[3536,12535,12439],{"class":3546},[3536,12537,12538,12540],{"class":3538,"line":3556},[3536,12539,7731],{"class":3546},[3536,12541,12542],{"class":3690},"\"X-Vault-Token: wrong_token\"\n",[4672,12544,12546,12552,12555,12558],{":cursor":4674,"title":12545},"Консольний вивід помилки",[4677,12547,12549],{"className":12548},[3538],[3536,12550,6638],{"className":12551},[6637,4688],[4677,12553,6571],{"className":12554},[3538],[4677,12556],{"className":12557},[3538],[4677,12559,12561],{"className":12560},[3538],"{\"detail\":\"Невірний токен доступу до сейфу документів\"}",[3385,12563,12564],{},"Оскільки перевірка токена провалилася всередині ланцюжка, бізнес-код ендпоінту та фаза очищення аудиту взагалі не запускалися.",[3406,12566],{},[3417,12568,12570],{"id":12569},"практика-в-taskforge-сервісний-шар-та-di","Практика в TaskForge: Сервісний шар та DI",[3385,12572,12573,12574,12577,12578,12580],{},"Тепер застосуємо отримані знання для рефакторингу нашого основного проекту ",[3392,12575,12576],{},"TaskForge",". Ваша задача — винести бізнес-логіку з контролерів у сервісний шар та інжектувати сервіси за допомогою ",[3395,12579,3397],{},". Це концептуально наблизить структуру проекту до сервісно-орієнтованої архітектури ASP.NET Core контролерів.",[4912,12582,12584],{"id":12583},"крок-1-створення-файлів-сервісів","Крок 1: Створення файлів сервісів",[3385,12586,12587,12588,12591],{},"Створіть у проекті директорію ",[3395,12589,12590],{},"app\u002Fservices\u002F"," та додайте два сервіси:",[3526,12593,12596],{"className":3528,"code":12594,"filename":12595,"language":3531,"meta":3532,"style":3532},"# app\u002Fservices\u002Fproject_service.py\n\n# Симуляція БД\ndb_projects = []\n\nclass ProjectService:\n    def create_project(self, name: str, description: str | None) -> dict:\n        new_project = {\n            \"id\": len(db_projects) + 1,\n            \"name\": name,\n            \"description\": description,\n            \"created_at\": \"2026-07-01T12:00:00Z\"\n        }\n        db_projects.append(new_project)\n        return new_project\n\n    def get_all_projects(self) -> list[dict]:\n        return db_projects\n\ndef get_project_service() -> ProjectService:\n    return ProjectService()\n","app\u002Fservices\u002Fproject_service.py",[3395,12597,12598,12603,12607,12612,12617,12621,12630,12668,12673,12690,12698,12706,12716,12721,12726,12733,12737,12755,12762,12766,12776],{"__ignoreMap":3532},[3536,12599,12600],{"class":3538,"line":3539},[3536,12601,12602],{"class":3577},"# app\u002Fservices\u002Fproject_service.py\n",[3536,12604,12605],{"class":3538,"line":3556},[3536,12606,3560],{"emptyLinePlaceholder":3559},[3536,12608,12609],{"class":3538,"line":3563},[3536,12610,12611],{"class":3577},"# Симуляція БД\n",[3536,12613,12614],{"class":3538,"line":3569},[3536,12615,12616],{"class":3546},"db_projects = []\n",[3536,12618,12619],{"class":3538,"line":3574},[3536,12620,3560],{"emptyLinePlaceholder":3559},[3536,12622,12623,12625,12628],{"class":3538,"line":3581},[3536,12624,4319],{"class":3584},[3536,12626,12627],{"class":3820}," ProjectService",[3536,12629,3607],{"class":3546},[3536,12631,12632,12634,12637,12639,12641,12643,12646,12648,12650,12652,12654,12656,12658,12660,12662,12664,12666],{"class":3538,"line":3595},[3536,12633,4334],{"class":3584},[3536,12635,12636],{"class":3588}," create_project",[3536,12638,3646],{"class":3546},[3536,12640,4342],{"class":3649},[3536,12642,3824],{"class":3546},[3536,12644,12645],{"class":3649},"name",[3536,12647,3753],{"class":3546},[3536,12649,6108],{"class":3820},[3536,12651,3824],{"class":3546},[3536,12653,6855],{"class":3649},[3536,12655,3753],{"class":3546},[3536,12657,6108],{"class":3820},[3536,12659,7336],{"class":3546},[3536,12661,5329],{"class":3584},[3536,12663,7021],{"class":3546},[3536,12665,7013],{"class":3820},[3536,12667,3607],{"class":3546},[3536,12669,12670],{"class":3538,"line":3601},[3536,12671,12672],{"class":3546},"        new_project = {\n",[3536,12674,12675,12678,12680,12683,12686,12688],{"class":3538,"line":3610},[3536,12676,12677],{"class":3690},"            \"id\"",[3536,12679,3753],{"class":3546},[3536,12681,12682],{"class":3588},"len",[3536,12684,12685],{"class":3546},"(db_projects) + ",[3536,12687,4611],{"class":4477},[3536,12689,7183],{"class":3546},[3536,12691,12692,12695],{"class":3538,"line":3619},[3536,12693,12694],{"class":3690},"            \"name\"",[3536,12696,12697],{"class":3546},": name,\n",[3536,12699,12700,12703],{"class":3538,"line":3627},[3536,12701,12702],{"class":3690},"            \"description\"",[3536,12704,12705],{"class":3546},": description,\n",[3536,12707,12708,12711,12713],{"class":3538,"line":3633},[3536,12709,12710],{"class":3690},"            \"created_at\"",[3536,12712,3753],{"class":3546},[3536,12714,12715],{"class":3690},"\"2026-07-01T12:00:00Z\"\n",[3536,12717,12718],{"class":3538,"line":3638},[3536,12719,12720],{"class":3546},"        }\n",[3536,12722,12723],{"class":3538,"line":3656},[3536,12724,12725],{"class":3546},"        db_projects.append(new_project)\n",[3536,12727,12728,12730],{"class":3538,"line":3662},[3536,12729,4076],{"class":3542},[3536,12731,12732],{"class":3546}," new_project\n",[3536,12734,12735],{"class":3538,"line":3671},[3536,12736,3560],{"emptyLinePlaceholder":3559},[3536,12738,12739,12741,12744,12746,12748,12751,12753],{"class":3538,"line":3676},[3536,12740,4334],{"class":3584},[3536,12742,12743],{"class":3588}," get_all_projects",[3536,12745,3646],{"class":3546},[3536,12747,4342],{"class":3649},[3536,12749,12750],{"class":3546},") -> list[",[3536,12752,7013],{"class":3820},[3536,12754,5332],{"class":3546},[3536,12756,12757,12759],{"class":3538,"line":3682},[3536,12758,4076],{"class":3542},[3536,12760,12761],{"class":3546}," db_projects\n",[3536,12763,12764],{"class":3538,"line":3697},[3536,12765,3560],{"emptyLinePlaceholder":3559},[3536,12767,12768,12770,12773],{"class":3538,"line":3712},[3536,12769,3585],{"class":3584},[3536,12771,12772],{"class":3588}," get_project_service",[3536,12774,12775],{"class":3546},"() -> ProjectService:\n",[3536,12777,12778,12780],{"class":3538,"line":3721},[3536,12779,3665],{"class":3542},[3536,12781,12782],{"class":3546}," ProjectService()\n",[3526,12784,12787],{"className":3528,"code":12785,"filename":12786,"language":3531,"meta":3532,"style":3532},"# app\u002Fservices\u002Ftask_service.py\n\n# Симуляція БД\ndb_tasks = []\n\nclass TaskService:\n    def create_task(self, title: str, description: str | None, project_id: int) -> dict:\n        new_task = {\n            \"id\": len(db_tasks) + 1,\n            \"title\": title,\n            \"description\": description,\n            \"project_id\": project_id,\n            \"created_at\": \"2026-07-01T12:05:00Z\"\n        }\n        db_tasks.append(new_task)\n        return new_task\n\n    def get_tasks_by_project(self, project_id: int) -> list[dict]:\n        return [task for task in db_tasks if task[\"project_id\"] == project_id]\n\ndef get_task_service() -> TaskService:\n    return TaskService()\n","app\u002Fservices\u002Ftask_service.py",[3395,12788,12789,12794,12798,12802,12807,12811,12819,12864,12869,12884,12892,12898,12906,12915,12919,12924,12931,12935,12960,12989,12993,13002],{"__ignoreMap":3532},[3536,12790,12791],{"class":3538,"line":3539},[3536,12792,12793],{"class":3577},"# app\u002Fservices\u002Ftask_service.py\n",[3536,12795,12796],{"class":3538,"line":3556},[3536,12797,3560],{"emptyLinePlaceholder":3559},[3536,12799,12800],{"class":3538,"line":3563},[3536,12801,12611],{"class":3577},[3536,12803,12804],{"class":3538,"line":3569},[3536,12805,12806],{"class":3546},"db_tasks = []\n",[3536,12808,12809],{"class":3538,"line":3574},[3536,12810,3560],{"emptyLinePlaceholder":3559},[3536,12812,12813,12815,12817],{"class":3538,"line":3581},[3536,12814,4319],{"class":3584},[3536,12816,9871],{"class":3820},[3536,12818,3607],{"class":3546},[3536,12820,12821,12823,12825,12827,12829,12831,12833,12835,12837,12839,12841,12843,12845,12847,12849,12851,12854,12856,12858,12860,12862],{"class":3538,"line":3595},[3536,12822,4334],{"class":3584},[3536,12824,9909],{"class":3588},[3536,12826,3646],{"class":3546},[3536,12828,4342],{"class":3649},[3536,12830,3824],{"class":3546},[3536,12832,9918],{"class":3649},[3536,12834,3753],{"class":3546},[3536,12836,6108],{"class":3820},[3536,12838,3824],{"class":3546},[3536,12840,6855],{"class":3649},[3536,12842,3753],{"class":3546},[3536,12844,6108],{"class":3820},[3536,12846,7336],{"class":3546},[3536,12848,5329],{"class":3584},[3536,12850,3824],{"class":3546},[3536,12852,12853],{"class":3649},"project_id",[3536,12855,3753],{"class":3546},[3536,12857,5446],{"class":3820},[3536,12859,7021],{"class":3546},[3536,12861,7013],{"class":3820},[3536,12863,3607],{"class":3546},[3536,12865,12866],{"class":3538,"line":3601},[3536,12867,12868],{"class":3546},"        new_task = {\n",[3536,12870,12871,12873,12875,12877,12880,12882],{"class":3538,"line":3610},[3536,12872,12677],{"class":3690},[3536,12874,3753],{"class":3546},[3536,12876,12682],{"class":3588},[3536,12878,12879],{"class":3546},"(db_tasks) + ",[3536,12881,4611],{"class":4477},[3536,12883,7183],{"class":3546},[3536,12885,12886,12889],{"class":3538,"line":3619},[3536,12887,12888],{"class":3690},"            \"title\"",[3536,12890,12891],{"class":3546},": title,\n",[3536,12893,12894,12896],{"class":3538,"line":3627},[3536,12895,12702],{"class":3690},[3536,12897,12705],{"class":3546},[3536,12899,12900,12903],{"class":3538,"line":3633},[3536,12901,12902],{"class":3690},"            \"project_id\"",[3536,12904,12905],{"class":3546},": project_id,\n",[3536,12907,12908,12910,12912],{"class":3538,"line":3638},[3536,12909,12710],{"class":3690},[3536,12911,3753],{"class":3546},[3536,12913,12914],{"class":3690},"\"2026-07-01T12:05:00Z\"\n",[3536,12916,12917],{"class":3538,"line":3656},[3536,12918,12720],{"class":3546},[3536,12920,12921],{"class":3538,"line":3662},[3536,12922,12923],{"class":3546},"        db_tasks.append(new_task)\n",[3536,12925,12926,12928],{"class":3538,"line":3671},[3536,12927,4076],{"class":3542},[3536,12929,12930],{"class":3546}," new_task\n",[3536,12932,12933],{"class":3538,"line":3676},[3536,12934,3560],{"emptyLinePlaceholder":3559},[3536,12936,12937,12939,12942,12944,12946,12948,12950,12952,12954,12956,12958],{"class":3538,"line":3682},[3536,12938,4334],{"class":3584},[3536,12940,12941],{"class":3588}," get_tasks_by_project",[3536,12943,3646],{"class":3546},[3536,12945,4342],{"class":3649},[3536,12947,3824],{"class":3546},[3536,12949,12853],{"class":3649},[3536,12951,3753],{"class":3546},[3536,12953,5446],{"class":3820},[3536,12955,12750],{"class":3546},[3536,12957,7013],{"class":3820},[3536,12959,5332],{"class":3546},[3536,12961,12962,12964,12967,12970,12973,12975,12978,12980,12983,12986],{"class":3538,"line":3697},[3536,12963,4076],{"class":3542},[3536,12965,12966],{"class":3546}," [task ",[3536,12968,12969],{"class":3542},"for",[3536,12971,12972],{"class":3546}," task ",[3536,12974,4518],{"class":3542},[3536,12976,12977],{"class":3546}," db_tasks ",[3536,12979,11635],{"class":3542},[3536,12981,12982],{"class":3546}," task[",[3536,12984,12985],{"class":3690},"\"project_id\"",[3536,12987,12988],{"class":3546},"] == project_id]\n",[3536,12990,12991],{"class":3538,"line":3712},[3536,12992,3560],{"emptyLinePlaceholder":3559},[3536,12994,12995,12997,12999],{"class":3538,"line":3721},[3536,12996,3585],{"class":3584},[3536,12998,10022],{"class":3588},[3536,13000,13001],{"class":3546},"() -> TaskService:\n",[3536,13003,13004,13006],{"class":3538,"line":3730},[3536,13005,3665],{"class":3542},[3536,13007,13008],{"class":3546}," TaskService()\n",[4912,13010,13012],{"id":13011},"крок-2-оновлення-роутера-задач","Крок 2: Оновлення роутера задач",[3385,13014,13015,13016,13019,13020,13022],{},"Перепишемо роутер задач, щоб він отримував залежність ",[3395,13017,13018],{},"TaskService"," через ",[3395,13021,3397],{}," і повністю делегував йому бізнес-операції:",[3526,13024,13027],{"className":3528,"code":13025,"filename":13026,"language":3531,"meta":3532,"style":3532},"# app\u002Frouters\u002Ftasks.py\nfrom fastapi import APIRouter, Depends, status, HTTPException\nfrom app.schemas.task import TaskCreate, TaskRead\nfrom app.services.task_service import TaskService, get_task_service\n\nrouter = APIRouter(prefix=\"\u002Ftasks\", tags=[\"Tasks\"])\n\n@router.post(\"\", response_model=TaskRead, status_code=status.HTTP_201_CREATED)\nasync def create_task(\n    task_in: TaskCreate, \n    service: TaskService = Depends(get_task_service)\n):\n    # Вся логіка збереження та генерації ID тепер винесена у сервіс\n    try:\n        return service.create_task(\n            title=task_in.title, \n            description=task_in.description, \n            project_id=task_in.project_id\n        )\n    except Exception as e:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=f\"Не вдалося створити задачу: {e}\"\n        )\n","app\u002Frouters\u002Ftasks.py",[3395,13028,13029,13034,13045,13057,13069,13073,13098,13102,13126,13136,13144,13151,13155,13160,13166,13173,13181,13189,13197,13201,13211,13217,13223,13242],{"__ignoreMap":3532},[3536,13030,13031],{"class":3538,"line":3539},[3536,13032,13033],{"class":3577},"# app\u002Frouters\u002Ftasks.py\n",[3536,13035,13036,13038,13040,13042],{"class":3538,"line":3556},[3536,13037,3543],{"class":3542},[3536,13039,3547],{"class":3546},[3536,13041,3550],{"class":3542},[3536,13043,13044],{"class":3546}," APIRouter, Depends, status, HTTPException\n",[3536,13046,13047,13049,13052,13054],{"class":3538,"line":3563},[3536,13048,3543],{"class":3542},[3536,13050,13051],{"class":3546}," app.schemas.task ",[3536,13053,3550],{"class":3542},[3536,13055,13056],{"class":3546}," TaskCreate, TaskRead\n",[3536,13058,13059,13061,13064,13066],{"class":3538,"line":3569},[3536,13060,3543],{"class":3542},[3536,13062,13063],{"class":3546}," app.services.task_service ",[3536,13065,3550],{"class":3542},[3536,13067,13068],{"class":3546}," TaskService, get_task_service\n",[3536,13070,13071],{"class":3538,"line":3574},[3536,13072,3560],{"emptyLinePlaceholder":3559},[3536,13074,13075,13078,13081,13083,13085,13087,13090,13093,13096],{"class":3538,"line":3581},[3536,13076,13077],{"class":3546},"router = APIRouter(",[3536,13079,13080],{"class":3649},"prefix",[3536,13082,4474],{"class":3546},[3536,13084,10059],{"class":3690},[3536,13086,3824],{"class":3546},[3536,13088,13089],{"class":3649},"tags",[3536,13091,13092],{"class":3546},"=[",[3536,13094,13095],{"class":3690},"\"Tasks\"",[3536,13097,6345],{"class":3546},[3536,13099,13100],{"class":3538,"line":3595},[3536,13101,3560],{"emptyLinePlaceholder":3559},[3536,13103,13104,13107,13109,13112,13114,13117,13120,13123],{"class":3538,"line":3601},[3536,13105,13106],{"class":3588},"@router.post",[3536,13108,3646],{"class":3546},[3536,13110,13111],{"class":3690},"\"\"",[3536,13113,3824],{"class":3546},[3536,13115,13116],{"class":3649},"response_model",[3536,13118,13119],{"class":3546},"=TaskRead, ",[3536,13121,13122],{"class":3649},"status_code",[3536,13124,13125],{"class":3546},"=status.HTTP_201_CREATED)\n",[3536,13127,13128,13130,13132,13134],{"class":3538,"line":3610},[3536,13129,3700],{"class":3584},[3536,13131,3703],{"class":3584},[3536,13133,9909],{"class":3588},[3536,13135,3709],{"class":3546},[3536,13137,13138,13141],{"class":3538,"line":3619},[3536,13139,13140],{"class":3649},"    task_in",[3536,13142,13143],{"class":3546},": TaskCreate, \n",[3536,13145,13146,13148],{"class":3538,"line":3627},[3536,13147,12263],{"class":3649},[3536,13149,13150],{"class":3546},": TaskService = Depends(get_task_service)\n",[3536,13152,13153],{"class":3538,"line":3633},[3536,13154,3733],{"class":3546},[3536,13156,13157],{"class":3538,"line":3638},[3536,13158,13159],{"class":3577},"    # Вся логіка збереження та генерації ID тепер винесена у сервіс\n",[3536,13161,13162,13164],{"class":3538,"line":3656},[3536,13163,3604],{"class":3542},[3536,13165,3607],{"class":3546},[3536,13167,13168,13170],{"class":3538,"line":3662},[3536,13169,4076],{"class":3542},[3536,13171,13172],{"class":3546}," service.create_task(\n",[3536,13174,13175,13178],{"class":3538,"line":3671},[3536,13176,13177],{"class":3649},"            title",[3536,13179,13180],{"class":3546},"=task_in.title, \n",[3536,13182,13183,13186],{"class":3538,"line":3676},[3536,13184,13185],{"class":3649},"            description",[3536,13187,13188],{"class":3546},"=task_in.description, \n",[3536,13190,13191,13194],{"class":3538,"line":3682},[3536,13192,13193],{"class":3649},"            project_id",[3536,13195,13196],{"class":3546},"=task_in.project_id\n",[3536,13198,13199],{"class":3538,"line":3697},[3536,13200,5504],{"class":3546},[3536,13202,13203,13205,13207,13209],{"class":3538,"line":3712},[3536,13204,5365],{"class":3542},[3536,13206,5368],{"class":3820},[3536,13208,5371],{"class":3542},[3536,13210,5374],{"class":3546},[3536,13212,13213,13215],{"class":3538,"line":3721},[3536,13214,5387],{"class":3542},[3536,13216,5478],{"class":3546},[3536,13218,13219,13221],{"class":3538,"line":3730},[3536,13220,5484],{"class":3649},[3536,13222,5487],{"class":3546},[3536,13224,13225,13227,13229,13231,13234,13236,13238,13240],{"class":3538,"line":3736},[3536,13226,5493],{"class":3649},[3536,13228,4474],{"class":3546},[3536,13230,4556],{"class":3584},[3536,13232,13233],{"class":3690},"\"Не вдалося створити задачу: ",[3536,13235,4562],{"class":3584},[3536,13237,11877],{"class":3546},[3536,13239,4568],{"class":3584},[3536,13241,6313],{"class":3690},[3536,13243,13244],{"class":3538,"line":4052},[3536,13245,5504],{"class":3546},[3385,13247,13248,13249,13252,13253,3398],{},"Завдяки цьому ми розділили обов'язки: роутер відповідає за HTTP-протокол, маршрутизацію та серіалізацію JSON-схем, а сервіс — за обробку даних. Крім того, під час тестування ми можемо з легкістю замінити ",[3395,13250,13251],{},"get_task_service"," на тестовий мок у словнику ",[3395,13254,4228],{},[3385,13256,13257],{},"Збережіть ваш прогрес у Git:",[3526,13259,13261],{"className":4657,"code":13260,"filename":4659,"language":4660,"meta":3532,"style":3532},"git add .\ngit commit -m \"feat: add dependency injection and service layer\"\n",[3395,13262,13263,13274],{"__ignoreMap":3532},[3536,13264,13265,13268,13271],{"class":3538,"line":3539},[3536,13266,13267],{"class":3588},"git",[3536,13269,13270],{"class":3690}," add",[3536,13272,13273],{"class":3690}," .\n",[3536,13275,13276,13278,13280,13283],{"class":3538,"line":3556},[3536,13277,13267],{"class":3588},[3536,13279,5224],{"class":3690},[3536,13281,13282],{"class":3584}," -m",[3536,13284,13285],{"class":3690}," \"feat: add dependency injection and service layer\"\n",[13287,13288,13289],"style",{},"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 .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 .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 .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 .sN1BT, html code.shiki .sN1BT{--shiki-light:#267F99;--shiki-default:#4EC9B0;--shiki-dark:#4EC9B0}html pre.shiki code .sJj4R, html code.shiki .sJj4R{--shiki-light:#098658;--shiki-default:#B5CEA8;--shiki-dark:#B5CEA8}html pre.shiki code .sjcCO, html code.shiki .sjcCO{--shiki-light:#EE0000;--shiki-default:#D7BA7D;--shiki-dark:#D7BA7D}",{"title":3532,"searchDepth":3556,"depth":3556,"links":13291},[13292,13297,13305,13313,13319,13326],{"id":3411,"depth":3556,"text":3412,"children":13293},[13294,13295,13296],{"id":3419,"depth":3563,"text":3420},{"id":3480,"depth":3563,"text":3481},{"id":4115,"depth":3563,"text":4116},{"id":4233,"depth":3556,"text":13298,"children":13299},"Механіка Depends() під капотом",[13300,13302,13303],{"id":4247,"depth":3563,"text":13301},"1. Аналіз за допомогою рефлексії (inspect)",{"id":4699,"depth":3563,"text":4700},{"id":4874,"depth":3563,"text":13304},"Кешування результатів залежностей (use_cache=True)",{"id":5028,"depth":3556,"text":5029,"children":13306},[13307,13309,13311,13312],{"id":5041,"depth":3563,"text":13308},"Управління ресурсами: Функції-залежності з yield",{"id":5671,"depth":3563,"text":13310},"Порівняння архітектурних концепцій: IDisposable в C# ↔ Генератори (yield) в Python",{"id":5836,"depth":3563,"text":5837},{"id":6459,"depth":3563,"text":6460},{"id":6653,"depth":3556,"text":6654,"children":13314},[13315,13316,13317,13318],{"id":6662,"depth":3563,"text":6663},{"id":7218,"depth":3563,"text":7219},{"id":7622,"depth":3563,"text":7623},{"id":7895,"depth":3563,"text":7896},{"id":8760,"depth":3556,"text":8761,"children":13320},[13321,13322,13324,13325],{"id":8769,"depth":3563,"text":8770},{"id":9301,"depth":3563,"text":13323},"Порівняння архітектурних концепцій: ConfigureTestServices (ASP.NET) ↔ dependency_overrides (FastAPI)",{"id":9513,"depth":3563,"text":9514},{"id":10147,"depth":3563,"text":10148},{"id":10340,"depth":3556,"text":10341,"children":13327},[13328,13329,13330],{"id":10349,"depth":3563,"text":10350},{"id":11327,"depth":3563,"text":11328},{"id":12569,"depth":3563,"text":12570},"Глибоке занурення в концепцію впровадження залежностей (Dependency Injection) у FastAPI. Розбір відмінностей від IoC-контейнерів ASP.NET Core, механіки Depends(), побудови графа залежностей та кешування результатів.","md",null,{},{"title":2634,"description":13331},"2YZWerpoUELyBpbZoFHegJ3uc6F1wAIrMG4tkFnQMCg",[13338,13340],{"title":2630,"path":2631,"stem":2632,"description":13339,"children":-1},"Глибоке занурення у проектування схем даних у FastAPI за допомогою Pydantic v2. Розбір стратегії розділення моделей для введення, виведення та оновлення, наслідування моделей та порівняння з DTO в ASP.NET Core.",{"title":2638,"path":2639,"stem":2640,"description":13341,"children":-1},"Глибоке занурення в архітектуру обробки помилок, конвеєр ASGI-middleware та налаштування безпеки CORS у FastAPI. Порівняння з підходами в ASP.NET Core.",1783248268545]