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

Commit c15d9fd

Browse files
committed
Initial version
Signed-off-by: Uilian Ries <uilianries@gmail.com>
1 parent 0dca381 commit c15d9fd

13 files changed

Lines changed: 437 additions & 2 deletions

.ci/install.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
3+
set -e
4+
set -x
5+
6+
if [[ "$(uname -s)" == 'Darwin' ]]; then
7+
brew update || brew update
8+
brew outdated pyenv || brew upgrade pyenv
9+
brew install pyenv-virtualenv
10+
11+
if which pyenv > /dev/null; then
12+
eval "$(pyenv init -)"
13+
fi
14+
15+
pyenv install 3.7.1
16+
pyenv virtualenv 3.7.1 conan
17+
pyenv rehash
18+
pyenv activate conan
19+
fi
20+
21+
pip install codecov
22+
pip install -e .[test]

.ci/run.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
3+
set -e
4+
set -x
5+
6+
if [[ "$(uname -s)" == 'Darwin' ]]; then
7+
if which pyenv > /dev/null; then
8+
eval "$(pyenv init -)"
9+
fi
10+
pyenv activate conan
11+
fi
12+
13+
python setup.py sdist
14+
pushd tests
15+
pytest -v -s --cov=bintray
16+
mv .coverage ..
17+
popd

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,4 @@ venv.bak/
102102

103103
# mypy
104104
.mypy_cache/
105+
.idea/

.travis.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
matrix:
2+
fast_finish: true
3+
include:
4+
- os: linux
5+
dist: xenial
6+
language: python
7+
python: '3.7'
8+
9+
install:
10+
- chmod +x .ci/install.sh
11+
- ".ci/install.sh"
12+
13+
script:
14+
- chmod +x .ci/run.sh
15+
- ".ci/run.sh"
16+
17+
after_success:
18+
- codecov
19+
20+
deploy:
21+
- provider: pypi
22+
user: ${PYPI_USERNAME}
23+
password: ${PYPI_PASSWORD}
24+
on:
25+
tags: true
26+
skip_cleanup: true
27+
skip_existing: true
28+
- provider: pypi
29+
user: ${TEST_PYPI_USERNAME}
30+
server: https://test.pypi.org/legacy/
31+
password: ${TEST_PYPI_PASSWORD}
32+
on:
33+
branch: master
34+
skip_cleanup: true
35+
skip_existing: true

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
1-
# bintray-python
2-
Python wrapper for Bintray API
1+
# Bintray Python
2+
3+
**The Python wrapper for Bintray API**
4+
5+
#### DOCUMENTATION
6+
7+
Please, read the official documentation from Bintray: https://bintray.com/docs/api
8+
9+
#### LICENSE
10+
[MIT](LICENSE)

bintray/__init__.py

Whitespace-only changes.

