--- title: Миграция между альфами description: Таблицы «было → стало» для альфа-переходов и для 0.2.0a5 → 0.3.0. freshness: false --- Eazy SDK в альфе переименовывает без deprecated-алиасов: старое имя исчезает, `ImportError` или `AttributeError` показывает, что именно нужно поменять. Эта страница служит единственной подсказкой. Каждая строка таблицы проверяется тестом: «стало» импортируется, «было» отсутствует. ## Один пакет вместо десяти (0.2.0a8) Ядро и все интеграции поставляются одним пакетом `eazy-sdk-core`. Имена импорта не изменились: `eazy_sdk`, `eazy_sdk_html`, `eazy_sdk_browser` и остальные остаются прежними. Меняется только команда установки. | Было | Стало | |---|---| | `eazy-sdk[httpx,pydantic]` | `eazy-sdk-core[httpx,pydantic]` | | `eazy-sdk-openapi[yaml]` | `eazy-sdk-core[openapi,yaml]` | | `eazy-sdk-asyncapi[yaml]` | `eazy-sdk-core[asyncapi,yaml]` | | `eazy-sdk-browser[playwright]` | `eazy-sdk-core[browser,playwright]` | | `eazy-sdk-sqlmodel` | `eazy-sdk-core[sqlmodel]` | | `eazy-sdk-presets` | `eazy-sdk-core[presets]` | | `eazy-sdk-adaptix` | `eazy-sdk-core[adaptix]` | | `eazy-sdk-xml` | `eazy-sdk-core[xml]` | Дополнение ставит сторонние библиотеки интеграции. Её код уже лежит в пакете, поэтому без дополнения импорт интеграции завершится `ImportError` с именем недостающей библиотеки. ## Куки сессии: один способ (0.2.0a11) Куку, которую поставил сайт, библиотека несёт сама: корень SDK объявляет `Cookies(...)`, а набор кук принадлежит `Identity`. Все прежние способы отправить такую куку удалены. | Было | Стало | |---|---| | `eazy_sdk.auth.session_cookie` | `eazy_sdk.auth.Cookies` | | `eazy_sdk.auth.Placed.cookie` | `eazy_sdk.auth.Cookies` | | `eazy_sdk.auth.Placed.cookies` | `eazy_sdk.auth.Cookies` | | `eazy_sdk.auth.cookies.parse_session_cookie` | `eazy_sdk.response.FromCookie` | | `eazy_sdk.auth.cookies.HttpCookieSession` | `eazy_sdk.cookies.StoredCookie` | | `eazy_sdk_browser.BrowserCookie` | `eazy_sdk.cookies.StoredCookie` | | `eazy_sdk_browser.browser_cookie_auth` | `eazy_sdk.cookies.CookieState` | | `eazy_sdk_browser.BrowserCookieBridge` | `eazy_sdk.cookies.CookieState` | | `eazy_sdk_browser.CookieAuthAdopter` | `eazy_sdk.cookies.CookieState` | Что поменять в коде: - **Сессия на куках.** Вместо `session_cookie("sid", ...)` объявите на корне `cookies = Cookies(required=("sid",))` и обычную `session_scheme(Model)`. Модель может не размещать ничего, если вся сессия живёт в куках. - **Поля для кук в шагах входа.** Удалите `FromCookie` в ответе и `Cookie[str]` в следующем запросе, если они только переносили куку сайта. `FromCookie` остаётся для значения, которое нужно как данные. - **Браузерная сессия для HTTP.** Вместо `browser_cookie_auth(state, scheme, "sid")` передайте `Identity(cookies=CookieState(state.cookies))`. - **Запись куки.** `StoredCookie.domain` пишется без ведущей точки. Куку для домена с поддоменами задаёт `host_only=False`; точка в домене приводит к `ValueError`. - **Своё хранилище сессий.** `SessionStore.save` получил необязательный аргумент `cookies`. Хранилищу токенной сессии менять нечего; хранилищу сайта с куками аргумент нужен. - **База `eazy_sdk_sqlmodel`.** Таблица сессий получила колонку. Для уже созданной базы выполните `ALTER TABLE sessions ADD COLUMN cookie_state JSON`. - **Сохранённые браузерные сессии** прежнего формата не читаются: по ним выполняется вход. ## Переход с 0.2.0a5 на 0.3.0 Корень SDK стал отдельным типом. `SyncApi` и `AsyncApi` являются роутерами: операции и атрибуты сервиса (`base_url`, `errors`, `security`, `signing`, `crypto`, `wire`, `signed`, `allow`). Композиция, владение клиентами и привязки живут в `SyncRoot`/`AsyncRoot`; `api_group(...)` объявляется только на корне. Подробности: {doc}`Мультисервисный SDK `. | Было | Стало | Комментарий | |---|---|---| | `eazy_sdk.SyncApi.from_handler` | `eazy_sdk.SyncRoot.from_handler` | Фабрика переехала на корень; она же создаёт и закрывает свой клиент. | | `eazy_sdk.AsyncApi.from_handler` | `eazy_sdk.AsyncRoot.from_handler` | То же для async. | | `eazy_sdk.SyncApi.from_client` | `eazy_sdk.SyncRoot` | Второго способа собрать корень нет: `ShopSdk(client)` вызывает обычный конструктор. | | `eazy_sdk.AsyncApi.from_client` | `eazy_sdk.AsyncRoot` | То же для async. | | `eazy_sdk.SyncApi.close` | `eazy_sdk.SyncRoot.close` | Роутер ничем не владеет; закрывает ресурсы корень. | | `eazy_sdk.AsyncApi.aclose` | `eazy_sdk.AsyncRoot.aclose` | То же для async. | | `eazy_sdk.ApiDefaults` | `eazy_sdk.SyncApi` | Дефолты роутера стали обычными атрибутами класса и наследуются по MRO. | Аутентификация уехала из транспорта в session scope. `Identity` объявляет auth-схемы, ключи подписи, DI и observer; он передаётся корню (`ShopSdk(client, identity=…)`) или отдельному роутеру (`UsersApi(client, identity=…)`). Один `Identity` обслуживает все сервисы корня как при общем клиенте, так и при отдельном клиенте у каждого сервиса. | Было | Стало | Комментарий | |---|---|---| | `eazy_sdk.ClientConfig.auth` | `eazy_sdk.Identity.auth` | Кортеж настроенных `Auth`; `session_auth(...)`/`SessionScheme.configure(...)` не изменились. | | `eazy_sdk.ClientConfig.key_provider` | `eazy_sdk.Identity.key_provider` | Алгоритм подписи входит в контракт API; ключ остаётся секретом вызывающей стороны. | | `eazy_sdk.ClientConfig.dependencies` | `eazy_sdk.Identity.dependencies` | DI зависит от окружения вызова, а не от пула соединений. | | `eazy_sdk.ClientConfig.observer` | `eazy_sdk.Identity.observer` | Наблюдатель следует за вызывающей стороной. | | `eazy_sdk.Client.bind_sdk` | `eazy_sdk.Identity` | Фабрику scoped SDK регистрирует тот, кому передан `identity=`. | Корень `eazy_sdk` экспортирует 39 имён вместо 55: то, что нужно автору SDK в первый час. Всё остальное импортируется из своего модуля. Это не сокрытие, а отказ от второго имени для одной вещи. | Было | Стало | Комментарий | |---|---|---| | `eazy_sdk.ModelAdapter` | `eazy_sdk.models.ModelAdapter` | Вместе с `ModelAdapterRegistry`, `ModelField`, `default_model_adapters` и ошибками адаптеров. | | `eazy_sdk.BodyCodec` | `eazy_sdk.codecs.BodyCodec` | Вместе с `ScalarCodec` и `DelimitedScalarCodec`. | | `eazy_sdk.PlanError` | `eazy_sdk.core.errors.PlanError` | Вместе с `BindingError`, `GraphError`, `PatchError`, `OperationBindingError`. | | `eazy_sdk.UnsafeReplayError` | `eazy_sdk.clients.UnsafeReplayError` | Вместе с `AttemptLimitError` и `RedirectLimitError`. | | `eazy_sdk.CapabilityMismatchError` | `eazy_sdk.handlers.CapabilityMismatchError` | | | `eazy_sdk.MiddlewareProtocolError` | `eazy_sdk.middleware.MiddlewareProtocolError` | | | `eazy_sdk.PreparationIncompleteError` | `eazy_sdk.preparation.PreparationIncompleteError` | Вместе с `PreparedValue`. | | `eazy_sdk.ResponseExtractor` | `eazy_sdk.response.ResponseExtractor` | Вместе с `AmbiguousResponseError` и `MalformedResponseError`. | | `eazy_sdk.core.ports.CryptoProfile` | `eazy_sdk.compile.http_compiler.CryptoProfile` | Порт переехал к единственному читателю; слоя ради разрыва импорта больше нет. | Синхронный клиент больше не создаёт event loop: тот же конвейер шагается на вызывающем потоке. `Client` теперь можно вызывать из кода, где loop уже работает; взамен колбэк, который сам уходит в сеть через `await`, поднимает `SynchronousSuspensionError` вместо того, чтобы молча крутиться в приватном loop. | Было | Стало | Комментарий | |---|---|---| | `eazy_sdk.clients.EventLoopConflictError` | `eazy_sdk.clients.SynchronousSuspensionError` | Ошибка сменила причину: не «loop уже работает», а «этот колбэк действительно приостанавливается». | `ClientConfig` перестал быть плоским списком из пятнадцати полей: остались три группы по владельцу плюс host-scoped `crypto` (его судьба решается в фазе 48). `guards=` понижается фабрикой `Security.of(...)`, а не мутацией frozen-объекта в `__post_init__`. | Было | Стало | Комментарий | |---|---|---| | `eazy_sdk.ClientConfig.retry` | `eazy_sdk.Resilience.retry` | Вместе с `auth_retries`, `max_redirects`, `timeout` и `rate_limiter`. | | `eazy_sdk.ClientConfig.timeout` | `eazy_sdk.Resilience.timeout` | Валидация бюджетов переехала в группу. | | `eazy_sdk.ClientConfig.rate_limiter` | `eazy_sdk.Resilience.rate_limiter` | | | `eazy_sdk.ClientConfig.protection` | `eazy_sdk.Security.protection` | Пустой `ProtectionBundle` вместо `None`; `ClientConfig.bundle` не изменился. | | `eazy_sdk.ClientConfig.guards` | `eazy_sdk.Security.of` | Публичного поля, которое всегда пусто, больше нет. | | `eazy_sdk.ClientConfig.middleware` | `eazy_sdk.Hooks.middleware` | | | `eazy_sdk.ClientConfig.models` | `eazy_sdk.Serialization.models` | Библиотека моделей относится к реализации SDK и объявляется на корне. | | `eazy_sdk.ClientConfig.profile` | `eazy_sdk.request.Wire` | `WireProfile` был наполовину мёртв: фаза 48 заменила его контрактом представления на операции. | Слово «wire» теперь означает ровно одно понятие: представление запроса на проводе. Всё, что влияет на байты, объявляется одним `Wire` на операции (и наследуется от роутера и миксина сервиса); всё, что не влияет, задаётся одним `Serialization` на корне. Подпись указывается только по месту: адресных правил `sign(scope=…)` больше нет, потому что опечатка в `hosts=` оставляла запрос молча неподписанным. Подробности: {doc}`Представление запроса `. | Было | Стало | Комментарий | |---|---|---| | `eazy_sdk.request.WireOptions` | `eazy_sdk.request.Wire` | `order=FieldOrder(query=…, header=…, cookie=…, body=…)`, `exact=`, `transport=`. | | `eazy_sdk.request.SigningRule` | `eazy_sdk.request.hmac_sha256` | Подпись объявляет операция или роутер параметром `signing=`; scoped-правил нет. | | `eazy_sdk.request.SigningOverride` | `eazy_sdk.SyncApi` | Переопределение задаётся объявлением на более специфичном роутере. | | `eazy_sdk.crypto.CryptoWire` | `eazy_sdk.crypto.Encrypted` | Тип шифрованного представления; у HTTP-операции это `wire=Wire(encrypted=…)`. | | `eazy_sdk.crypto.CryptoRule.wire` | `eazy_sdk.crypto.CryptoRule.encrypted` | Одно имя используется для одного понятия и в HTTP, и в WebSocket; `ws.call(encrypted=…)` использует его же. | Новое без замены: `eazy_sdk.serialization.Serialization(json=…, documents=(…,))` задаёт JSON-бэкенд и парсер документов объявляются на корне, а бэкенд, не способный выполнить контракт операции, отвергается при компиляции, а не молча меняет байты. Понятие конверта уехало из транспорта: `Envelope`, `ProtocolMessage`, `CorrelationKey`, `ChannelKey`, `ControlKind` и `InboundMessageKind` живут в `eazy_sdk.protocols` и описывают протокол прикладного уровня, а не WebSocket. `WsProtocol` объединяет `Envelope` с тем, что есть только у соединения. Подробности: {doc}`Протоколы и конверты `. | Было | Стало | Комментарий | |---|---|---| | `eazy_sdk.websocket.CorrelationKey` | `eazy_sdk.protocols.CorrelationKey` | Вместе с `ChannelKey`. | | `eazy_sdk.websocket.ProtocolMessage` | `eazy_sdk.protocols.ProtocolMessage` | Вместе с `ControlKind` и `InboundMessageKind`. | Новое без замены: `eazy_sdk.protocols.JsonRpc` и `@api.rpc("method")` позволяют описать сервис, у которого имя метода лежит в теле, так же, как REST; `rpc_result`, `rpc_error(code, model)` и `rpc_error_default(model)` строят обычные `Success`/`Error`, второй модели ошибок не появляется. Реализация `WsProtocol` получает метод `read(envelope)`, куда переезжает прежняя логика `inspect(frame)` после декодирования кадра. Новое без замены: `eazy_sdk.bind` и `eazy_sdk.Binding` задают привязку роутера или его миксина к другому клиенту и/или адресу при сборке (`bindings=(...)`). Операция стала классом. Поля класса являются полями запроса, а `__http__` содержит всё, что операция объявляет, а `op(...)` публикует её на роутере. Декоратор остался короткой формой того же самого: он синтезирует такой же frozen-класс. Причина заключается не в стиле: у `TypedDict` нет значений по умолчанию и нет `default_factory`, а у класса есть, и сам запрос становится значением, которое можно построить, изменить и отправить. Подробности: {doc}`Запросы ` и {doc}`Запрос как значение `. | Было | Стало | Причина | |---|---|---| | `def op(self, **request: Unpack[Req]) -> T` | класс `Op(HttpOperation[T])` вместе с `op(Op)` | defaults и `default_factory`, запрос как значение | | `responses=Responses(…)` | `success=…`, `errors=…`, `fallback=…` | один способ вместо трёх | | `response=Json()` | return-аннотация операции либо `success={200: Model}` | семья выводится из модели | | `Annotated[int, Query()]` | `Query[int]` или `Annotated[int, markers.Query("name")]` | короткая форма | | `eazy_sdk.Path` (дата-класс) | `eazy_sdk.request.markers.Path` | корневые имена стали алиасами | | `eazy_sdk.request.Query` (дата-класс) | `eazy_sdk.request.markers.Query` | то же для остальных маркеров | | `Inject(JsonField("x"), dep)` | `Inject(markers.JsonField("x"), dep)` | то же | | `Wire(projection=…)` | `projection=BodyProjection(target=…, using=…, encoding=…)` в `__http__` | проекция описывает представление, а не байтовый контракт | | `BodyProjection(source=TypedDict, ...)` | `source` опускается, поскольку источником служит класс операции | одно объявление полей | | `eazy_sdk.Responses` | `eazy_sdk.response.Responses` | бюджет корня; вместе с `Success` и `Error` | | `eazy_sdk.Text` | `eazy_sdk.response.Text` | вместе с `Bytes` | | `Error(404, Json(Problem), exception=NotFound)` | `errors={404: NotFound}` при `class NotFound(ApiError[Model])` | модель объявлена один раз | | `Error(..., exception=factory)` | `errors={404: (Model, factory)}` | без наследования от библиотеки | | `eazy_sdk.response.JsonResponse` | `eazy_sdk.response.Json` | одно имя для одного понятия | | `eazy_sdk.serialization.Serialization.html` | `eazy_sdk.serialization.Serialization.documents` | несколько документных бэкендов, выбор по медиа | | `eazy_sdk.websocket.WsOperationKind` | `eazy_sdk.websocket._messages.WsOperationKind` | плумбинг рантайма ушёл из публичной поверхности | | `eazy_sdk.websocket.RuntimeComposition` | `eazy_sdk.websocket.composition.RuntimeComposition` | то же | | `eazy_sdk.websocket.FrameLimits` | `eazy_sdk.websocket.frames.FrameLimits` | то же, вместе с `frame_from_zapros`/`frame_to_zapros` | | `eazy_sdk.response.HtmlExtractor` | `eazy_sdk.response.cases.HtmlExtractor` | вместе с `JsonExtractor` | | `eazy_sdk.request.TransportProtocol` | `eazy_sdk.request.wire.TransportProtocol` | читает компилятор, не автор SDK | | `eazy_sdk.crypto.ResolvedCrypto` | `eazy_sdk.crypto.core.ResolvedCrypto` | результат компиляции, не объявление | | `@ws.call("event") async def f(self, *, ...)` | по-прежнему работает; доступен класс `F(WsCall[T])` вместе с `op(F)` | классы и для WebSocket | | `ApiError.context` после pickle | `ApiError.summary` (`ErrorSummary`) | сводка вместо тела ответа и заголовков | Новое без замены: `Omittable[T] = UNSET` служит единственным способом не отправить поле; `unwrap = "/data"` на сервисе снимает конверт один раз; `ClientConfig(errors={host: {...}})` задаёт ошибки, которыми хост отвечает любому SDK; `RpcOperation`/`Rpc` в `eazy_sdk.protocols` и `WsCall`/`WsSubscribe`/ `WsSend`/`Ws` в `eazy_sdk.websocket` предоставляют те же классы для других протоколов; `eazy-sdk-adaptix` предоставляет центральный `Retort` как адаптер моделей. ## Переход с 0.2.0a4 на 0.2.0a5 | Было | Стало | Комментарий | |---|---|---| | `eazy_sdk.protection.advanced.ChallengeMalformedError` | `eazy_sdk.protection.advanced.ChallengeParseError` | Рантайм-ошибка «челлендж распознан, но не разобран». Детекторная `MalformedChallengeError` не изменилась. | | `eazy_sdk.handlers.EmitOptions.proxy` | `eazy_sdk.handlers.HandlerProfile.proxy` | Proxy объявляет handler (`Client.httpx(proxy=...)` и др.), а не per-call опции. Поля `verify_tls`/`stream_response` удалены: их никто не читал. | | `eazy_sdk.codegen.session_auth` | `eazy_sdk.codegen.generated_session_auth` | Codegen-хелпер получил своё имя; `eazy_sdk.auth.session_auth` является другой authoring-функцией. | | `eazy_sdk.codegen.session_scheme` | `eazy_sdk.codegen.generated_session_scheme` | То же для схемы. Сгенерированные модули нужно перегенерировать. | | extra `eazy-sdk-html[parsel]` | зависимость `eazy-sdk-html` | `parsel` стал обязательной зависимостью плагина; `pip install "eazy-sdk-core[html]"` достаточно. | Новые параметры без брейков: `proxy=` у `Client.httpx/requests/curl_cffi` и `AsyncClient.httpx/curl_cffi`, `SolveContext.remaining()`. ## Переход с 0.2.0a3 на 0.2.0a4 ### Фасад и клиенты | Было | Стало | Комментарий | |---|---|---| | `eazy_sdk.SyncSdk` | `eazy_sdk.SyncApi` | Корневой фасад слит с API-классом: `api_group`, `from_client`, `from_handler`. | | `eazy_sdk.AsyncSdk` | `eazy_sdk.AsyncApi` | То же для async. | | `eazy_sdk.Client.get` | `eazy_sdk.Client.request` | Сырые verb-методы (`get/post/...`) удалены; остался `request(method, url, ...)`. | | `eazy_sdk.AsyncClient.post` | `eazy_sdk.AsyncClient.request` | То же для async. | | `eazy_sdk.ClientConfig.challenge_policies` | `eazy_sdk.Security.of` | Guard'ы и presets ставятся через `security=Security.of(...)` (в 0.2.0a4 это было `guards=[...]`). | | `eazy_sdk.ClientConfig.challenge_solvers` | `eazy_sdk.Security.protection` | Advanced-политики и solver-биндинги входят в одну группу `ProtectionBundle`. | | `eazy_sdk.clients.AttemptLimitExceeded` | `eazy_sdk.clients.AttemptLimitError` | Единый суффикс `*Error`. | | `eazy_sdk.clients.RedirectLimitExceeded` | `eazy_sdk.clients.RedirectLimitError` | | | `eazy_sdk.handlers.TransportFailure` | `eazy_sdk.handlers.TransportError` | | | `eazy_sdk.exceptions.EazySDKError` | `eazy_sdk.exceptions.EazySdkContextError` | Базой всех исключений служит `eazy_sdk.core.errors.EazySdkError`. | ### Protection | Было | Стало | Комментарий | |---|---|---| | `eazy_sdk.protection.advanced.ChallengeSolverBindings` | `eazy_sdk.protection.advanced.SolverBindings` | Один реестр биндингов. | | `eazy_sdk.protection.advanced.ProtectionSolver` | `eazy_sdk.protection.advanced.ChallengeSolver` | Один протокол солвера. | | `eazy_sdk.protection.advanced.SolutionCookieSet` | `eazy_sdk.protection.advanced.PrivateCookieSetBinding` | Фабрика `solution_cookie_set()` осталась. | Простой путь теперь использует класс `Guard` из `eazy_sdk.protection` и `Security.of(...)`. ### Вынесенные пакеты | Было | Стало | Комментарий | |---|---|---| | `eazy_sdk.extraction.CSS` | `eazy_sdk_html.CSS` | Плагин `eazy-sdk-html` (`pip install "eazy-sdk-core[html]"`). | | `eazy_sdk.extraction.XPath` | `eazy_sdk_html.XPath` | | | `eazy_sdk.extraction.Scope` | `eazy_sdk_html.Scope` | | | `eazy_sdk.accounts.storage.MemoryStorage` | `eazy_sdk_accounts.storage.MemoryStorage` | Плагин `eazy-sdk-accounts` (`pip install "eazy-sdk-core[accounts]"`). | | `eazy_sdk.accounts.registration` | `eazy_sdk_accounts.registration` | | | `eazy_sdk._internal` | `eazy_sdk.core` | Ядро (`kernel`, `errors`, `http`, `http_plan`, `ports`) и компилятор `eazy_sdk.compile`. | Storage-хуки переименованы из middleware в observers: `StorageObserver`, `LoggingObserver`, `EventRecordingObserver`, параметр `observers=`.