Skip to main content

netkit (s-netkit)

Сетевой слой китов. Всё, чем интеграция разговаривает с чужим сервером: транспорт, темп, живучесть, деградация. Ставится и работает без оркестратора — зависимости идут строго вниз: netkit → corekit.

     clikit        adapterkit        <- ветки-оболочки
          \           /
           librarykit                <- ОРКЕСТРАТОР (сессии, браузер, склад)
               |
            netkit                   <- СЕТЬ (этот пакет)
               |
            corekit                  <- ОСНОВАНИЕ (значения и правила)

Что внутри

модуль ответственность
netkit.transport исполнители запроса поверх httpx (async + sync близнец), choke-point HttpClient/SyncHttpClient, RestHttpClient к своему backend, permissive-рецепты
netkit.outfit НАРЯД: единственная дверь, где персона и род запроса превращаются в то, что нужно проводу — заголовки, цель подражания, версия протокола
netkit.outfit_guard страж этой двери: исполнитель, собравший заголовки сам, ловится проверкой, а не ревью
netkit.fingerprint из чего наряд собран: версия браузера, платформа (персона → машина), User-Agent, client hints, Sec-Fetch по режиму запроса, кодировки ответа, порядок заголовков Chrome
netkit.stream persistent-каналы: StreamTransport, WS-реализация (extra [ws])
netkit.rpc codec-слой RPC: JsonCodec / PrefixedJsonCodec / RpcClient
netkit.graphql GraphQL-клиент поверх транспорта кита
netkit.limit RateLimiter + token-bucket: ПРОАКТИВНЫЙ темп, а не «поймал 429 — поспал»
netkit.retry политики повторов: header-driven (RetryPolicy) и фиксированная (SimpleRetryPolicy)
netkit.ladder лестница деградации: чем выполнять запросы и чем добывать состояние, память ступени, события спуска
netkit.pagination / netkit.upload / netkit.forms листание ресурса, resumable-догрузка, form-urlencoded кодек
netkit.errmap ответ сервера → доменная ошибка (декларативная таблица)
netkit.declare объявление транспорта (http/ws/rpc/graphql) с ЕДИНЫМ поведением лимитов и повторов
netkit.providers СЛОТЫ верхнего слоя: браузерный минт, склад состояния, диагностика, egress

Объявить транспорт декларативно

Один и тот же лимит и одна и та же политика повторов — на любом виде транспорта. Интеграция объявляет, а не пишет обвязку:

from netkit.declare import KIND_HTTP, KIND_WS, TransportSpec, declare
from netkit.limit import LimitPolicy, LimitScope
from netkit.retry import SimpleRetryPolicy

limit = LimitPolicy(rate=5, per=1.0)          # 5 обменов в секунду
retry = SimpleRetryPolicy(backoff=(0.0, 0.0))  # 2 повтора без пауз
scope = LimitScope(service="acme")

api = declare(TransportSpec(kind=KIND_HTTP, url="https://api.acme.io",
                            limit=limit, scope=scope, retry=retry))
live = declare(TransportSpec(kind=KIND_WS, channel=my_ws_channel,
                            limit=limit, scope=scope, retry=retry))

await api.call("GET", "/v1/items")   # ждёт квоту, повторяет 429/5xx и сбои
await live.call('{"op":"ping"}')     # ТОТ ЖЕ темп и ТЕ ЖЕ повторы — без своего кода

Свой вид (grpc, sse, …) добавляется register_kind(kind, builder) и сразу получает то же поведение.

Наряд: «чем мы выглядим» — в одном месте

Персона (corekit.persona.ClientPersona) описывает ОДНОГО посетителя: браузер, платформу, язык, часы, рукопожатие, выход. Превращается она в то, что нужно проводу, ровно один раз — дверью outfit_for, а исполнители получают ГОТОВОЕ:

from netkit.outfit import outfit_for

outfit = outfit_for(persona=persona, mode="page-request")
outfit.headers       # заголовки целиком, уже в браузерном ПОРЯДКЕ
outfit.http2         # версия протокола (персона о ней знает)
outfit.tls_target    # цель подражания — нужна только curl_cffi (считается лениво)

На практике этого не пишут вовсе: наряд собирают сами транспорты, choke-point'ы и permissive-рецепты — достаточно передать им persona=:

transport = HttpxTransport(persona=persona)                    # прямая ступень
transport = CurlCffiTransport(persona=persona)                 # ступень подражания
client = build_permissive_http_client(cookies=..., persona=persona)

Почему одна дверь. Пока сборка была скопирована по местам, персона доезжала до одних исполнителей и молча терялась у других — а видно это становилось не ошибкой, а отзывом сессии через несколько часов. Что дверь действительно одна, держит netkit.outfit_guard: он метит наряд на самой двери и ловит исполнителя, чей ушедший набор метки не несёт.

Платформа: персона → машина → названная константа

Персоны нет — платформа не берётся из константы, а выводится по машине (как и версия браузера). До этого она была зашита словом «Windows», и на Linux-ноде клиент заявлял Windows, показывая настоящую машину всем остальным.

from netkit.fingerprint import declared_platform

declared_platform().describe()          # 'Linux — x86/64 (откуда: машина)'
declared_platform(persona).describe()   # 'macOS 14.5 arm/64 (откуда: персона)'

Персона, называющая macOS с Linux-ноды, не врёт: заявленная платформа — это устройство аккаунта, а не нода, на которой крутится процесс, и узнавание устройства держится именно за неё. Лечится другое — платформа, которую никто не выбирал; поэтому у неё есть origin, и его печатают наряд (Outfit.platform) и проба (python -m netkit.fingerprint_probe). Написание платформы, её кусок в User-Agent и версия лежат ОДНОЙ строкой таблицы PLATFORMS — новая платформа добавляется строкой, а не ветвлением.

