1# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
  2
  3from __future__ import annotations
  4
  5import io
  6import os
  7import logging
  8import builtins
  9from typing import overload
 10from pathlib import Path
 11
 12import anyio
 13import httpx
 14
 15from ... import _legacy_response
 16from .parts import (
 17    Parts,
 18    AsyncParts,
 19    PartsWithRawResponse,
 20    AsyncPartsWithRawResponse,
 21    PartsWithStreamingResponse,
 22    AsyncPartsWithStreamingResponse,
 23)
 24from ...types import FilePurpose, upload_create_params, upload_complete_params
 25from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
 26from ..._utils import maybe_transform, async_maybe_transform
 27from ..._compat import cached_property
 28from ..._resource import SyncAPIResource, AsyncAPIResource
 29from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
 30from ..._base_client import make_request_options
 31from ...types.upload import Upload
 32from ...types.file_purpose import FilePurpose
 33
 34__all__ = ["Uploads", "AsyncUploads"]
 35
 36
 37# 64MB
 38DEFAULT_PART_SIZE = 64 * 1024 * 1024
 39
 40log: logging.Logger = logging.getLogger(__name__)
 41
 42
 43class Uploads(SyncAPIResource):
 44    @cached_property
 45    def parts(self) -> Parts:
 46        return Parts(self._client)
 47
 48    @cached_property
 49    def with_raw_response(self) -> UploadsWithRawResponse:
 50        """
 51        This property can be used as a prefix for any HTTP method call to return
 52        the raw response object instead of the parsed content.
 53
 54        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
 55        """
 56        return UploadsWithRawResponse(self)
 57
 58    @cached_property
 59    def with_streaming_response(self) -> UploadsWithStreamingResponse:
 60        """
 61        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
 62
 63        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
 64        """
 65        return UploadsWithStreamingResponse(self)
 66
 67    @overload
 68    def upload_file_chunked(
 69        self,
 70        *,
 71        file: os.PathLike[str],
 72        mime_type: str,
 73        purpose: FilePurpose,
 74        bytes: int | None = None,
 75        part_size: int | None = None,
 76        md5: str | Omit = omit,
 77    ) -> Upload:
 78        """Splits a file into multiple 64MB parts and uploads them sequentially."""
 79
 80    @overload
 81    def upload_file_chunked(
 82        self,
 83        *,
 84        file: bytes,
 85        filename: str,
 86        bytes: int,
 87        mime_type: str,
 88        purpose: FilePurpose,
 89        part_size: int | None = None,
 90        md5: str | Omit = omit,
 91    ) -> Upload:
 92        """Splits an in-memory file into multiple 64MB parts and uploads them sequentially."""
 93
 94    def upload_file_chunked(
 95        self,
 96        *,
 97        file: os.PathLike[str] | bytes,
 98        mime_type: str,
 99        purpose: FilePurpose,
100        filename: str | None = None,
101        bytes: int | None = None,
102        part_size: int | None = None,
103        md5: str | Omit = omit,
104    ) -> Upload:
105        """Splits the given file into multiple parts and uploads them sequentially.
106
107        ```py
108        from pathlib import Path
109
110        client.uploads.upload_file(
111            file=Path("my-paper.pdf"),
112            mime_type="pdf",
113            purpose="assistants",
114        )
115        ```
116        """
117        if isinstance(file, builtins.bytes):
118            if filename is None:
119                raise TypeError("The `filename` argument must be given for in-memory files")
120
121            if bytes is None:
122                raise TypeError("The `bytes` argument must be given for in-memory files")
123        else:
124            if not isinstance(file, Path):
125                file = Path(file)
126
127            if not filename:
128                filename = file.name
129
130            if bytes is None:
131                bytes = file.stat().st_size
132
133        upload = self.create(
134            bytes=bytes,
135            filename=filename,
136            mime_type=mime_type,
137            purpose=purpose,
138        )
139
140        part_ids: list[str] = []
141
142        if part_size is None:
143            part_size = DEFAULT_PART_SIZE
144
145        if isinstance(file, builtins.bytes):
146            buf: io.FileIO | io.BytesIO = io.BytesIO(file)
147        else:
148            buf = io.FileIO(file)
149
150        try:
151            while True:
152                data = buf.read(part_size)
153                if not data:
154                    # EOF
155                    break
156
157                part = self.parts.create(upload_id=upload.id, data=data)
158                log.info("Uploaded part %s for upload %s", part.id, upload.id)
159                part_ids.append(part.id)
160        finally:
161            buf.close()
162
163        return self.complete(upload_id=upload.id, part_ids=part_ids, md5=md5)
164
165    def create(
166        self,
167        *,
168        bytes: int,
169        filename: str,
170        mime_type: str,
171        purpose: FilePurpose,
172        expires_after: upload_create_params.ExpiresAfter | Omit = omit,
173        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
174        # The extra values given here take precedence over values defined on the client or passed to this method.
175        extra_headers: Headers | None = None,
176        extra_query: Query | None = None,
177        extra_body: Body | None = None,
178        timeout: float | httpx.Timeout | None | NotGiven = not_given,
179    ) -> Upload:
180        """
181        Creates an intermediate
182        [Upload](https://platform.openai.com/docs/api-reference/uploads/object) object
183        that you can add
184        [Parts](https://platform.openai.com/docs/api-reference/uploads/part-object) to.
185        Currently, an Upload can accept at most 8 GB in total and expires after an hour
186        after you create it.
187
188        Once you complete the Upload, we will create a
189        [File](https://platform.openai.com/docs/api-reference/files/object) object that
190        contains all the parts you uploaded. This File is usable in the rest of our
191        platform as a regular File object.
192
193        For certain `purpose` values, the correct `mime_type` must be specified. Please
194        refer to documentation for the
195        [supported MIME types for your use case](https://platform.openai.com/docs/assistants/tools/file-search#supported-files).
196
197        For guidance on the proper filename extensions for each purpose, please follow
198        the documentation on
199        [creating a File](https://platform.openai.com/docs/api-reference/files/create).
200
201        Args:
202          bytes: The number of bytes in the file you are uploading.
203
204          filename: The name of the file to upload.
205
206          mime_type: The MIME type of the file.
207
208              This must fall within the supported MIME types for your file purpose. See the
209              supported MIME types for assistants and vision.
210
211          purpose: The intended purpose of the uploaded file.
212
213              See the
214              [documentation on File purposes](https://platform.openai.com/docs/api-reference/files/create#files-create-purpose).
215
216          expires_after: The expiration policy for a file. By default, files with `purpose=batch` expire
217              after 30 days and all other files are persisted until they are manually deleted.
218
219          extra_headers: Send extra headers
220
221          extra_query: Add additional query parameters to the request
222
223          extra_body: Add additional JSON properties to the request
224
225          timeout: Override the client-level default timeout for this request, in seconds
226        """
227        return self._post(
228            "/uploads",
229            body=maybe_transform(
230                {
231                    "bytes": bytes,
232                    "filename": filename,
233                    "mime_type": mime_type,
234                    "purpose": purpose,
235                    "expires_after": expires_after,
236                },
237                upload_create_params.UploadCreateParams,
238            ),
239            options=make_request_options(
240                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
241            ),
242            cast_to=Upload,
243        )
244
245    def cancel(
246        self,
247        upload_id: str,
248        *,
249        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
250        # The extra values given here take precedence over values defined on the client or passed to this method.
251        extra_headers: Headers | None = None,
252        extra_query: Query | None = None,
253        extra_body: Body | None = None,
254        timeout: float | httpx.Timeout | None | NotGiven = not_given,
255    ) -> Upload:
256        """Cancels the Upload.
257
258        No Parts may be added after an Upload is cancelled.
259
260        Args:
261          extra_headers: Send extra headers
262
263          extra_query: Add additional query parameters to the request
264
265          extra_body: Add additional JSON properties to the request
266
267          timeout: Override the client-level default timeout for this request, in seconds
268        """
269        if not upload_id:
270            raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}")
271        return self._post(
272            f"/uploads/{upload_id}/cancel",
273            options=make_request_options(
274                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
275            ),
276            cast_to=Upload,
277        )
278
279    def complete(
280        self,
281        upload_id: str,
282        *,
283        part_ids: SequenceNotStr[str],
284        md5: str | Omit = omit,
285        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
286        # The extra values given here take precedence over values defined on the client or passed to this method.
287        extra_headers: Headers | None = None,
288        extra_query: Query | None = None,
289        extra_body: Body | None = None,
290        timeout: float | httpx.Timeout | None | NotGiven = not_given,
291    ) -> Upload:
292        """
293        Completes the
294        [Upload](https://platform.openai.com/docs/api-reference/uploads/object).
295
296        Within the returned Upload object, there is a nested
297        [File](https://platform.openai.com/docs/api-reference/files/object) object that
298        is ready to use in the rest of the platform.
299
300        You can specify the order of the Parts by passing in an ordered list of the Part
301        IDs.
302
303        The number of bytes uploaded upon completion must match the number of bytes
304        initially specified when creating the Upload object. No Parts may be added after
305        an Upload is completed.
306
307        Args:
308          part_ids: The ordered list of Part IDs.
309
310          md5: The optional md5 checksum for the file contents to verify if the bytes uploaded
311              matches what you expect.
312
313          extra_headers: Send extra headers
314
315          extra_query: Add additional query parameters to the request
316
317          extra_body: Add additional JSON properties to the request
318
319          timeout: Override the client-level default timeout for this request, in seconds
320        """
321        if not upload_id:
322            raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}")
323        return self._post(
324            f"/uploads/{upload_id}/complete",
325            body=maybe_transform(
326                {
327                    "part_ids": part_ids,
328                    "md5": md5,
329                },
330                upload_complete_params.UploadCompleteParams,
331            ),
332            options=make_request_options(
333                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
334            ),
335            cast_to=Upload,
336        )
337
338
339class AsyncUploads(AsyncAPIResource):
340    @cached_property
341    def parts(self) -> AsyncParts:
342        return AsyncParts(self._client)
343
344    @cached_property
345    def with_raw_response(self) -> AsyncUploadsWithRawResponse:
346        """
347        This property can be used as a prefix for any HTTP method call to return
348        the raw response object instead of the parsed content.
349
350        For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers
351        """
352        return AsyncUploadsWithRawResponse(self)
353
354    @cached_property
355    def with_streaming_response(self) -> AsyncUploadsWithStreamingResponse:
356        """
357        An alternative to `.with_raw_response` that doesn't eagerly read the response body.
358
359        For more information, see https://www.github.com/openai/openai-python#with_streaming_response
360        """
361        return AsyncUploadsWithStreamingResponse(self)
362
363    @overload
364    async def upload_file_chunked(
365        self,
366        *,
367        file: os.PathLike[str],
368        mime_type: str,
369        purpose: FilePurpose,
370        bytes: int | None = None,
371        part_size: int | None = None,
372        md5: str | Omit = omit,
373    ) -> Upload:
374        """Splits a file into multiple 64MB parts and uploads them sequentially."""
375
376    @overload
377    async def upload_file_chunked(
378        self,
379        *,
380        file: bytes,
381        filename: str,
382        bytes: int,
383        mime_type: str,
384        purpose: FilePurpose,
385        part_size: int | None = None,
386        md5: str | Omit = omit,
387    ) -> Upload:
388        """Splits an in-memory file into multiple 64MB parts and uploads them sequentially."""
389
390    async def upload_file_chunked(
391        self,
392        *,
393        file: os.PathLike[str] | bytes,
394        mime_type: str,
395        purpose: FilePurpose,
396        filename: str | None = None,
397        bytes: int | None = None,
398        part_size: int | None = None,
399        md5: str | Omit = omit,
400    ) -> Upload:
401        """Splits the given file into multiple parts and uploads them sequentially.
402
403        ```py
404        from pathlib import Path
405
406        client.uploads.upload_file(
407            file=Path("my-paper.pdf"),
408            mime_type="pdf",
409            purpose="assistants",
410        )
411        ```
412        """
413        if isinstance(file, builtins.bytes):
414            if filename is None:
415                raise TypeError("The `filename` argument must be given for in-memory files")
416
417            if bytes is None:
418                raise TypeError("The `bytes` argument must be given for in-memory files")
419        else:
420            if not isinstance(file, anyio.Path):
421                file = anyio.Path(file)
422
423            if not filename:
424                filename = file.name
425
426            if bytes is None:
427                stat = await file.stat()
428                bytes = stat.st_size
429
430        upload = await self.create(
431            bytes=bytes,
432            filename=filename,
433            mime_type=mime_type,
434            purpose=purpose,
435        )
436
437        part_ids: list[str] = []
438
439        if part_size is None:
440            part_size = DEFAULT_PART_SIZE
441
442        if isinstance(file, anyio.Path):
443            fd = await file.open("rb")
444            async with fd:
445                while True:
446                    data = await fd.read(part_size)
447                    if not data:
448                        # EOF
449                        break
450
451                    part = await self.parts.create(upload_id=upload.id, data=data)
452                    log.info("Uploaded part %s for upload %s", part.id, upload.id)
453                    part_ids.append(part.id)
454        else:
455            buf = io.BytesIO(file)
456
457            try:
458                while True:
459                    data = buf.read(part_size)
460                    if not data:
461                        # EOF
462                        break
463
464                    part = await self.parts.create(upload_id=upload.id, data=data)
465                    log.info("Uploaded part %s for upload %s", part.id, upload.id)
466                    part_ids.append(part.id)
467            finally:
468                buf.close()
469
470        return await self.complete(upload_id=upload.id, part_ids=part_ids, md5=md5)
471
472    async def create(
473        self,
474        *,
475        bytes: int,
476        filename: str,
477        mime_type: str,
478        purpose: FilePurpose,
479        expires_after: upload_create_params.ExpiresAfter | Omit = omit,
480        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
481        # The extra values given here take precedence over values defined on the client or passed to this method.
482        extra_headers: Headers | None = None,
483        extra_query: Query | None = None,
484        extra_body: Body | None = None,
485        timeout: float | httpx.Timeout | None | NotGiven = not_given,
486    ) -> Upload:
487        """
488        Creates an intermediate
489        [Upload](https://platform.openai.com/docs/api-reference/uploads/object) object
490        that you can add
491        [Parts](https://platform.openai.com/docs/api-reference/uploads/part-object) to.
492        Currently, an Upload can accept at most 8 GB in total and expires after an hour
493        after you create it.
494
495        Once you complete the Upload, we will create a
496        [File](https://platform.openai.com/docs/api-reference/files/object) object that
497        contains all the parts you uploaded. This File is usable in the rest of our
498        platform as a regular File object.
499
500        For certain `purpose` values, the correct `mime_type` must be specified. Please
501        refer to documentation for the
502        [supported MIME types for your use case](https://platform.openai.com/docs/assistants/tools/file-search#supported-files).
503
504        For guidance on the proper filename extensions for each purpose, please follow
505        the documentation on
506        [creating a File](https://platform.openai.com/docs/api-reference/files/create).
507
508        Args:
509          bytes: The number of bytes in the file you are uploading.
510
511          filename: The name of the file to upload.
512
513          mime_type: The MIME type of the file.
514
515              This must fall within the supported MIME types for your file purpose. See the
516              supported MIME types for assistants and vision.
517
518          purpose: The intended purpose of the uploaded file.
519
520              See the
521              [documentation on File purposes](https://platform.openai.com/docs/api-reference/files/create#files-create-purpose).
522
523          expires_after: The expiration policy for a file. By default, files with `purpose=batch` expire
524              after 30 days and all other files are persisted until they are manually deleted.
525
526          extra_headers: Send extra headers
527
528          extra_query: Add additional query parameters to the request
529
530          extra_body: Add additional JSON properties to the request
531
532          timeout: Override the client-level default timeout for this request, in seconds
533        """
534        return await self._post(
535            "/uploads",
536            body=await async_maybe_transform(
537                {
538                    "bytes": bytes,
539                    "filename": filename,
540                    "mime_type": mime_type,
541                    "purpose": purpose,
542                    "expires_after": expires_after,
543                },
544                upload_create_params.UploadCreateParams,
545            ),
546            options=make_request_options(
547                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
548            ),
549            cast_to=Upload,
550        )
551
552    async def cancel(
553        self,
554        upload_id: str,
555        *,
556        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
557        # The extra values given here take precedence over values defined on the client or passed to this method.
558        extra_headers: Headers | None = None,
559        extra_query: Query | None = None,
560        extra_body: Body | None = None,
561        timeout: float | httpx.Timeout | None | NotGiven = not_given,
562    ) -> Upload:
563        """Cancels the Upload.
564
565        No Parts may be added after an Upload is cancelled.
566
567        Args:
568          extra_headers: Send extra headers
569
570          extra_query: Add additional query parameters to the request
571
572          extra_body: Add additional JSON properties to the request
573
574          timeout: Override the client-level default timeout for this request, in seconds
575        """
576        if not upload_id:
577            raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}")
578        return await self._post(
579            f"/uploads/{upload_id}/cancel",
580            options=make_request_options(
581                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
582            ),
583            cast_to=Upload,
584        )
585
586    async def complete(
587        self,
588        upload_id: str,
589        *,
590        part_ids: SequenceNotStr[str],
591        md5: str | Omit = omit,
592        # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
593        # The extra values given here take precedence over values defined on the client or passed to this method.
594        extra_headers: Headers | None = None,
595        extra_query: Query | None = None,
596        extra_body: Body | None = None,
597        timeout: float | httpx.Timeout | None | NotGiven = not_given,
598    ) -> Upload:
599        """
600        Completes the
601        [Upload](https://platform.openai.com/docs/api-reference/uploads/object).
602
603        Within the returned Upload object, there is a nested
604        [File](https://platform.openai.com/docs/api-reference/files/object) object that
605        is ready to use in the rest of the platform.
606
607        You can specify the order of the Parts by passing in an ordered list of the Part
608        IDs.
609
610        The number of bytes uploaded upon completion must match the number of bytes
611        initially specified when creating the Upload object. No Parts may be added after
612        an Upload is completed.
613
614        Args:
615          part_ids: The ordered list of Part IDs.
616
617          md5: The optional md5 checksum for the file contents to verify if the bytes uploaded
618              matches what you expect.
619
620          extra_headers: Send extra headers
621
622          extra_query: Add additional query parameters to the request
623
624          extra_body: Add additional JSON properties to the request
625
626          timeout: Override the client-level default timeout for this request, in seconds
627        """
628        if not upload_id:
629            raise ValueError(f"Expected a non-empty value for `upload_id` but received {upload_id!r}")
630        return await self._post(
631            f"/uploads/{upload_id}/complete",
632            body=await async_maybe_transform(
633                {
634                    "part_ids": part_ids,
635                    "md5": md5,
636                },
637                upload_complete_params.UploadCompleteParams,
638            ),
639            options=make_request_options(
640                extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
641            ),
642            cast_to=Upload,
643        )
644
645
646class UploadsWithRawResponse:
647    def __init__(self, uploads: Uploads) -> None:
648        self._uploads = uploads
649
650        self.create = _legacy_response.to_raw_response_wrapper(
651            uploads.create,
652        )
653        self.cancel = _legacy_response.to_raw_response_wrapper(
654            uploads.cancel,
655        )
656        self.complete = _legacy_response.to_raw_response_wrapper(
657            uploads.complete,
658        )
659
660    @cached_property
661    def parts(self) -> PartsWithRawResponse:
662        return PartsWithRawResponse(self._uploads.parts)
663
664
665class AsyncUploadsWithRawResponse:
666    def __init__(self, uploads: AsyncUploads) -> None:
667        self._uploads = uploads
668
669        self.create = _legacy_response.async_to_raw_response_wrapper(
670            uploads.create,
671        )
672        self.cancel = _legacy_response.async_to_raw_response_wrapper(
673            uploads.cancel,
674        )
675        self.complete = _legacy_response.async_to_raw_response_wrapper(
676            uploads.complete,
677        )
678
679    @cached_property
680    def parts(self) -> AsyncPartsWithRawResponse:
681        return AsyncPartsWithRawResponse(self._uploads.parts)
682
683
684class UploadsWithStreamingResponse:
685    def __init__(self, uploads: Uploads) -> None:
686        self._uploads = uploads
687
688        self.create = to_streamed_response_wrapper(
689            uploads.create,
690        )
691        self.cancel = to_streamed_response_wrapper(
692            uploads.cancel,
693        )
694        self.complete = to_streamed_response_wrapper(
695            uploads.complete,
696        )
697
698    @cached_property
699    def parts(self) -> PartsWithStreamingResponse:
700        return PartsWithStreamingResponse(self._uploads.parts)
701
702
703class AsyncUploadsWithStreamingResponse:
704    def __init__(self, uploads: AsyncUploads) -> None:
705        self._uploads = uploads
706
707        self.create = async_to_streamed_response_wrapper(
708            uploads.create,
709        )
710        self.cancel = async_to_streamed_response_wrapper(
711            uploads.cancel,
712        )
713        self.complete = async_to_streamed_response_wrapper(
714            uploads.complete,
715        )
716
717    @cached_property
718    def parts(self) -> AsyncPartsWithStreamingResponse:
719        return AsyncPartsWithStreamingResponse(self._uploads.parts)