{ "info": { "author": "Ian Ogilvy", "author_email": "ian.ogilvy@saltminers.biz", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Utilities" ], "description": "Multi-App Sites With Bottle\n===========================\n\nThe Perfect Framework?\n-----------------------\n\nBottlepy is a perfect framework for a simple website, but more debatable is the choice of bottlepy for more complex multi app sites.\n\nThe mainstream current wisdom is that for a more complex website, django is the perfect choice.\n\nI would argue that this conventional wisdom has a flaw. The flaw is that as site complexity increases, so does the chance a complex predefined structure is not a perfect fit. Further, comparing the performance of bottle with django, bottle delivers the performance more critical for the complex environment.\n\nCertainly, the chances of an 'off the shelf' solution being available in django is far more likely. If living with the 'off the shelf' as is will work, this is powerful arugment for django. But if a lot of change will be needed, the pendulum swings back to the cleaner approach of bottle.\n\nOK, bottle is perfect for a simple site. What holds bottle back for a more complex app?\n\nMulti App Sites\n---------------\n\nLets consider a website comprised of smaller 'subapps'.\nFirst Question: What would utopia look like?\nSecond Question: How close does bottle get to utopia?\n\nUtopia\n++++++\n\nKey Points:\n * The structure for multiple apps has zero impact on single app websites\n * An app can be built as single app or a 'subapp' component of a multi app site without code changes\n * Within a multi app system, each app can be self contained using its own folder structure\n * Apps could be nested to any level\n * The complete system is git (or other VCS) friendly\n\nIn practice this would imply something like the following folder structure::\n\n App (folder)\n file1\n file2\n static (folder)\n static_file1\n static_file2\n views (folder)\n view_file1\n view_file2\n sub_app1 (folder)\n file1\n static (folder)\n static_file1\n views\n view_file1\n etc\n\nThe possible contentious point is having statics and views in eash sub_app rather than in their own folder trees.\nThe above is an 'app tree' approach, and I will call the alternative a views/static tree approach.\nI suggest that the logical structure above is utopia for git and development,\nbut the multi app system should be flexible and support separate image/static trees,\neven if not an automatic default.\nSingle file structure can support both trees through symlinks, but both structures should be workable\nwith a multi app system both with and without the use of symlinks.\n\nSo Far With Bottle\n++++++++++++++++++\n\nThe 'app' Structure\n*******************\nApplications in bottle are instances of the 'Bottle' class.\nEach instance of the bottle module has an AppStack, which is a list of Bottle instances, or a list of 'apps'.\n\nBoth 'app' and 'default_app' reference this AppStack. Import either 'app' or 'default_app' to access the AppStack instance.\nThis can be somewhat confusing having 'app' as a list as much documentation and general ideas these days descibes an 'app' as an instance of Bottle.\nEach one is an app in most languages and I recommend importing 'app' as 'apps'.\n\nThe AppStack is actually a class based on list, but with two extra methods, 'call' and 'push'. So app[0] (or default_app[0]) would\nretrieve the fist app in the list, and so on. Using the call method 'app()' is identical to 'app[-1]' and retries the last app in the list.\nThe 'push' method, appends a new Bottle instance to the list.\n::\n\n from bottle import app as apps, default_app # these are two references to the same 'AppStack' list of Bottle instances\n from bottle import Bottle # the class to instance 'apps'\n\n app[0] # retrieve the first Bottle instance in the Appstack list\n app[-1] # retrive the last Bottle instance in the list\n app() # same as app[-1]\n myApp = Bottle() # instance a new Bottle application\n app.push(myApp) # add the new application to the AppStack list\n myApp2 = app.push(Bottle()) # create a Bottle instance and add to the app list\n myApp3 = app.push() # same as above. push() with no parameter instance and pushes a new Bottle instance\n\nDefault Bottle Instance\n***********************\nThe bottle module not only instances an AppStack() with both the names 'app' and 'default_app',\nalso one instance of a Bottle object (or app) is pre-added to the AppStack.\nNote using this 'pre-added' instance of Bottle() is dangerous,\nbecause serveral modules can accidentally use the same instance.\nFor the AppStack, there *is* only on instance so no problem.\napp() or default_app() both retrieve the last app added to the AppStack, which will initially be the\nBottle instance created internally to the bottle module.\n@route etc decorators will by default use\nthe last Bottle instance added to the AppStack list. If using two apps in the same module, @route etc\nwill by default work with\nthe automatically created app until a new app is instanced e.g. ::\n\n from bottle import Bottle, route, app as apps\n\n @route('/page') # works with default app\n def pageapp1():\n pass\n\n app1 = apps() # save default app - you need to be sure only you will use this!\n newapp = Bottle() # use 'newapp = apps.push()' to do all steps at once\n\n @route('/page1') # route using last app in AppStack which is still app1\n def page1():\n pass\n\n @newapp.route('/page2') # explict route for 'newapp'\n def page2():\n pass\n\n apps.push(newapp) # add 'newapp' to AppStack, which will make newapp now the default\n\n @route('/page2b') # another route for newapp\n @newapp.route('/page2c') # explict route for same app\n def page2b():\n pass\n\n app1.route('/anotherpage') # explicit route for first app\n def pageNot2b():\n pass\n\n\n\nCombining Apps and Routes\n*************************\nSo even in a single file, it is possible to work with multiple bottle instances or 'apps'. But only one app is actually 'run',\nso it is necessary to combine these apps to run collectively.\n\nBottle provides two ways of combining apps::\n\n mainapp.mount('/subapp', subapp1) # mount subapp with '/subapp' as a path prefix\n mainapp.merge(subapp2) # mount subapp2 at site root\n\nIf 'subapp1' has a @route('main') then with the 'mount' above it, this 'main' route would become '/subapp/main'.\n::\n\n mainapp.mount('/', subapp)\n and\n mainapp.merge(subapp)\n\nWould seem to be the same, however using 'mount' in this case is forbidden and 'merge' is required.\nI am unsure why as it would seem using 'mount' for both cases would be elegant.\n::\n\n #hello app\n from bottle import route,app as apps\n\n myapp= apps.push()\n\n @route('/hello')\n def hello():\n return 'the main hello app page'\n\nMain file::\n\n #main app - helloapp is used as a sub app\n from bottle import route,mount,run,app as apps\n from helloapp import myapp as subapp\n\n myapp=apps.push() #note if both files used apps(), they would share the same app\n\n @route('/')\n @route('/home')\n def home():\n return 'site home page'\n\n myapp.mount('/sub')\n myapp.run()\n\nThis simple structure allows for a separate python program for each 'app'.\n\nNote: using 'apps.push()' in place of 'apps()' every time means that\nthere is one unused Bottle instance on the AppStack. But that is better than\naccidentally using that one automatic Bottle() twice.\n\nWhat about folders?\n+++++++++++++++++++\nThe previous section covers all that is needed for multiple applications\nwhere all files share the same folders. Which effectively means the 'apps' are developed together.\nPython files in the same folder, all statics in the same statics folder and all views in the same views folder.\nHowever the 'Utopia' was to allow the subapp to live in its\nown folder with self contained static and views folders.\nSimply adding an __init__.py to the sub app and adjusting the import allows the sub app to live in its own folder\nand a .gitignore line can even keep the projects separate if you use Git.\nThe import simply becomes::\n\n from sub/helloapp import myapp as subapp\n\nBut what about views and statics?\n*********************************\nBy default bottle creates two template directories::\n\n ['./', './views/']\n\nIn reality this is only useful if bottle is started with the current directory\nset to the app. On some servers, this does not happen so these settings are of no use.\nIf the app is accessed from 'pythonpath' for example, the the current directory\ncould be anywhere.\n\nSo sometimes the default settins work, in other cases they do not. Whether\nthe settings work is largely deployment specific.\n\nFurther, in the above 'hello' app example, even if the default settings work, we would\nwant the hello app, which is in the 'sub' folder ro have the following paths::\n\n [ './sub/', './sub/views/'\n './', './views/'\n ]\n\nAlternatively, with the alternate scheme mentioned in the 'utopia' secion the following could be desired::\n\n [ './sub/', './views/sub/'\n './', './views/'\n ]\n\nWe could modify the code for each deployment, but this is not desirable.\nGoals\n*****\n\n\nThe goals are:\n* minimise changes between deployments\n* support git based developments\n*\n\nSolution.\n*********\nA single file 'siteSettings.py' (or siteSettings.json or .conf), to override defaults\nmeets all solution criteria. Bottle already supports app configuration files, but these serve\na different purpose. Firstly these hold settings for each app as opposed to the 'site' which has an\nimpact on *all* apps. Secondly, they are intended to store all app settings, rather than only the\nsite settings. Specifically this means excluding the app config file through .gitignore is not desireable, whereas the\nsite config file is specifically designed to be excluded through .gitignore.\n\nA very common use of the site config is that testing of a site will occur with one site config on a local developer machine,\nthen move to another site config on a test host, before becomming live on a third live host site configuration.\n\n(The actual storage format and file name will depend on feedback as to what is popular)\n\nA 'site' object holdings all 'deployment' based settings is added to each 'app'.\n\nThis object provides simple access to deployment specific data, and the object is built\nusing defaults, overridden by default overrides from the 'Site' class in the siteSettings.py file.\n\nThe default values produce the following 'site' objects, in a 'main' app or a 'sub' app added through 'merge' respectively.\n\n+-----------+--------------------------+------------------------+------------------------+\n| field + siteSettings value + value in default 'app' + value in 'sub' app +\n| + (default) + + +\n+===========+==========================+========================+========================+\n+ views + .{path}/views/, .{path} + [ ./views , ./ ] + [ ./sub/views, ./subs/ +\n+ + + + , ./views , ./ ] +\n+-----------+--------------------------+------------------------+------------------------+\n+ static + .{path}/static/, + ./static/ + [ ./static/ +\n+ + + + , ./sub/static/ ] +\n+-----------+--------------------------+------------------------+------------------------+\n+ appStatic + .{path}/static/ + ./static/ + [ ./sub/static/ ] +\n+-----------+--------------------------+------------------------+------------------------+\n+ appURL + {path}/ + / + /subURL +\n+-----------+--------------------------+------------------------+------------------------+\n\nNote::\n\n The '/subURL' in the URL is derived from the path in the 'merge'\n app1.merge('/subURL',subapp)\n The './sub' in the static and views paths is derived from the python 'import'\n from sub import subapp\n\n The name of the module (or folder) used for the subapp need not match the URL prefix\n used with merge and for accessing the subapp. In this example 'subURL' as a prefix\n within web links, but accessing the 'sub' folder within the application folder tree.\n\n\nThe folder or module {path} is constructed from the python module path, and then used with .format() to build values\nin instances of 'site'. The 'appURL' attribute is passed to templates, so pages can use\nthe 'appURL' value to link to other pages or resources (including statics) referenced by the page.\n\nSo the results above\nassume the 'sub' app is imported as follows::\n\n from sub import subapp\n\nTo override these defaults, create a 'siteSettings' module in the main project folder\nand add a 'Site' class with values to over-ride the defaults::\n\n class Site:\n views = './views{path}/' #all views in tree within views folder\n\n # example of deployment where current folder is not set\n class Site:\n views = '/home/theApp{path}/views/', '/home/theApp{path}/'\n #project in 'theApp' folder\n\nValues are only required where the default is to be changed.\n\n*Note this system is currently implemented through 'newMerge' and method 'app.static_file'*\n*bottle could be upgraded with full backward compatibility*\n\nName Conflicts.\n***************\nSo why would the same name appear in both the main project and the sub 'app'?\n\nThere are two possible reasons:\n * the app holds a standin for what is hoped would be 'site global'\n * the app holds a value designed to override a 'site global'\n\nIn the first case, it is desired to look first in the 'global' location, and the opposite for the second case.\nCurrently the code implements the 'global first' approach on the thought that if the app wants a unique\nvalue it can choose a unique name", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://bitbucket.org/objdict/multiBottle", "keywords": "database bottle mongodb pymongo view mvc model", "license": "LGPL", "maintainer": "", "maintainer_email": "", "name": "MultiBottle", "package_url": "https://pypi.org/project/MultiBottle/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/MultiBottle/", "project_urls": { "Homepage": "https://bitbucket.org/objdict/multiBottle" }, "release_url": "https://pypi.org/project/MultiBottle/0.1.3/", "requires_dist": [ "bottle", "mako", "objdict", "pymongo", "viewmodel" ], "requires_python": "", "summary": "Additional code to work with Bottle web framework to provide mechanisms for multi app support, together with support for a sitemap built together with the routes table and support for database maintenance and secure logins for a basic site.", "version": "0.1.3" }, "last_serial": 3904465, "releases": { "0.1.1": [ { "comment_text": "", "digests": { "md5": "1d82533e06f8af54e9f7d4c8c157b04c", "sha256": "776e7355768dfc3ce9fbd57ea96c9fa78ef4870c8a8fed84368555cd3511a5c4" }, "downloads": -1, "filename": "MultiBottle-0.1.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "1d82533e06f8af54e9f7d4c8c157b04c", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 34816, "upload_time": "2016-10-25T02:29:28", "url": "https://files.pythonhosted.org/packages/14/18/77e8494a7525441ce1d6271a4dc1cbe785469050f2a41ada96676c9da953/MultiBottle-0.1.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "cfcc5f040192caaa25b0c7ba4952bcc5", "sha256": "775845156a7230aa5b5f1988ac736c65fa9ba4cdf298d0d8eb0ec2d75e84cbd9" }, "downloads": -1, "filename": "MultiBottle-0.1.1.tar.gz", "has_sig": false, "md5_digest": "cfcc5f040192caaa25b0c7ba4952bcc5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27558, "upload_time": "2016-10-25T02:29:44", "url": "https://files.pythonhosted.org/packages/3a/ba/cd9541c984c597a874e9b4ce51fea890e3fe7b6625ead8063b963fcdaa59/MultiBottle-0.1.1.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "672910a1028ddaca1fc23cf7f6e4a0d2", "sha256": "847131e678b807283ff72c33fc6e540f60b26041a95da68fbfd1cc72d8decb76" }, "downloads": -1, "filename": "MultiBottle-0.1.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "672910a1028ddaca1fc23cf7f6e4a0d2", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 34873, "upload_time": "2016-11-02T02:33:11", "url": "https://files.pythonhosted.org/packages/5c/1f/68f18838d6e529fe28de2f276752189496b83369c52d969246a54ba0f91f/MultiBottle-0.1.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "8b8c886a22a6d5d93be1a091901c4292", "sha256": "560eb178fe181198d518fc629c211759d067c7d7f1a9feb897720957c073f904" }, "downloads": -1, "filename": "MultiBottle-0.1.2.tar.gz", "has_sig": false, "md5_digest": "8b8c886a22a6d5d93be1a091901c4292", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27616, "upload_time": "2016-11-02T02:33:00", "url": "https://files.pythonhosted.org/packages/ce/3d/2625719dccc0f8348c3ccc506857dd5737a33c5488a0f1841ec53831eaa8/MultiBottle-0.1.2.tar.gz" } ], "0.1.3": [ { "comment_text": "", "digests": { "md5": "1821f1bcce13049d5e8ee815f4a9c4e5", "sha256": "bd3092ab9e8bc6508b06d8e1626f668636e82cb1307bf4f043277248046e4f13" }, "downloads": -1, "filename": "MultiBottle-0.1.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "1821f1bcce13049d5e8ee815f4a9c4e5", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 34889, "upload_time": "2016-11-11T02:39:45", "url": "https://files.pythonhosted.org/packages/af/4e/e57ace715c6d4a54d4aaa629a930f7eb407f956002fbe4bccc27e9a53613/MultiBottle-0.1.3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d3423e5fe8fee7cf31de8978d3667817", "sha256": "5ceaece04d5842642027fac79fbb6db6b92b9625a6ee172a6ff8c62f3209f7a9" }, "downloads": -1, "filename": "MultiBottle-0.1.3.tar.gz", "has_sig": false, "md5_digest": "d3423e5fe8fee7cf31de8978d3667817", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27687, "upload_time": "2016-11-11T02:39:59", "url": "https://files.pythonhosted.org/packages/90/6c/7ae1b20ad2883b9b6178f729b843461079b0e5164d0a2aed93879ef53512/MultiBottle-0.1.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "1821f1bcce13049d5e8ee815f4a9c4e5", "sha256": "bd3092ab9e8bc6508b06d8e1626f668636e82cb1307bf4f043277248046e4f13" }, "downloads": -1, "filename": "MultiBottle-0.1.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "1821f1bcce13049d5e8ee815f4a9c4e5", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 34889, "upload_time": "2016-11-11T02:39:45", "url": "https://files.pythonhosted.org/packages/af/4e/e57ace715c6d4a54d4aaa629a930f7eb407f956002fbe4bccc27e9a53613/MultiBottle-0.1.3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d3423e5fe8fee7cf31de8978d3667817", "sha256": "5ceaece04d5842642027fac79fbb6db6b92b9625a6ee172a6ff8c62f3209f7a9" }, "downloads": -1, "filename": "MultiBottle-0.1.3.tar.gz", "has_sig": false, "md5_digest": "d3423e5fe8fee7cf31de8978d3667817", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27687, "upload_time": "2016-11-11T02:39:59", "url": "https://files.pythonhosted.org/packages/90/6c/7ae1b20ad2883b9b6178f729b843461079b0e5164d0a2aed93879ef53512/MultiBottle-0.1.3.tar.gz" } ] }