Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Алексей Гончарук – Современный веб с темлейтами...
Search
Sobolev Nikita
April 09, 2025
Technology
160
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Алексей Гончарук – Современный веб с темлейтами @ PythoNN
Sobolev Nikita
April 09, 2025
More Decks by Sobolev Nikita
See All by Sobolev Nikita
adaptix_slides.pdf
sobolevn
0
4
Зачем программисту Twitch (что?) – Алексей Гладких, PythoNN
sobolevn
0
6
MQTT глазами BE для работы с embedded устройствами – Сергей Макаров, PythoNN
sobolevn
0
4
Я в СКРЫТОМ ПУЛЕ: выстраиваем процессы в команде без тильта – Максим Мельников, PythoNN
sobolevn
0
9
Я твой fastapi msgspec'ом ускорял – Александр Целуйко, PythoNN
sobolevn
0
5
Будущее локальных LLM – Алексей Голобурдин, PythoNN
sobolevn
0
33
Чего вы не знали о строках в Python – Василий Рябов, PythoNN
sobolevn
0
260
ИИ-Агенты в каждый дом – Алексей Порядин, PythoNN
sobolevn
0
230
Внутреннее устройство сборки мусора в CPython 3.14+ – Сергей Мирянов, PythoNN
sobolevn
0
150
Other Decks in Technology
See All in Technology
こんにちは!SORACOM です & IoT とは【SORACOM UG 東北 in 八戸】
soracom
PRO
0
110
React改造してみた
tsukina_7mochi
0
140
Codex を使った障害対応机上演習の実際
orgachem
PRO
0
120
トークン化預金DCJPYの取り組みと活用ユースケース
finengine
0
200
DBについての基礎説明(MySQL編) - コロプラ 2026年度 新卒研修
colopl
0
150
~最速でビジネスインパクトを創出する~ Sansanのデータ基盤と活用事例
sansantech
PRO
0
110
ウォンテッドリーの最近のプロダクトと技術の紹介
stomk
0
290
JAWS-UGグッズ職人仕事の流儀
kentapapa
0
130
事業成長とデータ活用を止めないためのマネジメントへ挑戦
hiracky16
0
420
S3 Filesで挑む! 八戸せんべい汁の食べ頃問題
usanchuu
0
130
PagerDuty Tech Hub-鳴らすけれど、人は動かない
ezaki
0
190
古い指輪から広がった JAVA コミュニティ とのつながり
yamad
0
130
Featured
See All Featured
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.4k
Building Applications with DynamoDB
mza
96
7.2k
ラッコキーワード サービス紹介資料
rakko
2
5.4M
How to Talk to Developers About Accessibility
jct
2
580
Balancing Empowerment & Direction
lara
6
1.3k
Building an army of robots
kneath
307
47k
The Curious Case for Waylosing
cassininazir
1
570
Statistics for Hackers
jakevdp
799
230k
Large-scale JavaScript Application Architecture
addyosmani
516
110k
brightonSEO & MeasureFest 2025 - Winning Strategies for Black Friday CRO & PPC - Christian Goodrich
cargoodrich
3
870
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2.2k
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.5k
Transcript
СОВРЕМЕННЫЙ WEB С СОВРЕМЕННЫМИ ТЕМПЛЕЙТАМИ Алексей Гончарук Газ ИТ
ЧЕМ Я ЗАНИМАЮСЬ • Работаю в Газ ИТ • Делаю
апишки на python • Делаю SPAшки на Vue • Разгребаю хранимки Oracle на PL/SQL
ЧТО МЫ ХОТИМ ПОЛУЧИТЬ
ИСХОДНЫЙ КОД
Между Django и FastAPI
• Flask – слишком маленький • FastAPI – лучше, но
всё ещё мало • Django – В целом ОК, но с апишками после FastAPI грустно ПОЧЕМУ LITESTAR А НЕ …
• Готовые базовые модели • Настроенный DI • Миграции •
Репозитории и Сервисы ADVANCED ALCHEMY
Готовые базовые модели from datetime import UTC, datetime from advanced_alchemy.base
import UUIDBase from advanced_alchemy.types import DateTimeUTC from sqlalchemy.orm import Mapped, mapped_column class Task(UUIDBase): __tablename__ = 'task' name: Mapped[str] = mapped_column(unique=True) description: Mapped[str] is_done: Mapped[bool] = mapped_column(default=False) creation_date: Mapped[datetime] = mapped_column( DateTimeUTC(timezone=True), default=lambda: datetime.now(UTC), )
Интеграция с Litestar from advanced_alchemy.extensions.litestar import ( AsyncSessionConfig, SQLAlchemyAsyncConfig, SQLAlchemyPlugin,
) from litestar import Litestar session_config = AsyncSessionConfig( expire_on_commit=False ) sqlalchemy_config = SQLAlchemyAsyncConfig( connection_string='sqlite+aiosqlite:///db.sqlite3', session_config=session_config, create_all=True, ) sqlalchemy_plugin = SQLAlchemyPlugin( config=sqlalchemy_config ) app = Litestar( plugins=[ sqlalchemy_plugin, ], )
Интеграция с Litestar $ litestar database init $ litestar database
make-migration -m "add_todo_table“ $ litestar run
Паттерн репозиторий from advanced_alchemy.repository import ( SQLAlchemyAsyncRepository, ) from todoshnik.database
import models class TaskRepository( SQLAlchemyAsyncRepository[models.Task] ): model_type = models.Task uniquify = True
Паттерн сервис from advanced_alchemy.service import ( SQLAlchemyAsyncRepositoryService, ) from todoshnik.database.models
import Task from todoshnik.database.repository import TaskRepository class TaskService( SQLAlchemyAsyncRepositoryService[ Task, TaskRepository ] ): repository_type = TaskRepository uniquify = True
• Быстрый роутинг за счёт Radix Tree • Быстрый DI
но неудобный • Классы контроллеры РОУТИНГ
Контроллеры class PagesController(Controller): path = '/page' tags = ['Pages'] dependencies
= { 'task_service': Provide(provide_task_service), } @get('/tasks', name='Задачи') async def tasks_page( self, task_service: TaskService, ) -> Template: ...
Контроллеры class PagesController(Controller): path = '/page' tags = ['Pages'] dependencies
= { 'task_service': Provide(provide_task_service), } @get('/tasks', name='Задачи') async def tasks_page( self, task_service: TaskService, ) -> Template: ...
Контроллеры class PagesController(Controller): path = '/page' tags = ['Pages'] dependencies
= { 'task_service': Provide(provide_task_service), } @get('/tasks', name='Задачи') async def tasks_page( self, task_service: TaskService, ) -> Template: tasks = await task_service.list( models.Task.is_done == False, ) return HTMXTemplate( template_name='pages/tasks.html', context={'tasks': tasks}, )
DTO слой class BaseTask(BaseModel): name: str description: str | None
is_done: bool = False creation_date: datetime | None = Field( default_factory=lambda: datetime.now(tz=UTC) ) class IdMixin(BaseModel): id: UUID | None = None class TaskIn(BaseTask): pass class TaskOut(IdMixin, BaseTask): pass
DTO слой class Task(BaseModel): id: UUID | None = None
name: str description: str | None is_done: bool = False creation_date: datetime | None = Field( default_factory=lambda: datetime.now(tz=UTC) ) class TaskDTO(PydanticDTO[Task]): config = DTOConfig(exclude={'id'})
DTO слой @post( path='/api/task', dto=TaskDTO, dependencies={ 'task_service': Provide(provide_task_service), }, )
async def create_task( task_service: TaskService, data: Annotated[ schemas.Task, Body( media_type=RequestEncodingType.URL_ENCODED ), ], ) -> None: await task_service.create( data=data, auto_commit=True, )
DTO слой @post( path='/api/task', dto=TaskDTO, dependencies={ 'task_service': Provide(provide_task_service), }, )
async def create_task( task_service: TaskService, data: Annotated[ schemas.Task, Body( media_type=RequestEncodingType.URL_ENCODED ), ], ) -> None: await task_service.create( data=data, auto_commit=True, )
• Шаблонизаторы с поддержкой HTMX • Есть встроенное Observability •
По тестам TechEmpower быстрее FastAPI • Умеет в каналы • Поддерживается сообществом ЧТО ЕЩЁ ЕСТЬ
ГРУСТНЫЙ СЛАЙД
</>htmx Настоящий hypermedia
Бустинг HTML <div hx-boost="true"> <a href="/page1">Go To Page 1</a> <a
href="/page2">Go To Page 2</a> </div>
Бустинг HTML <form action="/task" method="post" hx-boost="true"> <input type="text" name="task" />
<button>Post Your Task</button> </form>
Больше методов <form action="/task/{{ task.id }}/delete" method="post"> <button>Delete Task</button> </form>
Больше методов <button hx-delete="/task/{{ task.id }}"> Delete Task </button>
Выбор цели <button hx-delete="/task/{{ task.id }}" hx-target="#task-{{ task.id }}" >
Delete Task </button>
Способ замены <button hx-delete="/task/{{ task.id }}" hx-target="#task-{{ task.id }}" hx-swap="outerHTML"
> Delete Task </button>
Способ замены <button hx-post="/task" hx-target="#tasks-list" hx-swap="beforeend" > Save Task </button>
• Хорошая событийная система • Подтверждения <button hx-delete="/task" hx-confirm="Delete all?">Delete<button>
• Индикаторы запросов • Тюнинг заголовками ответов litestar_htmx.HTMXTemplate() • Декларативное описание в самих HTML шаблонах ЧТО ЕЩЁ ЕСТЬ
Сделать красиво
Варианты утилит // Псевдо классы hover:bg-fuchsia-600 focus:text-red-500 invalid:border-pink-500 // Псевдо
элементы after:ml-0.5 before:-inset-1 backdrop:bg-gray-50 // Медиа запросы <div class="grid grid-cols-3 md:grid-cols-4"> <!-- ... --> </div>
Варианты утилит // Контейнерные запросы <div class="@container"> <div class="flex flex-col
@md:flex-row"> <!-- ... --> </div> </div> // Тёмная тема dark:bg-black
Динамические утилиты @import "tailwindcss"; @theme { --font-display: "Satoshi", "sans-serif"; --breakpoint-3xl:
1920px; --color-avocado-200: oklch(0.98 0.04 113.22); --color-avocado-300: oklch(0.94 0.11 115.03); --color-avocado-400: oklch(0.92 0.19 114.08); --color-avocado-500: oklch(0.84 0.18 117.33); } <div class="text-avocado-500 bg-avocado-300"> Avocado Shop </div>
@apply для шаблонов @layer components { .btn { @apply bg-action
cursor-pointer rounded-xl p-2 text-base text-white uppercase shadow-xs shadow-black duration-300; } .btn-flat { @apply text-action cursor-pointer rounded-xl bg-transparent p-2 text-base uppercase duration-300; } }
///_hyperscript Как JQuery, только нормальный
<button _="on click increment :x then put the result into
the next <output/>" > Click Me </button> <output>--</output> Простой синтаксис
Сахар для читаемости <button _="on click increment :x then put
it into the next <output/>" > Click Me </button> <output>--</output>
Сахар для читаемости <button _="on click increment :x then put
the result into the next <output/>" > Click Me </button> <output>--</output>
Декларативный подход document.querySelector('#example-btn') .addEventListener('click', e => { document .querySelectorAll(".elements-to-remove") .forEach(value
=> value.remove()); }) on click from #example-btn remove .elements-to-remove
• Небольшой фреймворк с актуальными батарейками - Litestar • Динамичный
UI с шаблонами – HTMX • Если вам нужна хорошая кастомизация – TailwindCSS • Немного скриптов для большей динамики - Hyperscript ВЫВОДЫ
КОНТАКТЫ