1616from datetime import datetime
1717from . import exceptions
1818import json
19+ import mimetypes
1920import os
21+ import random
2022import re
23+ import string
2124import threading
25+ import urlparse
2226from ._fatomic import atomic_open
2327
2428_ISO_FMT = '%Y-%m-%dT%H:%M:%S.%f+00:00'
@@ -89,11 +93,138 @@ def check_status(response):
8993
9094
9195def get_filename (response ):
92- cd = response .headers .get ('content-disposition' , '' )
96+ """Derive a filename from the given response.
97+
98+ >>> import requests
99+ >>> from planet.api import utils
100+ >>> response = requests.Response()
101+ >>> response.headers = {
102+ ... 'date': 'Thu, 14 Feb 2019 16:13:26 GMT',
103+ ... 'last-modified': 'Wed, 22 Nov 2017 17:22:31 GMT',
104+ ... 'accept-ranges': 'bytes',
105+ ... 'content-type': 'image/tiff',
106+ ... 'content-length': '57350256',
107+ ... 'content-disposition': 'attachment; filename="open_california.tif"'
108+ ... }
109+ >>> response.url = 'https://planet.com/path/to/example.tif?foo=f6f1'
110+ >>> print(utils.get_filename(response))
111+ open_california.tif
112+ >>> del response
113+ >>> response = requests.Response()
114+ >>> response.headers = {
115+ ... 'date': 'Thu, 14 Feb 2019 16:13:26 GMT',
116+ ... 'last-modified': 'Wed, 22 Nov 2017 17:22:31 GMT',
117+ ... 'accept-ranges': 'bytes',
118+ ... 'content-type': 'image/tiff',
119+ ... 'content-length': '57350256'
120+ ... }
121+ >>> response.url = 'https://planet.com/path/to/example.tif?foo=f6f1'
122+ >>> print(utils.get_filename(response))
123+ example.tif
124+ >>> del response
125+ >>> response = requests.Response()
126+ >>> response.headers = {
127+ ... 'date': 'Thu, 14 Feb 2019 16:13:26 GMT',
128+ ... 'last-modified': 'Wed, 22 Nov 2017 17:22:31 GMT',
129+ ... 'accept-ranges': 'bytes',
130+ ... 'content-type': 'image/tiff',
131+ ... 'content-length': '57350256'
132+ ... }
133+ >>> response.url = 'https://planet.com/path/to/oops/'
134+ >>> print(utils.get_filename(response)) #doctest:+SKIP
135+ planet-bFL6pwki.tif
136+ >>>
137+
138+ :param response: An HTTP response.
139+ :type response: :py:class:`requests.Response`
140+ :returns: a filename (i.e. ``basename``)
141+ :rtype: str
142+ """
143+ name = (get_filename_from_headers (response .headers ) or
144+ get_filename_from_url (response .url ) or
145+ get_random_filename (response .headers .get ('content-type' )))
146+ return name
147+
148+
149+ def get_filename_from_headers (headers ):
150+ """Get a filename from the Content-Disposition header, if available.
151+
152+ >>> from planet.api import utils
153+ >>> headers = {
154+ ... 'date': 'Thu, 14 Feb 2019 16:13:26 GMT',
155+ ... 'last-modified': 'Wed, 22 Nov 2017 17:22:31 GMT',
156+ ... 'accept-ranges': 'bytes',
157+ ... 'content-type': 'image/tiff',
158+ ... 'content-length': '57350256',
159+ ... 'content-disposition': 'attachment; filename="open_california.tif"'
160+ ... }
161+ >>> name = utils.get_filename_from_headers(headers)
162+ >>> print(name)
163+ open_california.tif
164+ >>>
165+ >>> headers.pop('content-disposition', None)
166+ 'attachment; filename="open_california.tif"'
167+ >>> name = utils.get_filename_from_headers(headers)
168+ >>> print(name)
169+ None
170+ >>>
171+
172+ :param headers dict: a ``dict`` of response headers
173+ :returns: a filename (i.e. ``basename``)
174+ :rtype: str or None
175+ """
176+ cd = headers .get ('content-disposition' , '' )
93177 match = re .search ('filename="?([^"]+)"?' , cd )
94- if match :
95- return match .group (1 )
96- return cd
178+ return match .group (1 ) if match else None
179+
180+
181+ def get_filename_from_url (url ):
182+ """Get a filename from a URL.
183+
184+ >>> from planet.api import utils
185+ >>> urls = [
186+ ... 'https://planet.com/',
187+ ... 'https://planet.com/path/to/',
188+ ... 'https://planet.com/path/to/example.tif',
189+ ... 'https://planet.com/path/to/example.tif?foo=f6f1&bar=baz',
190+ ... 'https://planet.com/path/to/example.tif?foo=f6f1&bar=baz#quux'
191+ ... ]
192+ >>> for url in urls:
193+ ... print('{} -> {}'.format(url, utils.get_filename_from_url(url)))
194+ ...
195+ https://planet.com/ -> None
196+ https://planet.com/path/to/ -> None
197+ https://planet.com/path/to/example.tif -> example.tif
198+ https://planet.com/path/to/example.tif?foo=f6f1&bar=baz -> example.tif
199+ https://planet.com/path/to/example.tif?foo=f6f1&bar=baz#quux -> example.tif
200+ >>>
201+
202+ :returns: a filename (i.e. ``basename``)
203+ :rtype: str or None
204+ """
205+ path = urlparse .urlparse (url ).path
206+ name = path [path .rfind ('/' )+ 1 :]
207+ return name or None
208+
209+
210+ def get_random_filename (content_type = None ):
211+ """Get a pseudo-random, Planet-looking filename.
212+
213+ >>> from planet.api import utils
214+ >>> print(utils.get_random_filename()) #doctest:+SKIP
215+ planet-61FPnh7K
216+ >>> print(utils.get_random_filename('image/tiff')) #doctest:+SKIP
217+ planet-V8ELYxy5.tif
218+ >>>
219+
220+ :returns: a filename (i.e. ``basename``)
221+ :rtype: str
222+ """
223+ extension = mimetypes .guess_extension (content_type or '' ) or ''
224+ characters = string .ascii_letters + '0123456789'
225+ letters = '' .join (random .sample (characters , 8 ))
226+ name = 'planet-{}{}' .format (letters , extension )
227+ return name
97228
98229
99230def write_to_file (directory = None , callback = None , overwrite = True ):
@@ -112,6 +243,7 @@ def write_to_file(directory=None, callback=None, overwrite=True):
112243 write progress.
113244 :param overwrite bool: Overwrite any existing files. Defaults to True.
114245 '''
246+
115247 def writer (body ):
116248 file = os .path .join (directory or '.' , body .name )
117249 if overwrite or not os .path .exists (file ):
0 commit comments