bintray/bintray.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
""" Python Wrapper for Bintray API
4+
5+
https://bintray.com/docs/api
6+
"""
7+
import os
8+
import logging
9+
import requests
10+
from requests.auth import HTTPBasicAuth
11+
12+
13+
class Bintray(object):
14+
""" Python Wrapper for Bintray API
15+
16+
"""
17+
18+
# Bintray API URL
19+
BINTRAY_URL = "https://api.bintray.com"
20+
21+
def __init__(self, username=None, api_key=None):
22+
""" Initialize arguments for login
23+
24+
:param username: Bintray username
25+
:param api_key: Bintray API Key
26+
"""
27+
self._username = username or os.getenv("BINTRAY_USERNAME")
28+
self._password = api_key or os.getenv("BINTRAY_API_KEY")
29+
30+
self._logger = logging.getLogger(__file__)
31+
self._logger.setLevel(logging.INFO)
32+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
33+
ch = logging.StreamHandler()
34+
ch.setLevel(logging.INFO)
35+
ch.setFormatter(formatter)
36+
self._logger.addHandler(ch)
37+
38+
def _get_authentication(self):
39+
""" Retrieve Basic HTTP Authentication based on username and API key
40+
41+
:return: Basic Authentication handler
42+
"""
43+
if not self._username or not self._password:
44+
return None
45+
return HTTPBasicAuth(self._username, self._password)
46+
47+
def _add_status_code(self, response):
48+
""" Update JSON result with error and status code
49+
50+
:param response: Requests response
51+
:return: Response JSON
52+
"""
53+
json_data = response.json()
54+
if isinstance(json_data, list):
55+
json_data.append({"statusCode": response.status_code, "error": not response.ok})
56+
else:
57+
json_data.update({"statusCode": response.status_code, "error": not response.ok})
58+
return json_data
59+
60+
61+
def _bool_to_number(self, value):
62+
""" Convert boolean result into numeric string
63+
64+
:param value: Any boolean value
65+
:return: "1" when True. Otherwise, "0"
66+
"""
67+
return "1" if value else "0"
68+
69+
def _raise_error(self, message, response):
70+
try:
71+
response.raise_for_status()
72+
except Exception as error:
73+
raise Exception("{} ({}): {}".format(message, response.status_code, str(error)))
74+
75+
# Files
76+
77+
def get_package_files(self, subject, repo, package, include_unpublished=False):
78+
""" Get all files in a given package.
79+
80+
When called by a user with publishing rights on the package,
81+
includes unpublished files in the list. By default only published files are shown.
82+
:param subject: username or organization
83+
:param repo: repository name
84+
:param package: package name
85+
:param include_unpublished: Show not published files
86+
:return: List with all files
87+
"""
88+
parameters = {"include_unpublished": self._bool_to_number(include_unpublished)}
89+
url = "{}/packages/{}/{}/{}/files?include_unpublished={}".format(Bintray.BINTRAY_URL,
90+
subject,
91+
repo,
92+
package,
93+
include_unpublished)
94+
response = requests.get(url, auth=self._get_authentication(), params=parameters)
95+
if not response.ok:
96+
self._raise_error("Could not list package files", response)
97+
return self._add_status_code(response)
98+
99+
# Content Uploading & Publishing
100+
101+
def upload_content(self, subject, repo, package, version, remote_file_path, local_file_path,
102+
publish=True, override=False, explode=False):
103+
"""
104+
Upload content to the specified repository path, with package and version information (both required).
105+
106+
:param subject: username or organization
107+
:param repo: repository name
108+
:param package: package name
109+
:param version: package version
110+
:param remote_file_path: file name to be used on Bintray
111+
:param local_file_path: file path to be uploaded
112+
:param publish: publish after uploading
113+
:param override: override remote file
114+
:param explode: explode remote file
115+
:return:
116+
"""
117+
url = "{}/content/{}/{}/{}/{}/{}".format(Bintray.BINTRAY_URL, subject, repo, package,
118+
version, remote_file_path)
119+
parameters = {"publish": self._bool_to_number(publish),
120+
"override": self._bool_to_number(override),
121+
"explode": self._bool_to_number(explode)}
122+
123+
with open(local_file_path, 'rb') as file_content:
124+
response = requests.put(url, auth=self._get_authentication(), params=parameters,
125+
data=file_content)
126+
if response.status_code != 201:
127+
self._raise_error("Could not upload", response)
128+
self._logger.info("Upload successfully: {}".format(url))
129+
return self._add_status_code(response)
130+
131+
# Content Downloading
132+
133+
def download_content(self, subject, repo, remote_file_path, local_file_path):
134+
""" Download content from the specified repository path.
135+
136+
:param subject: username or organization
137+
:param repo: repository name
138+
:param remote_file_path: file name to be downloaded from Bintray
139+
:param local_file_path: file name to be stored in local storage
140+
"""
141+
download_base_url = "https://dl.bintray.com"
142+
url = "{}/{}/{}/{}".format(download_base_url, subject, repo, remote_file_path)
143+
response = requests.get(url, auth=self._get_authentication())
144+
if not response.ok:
145+
self._raise_error("Could not download file content", response)
146+
with open(local_file_path, 'wb') as local_fd:
147+
local_fd.write(response.content)
148+
self._logger.info("Download successfully: {}".format(url))
149+
return self._add_status_code(response)

bintray/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
requests==2.22.0

bintray/requirements_test.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pytest==5.0.0
2+
pytest-cov==2.7.1

