Потоковая отправка HTTP
Для отправки файла передайте открытый бинарный файл в upload. DuckoNet прочитает его небольшими порциями; собирать тело целиком или создавать специальный объект не нужно.
import os
from duckonet import HttpClient
with HttpClient(api_key=os.environ['DUCKONET_API_KEY'], browser='chrome', version='last') as client:
with client.run():
with open('report.zip', 'rb') as source:
response = client.post('https://api.example.com/upload', upload=source,
timeout_seconds=120)
response.raise_for_status()
Замените URL адресом своего сервера. Файлом владеет вызывающий код: библиотека не закрывает его. Чтение начинается с текущей позиции. После завершения позиция соответствует прочитанной части файла, в том числе при раннем отказе сервера.
Параметры запроса
Эти параметры принимают HttpClient и AsyncHttpClient в методах request, post, put, patch и других HTTP-методах, а также в stream.
| Параметр | Назначение |
|---|---|
upload | Бинарный файл с read(n) или iterable значений bytes. Async-клиент также принимает async iterable. Строки и текстовые файлы не подходят. |
upload_length=None | Точное число оставшихся байт сырого тела. Без явного значения остаток обычного файла или BytesIO определяется по метаданным, когда это возможно; иначе длина неизвестна. Параметр не обрезает источник: короткое или лишнее тело считается ошибкой. |
upload_limit_bytes=1024**3 | Предел потокового тела, по умолчанию 1 GiB. Положительный int до 2**63 - 1; задаётся в клиенте или запросе. Ограничивает и заранее созданный UploadBody. |
stream_upload=False | Значение True включает ленивый multipart вместе с files. Для сырого тела используйте upload. |
upload нельзя сочетать с body, data, json или files. Для multipart допускаются data и files вместе с stream_upload=True. Обычные буферизованные запросы сохраняют предел 16 MiB; лимит ответа задаётся отдельно через body_limit.
Очередь отправки ограничена 256 KiB, порции — 64 KiB. Библиотека не создаёт скрытую полную копию файла или временный архив. Уже созданный пользователем большой bytes-объект остаётся памятью источника.
Генераторы и async
В коротких фрагментах ниже client — уже открытый HttpClient. Полные запускаемые примеры приведены в конце страницы.
def parts():
yield b'first part\n'
yield b'second part\n'
response = client.post('https://api.example.com/upload', upload=parts())
response.raise_for_status()
Пустая порция итератора не означает конец тела. Тело завершается при окончании итератора; бинарный read(n) сообщает EOF значением b''.
import asyncio
import os
from duckonet import AsyncHttpClient
async def parts():
yield b'first part\n'
await asyncio.sleep(0)
yield b'second part\n'
async def main():
async with AsyncHttpClient(api_key=os.environ['DUCKONET_API_KEY'], browser='chrome', version='last') as client:
async with client.run():
response = await client.post('https://api.example.com/upload', upload=parts())
response.raise_for_status()
asyncio.run(main())
Async-источник выполняется на своём event loop. Обычный блокирующий файл читается вне него. Пользовательский read() или генератор должен возвращать управление: произвольный Python-код нельзя принудительно остановить. Отмена прекращает сеть и новые чтения; уже начатое непрерываемое чтение может закончиться позднее. До этого библиотека сохраняет необходимые ссылки и запрещает одновременное использование того же источника.
Multipart
with open('report.zip', 'rb') as source:
response = client.post('https://api.example.com/upload',
data={'title': 'Report'},
files={'archive': ('report.zip', source, 'application/zip')},
stream_upload=True, timeout_seconds=120)
response.raise_for_status()
Без stream_upload=True параметр files остаётся буферизованным. В потоковом multipart предел включает границы и заголовки частей. Суммарный размер обычных полей ограничен 16 MiB, описаний частей — 1 MiB. upload_length для multipart не задаётся: длина вычисляется из метаданных всех частей либо остаётся неизвестной. Content-Length, Transfer-Encoding, Host и pseudoheaders устанавливает транспорт.
Перенаправления и повторяемое тело
Файл с поддерживаемой начальной позицией можно воспроизвести при разрешённом redirect. На 307/308 он перематывается к сохранённой позиции. Обычный одноразовый итератор повторно не отправляется; возникает HttpRedirectError с code и response. Переход POST на GET при 301/302 и переход по 303 убирают тело по правилам клиента.
Если нужен собственный повторяемый источник, передайте фабрику:
from duckonet import UploadBody
body = UploadBody.from_factory(lambda: iter((b'first', b'second')), length=11)
response = client.post('https://api.example.com/upload', upload=body)
Полные сигнатуры: UploadBody(source, *, length=None, limit=1024**3, factory=None, async_factory=False) и UploadBody.from_factory(factory, *, length=None, limit=1024**3, async_source=False). Фабрика используется с source=None; обычно удобнее from_factory. Она вызывается после допуска запроса и должна возвращать подходящий источник. Для async-фабрики укажите async_source=True и используйте async-клиент.
replayable показывает возможность повтора; rewind() готовит источник к нему. Методы iter_chunks(*, deadline=None, cancelled=None) и aiter_chunks(*, deadline=None, cancelled=None) предназначены для собственного producer bridge. deadline — абсолютное значение time.monotonic(), cancelled — вызываемая без аргументов проверка отмены. Не читайте подготовленный источник вручную одновременно с запросом.
UploadError содержит code и bytes_consumed. Коды включают HTTP_UPLOAD_CANCELLED, HTTP_UPLOAD_SOURCE_BUSY, HTTP_UPLOAD_NOT_REPLAYABLE, HTTP_UPLOAD_LENGTH_EXCEEDED, HTTP_UPLOAD_EARLY_EOF, HTTP_TOTAL_TIMEOUT. Исходное исключение пользовательского источника сохраняется. Ошибка может возникнуть после передачи части тела; отсутствие ответа и нулевой счётчик не разрешают автоматически повторить неидемпотентную операцию.
Профиль на время прогона
При долгоживущем клиенте с version='last' оборачивайте каждый полный прогон скрипта в with client.run() или async with client.run(). Готовое обновление используется следующим прогоном. Активный upload, его редиректы и остальные запросы текущего прогона сохраняют свой профиль. Cookies принадлежат логическому клиенту и продолжают работать после переключения. Без явной границы прогона клиент сохраняет исходный профиль.
Адреса
В host URL допустимы Unicode-домен, его A-label и IPv6 в скобках: https://faß.test/, https://xn--fa-hia.test/, https://[::1]:8443/. UTS46 nontransitional сохраняет различие ß и ss. Каноническое имя используется для TLS, cookies, origins, прокси и redirect; исходные заголовки ответа сохраняют свои байты. Поддержка Unicode path/query этим не добавляется.
Zone identifiers, IPvFuture, завершающая точка домена и невалидные A-label отклоняются. Сертификат домена проверяется по DNS identity, IPv6 literal — по IP SAN; для IP literal DNS-SNI не отправляется. IPv4 и mapped IPv6 не объединяются в одну cookie-область. Для IPv6 cookies остаются host-only. Экспорт и импорт сохраняют схему и метаданные cookie jar.
Ограничения времени
with HttpClient(api_key=os.environ['DUCKONET_API_KEY'], browser='chrome', version='last',
connect_timeout_seconds=5, read_timeout_seconds=15) as client:
response = client.get('https://api.example.com/data', timeout_seconds=60)
timeout_seconds ограничивает весь запрос, включая редиректы и добровольные паузы чтения. По умолчанию 30 секунд, максимум 300. Для большого файла его может потребоваться увеличить.
connect_timeout_seconds включает DNS, подключение к прокси, CONNECT, TLS либо QUIC. Готовое соединение не получает новую фазу подключения. Попытки QUIC и TCP fallback одного перехода делят этот бюджет.
read_timeout_seconds ограничивает ожидание полезного HTTP-прогресса после отправки тела либо начала финального ответа. Медленный upload сам по себе не считается зависшим чтением. PING/ACK не продлевают этот срок. Добровольная пауза потребителя response stream исключается из read-idle; общий срок продолжает идти.
Оба фазовых значения по умолчанию None. Запрос без явного значения наследует клиентское; явное None отключает только этот фазовый предел. Допустим положительный конечный int или float до 300; bool, ноль, отрицательные значения, NaN и infinity отклоняются. В словаре advanced ключи connect_timeout_ms и read_timeout_ms принимают целые 0–300000; ноль отключает предел. Например: advanced={"connect_timeout_ms": 5000}. Не задавайте одну настройку одновременно через обычный параметр и advanced.
Таймаут нативного запроса передаётся как NativeError с кодом -6. Для новой фазовой ошибки доступны timeout_phase (connect, read, total) и timeout_reason (HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT, HTTP_TOTAL_TIMEOUT). Частичный ответ находится в partial, если он получен. Общий срок на уровне Python может завершиться совместимым HttpTimeoutError, наследующим TimeoutError.
Ручная подача через низкоуровневый API
В Session.compile_http доступны дополнительные keyword-only параметры connect_timeout_ms, read_timeout_ms, upload_enabled, upload_source, upload_length, upload_limit_bytes. Контекст запроса задаётся через BrowserHopContext. Для обычной загрузки файла этот интерфейс не требуется.
Метод Request | Поведение |
|---|---|
upload_info() | Возвращает UploadInfo. |
upload_feed(data) | Принимает bytes до 65536; возвращает число принятых байт, включая ноль и частичное принятие. Остаток подаётся позднее. |
upload_finish(error_code=0) | Сообщает EOF либо ошибку producer: целый код от -18 до 0. |
wait_upload(minimum_room=1, timeout=0.05) | Ждёт место; minimum_room 1–65536, timeout 0–1 секунды. Возвращает True, когда условие выполнено. |
UploadInfo содержит bytes_fed, bytes_read, queued_bytes, maximum_queued_bytes, ready, eof, terminal, timeout_reason. Последнее поле — None, total, connect или read. Принятые очередью байты и байты, прочитанные транспортом, не доказывают получение сервером. Не смешивайте ручную подачу с автоматическим producer одного запроса.
В HttpCapabilities доступны streaming_upload, phase_timeouts, idna_ipv6. Неподдерживаемая операция завершается отказом до сети, без подстановки другого HTTP-клиента или прямого маршрута. C-интерфейс описан в HTTP C API. Исполняемые примеры находятся в примерах HTTP.