[{"data":1,"prerenderedAt":13376},["ShallowReactive",2],{"navigation_docs":3,"-python-fastapi-fastapi-pydantic-schemas":3379,"-python-fastapi-fastapi-pydantic-schemas-surround":13371},[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":2630,"body":3381,"description":13365,"extension":13366,"links":13367,"meta":13368,"navigation":3670,"path":2631,"seo":13369,"stem":2632,"__hash__":13370},"docs\u002F05.python\u002Ffastapi\u002F19.fastapi-pydantic-schemas.md",{"type":3382,"value":3383,"toc":13323},"minimark",[3384,3393,3409,3412,3415,3420,3423,3470,3477,3479,3483,3497,3500,3559,3564,3567,3608,3610,3614,3621,3624,3630,3633,4015,4019,4022,4115,4117,4121,4129,4136,4144,4147,4651,4655,4658,4688,4691,4747,4754,4759,4765,4857,4880,4884,4887,4959,4980,4982,4986,4989,4991,5003,5012,5047,5065,5092,5106,5108,5115,5118,5136,5147,5150,5173,5186,5220,5229,5242,5274,5290,5293,5320,5347,5350,5355,5370,5404,5452,5460,5469,5642,5648,6081,6085,6088,6112,6116,6119,6199,6219,6223,6228,6304,6323,6325,6329,6332,6338,6353,6356,6544,6546,6553,6556,6560,6574,6616,6631,6635,6642,6657,6669,6681,6687,6714,6717,6872,6875,6877,6881,6888,6890,6897,6907,6910,6935,6939,6946,7344,7346,7350,7356,7359,7380,7384,7394,7530,7534,7540,7793,7797,7800,7824,7827,7901,7920,7922,7926,7940,7942,7950,7953,7977,7985,8001,8059,8065,8073,8083,8115,8121,8146,8357,8361,8364,8388,8391,8395,8404,8478,8497,8501,8508,8580,8599,8601,8605,8617,8620,8648,8654,8704,8720,8722,8732,8741,8744,8873,8876,9718,9732,9736,9738,9762,9773,9831,9850,9852,9856,9859,9861,9865,9869,9887,9907,10305,10309,10323,10523,10527,10536,10545,10671,10673,10677,10680,10684,10721,10724,12347,12351,12354,12378,12384,12458,12477,12479,12483,12493,12497,12504,12507,12596,12603,12822,12828,13100,13104,13107,13280,13287,13290,13319],[3385,3386,3387,3388,3392],"p",{},"У сучасній веб-розробці валідація вхідних даних та серіалізація відповідей є критично важливими процесами. Вони безпосередньо впливають на безпеку, продуктивність та архітектурну чистоту додатку. У екосистемі FastAPI ці завдання повністю делеговані бібліотеці ",[3389,3390,3391],"strong",{},"Pydantic"," — потужному інструменту для парсингу та валідації даних за допомогою стандартних підказок типів Python (Type Hints).",[3385,3394,3395,3396,3399,3400,3404,3405,3408],{},"З виходом ",[3389,3397,3398],{},"Pydantic v2"," архітектура валідації була встановлена на новому рівні та переписана на мові Rust (",[3401,3402,3403],"code",{},"pydantic-core","), що дало прискорення процесів валідації та серіалізації від 5 до 17 разів. FastAPI, починаючи з версії ",[3401,3406,3407],{},"0.100.0",", повністю інтегрував Pydantic v2, надаючи розробникам доступ до нових оптимізованих механізмів роботи зі схемами.",[3385,3410,3411],{},"У цій статті ми детально розглянемо стратегії проектування моделей даних, розберемо наслідування схем для дотримання принципу DRY (Don't Repeat Yourself) без порушення API-контрактів, а також проведемо детальні паралелі з концепцією DTO (Data Transfer Objects), що використовується в ASP.NET Core.",[3413,3414],"hr",{},[3416,3417,3419],"h2",{"id":3418},"pydantic-v2-як-технологічний-фундамент-fastapi","Pydantic v2 як технологічний фундамент FastAPI",[3385,3421,3422],{},"FastAPI та Pydantic працюють у тісній синергії. Коли запит надходить на сервер, FastAPI автоматично виконує такі кроки:",[3424,3425,3426,3437,3446,3456],"ol",{},[3427,3428,3429,3432,3433,3436],"li",{},[3389,3430,3431],{},"Аналіз сигнатури ендпоінту",": Фреймворк досліджує вказані в параметрах функції типи даних. Якщо тип параметра є підкласом ",[3401,3434,3435],{},"pydantic.BaseModel",", FastAPI очікує отримати тіло запиту у форматі JSON.",[3427,3438,3439,3442,3443,3445],{},[3389,3440,3441],{},"Десеріалізація та валідація",": Вхідний JSON-пакет конвертується у словник Python і передається у відповідну Pydantic-модель. Rust-рушій ",[3401,3444,3403],{}," валідує структуру на відповідність типам та додатковим обмеженням (наприклад, довжині рядка, формату email).",[3427,3447,3448,3451,3452,3455],{},[3389,3449,3450],{},"Обробка помилок",": Якщо дані не пройшли валідацію, Pydantic генерує детальний опис невідповідностей. FastAPI перехоплює це виключення та автоматично повертає клієнту відповідь зі статусом ",[3401,3453,3454],{},"422 Unprocessable Entity"," у структурованому вигляді.",[3427,3457,3458,3461,3462,3465,3466,3469],{},[3389,3459,3460],{},"Генерація OpenAPI-документації",": Pydantic моделі автоматично конвертуються в об'єкти JSON Schema, які потім вбудовуються у загальну специфікацію OpenAPI (",[3401,3463,3464],{},"\u002Fdocs"," або ",[3401,3467,3468],{},"\u002Fredoc",").",[3385,3471,3472,3473,3476],{},"Завдяки використанню стандарту ",[3389,3474,3475],{},"PEP 484 (Type Hints)"," розробнику не потрібно писати окремий код для парсингу чи генерації документації — декларативний опис типів слугує єдиним джерелом правди (Single Source of Truth).",[3413,3478],{},[3416,3480,3482],{"id":3481},"стратегія-проектування-моделей-чому-один-клас-для-всього-антипатерн","Стратегія проектування моделей: Чому один клас для всього — антипатерн",[3385,3484,3485,3486,3489,3490,3493,3494,3469],{},"Початківці часто припускаються серйозної архітектурної помилки: створюють одну-єдину модель Pydantic для відображення сутності на всіх етапах її життєвого циклу. Наприклад, оголошують єдиний клас ",[3401,3487,3488],{},"User",", який містить усе — від системного ідентифікатора (",[3401,3491,3492],{},"id",") до хешу пароля (",[3401,3495,3496],{},"hashed_password",[3385,3498,3499],{},"Такий підхід призводить до трьох основних проблем:",[3501,3502,3503,3513,3533],"field-group",{},[3504,3505,3508,3509,3512],"field",{"name":3506,"type":3507},"Витік конфіденційних даних","Security Vulnerability","Якщо модель містить паролі, платіжні дані чи внутрішні системні прапорці, використання її для повернення даних клієнту (наприклад, у відповіді ",[3401,3510,3511],{},"GET \u002Fusers\u002F{id}",") призведе до випадкового витоку чутливої інформації.",[3504,3514,3517,3518,3521,3522,3524,3525,3528,3529,3532],{"name":3515,"type":3516},"Конфлікт обмежень валідації","Validation Mismatch","При створенні об'єкта (",[3401,3519,3520],{},"POST",") поле ",[3401,3523,3492],{}," ще не існує (воно генерується базою даних), тому в моделі воно має бути опціональним (",[3401,3526,3527],{},"id: int | None = None","). Проте при читанні об'єкта (",[3401,3530,3531],{},"GET",") ідентифікатор є обов'язковим. Спроба об'єднати ці вимоги в одному класі послаблює валідацію.",[3504,3534,3537,3538,3541,3542,3545,3546,3548,3549,3552,3553,3555,3556,3558],{"name":3535,"type":3536},"Складність підтримки часткових оновлень (PATCH)","State Pollution","Для методу ",[3401,3539,3540],{},"PATCH"," усі поля мають бути необов'язковими, щоб клієнт міг оновити лише одне поле (наприклад, тільки ",[3401,3543,3544],{},"email","). Якщо використовувати ту саму модель, що й для створення (",[3401,3547,3520],{},"), де поля ",[3401,3550,3551],{},"username"," та ",[3401,3554,3544],{}," є обов'язковими, ви не зможете реалізувати коректний ",[3401,3557,3540],{},".",[3560,3561,3563],"h3",{"id":3562},"розділення-за-ролями-створення-ієрархії-dto","Розділення за ролями: Створення ієрархії DTO",[3385,3565,3566],{},"Для забезпечення безпеки та гнучкості API необхідно розділяти схеми за їхнім призначенням у життєвому циклі запиту:",[3424,3568,3569,3583,3600],{},[3427,3570,3571,3574,3575,3578,3579,3582],{},[3389,3572,3573],{},"Input-моделі (Request Schemas)"," — описують дані, які надсилає клієнт. Наприклад, ",[3401,3576,3577],{},"UserCreate"," для реєстрації (містить пароль у відкритому вигляді) або ",[3401,3580,3581],{},"UserUpdate"," для оновлення профілю.",[3427,3584,3585,3588,3589,3592,3593,3595,3596,3599],{},[3389,3586,3587],{},"Output-моделі (Response Schemas)"," — описують структуру відповіді сервера. Наприклад, ",[3401,3590,3591],{},"UserRead"," (містить ",[3401,3594,3492],{},", ",[3401,3597,3598],{},"created_at",", але не містить паролів чи токенів).",[3427,3601,3602,3605,3606,3469],{},[3389,3603,3604],{},"Internal-моделі (Internal\u002FDatabase Models)"," — використовуються всередині додатку для збереження у БД або передачі між шарами сервісів (містить ",[3401,3607,3496],{},[3413,3609],{},[3416,3611,3613],{"id":3612},"порівняння-архітектурних-концепцій-aspnet-core-dtos-fastapi-pydantic-schemas","Порівняння архітектурних концепцій: ASP.NET Core DTOs ↔ FastAPI Pydantic Schemas",[3385,3615,3616,3617,3620],{},"Концепція розділення моделей на вхідні та вихідні є загальноприйнятим індустріальним стандартом. У C# \u002F ASP.NET Core для цього використовується патерн ",[3389,3618,3619],{},"DTO (Data Transfer Object)",". Розглянемо концептуальні та синтаксичні відмінності між підходами в ASP.NET Core та FastAPI.",[3385,3622,3623],{},"У ASP.NET Core ви створюєте окремі класи DTO та накладаєте валідаційні обмеження за допомогою Data Annotations (атрибутів). Мапінг між сутностями бази даних (Entity) та DTO зазвичай виконується за допомогою бібліотек на кшталт AutoMapper або вручну.",[3385,3625,3626,3627,3469],{},"У FastAPI Pydantic-моделі одночасно виконують роль DTO, валідатора та серіалізатора. Мапінг із сутностями БД (наприклад, SQLAlchemy) відбувається автоматично через режим сумісності атрибутів (",[3401,3628,3629],{},"from_attributes=True",[3385,3631,3632],{},"Порівняємо опис вхідної моделі для створення користувача в обох екосистемах:",[3634,3635,3636,3775],"code-group",{},[3637,3638,3644],"pre",{"className":3639,"code":3640,"filename":3641,"language":3642,"meta":3643,"style":3643},"language-python shiki shiki-themes light-plus dark-plus dark-plus","from pydantic import BaseModel, EmailStr, Field\n\nclass UserCreate(BaseModel):\n    # EmailStr автоматично валідує формат пошти\n    email: EmailStr\n    # Field накладає обмеження на довжину пароля\n    password: str = Field(min_length=8, max_length=100)\n    username: str = Field(min_length=3, max_length=50)\n","FastAPI (Pydantic v2)","python","",[3401,3645,3646,3665,3672,3692,3699,3705,3711,3747],{"__ignoreMap":3643},[3647,3648,3651,3655,3659,3662],"span",{"class":3649,"line":3650},"line",1,[3647,3652,3654],{"class":3653},"s8xlr","from",[3647,3656,3658],{"class":3657},"sHH4Y"," pydantic ",[3647,3660,3661],{"class":3653},"import",[3647,3663,3664],{"class":3657}," BaseModel, EmailStr, Field\n",[3647,3666,3668],{"class":3649,"line":3667},2,[3647,3669,3671],{"emptyLinePlaceholder":3670},true,"\n",[3647,3673,3675,3679,3683,3686,3689],{"class":3649,"line":3674},3,[3647,3676,3678],{"class":3677},"su1O8","class",[3647,3680,3682],{"class":3681},"sN1BT"," UserCreate",[3647,3684,3685],{"class":3657},"(",[3647,3687,3688],{"class":3681},"BaseModel",[3647,3690,3691],{"class":3657},"):\n",[3647,3693,3695],{"class":3649,"line":3694},4,[3647,3696,3698],{"class":3697},"spJ8K","    # EmailStr автоматично валідує формат пошти\n",[3647,3700,3702],{"class":3649,"line":3701},5,[3647,3703,3704],{"class":3657},"    email: EmailStr\n",[3647,3706,3708],{"class":3649,"line":3707},6,[3647,3709,3710],{"class":3697},"    # Field накладає обмеження на довжину пароля\n",[3647,3712,3714,3717,3720,3723,3727,3730,3734,3736,3739,3741,3744],{"class":3649,"line":3713},7,[3647,3715,3716],{"class":3657},"    password: ",[3647,3718,3719],{"class":3681},"str",[3647,3721,3722],{"class":3657}," = Field(",[3647,3724,3726],{"class":3725},"siwwj","min_length",[3647,3728,3729],{"class":3657},"=",[3647,3731,3733],{"class":3732},"sJj4R","8",[3647,3735,3595],{"class":3657},[3647,3737,3738],{"class":3725},"max_length",[3647,3740,3729],{"class":3657},[3647,3742,3743],{"class":3732},"100",[3647,3745,3746],{"class":3657},")\n",[3647,3748,3750,3753,3755,3757,3759,3761,3764,3766,3768,3770,3773],{"class":3649,"line":3749},8,[3647,3751,3752],{"class":3657},"    username: ",[3647,3754,3719],{"class":3681},[3647,3756,3722],{"class":3657},[3647,3758,3726],{"class":3725},[3647,3760,3729],{"class":3657},[3647,3762,3763],{"class":3732},"3",[3647,3765,3595],{"class":3657},[3647,3767,3738],{"class":3725},[3647,3769,3729],{"class":3657},[3647,3771,3772],{"class":3732},"50",[3647,3774,3746],{"class":3657},[3637,3776,3781],{"className":3777,"code":3778,"filename":3779,"language":3780,"meta":3643,"style":3643},"language-csharp shiki shiki-themes light-plus dark-plus dark-plus","using System.ComponentModel.DataAnnotations;\n\npublic class UserCreateDto\n{\n    [Required]\n    [EmailAddress]\n    public string Email { get; set; } = string.Empty;\n\n    [Required]\n    [StringLength(100, MinimumLength = 8)]\n    public string Password { get; set; } = string.Empty;\n\n    [Required]\n    [StringLength(50, MinimumLength = 3)]\n    public string Username { get; set; } = string.Empty;\n}\n","ASP.NET Core (C#)","csharp",[3401,3782,3783,3804,3808,3819,3824,3835,3844,3880,3884,3893,3918,3946,3951,3960,3981,4009],{"__ignoreMap":3643},[3647,3784,3785,3788,3791,3793,3796,3798,3801],{"class":3649,"line":3650},[3647,3786,3787],{"class":3653},"using",[3647,3789,3790],{"class":3681}," System",[3647,3792,3558],{"class":3657},[3647,3794,3795],{"class":3681},"ComponentModel",[3647,3797,3558],{"class":3657},[3647,3799,3800],{"class":3681},"DataAnnotations",[3647,3802,3803],{"class":3657},";\n",[3647,3805,3806],{"class":3649,"line":3667},[3647,3807,3671],{"emptyLinePlaceholder":3670},[3647,3809,3810,3813,3816],{"class":3649,"line":3674},[3647,3811,3812],{"class":3677},"public",[3647,3814,3815],{"class":3677}," class",[3647,3817,3818],{"class":3681}," UserCreateDto\n",[3647,3820,3821],{"class":3649,"line":3694},[3647,3822,3823],{"class":3657},"{\n",[3647,3825,3826,3829,3832],{"class":3649,"line":3701},[3647,3827,3828],{"class":3657},"    [",[3647,3830,3831],{"class":3681},"Required",[3647,3833,3834],{"class":3657},"]\n",[3647,3836,3837,3839,3842],{"class":3649,"line":3707},[3647,3838,3828],{"class":3657},[3647,3840,3841],{"class":3681},"EmailAddress",[3647,3843,3834],{"class":3657},[3647,3845,3846,3849,3852,3855,3858,3861,3864,3867,3870,3873,3875,3878],{"class":3649,"line":3713},[3647,3847,3848],{"class":3677},"    public",[3647,3850,3851],{"class":3677}," string",[3647,3853,3854],{"class":3725}," Email",[3647,3856,3857],{"class":3657}," { ",[3647,3859,3860],{"class":3677},"get",[3647,3862,3863],{"class":3657},"; ",[3647,3865,3866],{"class":3677},"set",[3647,3868,3869],{"class":3657},"; } = ",[3647,3871,3872],{"class":3677},"string",[3647,3874,3558],{"class":3657},[3647,3876,3877],{"class":3725},"Empty",[3647,3879,3803],{"class":3657},[3647,3881,3882],{"class":3649,"line":3749},[3647,3883,3671],{"emptyLinePlaceholder":3670},[3647,3885,3887,3889,3891],{"class":3649,"line":3886},9,[3647,3888,3828],{"class":3657},[3647,3890,3831],{"class":3681},[3647,3892,3834],{"class":3657},[3647,3894,3896,3898,3901,3903,3905,3907,3910,3913,3915],{"class":3649,"line":3895},10,[3647,3897,3828],{"class":3657},[3647,3899,3900],{"class":3681},"StringLength",[3647,3902,3685],{"class":3657},[3647,3904,3743],{"class":3732},[3647,3906,3595],{"class":3657},[3647,3908,3909],{"class":3725},"MinimumLength",[3647,3911,3912],{"class":3657}," = ",[3647,3914,3733],{"class":3732},[3647,3916,3917],{"class":3657},")]\n",[3647,3919,3921,3923,3925,3928,3930,3932,3934,3936,3938,3940,3942,3944],{"class":3649,"line":3920},11,[3647,3922,3848],{"class":3677},[3647,3924,3851],{"class":3677},[3647,3926,3927],{"class":3725}," Password",[3647,3929,3857],{"class":3657},[3647,3931,3860],{"class":3677},[3647,3933,3863],{"class":3657},[3647,3935,3866],{"class":3677},[3647,3937,3869],{"class":3657},[3647,3939,3872],{"class":3677},[3647,3941,3558],{"class":3657},[3647,3943,3877],{"class":3725},[3647,3945,3803],{"class":3657},[3647,3947,3949],{"class":3649,"line":3948},12,[3647,3950,3671],{"emptyLinePlaceholder":3670},[3647,3952,3954,3956,3958],{"class":3649,"line":3953},13,[3647,3955,3828],{"class":3657},[3647,3957,3831],{"class":3681},[3647,3959,3834],{"class":3657},[3647,3961,3963,3965,3967,3969,3971,3973,3975,3977,3979],{"class":3649,"line":3962},14,[3647,3964,3828],{"class":3657},[3647,3966,3900],{"class":3681},[3647,3968,3685],{"class":3657},[3647,3970,3772],{"class":3732},[3647,3972,3595],{"class":3657},[3647,3974,3909],{"class":3725},[3647,3976,3912],{"class":3657},[3647,3978,3763],{"class":3732},[3647,3980,3917],{"class":3657},[3647,3982,3984,3986,3988,3991,3993,3995,3997,3999,4001,4003,4005,4007],{"class":3649,"line":3983},15,[3647,3985,3848],{"class":3677},[3647,3987,3851],{"class":3677},[3647,3989,3990],{"class":3725}," Username",[3647,3992,3857],{"class":3657},[3647,3994,3860],{"class":3677},[3647,3996,3863],{"class":3657},[3647,3998,3866],{"class":3677},[3647,4000,3869],{"class":3657},[3647,4002,3872],{"class":3677},[3647,4004,3558],{"class":3657},[3647,4006,3877],{"class":3725},[3647,4008,3803],{"class":3657},[3647,4010,4012],{"class":3649,"line":4011},16,[3647,4013,4014],{"class":3657},"}\n",[3560,4016,4018],{"id":4017},"встановлення-залежностей-та-запуск-коду","Встановлення залежностей та запуск коду",[3385,4020,4021],{},"Для роботи з Pydantic v2 та FastAPI вам потрібні відповідні пакети. Нижче наведено інструкції для їх встановлення:",[3634,4023,4024,4052,4076],{},[3637,4025,4030],{"className":4026,"code":4027,"filename":4028,"language":4029,"meta":3643,"style":3643},"language-bash shiki shiki-themes light-plus dark-plus dark-plus","# Встановлення FastAPI та Pydantic v2 разом з веб-сервером uvicorn та валідатором email\npip install fastapi[standard] pydantic[email]\n","pip","bash",[3401,4031,4032,4037],{"__ignoreMap":3643},[3647,4033,4034],{"class":3649,"line":3650},[3647,4035,4036],{"class":3697},"# Встановлення FastAPI та Pydantic v2 разом з веб-сервером uvicorn та валідатором email\n",[3647,4038,4039,4042,4046,4049],{"class":3649,"line":3667},[3647,4040,4028],{"class":4041},"s8Opu",[3647,4043,4045],{"class":4044},"sbdoH"," install",[3647,4047,4048],{"class":4044}," fastapi[standard]",[3647,4050,4051],{"class":4044}," pydantic[email]\n",[3637,4053,4056],{"className":4026,"code":4054,"filename":4055,"language":4029,"meta":3643,"style":3643},"# Швидке встановлення за допомогою uv\nuv pip install fastapi[standard] pydantic[email]\n","uv",[3401,4057,4058,4063],{"__ignoreMap":3643},[3647,4059,4060],{"class":3649,"line":3650},[3647,4061,4062],{"class":3697},"# Швидке встановлення за допомогою uv\n",[3647,4064,4065,4067,4070,4072,4074],{"class":3649,"line":3667},[3647,4066,4055],{"class":4041},[3647,4068,4069],{"class":4044}," pip",[3647,4071,4045],{"class":4044},[3647,4073,4048],{"class":4044},[3647,4075,4051],{"class":4044},[3637,4077,4080],{"className":4026,"code":4078,"filename":4079,"language":4029,"meta":3643,"style":3643},"# Додавання залежностей у проект Poetry\npoetry add fastapi\npoetry add uvicorn\npoetry add \"pydantic[email]\"\n","poetry",[3401,4081,4082,4087,4097,4106],{"__ignoreMap":3643},[3647,4083,4084],{"class":3649,"line":3650},[3647,4085,4086],{"class":3697},"# Додавання залежностей у проект Poetry\n",[3647,4088,4089,4091,4094],{"class":3649,"line":3667},[3647,4090,4079],{"class":4041},[3647,4092,4093],{"class":4044}," add",[3647,4095,4096],{"class":4044}," fastapi\n",[3647,4098,4099,4101,4103],{"class":3649,"line":3674},[3647,4100,4079],{"class":4041},[3647,4102,4093],{"class":4044},[3647,4104,4105],{"class":4044}," uvicorn\n",[3647,4107,4108,4110,4112],{"class":3649,"line":3694},[3647,4109,4079],{"class":4041},[3647,4111,4093],{"class":4044},[3647,4113,4114],{"class":4044}," \"pydantic[email]\"\n",[3413,4116],{},[3416,4118,4120],{"id":4119},"наслідування-моделей-дотримання-принципу-dry","Наслідування моделей: Дотримання принципу DRY",[3385,4122,4123,4124,3552,4126,4128],{},"Хоча створення багатьох моделей захищає контракти API, це може призвести до дублювання коду (оскільки ",[3401,4125,3544],{},[3401,4127,3551],{}," потрібні як при створенні, так і при читанні чи оновленні). Щоб уникнути копіювання, Pydantic підтримує звичайне ООП-наслідування класів.",[3385,4130,4131,4132,4135],{},"Рекомендований шаблон проектування передбачає створення базового класу ",[3401,4133,4134],{},"UserBase",", який містить спільні для всіх представлень поля. Конкретні класи наслідують його, додаючи специфічні поля чи конфігурацію:",[3637,4137,4142],{"className":4138,"code":4140,"language":4141},[4139],"language-text","          [ BaseModel ]\n                │\n                ▼\n          [ UserBase ]  (email, username)\n           ╱        ╲\n          ▼          ▼\n   [ UserCreate ]  [ UserRead ]\n   (password)      (id, is_active, created_at)\n","text",[3401,4143,4140],{"__ignoreMap":3643},[3385,4145,4146],{},"Розглянемо практичну реалізацію цієї структури та її інтеграцію в роутер FastAPI:",[3637,4148,4151],{"className":3639,"code":4149,"filename":4150,"language":3642,"meta":3643,"style":3643},"# main.py\nfrom datetime import datetime\nfrom pydantic import BaseModel, EmailStr, ConfigDict, Field\nfrom fastapi import FastAPI, status\n\napp = FastAPI(title=\"Pydantic Inheritance Demo\")\n\n# 1. Базова модель: спільні атрибути для входу та виходу\nclass UserBase(BaseModel):\n    email: EmailStr\n    username: str = Field(..., min_length=3, max_length=50)\n\n# 2. Модель для створення користувача: додаємо пароль\nclass UserCreate(UserBase):\n    password: str = Field(..., min_length=8, description=\"Пароль у відкритому вигляді\")\n\n# 3. Модель для відповіді: додаємо ID та статус, але приховуємо пароль\nclass UserRead(UserBase):\n    id: int\n    is_active: bool = True\n    created_at: datetime\n\n    # Конфігурація Pydantic v2 для роботи з ORM (наприклад, SQLAlchemy, Tortoise)\n    # Замінює orm_mode = True з Pydantic v1\n    model_config = ConfigDict(from_attributes=True)\n\n# Симуляція бази даних\ndatabase_users: list[dict] = []\nuser_id_counter = 1\n\n@app.post(\"\u002Fusers\", response_model=UserRead, status_code=status.HTTP_201_CREATED)\nasync def create_user(user_in: UserCreate):\n    global user_id_counter\n\n    # Хешування пароля (в реальному проекті використовуйте bcrypt\u002Fargon2)\n    hashed_password = f\"secret_hash_{user_in.password}\"\n\n    # Створюємо словник для збереження в БД\n    user_db = {\n        \"id\": user_id_counter,\n        \"email\": user_in.email,\n        \"username\": user_in.username,\n        \"hashed_password\": hashed_password,\n        \"is_active\": True,\n        \"created_at\": datetime.now()\n    }\n\n    database_users.append(user_db)\n    user_id_counter += 1\n\n    # FastAPI автоматично відфільтрує hashed_password і поверне дані\n    # відповідно до структури моделі UserRead (оскільки вказано response_model)\n    return user_db\n","main.py",[3401,4152,4153,4158,4170,4181,4193,4197,4212,4216,4221,4234,4238,4263,4267,4272,4284,4310,4314,4320,4334,4346,4360,4366,4371,4377,4383,4399,4404,4410,4422,4431,4436,4461,4481,4490,4495,4501,4525,4530,4536,4542,4551,4560,4569,4578,4591,4600,4606,4611,4617,4625,4630,4636,4642],{"__ignoreMap":3643},[3647,4154,4155],{"class":3649,"line":3650},[3647,4156,4157],{"class":3697},"# main.py\n",[3647,4159,4160,4162,4165,4167],{"class":3649,"line":3667},[3647,4161,3654],{"class":3653},[3647,4163,4164],{"class":3657}," datetime ",[3647,4166,3661],{"class":3653},[3647,4168,4169],{"class":3657}," datetime\n",[3647,4171,4172,4174,4176,4178],{"class":3649,"line":3674},[3647,4173,3654],{"class":3653},[3647,4175,3658],{"class":3657},[3647,4177,3661],{"class":3653},[3647,4179,4180],{"class":3657}," BaseModel, EmailStr, ConfigDict, Field\n",[3647,4182,4183,4185,4188,4190],{"class":3649,"line":3694},[3647,4184,3654],{"class":3653},[3647,4186,4187],{"class":3657}," fastapi ",[3647,4189,3661],{"class":3653},[3647,4191,4192],{"class":3657}," FastAPI, status\n",[3647,4194,4195],{"class":3649,"line":3701},[3647,4196,3671],{"emptyLinePlaceholder":3670},[3647,4198,4199,4202,4205,4207,4210],{"class":3649,"line":3707},[3647,4200,4201],{"class":3657},"app = FastAPI(",[3647,4203,4204],{"class":3725},"title",[3647,4206,3729],{"class":3657},[3647,4208,4209],{"class":4044},"\"Pydantic Inheritance Demo\"",[3647,4211,3746],{"class":3657},[3647,4213,4214],{"class":3649,"line":3713},[3647,4215,3671],{"emptyLinePlaceholder":3670},[3647,4217,4218],{"class":3649,"line":3749},[3647,4219,4220],{"class":3697},"# 1. Базова модель: спільні атрибути для входу та виходу\n",[3647,4222,4223,4225,4228,4230,4232],{"class":3649,"line":3886},[3647,4224,3678],{"class":3677},[3647,4226,4227],{"class":3681}," UserBase",[3647,4229,3685],{"class":3657},[3647,4231,3688],{"class":3681},[3647,4233,3691],{"class":3657},[3647,4235,4236],{"class":3649,"line":3895},[3647,4237,3704],{"class":3657},[3647,4239,4240,4242,4244,4247,4249,4251,4253,4255,4257,4259,4261],{"class":3649,"line":3920},[3647,4241,3752],{"class":3657},[3647,4243,3719],{"class":3681},[3647,4245,4246],{"class":3657}," = Field(..., ",[3647,4248,3726],{"class":3725},[3647,4250,3729],{"class":3657},[3647,4252,3763],{"class":3732},[3647,4254,3595],{"class":3657},[3647,4256,3738],{"class":3725},[3647,4258,3729],{"class":3657},[3647,4260,3772],{"class":3732},[3647,4262,3746],{"class":3657},[3647,4264,4265],{"class":3649,"line":3948},[3647,4266,3671],{"emptyLinePlaceholder":3670},[3647,4268,4269],{"class":3649,"line":3953},[3647,4270,4271],{"class":3697},"# 2. Модель для створення користувача: додаємо пароль\n",[3647,4273,4274,4276,4278,4280,4282],{"class":3649,"line":3962},[3647,4275,3678],{"class":3677},[3647,4277,3682],{"class":3681},[3647,4279,3685],{"class":3657},[3647,4281,4134],{"class":3681},[3647,4283,3691],{"class":3657},[3647,4285,4286,4288,4290,4292,4294,4296,4298,4300,4303,4305,4308],{"class":3649,"line":3983},[3647,4287,3716],{"class":3657},[3647,4289,3719],{"class":3681},[3647,4291,4246],{"class":3657},[3647,4293,3726],{"class":3725},[3647,4295,3729],{"class":3657},[3647,4297,3733],{"class":3732},[3647,4299,3595],{"class":3657},[3647,4301,4302],{"class":3725},"description",[3647,4304,3729],{"class":3657},[3647,4306,4307],{"class":4044},"\"Пароль у відкритому вигляді\"",[3647,4309,3746],{"class":3657},[3647,4311,4312],{"class":3649,"line":4011},[3647,4313,3671],{"emptyLinePlaceholder":3670},[3647,4315,4317],{"class":3649,"line":4316},17,[3647,4318,4319],{"class":3697},"# 3. Модель для відповіді: додаємо ID та статус, але приховуємо пароль\n",[3647,4321,4323,4325,4328,4330,4332],{"class":3649,"line":4322},18,[3647,4324,3678],{"class":3677},[3647,4326,4327],{"class":3681}," UserRead",[3647,4329,3685],{"class":3657},[3647,4331,4134],{"class":3681},[3647,4333,3691],{"class":3657},[3647,4335,4337,4340,4343],{"class":3649,"line":4336},19,[3647,4338,4339],{"class":4041},"    id",[3647,4341,4342],{"class":3657},": ",[3647,4344,4345],{"class":3681},"int\n",[3647,4347,4349,4352,4355,4357],{"class":3649,"line":4348},20,[3647,4350,4351],{"class":3657},"    is_active: ",[3647,4353,4354],{"class":3681},"bool",[3647,4356,3912],{"class":3657},[3647,4358,4359],{"class":3677},"True\n",[3647,4361,4363],{"class":3649,"line":4362},21,[3647,4364,4365],{"class":3657},"    created_at: datetime\n",[3647,4367,4369],{"class":3649,"line":4368},22,[3647,4370,3671],{"emptyLinePlaceholder":3670},[3647,4372,4374],{"class":3649,"line":4373},23,[3647,4375,4376],{"class":3697},"    # Конфігурація Pydantic v2 для роботи з ORM (наприклад, SQLAlchemy, Tortoise)\n",[3647,4378,4380],{"class":3649,"line":4379},24,[3647,4381,4382],{"class":3697},"    # Замінює orm_mode = True з Pydantic v1\n",[3647,4384,4386,4389,4392,4394,4397],{"class":3649,"line":4385},25,[3647,4387,4388],{"class":3657},"    model_config = ConfigDict(",[3647,4390,4391],{"class":3725},"from_attributes",[3647,4393,3729],{"class":3657},[3647,4395,4396],{"class":3677},"True",[3647,4398,3746],{"class":3657},[3647,4400,4402],{"class":3649,"line":4401},26,[3647,4403,3671],{"emptyLinePlaceholder":3670},[3647,4405,4407],{"class":3649,"line":4406},27,[3647,4408,4409],{"class":3697},"# Симуляція бази даних\n",[3647,4411,4413,4416,4419],{"class":3649,"line":4412},28,[3647,4414,4415],{"class":3657},"database_users: list[",[3647,4417,4418],{"class":3681},"dict",[3647,4420,4421],{"class":3657},"] = []\n",[3647,4423,4425,4428],{"class":3649,"line":4424},29,[3647,4426,4427],{"class":3657},"user_id_counter = ",[3647,4429,4430],{"class":3732},"1\n",[3647,4432,4434],{"class":3649,"line":4433},30,[3647,4435,3671],{"emptyLinePlaceholder":3670},[3647,4437,4439,4442,4444,4447,4449,4452,4455,4458],{"class":3649,"line":4438},31,[3647,4440,4441],{"class":4041},"@app.post",[3647,4443,3685],{"class":3657},[3647,4445,4446],{"class":4044},"\"\u002Fusers\"",[3647,4448,3595],{"class":3657},[3647,4450,4451],{"class":3725},"response_model",[3647,4453,4454],{"class":3657},"=UserRead, ",[3647,4456,4457],{"class":3725},"status_code",[3647,4459,4460],{"class":3657},"=status.HTTP_201_CREATED)\n",[3647,4462,4464,4467,4470,4473,4475,4478],{"class":3649,"line":4463},32,[3647,4465,4466],{"class":3677},"async",[3647,4468,4469],{"class":3677}," def",[3647,4471,4472],{"class":4041}," create_user",[3647,4474,3685],{"class":3657},[3647,4476,4477],{"class":3725},"user_in",[3647,4479,4480],{"class":3657},": UserCreate):\n",[3647,4482,4484,4487],{"class":3649,"line":4483},33,[3647,4485,4486],{"class":3677},"    global",[3647,4488,4489],{"class":3657}," user_id_counter\n",[3647,4491,4493],{"class":3649,"line":4492},34,[3647,4494,3671],{"emptyLinePlaceholder":3670},[3647,4496,4498],{"class":3649,"line":4497},35,[3647,4499,4500],{"class":3697},"    # Хешування пароля (в реальному проекті використовуйте bcrypt\u002Fargon2)\n",[3647,4502,4504,4507,4510,4513,4516,4519,4522],{"class":3649,"line":4503},36,[3647,4505,4506],{"class":3657},"    hashed_password = ",[3647,4508,4509],{"class":3677},"f",[3647,4511,4512],{"class":4044},"\"secret_hash_",[3647,4514,4515],{"class":3677},"{",[3647,4517,4518],{"class":3657},"user_in.password",[3647,4520,4521],{"class":3677},"}",[3647,4523,4524],{"class":4044},"\"\n",[3647,4526,4528],{"class":3649,"line":4527},37,[3647,4529,3671],{"emptyLinePlaceholder":3670},[3647,4531,4533],{"class":3649,"line":4532},38,[3647,4534,4535],{"class":3697},"    # Створюємо словник для збереження в БД\n",[3647,4537,4539],{"class":3649,"line":4538},39,[3647,4540,4541],{"class":3657},"    user_db = {\n",[3647,4543,4545,4548],{"class":3649,"line":4544},40,[3647,4546,4547],{"class":4044},"        \"id\"",[3647,4549,4550],{"class":3657},": user_id_counter,\n",[3647,4552,4554,4557],{"class":3649,"line":4553},41,[3647,4555,4556],{"class":4044},"        \"email\"",[3647,4558,4559],{"class":3657},": user_in.email,\n",[3647,4561,4563,4566],{"class":3649,"line":4562},42,[3647,4564,4565],{"class":4044},"        \"username\"",[3647,4567,4568],{"class":3657},": user_in.username,\n",[3647,4570,4572,4575],{"class":3649,"line":4571},43,[3647,4573,4574],{"class":4044},"        \"hashed_password\"",[3647,4576,4577],{"class":3657},": hashed_password,\n",[3647,4579,4581,4584,4586,4588],{"class":3649,"line":4580},44,[3647,4582,4583],{"class":4044},"        \"is_active\"",[3647,4585,4342],{"class":3657},[3647,4587,4396],{"class":3677},[3647,4589,4590],{"class":3657},",\n",[3647,4592,4594,4597],{"class":3649,"line":4593},45,[3647,4595,4596],{"class":4044},"        \"created_at\"",[3647,4598,4599],{"class":3657},": datetime.now()\n",[3647,4601,4603],{"class":3649,"line":4602},46,[3647,4604,4605],{"class":3657},"    }\n",[3647,4607,4609],{"class":3649,"line":4608},47,[3647,4610,3671],{"emptyLinePlaceholder":3670},[3647,4612,4614],{"class":3649,"line":4613},48,[3647,4615,4616],{"class":3657},"    database_users.append(user_db)\n",[3647,4618,4620,4623],{"class":3649,"line":4619},49,[3647,4621,4622],{"class":3657},"    user_id_counter += ",[3647,4624,4430],{"class":3732},[3647,4626,4628],{"class":3649,"line":4627},50,[3647,4629,3671],{"emptyLinePlaceholder":3670},[3647,4631,4633],{"class":3649,"line":4632},51,[3647,4634,4635],{"class":3697},"    # FastAPI автоматично відфільтрує hashed_password і поверне дані\n",[3647,4637,4639],{"class":3649,"line":4638},52,[3647,4640,4641],{"class":3697},"    # відповідно до структури моделі UserRead (оскільки вказано response_model)\n",[3647,4643,4645,4648],{"class":3649,"line":4644},53,[3647,4646,4647],{"class":3653},"    return",[3647,4649,4650],{"class":3657}," user_db\n",[3560,4652,4654],{"id":4653},"інструкція-запуску-та-тестування-ендпоінту","Інструкція запуску та тестування ендпоінту",[3385,4656,4657],{},"Для запуску веб-сервера з кодом асинхронного додатку використовується Uvicorn. Скопіюйте відповідну команду запуску для вашої операційної системи:",[3634,4659,4660,4677],{},[3637,4661,4664],{"className":4026,"code":4662,"filename":4663,"language":4029,"meta":3643,"style":3643},"uvicorn main:app --reload\n","Linux \u002F macOS",[3401,4665,4666],{"__ignoreMap":3643},[3647,4667,4668,4671,4674],{"class":3649,"line":3650},[3647,4669,4670],{"class":4041},"uvicorn",[3647,4672,4673],{"class":4044}," main:app",[3647,4675,4676],{"class":3677}," --reload\n",[3637,4678,4682],{"className":4679,"code":4662,"filename":4680,"language":4681,"meta":3643,"style":3643},"language-powershell shiki shiki-themes light-plus dark-plus dark-plus","PowerShell","powershell",[3401,4683,4684],{"__ignoreMap":3643},[3647,4685,4686],{"class":3649,"line":3650},[3647,4687,4662],{"class":3657},[3385,4689,4690],{},"Нижче наведено вивід терміналу, що підтверджує успішний запуск сервера:",[4692,4693,4696,4707,4719,4726,4733,4740],"terminal-preview",{":cursor":4694,"title":4695},"true","Вивід Uvicorn сервера",[4697,4698,4700,4706],"div",{"className":4699},[3649],[3647,4701,4705],{"className":4702},[4703,4704],"text-blue-400","font-bold","INFO:","     Will watch for changes in these directories: ['\u002FUsers\u002Farakviel\u002FWork\u002Fkostyl.dev']",[4697,4708,4710,4713,4714,4718],{"className":4709},[3649],[3647,4711,4705],{"className":4712},[4703,4704],"     Uvicorn running on ",[3389,4715,4717],{"className":4716},[4704],"http:\u002F\u002F127.0.0.1:8000"," (Press CTRL+C to quit)",[4697,4720,4722,4725],{"className":4721},[3649],[3647,4723,4705],{"className":4724},[4703,4704],"     Started reloader process [12345] using StatReload",[4697,4727,4729,4732],{"className":4728},[3649],[3647,4730,4705],{"className":4731},[4703,4704],"     Started server process [12346]",[4697,4734,4736,4739],{"className":4735},[3649],[3647,4737,4705],{"className":4738},[4703,4704],"     Waiting for application startup.",[4697,4741,4743,4746],{"className":4742},[3649],[3647,4744,4705],{"className":4745},[4703,4704],"     Application startup complete.",[3385,4748,4749,4750,4753],{},"Після запуску ви можете перевірити роботу валідації вхідних та вихідних схем за допомогою консольних ",[3401,4751,4752],{},"cURL","-запитів.",[4755,4756,4758],"h4",{"id":4757},"сценарій-1-успішне-створення-користувача-валідні-дані","Сценарій 1: Успішне створення користувача (Валідні дані)",[3385,4760,4761,4762,4764],{},"Надішлемо коректні дані, що відповідають усім вимогам ",[3401,4763,3577],{},". Скопіюйте команду відповідно до вашої системи:",[3634,4766,4767,4808],{},[3637,4768,4770],{"className":4026,"code":4769,"filename":4663,"language":4029,"meta":3643,"style":3643},"curl -X POST http:\u002F\u002F127.0.0.1:8000\u002Fusers \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"email\": \"john@example.com\", \"password\": \"supersecurepassword123\", \"username\": \"johndoe\"}'\n",[3401,4771,4772,4790,4800],{"__ignoreMap":3643},[3647,4773,4774,4777,4780,4783,4786],{"class":3649,"line":3650},[3647,4775,4776],{"class":4041},"curl",[3647,4778,4779],{"class":3677}," -X",[3647,4781,4782],{"class":4044}," POST",[3647,4784,4785],{"class":4044}," http:\u002F\u002F127.0.0.1:8000\u002Fusers",[3647,4787,4789],{"class":4788},"sjcCO"," \\\n",[3647,4791,4792,4795,4798],{"class":3649,"line":3667},[3647,4793,4794],{"class":3677},"  -H",[3647,4796,4797],{"class":4044}," \"Content-Type: application\u002Fjson\"",[3647,4799,4789],{"class":4788},[3647,4801,4802,4805],{"class":3649,"line":3674},[3647,4803,4804],{"class":3677},"  -d",[3647,4806,4807],{"class":4044}," '{\"email\": \"john@example.com\", \"password\": \"supersecurepassword123\", \"username\": \"johndoe\"}'\n",[3637,4809,4811],{"className":4679,"code":4810,"filename":4680,"language":4681,"meta":3643,"style":3643},"curl.exe -X POST http:\u002F\u002F127.0.0.1:8000\u002Fusers `\n  -H \"Content-Type: application\u002Fjson\" `\n  -d '{\"email\": \"john@example.com\", \"password\": \"supersecurepassword123\", \"username\": \"johndoe\"}'\n",[3401,4812,4813,4838,4849],{"__ignoreMap":3643},[3647,4814,4815,4818,4821,4824,4826,4829,4832,4835],{"class":3649,"line":3650},[3647,4816,4817],{"class":4041},"curl.exe",[3647,4819,4820],{"class":3657}," -X POST http:\u002F\u002F",[3647,4822,4823],{"class":3732},"127.0",[3647,4825,3558],{"class":3657},[3647,4827,4828],{"class":3732},"0.1",[3647,4830,4831],{"class":3657},":",[3647,4833,4834],{"class":3732},"8000",[3647,4836,4837],{"class":3657},"\u002Fusers `\n",[3647,4839,4840,4843,4846],{"class":3649,"line":3667},[3647,4841,4842],{"class":3657},"  -H ",[3647,4844,4845],{"class":4044},"\"Content-Type: application\u002Fjson\"",[3647,4847,4848],{"class":3657}," `\n",[3647,4850,4851,4854],{"class":3649,"line":3674},[3647,4852,4853],{"class":3657},"  -d ",[3647,4855,4856],{"class":4044},"'{\"email\": \"john@example.com\", \"password\": \"supersecurepassword123\", \"username\": \"johndoe\"}'\n",[4692,4858,4861,4869,4873,4876],{":cursor":4859,"title":4860},"false","Консольний вивід відповіді",[4697,4862,4864],{"className":4863},[3649],[3647,4865,4868],{"className":4866},[4867,4704],"text-green-400","HTTP\u002F1.1 201 Created",[4697,4870,4872],{"className":4871},[3649],"Content-Type: application\u002Fjson",[4697,4874],{"className":4875},[3649],[4697,4877,4879],{"className":4878},[3649],"{\"email\":\"john@example.com\",\"username\":\"johndoe\",\"id\":1,\"is_active\":true,\"created_at\":\"2026-07-01T10:10:00.123456\"}",[4755,4881,4883],{"id":4882},"сценарій-2-помилка-валідації-некоректний-email-та-занадто-короткий-пароль","Сценарій 2: Помилка валідації (Некоректний email та занадто короткий пароль)",[3385,4885,4886],{},"Надішлемо некоректний email та пароль менше 8 символів. Pydantic перехопить помилку ще до запуску бізнес-логіки обробника:",[3634,4888,4889,4921],{},[3637,4890,4892],{"className":4026,"code":4891,"filename":4663,"language":4029,"meta":3643,"style":3643},"curl -X POST http:\u002F\u002F127.0.0.1:8000\u002Fusers \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"email\": \"invalid-email-format\", \"password\": \"short\", \"username\": \"jo\"}'\n",[3401,4893,4894,4906,4914],{"__ignoreMap":3643},[3647,4895,4896,4898,4900,4902,4904],{"class":3649,"line":3650},[3647,4897,4776],{"class":4041},[3647,4899,4779],{"class":3677},[3647,4901,4782],{"class":4044},[3647,4903,4785],{"class":4044},[3647,4905,4789],{"class":4788},[3647,4907,4908,4910,4912],{"class":3649,"line":3667},[3647,4909,4794],{"class":3677},[3647,4911,4797],{"class":4044},[3647,4913,4789],{"class":4788},[3647,4915,4916,4918],{"class":3649,"line":3674},[3647,4917,4804],{"class":3677},[3647,4919,4920],{"class":4044}," '{\"email\": \"invalid-email-format\", \"password\": \"short\", \"username\": \"jo\"}'\n",[3637,4922,4924],{"className":4679,"code":4923,"filename":4680,"language":4681,"meta":3643,"style":3643},"curl.exe -X POST http:\u002F\u002F127.0.0.1:8000\u002Fusers `\n  -H \"Content-Type: application\u002Fjson\" `\n  -d '{\"email\": \"invalid-email-format\", \"password\": \"short\", \"username\": \"jo\"}'\n",[3401,4925,4926,4944,4952],{"__ignoreMap":3643},[3647,4927,4928,4930,4932,4934,4936,4938,4940,4942],{"class":3649,"line":3650},[3647,4929,4817],{"class":4041},[3647,4931,4820],{"class":3657},[3647,4933,4823],{"class":3732},[3647,4935,3558],{"class":3657},[3647,4937,4828],{"class":3732},[3647,4939,4831],{"class":3657},[3647,4941,4834],{"class":3732},[3647,4943,4837],{"class":3657},[3647,4945,4946,4948,4950],{"class":3649,"line":3667},[3647,4947,4842],{"class":3657},[3647,4949,4845],{"class":4044},[3647,4951,4848],{"class":3657},[3647,4953,4954,4956],{"class":3649,"line":3674},[3647,4955,4853],{"class":3657},[3647,4957,4958],{"class":4044},"'{\"email\": \"invalid-email-format\", \"password\": \"short\", \"username\": \"jo\"}'\n",[4692,4960,4962,4970,4973,4976],{":cursor":4859,"title":4961},"Консольний вивід відповіді помилки",[4697,4963,4965],{"className":4964},[3649],[3647,4966,4969],{"className":4967},[4968,4704],"text-rose-400","HTTP\u002F1.1 422 Unprocessable Entity",[4697,4971,4872],{"className":4972},[3649],[4697,4974],{"className":4975},[3649],[4697,4977,4979],{"className":4978},[3649],"{\"detail\":[{\"type\":\"value_error\",\"loc\":[\"body\",\"email\"],\"msg\":\"value is not a valid email address: An email address must have an @-sign.\",\"input\":\"invalid-email-format\"},{\"type\":\"string_too_short\",\"loc\":[\"body\",\"password\"],\"msg\":\"String should have at least 8 characters\",\"input\":\"short\",\"ctx\":{\"min_length\":8}},{\"type\":\"string_too_short\",\"loc\":[\"body\",\"username\"],\"msg\":\"String should have at least 3 characters\",\"input\":\"jo\",\"ctx\":{\"min_length\":3}}]}",[3413,4981],{},[3416,4983,4985],{"id":4984},"частина-ii-часткові-оновлення-та-вкладені-структури","Частина II: Часткові оновлення та вкладені структури",[3385,4987,4988],{},"У реальних API-інтерфейсах обмін даними рідко обмежується простими пласькими структурами. Клієнти часто надсилають запити на оновлення окремих полів ресурсу або запитують складні ієрархічні об'єкти (наприклад, замовлення разом із переліком товарів). Pydantic v2 надає витончені інструменти для вирішення цих завдань за допомогою параметрів експорту моделей, рекурсивної валідації та строкових посилань.",[3413,4990],{},[3560,4992,4994,4995,4998,4999,5002],{"id":4993},"сучасний-синтаксис-optional-чи-none","Сучасний синтаксис: ",[3401,4996,4997],{},"Optional"," чи ",[3401,5000,5001],{},"| None","?",[3385,5004,5005,5006,5008,5009,4831],{},"У попередніх версіях Python та Pydantic v1 для оголошення опціональних полів імпортували ",[3401,5007,4997],{}," з модуля ",[3401,5010,5011],{},"typing",[3637,5013,5015],{"className":3639,"code":5014,"filename":4150,"language":3642,"meta":3643,"style":3643},"# Застарілий синтаксис (PEP 484)\nfrom typing import Optional\nemail: Optional[str] = None\n",[3401,5016,5017,5022,5034],{"__ignoreMap":3643},[3647,5018,5019],{"class":3649,"line":3650},[3647,5020,5021],{"class":3697},"# Застарілий синтаксис (PEP 484)\n",[3647,5023,5024,5026,5029,5031],{"class":3649,"line":3667},[3647,5025,3654],{"class":3653},[3647,5027,5028],{"class":3657}," typing ",[3647,5030,3661],{"class":3653},[3647,5032,5033],{"class":3657}," Optional\n",[3647,5035,5036,5039,5041,5044],{"class":3649,"line":3674},[3647,5037,5038],{"class":3657},"email: Optional[",[3647,5040,3719],{"class":3681},[3647,5042,5043],{"class":3657},"] = ",[3647,5045,5046],{"class":3677},"None\n",[3385,5048,5049,5050,5053,5054,5057,5058,5061,5062,4831],{},"Починаючи з ",[3389,5051,5052],{},"Python 3.10+ (PEP 604)",", було введено оператор об'єднання типів за допомогою вертикальної риски ",[3401,5055,5056],{},"|",". Тепер замість ",[3401,5059,5060],{},"Optional[T]"," рекомендується використовувати сучасний та чистіший синтаксис ",[3401,5063,5064],{},"T | None",[3637,5066,5068],{"className":3639,"code":5067,"filename":4150,"language":3642,"meta":3643,"style":3643},"# Сучасний синтаксис (PEP 604)\nemail: str | None = None\n",[3401,5069,5070,5075],{"__ignoreMap":3643},[3647,5071,5072],{"class":3649,"line":3650},[3647,5073,5074],{"class":3697},"# Сучасний синтаксис (PEP 604)\n",[3647,5076,5077,5080,5082,5085,5088,5090],{"class":3649,"line":3667},[3647,5078,5079],{"class":3657},"email: ",[3647,5081,3719],{"class":3681},[3647,5083,5084],{"class":3657}," | ",[3647,5086,5087],{"class":3677},"None",[3647,5089,3912],{"class":3657},[3647,5091,5046],{"class":3677},[5093,5094,5095,5096,5099,5100,5102,5103,5105],"note",{},"FastAPI та Pydantic v2 повністю підтримують PEP 604. Використання ",[3401,5097,5098],{},"str | None"," усуває необхідність імпорту з модуля ",[3401,5101,5011],{},", полегшує читання коду та робить його лаконічнішим. ",[3401,5104,4997],{}," залишається в мові для зворотної сумісності, але у новому коді його використовувати не варто.",[3413,5107],{},[3560,5109,5111,5112,5114],{"id":5110},"часткові-оновлення-patch-та-робота-з-optional-полями","Часткові оновлення (PATCH) та робота з ",[3401,5113,4997],{}," полями",[3385,5116,5117],{},"В архітектурі REST для оновлення ресурсу використовуються два методи:",[5119,5120,5121,5129],"ul",{},[3427,5122,5123,5128],{},[3389,5124,5125],{},[3401,5126,5127],{},"PUT"," — повне заміщення ресурсу. Клієнт зобов'язаний передати всі обов'язкові поля. Якщо якесь поле не передане, воно перезаписується значенням за замовчуванням або скидається.",[3427,5130,5131,5135],{},[3389,5132,5133],{},[3401,5134,3540],{}," — часткове оновлення. Клієнт передає лише ті поля, які він хоче змінити. Інші поля сутності в базі даних залишаються незмінними.",[3385,5137,5138,5139,5141,5142,5144,5145,3558],{},"Для реалізації методу ",[3401,5140,3540],{}," у Pydantic створюється спеціальна схема (наприклад, ",[3401,5143,3581],{},"), де всі поля оголошуються як опціональні та мають значення за замовчуванням ",[3401,5146,5087],{},[3385,5148,5149],{},"Проте на практиці виникає проблема: як розрізнити два випадки?",[3424,5151,5152,5159],{},[3427,5153,5154,5155,5158],{},"Клієнт ",[3389,5156,5157],{},"не передав"," поле в JSON-пакеті (значення має залишитися незмінним у БД).",[3427,5160,5154,5161,5164,5165,5168,5169,5172],{},[3389,5162,5163],{},"явно передав"," поле зі значенням ",[3401,5166,5167],{},"null"," (наприклад, ",[3401,5170,5171],{},"\"bio\": null","), щоб стерти існуюче значення в БД.",[3385,5174,5175,5176,5179,5180,5185],{},"У Pydantic v1 для цього використовувався метод ",[3401,5177,5178],{},".dict(exclude_unset=True)",". У Pydantic v2 цей метод застарів, і замість нього використовується новий метод ",[3389,5181,5182],{},[3401,5183,5184],{},".model_dump()"," разом із наступними конфігураційними параметрами:",[3501,5187,5188,5199,5203],{},[3504,5189,5192,5193,5196,5197,4753],{"name":5190,"type":4354,"default":5191},"exclude_unset=True","False","Включає у вихідний словник лише ті поля, які були ",[3389,5194,5195],{},"явно присутні"," у вхідному JSON-пакеті від клієнта. Це головний інструмент для реалізації ",[3401,5198,3540],{},[3504,5200,5202],{"name":5201,"type":4354,"default":5191},"exclude_defaults=True","Виключає поля, значення яких дорівнюють їхнім значенням за замовчуванням (незалежно від того, були вони передані клієнтом чи ні).",[3504,5204,5206,5207,5209,5210,5212,5213,5216,5217,5219],{"name":5205,"type":4354,"default":5191},"exclude_none=True","Видаляє зі словника всі поля, значення яких дорівнюють ",[3401,5208,5087],{},". Для ",[3401,5211,3540],{},"-запитів цей параметр зазвичай ",[3389,5214,5215],{},"не використовується",", оскільки він не дозволяє клієнту явно встановити поле у ",[3401,5218,5167],{}," в базі даних.",[4755,5221,5223,5224,5226,5227],{"id":5222},"проблема-невалідності-null-як-дозволити-опускання-поля-але-заборонити-передачу-null","Проблема невалідності ",[3401,5225,5167],{},": як дозволити опускання поля, але заборонити передачу ",[3401,5228,5167],{},[3385,5230,5231,5232,5234,5235,5238,5239,5241],{},"Розглянемо випадок, коли поле ",[3401,5233,3551],{}," у базі даних є обов'язковим (",[3401,5236,5237],{},"NOT NULL","), але при ",[3401,5240,3540],{},"-запиті клієнт має право його опустити (не передавати). Якщо ми опишемо модель так:",[3637,5243,5245],{"className":3639,"code":5244,"filename":4150,"language":3642,"meta":3643,"style":3643},"class UserUpdate(BaseModel):\n    username: str | None = None\n",[3401,5246,5247,5260],{"__ignoreMap":3643},[3647,5248,5249,5251,5254,5256,5258],{"class":3649,"line":3650},[3647,5250,3678],{"class":3677},[3647,5252,5253],{"class":3681}," UserUpdate",[3647,5255,3685],{"class":3657},[3647,5257,3688],{"class":3681},[3647,5259,3691],{"class":3657},[3647,5261,5262,5264,5266,5268,5270,5272],{"class":3649,"line":3667},[3647,5263,3752],{"class":3657},[3647,5265,3719],{"class":3681},[3647,5267,5084],{"class":3657},[3647,5269,5087],{"class":3677},[3647,5271,3912],{"class":3657},[3647,5273,5046],{"class":3677},[3385,5275,5276,5277,5280,5281,5283,5284,5286,5287,5289],{},"При надсиланні запиту ",[3401,5278,5279],{},"{}"," (порожній словник) поле ",[3401,5282,3551],{}," прийме дефолтне значення ",[3401,5285,5087],{},". Завдяки ",[3401,5288,5190],{}," це поле не потрапить у словник оновлення, і база даних не зміниться.",[3385,5291,5292],{},"Але що станеться, якщо клієнт надішле запит:",[3637,5294,5299],{"className":5295,"code":5296,"filename":5297,"language":5298,"meta":3643,"style":3643},"language-json shiki shiki-themes light-plus dark-plus dark-plus","{\n    \"username\": null\n}\n","data.json","json",[3401,5300,5301,5305,5316],{"__ignoreMap":3643},[3647,5302,5303],{"class":3649,"line":3650},[3647,5304,3823],{"class":3657},[3647,5306,5307,5311,5313],{"class":3649,"line":3667},[3647,5308,5310],{"class":5309},"sLwNe","    \"username\"",[3647,5312,4342],{"class":3657},[3647,5314,5315],{"class":3677},"null\n",[3647,5317,5318],{"class":3649,"line":3674},[3647,5319,4014],{"class":3657},[3385,5321,5322,5323,5325,5326,5328,5329,5332,5333,5336,5337,5339,5340,5342,5343,5346],{},"Оскільки тип поля оголошено як ",[3401,5324,5098],{},", Pydantic пропустить цей запит як валідний, запише значення ",[3401,5327,5087],{}," у об'єкт, а після виклику ",[3401,5330,5331],{},"model_dump(exclude_unset=True)"," ми отримаємо ",[3401,5334,5335],{},"{\"username\": None}",". Спроба записати ",[3401,5338,5087],{}," (NULL) у колонку бази даних ",[3401,5341,3551],{}," призведе до помилки ",[3401,5344,5345],{},"IntegrityError"," на рівні СКБД.",[3385,5348,5349],{},"Для вирішення цієї проблеми у Pydantic v2 є два підходи:",[5351,5352,5354],"h5",{"id":5353},"_1-неузгодженість-типів-із-дефолтом-type-default-mismatch","1. Неузгодженість типів із дефолтом (Type-Default Mismatch)",[3385,5356,5357,5358,5360,5361,5363,5364,5366,5367,4831],{},"Ми оголошуємо тип поля як ",[3401,5359,3719],{}," (без ",[3401,5362,5001],{},"), але призначаємо йому дефолтне значення ",[3401,5365,5087],{}," за допомогою класу ",[3401,5368,5369],{},"Field",[3637,5371,5373],{"className":3639,"code":5372,"filename":4150,"language":3642,"meta":3643,"style":3643},"class UserUpdate(BaseModel):\n    username: str = Field(default=None)\n",[3401,5374,5375,5387],{"__ignoreMap":3643},[3647,5376,5377,5379,5381,5383,5385],{"class":3649,"line":3650},[3647,5378,3678],{"class":3677},[3647,5380,5253],{"class":3681},[3647,5382,3685],{"class":3657},[3647,5384,3688],{"class":3681},[3647,5386,3691],{"class":3657},[3647,5388,5389,5391,5393,5395,5398,5400,5402],{"class":3649,"line":3667},[3647,5390,3752],{"class":3657},[3647,5392,3719],{"class":3681},[3647,5394,3722],{"class":3657},[3647,5396,5397],{"class":3725},"default",[3647,5399,3729],{"class":3657},[3647,5401,5087],{"class":3677},[3647,5403,3746],{"class":3657},[3385,5405,5406,5410,5411,5414,5415,5418,5419,5421,5422,5425,5426,5428,5429,5431,5432,5435,5436,5439,5440,4998,5443,5446,5447,5449,5450,3558],{},[5407,5408,5409],"em",{},"Як це працює?"," Pydantic за замовчуванням ",[3389,5412,5413],{},"не валідує"," дефолтні значення при ініціалізації моделі без параметрів. Якщо клієнт опускає ",[3401,5416,5417],{},"\"username\"",", поле приймає значення ",[3401,5420,5087],{},". Проте, якщо клієнт явно передає ",[3401,5423,5424],{},"\"username\": null",", Pydantic валідує це вхідне значення проти вказаного типу ",[3401,5427,3719],{},". Оскільки ",[3401,5430,5087],{}," не є рядком, валідація впаде з помилкою ",[3401,5433,5434],{},"ValidationError: Input should be a valid string",".\n",[5407,5437,5438],{},"Недолік",": Статичні аналізатори кодів (наприклад, ",[3401,5441,5442],{},"mypy",[3401,5444,5445],{},"pyright",") покажуть попередження, оскільки типу ",[3401,5448,3719],{}," присвоюється значення ",[3401,5451,5087],{},[5351,5453,5455,5456,5459],{"id":5454},"_2-використання-валідатора-поля-field_validator-рекомендований-і-типізовано-безпечний-шлях","2. Використання валідатора поля (",[3401,5457,5458],{},"@field_validator",") — рекомендований і типізовано безпечний шлях",[3385,5461,5462,5463,5465,5466,5468],{},"Ми зберігаємо тип ",[3401,5464,5098],{},", але явно забороняємо передачу ",[3401,5467,5087],{}," на рівні валідатора:",[3637,5470,5472],{"className":3639,"code":5471,"filename":4150,"language":3642,"meta":3643,"style":3643},"from pydantic import BaseModel, Field, field_validator\n\nclass UserUpdate(BaseModel):\n    username: str | None = Field(default=None, min_length=3, max_length=50)\n\n    @field_validator(\"username\")\n    @classmethod\n    def prevent_null(cls, value: str | None) -> str | None:\n        # Якщо поле присутнє в запиті, воно не повинно бути None\n        if value is None:\n            raise ValueError(\"Поле username не може бути null (скинути значення не можна)\")\n        return value\n",[3401,5473,5474,5485,5489,5501,5537,5541,5552,5560,5598,5603,5619,5634],{"__ignoreMap":3643},[3647,5475,5476,5478,5480,5482],{"class":3649,"line":3650},[3647,5477,3654],{"class":3653},[3647,5479,3658],{"class":3657},[3647,5481,3661],{"class":3653},[3647,5483,5484],{"class":3657}," BaseModel, Field, field_validator\n",[3647,5486,5487],{"class":3649,"line":3667},[3647,5488,3671],{"emptyLinePlaceholder":3670},[3647,5490,5491,5493,5495,5497,5499],{"class":3649,"line":3674},[3647,5492,3678],{"class":3677},[3647,5494,5253],{"class":3681},[3647,5496,3685],{"class":3657},[3647,5498,3688],{"class":3681},[3647,5500,3691],{"class":3657},[3647,5502,5503,5505,5507,5509,5511,5513,5515,5517,5519,5521,5523,5525,5527,5529,5531,5533,5535],{"class":3649,"line":3694},[3647,5504,3752],{"class":3657},[3647,5506,3719],{"class":3681},[3647,5508,5084],{"class":3657},[3647,5510,5087],{"class":3677},[3647,5512,3722],{"class":3657},[3647,5514,5397],{"class":3725},[3647,5516,3729],{"class":3657},[3647,5518,5087],{"class":3677},[3647,5520,3595],{"class":3657},[3647,5522,3726],{"class":3725},[3647,5524,3729],{"class":3657},[3647,5526,3763],{"class":3732},[3647,5528,3595],{"class":3657},[3647,5530,3738],{"class":3725},[3647,5532,3729],{"class":3657},[3647,5534,3772],{"class":3732},[3647,5536,3746],{"class":3657},[3647,5538,5539],{"class":3649,"line":3701},[3647,5540,3671],{"emptyLinePlaceholder":3670},[3647,5542,5543,5546,5548,5550],{"class":3649,"line":3707},[3647,5544,5545],{"class":4041},"    @field_validator",[3647,5547,3685],{"class":3657},[3647,5549,5417],{"class":4044},[3647,5551,3746],{"class":3657},[3647,5553,5554,5557],{"class":3649,"line":3713},[3647,5555,5556],{"class":4041},"    @",[3647,5558,5559],{"class":3681},"classmethod\n",[3647,5561,5562,5565,5568,5570,5573,5575,5578,5580,5582,5584,5586,5589,5591,5593,5595],{"class":3649,"line":3749},[3647,5563,5564],{"class":3677},"    def",[3647,5566,5567],{"class":4041}," prevent_null",[3647,5569,3685],{"class":3657},[3647,5571,5572],{"class":3725},"cls",[3647,5574,3595],{"class":3657},[3647,5576,5577],{"class":3725},"value",[3647,5579,4342],{"class":3657},[3647,5581,3719],{"class":3681},[3647,5583,5084],{"class":3657},[3647,5585,5087],{"class":3677},[3647,5587,5588],{"class":3657},") -> ",[3647,5590,3719],{"class":3681},[3647,5592,5084],{"class":3657},[3647,5594,5087],{"class":3677},[3647,5596,5597],{"class":3657},":\n",[3647,5599,5600],{"class":3649,"line":3886},[3647,5601,5602],{"class":3697},"        # Якщо поле присутнє в запиті, воно не повинно бути None\n",[3647,5604,5605,5608,5611,5614,5617],{"class":3649,"line":3895},[3647,5606,5607],{"class":3653},"        if",[3647,5609,5610],{"class":3657}," value ",[3647,5612,5613],{"class":3677},"is",[3647,5615,5616],{"class":3677}," None",[3647,5618,5597],{"class":3657},[3647,5620,5621,5624,5627,5629,5632],{"class":3649,"line":3920},[3647,5622,5623],{"class":3653},"            raise",[3647,5625,5626],{"class":3681}," ValueError",[3647,5628,3685],{"class":3657},[3647,5630,5631],{"class":4044},"\"Поле username не може бути null (скинути значення не можна)\"",[3647,5633,3746],{"class":3657},[3647,5635,5636,5639],{"class":3649,"line":3948},[3647,5637,5638],{"class":3653},"        return",[3647,5640,5641],{"class":3657}," value\n",[3385,5643,5644,5645,5647],{},"Нижче наведено робочий приклад ",[3401,5646,3540],{},"-ендпоінту з вирішенням цієї проблеми:",[3637,5649,5652],{"className":3639,"code":5650,"filename":5651,"language":3642,"meta":3643,"style":3643},"# patch_demo.py\nfrom pydantic import BaseModel, EmailStr, Field, field_validator\nfrom fastapi import FastAPI, HTTPException, status\n\napp = FastAPI()\n\n# Симуляція бази даних в пам'яті\ndb_user = {\n    \"id\": 1,\n    \"email\": \"old_email@example.com\",\n    \"username\": \"old_username\",\n    \"bio\": \"Стара біографія\"\n}\n\nclass UserUpdate(BaseModel):\n    email: EmailStr | None = None\n    username: str | None = Field(None, min_length=3, max_length=50)\n    bio: str | None = Field(None, max_length=500) # Біографію скинути в null ДОЗВОЛЕНО\n\n    @field_validator(\"username\")\n    @classmethod\n    def prevent_null(cls, value: str | None) -> str | None:\n        if value is None:\n            raise ValueError(\"Поле username не може бути null\")\n        return value\n\n@app.patch(\"\u002Fusers\u002F{user_id}\", status_code=status.HTTP_200_OK)\nasync def update_user(user_id: int, user_in: UserUpdate):\n    if user_id != db_user[\"id\"]:\n        raise HTTPException(status_code=404, detail=\"Користувача не знайдено\")\n\n    # Отримуємо тільки явно передані клієнтом поля\n    update_data = user_in.model_dump(exclude_unset=True)\n\n    # Оновлюємо нашу симульовану базу даних\n    for key, value in update_data.items():\n        db_user[key] = value\n\n    return db_user\n","patch_demo.py",[3401,5653,5654,5659,5670,5681,5685,5690,5694,5699,5704,5716,5728,5739,5749,5753,5757,5769,5780,5812,5842,5846,5856,5862,5894,5906,5919,5925,5929,5952,5978,5992,6019,6023,6028,6042,6046,6051,6065,6070,6074],{"__ignoreMap":3643},[3647,5655,5656],{"class":3649,"line":3650},[3647,5657,5658],{"class":3697},"# patch_demo.py\n",[3647,5660,5661,5663,5665,5667],{"class":3649,"line":3667},[3647,5662,3654],{"class":3653},[3647,5664,3658],{"class":3657},[3647,5666,3661],{"class":3653},[3647,5668,5669],{"class":3657}," BaseModel, EmailStr, Field, field_validator\n",[3647,5671,5672,5674,5676,5678],{"class":3649,"line":3674},[3647,5673,3654],{"class":3653},[3647,5675,4187],{"class":3657},[3647,5677,3661],{"class":3653},[3647,5679,5680],{"class":3657}," FastAPI, HTTPException, status\n",[3647,5682,5683],{"class":3649,"line":3694},[3647,5684,3671],{"emptyLinePlaceholder":3670},[3647,5686,5687],{"class":3649,"line":3701},[3647,5688,5689],{"class":3657},"app = FastAPI()\n",[3647,5691,5692],{"class":3649,"line":3707},[3647,5693,3671],{"emptyLinePlaceholder":3670},[3647,5695,5696],{"class":3649,"line":3713},[3647,5697,5698],{"class":3697},"# Симуляція бази даних в пам'яті\n",[3647,5700,5701],{"class":3649,"line":3749},[3647,5702,5703],{"class":3657},"db_user = {\n",[3647,5705,5706,5709,5711,5714],{"class":3649,"line":3886},[3647,5707,5708],{"class":4044},"    \"id\"",[3647,5710,4342],{"class":3657},[3647,5712,5713],{"class":3732},"1",[3647,5715,4590],{"class":3657},[3647,5717,5718,5721,5723,5726],{"class":3649,"line":3895},[3647,5719,5720],{"class":4044},"    \"email\"",[3647,5722,4342],{"class":3657},[3647,5724,5725],{"class":4044},"\"old_email@example.com\"",[3647,5727,4590],{"class":3657},[3647,5729,5730,5732,5734,5737],{"class":3649,"line":3920},[3647,5731,5310],{"class":4044},[3647,5733,4342],{"class":3657},[3647,5735,5736],{"class":4044},"\"old_username\"",[3647,5738,4590],{"class":3657},[3647,5740,5741,5744,5746],{"class":3649,"line":3948},[3647,5742,5743],{"class":4044},"    \"bio\"",[3647,5745,4342],{"class":3657},[3647,5747,5748],{"class":4044},"\"Стара біографія\"\n",[3647,5750,5751],{"class":3649,"line":3953},[3647,5752,4014],{"class":3657},[3647,5754,5755],{"class":3649,"line":3962},[3647,5756,3671],{"emptyLinePlaceholder":3670},[3647,5758,5759,5761,5763,5765,5767],{"class":3649,"line":3983},[3647,5760,3678],{"class":3677},[3647,5762,5253],{"class":3681},[3647,5764,3685],{"class":3657},[3647,5766,3688],{"class":3681},[3647,5768,3691],{"class":3657},[3647,5770,5771,5774,5776,5778],{"class":3649,"line":4011},[3647,5772,5773],{"class":3657},"    email: EmailStr | ",[3647,5775,5087],{"class":3677},[3647,5777,3912],{"class":3657},[3647,5779,5046],{"class":3677},[3647,5781,5782,5784,5786,5788,5790,5792,5794,5796,5798,5800,5802,5804,5806,5808,5810],{"class":3649,"line":4316},[3647,5783,3752],{"class":3657},[3647,5785,3719],{"class":3681},[3647,5787,5084],{"class":3657},[3647,5789,5087],{"class":3677},[3647,5791,3722],{"class":3657},[3647,5793,5087],{"class":3677},[3647,5795,3595],{"class":3657},[3647,5797,3726],{"class":3725},[3647,5799,3729],{"class":3657},[3647,5801,3763],{"class":3732},[3647,5803,3595],{"class":3657},[3647,5805,3738],{"class":3725},[3647,5807,3729],{"class":3657},[3647,5809,3772],{"class":3732},[3647,5811,3746],{"class":3657},[3647,5813,5814,5817,5819,5821,5823,5825,5827,5829,5831,5833,5836,5839],{"class":3649,"line":4322},[3647,5815,5816],{"class":3657},"    bio: ",[3647,5818,3719],{"class":3681},[3647,5820,5084],{"class":3657},[3647,5822,5087],{"class":3677},[3647,5824,3722],{"class":3657},[3647,5826,5087],{"class":3677},[3647,5828,3595],{"class":3657},[3647,5830,3738],{"class":3725},[3647,5832,3729],{"class":3657},[3647,5834,5835],{"class":3732},"500",[3647,5837,5838],{"class":3657},") ",[3647,5840,5841],{"class":3697},"# Біографію скинути в null ДОЗВОЛЕНО\n",[3647,5843,5844],{"class":3649,"line":4336},[3647,5845,3671],{"emptyLinePlaceholder":3670},[3647,5847,5848,5850,5852,5854],{"class":3649,"line":4348},[3647,5849,5545],{"class":4041},[3647,5851,3685],{"class":3657},[3647,5853,5417],{"class":4044},[3647,5855,3746],{"class":3657},[3647,5857,5858,5860],{"class":3649,"line":4362},[3647,5859,5556],{"class":4041},[3647,5861,5559],{"class":3681},[3647,5863,5864,5866,5868,5870,5872,5874,5876,5878,5880,5882,5884,5886,5888,5890,5892],{"class":3649,"line":4368},[3647,5865,5564],{"class":3677},[3647,5867,5567],{"class":4041},[3647,5869,3685],{"class":3657},[3647,5871,5572],{"class":3725},[3647,5873,3595],{"class":3657},[3647,5875,5577],{"class":3725},[3647,5877,4342],{"class":3657},[3647,5879,3719],{"class":3681},[3647,5881,5084],{"class":3657},[3647,5883,5087],{"class":3677},[3647,5885,5588],{"class":3657},[3647,5887,3719],{"class":3681},[3647,5889,5084],{"class":3657},[3647,5891,5087],{"class":3677},[3647,5893,5597],{"class":3657},[3647,5895,5896,5898,5900,5902,5904],{"class":3649,"line":4373},[3647,5897,5607],{"class":3653},[3647,5899,5610],{"class":3657},[3647,5901,5613],{"class":3677},[3647,5903,5616],{"class":3677},[3647,5905,5597],{"class":3657},[3647,5907,5908,5910,5912,5914,5917],{"class":3649,"line":4379},[3647,5909,5623],{"class":3653},[3647,5911,5626],{"class":3681},[3647,5913,3685],{"class":3657},[3647,5915,5916],{"class":4044},"\"Поле username не може бути null\"",[3647,5918,3746],{"class":3657},[3647,5920,5921,5923],{"class":3649,"line":4385},[3647,5922,5638],{"class":3653},[3647,5924,5641],{"class":3657},[3647,5926,5927],{"class":3649,"line":4401},[3647,5928,3671],{"emptyLinePlaceholder":3670},[3647,5930,5931,5934,5936,5939,5942,5945,5947,5949],{"class":3649,"line":4406},[3647,5932,5933],{"class":4041},"@app.patch",[3647,5935,3685],{"class":3657},[3647,5937,5938],{"class":4044},"\"\u002Fusers\u002F",[3647,5940,5941],{"class":3677},"{user_id}",[3647,5943,5944],{"class":4044},"\"",[3647,5946,3595],{"class":3657},[3647,5948,4457],{"class":3725},[3647,5950,5951],{"class":3657},"=status.HTTP_200_OK)\n",[3647,5953,5954,5956,5958,5961,5963,5966,5968,5971,5973,5975],{"class":3649,"line":4412},[3647,5955,4466],{"class":3677},[3647,5957,4469],{"class":3677},[3647,5959,5960],{"class":4041}," update_user",[3647,5962,3685],{"class":3657},[3647,5964,5965],{"class":3725},"user_id",[3647,5967,4342],{"class":3657},[3647,5969,5970],{"class":3681},"int",[3647,5972,3595],{"class":3657},[3647,5974,4477],{"class":3725},[3647,5976,5977],{"class":3657},": UserUpdate):\n",[3647,5979,5980,5983,5986,5989],{"class":3649,"line":4424},[3647,5981,5982],{"class":3653},"    if",[3647,5984,5985],{"class":3657}," user_id != db_user[",[3647,5987,5988],{"class":4044},"\"id\"",[3647,5990,5991],{"class":3657},"]:\n",[3647,5993,5994,5997,6000,6002,6004,6007,6009,6012,6014,6017],{"class":3649,"line":4433},[3647,5995,5996],{"class":3653},"        raise",[3647,5998,5999],{"class":3657}," HTTPException(",[3647,6001,4457],{"class":3725},[3647,6003,3729],{"class":3657},[3647,6005,6006],{"class":3732},"404",[3647,6008,3595],{"class":3657},[3647,6010,6011],{"class":3725},"detail",[3647,6013,3729],{"class":3657},[3647,6015,6016],{"class":4044},"\"Користувача не знайдено\"",[3647,6018,3746],{"class":3657},[3647,6020,6021],{"class":3649,"line":4438},[3647,6022,3671],{"emptyLinePlaceholder":3670},[3647,6024,6025],{"class":3649,"line":4463},[3647,6026,6027],{"class":3697},"    # Отримуємо тільки явно передані клієнтом поля\n",[3647,6029,6030,6033,6036,6038,6040],{"class":3649,"line":4483},[3647,6031,6032],{"class":3657},"    update_data = user_in.model_dump(",[3647,6034,6035],{"class":3725},"exclude_unset",[3647,6037,3729],{"class":3657},[3647,6039,4396],{"class":3677},[3647,6041,3746],{"class":3657},[3647,6043,6044],{"class":3649,"line":4492},[3647,6045,3671],{"emptyLinePlaceholder":3670},[3647,6047,6048],{"class":3649,"line":4497},[3647,6049,6050],{"class":3697},"    # Оновлюємо нашу симульовану базу даних\n",[3647,6052,6053,6056,6059,6062],{"class":3649,"line":4503},[3647,6054,6055],{"class":3653},"    for",[3647,6057,6058],{"class":3657}," key, value ",[3647,6060,6061],{"class":3653},"in",[3647,6063,6064],{"class":3657}," update_data.items():\n",[3647,6066,6067],{"class":3649,"line":4527},[3647,6068,6069],{"class":3657},"        db_user[key] = value\n",[3647,6071,6072],{"class":3649,"line":4532},[3647,6073,3671],{"emptyLinePlaceholder":3670},[3647,6075,6076,6078],{"class":3649,"line":4538},[3647,6077,4647],{"class":3653},[3647,6079,6080],{"class":3657}," db_user\n",[3560,6082,6084],{"id":6083},"запуск-та-тестування-patch-ендпоінту","Запуск та тестування PATCH-ендпоінту",[3385,6086,6087],{},"Запустіть додаток через Uvicorn:",[3634,6089,6090,6104],{},[3637,6091,6093],{"className":4026,"code":6092,"filename":4663,"language":4029,"meta":3643,"style":3643},"uvicorn patch_demo:app --reload\n",[3401,6094,6095],{"__ignoreMap":3643},[3647,6096,6097,6099,6102],{"class":3649,"line":3650},[3647,6098,4670],{"class":4041},[3647,6100,6101],{"class":4044}," patch_demo:app",[3647,6103,4676],{"class":3677},[3637,6105,6106],{"className":4679,"code":6092,"filename":4680,"language":4681,"meta":3643,"style":3643},[3401,6107,6108],{"__ignoreMap":3643},[3647,6109,6110],{"class":3649,"line":3650},[3647,6111,6092],{"class":3657},[4755,6113,6115],{"id":6114},"сценарій-1-часткове-оновлення-профілю-та-скидання-біографії-в-null","Сценарій 1: Часткове оновлення профілю та скидання біографії в null",[3385,6117,6118],{},"Клієнт оновлює email та затирає біографію, інші поля не передаються:",[3634,6120,6121,6155],{},[3637,6122,6124],{"className":4026,"code":6123,"filename":4663,"language":4029,"meta":3643,"style":3643},"curl -X PATCH http:\u002F\u002F127.0.0.1:8000\u002Fusers\u002F1 \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"email\": \"new_email@example.com\", \"bio\": null}'\n",[3401,6125,6126,6140,6148],{"__ignoreMap":3643},[3647,6127,6128,6130,6132,6135,6138],{"class":3649,"line":3650},[3647,6129,4776],{"class":4041},[3647,6131,4779],{"class":3677},[3647,6133,6134],{"class":4044}," PATCH",[3647,6136,6137],{"class":4044}," http:\u002F\u002F127.0.0.1:8000\u002Fusers\u002F1",[3647,6139,4789],{"class":4788},[3647,6141,6142,6144,6146],{"class":3649,"line":3667},[3647,6143,4794],{"class":3677},[3647,6145,4797],{"class":4044},[3647,6147,4789],{"class":4788},[3647,6149,6150,6152],{"class":3649,"line":3674},[3647,6151,4804],{"class":3677},[3647,6153,6154],{"class":4044}," '{\"email\": \"new_email@example.com\", \"bio\": null}'\n",[3637,6156,6158],{"className":4679,"code":6157,"filename":4680,"language":4681,"meta":3643,"style":3643},"curl.exe -X PATCH http:\u002F\u002F127.0.0.1:8000\u002Fusers\u002F1 `\n  -H \"Content-Type: application\u002Fjson\" `\n  -d '{\"email\": \"new_email@example.com\", \"bio\": null}'\n",[3401,6159,6160,6184,6192],{"__ignoreMap":3643},[3647,6161,6162,6164,6167,6169,6171,6173,6175,6177,6180,6182],{"class":3649,"line":3650},[3647,6163,4817],{"class":4041},[3647,6165,6166],{"class":3657}," -X PATCH http:\u002F\u002F",[3647,6168,4823],{"class":3732},[3647,6170,3558],{"class":3657},[3647,6172,4828],{"class":3732},[3647,6174,4831],{"class":3657},[3647,6176,4834],{"class":3732},[3647,6178,6179],{"class":3657},"\u002Fusers\u002F",[3647,6181,5713],{"class":3732},[3647,6183,4848],{"class":3657},[3647,6185,6186,6188,6190],{"class":3649,"line":3667},[3647,6187,4842],{"class":3657},[3647,6189,4845],{"class":4044},[3647,6191,4848],{"class":3657},[3647,6193,6194,6196],{"class":3649,"line":3674},[3647,6195,4853],{"class":3657},[3647,6197,6198],{"class":4044},"'{\"email\": \"new_email@example.com\", \"bio\": null}'\n",[4692,6200,6202,6209,6212,6215],{":cursor":4859,"title":6201},"Консольний вивід відповіді PATCH",[4697,6203,6205],{"className":6204},[3649],[3647,6206,6208],{"className":6207},[4867,4704],"HTTP\u002F1.1 200 OK",[4697,6210,4872],{"className":6211},[3649],[4697,6213],{"className":6214},[3649],[4697,6216,6218],{"className":6217},[3649],"{\"id\":1,\"email\":\"new_email@example.com\",\"username\":\"old_username\",\"bio\":null}",[4755,6220,6222],{"id":6221},"сценарій-2-відхилення-запиту-при-спробі-скинути-обовязкове-поле-в-null","Сценарій 2: Відхилення запиту при спробі скинути обов'язкове поле в null",[3385,6224,6225,6226,4831],{},"Спробуємо скинути обов'язкове ім'я користувача, надіславши ",[3401,6227,5424],{},[3634,6229,6230,6262],{},[3637,6231,6233],{"className":4026,"code":6232,"filename":4663,"language":4029,"meta":3643,"style":3643},"curl -X PATCH http:\u002F\u002F127.0.0.1:8000\u002Fusers\u002F1 \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"username\": null}'\n",[3401,6234,6235,6247,6255],{"__ignoreMap":3643},[3647,6236,6237,6239,6241,6243,6245],{"class":3649,"line":3650},[3647,6238,4776],{"class":4041},[3647,6240,4779],{"class":3677},[3647,6242,6134],{"class":4044},[3647,6244,6137],{"class":4044},[3647,6246,4789],{"class":4788},[3647,6248,6249,6251,6253],{"class":3649,"line":3667},[3647,6250,4794],{"class":3677},[3647,6252,4797],{"class":4044},[3647,6254,4789],{"class":4788},[3647,6256,6257,6259],{"class":3649,"line":3674},[3647,6258,4804],{"class":3677},[3647,6260,6261],{"class":4044}," '{\"username\": null}'\n",[3637,6263,6265],{"className":4679,"code":6264,"filename":4680,"language":4681,"meta":3643,"style":3643},"curl.exe -X PATCH http:\u002F\u002F127.0.0.1:8000\u002Fusers\u002F1 `\n  -H \"Content-Type: application\u002Fjson\" `\n  -d '{\"username\": null}'\n",[3401,6266,6267,6289,6297],{"__ignoreMap":3643},[3647,6268,6269,6271,6273,6275,6277,6279,6281,6283,6285,6287],{"class":3649,"line":3650},[3647,6270,4817],{"class":4041},[3647,6272,6166],{"class":3657},[3647,6274,4823],{"class":3732},[3647,6276,3558],{"class":3657},[3647,6278,4828],{"class":3732},[3647,6280,4831],{"class":3657},[3647,6282,4834],{"class":3732},[3647,6284,6179],{"class":3657},[3647,6286,5713],{"class":3732},[3647,6288,4848],{"class":3657},[3647,6290,6291,6293,6295],{"class":3649,"line":3667},[3647,6292,4842],{"class":3657},[3647,6294,4845],{"class":4044},[3647,6296,4848],{"class":3657},[3647,6298,6299,6301],{"class":3649,"line":3674},[3647,6300,4853],{"class":3657},[3647,6302,6303],{"class":4044},"'{\"username\": null}'\n",[4692,6305,6307,6313,6316,6319],{":cursor":4859,"title":6306},"Консольний вивід помилки валідації",[4697,6308,6310],{"className":6309},[3649],[3647,6311,4969],{"className":6312},[4968,4704],[4697,6314,4872],{"className":6315},[3649],[4697,6317],{"className":6318},[3649],[4697,6320,6322],{"className":6321},[3649],"{\"detail\":[{\"type\":\"value_error\",\"loc\":[\"body\",\"username\"],\"msg\":\"Value error, Поле username не може бути null\",\"input\":null}]}",[3413,6324],{},[3560,6326,6328],{"id":6327},"вкладені-моделі-та-відношення-nested-models","Вкладені моделі та відношення (Nested Models)",[3385,6330,6331],{},"Реальні сутності часто мають зв'язки «один-до-багатьох» чи «багато-до-багатьох». У Pydantic зв'язки описуються шляхом використання інших моделей як типів полів.",[3385,6333,6334,6335,4831],{},"Коли Pydantic валідує вкладену модель, він виконує ",[3389,6336,6337],{},"рекурсивний аналіз",[3424,6339,6340,6343,6346],{},[3427,6341,6342],{},"Створюється екземпляр батьківської моделі.",[3427,6344,6345],{},"Для кожного вкладеного поля викликається відповідний валідатор дочірньої моделі.",[3427,6347,6348,6349,6352],{},"Якщо на будь-якому рівні вкладеності виникає помилка, Pydantic формує точний шлях до неї (наприклад, ",[3401,6350,6351],{},"loc: [\"body\", \"items\", 2, \"price\"]","), що дозволяє клієнту чітко зрозуміть, які саме дані є некоректними.",[3385,6354,6355],{},"Розглянемо схему замовлення з вкладеними товарами:",[3637,6357,6360],{"className":3639,"code":6358,"filename":6359,"language":3642,"meta":3643,"style":3643},"# nested_models.py\nfrom pydantic import BaseModel, Field\n\nclass OrderItem(BaseModel):\n    product_id: int\n    quantity: int = Field(..., gt=0, description=\"Кількість має бути більшою за нуль\")\n    price: float = Field(..., gt=0.0)\n\nclass OrderCreate(BaseModel):\n    customer_id: int\n    # Використовуємо список інших Pydantic моделей\n    items: list[OrderItem] = Field(..., min_length=1, description=\"Замовлення має містити хоча б один товар\")\n\n# Приклад вхідного валідного JSON:\n# {\n#   \"customer_id\": 42,\n#   \"items\": [\n#     {\"product_id\": 101, \"quantity\": 2, \"price\": 15.50},\n#     {\"product_id\": 102, \"quantity\": 1, \"price\": 9.99}\n#   ]\n# }\n","nested_models.py",[3401,6361,6362,6367,6378,6382,6395,6402,6430,6449,6453,6466,6473,6478,6500,6504,6509,6514,6519,6524,6529,6534,6539],{"__ignoreMap":3643},[3647,6363,6364],{"class":3649,"line":3650},[3647,6365,6366],{"class":3697},"# nested_models.py\n",[3647,6368,6369,6371,6373,6375],{"class":3649,"line":3667},[3647,6370,3654],{"class":3653},[3647,6372,3658],{"class":3657},[3647,6374,3661],{"class":3653},[3647,6376,6377],{"class":3657}," BaseModel, Field\n",[3647,6379,6380],{"class":3649,"line":3674},[3647,6381,3671],{"emptyLinePlaceholder":3670},[3647,6383,6384,6386,6389,6391,6393],{"class":3649,"line":3694},[3647,6385,3678],{"class":3677},[3647,6387,6388],{"class":3681}," OrderItem",[3647,6390,3685],{"class":3657},[3647,6392,3688],{"class":3681},[3647,6394,3691],{"class":3657},[3647,6396,6397,6400],{"class":3649,"line":3701},[3647,6398,6399],{"class":3657},"    product_id: ",[3647,6401,4345],{"class":3681},[3647,6403,6404,6407,6409,6411,6414,6416,6419,6421,6423,6425,6428],{"class":3649,"line":3707},[3647,6405,6406],{"class":3657},"    quantity: ",[3647,6408,5970],{"class":3681},[3647,6410,4246],{"class":3657},[3647,6412,6413],{"class":3725},"gt",[3647,6415,3729],{"class":3657},[3647,6417,6418],{"class":3732},"0",[3647,6420,3595],{"class":3657},[3647,6422,4302],{"class":3725},[3647,6424,3729],{"class":3657},[3647,6426,6427],{"class":4044},"\"Кількість має бути більшою за нуль\"",[3647,6429,3746],{"class":3657},[3647,6431,6432,6435,6438,6440,6442,6444,6447],{"class":3649,"line":3713},[3647,6433,6434],{"class":3657},"    price: ",[3647,6436,6437],{"class":3681},"float",[3647,6439,4246],{"class":3657},[3647,6441,6413],{"class":3725},[3647,6443,3729],{"class":3657},[3647,6445,6446],{"class":3732},"0.0",[3647,6448,3746],{"class":3657},[3647,6450,6451],{"class":3649,"line":3749},[3647,6452,3671],{"emptyLinePlaceholder":3670},[3647,6454,6455,6457,6460,6462,6464],{"class":3649,"line":3886},[3647,6456,3678],{"class":3677},[3647,6458,6459],{"class":3681}," OrderCreate",[3647,6461,3685],{"class":3657},[3647,6463,3688],{"class":3681},[3647,6465,3691],{"class":3657},[3647,6467,6468,6471],{"class":3649,"line":3895},[3647,6469,6470],{"class":3657},"    customer_id: ",[3647,6472,4345],{"class":3681},[3647,6474,6475],{"class":3649,"line":3920},[3647,6476,6477],{"class":3697},"    # Використовуємо список інших Pydantic моделей\n",[3647,6479,6480,6483,6485,6487,6489,6491,6493,6495,6498],{"class":3649,"line":3948},[3647,6481,6482],{"class":3657},"    items: list[OrderItem] = Field(..., ",[3647,6484,3726],{"class":3725},[3647,6486,3729],{"class":3657},[3647,6488,5713],{"class":3732},[3647,6490,3595],{"class":3657},[3647,6492,4302],{"class":3725},[3647,6494,3729],{"class":3657},[3647,6496,6497],{"class":4044},"\"Замовлення має містити хоча б один товар\"",[3647,6499,3746],{"class":3657},[3647,6501,6502],{"class":3649,"line":3953},[3647,6503,3671],{"emptyLinePlaceholder":3670},[3647,6505,6506],{"class":3649,"line":3962},[3647,6507,6508],{"class":3697},"# Приклад вхідного валідного JSON:\n",[3647,6510,6511],{"class":3649,"line":3983},[3647,6512,6513],{"class":3697},"# {\n",[3647,6515,6516],{"class":3649,"line":4011},[3647,6517,6518],{"class":3697},"#   \"customer_id\": 42,\n",[3647,6520,6521],{"class":3649,"line":4316},[3647,6522,6523],{"class":3697},"#   \"items\": [\n",[3647,6525,6526],{"class":3649,"line":4322},[3647,6527,6528],{"class":3697},"#     {\"product_id\": 101, \"quantity\": 2, \"price\": 15.50},\n",[3647,6530,6531],{"class":3649,"line":4336},[3647,6532,6533],{"class":3697},"#     {\"product_id\": 102, \"quantity\": 1, \"price\": 9.99}\n",[3647,6535,6536],{"class":3649,"line":4348},[3647,6537,6538],{"class":3697},"#   ]\n",[3647,6540,6541],{"class":3649,"line":4362},[3647,6542,6543],{"class":3697},"# }\n",[3413,6545],{},[3560,6547,6549,6550],{"id":6548},"глибокий-розбір-строкових-посилань-forward-references-та-model_rebuild","Глибокий розбір строкових посилань (Forward References) та ",[3401,6551,6552],{},"model_rebuild()",[3385,6554,6555],{},"Python є динамічною мовою, що інтерпретується послідовно зверху вниз. Це створює певні обмеження при моделюванні зв'язків між сутностями.",[4755,6557,6559],{"id":6558},"суть-проблеми-випереджальні-посилання-forward-references","Суть проблеми: Випереджальні посилання (Forward References)",[3385,6561,6562,6563,6566,6567,6570,6571,6573],{},"Уявімо класичну схему блогу, де автор (",[3401,6564,6565],{},"AuthorRead",") містить список своїх постів (",[3401,6568,6569],{},"PostRead","), а пост (",[3401,6572,6569],{},") містить посилання на свого автора:",[3637,6575,6577],{"className":3639,"code":6576,"filename":4150,"language":3642,"meta":3643,"style":3643},"class AuthorRead(BaseModel):\n    id: int\n    name: str\n    posts: list[PostRead]  # ❌ NameError: name 'PostRead' is not defined\n",[3401,6578,6579,6592,6600,6608],{"__ignoreMap":3643},[3647,6580,6581,6583,6586,6588,6590],{"class":3649,"line":3650},[3647,6582,3678],{"class":3677},[3647,6584,6585],{"class":3681}," AuthorRead",[3647,6587,3685],{"class":3657},[3647,6589,3688],{"class":3681},[3647,6591,3691],{"class":3657},[3647,6593,6594,6596,6598],{"class":3649,"line":3667},[3647,6595,4339],{"class":4041},[3647,6597,4342],{"class":3657},[3647,6599,4345],{"class":3681},[3647,6601,6602,6605],{"class":3649,"line":3674},[3647,6603,6604],{"class":3657},"    name: ",[3647,6606,6607],{"class":3681},"str\n",[3647,6609,6610,6613],{"class":3649,"line":3694},[3647,6611,6612],{"class":3657},"    posts: list[PostRead]  ",[3647,6614,6615],{"class":3697},"# ❌ NameError: name 'PostRead' is not defined\n",[3385,6617,6618,6619,6621,6622,6625,6626,6628,6629,3558],{},"Оскільки інтерпретатор Python ще не зустрів визначення класу ",[3401,6620,6569],{},", використання його як типу викликає помилку ",[3401,6623,6624],{},"NameError",". Ми не можемо просто поміняти класи місцями, тому що тоді ",[3401,6627,6569],{}," спробує послатися на невідомий йому ",[3401,6630,6565],{},[4755,6632,6634],{"id":6633},"як-це-вирішується-в-python-та-pydantic","Як це вирішується в Python та Pydantic?",[3385,6636,6637,6638,6641],{},"Для вирішення цієї проблеми використовуються ",[3389,6639,6640],{},"строкові посилання"," (Forward References). Ми вказуємо назву класу у вигляді звичайного рядка:",[3637,6643,6645],{"className":3639,"code":6644,"filename":4150,"language":3642,"meta":3643,"style":3643},"posts: list['PostRead'] = []\n",[3401,6646,6647],{"__ignoreMap":3643},[3647,6648,6649,6652,6655],{"class":3649,"line":3650},[3647,6650,6651],{"class":3657},"posts: list[",[3647,6653,6654],{"class":4044},"'PostRead'",[3647,6656,4421],{"class":3657},[3385,6658,6659,6660,6662,6663,6665,6666,6668],{},"У цьому випадку Python під час створення класу сприймає ",[3401,6661,6654],{}," як звичайний текстовий рядок (",[3401,6664,3719],{},") і не намагається розв'язати його як тип, завдяки чому помилка ",[3401,6667,6624],{}," не виникає.",[6670,6671,6672,6673,6676,6677,6680],"tip",{},"Починаючи з Python 3.7, ви можете додати імпорт ",[3401,6674,6675],{},"from __future__ import annotations"," на самому початку файлу. Ця інструкція змушує інтерпретатор автоматично перетворювати ",[3389,6678,6679],{},"всі"," анотації типів на текстові рядки на етапі парсингу файлу. Це знижує навантаження на систему при імпорті модулів та автоматично усуває більшість NameError випереджальних посилань.",[3385,6682,6683,6684,6686],{},"Проте Pydantic не може валідувати дані за допомогою звичайного текстового рядка ",[3401,6685,6654],{}," — йому потрібен доступ до реальної структури класу. Тому в Pydantic v2 існує двоетапний процес:",[3424,6688,6689,6699],{},[3427,6690,6691,6694,6695,6698],{},[3389,6692,6693],{},"Реєстрація ForwardRef",": Ми можемо явно створити об'єкт ",[3401,6696,6697],{},"ForwardRef"," або передати строковий тип.",[3427,6700,6701,6706,6707,6709,6710,6713],{},[3389,6702,6703,6704],{},"Виклик ",[3401,6705,6552],{},": Коли всі задіяні класи вже визначені, ми викликаємо метод ",[3401,6708,6552],{}," на батьківських класах. Це змушує Pydantic пройтися по всіх строкових типах у моделях, знайти реальні вонименні класи в глобальному просторі імен модуля (",[3401,6711,6712],{},"globals()",") та побудувати для них кінцеві валідаційні схеми.",[3385,6715,6716],{},"Повна реалізація взаємозв'язку між моделями без циклічних помилок:",[3637,6718,6721],{"className":3639,"code":6719,"filename":6720,"language":3642,"meta":3643,"style":3643},"# relationships.py\nfrom __future__ import annotations\nfrom pydantic import BaseModel\n\nclass AuthorRead(BaseModel):\n    id: int\n    name: str\n    # Вказуємо тип як рядок, оскільки PostRead ще не оголошено нижче\n    posts: list[PostRead] = []\n\nclass PostRead(BaseModel):\n    id: int\n    title: str\n    content: str\n    author_id: int\n    # Використовуємо реальний клас AuthorRead (він уже оголошений вище)\n    author: AuthorRead\n\n# Після того, як обидва класи завантажені в пам'ять,\n# ми змушуємо Pydantic зв'язати строкові посилання з реальними класами:\nAuthorRead.model_rebuild()\nPostRead.model_rebuild()\n","relationships.py",[3401,6722,6723,6728,6741,6752,6756,6768,6776,6782,6787,6792,6796,6809,6817,6824,6831,6838,6843,6848,6852,6857,6862,6867],{"__ignoreMap":3643},[3647,6724,6725],{"class":3649,"line":3650},[3647,6726,6727],{"class":3697},"# relationships.py\n",[3647,6729,6730,6732,6735,6738],{"class":3649,"line":3667},[3647,6731,3654],{"class":3653},[3647,6733,6734],{"class":3725}," __future__",[3647,6736,6737],{"class":3653}," import",[3647,6739,6740],{"class":3657}," annotations\n",[3647,6742,6743,6745,6747,6749],{"class":3649,"line":3674},[3647,6744,3654],{"class":3653},[3647,6746,3658],{"class":3657},[3647,6748,3661],{"class":3653},[3647,6750,6751],{"class":3657}," BaseModel\n",[3647,6753,6754],{"class":3649,"line":3694},[3647,6755,3671],{"emptyLinePlaceholder":3670},[3647,6757,6758,6760,6762,6764,6766],{"class":3649,"line":3701},[3647,6759,3678],{"class":3677},[3647,6761,6585],{"class":3681},[3647,6763,3685],{"class":3657},[3647,6765,3688],{"class":3681},[3647,6767,3691],{"class":3657},[3647,6769,6770,6772,6774],{"class":3649,"line":3707},[3647,6771,4339],{"class":4041},[3647,6773,4342],{"class":3657},[3647,6775,4345],{"class":3681},[3647,6777,6778,6780],{"class":3649,"line":3713},[3647,6779,6604],{"class":3657},[3647,6781,6607],{"class":3681},[3647,6783,6784],{"class":3649,"line":3749},[3647,6785,6786],{"class":3697},"    # Вказуємо тип як рядок, оскільки PostRead ще не оголошено нижче\n",[3647,6788,6789],{"class":3649,"line":3886},[3647,6790,6791],{"class":3657},"    posts: list[PostRead] = []\n",[3647,6793,6794],{"class":3649,"line":3895},[3647,6795,3671],{"emptyLinePlaceholder":3670},[3647,6797,6798,6800,6803,6805,6807],{"class":3649,"line":3920},[3647,6799,3678],{"class":3677},[3647,6801,6802],{"class":3681}," PostRead",[3647,6804,3685],{"class":3657},[3647,6806,3688],{"class":3681},[3647,6808,3691],{"class":3657},[3647,6810,6811,6813,6815],{"class":3649,"line":3948},[3647,6812,4339],{"class":4041},[3647,6814,4342],{"class":3657},[3647,6816,4345],{"class":3681},[3647,6818,6819,6822],{"class":3649,"line":3953},[3647,6820,6821],{"class":3657},"    title: ",[3647,6823,6607],{"class":3681},[3647,6825,6826,6829],{"class":3649,"line":3962},[3647,6827,6828],{"class":3657},"    content: ",[3647,6830,6607],{"class":3681},[3647,6832,6833,6836],{"class":3649,"line":3983},[3647,6834,6835],{"class":3657},"    author_id: ",[3647,6837,4345],{"class":3681},[3647,6839,6840],{"class":3649,"line":4011},[3647,6841,6842],{"class":3697},"    # Використовуємо реальний клас AuthorRead (він уже оголошений вище)\n",[3647,6844,6845],{"class":3649,"line":4316},[3647,6846,6847],{"class":3657},"    author: AuthorRead\n",[3647,6849,6850],{"class":3649,"line":4322},[3647,6851,3671],{"emptyLinePlaceholder":3670},[3647,6853,6854],{"class":3649,"line":4336},[3647,6855,6856],{"class":3697},"# Після того, як обидва класи завантажені в пам'ять,\n",[3647,6858,6859],{"class":3649,"line":4348},[3647,6860,6861],{"class":3697},"# ми змушуємо Pydantic зв'язати строкові посилання з реальними класами:\n",[3647,6863,6864],{"class":3649,"line":4362},[3647,6865,6866],{"class":3657},"AuthorRead.model_rebuild()\n",[3647,6868,6869],{"class":3649,"line":4368},[3647,6870,6871],{"class":3657},"PostRead.model_rebuild()\n",[3385,6873,6874],{},"Завдяки цьому ми отримуємо чисту двосторонню серіалізацію об'єктів без помилок на етапі імпорту модулів.",[3413,6876],{},[3416,6878,6880],{"id":6879},"частина-iii-кастомні-типи-даних-та-кастомізація-json-схеми","Частина III: Кастомні типи даних та кастомізація JSON-схеми",[3385,6882,6883,6884,6887],{},"Сучасні веб-додатки потребують валідації специфічних для бізнесу форматів даних (номерів телефонів, ІПН, спеціальних кодів). Можливостей стандартних типів (рядок, число) часто недостатньо. Pydantic v2 пропонує потужний механізм створення власних типів через поєднання стандартних інструментів та розширеної валідації ",[3401,6885,6886],{},"Annotated",", а також надає повний контроль над генерацією JSON-схем для інтерактивної документації Swagger.",[3413,6889],{},[3560,6891,6893,6894,6896],{"id":6892},"кастомні-типи-даних-за-допомогою-annotated-та-валідаторів","Кастомні типи даних за допомогою ",[3401,6895,6886],{}," та валідаторів",[3385,6898,6899,6900,6903,6904,6906],{},"У Python 3.9 було представлено ",[3389,6901,6902],{},"PEP 593 (Annotated Metadata)",", який дозволяє додавати довільні метадані до типів. Pydantic v2 зробив ",[3401,6905,6886],{}," першокласним стандартом для опису додаткових обмежень та правил валідації.",[3385,6908,6909],{},"Замість створення складних підкласів Pydantic дозволяє конструювати типи «на льоту», комбінуючи базові типи з об'єктами валідації:",[5119,6911,6912,6924],{},[3427,6913,6914,6919,6920,6923],{},[3389,6915,6916],{},[3401,6917,6918],{},"BeforeValidator"," — викликається ",[3389,6921,6922],{},"до"," внутрішньої валідації Pydantic. Отримує «сирі» дані (наприклад, рядок з HTTP-запиту) і зазвичай використовується для очищення, приведення типів або форматування (препроцесинг).",[3427,6925,6926,6919,6931,6934],{},[3389,6927,6928],{},[3401,6929,6930],{},"AfterValidator",[3389,6932,6933],{},"після"," успішної внутрішньої валідації Pydantic. Отримує вже типізований об'єкт і використовується для додаткової логічної перевірки бізнес-правил (постпроцесинг).",[4755,6936,6938],{"id":6937},"створення-кастомного-типу-для-номерів-телефонів-формат-e164","Створення кастомного типу для номерів телефонів (формат E.164)",[3385,6940,6941,6942,6945],{},"Розглянемо практичний приклад створення власного типу ",[3401,6943,6944],{},"UkrainePhoneNumber",". Цей тип має автоматично очищати номер від зайвих символів (пробілів, дужок) та перевіряти, чи належить він українському оператору зв'язку:",[3637,6947,6950],{"className":3639,"code":6948,"filename":6949,"language":3642,"meta":3643,"style":3643},"# custom_types.py\nimport re\nfrom typing import Annotated\nfrom pydantic import BaseModel, Field, AfterValidator, BeforeValidator\n\ndef clean_phone_number(v: str) -> str:\n    \"\"\"Видаляє всі символи крім цифр та знаку плюс (BeforeValidator).\"\"\"\n    if not isinstance(v, str):\n        raise ValueError(\"Номер телефону має бути рядком\")\n    cleaned = re.sub(r\"[ \\-\\(\\)]\", \"\", v)\n\n    if cleaned.startswith(\"0\") and len(cleaned) == 10:\n        cleaned = f\"+38{cleaned}\"\n    elif cleaned.startswith(\"380\") and len(cleaned) == 12:\n        cleaned = f\"+{cleaned}\"\n\n    return cleaned\n\ndef validate_ukraine_operator(v: str) -> str:\n    \"\"\"Перевіряє відповідність формату E.164 та коду України (AfterValidator).\"\"\"\n    pattern = r\"^\\+380\\d{9}$\"\n    if not re.match(pattern, v):\n        raise ValueError(\"Номер телефону має бути валідним українським номером у форматі +380XXXXXXXXX\")\n    return v\n\n# Оголошуємо кастомний тип за допомогою Annotated\nUkrainePhoneNumber = Annotated[\n    str,\n    BeforeValidator(clean_phone_number),\n    AfterValidator(validate_ukraine_operator)\n]\n\nclass CallbackRequest(BaseModel):\n    client_name: str = Field(..., min_length=2, max_length=100)\n    phone: UkrainePhoneNumber\n","custom_types.py",[3401,6951,6952,6957,6964,6975,6986,6990,7013,7018,7035,7048,7079,7083,7109,7128,7151,7168,7172,7179,7183,7204,7209,7232,7241,7254,7261,7265,7270,7275,7282,7287,7292,7296,7300,7313,7339],{"__ignoreMap":3643},[3647,6953,6954],{"class":3649,"line":3650},[3647,6955,6956],{"class":3697},"# custom_types.py\n",[3647,6958,6959,6961],{"class":3649,"line":3667},[3647,6960,3661],{"class":3653},[3647,6962,6963],{"class":3657}," re\n",[3647,6965,6966,6968,6970,6972],{"class":3649,"line":3674},[3647,6967,3654],{"class":3653},[3647,6969,5028],{"class":3657},[3647,6971,3661],{"class":3653},[3647,6973,6974],{"class":3657}," Annotated\n",[3647,6976,6977,6979,6981,6983],{"class":3649,"line":3694},[3647,6978,3654],{"class":3653},[3647,6980,3658],{"class":3657},[3647,6982,3661],{"class":3653},[3647,6984,6985],{"class":3657}," BaseModel, Field, AfterValidator, BeforeValidator\n",[3647,6987,6988],{"class":3649,"line":3701},[3647,6989,3671],{"emptyLinePlaceholder":3670},[3647,6991,6992,6995,6998,7000,7003,7005,7007,7009,7011],{"class":3649,"line":3707},[3647,6993,6994],{"class":3677},"def",[3647,6996,6997],{"class":4041}," clean_phone_number",[3647,6999,3685],{"class":3657},[3647,7001,7002],{"class":3725},"v",[3647,7004,4342],{"class":3657},[3647,7006,3719],{"class":3681},[3647,7008,5588],{"class":3657},[3647,7010,3719],{"class":3681},[3647,7012,5597],{"class":3657},[3647,7014,7015],{"class":3649,"line":3713},[3647,7016,7017],{"class":4044},"    \"\"\"Видаляє всі символи крім цифр та знаку плюс (BeforeValidator).\"\"\"\n",[3647,7019,7020,7022,7025,7028,7031,7033],{"class":3649,"line":3749},[3647,7021,5982],{"class":3653},[3647,7023,7024],{"class":3677}," not",[3647,7026,7027],{"class":4041}," isinstance",[3647,7029,7030],{"class":3657},"(v, ",[3647,7032,3719],{"class":3681},[3647,7034,3691],{"class":3657},[3647,7036,7037,7039,7041,7043,7046],{"class":3649,"line":3886},[3647,7038,5996],{"class":3653},[3647,7040,5626],{"class":3681},[3647,7042,3685],{"class":3657},[3647,7044,7045],{"class":4044},"\"Номер телефону має бути рядком\"",[3647,7047,3746],{"class":3657},[3647,7049,7050,7053,7056,7059,7063,7066,7069,7071,7073,7076],{"class":3649,"line":3895},[3647,7051,7052],{"class":3657},"    cleaned = re.sub(",[3647,7054,7055],{"class":3677},"r",[3647,7057,5944],{"class":7058},"shsrj",[3647,7060,7062],{"class":7061},"sAV2Q","[",[3647,7064,7065],{"class":4788}," \\-\\(\\)",[3647,7067,7068],{"class":7061},"]",[3647,7070,5944],{"class":7058},[3647,7072,3595],{"class":3657},[3647,7074,7075],{"class":4044},"\"\"",[3647,7077,7078],{"class":3657},", v)\n",[3647,7080,7081],{"class":3649,"line":3920},[3647,7082,3671],{"emptyLinePlaceholder":3670},[3647,7084,7085,7087,7090,7093,7095,7098,7101,7104,7107],{"class":3649,"line":3948},[3647,7086,5982],{"class":3653},[3647,7088,7089],{"class":3657}," cleaned.startswith(",[3647,7091,7092],{"class":4044},"\"0\"",[3647,7094,5838],{"class":3657},[3647,7096,7097],{"class":3677},"and",[3647,7099,7100],{"class":4041}," len",[3647,7102,7103],{"class":3657},"(cleaned) == ",[3647,7105,7106],{"class":3732},"10",[3647,7108,5597],{"class":3657},[3647,7110,7111,7114,7116,7119,7121,7124,7126],{"class":3649,"line":3953},[3647,7112,7113],{"class":3657},"        cleaned = ",[3647,7115,4509],{"class":3677},[3647,7117,7118],{"class":4044},"\"+38",[3647,7120,4515],{"class":3677},[3647,7122,7123],{"class":3657},"cleaned",[3647,7125,4521],{"class":3677},[3647,7127,4524],{"class":4044},[3647,7129,7130,7133,7135,7138,7140,7142,7144,7146,7149],{"class":3649,"line":3962},[3647,7131,7132],{"class":3653},"    elif",[3647,7134,7089],{"class":3657},[3647,7136,7137],{"class":4044},"\"380\"",[3647,7139,5838],{"class":3657},[3647,7141,7097],{"class":3677},[3647,7143,7100],{"class":4041},[3647,7145,7103],{"class":3657},[3647,7147,7148],{"class":3732},"12",[3647,7150,5597],{"class":3657},[3647,7152,7153,7155,7157,7160,7162,7164,7166],{"class":3649,"line":3983},[3647,7154,7113],{"class":3657},[3647,7156,4509],{"class":3677},[3647,7158,7159],{"class":4044},"\"+",[3647,7161,4515],{"class":3677},[3647,7163,7123],{"class":3657},[3647,7165,4521],{"class":3677},[3647,7167,4524],{"class":4044},[3647,7169,7170],{"class":3649,"line":4011},[3647,7171,3671],{"emptyLinePlaceholder":3670},[3647,7173,7174,7176],{"class":3649,"line":4316},[3647,7175,4647],{"class":3653},[3647,7177,7178],{"class":3657}," cleaned\n",[3647,7180,7181],{"class":3649,"line":4322},[3647,7182,3671],{"emptyLinePlaceholder":3670},[3647,7184,7185,7187,7190,7192,7194,7196,7198,7200,7202],{"class":3649,"line":4336},[3647,7186,6994],{"class":3677},[3647,7188,7189],{"class":4041}," validate_ukraine_operator",[3647,7191,3685],{"class":3657},[3647,7193,7002],{"class":3725},[3647,7195,4342],{"class":3657},[3647,7197,3719],{"class":3681},[3647,7199,5588],{"class":3657},[3647,7201,3719],{"class":3681},[3647,7203,5597],{"class":3657},[3647,7205,7206],{"class":3649,"line":4348},[3647,7207,7208],{"class":4044},"    \"\"\"Перевіряє відповідність формату E.164 та коду України (AfterValidator).\"\"\"\n",[3647,7210,7211,7214,7216,7219,7222,7225,7229],{"class":3649,"line":4362},[3647,7212,7213],{"class":3657},"    pattern = ",[3647,7215,7055],{"class":3677},[3647,7217,7218],{"class":7058},"\"^",[3647,7220,7221],{"class":4788},"\\+",[3647,7223,7224],{"class":7058},"380\\d",[3647,7226,7228],{"class":7227},"sYsli","{9}",[3647,7230,7231],{"class":7058},"$\"\n",[3647,7233,7234,7236,7238],{"class":3649,"line":4368},[3647,7235,5982],{"class":3653},[3647,7237,7024],{"class":3677},[3647,7239,7240],{"class":3657}," re.match(pattern, v):\n",[3647,7242,7243,7245,7247,7249,7252],{"class":3649,"line":4373},[3647,7244,5996],{"class":3653},[3647,7246,5626],{"class":3681},[3647,7248,3685],{"class":3657},[3647,7250,7251],{"class":4044},"\"Номер телефону має бути валідним українським номером у форматі +380XXXXXXXXX\"",[3647,7253,3746],{"class":3657},[3647,7255,7256,7258],{"class":3649,"line":4379},[3647,7257,4647],{"class":3653},[3647,7259,7260],{"class":3657}," v\n",[3647,7262,7263],{"class":3649,"line":4385},[3647,7264,3671],{"emptyLinePlaceholder":3670},[3647,7266,7267],{"class":3649,"line":4401},[3647,7268,7269],{"class":3697},"# Оголошуємо кастомний тип за допомогою Annotated\n",[3647,7271,7272],{"class":3649,"line":4406},[3647,7273,7274],{"class":3657},"UkrainePhoneNumber = Annotated[\n",[3647,7276,7277,7280],{"class":3649,"line":4412},[3647,7278,7279],{"class":3681},"    str",[3647,7281,4590],{"class":3657},[3647,7283,7284],{"class":3649,"line":4424},[3647,7285,7286],{"class":3657},"    BeforeValidator(clean_phone_number),\n",[3647,7288,7289],{"class":3649,"line":4433},[3647,7290,7291],{"class":3657},"    AfterValidator(validate_ukraine_operator)\n",[3647,7293,7294],{"class":3649,"line":4438},[3647,7295,3834],{"class":3657},[3647,7297,7298],{"class":3649,"line":4463},[3647,7299,3671],{"emptyLinePlaceholder":3670},[3647,7301,7302,7304,7307,7309,7311],{"class":3649,"line":4483},[3647,7303,3678],{"class":3677},[3647,7305,7306],{"class":3681}," CallbackRequest",[3647,7308,3685],{"class":3657},[3647,7310,3688],{"class":3681},[3647,7312,3691],{"class":3657},[3647,7314,7315,7318,7320,7322,7324,7326,7329,7331,7333,7335,7337],{"class":3649,"line":4492},[3647,7316,7317],{"class":3657},"    client_name: ",[3647,7319,3719],{"class":3681},[3647,7321,4246],{"class":3657},[3647,7323,3726],{"class":3725},[3647,7325,3729],{"class":3657},[3647,7327,7328],{"class":3732},"2",[3647,7330,3595],{"class":3657},[3647,7332,3738],{"class":3725},[3647,7334,3729],{"class":3657},[3647,7336,3743],{"class":3732},[3647,7338,3746],{"class":3657},[3647,7340,7341],{"class":3649,"line":4497},[3647,7342,7343],{"class":3657},"    phone: UkrainePhoneNumber\n",[3413,7345],{},[3560,7347,7349],{"id":7348},"кастомізація-json-схеми-та-swagger-ui","Кастомізація JSON-схеми та Swagger UI",[3385,7351,7352,7353,3558],{},"FastAPI автоматично генерує документацію Swagger UI, аналізуючи Pydantic-моделі та перетворюючи їх на специфікацію OpenAPI, яка базується на стандартах ",[3389,7354,7355],{},"JSON Schema",[3385,7357,7358],{},"Pydantic v2 надає розробнику повний контроль над тим, як саме поля та моделі відображатимуться в JSON-схемі, за допомогою двох механізмів:",[3424,7360,7361,7371],{},[3427,7362,7363,7364,7367,7368,7370],{},"Параметра ",[3401,7365,7366],{},"json_schema_extra"," в об'єкті ",[3401,7369,5369],{}," (для окремих полів).",[3427,7372,7363,7373,7375,7376,7379],{},[3401,7374,7366],{}," в конфігурації модели ",[3401,7377,7378],{},"ConfigDict"," (для всієї моделі).",[4755,7381,7383],{"id":7382},"_1-кастомізація-на-рівні-окремих-полів","1. Кастомізація на рівні окремих полів",[3385,7385,7386,7387,7389,7390,7393],{},"Через ",[3401,7388,5369],{}," ми можемо передати додаткові метадані, такі як приклади (",[3401,7391,7392],{},"examples","), які відображатимуться в Swagger UI як плейсхолдери для запиту:",[3637,7395,7397],{"className":3639,"code":7396,"filename":4150,"language":3642,"meta":3643,"style":3643},"from pydantic import BaseModel, Field\n\nclass ProductCreate(BaseModel):\n    name: str = Field(\n        ...,\n        min_length=3,\n        description=\"Назва товару для відображення в каталозі\",\n        json_schema_extra={\"examples\": [\"Ноутбук Lenovo ThinkPad\"]}\n    )\n    price: float = Field(\n        ...,\n        gt=0.0,\n        json_schema_extra={\"examples\": [1249.99]}\n    )\n",[3401,7398,7399,7409,7413,7426,7435,7440,7451,7463,7483,7488,7496,7500,7511,7526],{"__ignoreMap":3643},[3647,7400,7401,7403,7405,7407],{"class":3649,"line":3650},[3647,7402,3654],{"class":3653},[3647,7404,3658],{"class":3657},[3647,7406,3661],{"class":3653},[3647,7408,6377],{"class":3657},[3647,7410,7411],{"class":3649,"line":3667},[3647,7412,3671],{"emptyLinePlaceholder":3670},[3647,7414,7415,7417,7420,7422,7424],{"class":3649,"line":3674},[3647,7416,3678],{"class":3677},[3647,7418,7419],{"class":3681}," ProductCreate",[3647,7421,3685],{"class":3657},[3647,7423,3688],{"class":3681},[3647,7425,3691],{"class":3657},[3647,7427,7428,7430,7432],{"class":3649,"line":3694},[3647,7429,6604],{"class":3657},[3647,7431,3719],{"class":3681},[3647,7433,7434],{"class":3657}," = Field(\n",[3647,7436,7437],{"class":3649,"line":3701},[3647,7438,7439],{"class":3657},"        ...,\n",[3647,7441,7442,7445,7447,7449],{"class":3649,"line":3707},[3647,7443,7444],{"class":3725},"        min_length",[3647,7446,3729],{"class":3657},[3647,7448,3763],{"class":3732},[3647,7450,4590],{"class":3657},[3647,7452,7453,7456,7458,7461],{"class":3649,"line":3713},[3647,7454,7455],{"class":3725},"        description",[3647,7457,3729],{"class":3657},[3647,7459,7460],{"class":4044},"\"Назва товару для відображення в каталозі\"",[3647,7462,4590],{"class":3657},[3647,7464,7465,7468,7471,7474,7477,7480],{"class":3649,"line":3749},[3647,7466,7467],{"class":3725},"        json_schema_extra",[3647,7469,7470],{"class":3657},"={",[3647,7472,7473],{"class":4044},"\"examples\"",[3647,7475,7476],{"class":3657},": [",[3647,7478,7479],{"class":4044},"\"Ноутбук Lenovo ThinkPad\"",[3647,7481,7482],{"class":3657},"]}\n",[3647,7484,7485],{"class":3649,"line":3886},[3647,7486,7487],{"class":3657},"    )\n",[3647,7489,7490,7492,7494],{"class":3649,"line":3895},[3647,7491,6434],{"class":3657},[3647,7493,6437],{"class":3681},[3647,7495,7434],{"class":3657},[3647,7497,7498],{"class":3649,"line":3920},[3647,7499,7439],{"class":3657},[3647,7501,7502,7505,7507,7509],{"class":3649,"line":3948},[3647,7503,7504],{"class":3725},"        gt",[3647,7506,3729],{"class":3657},[3647,7508,6446],{"class":3732},[3647,7510,4590],{"class":3657},[3647,7512,7513,7515,7517,7519,7521,7524],{"class":3649,"line":3953},[3647,7514,7467],{"class":3725},[3647,7516,7470],{"class":3657},[3647,7518,7473],{"class":4044},[3647,7520,7476],{"class":3657},[3647,7522,7523],{"class":3732},"1249.99",[3647,7525,7482],{"class":3657},[3647,7527,7528],{"class":3649,"line":3962},[3647,7529,7487],{"class":3657},[4755,7531,7533],{"id":7532},"_2-кастомізація-на-рівні-всієї-моделі","2. Кастомізація на рівні всієї моделі",[3385,7535,7536,7537,4831],{},"Якщо необхідно надати цілісний приклад JSON-документа, зручніше налаштувати приклад на рівні моделі через ",[3401,7538,7539],{},"model_config",[3637,7541,7544],{"className":3639,"code":7542,"filename":7543,"language":3642,"meta":3643,"style":3643},"# schema_customization.py\nfrom pydantic import BaseModel, ConfigDict, Field\nfrom fastapi import FastAPI\n\napp = FastAPI()\n\nclass ProjectCreate(BaseModel):\n    title: str = Field(..., min_length=3, max_length=100)\n    description: str | None = Field(None, max_length=1000)\n    budget: float = Field(..., ge=0.0)\n\n    model_config = ConfigDict(\n        json_schema_extra={\n            \"example\": {\n                \"title\": \"Розробка корпоративного порталу\",\n                \"description\": \"Створення внутрішнього веб-ресурсу для співробітників компанії\",\n                \"budget\": 15000.00\n            }\n        }\n    )\n\n@app.post(\"\u002Fprojects\")\nasync def create_project(project: ProjectCreate):\n    return {\"status\": \"ok\", \"project\": project}\n","schema_customization.py",[3401,7545,7546,7551,7562,7573,7577,7581,7585,7598,7622,7648,7666,7670,7675,7682,7690,7702,7714,7724,7729,7734,7738,7742,7753,7770],{"__ignoreMap":3643},[3647,7547,7548],{"class":3649,"line":3650},[3647,7549,7550],{"class":3697},"# schema_customization.py\n",[3647,7552,7553,7555,7557,7559],{"class":3649,"line":3667},[3647,7554,3654],{"class":3653},[3647,7556,3658],{"class":3657},[3647,7558,3661],{"class":3653},[3647,7560,7561],{"class":3657}," BaseModel, ConfigDict, Field\n",[3647,7563,7564,7566,7568,7570],{"class":3649,"line":3674},[3647,7565,3654],{"class":3653},[3647,7567,4187],{"class":3657},[3647,7569,3661],{"class":3653},[3647,7571,7572],{"class":3657}," FastAPI\n",[3647,7574,7575],{"class":3649,"line":3694},[3647,7576,3671],{"emptyLinePlaceholder":3670},[3647,7578,7579],{"class":3649,"line":3701},[3647,7580,5689],{"class":3657},[3647,7582,7583],{"class":3649,"line":3707},[3647,7584,3671],{"emptyLinePlaceholder":3670},[3647,7586,7587,7589,7592,7594,7596],{"class":3649,"line":3713},[3647,7588,3678],{"class":3677},[3647,7590,7591],{"class":3681}," ProjectCreate",[3647,7593,3685],{"class":3657},[3647,7595,3688],{"class":3681},[3647,7597,3691],{"class":3657},[3647,7599,7600,7602,7604,7606,7608,7610,7612,7614,7616,7618,7620],{"class":3649,"line":3749},[3647,7601,6821],{"class":3657},[3647,7603,3719],{"class":3681},[3647,7605,4246],{"class":3657},[3647,7607,3726],{"class":3725},[3647,7609,3729],{"class":3657},[3647,7611,3763],{"class":3732},[3647,7613,3595],{"class":3657},[3647,7615,3738],{"class":3725},[3647,7617,3729],{"class":3657},[3647,7619,3743],{"class":3732},[3647,7621,3746],{"class":3657},[3647,7623,7624,7627,7629,7631,7633,7635,7637,7639,7641,7643,7646],{"class":3649,"line":3886},[3647,7625,7626],{"class":3657},"    description: ",[3647,7628,3719],{"class":3681},[3647,7630,5084],{"class":3657},[3647,7632,5087],{"class":3677},[3647,7634,3722],{"class":3657},[3647,7636,5087],{"class":3677},[3647,7638,3595],{"class":3657},[3647,7640,3738],{"class":3725},[3647,7642,3729],{"class":3657},[3647,7644,7645],{"class":3732},"1000",[3647,7647,3746],{"class":3657},[3647,7649,7650,7653,7655,7657,7660,7662,7664],{"class":3649,"line":3895},[3647,7651,7652],{"class":3657},"    budget: ",[3647,7654,6437],{"class":3681},[3647,7656,4246],{"class":3657},[3647,7658,7659],{"class":3725},"ge",[3647,7661,3729],{"class":3657},[3647,7663,6446],{"class":3732},[3647,7665,3746],{"class":3657},[3647,7667,7668],{"class":3649,"line":3920},[3647,7669,3671],{"emptyLinePlaceholder":3670},[3647,7671,7672],{"class":3649,"line":3948},[3647,7673,7674],{"class":3657},"    model_config = ConfigDict(\n",[3647,7676,7677,7679],{"class":3649,"line":3953},[3647,7678,7467],{"class":3725},[3647,7680,7681],{"class":3657},"={\n",[3647,7683,7684,7687],{"class":3649,"line":3962},[3647,7685,7686],{"class":4044},"            \"example\"",[3647,7688,7689],{"class":3657},": {\n",[3647,7691,7692,7695,7697,7700],{"class":3649,"line":3983},[3647,7693,7694],{"class":4044},"                \"title\"",[3647,7696,4342],{"class":3657},[3647,7698,7699],{"class":4044},"\"Розробка корпоративного порталу\"",[3647,7701,4590],{"class":3657},[3647,7703,7704,7707,7709,7712],{"class":3649,"line":4011},[3647,7705,7706],{"class":4044},"                \"description\"",[3647,7708,4342],{"class":3657},[3647,7710,7711],{"class":4044},"\"Створення внутрішнього веб-ресурсу для співробітників компанії\"",[3647,7713,4590],{"class":3657},[3647,7715,7716,7719,7721],{"class":3649,"line":4316},[3647,7717,7718],{"class":4044},"                \"budget\"",[3647,7720,4342],{"class":3657},[3647,7722,7723],{"class":3732},"15000.00\n",[3647,7725,7726],{"class":3649,"line":4322},[3647,7727,7728],{"class":3657},"            }\n",[3647,7730,7731],{"class":3649,"line":4336},[3647,7732,7733],{"class":3657},"        }\n",[3647,7735,7736],{"class":3649,"line":4348},[3647,7737,7487],{"class":3657},[3647,7739,7740],{"class":3649,"line":4362},[3647,7741,3671],{"emptyLinePlaceholder":3670},[3647,7743,7744,7746,7748,7751],{"class":3649,"line":4368},[3647,7745,4441],{"class":4041},[3647,7747,3685],{"class":3657},[3647,7749,7750],{"class":4044},"\"\u002Fprojects\"",[3647,7752,3746],{"class":3657},[3647,7754,7755,7757,7759,7762,7764,7767],{"class":3649,"line":4373},[3647,7756,4466],{"class":3677},[3647,7758,4469],{"class":3677},[3647,7760,7761],{"class":4041}," create_project",[3647,7763,3685],{"class":3657},[3647,7765,7766],{"class":3725},"project",[3647,7768,7769],{"class":3657},": ProjectCreate):\n",[3647,7771,7772,7774,7777,7780,7782,7785,7787,7790],{"class":3649,"line":4379},[3647,7773,4647],{"class":3653},[3647,7775,7776],{"class":3657}," {",[3647,7778,7779],{"class":4044},"\"status\"",[3647,7781,4342],{"class":3657},[3647,7783,7784],{"class":4044},"\"ok\"",[3647,7786,3595],{"class":3657},[3647,7788,7789],{"class":4044},"\"project\"",[3647,7791,7792],{"class":3657},": project}\n",[3560,7794,7796],{"id":7795},"запуск-та-тестування-ендпоінту-із-кастомною-json-схемою","Запуск та тестування ендпоінту із кастомною JSON-схемою",[3385,7798,7799],{},"Запустіть веб-сайт за допомогою Uvicorn:",[3634,7801,7802,7816],{},[3637,7803,7805],{"className":4026,"code":7804,"filename":4663,"language":4029,"meta":3643,"style":3643},"uvicorn schema_customization:app --reload\n",[3401,7806,7807],{"__ignoreMap":3643},[3647,7808,7809,7811,7814],{"class":3649,"line":3650},[3647,7810,4670],{"class":4041},[3647,7812,7813],{"class":4044}," schema_customization:app",[3647,7815,4676],{"class":3677},[3637,7817,7818],{"className":4679,"code":7804,"filename":4680,"language":4681,"meta":3643,"style":3643},[3401,7819,7820],{"__ignoreMap":3643},[3647,7821,7822],{"class":3649,"line":3650},[3647,7823,7804],{"class":3657},[3385,7825,7826],{},"Тепер надішлемо POST запит із приведеними вище параметрами проекту:",[3634,7828,7829,7862],{},[3637,7830,7832],{"className":4026,"code":7831,"filename":4663,"language":4029,"meta":3643,"style":3643},"curl -X POST http:\u002F\u002F127.0.0.1:8000\u002Fprojects \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"title\": \"Розробка корпоративного порталу\", \"description\": \"Створення внутрішнього веб-ресурсу для співробітників компанії\", \"budget\": 15000.00}'\n",[3401,7833,7834,7847,7855],{"__ignoreMap":3643},[3647,7835,7836,7838,7840,7842,7845],{"class":3649,"line":3650},[3647,7837,4776],{"class":4041},[3647,7839,4779],{"class":3677},[3647,7841,4782],{"class":4044},[3647,7843,7844],{"class":4044}," http:\u002F\u002F127.0.0.1:8000\u002Fprojects",[3647,7846,4789],{"class":4788},[3647,7848,7849,7851,7853],{"class":3649,"line":3667},[3647,7850,4794],{"class":3677},[3647,7852,4797],{"class":4044},[3647,7854,4789],{"class":4788},[3647,7856,7857,7859],{"class":3649,"line":3674},[3647,7858,4804],{"class":3677},[3647,7860,7861],{"class":4044}," '{\"title\": \"Розробка корпоративного порталу\", \"description\": \"Створення внутрішнього веб-ресурсу для співробітників компанії\", \"budget\": 15000.00}'\n",[3637,7863,7865],{"className":4679,"code":7864,"filename":4680,"language":4681,"meta":3643,"style":3643},"curl.exe -X POST http:\u002F\u002F127.0.0.1:8000\u002Fprojects `\n  -H \"Content-Type: application\u002Fjson\" `\n  -d '{\"title\": \"Розробка корпоративного порталу\", \"description\": \"Створення внутрішнього веб-ресурсу для співробітників компанії\", \"budget\": 15000.00}'\n",[3401,7866,7867,7886,7894],{"__ignoreMap":3643},[3647,7868,7869,7871,7873,7875,7877,7879,7881,7883],{"class":3649,"line":3650},[3647,7870,4817],{"class":4041},[3647,7872,4820],{"class":3657},[3647,7874,4823],{"class":3732},[3647,7876,3558],{"class":3657},[3647,7878,4828],{"class":3732},[3647,7880,4831],{"class":3657},[3647,7882,4834],{"class":3732},[3647,7884,7885],{"class":3657},"\u002Fprojects `\n",[3647,7887,7888,7890,7892],{"class":3649,"line":3667},[3647,7889,4842],{"class":3657},[3647,7891,4845],{"class":4044},[3647,7893,4848],{"class":3657},[3647,7895,7896,7898],{"class":3649,"line":3674},[3647,7897,4853],{"class":3657},[3647,7899,7900],{"class":4044},"'{\"title\": \"Розробка корпоративного порталу\", \"description\": \"Створення внутрішнього веб-ресурсу для співробітників компанії\", \"budget\": 15000.00}'\n",[4692,7902,7904,7910,7913,7916],{":cursor":4859,"title":7903},"Консольний вивід відповіді POST",[4697,7905,7907],{"className":7906},[3649],[3647,7908,6208],{"className":7909},[4867,4704],[4697,7911,4872],{"className":7912},[3649],[4697,7914],{"className":7915},[3649],[4697,7917,7919],{"className":7918},[3649],"{\"status\":\"ok\",\"project\":{\"title\":\"Розробка корпоративного порталу\",\"description\":\"Створення внутрішнього веб-ресурсу для співробітників компанії\",\"budget\":15000.0}}",[3413,7921],{},[3416,7923,7925],{"id":7924},"частина-iv-обмеження-значень-та-завантаження-файлів","Частина IV: Обмеження значень та завантаження файлів",[3385,7927,7928,7929,7932,7933,7936,7937,3558],{},"Для побудови стійких інтерфейсів необхідно обмежувати можливі значення полів фіксованими списками (наприклад, статуси задач, рівні доступу) та вміти працювати з бінарними даними — завантаженням файлів (зображень, документів) через HTTP. FastAPI та Pydantic надають для цього декларативні структури ",[3401,7930,7931],{},"Enum","\u002F",[3401,7934,7935],{},"Literal"," та оновлений клас ",[3401,7938,7939],{},"UploadFile",[3413,7941],{},[3560,7943,7945,7946,3552,7948],{"id":7944},"обмеження-значень-enum-та-literal","Обмеження значень: ",[3401,7947,7931],{},[3401,7949,7935],{},[3385,7951,7952],{},"Коли поле моделі має приймати значення виключно з чітко визначеного набору, використовуються два інструменти:",[3424,7954,7955,7968],{},[3427,7956,7957,7967],{},[3389,7958,7959,7962,7963,7966],{},[3401,7960,7961],{},"enum.Enum"," (або ",[3401,7964,7965],{},"enum.StrEnum"," у Python 3.11+)"," — системний перелік значень.",[3427,7969,7970,7976],{},[3389,7971,7972,7975],{},[3401,7973,7974],{},"typing.Literal"," (PEP 586)"," — дозволяє явно вказати конкретні допустимі літеральні значення.",[4755,7978,7980,7981,7984],{"id":7979},"використання-strenum-python-311","Використання ",[3401,7982,7983],{},"StrEnum"," (Python 3.11+)",[3385,7986,7987,7988,7990,7991,7994,7995,7997,7998,8000],{},"У класичному Python ",[3401,7989,7931],{}," значення елементів можуть бути будь-котрими, а серіалізація вимагає звернення до ",[3401,7992,7993],{},".value",". У розробці API на Python 3.11+ рекомендується використовувати ",[3401,7996,7983],{},". Клас ",[3401,7999,7983],{}," наслідує рядок, тому його елементи автоматично серіалізуються Pydantic як звичайні рядки:",[3637,8002,8004],{"className":3639,"code":8003,"filename":4150,"language":3642,"meta":3643,"style":3643},"from enum import StrEnum\n\nclass TaskStatus(StrEnum):\n    TODO = \"todo\"\n    IN_PROGRESS = \"in_progress\"\n    DONE = \"done\"\n",[3401,8005,8006,8018,8022,8035,8043,8051],{"__ignoreMap":3643},[3647,8007,8008,8010,8013,8015],{"class":3649,"line":3650},[3647,8009,3654],{"class":3653},[3647,8011,8012],{"class":3657}," enum ",[3647,8014,3661],{"class":3653},[3647,8016,8017],{"class":3657}," StrEnum\n",[3647,8019,8020],{"class":3649,"line":3667},[3647,8021,3671],{"emptyLinePlaceholder":3670},[3647,8023,8024,8026,8029,8031,8033],{"class":3649,"line":3674},[3647,8025,3678],{"class":3677},[3647,8027,8028],{"class":3681}," TaskStatus",[3647,8030,3685],{"class":3657},[3647,8032,7983],{"class":3681},[3647,8034,3691],{"class":3657},[3647,8036,8037,8040],{"class":3649,"line":3694},[3647,8038,8039],{"class":3657},"    TODO = ",[3647,8041,8042],{"class":4044},"\"todo\"\n",[3647,8044,8045,8048],{"class":3649,"line":3701},[3647,8046,8047],{"class":3657},"    IN_PROGRESS = ",[3647,8049,8050],{"class":4044},"\"in_progress\"\n",[3647,8052,8053,8056],{"class":3649,"line":3707},[3647,8054,8055],{"class":3657},"    DONE = ",[3647,8057,8058],{"class":4044},"\"done\"\n",[4755,8060,7980,8062,8064],{"id":8061},"використання-literal-pep-586-детальний-розбір",[3401,8063,7935],{}," (PEP 586) — детальний розбір",[3385,8066,8067,8068,5008,8070,8072],{},"Конструкція ",[3401,8069,7935],{},[3401,8071,5011],{}," дозволяє вказувати типи, значення яких мають точно відповідати одному із наданих текстових або числових літералів.",[3385,8074,8075],{},[5407,8076,8077,8078,8080,8081,5002],{},"Чим ",[3401,8079,7935],{}," відрізняється від ",[3401,8082,7931],{},[5119,8084,8085,8090],{},[3427,8086,8087,8089],{},[3401,8088,7931],{}," є окремим класом у Python, елементи якого представляють об'єкти типу Enum. Його краще використовувати для глобальних доменних переліків (наприклад, статуси завдань, рівні доступу користувачів), які використовуються у багатьох файлах проекту.",[3427,8091,8092,8094,8095,7932,8098,8101,8102,7932,8105,8108,8109,7932,8112,3469],{},[3401,8093,7935],{}," визначає тип на рівні окремої змінної\u002Fполя і не потребує оголошення додаткових класів. Він ідеально підходить для фіксації конкретних службових значень (наприклад, версія API ",[3401,8096,8097],{},"\"v1\"",[3401,8099,8100],{},"\"v2\"",", тип задачі ",[3401,8103,8104],{},"\"task\"",[3401,8106,8107],{},"\"bug\"",", або напрямок сортування ",[3401,8110,8111],{},"\"asc\"",[3401,8113,8114],{},"\"desc\"",[3385,8116,8117,8118,4831],{},"Коли Pydantic валідує поле з типом ",[3401,8119,8120],{},"Literal[\"task\", \"bug\"]",[5119,8122,8123,8126,8140],{},[3427,8124,8125],{},"Він перевіряє точне співпадіння вхідного рядка з одним із вказаних значень.",[3427,8127,8128,8129,8132,8133,8136,8137,3558],{},"Якщо надіслати ",[3401,8130,8131],{},"\"invalid\"",", Pydantic згенерує помилку ",[3401,8134,8135],{},"ValidationError"," із чітким повідомленням: ",[3401,8138,8139],{},"Input should be 'task' or 'bug'",[3427,8141,8142,8143,3558],{},"У схемі OpenAPI для цього поля автоматично прописується обмеження ",[3401,8144,8145],{},"enum: [\"task\", \"bug\"]",[3637,8147,8150],{"className":3639,"code":8148,"filename":8149,"language":3642,"meta":3643,"style":3643},"# enum_literal_demo.py\nfrom enum import StrEnum\nfrom typing import Literal\nfrom pydantic import BaseModel, Field\nfrom fastapi import FastAPI, status\n\napp = FastAPI()\n\nclass TaskPriority(StrEnum):\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n\nclass TaskCreate(BaseModel):\n    title: str = Field(..., min_length=3, max_length=100)\n    priority: TaskPriority = TaskPriority.MEDIUM\n    # Поле типу Literal — приймає тільки \"task\" або \"bug\"\n    task_type: Literal[\"task\", \"bug\"] = \"task\"\n\n@app.post(\"\u002Ftasks\", status_code=status.HTTP_201_CREATED)\nasync def create_task(task: TaskCreate):\n    return task\n","enum_literal_demo.py",[3401,8151,8152,8157,8167,8178,8188,8198,8202,8206,8210,8223,8231,8239,8247,8251,8264,8288,8293,8298,8314,8318,8333,8350],{"__ignoreMap":3643},[3647,8153,8154],{"class":3649,"line":3650},[3647,8155,8156],{"class":3697},"# enum_literal_demo.py\n",[3647,8158,8159,8161,8163,8165],{"class":3649,"line":3667},[3647,8160,3654],{"class":3653},[3647,8162,8012],{"class":3657},[3647,8164,3661],{"class":3653},[3647,8166,8017],{"class":3657},[3647,8168,8169,8171,8173,8175],{"class":3649,"line":3674},[3647,8170,3654],{"class":3653},[3647,8172,5028],{"class":3657},[3647,8174,3661],{"class":3653},[3647,8176,8177],{"class":3657}," Literal\n",[3647,8179,8180,8182,8184,8186],{"class":3649,"line":3694},[3647,8181,3654],{"class":3653},[3647,8183,3658],{"class":3657},[3647,8185,3661],{"class":3653},[3647,8187,6377],{"class":3657},[3647,8189,8190,8192,8194,8196],{"class":3649,"line":3701},[3647,8191,3654],{"class":3653},[3647,8193,4187],{"class":3657},[3647,8195,3661],{"class":3653},[3647,8197,4192],{"class":3657},[3647,8199,8200],{"class":3649,"line":3707},[3647,8201,3671],{"emptyLinePlaceholder":3670},[3647,8203,8204],{"class":3649,"line":3713},[3647,8205,5689],{"class":3657},[3647,8207,8208],{"class":3649,"line":3749},[3647,8209,3671],{"emptyLinePlaceholder":3670},[3647,8211,8212,8214,8217,8219,8221],{"class":3649,"line":3886},[3647,8213,3678],{"class":3677},[3647,8215,8216],{"class":3681}," TaskPriority",[3647,8218,3685],{"class":3657},[3647,8220,7983],{"class":3681},[3647,8222,3691],{"class":3657},[3647,8224,8225,8228],{"class":3649,"line":3895},[3647,8226,8227],{"class":3657},"    LOW = ",[3647,8229,8230],{"class":4044},"\"low\"\n",[3647,8232,8233,8236],{"class":3649,"line":3920},[3647,8234,8235],{"class":3657},"    MEDIUM = ",[3647,8237,8238],{"class":4044},"\"medium\"\n",[3647,8240,8241,8244],{"class":3649,"line":3948},[3647,8242,8243],{"class":3657},"    HIGH = ",[3647,8245,8246],{"class":4044},"\"high\"\n",[3647,8248,8249],{"class":3649,"line":3953},[3647,8250,3671],{"emptyLinePlaceholder":3670},[3647,8252,8253,8255,8258,8260,8262],{"class":3649,"line":3962},[3647,8254,3678],{"class":3677},[3647,8256,8257],{"class":3681}," TaskCreate",[3647,8259,3685],{"class":3657},[3647,8261,3688],{"class":3681},[3647,8263,3691],{"class":3657},[3647,8265,8266,8268,8270,8272,8274,8276,8278,8280,8282,8284,8286],{"class":3649,"line":3983},[3647,8267,6821],{"class":3657},[3647,8269,3719],{"class":3681},[3647,8271,4246],{"class":3657},[3647,8273,3726],{"class":3725},[3647,8275,3729],{"class":3657},[3647,8277,3763],{"class":3732},[3647,8279,3595],{"class":3657},[3647,8281,3738],{"class":3725},[3647,8283,3729],{"class":3657},[3647,8285,3743],{"class":3732},[3647,8287,3746],{"class":3657},[3647,8289,8290],{"class":3649,"line":4011},[3647,8291,8292],{"class":3657},"    priority: TaskPriority = TaskPriority.MEDIUM\n",[3647,8294,8295],{"class":3649,"line":4316},[3647,8296,8297],{"class":3697},"    # Поле типу Literal — приймає тільки \"task\" або \"bug\"\n",[3647,8299,8300,8303,8305,8307,8309,8311],{"class":3649,"line":4322},[3647,8301,8302],{"class":3657},"    task_type: Literal[",[3647,8304,8104],{"class":4044},[3647,8306,3595],{"class":3657},[3647,8308,8107],{"class":4044},[3647,8310,5043],{"class":3657},[3647,8312,8313],{"class":4044},"\"task\"\n",[3647,8315,8316],{"class":3649,"line":4336},[3647,8317,3671],{"emptyLinePlaceholder":3670},[3647,8319,8320,8322,8324,8327,8329,8331],{"class":3649,"line":4348},[3647,8321,4441],{"class":4041},[3647,8323,3685],{"class":3657},[3647,8325,8326],{"class":4044},"\"\u002Ftasks\"",[3647,8328,3595],{"class":3657},[3647,8330,4457],{"class":3725},[3647,8332,4460],{"class":3657},[3647,8334,8335,8337,8339,8342,8344,8347],{"class":3649,"line":4362},[3647,8336,4466],{"class":3677},[3647,8338,4469],{"class":3677},[3647,8340,8341],{"class":4041}," create_task",[3647,8343,3685],{"class":3657},[3647,8345,8346],{"class":3725},"task",[3647,8348,8349],{"class":3657},": TaskCreate):\n",[3647,8351,8352,8354],{"class":3649,"line":4368},[3647,8353,4647],{"class":3653},[3647,8355,8356],{"class":3657}," task\n",[3560,8358,8360],{"id":8359},"запуск-та-тестування-ендпоінту-з-enumliteral","Запуск та тестування ендпоінту з Enum\u002FLiteral",[3385,8362,8363],{},"Запустимо веб-сайт з додатком Uvicorn:",[3634,8365,8366,8380],{},[3637,8367,8369],{"className":4026,"code":8368,"filename":4663,"language":4029,"meta":3643,"style":3643},"uvicorn enum_literal_demo:app --reload\n",[3401,8370,8371],{"__ignoreMap":3643},[3647,8372,8373,8375,8378],{"class":3649,"line":3650},[3647,8374,4670],{"class":4041},[3647,8376,8377],{"class":4044}," enum_literal_demo:app",[3647,8379,4676],{"class":3677},[3637,8381,8382],{"className":4679,"code":8368,"filename":4680,"language":4681,"meta":3643,"style":3643},[3401,8383,8384],{"__ignoreMap":3643},[3647,8385,8386],{"class":3649,"line":3650},[3647,8387,8368],{"class":3657},[3385,8389,8390],{},"Тепер виконаємо перевірки вхідної валідації.",[4755,8392,8394],{"id":8393},"сценарій-1-успішний-запит-значення-з-переліків","Сценарій 1: Успішний запит (Значення з переліків)",[3385,8396,8397,8398,3552,8401,4831],{},"Надішлемо коректні значення ",[3401,8399,8400],{},"priority",[3401,8402,8403],{},"task_type",[3634,8405,8406,8439],{},[3637,8407,8409],{"className":4026,"code":8408,"filename":4663,"language":4029,"meta":3643,"style":3643},"curl -X POST http:\u002F\u002F127.0.0.1:8000\u002Ftasks \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"title\": \"Написати тести\", \"priority\": \"high\", \"task_type\": \"bug\"}'\n",[3401,8410,8411,8424,8432],{"__ignoreMap":3643},[3647,8412,8413,8415,8417,8419,8422],{"class":3649,"line":3650},[3647,8414,4776],{"class":4041},[3647,8416,4779],{"class":3677},[3647,8418,4782],{"class":4044},[3647,8420,8421],{"class":4044}," http:\u002F\u002F127.0.0.1:8000\u002Ftasks",[3647,8423,4789],{"class":4788},[3647,8425,8426,8428,8430],{"class":3649,"line":3667},[3647,8427,4794],{"class":3677},[3647,8429,4797],{"class":4044},[3647,8431,4789],{"class":4788},[3647,8433,8434,8436],{"class":3649,"line":3674},[3647,8435,4804],{"class":3677},[3647,8437,8438],{"class":4044}," '{\"title\": \"Написати тести\", \"priority\": \"high\", \"task_type\": \"bug\"}'\n",[3637,8440,8442],{"className":4679,"code":8441,"filename":4680,"language":4681,"meta":3643,"style":3643},"curl.exe -X POST http:\u002F\u002F127.0.0.1:8000\u002Ftasks `\n  -H \"Content-Type: application\u002Fjson\" `\n  -d '{\"title\": \"Написати тести\", \"priority\": \"high\", \"task_type\": \"bug\"}'\n",[3401,8443,8444,8463,8471],{"__ignoreMap":3643},[3647,8445,8446,8448,8450,8452,8454,8456,8458,8460],{"class":3649,"line":3650},[3647,8447,4817],{"class":4041},[3647,8449,4820],{"class":3657},[3647,8451,4823],{"class":3732},[3647,8453,3558],{"class":3657},[3647,8455,4828],{"class":3732},[3647,8457,4831],{"class":3657},[3647,8459,4834],{"class":3732},[3647,8461,8462],{"class":3657},"\u002Ftasks `\n",[3647,8464,8465,8467,8469],{"class":3649,"line":3667},[3647,8466,4842],{"class":3657},[3647,8468,4845],{"class":4044},[3647,8470,4848],{"class":3657},[3647,8472,8473,8475],{"class":3649,"line":3674},[3647,8474,4853],{"class":3657},[3647,8476,8477],{"class":4044},"'{\"title\": \"Написати тести\", \"priority\": \"high\", \"task_type\": \"bug\"}'\n",[4692,8479,8481,8487,8490,8493],{":cursor":4859,"title":8480},"Консольний вивід успіху",[4697,8482,8484],{"className":8483},[3649],[3647,8485,4868],{"className":8486},[4867,4704],[4697,8488,4872],{"className":8489},[3649],[4697,8491],{"className":8492},[3649],[4697,8494,8496],{"className":8495},[3649],"{\"title\":\"Написати тести\",\"priority\":\"high\",\"task_type\":\"bug\"}",[4755,8498,8500],{"id":8499},"сценарій-2-помилка-валідації-передача-недопустимого-значення-в-literal","Сценарій 2: Помилка валідації (Передача недопустимого значення в Literal)",[3385,8502,8503,8504,8507],{},"Надішлемо ",[3401,8505,8506],{},"\"task_type\": \"story\"",", якого немає у списку Literal:",[3634,8509,8510,8542],{},[3637,8511,8513],{"className":4026,"code":8512,"filename":4663,"language":4029,"meta":3643,"style":3643},"curl -X POST http:\u002F\u002F127.0.0.1:8000\u002Ftasks \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"title\": \"Написати тести\", \"task_type\": \"story\"}'\n",[3401,8514,8515,8527,8535],{"__ignoreMap":3643},[3647,8516,8517,8519,8521,8523,8525],{"class":3649,"line":3650},[3647,8518,4776],{"class":4041},[3647,8520,4779],{"class":3677},[3647,8522,4782],{"class":4044},[3647,8524,8421],{"class":4044},[3647,8526,4789],{"class":4788},[3647,8528,8529,8531,8533],{"class":3649,"line":3667},[3647,8530,4794],{"class":3677},[3647,8532,4797],{"class":4044},[3647,8534,4789],{"class":4788},[3647,8536,8537,8539],{"class":3649,"line":3674},[3647,8538,4804],{"class":3677},[3647,8540,8541],{"class":4044}," '{\"title\": \"Написати тести\", \"task_type\": \"story\"}'\n",[3637,8543,8545],{"className":4679,"code":8544,"filename":4680,"language":4681,"meta":3643,"style":3643},"curl.exe -X POST http:\u002F\u002F127.0.0.1:8000\u002Ftasks `\n  -H \"Content-Type: application\u002Fjson\" `\n  -d '{\"title\": \"Написати тести\", \"task_type\": \"story\"}'\n",[3401,8546,8547,8565,8573],{"__ignoreMap":3643},[3647,8548,8549,8551,8553,8555,8557,8559,8561,8563],{"class":3649,"line":3650},[3647,8550,4817],{"class":4041},[3647,8552,4820],{"class":3657},[3647,8554,4823],{"class":3732},[3647,8556,3558],{"class":3657},[3647,8558,4828],{"class":3732},[3647,8560,4831],{"class":3657},[3647,8562,4834],{"class":3732},[3647,8564,8462],{"class":3657},[3647,8566,8567,8569,8571],{"class":3649,"line":3667},[3647,8568,4842],{"class":3657},[3647,8570,4845],{"class":4044},[3647,8572,4848],{"class":3657},[3647,8574,8575,8577],{"class":3649,"line":3674},[3647,8576,4853],{"class":3657},[3647,8578,8579],{"class":4044},"'{\"title\": \"Написати тести\", \"task_type\": \"story\"}'\n",[4692,8581,8583,8589,8592,8595],{":cursor":4859,"title":8582},"Консольний вивід помилки Literal",[4697,8584,8586],{"className":8585},[3649],[3647,8587,4969],{"className":8588},[4968,4704],[4697,8590,4872],{"className":8591},[3649],[4697,8593],{"className":8594},[3649],[4697,8596,8598],{"className":8597},[3649],"{\"detail\":[{\"type\":\"literal_error\",\"loc\":[\"body\",\"task_type\"],\"msg\":\"Input should be 'task' or 'bug'\",\"input\":\"story\",\"ctx\":{\"expected\":\"'task' or 'bug'\"}}]}",[3413,8600],{},[3560,8602,8604],{"id":8603},"завантаження-файлів-file-upload","Завантаження файлів (File Upload)",[3385,8606,8607,8608,8611,8612,3558],{},"На відміну від передачі текстових даних у форматі ",[3401,8609,8610],{},"application\u002Fjson",", завантаження файлів на сервер здійснюється за допомогою кодування ",[3389,8613,8614],{},[3401,8615,8616],{},"multipart\u002Fform-data",[3385,8618,8619],{},"FastAPI пропонує два способи отримання файлів від клієнта:",[3424,8621,8622,8637],{},[3427,8623,8624,8629,8630,8633,8636],{},[3389,8625,8626],{},[3401,8627,8628],{},"bytes"," — завантаження всього файлу в оперативну пам'ять сервера:",[8631,8632],"br",{},[5407,8634,8635],{},"Застереження",": Підходить лише для маленьких файлів. Завантаження файлу розміром 1 ГБ призведе до споживання 1 ГБ оперативної пам'яті процесом Python, що може викликати збій сервера через переповнення пам'яті (Out of Memory).",[3427,8638,8639,8643,8644,8647],{},[3389,8640,8641],{},[3401,8642,7939],{}," — асинхронна обгортка над тимчасовим файлом на диску. Вона використовує буферизований файл (",[3401,8645,8646],{},"SpooledTemporaryFile","), який зберігається в пам'яті до певного ліміту розміру, а при перевищенні автоматично записується на диск. Цей підхід є максимально пам'яттєво-ефективним.",[4755,8649,8651,8652],{"id":8650},"ключові-властивості-та-методи-uploadfile","Ключові властивості та методи ",[3401,8653,7939],{},[5119,8655,8656,8665,8674,8680,8686,8692,8698],{},[3427,8657,8658,8661,8662,3469],{},[3401,8659,8660],{},"filename"," — оригінальне ім'я файлу (наприклад, ",[3401,8663,8664],{},"photo.jpg",[3427,8666,8667,8670,8671,3469],{},[3401,8668,8669],{},"content_type"," — MIME-тип файлу (наприклад, ",[3401,8672,8673],{},"image\u002Fjpeg",[3427,8675,8676,8679],{},[3401,8677,8678],{},"file"," — об'єкт файлового дескриптора Python (можна передавати безпосередньо в інші функції читання).",[3427,8681,8682,8685],{},[3401,8683,8684],{},"await read(size)"," — асинхронне зчитування вказаної кількості байт з файлу.",[3427,8687,8688,8691],{},[3401,8689,8690],{},"await write(data)"," — асинхронний запис бінарних даних у файл.",[3427,8693,8694,8697],{},[3401,8695,8696],{},"await seek(offset)"," — переміщення вказівника у файлі.",[3427,8699,8700,8703],{},[3401,8701,8702],{},"await close()"," — закриття файлу та очищення тимчасової пам'яті\u002Fфайлу на диску.",[5093,8705,8706,8707,3595,8710,3595,8713,3552,8716,8719],{},"Методи ",[3401,8708,8709],{},"read()",[3401,8711,8712],{},"write()",[3401,8714,8715],{},"seek()",[3401,8717,8718],{},"close()"," є асинхронними (корутинами), оскільки під капотом FastAPI використовує пул потоків для взаємодії з дисковою файловою системою, щоб не блокувати головний Event Loop вашого додатку під час тривалого запису великих файлів.",[3413,8721],{},[3560,8723,8725,8726,8729,8730],{"id":8724},"порівняння-архітектурних-концепцій-aspnet-core-iformfile-fastapi-uploadfile","Порівняння архітектурних концепцій: ASP.NET Core ",[3401,8727,8728],{},"IFormFile"," ↔ FastAPI ",[3401,8731,7939],{},[3385,8733,8734,8735,8737,8738,8740],{},"Розробники, які переходять з екосистеми .NET Core, звикли працювати з інтерфейсом ",[3401,8736,8728],{}," для отримання файлів у контролерах. Концептуально ",[3401,8739,7939],{}," у FastAPI виконує абсолютно ідентичну роль, оптимізуючи роботу з пам'яттю та надаючи асинхронний інтерфейс.",[3385,8742,8743],{},"Порівняємо основні властивості та методи обох об'єктів:",[8745,8746,8747,8769],"table",{},[8748,8749,8750],"thead",{},[8751,8752,8753,8758,8764],"tr",{},[8754,8755,8757],"th",{"align":8756},"left","Характеристика \u002F Задача",[8754,8759,8760,8761,8763],{"align":8756},"ASP.NET Core (",[3401,8762,8728],{},")",[8754,8765,8766,8767,8763],{"align":8756},"FastAPI (",[3401,8768,7939],{},[8770,8771,8772,8790,8807,8833,8851],"tbody",{},[8751,8773,8774,8780,8785],{},[8775,8776,8777],"td",{"align":8756},[3389,8778,8779],{},"Оригінальне ім'я файлу",[8775,8781,8782],{"align":8756},[3401,8783,8784],{},"file.FileName",[8775,8786,8787],{"align":8756},[3401,8788,8789],{},"file.filename",[8751,8791,8792,8797,8802],{},[8775,8793,8794],{"align":8756},[3389,8795,8796],{},"MIME-тип файлу",[8775,8798,8799],{"align":8756},[3401,8800,8801],{},"file.ContentType",[8775,8803,8804],{"align":8756},[3401,8805,8806],{},"file.content_type",[8751,8808,8809,8814,8820],{},[8775,8810,8811],{"align":8756},[3389,8812,8813],{},"Розмір файлу",[8775,8815,8816,8819],{"align":8756},[3401,8817,8818],{},"file.Length"," (у байтах)",[8775,8821,8822,8823,7932,8826,8829,8830,8763],{"align":8756},"Немає прямої властивості (потрібно зчитати через ",[3401,8824,8825],{},"seek",[3401,8827,8828],{},"read"," або перевірити заголовок ",[3401,8831,8832],{},"Content-Length",[8751,8834,8835,8840,8845],{},[8775,8836,8837],{"align":8756},[3389,8838,8839],{},"Отримання потоку даних",[8775,8841,8842],{"align":8756},[3401,8843,8844],{},"file.OpenReadStream()",[8775,8846,8847,8848],{"align":8756},"Доступ до файлу безпосередньо через ",[3401,8849,8850],{},"file.file",[8751,8852,8853,8858,8863],{},[8775,8854,8855],{"align":8756},[3389,8856,8857],{},"Асинхронне збереження",[8775,8859,8860],{"align":8756},[3401,8861,8862],{},"await file.CopyToAsync(stream)",[8775,8864,8865,8866,8869,8870,8763],{"align":8756},"Запис зчитаних байтів через ",[3401,8867,8868],{},"await file.read()"," та стандартні асинхронні бібліотеки роботи з диском (наприклад, ",[3401,8871,8872],{},"aiofiles",[3385,8874,8875],{},"Порівняємо код ендпоінтів для збереження завантаженого аватара користувача на диск. Приклад для FastAPI є повним: він також містить GET-маршрут для відображення HTML-форми завантаження:",[3634,8877,8878,9315],{},[3637,8879,8882],{"className":3639,"code":8880,"filename":8881,"language":3642,"meta":3643,"style":3643},"# avatar_demo.py\nimport os\nimport aiofiles\nfrom fastapi import FastAPI, File, UploadFile, status\nfrom fastapi.responses import HTMLResponse\n\napp = FastAPI()\n\n@app.get(\"\u002F\", response_class=HTMLResponse)\nasync def main():\n    # Повертаємо просту HTML-сторінку з формою для завантаження файлу\n    content = \"\"\"\n    \u003C!DOCTYPE html>\n    \u003Chtml>\n    \u003Chead>\n        \u003Ctitle>Завантаження аватара\u003C\u002Ftitle>\n        \u003Cstyle>\n            body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f3f4f6; margin: 0; }\n            .card { background: white; padding: 2rem; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); text-align: center; }\n            input[type=\"file\"] { margin: 1rem 0; display: block; }\n            button { background: #3b82f6; color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; }\n            button:hover { background: #2563eb; }\n        \u003C\u002Fstyle>\n    \u003C\u002Fhead>\n    \u003Cbody>\n        \u003Cdiv class=\"card\">\n            \u003Ch2>Завантажити аватар\u003C\u002Fh2>\n            \u003C!-- Важливо: enctype=\"multipart\u002Fform-data\" -->\n            \u003Cform action=\"\u002Fupload-avatar\" enctype=\"multipart\u002Fform-data\" method=\"post\">\n                \u003Cinput name=\"file\" type=\"file\" accept=\"image\u002F*\" required>\n                \u003Cbutton type=\"submit\">Надіслати\u003C\u002Fbutton>\n            \u003C\u002Fform>\n        \u003C\u002Fdiv>\n    \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n    \"\"\"\n    return HTMLResponse(content=content)\n\n@app.post(\"\u002Fupload-avatar\", status_code=status.HTTP_201_CREATED)\nasync def upload_avatar(file: UploadFile = File(...)):\n    # Створюємо директорію для збереження аватарок, якщо її немає\n    os.makedirs(\"static\u002Favatars\", exist_ok=True)\n    destination_path = f\"static\u002Favatars\u002F{file.filename}\"\n\n    # Асинхронно відкриваємо файл для запису бінарних даних (wb)\n    async with aiofiles.open(destination_path, \"wb\") as out_file:\n        while content := await file.read(1024 * 1024):  # Читаємо порціями по 1 МБ\n            await out_file.write(content)\n\n    return {\n        \"filename\": file.filename,\n        \"content_type\": file.content_type,\n        \"status\": \"success\"\n    }\n","FastAPI (Python)",[3401,8883,8884,8889,8896,8903,8914,8926,8930,8934,8938,8956,8968,8973,8981,8986,8991,8996,9001,9006,9011,9016,9021,9026,9031,9036,9041,9046,9051,9056,9061,9066,9071,9076,9081,9086,9091,9096,9101,9114,9118,9133,9149,9154,9173,9195,9199,9204,9226,9257,9265,9269,9276,9288,9300,9310],{"__ignoreMap":3643},[3647,8885,8886],{"class":3649,"line":3650},[3647,8887,8888],{"class":3697},"# avatar_demo.py\n",[3647,8890,8891,8893],{"class":3649,"line":3667},[3647,8892,3661],{"class":3653},[3647,8894,8895],{"class":3657}," os\n",[3647,8897,8898,8900],{"class":3649,"line":3674},[3647,8899,3661],{"class":3653},[3647,8901,8902],{"class":3657}," aiofiles\n",[3647,8904,8905,8907,8909,8911],{"class":3649,"line":3694},[3647,8906,3654],{"class":3653},[3647,8908,4187],{"class":3657},[3647,8910,3661],{"class":3653},[3647,8912,8913],{"class":3657}," FastAPI, File, UploadFile, status\n",[3647,8915,8916,8918,8921,8923],{"class":3649,"line":3701},[3647,8917,3654],{"class":3653},[3647,8919,8920],{"class":3657}," fastapi.responses ",[3647,8922,3661],{"class":3653},[3647,8924,8925],{"class":3657}," HTMLResponse\n",[3647,8927,8928],{"class":3649,"line":3707},[3647,8929,3671],{"emptyLinePlaceholder":3670},[3647,8931,8932],{"class":3649,"line":3713},[3647,8933,5689],{"class":3657},[3647,8935,8936],{"class":3649,"line":3749},[3647,8937,3671],{"emptyLinePlaceholder":3670},[3647,8939,8940,8943,8945,8948,8950,8953],{"class":3649,"line":3886},[3647,8941,8942],{"class":4041},"@app.get",[3647,8944,3685],{"class":3657},[3647,8946,8947],{"class":4044},"\"\u002F\"",[3647,8949,3595],{"class":3657},[3647,8951,8952],{"class":3725},"response_class",[3647,8954,8955],{"class":3657},"=HTMLResponse)\n",[3647,8957,8958,8960,8962,8965],{"class":3649,"line":3895},[3647,8959,4466],{"class":3677},[3647,8961,4469],{"class":3677},[3647,8963,8964],{"class":4041}," main",[3647,8966,8967],{"class":3657},"():\n",[3647,8969,8970],{"class":3649,"line":3920},[3647,8971,8972],{"class":3697},"    # Повертаємо просту HTML-сторінку з формою для завантаження файлу\n",[3647,8974,8975,8978],{"class":3649,"line":3948},[3647,8976,8977],{"class":3657},"    content = ",[3647,8979,8980],{"class":4044},"\"\"\"\n",[3647,8982,8983],{"class":3649,"line":3953},[3647,8984,8985],{"class":4044},"    \u003C!DOCTYPE html>\n",[3647,8987,8988],{"class":3649,"line":3962},[3647,8989,8990],{"class":4044},"    \u003Chtml>\n",[3647,8992,8993],{"class":3649,"line":3983},[3647,8994,8995],{"class":4044},"    \u003Chead>\n",[3647,8997,8998],{"class":3649,"line":4011},[3647,8999,9000],{"class":4044},"        \u003Ctitle>Завантаження аватара\u003C\u002Ftitle>\n",[3647,9002,9003],{"class":3649,"line":4316},[3647,9004,9005],{"class":4044},"        \u003Cstyle>\n",[3647,9007,9008],{"class":3649,"line":4322},[3647,9009,9010],{"class":4044},"            body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f3f4f6; margin: 0; }\n",[3647,9012,9013],{"class":3649,"line":4336},[3647,9014,9015],{"class":4044},"            .card { background: white; padding: 2rem; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); text-align: center; }\n",[3647,9017,9018],{"class":3649,"line":4348},[3647,9019,9020],{"class":4044},"            input[type=\"file\"] { margin: 1rem 0; display: block; }\n",[3647,9022,9023],{"class":3649,"line":4362},[3647,9024,9025],{"class":4044},"            button { background: #3b82f6; color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; }\n",[3647,9027,9028],{"class":3649,"line":4368},[3647,9029,9030],{"class":4044},"            button:hover { background: #2563eb; }\n",[3647,9032,9033],{"class":3649,"line":4373},[3647,9034,9035],{"class":4044},"        \u003C\u002Fstyle>\n",[3647,9037,9038],{"class":3649,"line":4379},[3647,9039,9040],{"class":4044},"    \u003C\u002Fhead>\n",[3647,9042,9043],{"class":3649,"line":4385},[3647,9044,9045],{"class":4044},"    \u003Cbody>\n",[3647,9047,9048],{"class":3649,"line":4401},[3647,9049,9050],{"class":4044},"        \u003Cdiv class=\"card\">\n",[3647,9052,9053],{"class":3649,"line":4406},[3647,9054,9055],{"class":4044},"            \u003Ch2>Завантажити аватар\u003C\u002Fh2>\n",[3647,9057,9058],{"class":3649,"line":4412},[3647,9059,9060],{"class":4044},"            \u003C!-- Важливо: enctype=\"multipart\u002Fform-data\" -->\n",[3647,9062,9063],{"class":3649,"line":4424},[3647,9064,9065],{"class":4044},"            \u003Cform action=\"\u002Fupload-avatar\" enctype=\"multipart\u002Fform-data\" method=\"post\">\n",[3647,9067,9068],{"class":3649,"line":4433},[3647,9069,9070],{"class":4044},"                \u003Cinput name=\"file\" type=\"file\" accept=\"image\u002F*\" required>\n",[3647,9072,9073],{"class":3649,"line":4438},[3647,9074,9075],{"class":4044},"                \u003Cbutton type=\"submit\">Надіслати\u003C\u002Fbutton>\n",[3647,9077,9078],{"class":3649,"line":4463},[3647,9079,9080],{"class":4044},"            \u003C\u002Fform>\n",[3647,9082,9083],{"class":3649,"line":4483},[3647,9084,9085],{"class":4044},"        \u003C\u002Fdiv>\n",[3647,9087,9088],{"class":3649,"line":4492},[3647,9089,9090],{"class":4044},"    \u003C\u002Fbody>\n",[3647,9092,9093],{"class":3649,"line":4497},[3647,9094,9095],{"class":4044},"    \u003C\u002Fhtml>\n",[3647,9097,9098],{"class":3649,"line":4503},[3647,9099,9100],{"class":4044},"    \"\"\"\n",[3647,9102,9103,9105,9108,9111],{"class":3649,"line":4527},[3647,9104,4647],{"class":3653},[3647,9106,9107],{"class":3657}," HTMLResponse(",[3647,9109,9110],{"class":3725},"content",[3647,9112,9113],{"class":3657},"=content)\n",[3647,9115,9116],{"class":3649,"line":4532},[3647,9117,3671],{"emptyLinePlaceholder":3670},[3647,9119,9120,9122,9124,9127,9129,9131],{"class":3649,"line":4538},[3647,9121,4441],{"class":4041},[3647,9123,3685],{"class":3657},[3647,9125,9126],{"class":4044},"\"\u002Fupload-avatar\"",[3647,9128,3595],{"class":3657},[3647,9130,4457],{"class":3725},[3647,9132,4460],{"class":3657},[3647,9134,9135,9137,9139,9142,9144,9146],{"class":3649,"line":4544},[3647,9136,4466],{"class":3677},[3647,9138,4469],{"class":3677},[3647,9140,9141],{"class":4041}," upload_avatar",[3647,9143,3685],{"class":3657},[3647,9145,8678],{"class":3725},[3647,9147,9148],{"class":3657},": UploadFile = File(...)):\n",[3647,9150,9151],{"class":3649,"line":4553},[3647,9152,9153],{"class":3697},"    # Створюємо директорію для збереження аватарок, якщо її немає\n",[3647,9155,9156,9159,9162,9164,9167,9169,9171],{"class":3649,"line":4562},[3647,9157,9158],{"class":3657},"    os.makedirs(",[3647,9160,9161],{"class":4044},"\"static\u002Favatars\"",[3647,9163,3595],{"class":3657},[3647,9165,9166],{"class":3725},"exist_ok",[3647,9168,3729],{"class":3657},[3647,9170,4396],{"class":3677},[3647,9172,3746],{"class":3657},[3647,9174,9175,9178,9180,9183,9185,9188,9191,9193],{"class":3649,"line":4571},[3647,9176,9177],{"class":3657},"    destination_path = ",[3647,9179,4509],{"class":3677},[3647,9181,9182],{"class":4044},"\"static\u002Favatars\u002F",[3647,9184,4515],{"class":3677},[3647,9186,8678],{"class":9187},"sD7JJ",[3647,9189,9190],{"class":3657},".filename",[3647,9192,4521],{"class":3677},[3647,9194,4524],{"class":4044},[3647,9196,9197],{"class":3649,"line":4580},[3647,9198,3671],{"emptyLinePlaceholder":3670},[3647,9200,9201],{"class":3649,"line":4593},[3647,9202,9203],{"class":3697},"    # Асинхронно відкриваємо файл для запису бінарних даних (wb)\n",[3647,9205,9206,9209,9212,9215,9218,9220,9223],{"class":3649,"line":4602},[3647,9207,9208],{"class":3653},"    async",[3647,9210,9211],{"class":3653}," with",[3647,9213,9214],{"class":3657}," aiofiles.open(destination_path, ",[3647,9216,9217],{"class":4044},"\"wb\"",[3647,9219,5838],{"class":3657},[3647,9221,9222],{"class":3653},"as",[3647,9224,9225],{"class":3657}," out_file:\n",[3647,9227,9228,9231,9234,9237,9240,9243,9246,9249,9251,9254],{"class":3649,"line":4608},[3647,9229,9230],{"class":3653},"        while",[3647,9232,9233],{"class":3657}," content := ",[3647,9235,9236],{"class":3653},"await",[3647,9238,9239],{"class":9187}," file",[3647,9241,9242],{"class":3657},".read(",[3647,9244,9245],{"class":3732},"1024",[3647,9247,9248],{"class":3657}," * ",[3647,9250,9245],{"class":3732},[3647,9252,9253],{"class":3657},"):  ",[3647,9255,9256],{"class":3697},"# Читаємо порціями по 1 МБ\n",[3647,9258,9259,9262],{"class":3649,"line":4613},[3647,9260,9261],{"class":3653},"            await",[3647,9263,9264],{"class":3657}," out_file.write(content)\n",[3647,9266,9267],{"class":3649,"line":4619},[3647,9268,3671],{"emptyLinePlaceholder":3670},[3647,9270,9271,9273],{"class":3649,"line":4627},[3647,9272,4647],{"class":3653},[3647,9274,9275],{"class":3657}," {\n",[3647,9277,9278,9281,9283,9285],{"class":3649,"line":4632},[3647,9279,9280],{"class":4044},"        \"filename\"",[3647,9282,4342],{"class":3657},[3647,9284,8678],{"class":9187},[3647,9286,9287],{"class":3657},".filename,\n",[3647,9289,9290,9293,9295,9297],{"class":3649,"line":4638},[3647,9291,9292],{"class":4044},"        \"content_type\"",[3647,9294,4342],{"class":3657},[3647,9296,8678],{"class":9187},[3647,9298,9299],{"class":3657},".content_type,\n",[3647,9301,9302,9305,9307],{"class":3649,"line":4644},[3647,9303,9304],{"class":4044},"        \"status\"",[3647,9306,4342],{"class":3657},[3647,9308,9309],{"class":4044},"\"success\"\n",[3647,9311,9313],{"class":3649,"line":9312},54,[3647,9314,4605],{"class":3657},[3637,9316,9318],{"className":3777,"code":9317,"filename":3779,"language":3780,"meta":3643,"style":3643},"using Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing System.IO;\nusing System.Threading.Tasks;\n\n[ApiController]\n[Route(\"[controller]\")]\npublic class AvatarController : ControllerBase\n{\n    \u002F\u002F В ASP.NET Core для рендерингу HTML форми зазвичай використовують Razor Pages \u002F Views\n    [HttpPost(\"upload-avatar\")]\n    public async Task\u003CIActionResult> UploadAvatar(IFormFile file)\n    {\n        if (file == null || file.Length == 0)\n            return BadRequest(\"Файл не передано\");\n\n        var destinationPath = Path.Combine(\"static\u002Favatars\", file.FileName);\n\n        using (var stream = new FileStream(destinationPath, FileMode.Create))\n        {\n            await file.CopyToAsync(stream);\n        }\n\n        return Created(string.Empty, new {\n            filename = file.FileName,\n            contentType = file.ContentType,\n            status = \"success\"\n        });\n    }\n}\n",[3401,9319,9320,9339,9356,9369,9387,9391,9400,9414,9429,9433,9438,9452,9482,9487,9517,9533,9537,9570,9574,9613,9618,9636,9640,9644,9665,9680,9696,9705,9710,9714],{"__ignoreMap":3643},[3647,9321,9322,9324,9327,9329,9332,9334,9337],{"class":3649,"line":3650},[3647,9323,3787],{"class":3653},[3647,9325,9326],{"class":3681}," Microsoft",[3647,9328,3558],{"class":3657},[3647,9330,9331],{"class":3681},"AspNetCore",[3647,9333,3558],{"class":3657},[3647,9335,9336],{"class":3681},"Http",[3647,9338,3803],{"class":3657},[3647,9340,9341,9343,9345,9347,9349,9351,9354],{"class":3649,"line":3667},[3647,9342,3787],{"class":3653},[3647,9344,9326],{"class":3681},[3647,9346,3558],{"class":3657},[3647,9348,9331],{"class":3681},[3647,9350,3558],{"class":3657},[3647,9352,9353],{"class":3681},"Mvc",[3647,9355,3803],{"class":3657},[3647,9357,9358,9360,9362,9364,9367],{"class":3649,"line":3674},[3647,9359,3787],{"class":3653},[3647,9361,3790],{"class":3681},[3647,9363,3558],{"class":3657},[3647,9365,9366],{"class":3681},"IO",[3647,9368,3803],{"class":3657},[3647,9370,9371,9373,9375,9377,9380,9382,9385],{"class":3649,"line":3694},[3647,9372,3787],{"class":3653},[3647,9374,3790],{"class":3681},[3647,9376,3558],{"class":3657},[3647,9378,9379],{"class":3681},"Threading",[3647,9381,3558],{"class":3657},[3647,9383,9384],{"class":3681},"Tasks",[3647,9386,3803],{"class":3657},[3647,9388,9389],{"class":3649,"line":3701},[3647,9390,3671],{"emptyLinePlaceholder":3670},[3647,9392,9393,9395,9398],{"class":3649,"line":3707},[3647,9394,7062],{"class":3657},[3647,9396,9397],{"class":3681},"ApiController",[3647,9399,3834],{"class":3657},[3647,9401,9402,9404,9407,9409,9412],{"class":3649,"line":3713},[3647,9403,7062],{"class":3657},[3647,9405,9406],{"class":3681},"Route",[3647,9408,3685],{"class":3657},[3647,9410,9411],{"class":4044},"\"[controller]\"",[3647,9413,3917],{"class":3657},[3647,9415,9416,9418,9420,9423,9426],{"class":3649,"line":3749},[3647,9417,3812],{"class":3677},[3647,9419,3815],{"class":3677},[3647,9421,9422],{"class":3681}," AvatarController",[3647,9424,9425],{"class":3657}," : ",[3647,9427,9428],{"class":3681},"ControllerBase\n",[3647,9430,9431],{"class":3649,"line":3886},[3647,9432,3823],{"class":3657},[3647,9434,9435],{"class":3649,"line":3895},[3647,9436,9437],{"class":3697},"    \u002F\u002F В ASP.NET Core для рендерингу HTML форми зазвичай використовують Razor Pages \u002F Views\n",[3647,9439,9440,9442,9445,9447,9450],{"class":3649,"line":3920},[3647,9441,3828],{"class":3657},[3647,9443,9444],{"class":3681},"HttpPost",[3647,9446,3685],{"class":3657},[3647,9448,9449],{"class":4044},"\"upload-avatar\"",[3647,9451,3917],{"class":3657},[3647,9453,9454,9456,9459,9462,9465,9468,9471,9474,9476,9478,9480],{"class":3649,"line":3948},[3647,9455,3848],{"class":3677},[3647,9457,9458],{"class":3677}," async",[3647,9460,9461],{"class":3681}," Task",[3647,9463,9464],{"class":3657},"\u003C",[3647,9466,9467],{"class":3681},"IActionResult",[3647,9469,9470],{"class":3657},"> ",[3647,9472,9473],{"class":4041},"UploadAvatar",[3647,9475,3685],{"class":3657},[3647,9477,8728],{"class":3681},[3647,9479,9239],{"class":3725},[3647,9481,3746],{"class":3657},[3647,9483,9484],{"class":3649,"line":3953},[3647,9485,9486],{"class":3657},"    {\n",[3647,9488,9489,9491,9494,9496,9499,9501,9504,9506,9508,9511,9513,9515],{"class":3649,"line":3962},[3647,9490,5607],{"class":3653},[3647,9492,9493],{"class":3657}," (",[3647,9495,8678],{"class":3725},[3647,9497,9498],{"class":3657}," == ",[3647,9500,5167],{"class":3677},[3647,9502,9503],{"class":3657}," || ",[3647,9505,8678],{"class":3725},[3647,9507,3558],{"class":3657},[3647,9509,9510],{"class":3725},"Length",[3647,9512,9498],{"class":3657},[3647,9514,6418],{"class":3732},[3647,9516,3746],{"class":3657},[3647,9518,9519,9522,9525,9527,9530],{"class":3649,"line":3983},[3647,9520,9521],{"class":3653},"            return",[3647,9523,9524],{"class":4041}," BadRequest",[3647,9526,3685],{"class":3657},[3647,9528,9529],{"class":4044},"\"Файл не передано\"",[3647,9531,9532],{"class":3657},");\n",[3647,9534,9535],{"class":3649,"line":4011},[3647,9536,3671],{"emptyLinePlaceholder":3670},[3647,9538,9539,9542,9545,9547,9550,9552,9555,9557,9559,9561,9563,9565,9568],{"class":3649,"line":4316},[3647,9540,9541],{"class":3677},"        var",[3647,9543,9544],{"class":3725}," destinationPath",[3647,9546,3912],{"class":3657},[3647,9548,9549],{"class":3725},"Path",[3647,9551,3558],{"class":3657},[3647,9553,9554],{"class":4041},"Combine",[3647,9556,3685],{"class":3657},[3647,9558,9161],{"class":4044},[3647,9560,3595],{"class":3657},[3647,9562,8678],{"class":3725},[3647,9564,3558],{"class":3657},[3647,9566,9567],{"class":3725},"FileName",[3647,9569,9532],{"class":3657},[3647,9571,9572],{"class":3649,"line":4322},[3647,9573,3671],{"emptyLinePlaceholder":3670},[3647,9575,9576,9579,9581,9584,9587,9589,9592,9595,9597,9600,9602,9605,9607,9610],{"class":3649,"line":4336},[3647,9577,9578],{"class":3653},"        using",[3647,9580,9493],{"class":3657},[3647,9582,9583],{"class":3677},"var",[3647,9585,9586],{"class":3725}," stream",[3647,9588,3912],{"class":3657},[3647,9590,9591],{"class":3677},"new",[3647,9593,9594],{"class":3681}," FileStream",[3647,9596,3685],{"class":3657},[3647,9598,9599],{"class":3725},"destinationPath",[3647,9601,3595],{"class":3657},[3647,9603,9604],{"class":3725},"FileMode",[3647,9606,3558],{"class":3657},[3647,9608,9609],{"class":3725},"Create",[3647,9611,9612],{"class":3657},"))\n",[3647,9614,9615],{"class":3649,"line":4348},[3647,9616,9617],{"class":3657},"        {\n",[3647,9619,9620,9622,9624,9626,9629,9631,9634],{"class":3649,"line":4362},[3647,9621,9261],{"class":3677},[3647,9623,9239],{"class":3725},[3647,9625,3558],{"class":3657},[3647,9627,9628],{"class":4041},"CopyToAsync",[3647,9630,3685],{"class":3657},[3647,9632,9633],{"class":3725},"stream",[3647,9635,9532],{"class":3657},[3647,9637,9638],{"class":3649,"line":4368},[3647,9639,7733],{"class":3657},[3647,9641,9642],{"class":3649,"line":4373},[3647,9643,3671],{"emptyLinePlaceholder":3670},[3647,9645,9646,9648,9651,9653,9655,9657,9659,9661,9663],{"class":3649,"line":4379},[3647,9647,5638],{"class":3653},[3647,9649,9650],{"class":4041}," Created",[3647,9652,3685],{"class":3657},[3647,9654,3872],{"class":3677},[3647,9656,3558],{"class":3657},[3647,9658,3877],{"class":3725},[3647,9660,3595],{"class":3657},[3647,9662,9591],{"class":3677},[3647,9664,9275],{"class":3657},[3647,9666,9667,9670,9672,9674,9676,9678],{"class":3649,"line":4385},[3647,9668,9669],{"class":3725},"            filename",[3647,9671,3912],{"class":3657},[3647,9673,8678],{"class":3725},[3647,9675,3558],{"class":3657},[3647,9677,9567],{"class":3725},[3647,9679,4590],{"class":3657},[3647,9681,9682,9685,9687,9689,9691,9694],{"class":3649,"line":4401},[3647,9683,9684],{"class":3725},"            contentType",[3647,9686,3912],{"class":3657},[3647,9688,8678],{"class":3725},[3647,9690,3558],{"class":3657},[3647,9692,9693],{"class":3725},"ContentType",[3647,9695,4590],{"class":3657},[3647,9697,9698,9701,9703],{"class":3649,"line":4406},[3647,9699,9700],{"class":3725},"            status",[3647,9702,3912],{"class":3657},[3647,9704,9309],{"class":4044},[3647,9706,9707],{"class":3649,"line":4412},[3647,9708,9709],{"class":3657},"        });\n",[3647,9711,9712],{"class":3649,"line":4424},[3647,9713,4605],{"class":3657},[3647,9715,9716],{"class":3649,"line":4433},[3647,9717,4014],{"class":3657},[9719,9720,9721,9722,9724,9725,9728,9729,9731],"important",{},"Зверніть увагу: для асинхронної роботи з файловою системою в Python використовується бібліотека ",[3401,9723,8872],{},", оскільки стандартна вбудована функція Python ",[3401,9726,9727],{},"open()"," є блокуючою (синхронною). Бібліотека ",[3401,9730,8872],{}," делегує дискові операції пулу потоків, зберігаючи Event Loop вільним від блокувань.",[3560,9733,9735],{"id":9734},"запуск-та-тестування-ендпоінту-завантаження-файлів","Запуск та тестування ендпоінту завантаження файлів",[3385,9737,8363],{},[3634,9739,9740,9754],{},[3637,9741,9743],{"className":4026,"code":9742,"filename":4663,"language":4029,"meta":3643,"style":3643},"uvicorn avatar_demo:app --reload\n",[3401,9744,9745],{"__ignoreMap":3643},[3647,9746,9747,9749,9752],{"class":3649,"line":3650},[3647,9748,4670],{"class":4041},[3647,9750,9751],{"class":4044}," avatar_demo:app",[3647,9753,4676],{"class":3677},[3637,9755,9756],{"className":4679,"code":9742,"filename":4680,"language":4681,"meta":3643,"style":3643},[3401,9757,9758],{"__ignoreMap":3643},[3647,9759,9760],{"class":3649,"line":3650},[3647,9761,9742],{"class":3657},[3385,9763,9764,9765,9768,9769,9772],{},"Тепер ви можете або відкрити браузер за адресою ",[3401,9766,9767],{},"http:\u002F\u002F127.0.0.1:8000\u002F"," та скористатися графічним інтерфейсом завантаження файлу, або виконати cURL-запит з використанням прапорця ",[3401,9770,9771],{},"-F"," (Multipart Form data) для завантаження файлу безпосередньо з консолі:",[3634,9774,9775,9801],{},[3637,9776,9778],{"className":4026,"code":9777,"filename":4663,"language":4029,"meta":3643,"style":3643},"curl -X POST http:\u002F\u002F127.0.0.1:8000\u002Fupload-avatar \\\n  -F \"file=@avatar.png\"\n",[3401,9779,9780,9793],{"__ignoreMap":3643},[3647,9781,9782,9784,9786,9788,9791],{"class":3649,"line":3650},[3647,9783,4776],{"class":4041},[3647,9785,4779],{"class":3677},[3647,9787,4782],{"class":4044},[3647,9789,9790],{"class":4044}," http:\u002F\u002F127.0.0.1:8000\u002Fupload-avatar",[3647,9792,4789],{"class":4788},[3647,9794,9795,9798],{"class":3649,"line":3667},[3647,9796,9797],{"class":3677},"  -F",[3647,9799,9800],{"class":4044}," \"file=@avatar.png\"\n",[3637,9802,9804],{"className":4679,"code":9803,"filename":4680,"language":4681,"meta":3643,"style":3643},"curl.exe -X POST http:\u002F\u002F127.0.0.1:8000\u002Fupload-avatar `\n  -F \"file=@avatar.png\"\n",[3401,9805,9806,9825],{"__ignoreMap":3643},[3647,9807,9808,9810,9812,9814,9816,9818,9820,9822],{"class":3649,"line":3650},[3647,9809,4817],{"class":4041},[3647,9811,4820],{"class":3657},[3647,9813,4823],{"class":3732},[3647,9815,3558],{"class":3657},[3647,9817,4828],{"class":3732},[3647,9819,4831],{"class":3657},[3647,9821,4834],{"class":3732},[3647,9823,9824],{"class":3657},"\u002Fupload-avatar `\n",[3647,9826,9827,9829],{"class":3649,"line":3667},[3647,9828,9797],{"class":3657},[3647,9830,9800],{"class":4044},[4692,9832,9834,9840,9843,9846],{":cursor":4859,"title":9833},"Консольний вивід відповіді завантаження",[4697,9835,9837],{"className":9836},[3649],[3647,9838,4868],{"className":9839},[4867,4704],[4697,9841,4872],{"className":9842},[3649],[4697,9844],{"className":9845},[3649],[4697,9847,9849],{"className":9848},[3649],"{\"filename\":\"avatar.png\",\"content_type\":\"image\u002Fpng\",\"status\":\"success\"}",[3413,9851],{},[3416,9853,9855],{"id":9854},"частина-v-практичні-завдання-та-інтеграція-в-taskforge","Частина V: Практичні завдання та інтеграція в TaskForge",[3385,9857,9858],{},"Для закріплення матеріалу розберемо практичні вправи різного рівня складності: від проектування моделей блогу та побудови універсальних generic-схем до розробки самостійного мікропроекту валідації платежів та рефакторингу TaskForge.",[3413,9860],{},[3560,9862,9864],{"id":9863},"практичні-вправи","Практичні вправи",[4755,9866,9868],{"id":9867},"вправа-1-проектування-схем-для-crud-блогу","Вправа 1: Проектування схем для CRUD блогу",[3385,9870,9871,9874,9875,9878,9879,9882,9883,9886],{},[3389,9872,9873],{},"Завдання",": Спроектувати ієрархію Pydantic-моделей для сутностей ",[3401,9876,9877],{},"Author"," (Автор), ",[3401,9880,9881],{},"Post"," (Пост) та ",[3401,9884,9885],{},"Comment"," (Коментар). Моделі мають запобігати дублюванню коду (DRY) та підтримувати такі вимоги:",[5119,9888,9889,9895],{},[3427,9890,9891,9892,3558],{},"При створенні коментаря обов'язково передавати ",[3401,9893,9894],{},"post_id",[3427,9896,9897,9898,9900,9901,9904,9905,3469],{},"При виведенні поста (",[3401,9899,6569],{},") повертати список коментарів (",[3401,9902,9903],{},"list[CommentRead]",") та повну інформацію про автора (",[3401,9906,6565],{},[3637,9908,9911],{"className":3639,"code":9909,"filename":9910,"language":3642,"meta":3643,"style":3643},"# blog_schemas.py\nfrom datetime import datetime\nfrom pydantic import BaseModel, EmailStr, Field\n\n# --- СХЕМИ АВТОРА ---\nclass AuthorBase(BaseModel):\n    name: str = Field(..., min_length=2, max_length=100)\n    email: EmailStr\n\nclass AuthorCreate(AuthorBase):\n    pass\n\nclass AuthorRead(AuthorBase):\n    id: int\n    class Config:\n        from_attributes = True\n\n# --- СХЕМИ КОМЕНТАРЯ ---\nclass CommentBase(BaseModel):\n    content: str = Field(..., min_length=1, max_length=500)\n\nclass CommentCreate(CommentBase):\n    post_id: int\n\nclass CommentRead(CommentBase):\n    id: int\n    created_at: datetime\n    class Config:\n        from_attributes = True\n\n# --- СХЕМИ ПОСТА ---\nclass PostBase(BaseModel):\n    title: str = Field(..., min_length=5, max_length=200)\n    content: str = Field(..., min_length=10)\n\nclass PostCreate(PostBase):\n    author_id: int\n\nclass PostRead(PostBase):\n    id: int\n    author: AuthorRead\n    comments: list[CommentRead] = []\n    created_at: datetime\n    class Config:\n        from_attributes = True\n","blog_schemas.py",[3401,9912,9913,9918,9928,9938,9942,9947,9960,9984,9988,9992,10006,10011,10015,10027,10035,10045,10052,10056,10061,10074,10098,10102,10116,10123,10127,10140,10148,10152,10160,10166,10170,10175,10188,10214,10230,10234,10248,10254,10258,10270,10278,10282,10287,10291,10299],{"__ignoreMap":3643},[3647,9914,9915],{"class":3649,"line":3650},[3647,9916,9917],{"class":3697},"# blog_schemas.py\n",[3647,9919,9920,9922,9924,9926],{"class":3649,"line":3667},[3647,9921,3654],{"class":3653},[3647,9923,4164],{"class":3657},[3647,9925,3661],{"class":3653},[3647,9927,4169],{"class":3657},[3647,9929,9930,9932,9934,9936],{"class":3649,"line":3674},[3647,9931,3654],{"class":3653},[3647,9933,3658],{"class":3657},[3647,9935,3661],{"class":3653},[3647,9937,3664],{"class":3657},[3647,9939,9940],{"class":3649,"line":3694},[3647,9941,3671],{"emptyLinePlaceholder":3670},[3647,9943,9944],{"class":3649,"line":3701},[3647,9945,9946],{"class":3697},"# --- СХЕМИ АВТОРА ---\n",[3647,9948,9949,9951,9954,9956,9958],{"class":3649,"line":3707},[3647,9950,3678],{"class":3677},[3647,9952,9953],{"class":3681}," AuthorBase",[3647,9955,3685],{"class":3657},[3647,9957,3688],{"class":3681},[3647,9959,3691],{"class":3657},[3647,9961,9962,9964,9966,9968,9970,9972,9974,9976,9978,9980,9982],{"class":3649,"line":3713},[3647,9963,6604],{"class":3657},[3647,9965,3719],{"class":3681},[3647,9967,4246],{"class":3657},[3647,9969,3726],{"class":3725},[3647,9971,3729],{"class":3657},[3647,9973,7328],{"class":3732},[3647,9975,3595],{"class":3657},[3647,9977,3738],{"class":3725},[3647,9979,3729],{"class":3657},[3647,9981,3743],{"class":3732},[3647,9983,3746],{"class":3657},[3647,9985,9986],{"class":3649,"line":3749},[3647,9987,3704],{"class":3657},[3647,9989,9990],{"class":3649,"line":3886},[3647,9991,3671],{"emptyLinePlaceholder":3670},[3647,9993,9994,9996,9999,10001,10004],{"class":3649,"line":3895},[3647,9995,3678],{"class":3677},[3647,9997,9998],{"class":3681}," AuthorCreate",[3647,10000,3685],{"class":3657},[3647,10002,10003],{"class":3681},"AuthorBase",[3647,10005,3691],{"class":3657},[3647,10007,10008],{"class":3649,"line":3920},[3647,10009,10010],{"class":3653},"    pass\n",[3647,10012,10013],{"class":3649,"line":3948},[3647,10014,3671],{"emptyLinePlaceholder":3670},[3647,10016,10017,10019,10021,10023,10025],{"class":3649,"line":3953},[3647,10018,3678],{"class":3677},[3647,10020,6585],{"class":3681},[3647,10022,3685],{"class":3657},[3647,10024,10003],{"class":3681},[3647,10026,3691],{"class":3657},[3647,10028,10029,10031,10033],{"class":3649,"line":3962},[3647,10030,4339],{"class":4041},[3647,10032,4342],{"class":3657},[3647,10034,4345],{"class":3681},[3647,10036,10037,10040,10043],{"class":3649,"line":3983},[3647,10038,10039],{"class":3677},"    class",[3647,10041,10042],{"class":3681}," Config",[3647,10044,5597],{"class":3657},[3647,10046,10047,10050],{"class":3649,"line":4011},[3647,10048,10049],{"class":3657},"        from_attributes = ",[3647,10051,4359],{"class":3677},[3647,10053,10054],{"class":3649,"line":4316},[3647,10055,3671],{"emptyLinePlaceholder":3670},[3647,10057,10058],{"class":3649,"line":4322},[3647,10059,10060],{"class":3697},"# --- СХЕМИ КОМЕНТАРЯ ---\n",[3647,10062,10063,10065,10068,10070,10072],{"class":3649,"line":4336},[3647,10064,3678],{"class":3677},[3647,10066,10067],{"class":3681}," CommentBase",[3647,10069,3685],{"class":3657},[3647,10071,3688],{"class":3681},[3647,10073,3691],{"class":3657},[3647,10075,10076,10078,10080,10082,10084,10086,10088,10090,10092,10094,10096],{"class":3649,"line":4348},[3647,10077,6828],{"class":3657},[3647,10079,3719],{"class":3681},[3647,10081,4246],{"class":3657},[3647,10083,3726],{"class":3725},[3647,10085,3729],{"class":3657},[3647,10087,5713],{"class":3732},[3647,10089,3595],{"class":3657},[3647,10091,3738],{"class":3725},[3647,10093,3729],{"class":3657},[3647,10095,5835],{"class":3732},[3647,10097,3746],{"class":3657},[3647,10099,10100],{"class":3649,"line":4362},[3647,10101,3671],{"emptyLinePlaceholder":3670},[3647,10103,10104,10106,10109,10111,10114],{"class":3649,"line":4368},[3647,10105,3678],{"class":3677},[3647,10107,10108],{"class":3681}," CommentCreate",[3647,10110,3685],{"class":3657},[3647,10112,10113],{"class":3681},"CommentBase",[3647,10115,3691],{"class":3657},[3647,10117,10118,10121],{"class":3649,"line":4373},[3647,10119,10120],{"class":3657},"    post_id: ",[3647,10122,4345],{"class":3681},[3647,10124,10125],{"class":3649,"line":4379},[3647,10126,3671],{"emptyLinePlaceholder":3670},[3647,10128,10129,10131,10134,10136,10138],{"class":3649,"line":4385},[3647,10130,3678],{"class":3677},[3647,10132,10133],{"class":3681}," CommentRead",[3647,10135,3685],{"class":3657},[3647,10137,10113],{"class":3681},[3647,10139,3691],{"class":3657},[3647,10141,10142,10144,10146],{"class":3649,"line":4401},[3647,10143,4339],{"class":4041},[3647,10145,4342],{"class":3657},[3647,10147,4345],{"class":3681},[3647,10149,10150],{"class":3649,"line":4406},[3647,10151,4365],{"class":3657},[3647,10153,10154,10156,10158],{"class":3649,"line":4412},[3647,10155,10039],{"class":3677},[3647,10157,10042],{"class":3681},[3647,10159,5597],{"class":3657},[3647,10161,10162,10164],{"class":3649,"line":4424},[3647,10163,10049],{"class":3657},[3647,10165,4359],{"class":3677},[3647,10167,10168],{"class":3649,"line":4433},[3647,10169,3671],{"emptyLinePlaceholder":3670},[3647,10171,10172],{"class":3649,"line":4438},[3647,10173,10174],{"class":3697},"# --- СХЕМИ ПОСТА ---\n",[3647,10176,10177,10179,10182,10184,10186],{"class":3649,"line":4463},[3647,10178,3678],{"class":3677},[3647,10180,10181],{"class":3681}," PostBase",[3647,10183,3685],{"class":3657},[3647,10185,3688],{"class":3681},[3647,10187,3691],{"class":3657},[3647,10189,10190,10192,10194,10196,10198,10200,10203,10205,10207,10209,10212],{"class":3649,"line":4483},[3647,10191,6821],{"class":3657},[3647,10193,3719],{"class":3681},[3647,10195,4246],{"class":3657},[3647,10197,3726],{"class":3725},[3647,10199,3729],{"class":3657},[3647,10201,10202],{"class":3732},"5",[3647,10204,3595],{"class":3657},[3647,10206,3738],{"class":3725},[3647,10208,3729],{"class":3657},[3647,10210,10211],{"class":3732},"200",[3647,10213,3746],{"class":3657},[3647,10215,10216,10218,10220,10222,10224,10226,10228],{"class":3649,"line":4492},[3647,10217,6828],{"class":3657},[3647,10219,3719],{"class":3681},[3647,10221,4246],{"class":3657},[3647,10223,3726],{"class":3725},[3647,10225,3729],{"class":3657},[3647,10227,7106],{"class":3732},[3647,10229,3746],{"class":3657},[3647,10231,10232],{"class":3649,"line":4497},[3647,10233,3671],{"emptyLinePlaceholder":3670},[3647,10235,10236,10238,10241,10243,10246],{"class":3649,"line":4503},[3647,10237,3678],{"class":3677},[3647,10239,10240],{"class":3681}," PostCreate",[3647,10242,3685],{"class":3657},[3647,10244,10245],{"class":3681},"PostBase",[3647,10247,3691],{"class":3657},[3647,10249,10250,10252],{"class":3649,"line":4527},[3647,10251,6835],{"class":3657},[3647,10253,4345],{"class":3681},[3647,10255,10256],{"class":3649,"line":4532},[3647,10257,3671],{"emptyLinePlaceholder":3670},[3647,10259,10260,10262,10264,10266,10268],{"class":3649,"line":4538},[3647,10261,3678],{"class":3677},[3647,10263,6802],{"class":3681},[3647,10265,3685],{"class":3657},[3647,10267,10245],{"class":3681},[3647,10269,3691],{"class":3657},[3647,10271,10272,10274,10276],{"class":3649,"line":4544},[3647,10273,4339],{"class":4041},[3647,10275,4342],{"class":3657},[3647,10277,4345],{"class":3681},[3647,10279,10280],{"class":3649,"line":4553},[3647,10281,6847],{"class":3657},[3647,10283,10284],{"class":3649,"line":4562},[3647,10285,10286],{"class":3657},"    comments: list[CommentRead] = []\n",[3647,10288,10289],{"class":3649,"line":4571},[3647,10290,4365],{"class":3657},[3647,10292,10293,10295,10297],{"class":3649,"line":4580},[3647,10294,10039],{"class":3677},[3647,10296,10042],{"class":3681},[3647,10298,5597],{"class":3657},[3647,10300,10301,10303],{"class":3649,"line":4593},[3647,10302,10049],{"class":3657},[3647,10304,4359],{"class":3677},[4755,10306,10308],{"id":10307},"вправа-2-часткове-оновлення-статті-з-бізнес-валідацією","Вправа 2: Часткове оновлення статті з бізнес-валідацією",[3385,10310,10311,10313,10314,10317,10318,3465,10320,10322],{},[3389,10312,9873],{},": Написати схему ",[3401,10315,10316],{},"PostUpdate",", де всі поля є опціональними. Додати валідатор, який перевіряє, що якщо поле ",[3401,10319,4204],{},[3401,10321,9110],{}," було передано, воно не повинно бути порожнім (не містити лише пробіли).",[3637,10324,10327],{"className":3639,"code":10325,"filename":10326,"language":3642,"meta":3643,"style":3643},"# post_update.py\nfrom pydantic import BaseModel, Field, field_validator\n\nclass PostUpdate(BaseModel):\n    title: str | None = Field(default=None, min_length=5, max_length=200)\n    content: str | None = Field(default=None, min_length=10)\n\n    @field_validator(\"title\", \"content\")\n    @classmethod\n    def check_not_empty(cls, value: str | None) -> str | None:\n        if value is not None and not value.strip():\n            raise ValueError(\"Поле не може складатися лише з пробілів\")\n        return value\n","post_update.py",[3401,10328,10329,10334,10344,10348,10361,10397,10425,10429,10445,10451,10484,10504,10517],{"__ignoreMap":3643},[3647,10330,10331],{"class":3649,"line":3650},[3647,10332,10333],{"class":3697},"# post_update.py\n",[3647,10335,10336,10338,10340,10342],{"class":3649,"line":3667},[3647,10337,3654],{"class":3653},[3647,10339,3658],{"class":3657},[3647,10341,3661],{"class":3653},[3647,10343,5484],{"class":3657},[3647,10345,10346],{"class":3649,"line":3674},[3647,10347,3671],{"emptyLinePlaceholder":3670},[3647,10349,10350,10352,10355,10357,10359],{"class":3649,"line":3694},[3647,10351,3678],{"class":3677},[3647,10353,10354],{"class":3681}," PostUpdate",[3647,10356,3685],{"class":3657},[3647,10358,3688],{"class":3681},[3647,10360,3691],{"class":3657},[3647,10362,10363,10365,10367,10369,10371,10373,10375,10377,10379,10381,10383,10385,10387,10389,10391,10393,10395],{"class":3649,"line":3701},[3647,10364,6821],{"class":3657},[3647,10366,3719],{"class":3681},[3647,10368,5084],{"class":3657},[3647,10370,5087],{"class":3677},[3647,10372,3722],{"class":3657},[3647,10374,5397],{"class":3725},[3647,10376,3729],{"class":3657},[3647,10378,5087],{"class":3677},[3647,10380,3595],{"class":3657},[3647,10382,3726],{"class":3725},[3647,10384,3729],{"class":3657},[3647,10386,10202],{"class":3732},[3647,10388,3595],{"class":3657},[3647,10390,3738],{"class":3725},[3647,10392,3729],{"class":3657},[3647,10394,10211],{"class":3732},[3647,10396,3746],{"class":3657},[3647,10398,10399,10401,10403,10405,10407,10409,10411,10413,10415,10417,10419,10421,10423],{"class":3649,"line":3707},[3647,10400,6828],{"class":3657},[3647,10402,3719],{"class":3681},[3647,10404,5084],{"class":3657},[3647,10406,5087],{"class":3677},[3647,10408,3722],{"class":3657},[3647,10410,5397],{"class":3725},[3647,10412,3729],{"class":3657},[3647,10414,5087],{"class":3677},[3647,10416,3595],{"class":3657},[3647,10418,3726],{"class":3725},[3647,10420,3729],{"class":3657},[3647,10422,7106],{"class":3732},[3647,10424,3746],{"class":3657},[3647,10426,10427],{"class":3649,"line":3713},[3647,10428,3671],{"emptyLinePlaceholder":3670},[3647,10430,10431,10433,10435,10438,10440,10443],{"class":3649,"line":3749},[3647,10432,5545],{"class":4041},[3647,10434,3685],{"class":3657},[3647,10436,10437],{"class":4044},"\"title\"",[3647,10439,3595],{"class":3657},[3647,10441,10442],{"class":4044},"\"content\"",[3647,10444,3746],{"class":3657},[3647,10446,10447,10449],{"class":3649,"line":3886},[3647,10448,5556],{"class":4041},[3647,10450,5559],{"class":3681},[3647,10452,10453,10455,10458,10460,10462,10464,10466,10468,10470,10472,10474,10476,10478,10480,10482],{"class":3649,"line":3895},[3647,10454,5564],{"class":3677},[3647,10456,10457],{"class":4041}," check_not_empty",[3647,10459,3685],{"class":3657},[3647,10461,5572],{"class":3725},[3647,10463,3595],{"class":3657},[3647,10465,5577],{"class":3725},[3647,10467,4342],{"class":3657},[3647,10469,3719],{"class":3681},[3647,10471,5084],{"class":3657},[3647,10473,5087],{"class":3677},[3647,10475,5588],{"class":3657},[3647,10477,3719],{"class":3681},[3647,10479,5084],{"class":3657},[3647,10481,5087],{"class":3677},[3647,10483,5597],{"class":3657},[3647,10485,10486,10488,10490,10492,10494,10496,10499,10501],{"class":3649,"line":3920},[3647,10487,5607],{"class":3653},[3647,10489,5610],{"class":3657},[3647,10491,5613],{"class":3677},[3647,10493,7024],{"class":3677},[3647,10495,5616],{"class":3677},[3647,10497,10498],{"class":3677}," and",[3647,10500,7024],{"class":3677},[3647,10502,10503],{"class":3657}," value.strip():\n",[3647,10505,10506,10508,10510,10512,10515],{"class":3649,"line":3948},[3647,10507,5623],{"class":3653},[3647,10509,5626],{"class":3681},[3647,10511,3685],{"class":3657},[3647,10513,10514],{"class":4044},"\"Поле не може складатися лише з пробілів\"",[3647,10516,3746],{"class":3657},[3647,10518,10519,10521],{"class":3649,"line":3953},[3647,10520,5638],{"class":3653},[3647,10522,5641],{"class":3657},[4755,10524,10526],{"id":10525},"вправа-3-побудова-універсального-generic-серіалізатора-для-пагінації","Вправа 3: Побудова універсального Generic-серіалізатора для пагінації",[3385,10528,10529,10531,10532,10535],{},[3389,10530,9873],{},": Створити універсальну модель відповіді ",[3401,10533,10534],{},"PaginatedResponse[T]",". Вона повинна огортати список об'єктів будь-якого типу та повертати службові дані про сторінки, кількість елементів тощо.",[3385,10537,10538,10539,10542,10543,4831],{},"У професійній розробці опис схем пагінації для кожної сутності окремо призводить до дублювання коду. Для створення універсальної схеми використовується концепція ",[3389,10540,10541],{},"Generics"," (узагальнених типів) з модуля ",[3401,10544,5011],{},[3637,10546,10549],{"className":3639,"code":10547,"filename":10548,"language":3642,"meta":3643,"style":3643},"# pagination.py\nfrom typing import Generic, TypeVar\nfrom pydantic import BaseModel\n\n# Оголошуємо параметр типу T\nT = TypeVar(\"T\")\n\nclass PaginatedResponse(BaseModel, Generic[T]):\n    items: list[T]\n    total: int\n    page: int\n    size: int\n    pages: int\n\n# Приклад використання у FastAPI:\n# @app.get(\"\u002Fusers\", response_model=PaginatedResponse[UserRead])\n# async def get_users():\n#     ...\n","pagination.py",[3401,10550,10551,10556,10567,10577,10581,10586,10596,10600,10614,10619,10626,10633,10640,10647,10651,10656,10661,10666],{"__ignoreMap":3643},[3647,10552,10553],{"class":3649,"line":3650},[3647,10554,10555],{"class":3697},"# pagination.py\n",[3647,10557,10558,10560,10562,10564],{"class":3649,"line":3667},[3647,10559,3654],{"class":3653},[3647,10561,5028],{"class":3657},[3647,10563,3661],{"class":3653},[3647,10565,10566],{"class":3657}," Generic, TypeVar\n",[3647,10568,10569,10571,10573,10575],{"class":3649,"line":3674},[3647,10570,3654],{"class":3653},[3647,10572,3658],{"class":3657},[3647,10574,3661],{"class":3653},[3647,10576,6751],{"class":3657},[3647,10578,10579],{"class":3649,"line":3694},[3647,10580,3671],{"emptyLinePlaceholder":3670},[3647,10582,10583],{"class":3649,"line":3701},[3647,10584,10585],{"class":3697},"# Оголошуємо параметр типу T\n",[3647,10587,10588,10591,10594],{"class":3649,"line":3707},[3647,10589,10590],{"class":3657},"T = TypeVar(",[3647,10592,10593],{"class":4044},"\"T\"",[3647,10595,3746],{"class":3657},[3647,10597,10598],{"class":3649,"line":3713},[3647,10599,3671],{"emptyLinePlaceholder":3670},[3647,10601,10602,10604,10607,10609,10611],{"class":3649,"line":3749},[3647,10603,3678],{"class":3677},[3647,10605,10606],{"class":3681}," PaginatedResponse",[3647,10608,3685],{"class":3657},[3647,10610,3688],{"class":3681},[3647,10612,10613],{"class":3657},", Generic[T]):\n",[3647,10615,10616],{"class":3649,"line":3886},[3647,10617,10618],{"class":3657},"    items: list[T]\n",[3647,10620,10621,10624],{"class":3649,"line":3895},[3647,10622,10623],{"class":3657},"    total: ",[3647,10625,4345],{"class":3681},[3647,10627,10628,10631],{"class":3649,"line":3920},[3647,10629,10630],{"class":3657},"    page: ",[3647,10632,4345],{"class":3681},[3647,10634,10635,10638],{"class":3649,"line":3948},[3647,10636,10637],{"class":3657},"    size: ",[3647,10639,4345],{"class":3681},[3647,10641,10642,10645],{"class":3649,"line":3953},[3647,10643,10644],{"class":3657},"    pages: ",[3647,10646,4345],{"class":3681},[3647,10648,10649],{"class":3649,"line":3962},[3647,10650,3671],{"emptyLinePlaceholder":3670},[3647,10652,10653],{"class":3649,"line":3983},[3647,10654,10655],{"class":3697},"# Приклад використання у FastAPI:\n",[3647,10657,10658],{"class":3649,"line":4011},[3647,10659,10660],{"class":3697},"# @app.get(\"\u002Fusers\", response_model=PaginatedResponse[UserRead])\n",[3647,10662,10663],{"class":3649,"line":4316},[3647,10664,10665],{"class":3697},"# async def get_users():\n",[3647,10667,10668],{"class":3649,"line":4322},[3647,10669,10670],{"class":3697},"#     ...\n",[3413,10672],{},[3560,10674,10676],{"id":10675},"самостійний-проект-валідація-транзакцій-платіжного-шлюзу","Самостійний проект: Валідація транзакцій платіжного шлюзу",[3385,10678,10679],{},"Розробимо завершений асинхронний веб-додаток, який виконує роль платіжного шлюзу, перевіряючи складні вхідні запити на оплату.",[4755,10681,10683],{"id":10682},"функціонал-проекту","Функціонал проекту:",[3424,10685,10686,10709],{},[3427,10687,10688,5597,10691],{},[3389,10689,10690],{},"Кастомні типи",[5119,10692,10693,10699],{},[3427,10694,10695,10698],{},[3401,10696,10697],{},"CardNumber"," — перевірка номера карти за алгоритмом Луна (Luhn Algorithm) та приведення до чистого рядка цифр.",[3427,10700,10701,10704,10705,10708],{},[3401,10702,10703],{},"ExpiryDate"," — перевірка формату ",[3401,10706,10707],{},"MM\u002FYY"," та підтвердження того, що термін дії картки ще не закінчився.",[3427,10710,10711,4342,10714,10717,10718,3469],{},[3389,10712,10713],{},"Вкладена структура",[3401,10715,10716],{},"PaymentRequest"," містить інформацію про транзакцію, дані картки та вкладену адресу виставлення рахунку (",[3401,10719,10720],{},"BillingAddress",[3385,10722,10723],{},"Створимо та запустимо цей додаток:",[3637,10725,10728],{"className":3639,"code":10726,"filename":10727,"language":3642,"meta":3643,"style":3643},"# payment_gateway.py\nimport re\nfrom datetime import datetime\nfrom typing import Annotated, Literal\nfrom pydantic import BaseModel, Field, AfterValidator, BeforeValidator, ConfigDict, field_validator\nfrom fastapi import FastAPI, status, HTTPException\nfrom fastapi.responses import HTMLResponse\n\napp = FastAPI(title=\"Payment Gateway Validator\")\n\n# --- КАСТОМНІ ВАЛІДАТОРИ ---\n\ndef clean_card_number(v: str) -> str:\n    if not isinstance(v, str):\n        raise ValueError(\"Номер карти має бути рядком\")\n    # Видаляємо пробіли та дефіси\n    return re.sub(r\"[\\s-]\", \"\", v)\n\ndef validate_luhn(v: str) -> str:\n    if not re.match(r\"^\\d{16}$\", v):\n        raise ValueError(\"Номер карти має складатися з 16 цифр\")\n\n    # Алгоритм Луна\n    digits = [int(d) for d in v]\n    checksum = 0\n    for idx, digit in enumerate(reversed(digits)):\n        if idx % 2 == 1:\n            digit *= 2\n            if digit > 9:\n                digit -= 9\n        checksum += digit\n\n    if checksum % 10 != 0:\n        raise ValueError(\"Невалідний номер карти (перевірка контрольної суми Луна провалена)\")\n    return v\n\ndef validate_expiry(v: str) -> str:\n    if not re.match(r\"^(0[1-9]|1[0-2])\\\u002F\\d{2}$\", v):\n        raise ValueError(\"Термін дії має бути у форматі MM\u002FYY\")\n\n    month, year = map(int, v.split(\"\u002F\"))\n    current_year = int(str(datetime.now().year)[2:])\n    current_month = datetime.now().month\n\n    if year \u003C current_year or (year == current_year and month \u003C current_month):\n        raise ValueError(\"Термін дії картки закінчився\")\n    return v\n\n# --- ОГОЛОШЕННЯ ТИПІВ ---\nCardNumber = Annotated[str, BeforeValidator(clean_card_number), AfterValidator(validate_luhn)]\nExpiryDate = Annotated[str, AfterValidator(validate_expiry)]\n\n# --- МОДЕЛІ ДАНИХ ---\n\nclass BillingAddress(BaseModel):\n    country: str = Field(..., min_length=2, max_length=2)  # Код країни ISO (напр. UA)\n    city: str = Field(..., min_length=2, max_length=100)\n    zip_code: str = Field(..., min_length=3, max_length=10)\n\nclass PaymentRequest(BaseModel):\n    amount: float = Field(..., gt=0.0, description=\"Сума транзакції\")\n    currency: Literal[\"UAH\", \"USD\", \"EUR\"]\n    card_number: CardNumber\n    card_expiry: ExpiryDate\n    cvv: str = Field(..., pattern=r\"^\\d{3}$\", description=\"Тризначний код CVV\")\n    billing_address: BillingAddress\n\n    model_config = ConfigDict(\n        json_schema_extra={\n            \"example\": {\n                \"amount\": 250.50,\n                \"currency\": \"UAH\",\n                \"card_number\": \"4321-4321-4321-4321\", # Необхідно вказати реальний номер Луна для тестів\n                \"card_expiry\": \"12\u002F28\",\n                \"cvv\": \"123\",\n                \"billing_address\": {\n                    \"country\": \"UA\",\n                    \"city\": \"Kyiv\",\n                    \"zip_code\": \"01001\"\n                }\n            }\n        }\n    )\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>Payment Test\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; }\n            .form-group { margin-bottom: 1rem; }\n            label { display: block; font-weight: bold; margin-bottom: 0.5rem; }\n            input, select { width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 6px; box-sizing: border-box; }\n            button { background: #10b981; color: white; border: none; padding: 0.75rem; width: 100%; border-radius: 6px; font-weight: bold; cursor: pointer; }\n            button:hover { background: #059669; }\n        \u003C\u002Fstyle>\n    \u003C\u002Fhead>\n    \u003Cbody>\n        \u003Cdiv class=\"card\">\n            \u003Ch2>Оплата карткою\u003C\u002Fh2>\n            \u003Cform action=\"\u002Fpay\" method=\"post\" id=\"payForm\">\n                \u003Cdiv class=\"form-group\">\n                    \u003Clabel>Сума та Валюта\u003C\u002Flabel>\n                    \u003Cdiv style=\"display: flex; gap: 0.5rem;\">\n                        \u003Cinput name=\"amount\" type=\"number\" step=\"0.01\" value=\"100.00\" required>\n                        \u003Cselect name=\"currency\">\n                            \u003Coption value=\"UAH\">UAH\u003C\u002Foption>\n                            \u003Coption value=\"USD\">USD\u003C\u002Foption>\n                            \u003Coption value=\"EUR\">EUR\u003C\u002Foption>\n                        \u003C\u002Fselect>\n                    \u003C\u002Fdiv>\n                \u003C\u002Fdiv>\n                \u003Cdiv class=\"form-group\">\n                    \u003Clabel>Номер карти\u003C\u002Flabel>\n                    \u003Cinput name=\"card_number\" type=\"text\" placeholder=\"4321-4321-4321-4321\" required>\n                \u003C\u002Fdiv>\n                \u003Cdiv class=\"form-group\">\n                    \u003Clabel>Термін дії та CVV\u003C\u002Flabel>\n                    \u003Cdiv style=\"display: flex; gap: 0.5rem;\">\n                        \u003Cinput name=\"card_expiry\" type=\"text\" placeholder=\"MM\u002FYY\" required>\n                        \u003Cinput name=\"cvv\" type=\"password\" maxlength=\"3\" placeholder=\"CVV\" required>\n                    \u003C\u002Fdiv>\n                \u003C\u002Fdiv>\n                \u003Cdiv class=\"form-group\">\n                    \u003Clabel>Країна (ISO) та Місто\u003C\u002Flabel>\n                    \u003Cdiv style=\"display: flex; gap: 0.5rem;\">\n                        \u003Cinput name=\"country\" type=\"text\" placeholder=\"UA\" value=\"UA\" required>\n                        \u003Cinput name=\"city\" type=\"text\" placeholder=\"Kyiv\" required>\n                    \u003C\u002Fdiv>\n                \u003C\u002Fdiv>\n                \u003Cdiv class=\"form-group\">\n                    \u003Clabel>Поштовий індекс\u003C\u002Flabel>\n                    \u003Cinput name=\"zip_code\" type=\"text\" placeholder=\"01001\" required>\n                \u003C\u002Fdiv>\n                \u003Cbutton type=\"submit\">Сплатити\u003C\u002Fbutton>\n            \u003C\u002Fform>\n        \u003C\u002Fdiv>\n        \u003Cscript>\n            document.getElementById('payForm').addEventListener('submit', async (e) => {\n                e.preventDefault();\n                const formData = new FormData(e.target);\n                const payload = {\n                    amount: parseFloat(formData.get('amount')),\n                    currency: formData.get('currency'),\n                    card_number: formData.get('card_number'),\n                    card_expiry: formData.get('card_expiry'),\n                    cvv: formData.get('cvv'),\n                    billing_address: {\n                        country: formData.get('country'),\n                        city: formData.get('city'),\n                        zip_code: formData.get('zip_code')\n                    }\n                };\n\n                const res = await fetch('\u002Fpay', {\n                    method: 'POST',\n                    headers: { 'Content-Type': 'application\u002Fjson' },\n                    body: JSON.stringify(payload)\n                });\n                const data = await res.json();\n                alert(JSON.stringify(data, null, 2));\n            });\n        \u003C\u002Fscript>\n    \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n    \"\"\"\n    return HTMLResponse(content=content)\n\n@app.post(\"\u002Fpay\", status_code=status.HTTP_200_OK)\nasync def process_payment(payload: PaymentRequest):\n    # Оскільки вся валідація пройшла успішно на рівні Pydantic,\n    # ми впевнені у валідності номерів карт, термінів та форматів даних.\n    return {\n        \"transaction_id\": \"tx_9876543210\",\n        \"status\": \"success\",\n        \"processed_at\": datetime.now().isoformat(),\n        \"amount\": payload.amount,\n        \"currency\": payload.currency\n    }\n","payment_gateway.py",[3401,10729,10730,10735,10741,10751,10762,10773,10784,10794,10798,10811,10815,10820,10824,10845,10859,10872,10877,10903,10907,10928,10951,10964,10968,10973,10994,11002,11022,11037,11045,11058,11066,11071,11075,11091,11104,11110,11114,11135,11183,11196,11200,11219,11238,11243,11247,11265,11278,11284,11288,11293,11303,11313,11317,11322,11326,11340,11370,11396,11422,11427,11441,11468,11489,11495,11501,11536,11542,11547,11552,11559,11566,11579,11591,11607,11620,11633,11641,11654,11667,11678,11684,11689,11694,11699,11704,11710,11715,11730,11742,11749,11754,11759,11764,11770,11775,11781,11787,11793,11799,11805,11811,11817,11822,11827,11832,11837,11843,11849,11855,11861,11867,11873,11879,11885,11891,11897,11903,11909,11915,11920,11926,11932,11937,11942,11948,11953,11959,11965,11970,11975,11980,11986,11991,11997,12003,12008,12013,12018,12024,12030,12035,12041,12046,12051,12057,12063,12069,12075,12081,12087,12093,12099,12105,12111,12117,12123,12129,12135,12141,12147,12152,12158,12164,12170,12176,12182,12188,12194,12200,12206,12211,12216,12221,12232,12237,12253,12271,12277,12283,12290,12303,12315,12324,12333,12342],{"__ignoreMap":3643},[3647,10731,10732],{"class":3649,"line":3650},[3647,10733,10734],{"class":3697},"# payment_gateway.py\n",[3647,10736,10737,10739],{"class":3649,"line":3667},[3647,10738,3661],{"class":3653},[3647,10740,6963],{"class":3657},[3647,10742,10743,10745,10747,10749],{"class":3649,"line":3674},[3647,10744,3654],{"class":3653},[3647,10746,4164],{"class":3657},[3647,10748,3661],{"class":3653},[3647,10750,4169],{"class":3657},[3647,10752,10753,10755,10757,10759],{"class":3649,"line":3694},[3647,10754,3654],{"class":3653},[3647,10756,5028],{"class":3657},[3647,10758,3661],{"class":3653},[3647,10760,10761],{"class":3657}," Annotated, Literal\n",[3647,10763,10764,10766,10768,10770],{"class":3649,"line":3701},[3647,10765,3654],{"class":3653},[3647,10767,3658],{"class":3657},[3647,10769,3661],{"class":3653},[3647,10771,10772],{"class":3657}," BaseModel, Field, AfterValidator, BeforeValidator, ConfigDict, field_validator\n",[3647,10774,10775,10777,10779,10781],{"class":3649,"line":3707},[3647,10776,3654],{"class":3653},[3647,10778,4187],{"class":3657},[3647,10780,3661],{"class":3653},[3647,10782,10783],{"class":3657}," FastAPI, status, HTTPException\n",[3647,10785,10786,10788,10790,10792],{"class":3649,"line":3713},[3647,10787,3654],{"class":3653},[3647,10789,8920],{"class":3657},[3647,10791,3661],{"class":3653},[3647,10793,8925],{"class":3657},[3647,10795,10796],{"class":3649,"line":3749},[3647,10797,3671],{"emptyLinePlaceholder":3670},[3647,10799,10800,10802,10804,10806,10809],{"class":3649,"line":3886},[3647,10801,4201],{"class":3657},[3647,10803,4204],{"class":3725},[3647,10805,3729],{"class":3657},[3647,10807,10808],{"class":4044},"\"Payment Gateway Validator\"",[3647,10810,3746],{"class":3657},[3647,10812,10813],{"class":3649,"line":3895},[3647,10814,3671],{"emptyLinePlaceholder":3670},[3647,10816,10817],{"class":3649,"line":3920},[3647,10818,10819],{"class":3697},"# --- КАСТОМНІ ВАЛІДАТОРИ ---\n",[3647,10821,10822],{"class":3649,"line":3948},[3647,10823,3671],{"emptyLinePlaceholder":3670},[3647,10825,10826,10828,10831,10833,10835,10837,10839,10841,10843],{"class":3649,"line":3953},[3647,10827,6994],{"class":3677},[3647,10829,10830],{"class":4041}," clean_card_number",[3647,10832,3685],{"class":3657},[3647,10834,7002],{"class":3725},[3647,10836,4342],{"class":3657},[3647,10838,3719],{"class":3681},[3647,10840,5588],{"class":3657},[3647,10842,3719],{"class":3681},[3647,10844,5597],{"class":3657},[3647,10846,10847,10849,10851,10853,10855,10857],{"class":3649,"line":3962},[3647,10848,5982],{"class":3653},[3647,10850,7024],{"class":3677},[3647,10852,7027],{"class":4041},[3647,10854,7030],{"class":3657},[3647,10856,3719],{"class":3681},[3647,10858,3691],{"class":3657},[3647,10860,10861,10863,10865,10867,10870],{"class":3649,"line":3983},[3647,10862,5996],{"class":3653},[3647,10864,5626],{"class":3681},[3647,10866,3685],{"class":3657},[3647,10868,10869],{"class":4044},"\"Номер карти має бути рядком\"",[3647,10871,3746],{"class":3657},[3647,10873,10874],{"class":3649,"line":4011},[3647,10875,10876],{"class":3697},"    # Видаляємо пробіли та дефіси\n",[3647,10878,10879,10881,10884,10886,10888,10890,10893,10895,10897,10899,10901],{"class":3649,"line":4316},[3647,10880,4647],{"class":3653},[3647,10882,10883],{"class":3657}," re.sub(",[3647,10885,7055],{"class":3677},[3647,10887,5944],{"class":7058},[3647,10889,7062],{"class":7061},[3647,10891,10892],{"class":7058},"\\s-",[3647,10894,7068],{"class":7061},[3647,10896,5944],{"class":7058},[3647,10898,3595],{"class":3657},[3647,10900,7075],{"class":4044},[3647,10902,7078],{"class":3657},[3647,10904,10905],{"class":3649,"line":4322},[3647,10906,3671],{"emptyLinePlaceholder":3670},[3647,10908,10909,10911,10914,10916,10918,10920,10922,10924,10926],{"class":3649,"line":4336},[3647,10910,6994],{"class":3677},[3647,10912,10913],{"class":4041}," validate_luhn",[3647,10915,3685],{"class":3657},[3647,10917,7002],{"class":3725},[3647,10919,4342],{"class":3657},[3647,10921,3719],{"class":3681},[3647,10923,5588],{"class":3657},[3647,10925,3719],{"class":3681},[3647,10927,5597],{"class":3657},[3647,10929,10930,10932,10934,10937,10939,10942,10945,10948],{"class":3649,"line":4348},[3647,10931,5982],{"class":3653},[3647,10933,7024],{"class":3677},[3647,10935,10936],{"class":3657}," re.match(",[3647,10938,7055],{"class":3677},[3647,10940,10941],{"class":7058},"\"^\\d",[3647,10943,10944],{"class":7227},"{16}",[3647,10946,10947],{"class":7058},"$\"",[3647,10949,10950],{"class":3657},", v):\n",[3647,10952,10953,10955,10957,10959,10962],{"class":3649,"line":4362},[3647,10954,5996],{"class":3653},[3647,10956,5626],{"class":3681},[3647,10958,3685],{"class":3657},[3647,10960,10961],{"class":4044},"\"Номер карти має складатися з 16 цифр\"",[3647,10963,3746],{"class":3657},[3647,10965,10966],{"class":3649,"line":4368},[3647,10967,3671],{"emptyLinePlaceholder":3670},[3647,10969,10970],{"class":3649,"line":4373},[3647,10971,10972],{"class":3697},"    # Алгоритм Луна\n",[3647,10974,10975,10978,10980,10983,10986,10989,10991],{"class":3649,"line":4379},[3647,10976,10977],{"class":3657},"    digits = [",[3647,10979,5970],{"class":3681},[3647,10981,10982],{"class":3657},"(d) ",[3647,10984,10985],{"class":3653},"for",[3647,10987,10988],{"class":3657}," d ",[3647,10990,6061],{"class":3653},[3647,10992,10993],{"class":3657}," v]\n",[3647,10995,10996,10999],{"class":3649,"line":4385},[3647,10997,10998],{"class":3657},"    checksum = ",[3647,11000,11001],{"class":3732},"0\n",[3647,11003,11004,11006,11009,11011,11014,11016,11019],{"class":3649,"line":4401},[3647,11005,6055],{"class":3653},[3647,11007,11008],{"class":3657}," idx, digit ",[3647,11010,6061],{"class":3653},[3647,11012,11013],{"class":4041}," enumerate",[3647,11015,3685],{"class":3657},[3647,11017,11018],{"class":4041},"reversed",[3647,11020,11021],{"class":3657},"(digits)):\n",[3647,11023,11024,11026,11029,11031,11033,11035],{"class":3649,"line":4406},[3647,11025,5607],{"class":3653},[3647,11027,11028],{"class":3657}," idx % ",[3647,11030,7328],{"class":3732},[3647,11032,9498],{"class":3657},[3647,11034,5713],{"class":3732},[3647,11036,5597],{"class":3657},[3647,11038,11039,11042],{"class":3649,"line":4412},[3647,11040,11041],{"class":3657},"            digit *= ",[3647,11043,11044],{"class":3732},"2\n",[3647,11046,11047,11050,11053,11056],{"class":3649,"line":4424},[3647,11048,11049],{"class":3653},"            if",[3647,11051,11052],{"class":3657}," digit > ",[3647,11054,11055],{"class":3732},"9",[3647,11057,5597],{"class":3657},[3647,11059,11060,11063],{"class":3649,"line":4433},[3647,11061,11062],{"class":3657},"                digit -= ",[3647,11064,11065],{"class":3732},"9\n",[3647,11067,11068],{"class":3649,"line":4438},[3647,11069,11070],{"class":3657},"        checksum += digit\n",[3647,11072,11073],{"class":3649,"line":4463},[3647,11074,3671],{"emptyLinePlaceholder":3670},[3647,11076,11077,11079,11082,11084,11087,11089],{"class":3649,"line":4483},[3647,11078,5982],{"class":3653},[3647,11080,11081],{"class":3657}," checksum % ",[3647,11083,7106],{"class":3732},[3647,11085,11086],{"class":3657}," != ",[3647,11088,6418],{"class":3732},[3647,11090,5597],{"class":3657},[3647,11092,11093,11095,11097,11099,11102],{"class":3649,"line":4492},[3647,11094,5996],{"class":3653},[3647,11096,5626],{"class":3681},[3647,11098,3685],{"class":3657},[3647,11100,11101],{"class":4044},"\"Невалідний номер карти (перевірка контрольної суми Луна провалена)\"",[3647,11103,3746],{"class":3657},[3647,11105,11106,11108],{"class":3649,"line":4497},[3647,11107,4647],{"class":3653},[3647,11109,7260],{"class":3657},[3647,11111,11112],{"class":3649,"line":4503},[3647,11113,3671],{"emptyLinePlaceholder":3670},[3647,11115,11116,11118,11121,11123,11125,11127,11129,11131,11133],{"class":3649,"line":4527},[3647,11117,6994],{"class":3677},[3647,11119,11120],{"class":4041}," validate_expiry",[3647,11122,3685],{"class":3657},[3647,11124,7002],{"class":3725},[3647,11126,4342],{"class":3657},[3647,11128,3719],{"class":3681},[3647,11130,5588],{"class":3657},[3647,11132,3719],{"class":3681},[3647,11134,5597],{"class":3657},[3647,11136,11137,11139,11141,11143,11145,11147,11149,11151,11153,11156,11158,11160,11162,11164,11167,11170,11173,11176,11179,11181],{"class":3649,"line":4532},[3647,11138,5982],{"class":3653},[3647,11140,7024],{"class":3677},[3647,11142,10936],{"class":3657},[3647,11144,7055],{"class":3677},[3647,11146,7218],{"class":7058},[3647,11148,3685],{"class":7061},[3647,11150,6418],{"class":7058},[3647,11152,7062],{"class":7061},[3647,11154,11155],{"class":7058},"1-9",[3647,11157,7068],{"class":7061},[3647,11159,5056],{"class":3657},[3647,11161,5713],{"class":7058},[3647,11163,7062],{"class":7061},[3647,11165,11166],{"class":7058},"0-2",[3647,11168,11169],{"class":7061},"])",[3647,11171,11172],{"class":4788},"\\\u002F",[3647,11174,11175],{"class":7058},"\\d",[3647,11177,11178],{"class":7227},"{2}",[3647,11180,10947],{"class":7058},[3647,11182,10950],{"class":3657},[3647,11184,11185,11187,11189,11191,11194],{"class":3649,"line":4538},[3647,11186,5996],{"class":3653},[3647,11188,5626],{"class":3681},[3647,11190,3685],{"class":3657},[3647,11192,11193],{"class":4044},"\"Термін дії має бути у форматі MM\u002FYY\"",[3647,11195,3746],{"class":3657},[3647,11197,11198],{"class":3649,"line":4544},[3647,11199,3671],{"emptyLinePlaceholder":3670},[3647,11201,11202,11205,11208,11210,11212,11215,11217],{"class":3649,"line":4553},[3647,11203,11204],{"class":3657},"    month, year = ",[3647,11206,11207],{"class":4041},"map",[3647,11209,3685],{"class":3657},[3647,11211,5970],{"class":3681},[3647,11213,11214],{"class":3657},", v.split(",[3647,11216,8947],{"class":4044},[3647,11218,9612],{"class":3657},[3647,11220,11221,11224,11226,11228,11230,11233,11235],{"class":3649,"line":4562},[3647,11222,11223],{"class":3657},"    current_year = ",[3647,11225,5970],{"class":3681},[3647,11227,3685],{"class":3657},[3647,11229,3719],{"class":3681},[3647,11231,11232],{"class":3657},"(datetime.now().year)[",[3647,11234,7328],{"class":3732},[3647,11236,11237],{"class":3657},":])\n",[3647,11239,11240],{"class":3649,"line":4571},[3647,11241,11242],{"class":3657},"    current_month = datetime.now().month\n",[3647,11244,11245],{"class":3649,"line":4580},[3647,11246,3671],{"emptyLinePlaceholder":3670},[3647,11248,11249,11251,11254,11257,11260,11262],{"class":3649,"line":4593},[3647,11250,5982],{"class":3653},[3647,11252,11253],{"class":3657}," year \u003C current_year ",[3647,11255,11256],{"class":3677},"or",[3647,11258,11259],{"class":3657}," (year == current_year ",[3647,11261,7097],{"class":3677},[3647,11263,11264],{"class":3657}," month \u003C current_month):\n",[3647,11266,11267,11269,11271,11273,11276],{"class":3649,"line":4602},[3647,11268,5996],{"class":3653},[3647,11270,5626],{"class":3681},[3647,11272,3685],{"class":3657},[3647,11274,11275],{"class":4044},"\"Термін дії картки закінчився\"",[3647,11277,3746],{"class":3657},[3647,11279,11280,11282],{"class":3649,"line":4608},[3647,11281,4647],{"class":3653},[3647,11283,7260],{"class":3657},[3647,11285,11286],{"class":3649,"line":4613},[3647,11287,3671],{"emptyLinePlaceholder":3670},[3647,11289,11290],{"class":3649,"line":4619},[3647,11291,11292],{"class":3697},"# --- ОГОЛОШЕННЯ ТИПІВ ---\n",[3647,11294,11295,11298,11300],{"class":3649,"line":4627},[3647,11296,11297],{"class":3657},"CardNumber = Annotated[",[3647,11299,3719],{"class":3681},[3647,11301,11302],{"class":3657},", BeforeValidator(clean_card_number), AfterValidator(validate_luhn)]\n",[3647,11304,11305,11308,11310],{"class":3649,"line":4632},[3647,11306,11307],{"class":3657},"ExpiryDate = Annotated[",[3647,11309,3719],{"class":3681},[3647,11311,11312],{"class":3657},", AfterValidator(validate_expiry)]\n",[3647,11314,11315],{"class":3649,"line":4638},[3647,11316,3671],{"emptyLinePlaceholder":3670},[3647,11318,11319],{"class":3649,"line":4644},[3647,11320,11321],{"class":3697},"# --- МОДЕЛІ ДАНИХ ---\n",[3647,11323,11324],{"class":3649,"line":9312},[3647,11325,3671],{"emptyLinePlaceholder":3670},[3647,11327,11329,11331,11334,11336,11338],{"class":3649,"line":11328},55,[3647,11330,3678],{"class":3677},[3647,11332,11333],{"class":3681}," BillingAddress",[3647,11335,3685],{"class":3657},[3647,11337,3688],{"class":3681},[3647,11339,3691],{"class":3657},[3647,11341,11343,11346,11348,11350,11352,11354,11356,11358,11360,11362,11364,11367],{"class":3649,"line":11342},56,[3647,11344,11345],{"class":3657},"    country: ",[3647,11347,3719],{"class":3681},[3647,11349,4246],{"class":3657},[3647,11351,3726],{"class":3725},[3647,11353,3729],{"class":3657},[3647,11355,7328],{"class":3732},[3647,11357,3595],{"class":3657},[3647,11359,3738],{"class":3725},[3647,11361,3729],{"class":3657},[3647,11363,7328],{"class":3732},[3647,11365,11366],{"class":3657},")  ",[3647,11368,11369],{"class":3697},"# Код країни ISO (напр. UA)\n",[3647,11371,11373,11376,11378,11380,11382,11384,11386,11388,11390,11392,11394],{"class":3649,"line":11372},57,[3647,11374,11375],{"class":3657},"    city: ",[3647,11377,3719],{"class":3681},[3647,11379,4246],{"class":3657},[3647,11381,3726],{"class":3725},[3647,11383,3729],{"class":3657},[3647,11385,7328],{"class":3732},[3647,11387,3595],{"class":3657},[3647,11389,3738],{"class":3725},[3647,11391,3729],{"class":3657},[3647,11393,3743],{"class":3732},[3647,11395,3746],{"class":3657},[3647,11397,11399,11402,11404,11406,11408,11410,11412,11414,11416,11418,11420],{"class":3649,"line":11398},58,[3647,11400,11401],{"class":3657},"    zip_code: ",[3647,11403,3719],{"class":3681},[3647,11405,4246],{"class":3657},[3647,11407,3726],{"class":3725},[3647,11409,3729],{"class":3657},[3647,11411,3763],{"class":3732},[3647,11413,3595],{"class":3657},[3647,11415,3738],{"class":3725},[3647,11417,3729],{"class":3657},[3647,11419,7106],{"class":3732},[3647,11421,3746],{"class":3657},[3647,11423,11425],{"class":3649,"line":11424},59,[3647,11426,3671],{"emptyLinePlaceholder":3670},[3647,11428,11430,11432,11435,11437,11439],{"class":3649,"line":11429},60,[3647,11431,3678],{"class":3677},[3647,11433,11434],{"class":3681}," PaymentRequest",[3647,11436,3685],{"class":3657},[3647,11438,3688],{"class":3681},[3647,11440,3691],{"class":3657},[3647,11442,11444,11447,11449,11451,11453,11455,11457,11459,11461,11463,11466],{"class":3649,"line":11443},61,[3647,11445,11446],{"class":3657},"    amount: ",[3647,11448,6437],{"class":3681},[3647,11450,4246],{"class":3657},[3647,11452,6413],{"class":3725},[3647,11454,3729],{"class":3657},[3647,11456,6446],{"class":3732},[3647,11458,3595],{"class":3657},[3647,11460,4302],{"class":3725},[3647,11462,3729],{"class":3657},[3647,11464,11465],{"class":4044},"\"Сума транзакції\"",[3647,11467,3746],{"class":3657},[3647,11469,11471,11474,11477,11479,11482,11484,11487],{"class":3649,"line":11470},62,[3647,11472,11473],{"class":3657},"    currency: Literal[",[3647,11475,11476],{"class":4044},"\"UAH\"",[3647,11478,3595],{"class":3657},[3647,11480,11481],{"class":4044},"\"USD\"",[3647,11483,3595],{"class":3657},[3647,11485,11486],{"class":4044},"\"EUR\"",[3647,11488,3834],{"class":3657},[3647,11490,11492],{"class":3649,"line":11491},63,[3647,11493,11494],{"class":3657},"    card_number: CardNumber\n",[3647,11496,11498],{"class":3649,"line":11497},64,[3647,11499,11500],{"class":3657},"    card_expiry: ExpiryDate\n",[3647,11502,11504,11507,11509,11511,11514,11516,11518,11520,11523,11525,11527,11529,11531,11534],{"class":3649,"line":11503},65,[3647,11505,11506],{"class":3657},"    cvv: ",[3647,11508,3719],{"class":3681},[3647,11510,4246],{"class":3657},[3647,11512,11513],{"class":3725},"pattern",[3647,11515,3729],{"class":3657},[3647,11517,7055],{"class":3677},[3647,11519,10941],{"class":7058},[3647,11521,11522],{"class":7227},"{3}",[3647,11524,10947],{"class":7058},[3647,11526,3595],{"class":3657},[3647,11528,4302],{"class":3725},[3647,11530,3729],{"class":3657},[3647,11532,11533],{"class":4044},"\"Тризначний код CVV\"",[3647,11535,3746],{"class":3657},[3647,11537,11539],{"class":3649,"line":11538},66,[3647,11540,11541],{"class":3657},"    billing_address: BillingAddress\n",[3647,11543,11545],{"class":3649,"line":11544},67,[3647,11546,3671],{"emptyLinePlaceholder":3670},[3647,11548,11550],{"class":3649,"line":11549},68,[3647,11551,7674],{"class":3657},[3647,11553,11555,11557],{"class":3649,"line":11554},69,[3647,11556,7467],{"class":3725},[3647,11558,7681],{"class":3657},[3647,11560,11562,11564],{"class":3649,"line":11561},70,[3647,11563,7686],{"class":4044},[3647,11565,7689],{"class":3657},[3647,11567,11569,11572,11574,11577],{"class":3649,"line":11568},71,[3647,11570,11571],{"class":4044},"                \"amount\"",[3647,11573,4342],{"class":3657},[3647,11575,11576],{"class":3732},"250.50",[3647,11578,4590],{"class":3657},[3647,11580,11582,11585,11587,11589],{"class":3649,"line":11581},72,[3647,11583,11584],{"class":4044},"                \"currency\"",[3647,11586,4342],{"class":3657},[3647,11588,11476],{"class":4044},[3647,11590,4590],{"class":3657},[3647,11592,11594,11597,11599,11602,11604],{"class":3649,"line":11593},73,[3647,11595,11596],{"class":4044},"                \"card_number\"",[3647,11598,4342],{"class":3657},[3647,11600,11601],{"class":4044},"\"4321-4321-4321-4321\"",[3647,11603,3595],{"class":3657},[3647,11605,11606],{"class":3697},"# Необхідно вказати реальний номер Луна для тестів\n",[3647,11608,11610,11613,11615,11618],{"class":3649,"line":11609},74,[3647,11611,11612],{"class":4044},"                \"card_expiry\"",[3647,11614,4342],{"class":3657},[3647,11616,11617],{"class":4044},"\"12\u002F28\"",[3647,11619,4590],{"class":3657},[3647,11621,11623,11626,11628,11631],{"class":3649,"line":11622},75,[3647,11624,11625],{"class":4044},"                \"cvv\"",[3647,11627,4342],{"class":3657},[3647,11629,11630],{"class":4044},"\"123\"",[3647,11632,4590],{"class":3657},[3647,11634,11636,11639],{"class":3649,"line":11635},76,[3647,11637,11638],{"class":4044},"                \"billing_address\"",[3647,11640,7689],{"class":3657},[3647,11642,11644,11647,11649,11652],{"class":3649,"line":11643},77,[3647,11645,11646],{"class":4044},"                    \"country\"",[3647,11648,4342],{"class":3657},[3647,11650,11651],{"class":4044},"\"UA\"",[3647,11653,4590],{"class":3657},[3647,11655,11657,11660,11662,11665],{"class":3649,"line":11656},78,[3647,11658,11659],{"class":4044},"                    \"city\"",[3647,11661,4342],{"class":3657},[3647,11663,11664],{"class":4044},"\"Kyiv\"",[3647,11666,4590],{"class":3657},[3647,11668,11670,11673,11675],{"class":3649,"line":11669},79,[3647,11671,11672],{"class":4044},"                    \"zip_code\"",[3647,11674,4342],{"class":3657},[3647,11676,11677],{"class":4044},"\"01001\"\n",[3647,11679,11681],{"class":3649,"line":11680},80,[3647,11682,11683],{"class":3657},"                }\n",[3647,11685,11687],{"class":3649,"line":11686},81,[3647,11688,7728],{"class":3657},[3647,11690,11692],{"class":3649,"line":11691},82,[3647,11693,7733],{"class":3657},[3647,11695,11697],{"class":3649,"line":11696},83,[3647,11698,7487],{"class":3657},[3647,11700,11702],{"class":3649,"line":11701},84,[3647,11703,3671],{"emptyLinePlaceholder":3670},[3647,11705,11707],{"class":3649,"line":11706},85,[3647,11708,11709],{"class":3697},"# --- МАРШРУТИ ---\n",[3647,11711,11713],{"class":3649,"line":11712},86,[3647,11714,3671],{"emptyLinePlaceholder":3670},[3647,11716,11718,11720,11722,11724,11726,11728],{"class":3649,"line":11717},87,[3647,11719,8942],{"class":4041},[3647,11721,3685],{"class":3657},[3647,11723,8947],{"class":4044},[3647,11725,3595],{"class":3657},[3647,11727,8952],{"class":3725},[3647,11729,8955],{"class":3657},[3647,11731,11733,11735,11737,11740],{"class":3649,"line":11732},88,[3647,11734,4466],{"class":3677},[3647,11736,4469],{"class":3677},[3647,11738,11739],{"class":4041}," index",[3647,11741,8967],{"class":3657},[3647,11743,11745,11747],{"class":3649,"line":11744},89,[3647,11746,8977],{"class":3657},[3647,11748,8980],{"class":4044},[3647,11750,11752],{"class":3649,"line":11751},90,[3647,11753,8985],{"class":4044},[3647,11755,11757],{"class":3649,"line":11756},91,[3647,11758,8990],{"class":4044},[3647,11760,11762],{"class":3649,"line":11761},92,[3647,11763,8995],{"class":4044},[3647,11765,11767],{"class":3649,"line":11766},93,[3647,11768,11769],{"class":4044},"        \u003Ctitle>Payment Test\u003C\u002Ftitle>\n",[3647,11771,11773],{"class":3649,"line":11772},94,[3647,11774,9005],{"class":4044},[3647,11776,11778],{"class":3649,"line":11777},95,[3647,11779,11780],{"class":4044},"            body { font-family: sans-serif; background-color: #f3f4f6; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }\n",[3647,11782,11784],{"class":3649,"line":11783},96,[3647,11785,11786],{"class":4044},"            .card { background: white; padding: 2rem; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); width: 400px; }\n",[3647,11788,11790],{"class":3649,"line":11789},97,[3647,11791,11792],{"class":4044},"            .form-group { margin-bottom: 1rem; }\n",[3647,11794,11796],{"class":3649,"line":11795},98,[3647,11797,11798],{"class":4044},"            label { display: block; font-weight: bold; margin-bottom: 0.5rem; }\n",[3647,11800,11802],{"class":3649,"line":11801},99,[3647,11803,11804],{"class":4044},"            input, select { width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 6px; box-sizing: border-box; }\n",[3647,11806,11808],{"class":3649,"line":11807},100,[3647,11809,11810],{"class":4044},"            button { background: #10b981; color: white; border: none; padding: 0.75rem; width: 100%; border-radius: 6px; font-weight: bold; cursor: pointer; }\n",[3647,11812,11814],{"class":3649,"line":11813},101,[3647,11815,11816],{"class":4044},"            button:hover { background: #059669; }\n",[3647,11818,11820],{"class":3649,"line":11819},102,[3647,11821,9035],{"class":4044},[3647,11823,11825],{"class":3649,"line":11824},103,[3647,11826,9040],{"class":4044},[3647,11828,11830],{"class":3649,"line":11829},104,[3647,11831,9045],{"class":4044},[3647,11833,11835],{"class":3649,"line":11834},105,[3647,11836,9050],{"class":4044},[3647,11838,11840],{"class":3649,"line":11839},106,[3647,11841,11842],{"class":4044},"            \u003Ch2>Оплата карткою\u003C\u002Fh2>\n",[3647,11844,11846],{"class":3649,"line":11845},107,[3647,11847,11848],{"class":4044},"            \u003Cform action=\"\u002Fpay\" method=\"post\" id=\"payForm\">\n",[3647,11850,11852],{"class":3649,"line":11851},108,[3647,11853,11854],{"class":4044},"                \u003Cdiv class=\"form-group\">\n",[3647,11856,11858],{"class":3649,"line":11857},109,[3647,11859,11860],{"class":4044},"                    \u003Clabel>Сума та Валюта\u003C\u002Flabel>\n",[3647,11862,11864],{"class":3649,"line":11863},110,[3647,11865,11866],{"class":4044},"                    \u003Cdiv style=\"display: flex; gap: 0.5rem;\">\n",[3647,11868,11870],{"class":3649,"line":11869},111,[3647,11871,11872],{"class":4044},"                        \u003Cinput name=\"amount\" type=\"number\" step=\"0.01\" value=\"100.00\" required>\n",[3647,11874,11876],{"class":3649,"line":11875},112,[3647,11877,11878],{"class":4044},"                        \u003Cselect name=\"currency\">\n",[3647,11880,11882],{"class":3649,"line":11881},113,[3647,11883,11884],{"class":4044},"                            \u003Coption value=\"UAH\">UAH\u003C\u002Foption>\n",[3647,11886,11888],{"class":3649,"line":11887},114,[3647,11889,11890],{"class":4044},"                            \u003Coption value=\"USD\">USD\u003C\u002Foption>\n",[3647,11892,11894],{"class":3649,"line":11893},115,[3647,11895,11896],{"class":4044},"                            \u003Coption value=\"EUR\">EUR\u003C\u002Foption>\n",[3647,11898,11900],{"class":3649,"line":11899},116,[3647,11901,11902],{"class":4044},"                        \u003C\u002Fselect>\n",[3647,11904,11906],{"class":3649,"line":11905},117,[3647,11907,11908],{"class":4044},"                    \u003C\u002Fdiv>\n",[3647,11910,11912],{"class":3649,"line":11911},118,[3647,11913,11914],{"class":4044},"                \u003C\u002Fdiv>\n",[3647,11916,11918],{"class":3649,"line":11917},119,[3647,11919,11854],{"class":4044},[3647,11921,11923],{"class":3649,"line":11922},120,[3647,11924,11925],{"class":4044},"                    \u003Clabel>Номер карти\u003C\u002Flabel>\n",[3647,11927,11929],{"class":3649,"line":11928},121,[3647,11930,11931],{"class":4044},"                    \u003Cinput name=\"card_number\" type=\"text\" placeholder=\"4321-4321-4321-4321\" required>\n",[3647,11933,11935],{"class":3649,"line":11934},122,[3647,11936,11914],{"class":4044},[3647,11938,11940],{"class":3649,"line":11939},123,[3647,11941,11854],{"class":4044},[3647,11943,11945],{"class":3649,"line":11944},124,[3647,11946,11947],{"class":4044},"                    \u003Clabel>Термін дії та CVV\u003C\u002Flabel>\n",[3647,11949,11951],{"class":3649,"line":11950},125,[3647,11952,11866],{"class":4044},[3647,11954,11956],{"class":3649,"line":11955},126,[3647,11957,11958],{"class":4044},"                        \u003Cinput name=\"card_expiry\" type=\"text\" placeholder=\"MM\u002FYY\" required>\n",[3647,11960,11962],{"class":3649,"line":11961},127,[3647,11963,11964],{"class":4044},"                        \u003Cinput name=\"cvv\" type=\"password\" maxlength=\"3\" placeholder=\"CVV\" required>\n",[3647,11966,11968],{"class":3649,"line":11967},128,[3647,11969,11908],{"class":4044},[3647,11971,11973],{"class":3649,"line":11972},129,[3647,11974,11914],{"class":4044},[3647,11976,11978],{"class":3649,"line":11977},130,[3647,11979,11854],{"class":4044},[3647,11981,11983],{"class":3649,"line":11982},131,[3647,11984,11985],{"class":4044},"                    \u003Clabel>Країна (ISO) та Місто\u003C\u002Flabel>\n",[3647,11987,11989],{"class":3649,"line":11988},132,[3647,11990,11866],{"class":4044},[3647,11992,11994],{"class":3649,"line":11993},133,[3647,11995,11996],{"class":4044},"                        \u003Cinput name=\"country\" type=\"text\" placeholder=\"UA\" value=\"UA\" required>\n",[3647,11998,12000],{"class":3649,"line":11999},134,[3647,12001,12002],{"class":4044},"                        \u003Cinput name=\"city\" type=\"text\" placeholder=\"Kyiv\" required>\n",[3647,12004,12006],{"class":3649,"line":12005},135,[3647,12007,11908],{"class":4044},[3647,12009,12011],{"class":3649,"line":12010},136,[3647,12012,11914],{"class":4044},[3647,12014,12016],{"class":3649,"line":12015},137,[3647,12017,11854],{"class":4044},[3647,12019,12021],{"class":3649,"line":12020},138,[3647,12022,12023],{"class":4044},"                    \u003Clabel>Поштовий індекс\u003C\u002Flabel>\n",[3647,12025,12027],{"class":3649,"line":12026},139,[3647,12028,12029],{"class":4044},"                    \u003Cinput name=\"zip_code\" type=\"text\" placeholder=\"01001\" required>\n",[3647,12031,12033],{"class":3649,"line":12032},140,[3647,12034,11914],{"class":4044},[3647,12036,12038],{"class":3649,"line":12037},141,[3647,12039,12040],{"class":4044},"                \u003Cbutton type=\"submit\">Сплатити\u003C\u002Fbutton>\n",[3647,12042,12044],{"class":3649,"line":12043},142,[3647,12045,9080],{"class":4044},[3647,12047,12049],{"class":3649,"line":12048},143,[3647,12050,9085],{"class":4044},[3647,12052,12054],{"class":3649,"line":12053},144,[3647,12055,12056],{"class":4044},"        \u003Cscript>\n",[3647,12058,12060],{"class":3649,"line":12059},145,[3647,12061,12062],{"class":4044},"            document.getElementById('payForm').addEventListener('submit', async (e) => {\n",[3647,12064,12066],{"class":3649,"line":12065},146,[3647,12067,12068],{"class":4044},"                e.preventDefault();\n",[3647,12070,12072],{"class":3649,"line":12071},147,[3647,12073,12074],{"class":4044},"                const formData = new FormData(e.target);\n",[3647,12076,12078],{"class":3649,"line":12077},148,[3647,12079,12080],{"class":4044},"                const payload = {\n",[3647,12082,12084],{"class":3649,"line":12083},149,[3647,12085,12086],{"class":4044},"                    amount: parseFloat(formData.get('amount')),\n",[3647,12088,12090],{"class":3649,"line":12089},150,[3647,12091,12092],{"class":4044},"                    currency: formData.get('currency'),\n",[3647,12094,12096],{"class":3649,"line":12095},151,[3647,12097,12098],{"class":4044},"                    card_number: formData.get('card_number'),\n",[3647,12100,12102],{"class":3649,"line":12101},152,[3647,12103,12104],{"class":4044},"                    card_expiry: formData.get('card_expiry'),\n",[3647,12106,12108],{"class":3649,"line":12107},153,[3647,12109,12110],{"class":4044},"                    cvv: formData.get('cvv'),\n",[3647,12112,12114],{"class":3649,"line":12113},154,[3647,12115,12116],{"class":4044},"                    billing_address: {\n",[3647,12118,12120],{"class":3649,"line":12119},155,[3647,12121,12122],{"class":4044},"                        country: formData.get('country'),\n",[3647,12124,12126],{"class":3649,"line":12125},156,[3647,12127,12128],{"class":4044},"                        city: formData.get('city'),\n",[3647,12130,12132],{"class":3649,"line":12131},157,[3647,12133,12134],{"class":4044},"                        zip_code: formData.get('zip_code')\n",[3647,12136,12138],{"class":3649,"line":12137},158,[3647,12139,12140],{"class":4044},"                    }\n",[3647,12142,12144],{"class":3649,"line":12143},159,[3647,12145,12146],{"class":4044},"                };\n",[3647,12148,12150],{"class":3649,"line":12149},160,[3647,12151,3671],{"emptyLinePlaceholder":3670},[3647,12153,12155],{"class":3649,"line":12154},161,[3647,12156,12157],{"class":4044},"                const res = await fetch('\u002Fpay', {\n",[3647,12159,12161],{"class":3649,"line":12160},162,[3647,12162,12163],{"class":4044},"                    method: 'POST',\n",[3647,12165,12167],{"class":3649,"line":12166},163,[3647,12168,12169],{"class":4044},"                    headers: { 'Content-Type': 'application\u002Fjson' },\n",[3647,12171,12173],{"class":3649,"line":12172},164,[3647,12174,12175],{"class":4044},"                    body: JSON.stringify(payload)\n",[3647,12177,12179],{"class":3649,"line":12178},165,[3647,12180,12181],{"class":4044},"                });\n",[3647,12183,12185],{"class":3649,"line":12184},166,[3647,12186,12187],{"class":4044},"                const data = await res.json();\n",[3647,12189,12191],{"class":3649,"line":12190},167,[3647,12192,12193],{"class":4044},"                alert(JSON.stringify(data, null, 2));\n",[3647,12195,12197],{"class":3649,"line":12196},168,[3647,12198,12199],{"class":4044},"            });\n",[3647,12201,12203],{"class":3649,"line":12202},169,[3647,12204,12205],{"class":4044},"        \u003C\u002Fscript>\n",[3647,12207,12209],{"class":3649,"line":12208},170,[3647,12210,9090],{"class":4044},[3647,12212,12214],{"class":3649,"line":12213},171,[3647,12215,9095],{"class":4044},[3647,12217,12219],{"class":3649,"line":12218},172,[3647,12220,9100],{"class":4044},[3647,12222,12224,12226,12228,12230],{"class":3649,"line":12223},173,[3647,12225,4647],{"class":3653},[3647,12227,9107],{"class":3657},[3647,12229,9110],{"class":3725},[3647,12231,9113],{"class":3657},[3647,12233,12235],{"class":3649,"line":12234},174,[3647,12236,3671],{"emptyLinePlaceholder":3670},[3647,12238,12240,12242,12244,12247,12249,12251],{"class":3649,"line":12239},175,[3647,12241,4441],{"class":4041},[3647,12243,3685],{"class":3657},[3647,12245,12246],{"class":4044},"\"\u002Fpay\"",[3647,12248,3595],{"class":3657},[3647,12250,4457],{"class":3725},[3647,12252,5951],{"class":3657},[3647,12254,12256,12258,12260,12263,12265,12268],{"class":3649,"line":12255},176,[3647,12257,4466],{"class":3677},[3647,12259,4469],{"class":3677},[3647,12261,12262],{"class":4041}," process_payment",[3647,12264,3685],{"class":3657},[3647,12266,12267],{"class":3725},"payload",[3647,12269,12270],{"class":3657},": PaymentRequest):\n",[3647,12272,12274],{"class":3649,"line":12273},177,[3647,12275,12276],{"class":3697},"    # Оскільки вся валідація пройшла успішно на рівні Pydantic,\n",[3647,12278,12280],{"class":3649,"line":12279},178,[3647,12281,12282],{"class":3697},"    # ми впевнені у валідності номерів карт, термінів та форматів даних.\n",[3647,12284,12286,12288],{"class":3649,"line":12285},179,[3647,12287,4647],{"class":3653},[3647,12289,9275],{"class":3657},[3647,12291,12293,12296,12298,12301],{"class":3649,"line":12292},180,[3647,12294,12295],{"class":4044},"        \"transaction_id\"",[3647,12297,4342],{"class":3657},[3647,12299,12300],{"class":4044},"\"tx_9876543210\"",[3647,12302,4590],{"class":3657},[3647,12304,12306,12308,12310,12313],{"class":3649,"line":12305},181,[3647,12307,9304],{"class":4044},[3647,12309,4342],{"class":3657},[3647,12311,12312],{"class":4044},"\"success\"",[3647,12314,4590],{"class":3657},[3647,12316,12318,12321],{"class":3649,"line":12317},182,[3647,12319,12320],{"class":4044},"        \"processed_at\"",[3647,12322,12323],{"class":3657},": datetime.now().isoformat(),\n",[3647,12325,12327,12330],{"class":3649,"line":12326},183,[3647,12328,12329],{"class":4044},"        \"amount\"",[3647,12331,12332],{"class":3657},": payload.amount,\n",[3647,12334,12336,12339],{"class":3649,"line":12335},184,[3647,12337,12338],{"class":4044},"        \"currency\"",[3647,12340,12341],{"class":3657},": payload.currency\n",[3647,12343,12345],{"class":3649,"line":12344},185,[3647,12346,4605],{"class":3657},[4755,12348,12350],{"id":12349},"запуск-та-тестування-платіжного-шлюзу","Запуск та тестування платіжного шлюзу",[3385,12352,12353],{},"Запустимо веб-сайт з Uvicorn:",[3634,12355,12356,12370],{},[3637,12357,12359],{"className":4026,"code":12358,"filename":4663,"language":4029,"meta":3643,"style":3643},"uvicorn payment_gateway:app --reload\n",[3401,12360,12361],{"__ignoreMap":3643},[3647,12362,12363,12365,12368],{"class":3649,"line":3650},[3647,12364,4670],{"class":4041},[3647,12366,12367],{"class":4044}," payment_gateway:app",[3647,12369,4676],{"class":3677},[3637,12371,12372],{"className":4679,"code":12358,"filename":4680,"language":4681,"meta":3643,"style":3643},[3401,12373,12374],{"__ignoreMap":3643},[3647,12375,12376],{"class":3649,"line":3650},[3647,12377,12358],{"class":3657},[3385,12379,12380,12381,12383],{},"Тепер ви можете відправити запит за допомогою ",[3401,12382,4752],{},". Ми надішлемо карту, яка провалює перевірку Луна (некоректна контрольна сума):",[3634,12385,12386,12419],{},[3637,12387,12389],{"className":4026,"code":12388,"filename":4663,"language":4029,"meta":3643,"style":3643},"curl -X POST http:\u002F\u002F127.0.0.1:8000\u002Fpay \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"amount\": 150.0, \"currency\": \"USD\", \"card_number\": \"4321-4321-4321-4322\", \"card_expiry\": \"12\u002F29\", \"cvv\": \"999\", \"billing_address\": {\"country\": \"UA\", \"city\": \"Lviv\", \"zip_code\": \"79000\"}}'\n",[3401,12390,12391,12404,12412],{"__ignoreMap":3643},[3647,12392,12393,12395,12397,12399,12402],{"class":3649,"line":3650},[3647,12394,4776],{"class":4041},[3647,12396,4779],{"class":3677},[3647,12398,4782],{"class":4044},[3647,12400,12401],{"class":4044}," http:\u002F\u002F127.0.0.1:8000\u002Fpay",[3647,12403,4789],{"class":4788},[3647,12405,12406,12408,12410],{"class":3649,"line":3667},[3647,12407,4794],{"class":3677},[3647,12409,4797],{"class":4044},[3647,12411,4789],{"class":4788},[3647,12413,12414,12416],{"class":3649,"line":3674},[3647,12415,4804],{"class":3677},[3647,12417,12418],{"class":4044}," '{\"amount\": 150.0, \"currency\": \"USD\", \"card_number\": \"4321-4321-4321-4322\", \"card_expiry\": \"12\u002F29\", \"cvv\": \"999\", \"billing_address\": {\"country\": \"UA\", \"city\": \"Lviv\", \"zip_code\": \"79000\"}}'\n",[3637,12420,12422],{"className":4679,"code":12421,"filename":4680,"language":4681,"meta":3643,"style":3643},"curl.exe -X POST http:\u002F\u002F127.0.0.1:8000\u002Fpay `\n  -H \"Content-Type: application\u002Fjson\" `\n  -d '{\"amount\": 150.0, \"currency\": \"USD\", \"card_number\": \"4321-4321-4321-4322\", \"card_expiry\": \"12\u002F29\", \"cvv\": \"999\", \"billing_address\": {\"country\": \"UA\", \"city\": \"Lviv\", \"zip_code\": \"79000\"}}'\n",[3401,12423,12424,12443,12451],{"__ignoreMap":3643},[3647,12425,12426,12428,12430,12432,12434,12436,12438,12440],{"class":3649,"line":3650},[3647,12427,4817],{"class":4041},[3647,12429,4820],{"class":3657},[3647,12431,4823],{"class":3732},[3647,12433,3558],{"class":3657},[3647,12435,4828],{"class":3732},[3647,12437,4831],{"class":3657},[3647,12439,4834],{"class":3732},[3647,12441,12442],{"class":3657},"\u002Fpay `\n",[3647,12444,12445,12447,12449],{"class":3649,"line":3667},[3647,12446,4842],{"class":3657},[3647,12448,4845],{"class":4044},[3647,12450,4848],{"class":3657},[3647,12452,12453,12455],{"class":3649,"line":3674},[3647,12454,4853],{"class":3657},[3647,12456,12457],{"class":4044},"'{\"amount\": 150.0, \"currency\": \"USD\", \"card_number\": \"4321-4321-4321-4322\", \"card_expiry\": \"12\u002F29\", \"cvv\": \"999\", \"billing_address\": {\"country\": \"UA\", \"city\": \"Lviv\", \"zip_code\": \"79000\"}}'\n",[4692,12459,12461,12467,12470,12473],{":cursor":4859,"title":12460},"Консольний вивід помилки карти",[4697,12462,12464],{"className":12463},[3649],[3647,12465,4969],{"className":12466},[4968,4704],[4697,12468,4872],{"className":12469},[3649],[4697,12471],{"className":12472},[3649],[4697,12474,12476],{"className":12475},[3649],"{\"detail\":[{\"type\":\"value_error\",\"loc\":[\"body\",\"card_number\"],\"msg\":\"Value error, Невалідний номер карти (перевірка контрольної суми Луна провалена)\",\"input\":\"4321432143214322\"}]}",[3413,12478],{},[3560,12480,12482],{"id":12481},"практика-в-taskforge-рефакторинг-та-захист-роутерів","Практика в TaskForge: Рефакторинг та захист роутерів",[3385,12484,12485,12486,12489,12490,12492],{},"Тепер перенесемо отримані знання в наш навчальний проект ",[3389,12487,12488],{},"TaskForge",". Ваша задача — замінити нетипізовану роботу зі словниками (",[3401,12491,4418],{},") у контролерах на валідацію за допомогою схем Pydantic v2.",[4755,12494,12496],{"id":12495},"крок-1-створення-файлів-схем","Крок 1: Створення файлів схем",[3385,12498,12499,12500,12503],{},"Створіть директорію ",[3401,12501,12502],{},"schemas"," всередині проекту та додайте такі описи.",[3385,12505,12506],{},"Для роботи зі статусами та пріоритетами задач додайте файли переліків:",[3637,12508,12511],{"className":3639,"code":12509,"filename":12510,"language":3642,"meta":3643,"style":3643},"# app\u002Fschemas\u002Fenums.py\nfrom enum import StrEnum\n\nclass TaskStatus(StrEnum):\n    TODO = \"todo\"\n    IN_PROGRESS = \"in_progress\"\n    DONE = \"done\"\n\nclass TaskPriority(StrEnum):\n    LOW = \"low\"\n    MEDIUM = \"medium\"\n    HIGH = \"high\"\n","app\u002Fschemas\u002Fenums.py",[3401,12512,12513,12518,12528,12532,12544,12550,12556,12562,12566,12578,12584,12590],{"__ignoreMap":3643},[3647,12514,12515],{"class":3649,"line":3650},[3647,12516,12517],{"class":3697},"# app\u002Fschemas\u002Fenums.py\n",[3647,12519,12520,12522,12524,12526],{"class":3649,"line":3667},[3647,12521,3654],{"class":3653},[3647,12523,8012],{"class":3657},[3647,12525,3661],{"class":3653},[3647,12527,8017],{"class":3657},[3647,12529,12530],{"class":3649,"line":3674},[3647,12531,3671],{"emptyLinePlaceholder":3670},[3647,12533,12534,12536,12538,12540,12542],{"class":3649,"line":3694},[3647,12535,3678],{"class":3677},[3647,12537,8028],{"class":3681},[3647,12539,3685],{"class":3657},[3647,12541,7983],{"class":3681},[3647,12543,3691],{"class":3657},[3647,12545,12546,12548],{"class":3649,"line":3701},[3647,12547,8039],{"class":3657},[3647,12549,8042],{"class":4044},[3647,12551,12552,12554],{"class":3649,"line":3707},[3647,12553,8047],{"class":3657},[3647,12555,8050],{"class":4044},[3647,12557,12558,12560],{"class":3649,"line":3713},[3647,12559,8055],{"class":3657},[3647,12561,8058],{"class":4044},[3647,12563,12564],{"class":3649,"line":3749},[3647,12565,3671],{"emptyLinePlaceholder":3670},[3647,12567,12568,12570,12572,12574,12576],{"class":3649,"line":3886},[3647,12569,3678],{"class":3677},[3647,12571,8216],{"class":3681},[3647,12573,3685],{"class":3657},[3647,12575,7983],{"class":3681},[3647,12577,3691],{"class":3657},[3647,12579,12580,12582],{"class":3649,"line":3895},[3647,12581,8227],{"class":3657},[3647,12583,8230],{"class":4044},[3647,12585,12586,12588],{"class":3649,"line":3920},[3647,12587,8235],{"class":3657},[3647,12589,8238],{"class":4044},[3647,12591,12592,12594],{"class":3649,"line":3948},[3647,12593,8243],{"class":3657},[3647,12595,8246],{"class":4044},[3385,12597,12598,12599,12602],{},"Схема проекту (",[3401,12600,12601],{},"app\u002Fschemas\u002Fproject.py","):",[3637,12604,12606],{"className":3639,"code":12605,"filename":12601,"language":3642,"meta":3643,"style":3643},"# app\u002Fschemas\u002Fproject.py\nfrom datetime import datetime\nfrom pydantic import BaseModel, Field, ConfigDict\n\nclass ProjectBase(BaseModel):\n    name: str = Field(..., min_length=3, max_length=100)\n    description: str | None = Field(default=None, max_length=1000)\n\nclass ProjectCreate(ProjectBase):\n    pass\n\nclass ProjectUpdate(ProjectBase):\n    name: str | None = Field(default=None, min_length=3, max_length=100)\n\nclass ProjectRead(ProjectBase):\n    id: int\n    created_at: datetime\n\n    model_config = ConfigDict(from_attributes=True)\n",[3401,12607,12608,12613,12623,12634,12638,12651,12675,12703,12707,12720,12724,12728,12741,12777,12781,12794,12802,12806,12810],{"__ignoreMap":3643},[3647,12609,12610],{"class":3649,"line":3650},[3647,12611,12612],{"class":3697},"# app\u002Fschemas\u002Fproject.py\n",[3647,12614,12615,12617,12619,12621],{"class":3649,"line":3667},[3647,12616,3654],{"class":3653},[3647,12618,4164],{"class":3657},[3647,12620,3661],{"class":3653},[3647,12622,4169],{"class":3657},[3647,12624,12625,12627,12629,12631],{"class":3649,"line":3674},[3647,12626,3654],{"class":3653},[3647,12628,3658],{"class":3657},[3647,12630,3661],{"class":3653},[3647,12632,12633],{"class":3657}," BaseModel, Field, ConfigDict\n",[3647,12635,12636],{"class":3649,"line":3694},[3647,12637,3671],{"emptyLinePlaceholder":3670},[3647,12639,12640,12642,12645,12647,12649],{"class":3649,"line":3701},[3647,12641,3678],{"class":3677},[3647,12643,12644],{"class":3681}," ProjectBase",[3647,12646,3685],{"class":3657},[3647,12648,3688],{"class":3681},[3647,12650,3691],{"class":3657},[3647,12652,12653,12655,12657,12659,12661,12663,12665,12667,12669,12671,12673],{"class":3649,"line":3707},[3647,12654,6604],{"class":3657},[3647,12656,3719],{"class":3681},[3647,12658,4246],{"class":3657},[3647,12660,3726],{"class":3725},[3647,12662,3729],{"class":3657},[3647,12664,3763],{"class":3732},[3647,12666,3595],{"class":3657},[3647,12668,3738],{"class":3725},[3647,12670,3729],{"class":3657},[3647,12672,3743],{"class":3732},[3647,12674,3746],{"class":3657},[3647,12676,12677,12679,12681,12683,12685,12687,12689,12691,12693,12695,12697,12699,12701],{"class":3649,"line":3713},[3647,12678,7626],{"class":3657},[3647,12680,3719],{"class":3681},[3647,12682,5084],{"class":3657},[3647,12684,5087],{"class":3677},[3647,12686,3722],{"class":3657},[3647,12688,5397],{"class":3725},[3647,12690,3729],{"class":3657},[3647,12692,5087],{"class":3677},[3647,12694,3595],{"class":3657},[3647,12696,3738],{"class":3725},[3647,12698,3729],{"class":3657},[3647,12700,7645],{"class":3732},[3647,12702,3746],{"class":3657},[3647,12704,12705],{"class":3649,"line":3749},[3647,12706,3671],{"emptyLinePlaceholder":3670},[3647,12708,12709,12711,12713,12715,12718],{"class":3649,"line":3886},[3647,12710,3678],{"class":3677},[3647,12712,7591],{"class":3681},[3647,12714,3685],{"class":3657},[3647,12716,12717],{"class":3681},"ProjectBase",[3647,12719,3691],{"class":3657},[3647,12721,12722],{"class":3649,"line":3895},[3647,12723,10010],{"class":3653},[3647,12725,12726],{"class":3649,"line":3920},[3647,12727,3671],{"emptyLinePlaceholder":3670},[3647,12729,12730,12732,12735,12737,12739],{"class":3649,"line":3948},[3647,12731,3678],{"class":3677},[3647,12733,12734],{"class":3681}," ProjectUpdate",[3647,12736,3685],{"class":3657},[3647,12738,12717],{"class":3681},[3647,12740,3691],{"class":3657},[3647,12742,12743,12745,12747,12749,12751,12753,12755,12757,12759,12761,12763,12765,12767,12769,12771,12773,12775],{"class":3649,"line":3953},[3647,12744,6604],{"class":3657},[3647,12746,3719],{"class":3681},[3647,12748,5084],{"class":3657},[3647,12750,5087],{"class":3677},[3647,12752,3722],{"class":3657},[3647,12754,5397],{"class":3725},[3647,12756,3729],{"class":3657},[3647,12758,5087],{"class":3677},[3647,12760,3595],{"class":3657},[3647,12762,3726],{"class":3725},[3647,12764,3729],{"class":3657},[3647,12766,3763],{"class":3732},[3647,12768,3595],{"class":3657},[3647,12770,3738],{"class":3725},[3647,12772,3729],{"class":3657},[3647,12774,3743],{"class":3732},[3647,12776,3746],{"class":3657},[3647,12778,12779],{"class":3649,"line":3962},[3647,12780,3671],{"emptyLinePlaceholder":3670},[3647,12782,12783,12785,12788,12790,12792],{"class":3649,"line":3983},[3647,12784,3678],{"class":3677},[3647,12786,12787],{"class":3681}," ProjectRead",[3647,12789,3685],{"class":3657},[3647,12791,12717],{"class":3681},[3647,12793,3691],{"class":3657},[3647,12795,12796,12798,12800],{"class":3649,"line":4011},[3647,12797,4339],{"class":4041},[3647,12799,4342],{"class":3657},[3647,12801,4345],{"class":3681},[3647,12803,12804],{"class":3649,"line":4316},[3647,12805,4365],{"class":3657},[3647,12807,12808],{"class":3649,"line":4322},[3647,12809,3671],{"emptyLinePlaceholder":3670},[3647,12811,12812,12814,12816,12818,12820],{"class":3649,"line":4336},[3647,12813,4388],{"class":3657},[3647,12815,4391],{"class":3725},[3647,12817,3729],{"class":3657},[3647,12819,4396],{"class":3677},[3647,12821,3746],{"class":3657},[3385,12823,12824,12825,12602],{},"Схема завдання (",[3401,12826,12827],{},"app\u002Fschemas\u002Ftask.py",[3637,12829,12831],{"className":3639,"code":12830,"filename":12827,"language":3642,"meta":3643,"style":3643},"# app\u002Fschemas\u002Ftask.py\nfrom datetime import datetime\nfrom pydantic import BaseModel, Field, ConfigDict\nfrom app.schemas.enums import TaskStatus, TaskPriority\n\nclass TaskBase(BaseModel):\n    title: str = Field(..., min_length=3, max_length=150)\n    description: str | None = Field(default=None, max_length=2000)\n    status: TaskStatus = TaskStatus.TODO\n    priority: TaskPriority = TaskPriority.MEDIUM\n\nclass TaskCreate(TaskBase):\n    project_id: int\n\nclass TaskUpdate(TaskBase):\n    title: str | None = Field(default=None, min_length=3, max_length=150)\n    status: TaskStatus | None = None\n    priority: TaskPriority | None = None\n\nclass TaskRead(TaskBase):\n    id: int\n    project_id: int\n    created_at: datetime\n\n    model_config = ConfigDict(from_attributes=True)\n",[3401,12832,12833,12838,12848,12858,12870,12874,12887,12912,12941,12946,12950,12954,12967,12974,12978,12991,13027,13038,13049,13053,13066,13074,13080,13084,13088],{"__ignoreMap":3643},[3647,12834,12835],{"class":3649,"line":3650},[3647,12836,12837],{"class":3697},"# app\u002Fschemas\u002Ftask.py\n",[3647,12839,12840,12842,12844,12846],{"class":3649,"line":3667},[3647,12841,3654],{"class":3653},[3647,12843,4164],{"class":3657},[3647,12845,3661],{"class":3653},[3647,12847,4169],{"class":3657},[3647,12849,12850,12852,12854,12856],{"class":3649,"line":3674},[3647,12851,3654],{"class":3653},[3647,12853,3658],{"class":3657},[3647,12855,3661],{"class":3653},[3647,12857,12633],{"class":3657},[3647,12859,12860,12862,12865,12867],{"class":3649,"line":3694},[3647,12861,3654],{"class":3653},[3647,12863,12864],{"class":3657}," app.schemas.enums ",[3647,12866,3661],{"class":3653},[3647,12868,12869],{"class":3657}," TaskStatus, TaskPriority\n",[3647,12871,12872],{"class":3649,"line":3701},[3647,12873,3671],{"emptyLinePlaceholder":3670},[3647,12875,12876,12878,12881,12883,12885],{"class":3649,"line":3707},[3647,12877,3678],{"class":3677},[3647,12879,12880],{"class":3681}," TaskBase",[3647,12882,3685],{"class":3657},[3647,12884,3688],{"class":3681},[3647,12886,3691],{"class":3657},[3647,12888,12889,12891,12893,12895,12897,12899,12901,12903,12905,12907,12910],{"class":3649,"line":3713},[3647,12890,6821],{"class":3657},[3647,12892,3719],{"class":3681},[3647,12894,4246],{"class":3657},[3647,12896,3726],{"class":3725},[3647,12898,3729],{"class":3657},[3647,12900,3763],{"class":3732},[3647,12902,3595],{"class":3657},[3647,12904,3738],{"class":3725},[3647,12906,3729],{"class":3657},[3647,12908,12909],{"class":3732},"150",[3647,12911,3746],{"class":3657},[3647,12913,12914,12916,12918,12920,12922,12924,12926,12928,12930,12932,12934,12936,12939],{"class":3649,"line":3749},[3647,12915,7626],{"class":3657},[3647,12917,3719],{"class":3681},[3647,12919,5084],{"class":3657},[3647,12921,5087],{"class":3677},[3647,12923,3722],{"class":3657},[3647,12925,5397],{"class":3725},[3647,12927,3729],{"class":3657},[3647,12929,5087],{"class":3677},[3647,12931,3595],{"class":3657},[3647,12933,3738],{"class":3725},[3647,12935,3729],{"class":3657},[3647,12937,12938],{"class":3732},"2000",[3647,12940,3746],{"class":3657},[3647,12942,12943],{"class":3649,"line":3886},[3647,12944,12945],{"class":3657},"    status: TaskStatus = TaskStatus.TODO\n",[3647,12947,12948],{"class":3649,"line":3895},[3647,12949,8292],{"class":3657},[3647,12951,12952],{"class":3649,"line":3920},[3647,12953,3671],{"emptyLinePlaceholder":3670},[3647,12955,12956,12958,12960,12962,12965],{"class":3649,"line":3948},[3647,12957,3678],{"class":3677},[3647,12959,8257],{"class":3681},[3647,12961,3685],{"class":3657},[3647,12963,12964],{"class":3681},"TaskBase",[3647,12966,3691],{"class":3657},[3647,12968,12969,12972],{"class":3649,"line":3953},[3647,12970,12971],{"class":3657},"    project_id: ",[3647,12973,4345],{"class":3681},[3647,12975,12976],{"class":3649,"line":3962},[3647,12977,3671],{"emptyLinePlaceholder":3670},[3647,12979,12980,12982,12985,12987,12989],{"class":3649,"line":3983},[3647,12981,3678],{"class":3677},[3647,12983,12984],{"class":3681}," TaskUpdate",[3647,12986,3685],{"class":3657},[3647,12988,12964],{"class":3681},[3647,12990,3691],{"class":3657},[3647,12992,12993,12995,12997,12999,13001,13003,13005,13007,13009,13011,13013,13015,13017,13019,13021,13023,13025],{"class":3649,"line":4011},[3647,12994,6821],{"class":3657},[3647,12996,3719],{"class":3681},[3647,12998,5084],{"class":3657},[3647,13000,5087],{"class":3677},[3647,13002,3722],{"class":3657},[3647,13004,5397],{"class":3725},[3647,13006,3729],{"class":3657},[3647,13008,5087],{"class":3677},[3647,13010,3595],{"class":3657},[3647,13012,3726],{"class":3725},[3647,13014,3729],{"class":3657},[3647,13016,3763],{"class":3732},[3647,13018,3595],{"class":3657},[3647,13020,3738],{"class":3725},[3647,13022,3729],{"class":3657},[3647,13024,12909],{"class":3732},[3647,13026,3746],{"class":3657},[3647,13028,13029,13032,13034,13036],{"class":3649,"line":4316},[3647,13030,13031],{"class":3657},"    status: TaskStatus | ",[3647,13033,5087],{"class":3677},[3647,13035,3912],{"class":3657},[3647,13037,5046],{"class":3677},[3647,13039,13040,13043,13045,13047],{"class":3649,"line":4322},[3647,13041,13042],{"class":3657},"    priority: TaskPriority | ",[3647,13044,5087],{"class":3677},[3647,13046,3912],{"class":3657},[3647,13048,5046],{"class":3677},[3647,13050,13051],{"class":3649,"line":4336},[3647,13052,3671],{"emptyLinePlaceholder":3670},[3647,13054,13055,13057,13060,13062,13064],{"class":3649,"line":4348},[3647,13056,3678],{"class":3677},[3647,13058,13059],{"class":3681}," TaskRead",[3647,13061,3685],{"class":3657},[3647,13063,12964],{"class":3681},[3647,13065,3691],{"class":3657},[3647,13067,13068,13070,13072],{"class":3649,"line":4362},[3647,13069,4339],{"class":4041},[3647,13071,4342],{"class":3657},[3647,13073,4345],{"class":3681},[3647,13075,13076,13078],{"class":3649,"line":4368},[3647,13077,12971],{"class":3657},[3647,13079,4345],{"class":3681},[3647,13081,13082],{"class":3649,"line":4373},[3647,13083,4365],{"class":3657},[3647,13085,13086],{"class":3649,"line":4379},[3647,13087,3671],{"emptyLinePlaceholder":3670},[3647,13089,13090,13092,13094,13096,13098],{"class":3649,"line":4385},[3647,13091,4388],{"class":3657},[3647,13093,4391],{"class":3725},[3647,13095,3729],{"class":3657},[3647,13097,4396],{"class":3677},[3647,13099,3746],{"class":3657},[4755,13101,13103],{"id":13102},"крок-2-інтеграція-в-роутери","Крок 2: Інтеграція в роутери",[3385,13105,13106],{},"Оновіть ваші роутери FastAPI, використовуючи створені схеми як параметри запитів та для серіалізації відповідей:",[3637,13108,13111],{"className":3639,"code":13109,"filename":13110,"language":3642,"meta":3643,"style":3643},"# app\u002Frouters\u002Ftasks.py\nfrom fastapi import APIRouter, status, HTTPException\nfrom app.schemas.task import TaskCreate, TaskRead, TaskUpdate\n\nrouter = APIRouter(prefix=\"\u002Ftasks\", tags=[\"Tasks\"])\n\n# Симуляція БД\ndb_tasks = []\n\n@router.post(\"\", response_model=TaskRead, status_code=status.HTTP_201_CREATED)\nasync def create_task(task_in: TaskCreate):\n    # Логіка збереження\n    new_task = {\n        \"id\": len(db_tasks) + 1,\n        **task_in.model_dump(),\n        \"created_at\": \"2026-07-01T10:15:00Z\"\n    }\n    db_tasks.append(new_task)\n    return new_task\n","app\u002Frouters\u002Ftasks.py",[3401,13112,13113,13118,13129,13141,13145,13171,13175,13180,13185,13189,13209,13224,13229,13234,13250,13255,13264,13268,13273],{"__ignoreMap":3643},[3647,13114,13115],{"class":3649,"line":3650},[3647,13116,13117],{"class":3697},"# app\u002Frouters\u002Ftasks.py\n",[3647,13119,13120,13122,13124,13126],{"class":3649,"line":3667},[3647,13121,3654],{"class":3653},[3647,13123,4187],{"class":3657},[3647,13125,3661],{"class":3653},[3647,13127,13128],{"class":3657}," APIRouter, status, HTTPException\n",[3647,13130,13131,13133,13136,13138],{"class":3649,"line":3674},[3647,13132,3654],{"class":3653},[3647,13134,13135],{"class":3657}," app.schemas.task ",[3647,13137,3661],{"class":3653},[3647,13139,13140],{"class":3657}," TaskCreate, TaskRead, TaskUpdate\n",[3647,13142,13143],{"class":3649,"line":3694},[3647,13144,3671],{"emptyLinePlaceholder":3670},[3647,13146,13147,13150,13153,13155,13157,13159,13162,13165,13168],{"class":3649,"line":3701},[3647,13148,13149],{"class":3657},"router = APIRouter(",[3647,13151,13152],{"class":3725},"prefix",[3647,13154,3729],{"class":3657},[3647,13156,8326],{"class":4044},[3647,13158,3595],{"class":3657},[3647,13160,13161],{"class":3725},"tags",[3647,13163,13164],{"class":3657},"=[",[3647,13166,13167],{"class":4044},"\"Tasks\"",[3647,13169,13170],{"class":3657},"])\n",[3647,13172,13173],{"class":3649,"line":3707},[3647,13174,3671],{"emptyLinePlaceholder":3670},[3647,13176,13177],{"class":3649,"line":3713},[3647,13178,13179],{"class":3697},"# Симуляція БД\n",[3647,13181,13182],{"class":3649,"line":3749},[3647,13183,13184],{"class":3657},"db_tasks = []\n",[3647,13186,13187],{"class":3649,"line":3886},[3647,13188,3671],{"emptyLinePlaceholder":3670},[3647,13190,13191,13194,13196,13198,13200,13202,13205,13207],{"class":3649,"line":3895},[3647,13192,13193],{"class":4041},"@router.post",[3647,13195,3685],{"class":3657},[3647,13197,7075],{"class":4044},[3647,13199,3595],{"class":3657},[3647,13201,4451],{"class":3725},[3647,13203,13204],{"class":3657},"=TaskRead, ",[3647,13206,4457],{"class":3725},[3647,13208,4460],{"class":3657},[3647,13210,13211,13213,13215,13217,13219,13222],{"class":3649,"line":3920},[3647,13212,4466],{"class":3677},[3647,13214,4469],{"class":3677},[3647,13216,8341],{"class":4041},[3647,13218,3685],{"class":3657},[3647,13220,13221],{"class":3725},"task_in",[3647,13223,8349],{"class":3657},[3647,13225,13226],{"class":3649,"line":3948},[3647,13227,13228],{"class":3697},"    # Логіка збереження\n",[3647,13230,13231],{"class":3649,"line":3953},[3647,13232,13233],{"class":3657},"    new_task = {\n",[3647,13235,13236,13238,13240,13243,13246,13248],{"class":3649,"line":3962},[3647,13237,4547],{"class":4044},[3647,13239,4342],{"class":3657},[3647,13241,13242],{"class":4041},"len",[3647,13244,13245],{"class":3657},"(db_tasks) + ",[3647,13247,5713],{"class":3732},[3647,13249,4590],{"class":3657},[3647,13251,13252],{"class":3649,"line":3983},[3647,13253,13254],{"class":3657},"        **task_in.model_dump(),\n",[3647,13256,13257,13259,13261],{"class":3649,"line":4011},[3647,13258,4596],{"class":4044},[3647,13260,4342],{"class":3657},[3647,13262,13263],{"class":4044},"\"2026-07-01T10:15:00Z\"\n",[3647,13265,13266],{"class":3649,"line":4316},[3647,13267,4605],{"class":3657},[3647,13269,13270],{"class":3649,"line":4322},[3647,13271,13272],{"class":3657},"    db_tasks.append(new_task)\n",[3647,13274,13275,13277],{"class":3649,"line":4336},[3647,13276,4647],{"class":3653},[3647,13278,13279],{"class":3657}," new_task\n",[3385,13281,13282,13283,13286],{},"Завдяки вказівці ",[3401,13284,13285],{},"response_model=TaskRead"," FastAPI автоматично відфільтрує будь-які внутрішні поля та надасть клієнту відповідь, що гарантовано відповідає інтерфейсному контракту.",[3385,13288,13289],{},"Збережіть ваш прогрес у Git:",[3637,13291,13294],{"className":4026,"code":13292,"filename":13293,"language":4029,"meta":3643,"style":3643},"git add .\ngit commit -m \"feat: add Pydantic schemas for request\u002Fresponse validation\"\n","terminal",[3401,13295,13296,13306],{"__ignoreMap":3643},[3647,13297,13298,13301,13303],{"class":3649,"line":3650},[3647,13299,13300],{"class":4041},"git",[3647,13302,4093],{"class":4044},[3647,13304,13305],{"class":4044}," .\n",[3647,13307,13308,13310,13313,13316],{"class":3649,"line":3667},[3647,13309,13300],{"class":4041},[3647,13311,13312],{"class":4044}," commit",[3647,13314,13315],{"class":3677}," -m",[3647,13317,13318],{"class":4044}," \"feat: add Pydantic schemas for request\u002Fresponse validation\"\n",[13320,13321,13322],"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 .su1O8, html code.shiki .su1O8{--shiki-light:#0000FF;--shiki-default:#569CD6;--shiki-dark:#569CD6}html pre.shiki code .sN1BT, html code.shiki .sN1BT{--shiki-light:#267F99;--shiki-default:#4EC9B0;--shiki-dark:#4EC9B0}html pre.shiki code .spJ8K, html code.shiki .spJ8K{--shiki-light:#008000;--shiki-default:#6A9955;--shiki-dark:#6A9955}html pre.shiki code .siwwj, html code.shiki .siwwj{--shiki-light:#001080;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .sJj4R, html code.shiki .sJj4R{--shiki-light:#098658;--shiki-default:#B5CEA8;--shiki-dark:#B5CEA8}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 .s8Opu, html code.shiki .s8Opu{--shiki-light:#795E26;--shiki-default:#DCDCAA;--shiki-dark:#DCDCAA}html pre.shiki code .sbdoH, html code.shiki .sbdoH{--shiki-light:#A31515;--shiki-default:#CE9178;--shiki-dark:#CE9178}html pre.shiki code .sjcCO, html code.shiki .sjcCO{--shiki-light:#EE0000;--shiki-default:#D7BA7D;--shiki-dark:#D7BA7D}html pre.shiki code .sLwNe, html code.shiki .sLwNe{--shiki-light:#0451A5;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .shsrj, html code.shiki .shsrj{--shiki-light:#811F3F;--shiki-default:#D16969;--shiki-dark:#D16969}html pre.shiki code .sAV2Q, html code.shiki .sAV2Q{--shiki-light:#D16969;--shiki-default:#CE9178;--shiki-dark:#CE9178}html pre.shiki code .sYsli, html code.shiki .sYsli{--shiki-light:#000000;--shiki-default:#D7BA7D;--shiki-dark:#D7BA7D}html pre.shiki code .sD7JJ, html code.shiki .sD7JJ{--shiki-light:#000000FF;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}",{"title":3643,"searchDepth":3667,"depth":3667,"links":13324},[13325,13326,13329,13332,13335,13345,13351,13360],{"id":3418,"depth":3667,"text":3419},{"id":3481,"depth":3667,"text":3482,"children":13327},[13328],{"id":3562,"depth":3674,"text":3563},{"id":3612,"depth":3667,"text":3613,"children":13330},[13331],{"id":4017,"depth":3674,"text":4018},{"id":4119,"depth":3667,"text":4120,"children":13333},[13334],{"id":4653,"depth":3674,"text":4654},{"id":4984,"depth":3667,"text":4985,"children":13336},[13337,13339,13341,13342,13343],{"id":4993,"depth":3674,"text":13338},"Сучасний синтаксис: Optional чи | None?",{"id":5110,"depth":3674,"text":13340},"Часткові оновлення (PATCH) та робота з Optional полями",{"id":6083,"depth":3674,"text":6084},{"id":6327,"depth":3674,"text":6328},{"id":6548,"depth":3674,"text":13344},"Глибокий розбір строкових посилань (Forward References) та model_rebuild()",{"id":6879,"depth":3667,"text":6880,"children":13346},[13347,13349,13350],{"id":6892,"depth":3674,"text":13348},"Кастомні типи даних за допомогою Annotated та валідаторів",{"id":7348,"depth":3674,"text":7349},{"id":7795,"depth":3674,"text":7796},{"id":7924,"depth":3667,"text":7925,"children":13352},[13353,13355,13356,13357,13359],{"id":7944,"depth":3674,"text":13354},"Обмеження значень: Enum та Literal",{"id":8359,"depth":3674,"text":8360},{"id":8603,"depth":3674,"text":8604},{"id":8724,"depth":3674,"text":13358},"Порівняння архітектурних концепцій: ASP.NET Core IFormFile ↔ FastAPI UploadFile",{"id":9734,"depth":3674,"text":9735},{"id":9854,"depth":3667,"text":9855,"children":13361},[13362,13363,13364],{"id":9863,"depth":3674,"text":9864},{"id":10675,"depth":3674,"text":10676},{"id":12481,"depth":3674,"text":12482},"Глибоке занурення у проектування схем даних у FastAPI за допомогою Pydantic v2. Розбір стратегії розділення моделей для введення, виведення та оновлення, наслідування моделей та порівняння з DTO в ASP.NET Core.","md",null,{},{"title":2630,"description":13365},"eNsf4r4_vnVnOz0ALJVr21ueaKBFh7y-E-P6U6EJH6U",[13372,13374],{"title":2626,"path":2627,"stem":2628,"description":13373,"children":-1},"Детальний розбір маршрутизації у FastAPI. Робота з Path та Query параметрами, концепція Data Binding, порівняння з ASP.NET Core моделями та використання валідації через класи Path та Query.",{"title":2634,"path":2635,"stem":2636,"description":13375,"children":-1},"Глибоке занурення в концепцію впровадження залежностей (Dependency Injection) у FastAPI. Розбір відмінностей від IoC-контейнерів ASP.NET Core, механіки Depends(), побудови графа залежностей та кешування результатів.",1783248268042]