setup.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""A setuptools based setup module.
2+
See:
3+
https://packaging.python.org/en/latest/distributing.html
4+
https://github.com/pypa/sampleproject
5+
"""
6+
7+
# Always prefer setuptools over distutils
8+
import re
9+
import os
10+
from setuptools import setup, find_packages
11+
# To use a consistent encoding
12+
from codecs import open
13+
14+
15+
here = os.path.abspath(os.path.dirname(__file__))
16+
17+
# Get the long description from the README file
18+
with open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
19+
long_description = f.read()
20+
21+
22+
def get_requires(filename):
23+
requirements = []
24+
with open(filename) as req_file:
25+
for line in req_file.read().splitlines():
26+
if not line.strip().startswith("#"):
27+
requirements.append(line)
28+
return requirements
29+
30+
31+
def load_version():
32+
"""Loads a file content"""
33+
filename = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)),
34+
"bincrafters_conventions", "bincrafters_conventions.py"))
35+
with open(filename, "rt") as version_file:
36+
conan_init = version_file.read()
37+
version = re.search("__version__ = '([0-9a-z.-]+)'", conan_init).group(1)
38+
return version
39+
40+
setup(
41+
name='bincrafters_conventions',
42+
# Versions should comply with PEP440. For a discussion on single-sourcing
43+
# the version across setup.py and the project code, see
44+
# https://packaging.python.org/en/latest/single_source_version.html
45+
version=load_version(),
46+
47+
# This is an optional longer description of your project that represents
48+
# the body of text which users will see when they visit PyPI.
49+
#
50+
# Often, this is the same as your README, so you can just read it in from
51+
# that file directly (as we have already done above)
52+
#
53+
# This field corresponds to the "Description" metadata field:
54+
# https://packaging.python.org/specifications/core-metadata/#description-optional
55+
long_description=long_description, # Optional
56+
57+
description='Python Wrapper for Bintray API',
58+
59+
# The project's main homepage.
60+
url='https://github.com/uilianries/bintray-python',
61+
62+
# Author details
63+
author='Uilian Ries',
64+
author_email='uilianries@gmail.com',
65+
66+
# Choose your license
67+
license='MIT',
68+
69+
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
70+
classifiers=[
71+
'Development Status :: 4 - Beta',
72+
'Intended Audience :: Developers',
73+
'Topic :: Software Development :: Build Tools',
74+
'License :: OSI Approved :: MIT License',
75+
'Programming Language :: Python :: 3',
76+
],
77+
78+
# What does your project relate to?
79+
keywords=['jfrog', 'bintray', 'api', 'libraries', 'developer', 'manager',
80+
'dependency', 'package', 'python', 'wrapper'],
81+
82+
# You can just specify the packages manually here if your project is
83+
# simple. Or you can use find_packages().
84+
packages=find_packages(exclude=['tests']),
85+
86+
# Alternatively, if you want to distribute just a my_module.py, uncomment
87+
# this:
88+
# py_modules=["my_module"],
89+
90+
# List run-time dependencies here. These will be installed by pip when
91+
# your project is installed. For an analysis of "install_requires" vs pip's
92+
# requirements files see:
93+
# https://packaging.python.org/en/latest/requirements.html
94+
install_requires=get_requires(os.path.join('bintray', 'requirements.txt')),
95+
96+
# List additional groups of dependencies here (e.g. development
97+
# dependencies). You can install these using the following syntax,
98+
# for example:
99+
# $ pip install -e .[dev,test]
100+
extras_require={
101+
'test': get_requires(os.path.join('bintray', 'requirements_test.txt'))
102+
},
103+
104+
# If there are data files included in your packages that need to be
105+
# installed, specify them here. If using Python 2.6 or less, then these
106+
# have to be included in MANIFEST.in as well.
107+
package_data={
108+
'': ['*.md'],
109+
'bintray': ['*.txt'],
110+
},
111+
112+
# Although 'package_data' is the preferred approach, in some case you may
113+
# need to place data files outside of your packages. See:
114+
# http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa
115+
# In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
116+
# data_files=[('my_data', ['data/data_file'])],
117+
118+
# To provide executable scripts, use entry points in preference to the
119+
# "scripts" keyword. Entry points provide cross-platform support and allow
120+
# pip to create the appropriate form of executable for the target platform.
121+
#entry_points={
122+
#
123+
#},
124+
)

0 commit comments

Comments
 (0)