Commits

Michael Twomey committed 0a37d2a

Switch to Flask

Switch to Flask as app layout is neater and easier to test.

Only thing missing is PUT support.

Comments (0)

Files changed (6)

 language: python
 python:
   - "2.7"
-  - "3.2"
+#Flask doesn't support python 3 yet
+#  - "3.2"
 install: make init
 script: make test
 
     curl -X POST -F requirements=@requirements.txt http://localhost:8080/requirements.txt | python -m json.tool
 
-You can also upload packages directly, either into the normal PyPI package location via a PUT or POST it::
+You can also upload packages directly, either into the normal PyPI package location via a POST::
 
-    curl -X PUT --data-binary @dist/mypackage-1.0.tar.gz http://localhost:8080/packages/source/m/mypackage/mypackage-1.0.tar.gz
 
-or::
 
     curl -X POST -F sdist=@dist/mypackage-1.0.tar.gz  http://localhost:8080/uploadpackage/
 
+..
+  or::
+
+    curl -X PUT --data-binary @dist/mypackage-1.0.tar.gz http://localhost:8080/packages/source/m/mypackage/mypackage-1.0.tar.gz
+
 URLs
 ----
 
 
 - GET /packages/source/m/mypackage/mypackage-1.0.tar.gz
   - Checks PyPI if not present locally
-- PUT /packages/source/m/mypackage/mypackage-1.0.tar.gz
-  - Can't overwrite packages
+
+..
+  - PUT /packages/source/m/mypackage/mypackage-1.0.tar.gz
+    - Can't overwrite packages
 
 - GET /packages/2.7/m/mypackage/mypackage-1.0-py2.7.egg
   - not implemented

pypicache/main.py

 import argparse
 import logging
 
-import bottle
-
 from pypicache import cache
 from pypicache import server
 
     parser.add_argument("--port", default=8080, help="Port to listent on")
     parser.add_argument("--debug", default=False, action="store_true", help="Turn on debugging?")
     parser.add_argument("--reload", default=False, action="store_true", help="Turn on automatic reloading on code changes")
-    parser.add_argument("--server", default="wsgiref", choices=sorted(name for name in bottle.server_names), help="Server to run app with.")
+    parser.add_argument("--processes", default=1, type=int, help="Number of processes to run")
     args = parser.parse_args()
 
     loglevel = logging.DEBUG if args.debug else logging.INFO
     )
     logging.info("Debugging: {!r}".format(args.debug))
     logging.info("Reloading: {!r}".format(args.reload))
-    logging.info("Server: {!r}".format(args.server))
-
-    bottle.debug(args.debug)
 
     package_cache = cache.PackageCache(args.prefix)
-    app = server.make_app(package_cache)
-    bottle.run(app, port=args.port, host=args.address, reloader=args.reload, server=args.server)
+    app = server.configure_app(package_cache, debug=args.debug)
+    app.run(host=args.address, port=args.port, debug=args.debug, use_reloader=args.reload, processes=args.processes)
 
 if __name__ == '__main__':
     main()

pypicache/server.py

 import logging
 import mimetypes
-import os
 import re
 
-import bottle
+from flask import (
+    abort,
+    Flask,
+    jsonify,
+    make_response,
+    render_template,
+    request,
+)
 
 from pypicache import cache
 
-# For the moment you have to set catchall to False to debug test failures.
-app = bottle.Bottle(catchall=True)
+app = Flask("pypicache")
 
-def make_app(package_cache):
+def configure_app(package_cache, debug=False, testing=False):
+    app.debug = debug
+    app.testing = testing
     app.config["pypi"] = package_cache
-    # Not 100% sure this is the best way to handle this.
-    bottle.TEMPLATE_PATH.append(os.path.join(os.path.dirname(__file__), "templates"))
     return app
 
 @app.route("/")
-@bottle.jinja2_view("index")
 def index():
-    return {}
+    return render_template("index.html")
 
-@app.route('/static/<path:path>')
-def callback(path):
-    root = os.path.join(os.path.dirname(__file__), "static")
-    return bottle.static_file(path, root=root)
-
-@app.route("/simple")
 @app.route("/simple/")
-@bottle.jinja2_view("simple")
 def simple_index():
     """The top level simple index page
 
     """
-    return {}
+    return render_template("simple.html")
 
-@app.route("/simple/<package>")
 @app.route("/simple/<package>/")
 def pypi_simple_package_info(package):
     return app.config["pypi"].pypi_get_simple_package_info(package)
 
