Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 8b57e9c
Showing
3 changed files
with
185 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: <whatever> | ||
password: <doesn't matter, see above> | ||
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! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 '<html><head><title>404 Not Found</title></head><body><h1>Not found</h1></body></html>' | ||
|
||
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 '<html><head><title>simplepypi</title></head><body><a href="/packages">packages</a></body></html>' | ||
|
||
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 = '<html><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 += '<a href="/packages/%(package)s/%(version)s/%(package)s-%(version)s.tar.gz">%(package)s-%(version)s</a>' % {'package': each, 'version': versions_list[-1]} | ||
|
||
body += '</body></html>' | ||
|
||
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 '<html><body><a href="/packages/%(package)s/%(version)s/%(package)s-%(version)s.tar.gz">%(package)s-%(version)s.tar.gz</a></body></html>' % {'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 '<html><body><a href="/packages/%(package)s/%(version)s/%(package)s-%(version)s.tar.gz">%(package)s-%(version)s.tar.gz</a></body></html>' % {'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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'], | ||
) |