├── .gitignore ├── .hgignore ├── HACKING.txt ├── MANIFEST.in ├── NEWS.txt ├── README.rst ├── bootstrap.py ├── buildout.cfg ├── pythrust ├── __init__.py ├── api.py ├── base.py └── window.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | .installed.cfg 4 | bin 5 | develop-eggs 6 | 7 | *.egg-info 8 | 9 | tmp 10 | build 11 | dist 12 | 13 | vendor 14 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | .installed.cfg 4 | bin 5 | develop-eggs 6 | 7 | *.egg-info 8 | 9 | tmp 10 | build 11 | dist 12 | -------------------------------------------------------------------------------- /HACKING.txt: -------------------------------------------------------------------------------- 1 | Development setup 2 | ================= 3 | 4 | To create a buildout, 5 | 6 | $ python bootstrap.py 7 | $ bin/buildout 8 | 9 | Release HOWTO 10 | ============= 11 | 12 | To make a release, 13 | 14 | 1) Update release date/version in NEWS.txt and setup.py 15 | 2) Run 'python setup.py sdist' 16 | 3) Test the generated source distribution in dist/ 17 | 4) Upload to PyPI: 'python setup.py sdist register upload' 18 | 5) Increase version in setup.py (for next release) 19 | 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include NEWS.txt 3 | -------------------------------------------------------------------------------- /NEWS.txt: -------------------------------------------------------------------------------- 1 | .. This is your project NEWS file which will contain the release notes. 2 | .. Example: http://www.python.org/download/releases/2.6/NEWS.txt 3 | .. The content of this file, along with README.rst, will appear in your 4 | .. project's PyPI page. 5 | 6 | News 7 | ==== 8 | 9 | 0.7.5 10 | ----- 11 | 12 | *Release date: UNRELEASED* 13 | 14 | * Base API mechanism 15 | * Window bindings 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pythrust 2 | ======== 3 | 4 | .. _Thrust: https://github.com/breach/thrust 5 | .. _Python3: https://www.python.org/ 6 | .. _`Thrust Documentation`: https://github.com/breach/thrust/tree/master/docs 7 | 8 | Official Python bindings library for Thrust_ 9 | 10 | Getting Started 11 | =============== 12 | 13 | ``pythrust`` requires Python3_ as it relies on the ``asyncio`` module. 14 | 15 | :: 16 | 17 | 18 | pip3 install pythrust [--user] 19 | 20 | At install, pythrust's ``setup.py`` automatically downloads a binary 21 | distribution of Thrust for the current platform. 22 | 23 | :: 24 | 25 | import pythrust 26 | import asyncio 27 | 28 | loop = asyncio.get_event_loop() 29 | api = pythrust.API(loop) 30 | 31 | asyncio.async(api.spawn()) 32 | 33 | window = api.window({}) 34 | asyncio.async(window.show()) 35 | 36 | loop.run_forever() 37 | 38 | Status 39 | ====== 40 | 41 | Support is only limited to the ``window`` object for now. Contributions are 42 | welcomed 43 | 44 | Support tested on Linux and MacOSX. Still a few bugs on Windows. 45 | 46 | Documentation 47 | ============= 48 | 49 | Pending specific pythrust documentation, full API reference is available 50 | in the `Thrust Documentation`_ 51 | -------------------------------------------------------------------------------- /bootstrap.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2006 Zope Corporation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Bootstrap a buildout-based project 15 | 16 | Simply run this script in a directory containing a buildout.cfg. 17 | The script accepts buildout command-line options, so you can 18 | use the -c option to specify an alternate configuration file. 19 | 20 | $Id: bootstrap.py 102545 2009-08-06 14:49:47Z chrisw $ 21 | """ 22 | 23 | import os, shutil, sys, tempfile, urllib2 24 | from optparse import OptionParser 25 | 26 | tmpeggs = tempfile.mkdtemp() 27 | 28 | is_jython = sys.platform.startswith('java') 29 | 30 | # parsing arguments 31 | parser = OptionParser() 32 | parser.add_option("-v", "--version", dest="version", 33 | help="use a specific zc.buildout version") 34 | parser.add_option("-d", "--distribute", 35 | action="store_true", dest="distribute", default=True, 36 | help="Use Disribute rather than Setuptools.") 37 | 38 | options, args = parser.parse_args() 39 | 40 | if options.version is not None: 41 | VERSION = '==%s' % options.version 42 | else: 43 | VERSION = '' 44 | 45 | USE_DISTRIBUTE = options.distribute 46 | args = args + ['bootstrap'] 47 | 48 | to_reload = False 49 | try: 50 | import pkg_resources 51 | if not hasattr(pkg_resources, '_distribute'): 52 | to_reload = True 53 | raise ImportError 54 | except ImportError: 55 | ez = {} 56 | if USE_DISTRIBUTE: 57 | exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' 58 | ).read() in ez 59 | ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) 60 | else: 61 | exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' 62 | ).read() in ez 63 | ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) 64 | 65 | if to_reload: 66 | reload(pkg_resources) 67 | else: 68 | import pkg_resources 69 | 70 | if sys.platform == 'win32': 71 | def quote(c): 72 | if ' ' in c: 73 | return '"%s"' % c # work around spawn lamosity on windows 74 | else: 75 | return c 76 | else: 77 | def quote (c): 78 | return c 79 | 80 | cmd = 'from setuptools.command.easy_install import main; main()' 81 | ws = pkg_resources.working_set 82 | 83 | if USE_DISTRIBUTE: 84 | requirement = 'distribute' 85 | else: 86 | requirement = 'setuptools' 87 | 88 | if is_jython: 89 | import subprocess 90 | 91 | assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', 92 | quote(tmpeggs), 'zc.buildout' + VERSION], 93 | env=dict(os.environ, 94 | PYTHONPATH= 95 | ws.find(pkg_resources.Requirement.parse(requirement)).location 96 | ), 97 | ).wait() == 0 98 | 99 | else: 100 | assert os.spawnle( 101 | os.P_WAIT, sys.executable, quote (sys.executable), 102 | '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, 103 | dict(os.environ, 104 | PYTHONPATH= 105 | ws.find(pkg_resources.Requirement.parse(requirement)).location 106 | ), 107 | ) == 0 108 | 109 | ws.add_entry(tmpeggs) 110 | ws.require('zc.buildout' + VERSION) 111 | import zc.buildout.buildout 112 | zc.buildout.buildout.main(args) 113 | shutil.rmtree(tmpeggs) 114 | -------------------------------------------------------------------------------- /buildout.cfg: -------------------------------------------------------------------------------- 1 | [buildout] 2 | parts = python scripts 3 | develop = . 4 | eggs = pythrust 5 | 6 | [python] 7 | recipe = zc.recipe.egg 8 | interpreter = python 9 | eggs = ${buildout:eggs} 10 | 11 | [scripts] 12 | recipe = zc.recipe.egg:scripts 13 | eggs = ${buildout:eggs} 14 | -------------------------------------------------------------------------------- /pythrust/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import API 2 | from .window import Window 3 | -------------------------------------------------------------------------------- /pythrust/api.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import locale 3 | import os 4 | import inspect 5 | import sys 6 | import json 7 | import signal 8 | 9 | from asyncio.subprocess import PIPE 10 | 11 | from .window import Window 12 | 13 | class API: 14 | def __init__(self, loop=None): 15 | if loop is None: 16 | self.loop = asyncio.get_event_loop() 17 | else: 18 | self.loop = loop 19 | 20 | self.TARGET_PLATFORM = { 21 | 'cygwin': 'win32', 22 | 'darwin': 'darwin', 23 | 'linux': 'linux', 24 | 'win32': 'win32', 25 | }[sys.platform] 26 | self.SOURCE_ROOT = os.path.dirname(os.path.abspath(inspect.getfile( 27 | inspect.currentframe()))) 28 | self.THRUST_PATH = os.path.join(self.SOURCE_ROOT, 'vendor', 29 | 'thrust') 30 | self.THRUST_EXEC = { 31 | 'linux': os.path.join(self.THRUST_PATH, 'thrust_shell'), 32 | 'win32': os.path.join(self.THRUST_PATH, 'thrust_shell'), 33 | 'darwin': os.path.join(self.THRUST_PATH, 'ThrustShell.app', 34 | 'Contents', 'MacOS', 'ThrustShell') 35 | }[self.TARGET_PLATFORM] 36 | 37 | self.BOUNDARY = '--(Foo)++__THRUST_SHELL_BOUNDARY__++(Bar)--' 38 | 39 | self.actions = {} 40 | self.objects = {} 41 | 42 | self.next_id = 0 43 | self.proc = None 44 | self.spawn_cnd = asyncio.Condition(loop=self.loop) 45 | 46 | @asyncio.coroutine 47 | def spawn(self, exec_path=None): 48 | if exec_path is None: 49 | exec_path = self.THRUST_EXEC 50 | print("exec_path: ", exec_path) 51 | 52 | # start Thrust process 53 | self.proc = yield from asyncio.create_subprocess_exec(exec_path, 54 | stdin=PIPE, 55 | stdout=PIPE, 56 | loop=self.loop) 57 | yield from self.spawn_cnd.acquire() 58 | print("pid: %s" % self.proc.pid) 59 | self.spawn_cnd.notify_all() 60 | self.spawn_cnd.release() 61 | 62 | acc = ''; 63 | while True: 64 | line = yield from self.proc.stdout.readline() 65 | if not line: 66 | break 67 | acc = acc + line.decode('utf8').rstrip(); 68 | splits = acc.split(self.BOUNDARY); 69 | 70 | while len(splits) > 1: 71 | data = splits.pop(0) 72 | acc = self.BOUNDARY.join(splits) 73 | if data and len(data) > 0: 74 | action = json.loads(data) 75 | print('RECEIVED: ', str(action)) 76 | if action['_action'] == 'reply': 77 | sync = self.actions[str(action['_id'])] 78 | if sync is not None: 79 | sync['error'] = action['_error'] 80 | sync['result'] = action['_result'] 81 | yield from sync['condition'].acquire() 82 | sync['condition'].notify_all() 83 | sync['condition'].release() 84 | if action['_action'] == 'event': 85 | obj = self.objects[str(action['_target'])] 86 | if obj is not None: 87 | obj.emit(action['_type'], action['_event']) 88 | if action['_action'] == 'invoke': 89 | obj = self.objects[str(action['_target'])] 90 | if obj is not None: 91 | pass 92 | 93 | try: 94 | self.proc.kill() 95 | except ProcessLookupError: 96 | pass 97 | rc = yield from self.proc.wait() 98 | return rc; 99 | 100 | @asyncio.coroutine 101 | def pre(self): 102 | if self.proc is None: 103 | yield from self.spawn_cnd.acquire() 104 | yield from self.spawn_cnd.wait() 105 | self.spawn_cnd.release() 106 | 107 | @asyncio.coroutine 108 | def perform(self, action): 109 | yield from self.pre() 110 | condition = asyncio.Condition(loop=self.loop) 111 | sync = { 112 | 'condition': condition, 113 | 'error': None, 114 | 'result': None 115 | } 116 | self.actions[str(action['_id'])] = sync 117 | print('PERFORM: ', json.dumps(action)) 118 | data = str(json.dumps(action)) + '\n' + self.BOUNDARY + '\n' 119 | self.proc.stdin.write(data.encode()) 120 | yield from condition.acquire() 121 | yield from condition.wait() 122 | condition.release() 123 | result = sync['result'] 124 | del self.actions[str(action['_id'])] 125 | return result 126 | 127 | def action_id(self): 128 | self.next_id = self.next_id + 1; 129 | return self.next_id 130 | 131 | def register(self, obj): 132 | self.objects[str(obj.target)] = obj; 133 | 134 | def unregister(self, obj): 135 | del self.objects[str(obj.target)]; 136 | 137 | def window(self, args): 138 | return Window(self, args, loop=self.loop) 139 | 140 | -------------------------------------------------------------------------------- /pythrust/base.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import pyee 3 | 4 | class Base(pyee.EventEmitter): 5 | def __init__(self, api, type, args, loop=None): 6 | super().__init__() 7 | if loop is None: 8 | self.loop = asyncio.get_event_loop() 9 | else: 10 | self.loop = loop 11 | print('BASE CONSTRUCTOR') 12 | self.api = api 13 | self.type = type 14 | self.target = None 15 | self.destroyed = False 16 | self.create_cnd = asyncio.Condition(loop=loop) 17 | asyncio.async(self.create(args)) 18 | 19 | @asyncio.coroutine 20 | def create(self, args): 21 | result = yield from self.api.perform({ 22 | '_id': self.api.action_id(), 23 | '_action': 'create', 24 | '_type': self.type, 25 | '_args': args 26 | }) 27 | self.target = result['_target'] 28 | self.api.register(self) 29 | yield from self.create_cnd.acquire() 30 | self.create_cnd.notify_all() 31 | self.create_cnd.release() 32 | 33 | @asyncio.coroutine 34 | def destroy(self): 35 | result = yield from self.api.perform({ 36 | '_id': self.api.action_id(), 37 | '_action': 'delete', 38 | '_target': self.target 39 | }, loop=self.loop) 40 | self.destroyed = True 41 | self.api.unregister(self) 42 | 43 | @asyncio.coroutine 44 | def pre(self): 45 | if self.target is None: 46 | yield from self.create_cnd.acquire() 47 | yield from self.create_cnd.wait() 48 | self.create_cnd.release() 49 | 50 | @asyncio.coroutine 51 | def call(self, method, args): 52 | yield from self.pre() 53 | result = yield from self.api.perform({ 54 | '_id': self.api.action_id(), 55 | '_action': 'call', 56 | '_target': self.target, 57 | '_method': method, 58 | '_args': args 59 | }) 60 | return result 61 | 62 | @asyncio.coroutine 63 | def invoke(self, method, args): 64 | yield from self.pre() 65 | if self['invoke_' + method] is not None: 66 | raise NameError('Method not found [', self.type, ']: ', method) 67 | result = yield from self['infoke_' + method]() 68 | return result 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /pythrust/window.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from .base import Base 4 | 5 | class Window(Base): 6 | def __init__(self, api, args, loop=None): 7 | super().__init__(api, 'window', args, loop=loop) 8 | print('WINDOW CONSTRUCTOR') 9 | 10 | @asyncio.coroutine 11 | def create(self, args): 12 | if 'session' in args: 13 | yield from args.session.pre() 14 | yield from super().create(args) 15 | 16 | @asyncio.coroutine 17 | def show(self): 18 | r = yield from super().call('show', {}) 19 | return r 20 | 21 | @asyncio.coroutine 22 | def focus(self): 23 | r = yield from super().call('focus', {}) 24 | return r 25 | 26 | @asyncio.coroutine 27 | def maximize(self): 28 | r = yield from super().call('maximize', {}) 29 | return r 30 | 31 | @asyncio.coroutine 32 | def unmaximize(self): 33 | r = yield from super().call('unmaximize', {}) 34 | return r 35 | 36 | @asyncio.coroutine 37 | def close(self): 38 | r = yield from super().call('close', {}) 39 | return r 40 | 41 | @asyncio.coroutine 42 | def set_title(self, title): 43 | r = yield from super().call('set_title', { 44 | 'title': title 45 | }) 46 | return r 47 | 48 | @asyncio.coroutine 49 | def set_fullscreen(self, fullscreen): 50 | r = yield from super().call('set_fullscreen', { 51 | 'fullscreen': fullscreen 52 | }) 53 | return r 54 | 55 | @asyncio.coroutine 56 | def set_kiosk(self, kiosk): 57 | r = yield from super().call('set_kiosk', { 58 | 'kiosk': kiosk 59 | }) 60 | return r 61 | 62 | @asyncio.coroutine 63 | def open_devtools(self): 64 | r = yield from super().call('open_devtools', {}) 65 | return r 66 | 67 | @asyncio.coroutine 68 | def close_devtools(self): 69 | r = yield from super().call('close_devtools', {}) 70 | return r 71 | 72 | @asyncio.coroutine 73 | def move(self, x, y): 74 | r = yield from super().call('move', { 75 | 'x': x, 76 | 'y': y 77 | }) 78 | return r 79 | 80 | @asyncio.coroutine 81 | def resize(self, width, height): 82 | r = yield from super().call('resize', { 83 | 'width': width, 84 | 'width': height 85 | }) 86 | return r 87 | 88 | @asyncio.coroutine 89 | def is_closed(self): 90 | r = yield from super().call('is_closed', {}) 91 | return r['closed'] 92 | 93 | @asyncio.coroutine 94 | def is_maximized(self): 95 | r = yield from super().call('is_maximized', {}) 96 | return r['maximized'] 97 | 98 | @asyncio.coroutine 99 | def is_minimized(self): 100 | r = yield from super().call('is_minimized', {}) 101 | return r['minimized'] 102 | 103 | @asyncio.coroutine 104 | def is_fullscreen(self): 105 | r = yield from super().call('is_fullscreen', {}) 106 | return r['fullscreen'] 107 | 108 | @asyncio.coroutine 109 | def is_kiosk(self): 110 | r = yield from super().call('is_kiosk', {}) 111 | return r['kiosk'] 112 | 113 | @asyncio.coroutine 114 | def is_devtools_opened(self): 115 | r = yield from super().call('is_devtools_opened', {}) 116 | return r['opened'] 117 | 118 | @asyncio.coroutine 119 | def size(self): 120 | r = yield from super().call('size', {}) 121 | return r['size'] 122 | 123 | @asyncio.coroutine 124 | def position(self): 125 | r = yield from super().call('position', {}) 126 | return r['position'] 127 | 128 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | from setuptools.command.install import install as _install 5 | from setuptools.command.develop import develop as _develop 6 | 7 | import urllib 8 | import contextlib 9 | import zipfile 10 | import tempfile 11 | import errno 12 | import os 13 | import inspect 14 | import sys 15 | import shutil 16 | import stat 17 | import subprocess 18 | import platform 19 | 20 | here = os.path.abspath(os.path.dirname(__file__)) 21 | README = open(os.path.join(here, 'README.rst')).read() 22 | NEWS = open(os.path.join(here, 'NEWS.txt')).read() 23 | 24 | 25 | THRUST_VERSION = 'v0.7.5' 26 | 27 | version = '0.7.5' 28 | install_requires = [ 29 | 'pyee' 30 | ] 31 | 32 | 33 | ARCH = { 34 | 'cygwin': '32bit', 35 | 'darwin': '64bit', 36 | 'linux': platform.architecture()[0], 37 | 'win32': '32bit', 38 | }[sys.platform] 39 | 40 | DIST_ARCH = { 41 | '32bit': 'ia32', 42 | '64bit': 'x64', 43 | }[ARCH] 44 | THRUST_PLATFORM = { 45 | 'darwin': { 46 | 'x64': 'darwin-x64' 47 | }, 48 | 'linux': { 49 | 'ia32': 'linux-ia32', 50 | 'x64': 'linux-x64', 51 | }, 52 | 'cygwin': { 53 | 'ia32': 'win32-ia32' 54 | }, 55 | 'win32': { 56 | 'ia32': 'win32-ia32' 57 | } 58 | }[sys.platform][DIST_ARCH] 59 | THRUST_BASE_URL = 'https://github.com/breach/thrust/releases/download/' 60 | 61 | def execute(argv): 62 | try: 63 | output = subprocess.check_output(argv) 64 | return output 65 | except subprocess.CalledProcessError as e: 66 | print(e.output) 67 | raise e 68 | 69 | 70 | def download_and_extract(destination, url): 71 | with tempfile.NamedTemporaryFile() as t: 72 | with contextlib.closing(urllib.request.urlopen(url)) as u: 73 | while True: 74 | chunk = u.read(1024*1024) 75 | if not len(chunk): 76 | break 77 | sys.stderr.write('.') 78 | sys.stderr.flush() 79 | t.write(chunk) 80 | sys.stderr.write('\nExtracting to {0}\n'.format(destination)) 81 | sys.stderr.flush() 82 | if sys.platform == 'darwin': 83 | # Use unzip command on Mac to keep symbol links in zip file work. 84 | execute(['unzip', str(t.name), '-d', destination]) 85 | else: 86 | with zipfile.ZipFile(t) as z: 87 | z.extractall(destination) 88 | if sys.platform == 'linux': 89 | thrust_shell_path = os.path.join(destination, 'thrust_shell'); 90 | st = os.stat(thrust_shell_path) 91 | os.chmod(thrust_shell_path, 92 | st.st_mode | 93 | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) 94 | 95 | def rm_rf(path): 96 | try: 97 | shutil.rmtree(path) 98 | except OSError as e: 99 | if e.errno != errno.ENOENT: 100 | raise 101 | 102 | def boostrap(dest, base_url, version, platform, force=False): 103 | THRUST_PATH = os.path.realpath( 104 | os.path.join(dest, 'vendor', 'thrust')) 105 | THRUST_VERSION_PATH = os.path.join(THRUST_PATH, '.version') 106 | THRUST_RELEASE_FILENAME = 'thrust-' + version + '-' + platform + '.zip' 107 | THRUST_RELEASE_URL = base_url + version + '/' + THRUST_RELEASE_FILENAME; 108 | 109 | if force is False: 110 | existing_version = '' 111 | try: 112 | with open(THRUST_VERSION_PATH, 'r') as f: 113 | existing_version = f.readline().strip() 114 | except IOError as e: 115 | if e.errno != errno.ENOENT: 116 | raise 117 | if existing_version == THRUST_RELEASE_URL: 118 | sys.stderr.write('Found {0} at {1}\n'.format(version, THRUST_PATH)) 119 | sys.stderr.flush() 120 | return 121 | 122 | rm_rf(THRUST_PATH) 123 | os.makedirs(THRUST_PATH) 124 | 125 | sys.stderr.write('Downloading {0}...\n'.format(THRUST_RELEASE_URL)) 126 | sys.stderr.flush() 127 | download_and_extract(THRUST_PATH, THRUST_RELEASE_URL) 128 | with open(THRUST_VERSION_PATH, 'w') as f: 129 | f.write('{0}\n'.format(THRUST_RELEASE_URL)) 130 | 131 | 132 | 133 | def _post_install(dest): 134 | boostrap(dest, 135 | THRUST_BASE_URL, THRUST_VERSION, THRUST_PLATFORM) 136 | 137 | class my_install(_install): 138 | def run(self): 139 | _install.run(self) 140 | self.execute(_post_install, 141 | [os.path.join(self.install_platlib, self.config_vars['dist_name'])], 142 | msg="running post_install script") 143 | 144 | class my_develop(_develop): 145 | def run(self): 146 | _develop.run(self) 147 | self.execute(_post_install, 148 | [os.path.join(self.install_platlib, self.config_vars['dist_name'])], 149 | msg="running post_develop script") 150 | 151 | 152 | setup(name='pythrust', 153 | version=version, 154 | description="Python language bindings for Thrust", 155 | long_description=README + '\n\n' + NEWS, 156 | classifiers=[ 157 | # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers 158 | ], 159 | keywords='thrust application shell framework chromium content module python', 160 | author='Stanislas Polu', 161 | author_email='polu.stanislas@gmail.com', 162 | url='https://github.com/breach/pythrust', 163 | license='MIT', 164 | packages=find_packages(), 165 | include_package_data=True, 166 | zip_safe=False, 167 | install_requires=install_requires, 168 | cmdclass={ 169 | 'install': my_install, # override install 170 | 'develop': my_develop # develop is used for pip install -e . 171 | } 172 | ) 173 | --------------------------------------------------------------------------------