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

Commit

Permalink
Initial version
Browse files Browse the repository at this point in the history
Signed-off-by: Uilian Ries <[email protected]>
  • Loading branch information
uilianries committed Jul 2, 2019
1 parent 0dca381 commit c15d9fd
Show file tree
Hide file tree
Showing 13 changed files with 437 additions and 2 deletions.
22 changes: 22 additions & 0 deletions .ci/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash

set -e
set -x

if [[ "$(uname -s)" == 'Darwin' ]]; then
brew update || brew update
brew outdated pyenv || brew upgrade pyenv
brew install pyenv-virtualenv

if which pyenv > /dev/null; then
eval "$(pyenv init -)"
fi

pyenv install 3.7.1
pyenv virtualenv 3.7.1 conan
pyenv rehash
pyenv activate conan
fi

pip install codecov
pip install -e .[test]
17 changes: 17 additions & 0 deletions .ci/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

set -e
set -x

if [[ "$(uname -s)" == 'Darwin' ]]; then
if which pyenv > /dev/null; then
eval "$(pyenv init -)"
fi
pyenv activate conan
fi

python setup.py sdist
pushd tests
pytest -v -s --cov=bintray
mv .coverage ..
popd
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,4 @@ venv.bak/

# mypy
.mypy_cache/
.idea/
35 changes: 35 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
matrix:
fast_finish: true
include:
- os: linux
dist: xenial
language: python
python: '3.7'

install:
- chmod +x .ci/install.sh
- ".ci/install.sh"

script:
- chmod +x .ci/run.sh
- ".ci/run.sh"

after_success:
- codecov

deploy:
- provider: pypi
user: ${PYPI_USERNAME}
password: ${PYPI_PASSWORD}
on:
tags: true
skip_cleanup: true
skip_existing: true
- provider: pypi
user: ${TEST_PYPI_USERNAME}
server: https://test.pypi.org/legacy/
password: ${TEST_PYPI_PASSWORD}
on:
branch: master
skip_cleanup: true
skip_existing: true
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
# bintray-python
Python wrapper for Bintray API
# Bintray Python

**The Python wrapper for Bintray API**

#### DOCUMENTATION

Please, read the official documentation from Bintray: https://bintray.com/docs/api

