1. Hong Minhee
  2. bitbucket-distutils

Commits

Hong Minhee  committed d393056

First commit.

  • Participants
  • Branches default

Comments (0)

Files changed (3)

File README.rst

View file
+bitbucket-distutils
+===================
+
+Intro
+-----
+
+Distribute_/setuptools_/distutils_ command for Bitbucket_. You can use
+Bitbucket downloads instead of PyPI_ downloads for release.
+
+To use this, follow the instruction.
+
+.. _Distribute: http://packages.python.org/distribute/
+.. _setuptools: http://pypi.python.org/pypi/setuptools
+.. _distutils: http://docs.python.org/library/distutils.html
+.. _Bitbucket: https://bitbucket.org/
+.. _PyPI: http://pypi.python.org/
+
+
+Instruction
+-----------
+
+First of all your software must be packaged within the standard distribution
+way: use distutils_, Distribute_ or setuptools_.  This package contains
+an extension command for that.  Insert the following lines into the head of
+your ``setup.py`` file::
+
+    try:
+        from bitbucket_distutils import commands
+    except ImportError:
+        commands = {}
+
+and then, pass the ``commands`` dictionary into your ``setup()`` function,
+with ``setup_requires`` parameter::
+
+    setup(name='YourPackageName',
+          version='1.2.3',
+          ...,
+          setup_requires=['bitbucket-distutils'],
+          commands=commands)
+
+Now there will be the overwritten ``upload`` command for your ``setup.py``::
+
+    $ python setup.py upload --help
+    Common commands: (see '--help-commands' for more)
+
+    ...
+
+    Options for 'upload' command:
+      --bb-repository (-R)  Bitbucket repository name e.g. user/reponame
+      --bb-username (-u)    Bitbucket username
+      --bb-password (-p)    Bitbucket password
+
+    ...
+
+As you can see there are ``--bb-``-prefixed options for the command.
+If ``-u``/``--bb-username`` and ``--p``/``--bb-password`` are not present,
+it shows the prompt.  ``-R``/``--bb-repository`` is required.
+
+
+Upload
+------
+
+Upload is very easy::
+
+    $ python setup.py sdist upload -R user/reponame register
+
+By explained:
+
+``sdist``
+    Makes the source distribution file.  If your package name is
+    ``YourPackageName`` and its version is ``1.2.3``, and then its file name
+    becomes ``YourPackageName-1.2.3.tar.gz``.
+
+``upload -R user/reponame``
+    Uploads the built source distribution file into your Bitbucket repository.
+    It does not mean that it will be version-controlled, but it will be simply
+    uploaded to its downloads page.
+
+``register``
+    Using the Bitbucket download URL registers the package of this version
+    into PyPI.
+    The URL of PyPI page will be http://pypi.python.org/YourPackageName/1.2.3
+
+
+Defaulting options
+------------------
+
+You can make default values for these options by specifying in the ``setup.cfg``
+configuration file.  For example, if you want to default ``--bb-repository``,
+make ``setup.cfg`` file like (hyphens becomes underscores)::
+
+    [upload]
+    bb_repository = user/reponame
+
+You can make a shorthand alias as well::
+
+    [aliases]
+    release = sdist upload register
+
+
+Author and license
+------------------
+
+It is distributed under Public Domain.  Just do what you do with this.  :-)
+Written by `Hong Minhee`__.
+
+You can checkout the source code from its `Bitbucket Mercurial repository`__::
+
+    $ hg clone https://bitbucket.org/dahlia/bitbucket-distutils
+
+If you found a bug, please report it to the `issue tracker`__.
+
+__ http://dahlia.kr/
+__ https://bitbucket.org/dahlia/bitbucket-distutils
+__ https://bitbucket.org/dahlia/bitbucket-distutils/issues

File bitbucket_distutils.py

