14
14
# limitations under the License.
15
15
"""Manage data for requests and responses."""
16
16
import logging
17
- import mimetypes
18
- from pathlib import Path
19
- import random
20
17
import re
21
- import string
22
18
from typing import AsyncGenerator , Callable , List , Optional
23
19
from urllib .parse import urlparse
24
20
25
21
import httpx
26
- from tqdm .asyncio import tqdm
27
22
28
23
from .exceptions import PagingError
29
24
@@ -49,134 +44,59 @@ def status_code(self) -> int:
49
44
"""HTTP status code"""
50
45
return self ._http_response .status_code
51
46
52
- def json (self ) -> dict :
53
- """Response json"""
54
- return self ._http_response .json ()
55
-
56
-
57
- class StreamingResponse (Response ):
58
-
59
- @property
60
- def headers (self ) -> httpx .Headers :
61
- return self ._http_response .headers
62
-
63
47
@property
64
- def url (self ) -> str :
65
- return str ( self . _http_response . url )
48
+ def filename (self ) -> Optional [ str ] :
49
+ """Name of the download file.
66
50
67
- @ property
68
- def num_bytes_downloaded ( self ) -> int :
69
- return self . _http_response . num_bytes_downloaded
51
+ The filename is None if the response does not represent a download.
52
+ """
53
+ filename = None
70
54
71
- async def aiter_bytes (self ):
72
- async for c in self ._http_response .aiter_bytes ():
73
- yield c
55
+ if self .length is not None : # is a download file
56
+ filename = _get_filename_from_response (self ._http_response )
74
57
75
- async def aclose (self ):
76
- await self ._http_response .aclose ()
58
+ return filename
77
59
60
+ @property
61
+ def length (self ) -> Optional [int ]:
62
+ """Length of the download file.
78
63
79
- class StreamingBody :
80
- """A representation of a streaming resource from the API."""
64
+ The length is None if the response does not represent a download.
65
+ """
66
+ LOGGER .warning ('here' )
67
+ try :
68
+ length = int (self ._http_response .headers ["Content-Length" ])
69
+ except KeyError :
70
+ length = None
71
+ LOGGER .warning (length )
72
+ return length
81
73
82
- def __init__ (self , response : StreamingResponse ):
83
- """Initialize the object.
74
+ def json (self ) -> dict :
75
+ """Response json"""
76
+ return self ._http_response .json ()
84
77
85
- Parameters:
86
- response: Response that was received from the server.
87
- """
88
- self ._response = response
89
78
90
- @property
91
- def name (self ) -> str :
92
- """The name of this resource.
79
+ def _get_filename_from_response (response ) -> Optional [str ]:
80
+ """The name of the response resource.
93
81
94
82
The default is to use the content-disposition header value from the
95
83
response. If not found, falls back to resolving the name from the url
96
84
or generating a random name with the type from the response.
97
85
"""
98
- name = (_get_filename_from_headers (self ._response .headers )
99
- or _get_filename_from_url (self ._response .url )
100
- or _get_random_filename (
101
- self ._response .headers .get ('content-type' )))
102
- return name
103
-
104
- @property
105
- def size (self ) -> int :
106
- """The size of the body."""
107
- return int (self ._response .headers ['Content-Length' ])
108
-
109
- async def write (self ,
110
- filename : Path ,
111
- overwrite : bool = True ,
112
- progress_bar : bool = True ):
113
- """Write the body to a file.
114
- Parameters:
115
- filename: Name to assign to downloaded file.
116
- overwrite: Overwrite any existing files.
117
- progress_bar: Show progress bar during download.
118
- """
119
-
120
- class _LOG :
121
-
122
- def __init__ (self , total , unit , filename , disable ):
123
- self .total = total
124
- self .unit = unit
125
- self .disable = disable
126
- self .previous = 0
127
- self .filename = str (filename )
128
-
129
- if not self .disable :
130
- LOGGER .debug (f'writing to { self .filename } ' )
131
-
132
- def update (self , new ):
133
- if new - self .previous > self .unit and not self .disable :
134
- # LOGGER.debug(f'{new-self.previous}')
135
- perc = int (100 * new / self .total )
136
- LOGGER .debug (f'{ self .filename } : '
137
- f'wrote { perc } % of { self .total } ' )
138
- self .previous = new
86
+ name = (_get_filename_from_headers (response .headers )
87
+ or _get_filename_from_url (str (response .url )))
88
+ return name
139
89
140
- unit = 1024 * 1024
141
90
142
- mode = 'wb' if overwrite else 'xb'
143
- try :
144
- with open (filename , mode ) as fp :
145
- _log = _LOG (self .size ,
146
- 16 * unit ,
147
- filename ,
148
- disable = progress_bar )
149
- with tqdm (total = self .size ,
150
- unit_scale = True ,
151
- unit_divisor = unit ,
152
- unit = 'B' ,
153
- desc = str (filename ),
154
- disable = not progress_bar ) as progress :
155
- previous = self ._response .num_bytes_downloaded
156
- async for chunk in self ._response .aiter_bytes ():
157
- fp .write (chunk )
158
- new = self ._response .num_bytes_downloaded
159
- _log .update (new )
160
- progress .update (new - previous )
161
- previous = new
162
- except FileExistsError :
163
- LOGGER .info (f'File { filename } exists, not overwriting' )
164
-
165
-
166
- def _get_filename_from_headers (headers ):
167
- """Get a filename from the Content-Disposition header, if available.
168
-
169
- :param headers dict: a ``dict`` of response headers
170
- :returns: a filename (i.e. ``basename``)
171
- :rtype: str or None
172
- """
91
+ def _get_filename_from_headers (headers : httpx .Headers ) -> Optional [str ]:
92
+ """Get a filename from the Content-Disposition header, if available."""
173
93
cd = headers .get ('content-disposition' , '' )
174
94
match = re .search ('filename="?([^"]+)"?' , cd )
175
95
return match .group (1 ) if match else None
176
96
177
97
178
98
def _get_filename_from_url (url : str ) -> Optional [str ]:
179
- """Get a filename from a url.
99
+ """Get a filename from the url.
180
100
181
101
Getting a name for Landsat imagery uses this function.
182
102
"""
@@ -185,19 +105,6 @@ def _get_filename_from_url(url: str) -> Optional[str]:
185
105
return name or None
186
106
187
107
188
- def _get_random_filename (content_type = None ):
189
- """Get a pseudo-random, Planet-looking filename.
190
-
191
- :returns: a filename (i.e. ``basename``)
192
- :rtype: str
193
- """
194
- extension = mimetypes .guess_extension (content_type or '' ) or ''
195
- characters = string .ascii_letters + '0123456789'
196
- letters = '' .join (random .sample (characters , 8 ))
197
- name = 'planet-{}{}' .format (letters , extension )
198
- return name
199
-
200
-
201
108
class Paged :
202
109
"""Asynchronous iterator over results in a paged resource.
203
110
0 commit comments