#### LICENSE
[MIT](LICENSE)
Empty file added bintray/__init__.py
Empty file.
149 changes: 149 additions & 0 deletions bintray/bintray.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" Python Wrapper for Bintray API
https://bintray.com/docs/api
"""
import os
import logging
import requests
from requests.auth import HTTPBasicAuth


class Bintray(object):
""" Python Wrapper for Bintray API
"""

# Bintray API URL
BINTRAY_URL = "https://api.bintray.com"

def __init__(self, username=None, api_key=None):
""" Initialize arguments for login
:param username: Bintray username
:param api_key: Bintray API Key
"""
self._username = username or os.getenv("BINTRAY_USERNAME")
self._password = api_key or os.getenv("BINTRAY_API_KEY")

self._logger = logging.getLogger(__file__)
self._logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(formatter)
self._logger.addHandler(ch)

def _get_authentication(self):
""" Retrieve Basic HTTP Authentication based on username and API key
:return: Basic Authentication handler
"""
if not self._username or not self._password:
return None
return HTTPBasicAuth(self._username, self._password)

def _add_status_code(self, response):
""" Update JSON result with error and status code
:param response: Requests response
:return: Response JSON
"""
json_data = response.json()
if isinstance(json_data, list):
json_data.append({"statusCode": response.status_code, "error": not response.ok})
else:
json_data.update({"statusCode": response.status_code, "error": not response.ok})
return json_data


def _bool_to_number(self, value):
""" Convert boolean result into numeric string
:param value: Any boolean value
:return: "1" when True. Otherwise, "0"
"""
return "1" if value else "0"

def _raise_error(self, message, response):
try:
response.raise_for_status()
except Exception as error:
raise Exception("{} ({}): {}".format(message, response.status_code, str(error)))

# Files

def get_package_files(self, subject, repo, package, include_unpublished=False):
""" Get all files in a given package.
When called by a user with publishing rights on the package,
includes unpublished files in the list. By default only published files are shown.
:param subject: username or organization
:param repo: repository name
:param package: package name
:param include_unpublished: Show not published files
:return: List with all files
"""
parameters = {"include_unpublished": self._bool_to_number(include_unpublished)}
url = "{}/packages/{}/{}/{}/files?include_unpublished={}".format(Bintray.BINTRAY_URL,
subject,
repo,
package,
include_unpublished)
response = requests.get(url, auth=self._get_authentication(), params=parameters)
if not response.ok:
self._raise_error("Could not list package files", response)
return self._add_status_code(response)

# Content Uploading & Publishing

def upload_content(self, subject, repo, package, version, remote_file_path, local_file_path,
publish=True, override=False, explode=False):
"""
Upload content to the specified repository path, with package and version information (both required).
:param subject: username or organization
:param repo: repository name
:param package: package name
:param version: package version
:param remote_file_path: file name to be used on Bintray
:param local_file_path: file path to be uploaded
:param publish: publish after uploading
:param override: override remote file
:param explode: explode remote file
:return:
"""
url = "{}/content/{}/{}/{}/{}/{}".format(Bintray.BINTRAY_URL, subject, repo, package,
version, remote_file_path)
parameters = {"publish": self._bool_to_number(publish),
"override": self._bool_to_number(override),
"explode": self._bool_to_number(explode)}

with open(local_file_path, 'rb') as file_content:
response = requests.put(url, auth=self._get_authentication(), params=parameters,
data=file_content)
if response.status_code != 201:
self._raise_error("Could not upload", response)
self._logger.info("Upload successfully: {}".format(url))
return self._add_status_code(response)

# Content Downloading

def download_content(self, subject, repo, remote_file_path, local_file_path):
""" Download content from the specified repository path.
:param subject: username or organization
:param repo: repository name
:param remote_file_path: file name to be downloaded from Bintray
:param local_file_path: file name to be stored in local storage
"""
download_base_url = "https://dl.bintray.com"
url = "{}/{}/{}/{}".format(download_base_url, subject, repo, remote_file_path)
response = requests.get(url, auth=self._get_authentication())
if not response.ok:
self._raise_error("Could not download file content", response)
with open(local_file_path, 'wb') as local_fd:
local_fd.write(response.content)
self._logger.info("Download successfully: {}".format(url))
return self._add_status_code(response)
1 change: 1 addition & 0 deletions bintray/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
requests==2.22.0
2 changes: 2 additions & 0 deletions bintray/requirements_test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pytest==5.0.0
pytest-cov==2.7.1
124 changes: 124 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""A setuptools based setup module.
See:
https://packaging.python.org/en/latest/distributing.html
https://github.com/pypa/sampleproject
"""

# Always prefer setuptools over distutils
import re
import os
from setuptools import setup, find_packages
# To use a consistent encoding
from codecs import open


here = os.path.abspath(os.path.dirname(__file__))

# Get the long description from the README file
with open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
long_description = f.read()


def get_requires(filename):
requirements = []
with open(filename) as req_file:
for line in req_file.read().splitlines():
if not line.strip().startswith("#"):
requirements.append(line)
return requirements


def load_version():
"""Loads a file content"""
filename = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)),
"bincrafters_conventions", "bincrafters_conventions.py"))
with open(filename, "rt") as version_file:
conan_init = version_file.read()
version = re.search("__version__ = '([0-9a-z.-]+)'", conan_init).group(1)
return version

setup(
name='bincrafters_conventions',
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# https://packaging.python.org/en/latest/single_source_version.html
version=load_version(),

# This is an optional longer description of your project that represents
# the body of text which users will see when they visit PyPI.
#
# Often, this is the same as your README, so you can just read it in from
# that file directly (as we have already done above)
#
# This field corresponds to the "Description" metadata field:
# https://packaging.python.org/specifications/core-metadata/#description-optional
long_description=long_description, # Optional

description='Python Wrapper for Bintray API',

# The project's main homepage.
url='https://github.com/uilianries/bintray-python',

# Author details
author='Uilian Ries',
author_email='[email protected]',

# Choose your license
license='MIT',

# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Topic :: Software Development :: Build Tools',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3',
],

# What does your project relate to?
keywords=['jfrog', 'bintray', 'api', 'libraries', 'developer', 'manager',
'dependency', 'package', 'python', 'wrapper'],

# You can just specify the packages manually here if your project is
# simple. Or you can use find_packages().
packages=find_packages(exclude=['tests']),

# Alternatively, if you want to distribute just a my_module.py, uncomment
# this:
# py_modules=["my_module"],