-@app.route("/local/<package>")
 @app.route("/local/<package>/")
 def local_simple_package_info(package):
     return app.config["pypi"].local_get_simple_package_info(package)
 
-@app.route("/packages/source/<firstletter>/<package>/<filename>", "GET")
+@app.route("/packages/source/<firstletter>/<package>/<filename>", methods=["GET"])
 def get_sdist(firstletter, package, filename):
     try:
         content_type, _ = mimetypes.guess_type(filename)
         logging.debug("Setting mime type of {!r} to {!r}".format(filename, content_type))
-        bottle.response.content_type = content_type
-        return app.config["pypi"].get_sdist(package, filename)
+        response = make_response(app.config["pypi"].get_sdist(package, filename))
+        response.content_type = content_type
+        return response
     except cache.NotFound:
-        return bottle.abort(404)
-
-@app.route("/packages/source/<firstletter>/<package>/<filename>", "PUT")
-def put_sdist(firstletter, package, filename):
-    """PUT a sdist package
-
-    """
-    app.config["pypi"].add_sdist(package, filename, bottle.request.body)
-    return {"uploaded": "ok"}
-
-@app.route("/uploadpackage/", "POST")
-def post_sdist():
+        return abort(404)
+
+# @app.route("/packages/source/<firstletter>/<package>/<filename>", methods=["PUT"])
+# def put_sdist(firstletter, package, filename):
+#     """PUT a sdist package
+
+#     """
+#     # Flask doesn't appear to support a PUT request with raw content
+#     # very well. Trying to figure out a magic combination to make it
+#     # work.
+#     print vars(request)
+#     # fp = StringIO(list(request.form)[0])
+#     assert request.data
+#     fp = StringIO(request.data)
+#     app.config["pypi"].add_sdist(package, filename, fp)
+#     return jsonify({"uploaded": "ok"})
+
+@app.route("/uploadpackage/", methods=["POST"])
+def post_uploadpackage():
     """POST an sdist package
 
     """
     # TODO parse package versions properly, hopefully via distutils2 style library
     # Assume package in form <package>-<version>.tar.gz
-    if "sdist" not in bottle.request.files:
-        bottle.response.status = 400
-        return {"error": True, "message": "Missing sdist data."}
-    filename = bottle.request.files.sdist.filename
+    if "sdist" not in request.files:
+        response = jsonify({"error": True, "message": "Missing sdist data."})
+        response.status_code = 400
+        return response
+    filename = request.files["sdist"].filename
     package = re.match(r"(?P<package>.*?)-.*?\..*", filename).groupdict()["package"]
     logging.debug("Parsed {!r} out of {!r}".format(package, filename))
-    app.config["pypi"].add_sdist(package, filename, bottle.request.files.sdist.file)
-    return {"uploaded": "ok"}
+    app.config["pypi"].add_sdist(package, filename, request.files["sdist"].stream)
+    return jsonify({"uploaded": "ok"})
 
-@app.route("/requirements.txt", "POST")
+@app.route("/requirements.txt", methods=["POST"])
 def POST_requirements_txt():
     """POST a requirements.txt to get the packages therein
 
     """
-    if "requirements" not in bottle.request.files:
-        bottle.response.status = 400
-        return {"error": True, "message": "Missing requirements data."}
-    return app.config["pypi"].cache_requirements_txt(bottle.request.files.requirements.file)
+    if "requirements" not in request.files:
+        response = jsonify({"error": True, "message": "Missing requirements data."})
+        response.status_code = 400
+        return response
+    return jsonify(app.config["pypi"].cache_requirements_txt(request.files["requirements"].stream))
-bottle==0.10.9
+Flask==0.8
 Jinja2==2.6
 requests==0.11.2

tests/test_server.py

 class ServerTestCase(unittest.TestCase):
     def setUp(self):
         self.mock_packagecache = mock.Mock(spec=cache.PackageCache)
-        self.app = TestApp(server.make_app(self.mock_packagecache, ))
+        self.app = TestApp(server.configure_app(self.mock_packagecache, testing=True))
 
     def test_index(self):
         response = self.app.get("/")
         self.app.get("/packages/source/m/mypackage/mypackage-1.1.tar.gz", status=404)
         self.mock_packagecache.get_sdist.assert_called_with("mypackage", "mypackage-1.1.tar.gz")
 
+    @unittest.skip("Need to figure out PUT under flask")
     def test_put_packages_source_sdist(self):
         response = self.app.put("/packages/source/m/mypackage/mypackage-1.0.tar.gz", "--package-data--")
         self.assertDictEqual(response.json, {"uploaded": "ok"})