Кодировки: заявляем ровно то, что разожмём

Chrome шлёт Accept-Encoding: gzip, deflate, br, zstd — и кит шлёт то же самое. Списать эту строку дословно было нельзя: сервер верит и присылает br, а клиент без декодера пропускает сжатое тело наверх молча, и падает потом разбор JSON где-то в навыке. Поэтому строка собирается из двух фактов — что заявляет браузер и что умеет распаковать ТОТ, КТО понесёт запрос:

from netkit.fingerprint import DECODER_CURL, accept_encoding

accept_encoding()               # 'gzip, deflate, br, zstd' — распакует httpx
accept_encoding(DECODER_CURL)   # то же, но умения спрошены у сборки curl

Декодеры (brotli, zstandard) приезжают ОСНОВНОЙ зависимостью — через extra самого httpx, чтобы вилки версий объявлял он, а не мы. В окружении, где их всё-таки нет (--no-deps, замороженный requirements), набор сужается сам — заявляем меньше, а не врём больше. У ступени подражания распаковщик свой (кодеки, вкомпилированные в curl-impersonate), поэтому её строка не зависит от питоновских вендоров вовсе — и наоборот.

Шкала ступеней: по стоимости, а не по «продвинутости»

  1. прямой запрос (httpx);
  2. прямой запрос с имитацией браузера (curl_cffi: подделка рукопожатия) — цель работы в том, чтобы оставаться на этих двух: ни окна, ни процесса браузера здесь нет;
  3. настоящий браузер БЕЗ окна (headless): основной движок nodriver, camoufox — запасной НА ТОЙ ЖЕ ступени, когда nodriver палится;
  4. настоящий браузер С ВИДИМЫМ ОКНОМ — только вход человека; лестница сюда не спускается вовсе.

Третья и четвёртая различаются ВИДИМОСТЬЮ ОКНА, а не стелсом. Шкала объявлена данными (netkit.ladder.LADDER_RUNGS), поэтому новая возможность обязана сказать, что при ней происходит и сколько это стоит.

Слоты: как netkit зовёт то, что живёт выше

Последняя ступень лестницы поднимает браузер, а браузер — чужой кит, который сам зависит от сети. Прямой импорт дал бы цикл, поэтому направление разворачивается: netkit объявляет слот, верхний слой заполняет его на своём импорте.

from netkit.providers import SLOT_BROWSER_MINT, register_provider
register_provider(SLOT_BROWSER_MINT, my_mint_session)

Слоты со своим дефолтом (json_store, file_lock, state_root, path_slug, transport_factory, diagnose, egress_proxy) никогда не роняют вызов — netkit умеет их сам, верхний слой лишь уточняет. Слоты без дефолта (браузерные) при обращении поднимают ProviderMissing с инструкцией: молчаливой деградации «ступень тихо ничего не сделала» здесь нет.

Если в окружении стоит librarykit, его импорт заполняет все слоты сам — отдельная регистрация не нужна.

Совместимость

librarykit остаётся фасадом: librarykit.transport, librarykit.ladder, librarykit.limit, librarykit.retry, librarykit.pagination, librarykit.upload, librarykit.forms, librarykit.rpc, librarykit.stream, librarykit.graphql, librarykit.errmap реэкспортируют ТЕ ЖЕ объекты (не копии) — isinstance / except / is работают через любой из путей.

Стоимость импорта

import netkit не исполняет ни одного подмодуля: ни httpx, ни stamina, ни asyncio. Имена резолвятся по PEP 562 при первом обращении — платит тот, кому нужно.

Установка

pip install s-netkit          # ядро: corekit + httpx + stamina
pip install s-netkit[ws]      # + websockets для WS-транспорта

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

s_netkit-0.0.17.tar.gz (350.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

s_netkit-0.0.17-py3-none-any.whl (224.8 kB view details)

Uploaded Python 3

File details

Details for the file s_netkit-0.0.17.tar.gz.

File metadata

  • Download URL: s_netkit-0.0.17.tar.gz
  • Upload date:
  • Size: 350.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for s_netkit-0.0.17.tar.gz
Algorithm Hash digest
SHA256 d3fe7b5ae989d59c113358e0895980f377d1c82fe5cc777b0f9ca010719375ce
MD5 5ef8fa8f06172c7f2617af298685e6e3
BLAKE2b-256 86870bf8a0c2c5941f01474797a7d300534b8d008e6e48ac463e2fa96a337455

See more details on using hashes here.

File details

Details for the file s_netkit-0.0.17-py3-none-any.whl.

File metadata

  • Download URL: s_netkit-0.0.17-py3-none-any.whl
  • Upload date:
  • Size: 224.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for s_netkit-0.0.17-py3-none-any.whl
Algorithm Hash digest
SHA256 e6e5d89a23a1f3ad48985aedc3d192056b9e850de9cb1a2910770b44fc906e55
MD5 3f60c71c0d77043b3d6838969b085bc5
BLAKE2b-256 f39da5f7f39f6ae247ce228654ec4b8e582483bebcf865d1ad1243223edc6f07

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

0.0.22

2 files

0.0.21

2 files

0.0.20

2 files

0.0.19

2 files

0.0.18

2 files

This release

0.0.17 This release

2 files

0.0.16

2 files

0.0.15

2 files

0.0.11

2 files

0.0.10

2 files

0.0.9

2 files

0.0.8

2 files

0.0.7

2 files

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page