# List run-time dependencies here. These will be installed by pip when
# your project is installed. For an analysis of "install_requires" vs pip's
# requirements files see:
# https://packaging.python.org/en/latest/requirements.html
install_requires=get_requires(os.path.join('bintray', 'requirements.txt')),

# List additional groups of dependencies here (e.g. development
# dependencies). You can install these using the following syntax,
# for example:
# $ pip install -e .[dev,test]
extras_require={
'test': get_requires(os.path.join('bintray', 'requirements_test.txt'))
},

# If there are data files included in your packages that need to be
# installed, specify them here. If using Python 2.6 or less, then these
# have to be included in MANIFEST.in as well.
package_data={
'': ['*.md'],
'bintray': ['*.txt'],
},

# Although 'package_data' is the preferred approach, in some case you may
# need to place data files outside of your packages. See:
# http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa
# In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
# data_files=[('my_data', ['data/data_file'])],

# To provide executable scripts, use entry points in preference to the
# "scripts" keyword. Entry points provide cross-platform support and allow
# pip to create the appropriate form of executable for the target platform.
#entry_points={
#
#},
)
Empty file added tests/__init__.py
Empty file.
1 change: 1 addition & 0 deletions tests/packages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"3.5.2": {"2bb76c9adac7b8cd7c5e3b377ac9f06934aba606": 1, "66c5327ebdcecae0a01a863939964495fa019a06": 3, "b28bcde4cbb80d2db194739511c07ae209a4ab3a": 5, "6cc50b139b9c3d27b3e9042d5f5372d327b3a9f7": 1, "63da998e3642b50bee33f4449826b2d623661505": 1, "20b82f03d2422a719e5621a9d3c32ba02e7e332d": 1, "d0ec62fc032e5a10524fa454546aa1bdbb22baf8": 1}, "3.6.1": {"480b6c2b270c52242654b615baf089e7adedb890": 2, "b759e10106fc0b4923414b05bb78eba0bbc8b30b": 1, "6cc50b139b9c3d27b3e9042d5f5372d327b3a9f7": 23, "a506942c4fdc4003ced5ed2afee2a0486db24337": 21, "3b345617ce2bd705ca33e4a8969fc7eb031ea30a": 23, "2a0b7ed68eb56c5f3ad8a5651d2edee732783124": 1, "8cf01e2f50fcd6b63525e70584df0326550364e1": 3, "c0e9c564ed574ab16d81fcdb2696580d62f12a3b": 1, "c32596dcd26b8c708dc3d19cb73738d2b48f12a8": 1, "963bb116781855de98dbb23aaac41621e5d312d8": 1, "7bd6f2c3d5c4e48a75805376b58cde753392f711": 1, "038f8796e196b3dba76fcc5fd4ef5d3d9c6866ec": 8, "ab4797bac78775d9e373e5fbcbd9b3fb01264904": 1, "56e0cf6d16ee57367a0661ab743f4e43b29223f8": 13, "053ea29eb0edc6b1695c893b738a971110c756fd": 1, "bf8bf3502ae799c155450cfd720fb00e0f31c39c": 1, "3d70729ad0dce2c6d7b062b47c24bd4534a22930": 1, "d0ec62fc032e5a10524fa454546aa1bdbb22baf8": 1, "1a651c5b4129ad794b88522bece2281a7453aee4": 2, "4d887c1c2779c63d2cdd81580698d2e22cb35b29": 9, "63da998e3642b50bee33f4449826b2d623661505": 3, "d8916f6016f745b1163b85952f5e984556ca5311": 8, "bf81e8e693fa7ccd30472785bfbb7216b8c6109b": 1, "d351525cc53ebe68279edf1978846402420066e7": 1, "853c4b61e2571e98cd7b854c1cda6bc111b8b32c": 1, "df81ad20137149d7a51276fd3e24009b45e5964a": 1, "f45f7e8b462318f40caf39189f9ba2d2d6dcfac0": 1}, "3.5.1": {"d351525cc53ebe68279edf1978846402420066e7": 90, "4d887c1c2779c63d2cdd81580698d2e22cb35b29": 10, "b759e10106fc0b4923414b05bb78eba0bbc8b30b": 1, "8cf01e2f50fcd6b63525e70584df0326550364e1": 39, "d0ec62fc032e5a10524fa454546aa1bdbb22baf8": 2, "66c5327ebdcecae0a01a863939964495fa019a06": 1, "853c4b61e2571e98cd7b854c1cda6bc111b8b32c": 1}}
Loading

0 comments on commit c15d9fd

Please sign in to comment.