File size: 2,286 Bytes
72268ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"""
Adapt s3 to package_streaming
"""

from __future__ import annotations

import typing
from contextlib import closing
from typing import Any

from . import package_streaming

if typing.TYPE_CHECKING:  # pragma: no cover
    from mypy_boto3_s3 import Client
    from mypy_boto3_s3.type_defs import GetObjectOutputTypeDef
else:
    Client = GetObjectOutputTypeDef = None

from .url import conda_reader_for_url

__all__ = ["stream_conda_info", "conda_reader_for_s3"]


class ResponseFacade:
    def __init__(self, response: GetObjectOutputTypeDef):
        self.response = response
        self.raw: Any = response["Body"]

    def raise_for_status(self):
        # s3 get_object raises automatically?
        pass

    @property
    def status_code(self):
        return self.response["ResponseMetadata"]["HTTPStatusCode"]

    @property
    def headers(self):
        # a case-sensitive dict; keys may be lowercased always?
        return self.response["ResponseMetadata"]["HTTPHeaders"]

    def iter_content(self, n: int):
        return iter(lambda: self.raw.read(n), b"")


class SessionFacade:
    """
    Make s3 client look just enough like a requests.session for LazyZipOverHTTP
    """

    def __init__(self, client: Client, bucket: str, key: str):
        self.client = client
        self.bucket = bucket
        self.key = key

    def get(self, url, *, headers: dict | None = None, stream=True):
        if headers and "Range" in headers:
            response = self.client.get_object(
                Bucket=self.bucket, Key=self.key, Range=headers["Range"]
            )
        else:
            response = self.client.get_object(Bucket=self.bucket, Key=self.key)
        return ResponseFacade(response)


def stream_conda_info(client, bucket, key):
    """
    Yield (tar, member) for conda package.

    Just "info/" for .conda, all members for tar.
    """
    filename, conda = conda_reader_for_s3(client, bucket, key)

    with closing(conda):
        yield from package_streaming.stream_conda_info(filename, conda)


def conda_reader_for_s3(client: Client, bucket: str, key: str):
    """
    Return (name, file_like) suitable for package_streaming APIs
    """
    session: Any = SessionFacade(client, bucket, key)
    return conda_reader_for_url(key, session)