View file
+from __future__ import with_statement
+import re
+import os.path
+import mimetypes
+import getpass
+from distutils.core import Command
+from distutils.errors import DistutilsOptionError
+
+__author__ = 'Hong Minhee'
+__email__ = 'minhee' '@' 'dahlia.kr'
+__copyright__ = 'Copyright 2012, Hong Minhee'
+__license__ = 'Public Domain'
+__version__ = '0.1.0'
+
+
+class BitbucketClient(object):
+    """Minimal Bitbucket that signs in and uploads files."""
+
+    def __init__(self, username, password, repository):
+        import requests  # IF YOU SEE ImportError, DO `easy_install requests`
+        self.session = requests.session(
+        )  # IF YOU SEE AttributeError, DO `easy_install -U request`
+        self.signin(username, password)
+        self.repository = repository
+
+    def signin(self, username, password):
+        url = 'https://bitbucket.org/account/signin/'
+        form = self.session.get(url)
+        token = self._find_field(form.content, 'csrfmiddlewaretoken')
+        data = {'username': username, 'password': password,
+                'csrfmiddlewaretoken': token}
+        login = self.session.post(url, data=data, cookies=form.cookies,
+                                  headers={'Referer': url})
+        self.cookies = login.cookies
+
+    def upload(self, filename):
+        try:
+            from collections import OrderedDict as odict
+        except ImportError:
+            from odict \
+            import odict  # IF YOU SEE ImportError, DO `easy_install odict`
+        url = 'https://bitbucket.org/' + self.repository + '/downloads'
+        s3_url = 'https://bbuseruploads.s3.amazonaws.com/'
+        fields = ('acl', 'success_action_redirect', 'AWSAccessKeyId',
+                  'Policy', 'Signature', 'Content-Type', 'key')
+        form = self.session.get(url, cookies=self.cookies)
+        data = odict((f, self._find_field(form.content, f)) for f in fields)
+        basename = os.path.basename(filename)
+        data['Content-Type'] = mimetypes.guess_type(filename)[0]
+        data['key'] += basename
+        class FoolishHack(object):
+            """requests doesn't maintain form fields' order, so we have to
+            do workaround it.  Works with requests==0.10.8"""
+            def __init__(self, odict):
+                self.odict = odict
+            def copy(self):
+                return self.odict
+        with open(filename, 'rb') as fp:
+            files = {'file': (basename, fp)}
+            response = self.session.post(s3_url, data=FoolishHack(data),
+                                         files=files)
+        if 300 <= response.status_code < 400 and 'location' in response.headers:
+            response = self.session.get(response.headers['location'])
+        return url + '/' + basename
+
+    def _find_field(self, form_string, name):
+        pattern = (r'<input\s[^<>]*name=[\'"]' + re.escape(name) +
+                   r'[\'"]\s[^>]*>')
+        tag = re.search(pattern, form_string)
+        token = re.search(r'value=[\'"]([^\'"]+)[\'"]', tag.group(0))
+        return token.group(1)
+
+
+class upload(Command):
+    """Upload package to Bitbucket."""
+
+    description = __doc__
+    user_options = [
+        ('bb-repository=', 'R', 'Bitbucket repository name e.g. user/reponame'),
+        ('bb-username=', 'u', 'Bitbucket username'),
+        ('bb-password=', 'p', 'Bitbucket password')
+    ]
+
+    def initialize_options(self):
+        self.bb_repository = ''
+        self.bb_username = ''
+        self.bb_password = ''
+        self.bb_password_prompt = False
+
+    def finalize_options(self):
+        if not self.bb_username:
+            self.bb_username = raw_input('Bitbucket username: ')
+        if not self.bb_password:
+            self.bb_password = getpass.getpass('Bitbucket password: ')
+        if not re.match(r'^[-_.a-z]+/[-_.a-z]+$', self.bb_repository):
+            raise DistutilsOptionError('-R/--bb-repository option is incorrect')
+
+    def run(self):
+        if not self.distribution.dist_files:
+            raise DistutilsOptionError(
+                'No dist file created in earlier command'
+            )
+        bb = BitbucketClient(self.bb_username,
+                             self.bb_password,
+                             self.bb_repository)
+        sdist_url = None
+        for command, pyversion, filename in self.distribution.dist_files:
+            url = bb.upload(filename)
+            if command == 'sdist':
+                sdist_url = url
+        if sdist_url:
+            url = sdist_url
+        self.distribution.metadata.download_url = url
+

File setup.py

View file
+import sys
+try:
+    from setuptools import setup
+except ImportError:
+    from distutils.core import setup
+from bitbucket_distutils import __author__, __email__, __license__, __version__
+
+under_270 = sys.version_info < (2, 7, 0)
+
+
+setup(name='bitbucket-distutils',
+      version=__version__,
+      py_modules=['bitbucket_distutils'],
+      author=__author__,
+      author_email=__email__,
+      license=__license__,
+      install_requires=['requests'] + (['odict'] if under_270 else []))
+