Skip to content
This repository was archived by the owner on Feb 27, 2023. It is now read-only.

Commit 282772f

Browse files
authored
Feature/upload content (#29)
Feature/upload content
2 parents 60ea0d5 + 5e6794d commit 282772f

3 files changed

Lines changed: 213 additions & 8 deletions

File tree

bintray/bintray.py

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,7 @@ def file_in_download_list(self, subject, repo, file_path, add_or_remove):
165165

166166
def upload_content(self, subject, repo, package, version, remote_file_path, local_file_path,
167167
publish=True, override=False, explode=False):
168-
"""
169-
Upload content to the specified repository path, with package and version information (both required).
168+
""" Upload content to the specified repository path, with package and version information.
170169
171170
:param subject: username or organization
172171
:param repo: repository name
@@ -177,7 +176,7 @@ def upload_content(self, subject, repo, package, version, remote_file_path, loca
177176
:param publish: publish after uploading
178177
:param override: override remote file
179178
:param explode: explode remote file
180-
:return:
179+
:return: Request response
181180
"""
182181
url = "{}/content/{}/{}/{}/{}/{}".format(Bintray.BINTRAY_URL, subject, repo, package,
183182
version, remote_file_path)
@@ -191,6 +190,161 @@ def upload_content(self, subject, repo, package, version, remote_file_path, loca
191190
self._logger.info("Upload successfully: {}".format(url))
192191
return response
193192

193+
def maven_upload(self, subject, repo, package, remote_file_path, local_file_path, publish=True,
194+
passphrase=None):
195+
""" Upload Maven artifacts to the specified repository path, with package information.
196+
197+
Version information is resolved from the path, which is expected to follow the Maven
198+
layout.
199+
200+
You may supply a passphrase for signing uploaded files using the X-GPG-PASSPHRASE header
201+
202+
:param subject: username or organization
203+
:param repo: repository name
204+
:param package: package name
205+
:param remote_file_path: file name to be used on Bintray
206+
:param local_file_path: file path to be uploaded
207+
:param publish: publish after uploading
208+
:param passphrase: GPG passphrase
209+
:return: Request response
210+
"""
211+
url = "{}/maven/{}/{}/{}/{}".format(Bintray.BINTRAY_URL, subject, repo, package,
212+
remote_file_path)
213+
parameters = {"publish": bool_to_number(publish)}
214+
headers = {"X-GPG-PASSPHRASE": passphrase} if passphrase else None
215+
216+
with open(local_file_path, 'rb') as file_content:
217+
response = self._requester.put(url, params=parameters, data=file_content,
218+
headers=headers)
219+
220+
self._logger.info("Upload successfully: {}".format(url))
221+
return response
222+
223+
def debian_upload(self, subject, repo, package, version, remote_file_path, local_file_path,
224+
deb_distribution, deb_component, deb_architecture, publish=True,
225+
override=False, passphrase=None):
226+
""" Upload Debian artifacts to the specified repository path, with package information.
227+
228+
When artifacts are uploaded to a Debian repository using the Automatic index layout,
229+
the Debian distribution information is required and must be specified.
230+
231+
:param subject: username or organization
232+
:param repo: repository name
233+
:param package: package name
234+
:param version: package version
235+
:param remote_file_path: file name to be used on Bintray
236+
:param local_file_path: file path to be uploaded
237+
:param deb_distribution: Debian package distribution e.g. wheezy
238+
:param deb_component: Debian package component e.g. main
239+
:param deb_architecture: Debian package architecture e.g. i386,amd64
240+
:param publish: publish after uploading
241+
:param override: override remote file
242+
:param passphrase: GPG passphrase
243+
:return: Request response
244+
"""
245+
url = "{}/content/{}/{}/{}/{}/{}".format(Bintray.BINTRAY_URL, subject, repo, package,
246+
version, remote_file_path)
247+
parameters = {"publish": bool_to_number(publish),
248+
"override": bool_to_number(override)}
249+
headers = {
250+
"X-Bintray-Debian-Distribution": deb_distribution,
251+
"X-Bintray-Debian-Component": deb_component,
252+
"X-Bintray-Debian-Architecture": deb_architecture
253+
}
254+
if passphrase:
255+
headers["X-GPG-PASSPHRASE"] = passphrase
256+
257+
with open(local_file_path, 'rb') as file_content:
258+
response = self._requester.put(url, params=parameters, data=file_content,
259+
headers=headers)
260+
261+
self._logger.info("Upload successfully: {}".format(url))
262+
return response
263+
264+
def _publish_discard_uploaded_content(self, subject, repo, package, version, discard=False,
265+
publish_wait_for_secs=-1, passphrase=None):
266+
""" Asynchronously publishes all unpublished content for a user’s package version. Returns
267+
the number of to-be-published files.
268+
269+
In order to wait for publishing to finish and run this call synchronously, specify a
270+
"publish_wait_for_secs" timeout in seconds. To wait for the maximum timeout allowed by
271+
Bintray use a wait value of -1 . A wait value of 0 is the default and is the same as
272+
running this call asynchronously without waiting.
273+
274+
Optionally, pass in a "discard" flag to discard any unpublished content, instead of
275+
publishing.
276+
277+
Automatic Signing for Repository Metadata
278+
279+
For repositories that support automatic calculation of repository metadata (such as
280+
Debian and YUM), you may supply signing required information.
281+
282+
:param subject: username or organization
283+
:param repo: repository name
284+
:param package: package name
285+
:param version: package version
286+
:param discard: Discard package
287+
:param publish_wait_for_secs: Publishing timeout
288+
:param passphrase: GPG passphrase
289+
:return: Request response
290+
"""
291+
url = "{}/content/{}/{}/{}/{}/publish".format(Bintray.BINTRAY_URL, subject, repo, package,
292+
version)
293+
body = {'discard': discard,
294+
'publish_wait_for_secs': publish_wait_for_secs}
295+
headers = {"X-GPG-PASSPHRASE": passphrase} if passphrase else None
296+
297+
response = self._requester.post(url, json=body, headers=headers)
298+
299+
self._logger.info("Publish/Discard successfully: {}".format(url))
300+
return response
301+
302+
def publish_uploaded_content(self, subject, repo, package, version, passphrase=None):
303+
""" Asynchronously publishes all unpublished content for a user’s package version.
304+
305+
:param subject: username or organization
306+
:param repo: repository name
307+
:param package: package name
308+
:param version: package version
309+
:param passphrase: GPG passphrase
310+
:return: the number of to-be-published files.
311+
"""
312+
return self._publish_discard_uploaded_content(subject, repo, package, version,
313+
discard=False, passphrase=passphrase)
314+
315+
def discard_uploaded_content(self, subject, repo, package, version, passphrase=None):
316+
""" Asynchronously discard all unpublished content for a user’s package version.
317+
318+
:param subject: username or organization
319+
:param repo: repository name
320+
:param package: package name
321+
:param version: package version
322+
:param passphrase: GPG passphrase
323+
:return: the number of discarded files.
324+
"""
325+
return self._publish_discard_uploaded_content(subject, repo, package, version,
326+
discard=True, passphrase=passphrase)
327+
328+
def delete_content(self, subject, repo, file_path):
329+
""" Delete content from the specified repository path,
330+
331+
Currently supports only deletion of files.
332+
For OSS, this action is limited for 180 days from the content’s publish date.
333+
334+
Security: Authenticated user with 'publish' permission, or read/write entitlement for a
335+
repository path
336+
337+
:param subject: username or organization
338+
:param repo: repository name
339+
:param file_path: file to be deleted
340+
:return: request response
341+
"""
342+
url = "{}/content/{}/{}/{}".format(Bintray.BINTRAY_URL, subject, repo, file_path)
343+
response = self._requester.delete(url)
344+
345+
self._logger.info("Delete successfully: {}".format(url))
346+
return response
347+
194348
# Content Downloading
195349

196350
def download_content(self, subject, repo, remote_file_path, local_file_path):

bintray/requester.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,34 +66,39 @@ def download(self, url, params=None):
6666
self._raise_error("Could not GET", response)
6767
return self._add_status_code(response), response.content
6868

69-
def put(self, url, params=None, data=None, json=None):
69+
def put(self, url, params=None, data=None, json=None, headers=None):
7070
""" Forward PUT method
7171
7272
:param url: URL address
7373
:param params: URL params
7474
:param data: Data content
7575
:param json: JSON content
76+
:param headers: Request headers
7677
:return: JSON
7778
"""
7879
if data and json:
7980
raise Exception("Only accept 'data' or 'json'")
8081
if data:
81-
response = requests.put(url, auth=self._get_authentication(), params=params, data=data)
82+
response = requests.put(url, auth=self._get_authentication(), params=params, data=data,
83+
headers=headers)
8284
else:
83-
response = requests.put(url, auth=self._get_authentication(), params=params, json=json)
85+
response = requests.put(url, auth=self._get_authentication(), params=params, json=json,
86+
headers=headers)
8487
if not response.ok:
8588
self._raise_error("Could not PUT", response)
8689
return self._add_status_code(response)
8790

88-
def post(self, url, json=None, params=None):
91+
def post(self, url, json=None, params=None, headers=None):
8992
""" Forward POST method
9093
9194
:param url: URL address
9295
:param params: URL parameters
9396
:param json: Data to be posted
97+
:param headers: Request headers
9498
:return: Request response
9599
"""
96-
response = requests.post(url, auth=self._get_authentication(), json=json, params=params)
100+
response = requests.post(url, auth=self._get_authentication(), json=json, params=params,
101+
headers=headers)
97102
if not response.ok:
98103
self._raise_error("Could not POST", response)
99104
return self._add_status_code(response)

tests/test_upload_publishing.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,49 @@ def test_bad_credentials_for_upload_content():
2424
assert "Could not PUT (401): 401 Client Error: Unauthorized for url: " \
2525
"https://api.bintray.com/content/uilianries/generic/statistics/test/test.txt?" \
2626
"publish=1&override=0&explode=0" == error_message
27+
28+
29+
def test_maven_upload():
30+
bintray = Bintray()
31+
_, temp_path = tempfile.mkstemp()
32+
error_message = ""
33+
try:
34+
bintray.maven_upload("uilianries", "generic", "statistics", "pom.xml",
35+
temp_path, publish=True)
36+
except Exception as error:
37+
error_message = str(error)
38+
assert "Could not PUT (400): 400 Client Error: Bad Request for url:" \
39+
" https://api.bintray.com/maven/uilianries/generic/statistics/pom.xml" \
40+
"?publish=1" == error_message
41+
42+
43+
def test_debian_upload():
44+
bintray = Bintray()
45+
_, temp_path = tempfile.mkstemp()
46+
response = bintray.debian_upload("uilianries", "generic", "statistics", "test", "test.deb",
47+
temp_path, deb_distribution="wheezy", deb_component="main",
48+
deb_architecture="i386,amd64", publish=True, override=True)
49+
assert {'error': False, 'message': 'success', 'statusCode': 201} == response
50+
51+
52+
def test_publish_uploaded_content():
53+
bintray = Bintray()
54+
response = bintray.publish_uploaded_content("uilianries", "generic", "statistics", "test")
55+
assert {'error': False, 'files': 0, 'statusCode': 200} == response
56+
57+
58+
def test_discard_uploaded_content():
59+
bintray = Bintray()
60+
response = bintray.discard_uploaded_content("uilianries", "generic", "statistics", "test")
61+
assert {'error': False, 'files': 0, 'statusCode': 200} == response
62+
63+
64+
def test_delete_content():
65+
bintray = Bintray()
66+
_, temp_path = tempfile.mkstemp()
67+
response = bintray.upload_content("uilianries", "generic", "statistics", "test", "test.txt",
68+
temp_path, override=True)
69+
assert {'error': False, 'message': 'success', 'statusCode': 201} == response
70+
71+
response = bintray.delete_content("uilianries", "generic", "test.txt")
72+
assert {'error': False, 'message': 'success', 'statusCode': 200} == response

0 commit comments

Comments
 (0)