From 8b57e9c0da300546bc7987690261bb442d8c1bb5 Mon Sep 17 00:00:00 2001 From: Zach Steindler Date: Thu, 9 Jun 2011 22:27:06 -0400 Subject: [PATCH] initial commit --- README | 32 ++++++++++++ bin/simplepypi | 137 +++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 16 ++++++ 3 files changed, 185 insertions(+) create mode 100644 README create mode 100755 bin/simplepypi create mode 100644 setup.py diff --git a/README b/README new file mode 100644 index 0000000..46f3302 --- /dev/null +++ b/README @@ -0,0 +1,32 @@ +This is a really, really, simple HTTP PyPI-like server. + +It is intended to be used for companies or organizations that need a private +PyPi. + +It literally supports four functions: + + - Allows uploading of packages + - Downloading by package (or package and version) + - A / page that is navigatable with a web browser + - /pypi/ listing + +It does not support: + + - Stopping you from overwriting a package with the same name / version + - Registering packages + - Any sort of ACLs + +To use it run "simplepypi". You can upload packages by: + + - Adding this to your ~/.pypirc + + [local] + username: + password: + repository: http://127.0.0.1:8000 + + - Running this on the setup.py of your favorite package: + + python setup.py sdist upload -r local + +And that's it! diff --git a/bin/simplepypi b/bin/simplepypi new file mode 100755 index 0000000..c3d4d88 --- /dev/null +++ b/bin/simplepypi @@ -0,0 +1,137 @@ +#!/usr/bin/env python + +import os.path + +from twisted.internet import reactor +import twisted.python.log +from twisted.web.server import Site +from twisted.web.static import File + +import txroutes + + +def return404(request): + request.setResponseCode(404) + return '404 Not Found

Not found

' + +def get_package_path(package, version=None, want_file=False): + path = os.path.join(os.path.dirname(__file__), 'packages', package) + + if version: + path = os.path.join(path, version) + + if want_file: + path = os.path.join(path, '%s-%s.tar.gz' % (package, version)) + + return path + + +class Controller(object): + + def get_index(self, request): + return 'simplepypipackages' + + def post_index(self, request): + name = request.args.get('name', [None])[0] + version = request.args.get('version', [None])[0] + action = request.args.get(':action', [None])[0] + content = request.args.get('content', [None])[0] + + if name is not None and version is not None and content is not None \ + and action == 'file_upload': + + name = name.lower() + + path = get_package_path(name, version, want_file=True) + + if not os.path.exists(os.path.dirname(path)): + os.makedirs(os.path.dirname(path)) + + fd = open(path, 'wb') + fd.write(content) + fd.close() + + return '' + + def get_pypi(self, request): + body = '\n' + + packages_path = os.path.join(os.path.dirname(__file__), 'packages') + packages_list = os.listdir(packages_path) + + for each in packages_list: + versions_path = os.path.join(packages_path, each) + versions_list = os.listdir(versions_path) + versions_list.sort() + + if len(versions_list) > 0: + body += '%(package)s-%(version)s' % {'package': each, 'version': versions_list[-1]} + + body += '' + + return body + + def package(self, request, package): + package = package.lower() + + package_path = get_package_path(package) + + if not os.path.exists(package_path): + return return404(request) + + versions = os.listdir(package_path) + versions.sort() + + if len(versions) == 0: + return return404(request) + + if os.path.exists(package_path): + return '%(package)s-%(version)s.tar.gz' % {'package': package, 'version': versions[-1]} + + else: + return return404(request) + + def package_and_version(self, request, package, version): + package = package.lower() + + path = get_package_path(package, version, want_file=True) + + if os.path.exists(path): + return '%(package)s-%(version)s.tar.gz' % {'package': package, 'version': version} + + else: + return return404(request) + +def set_up_server(port): + observer = twisted.python.log.PythonLoggingObserver() + observer.start() + + dispatcher = txroutes.Dispatcher() + controller = Controller() + + dispatcher.connect('get_index', '/', controller=controller, + action='get_index', conditions=dict(method=['GET'])) + + dispatcher.connect('post_index', '/', controller=controller, + action='post_index', conditions=dict(method=['POST'])) + + dispatcher.connect('get_pypi', '/pypi/', controller=controller, + action='get_pypi', conditions=dict(method=['GET'])) + + dispatcher.connect('package', '/pypi/{package}/', controller=controller, + action='package') + + dispatcher.connect('package_and_version', '/pypi/{package}/{version}', + controller=controller, action='package_and_version') + + package_path = os.path.join(os.path.dirname(__file__), 'packages') + dispatcher.putChild('packages', File(package_path)) + + factory = Site(dispatcher) + reactor.listenTCP(port, factory) + + print 'Running on %d' % port + reactor.run() + +if __name__ == '__main__': + set_up_server(8000) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f8d8410 --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +from setuptools import setup + +setup(name='simplepypi', + version='0.0.1', + author='Zach Steindler', + author_email='steiza@coffeehousecoders.org', + url='http://github.com/steiza/simplepypi', + description='A really, really, simple HTTP PyPI-like server', + long_description='''It is intended to be used for companies or organizations that need a private PyPi''', + keywords='pypi, replacement, cheeseshop', + classifiers=['Programming Language :: Python', 'License :: OSI Approved :: BSD License'], + license='BSD', + packages=['simplepypi'], + scripts=['bin/simplepypi'], + install_requires=['twisted', 'txroutes'], + )