{"id":48573475,"url":"https://github.com/polydojo/vilo","last_synced_at":"2026-04-08T15:35:03.618Z","repository":{"id":57477463,"uuid":"307089622","full_name":"polydojo/vilo","owner":"polydojo","description":"Simple, unopinionated Python web framework.","archived":false,"fork":false,"pushed_at":"2020-11-22T16:50:04.000Z","size":80,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-24T22:51:34.038Z","etag":null,"topics":["bottle","django-alternative","flask","flask-alternative","python","vilo","web-framework","wsgi"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/polydojo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-25T12:03:02.000Z","updated_at":"2024-01-25T15:55:45.000Z","dependencies_parsed_at":"2022-08-30T15:03:07.868Z","dependency_job_id":null,"html_url":"https://github.com/polydojo/vilo","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/polydojo/vilo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polydojo%2Fvilo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polydojo%2Fvilo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polydojo%2Fvilo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polydojo%2Fvilo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/polydojo","download_url":"https://codeload.github.com/polydojo/vilo/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polydojo%2Fvilo/sbom","scorecard":{"id":740389,"data":{"date":"2025-08-11","repo":{"name":"github.com/polydojo/vilo","commit":"73a8686d7d878f155deedca781295e3804a8e944"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/12 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-22T17:15:31.096Z","repository_id":57477463,"created_at":"2025-08-22T17:15:31.096Z","updated_at":"2025-08-22T17:15:31.096Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31562691,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"ssl_error","status_checked_at":"2026-04-08T14:31:17.202Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bottle","django-alternative","flask","flask-alternative","python","vilo","web-framework","wsgi"],"created_at":"2026-04-08T15:35:02.880Z","updated_at":"2026-04-08T15:35:03.610Z","avatar_url":"https://github.com/polydojo.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Vilo\n====\n\nVilo is a WSGI micro-framework for building web apps with Python. Inspired by [Express](https://expressjs.com/) and [Bottle](https://bottlepy.org/), Vilo is lightweight, unopinionated, and flexible.\n\nInstallation\n--------------\nInstall Vilo via pip:\n```\npip install vilo\n```\n[Waitress](https://docs.pylonsproject.org/projects/waitress/en/stable/) is a recommended for running Vilo apps, paired with [Hupper](https://docs.pylonsproject.org/projects/hupper/en/latest/). Install via:  \n`pip install waitress hupper`\n\nAlternatively [Gunicorn](https://gunicorn.org/) is a popular option, installable via:  \n`pip install gunicorn`\n\nHello, World!\n----------------\n\nCreate `hello.py`:\n```py\n# Define App: ##################################\nimport vilo;                # Import vilo\n\napp = vilo.buildApp();      # Build (blank) app\n\n@app.route(\"GET\", \"/\")      # Add route\ndef get_homepage (req, res):\n    return \"Hello, World!\";\n\nwsgi = app.wsgi;            # WSGI callable\n\n# Run App: #####################################\nif __name__ == \"__main__\":\n    import waitress;\n    waitress.serve(wsgi);\n```\nRun `python hello.py` to start the app. Once the app is running, [visit `localhost:8080`](http://localhost:8080) in your favorite browser! You should see a familiar greeting. (To stop running the app, press: `Ctrl`+`C`)\n\nFrom The Command Line\n------------------------------\n\nContinuing with the `hello.py` example above, instead of calling `waitress.serve(.)` from Python, you can run the app from the command line as follows:\n\n#### With Waitress:\n\n+ Using waitress-serve: `waitress-serve hello:wsgi`\n+ With Hupper for dev: `hupper -m waitress hello:wsgi`\n\nWaitress includes the `waitress-serve` command, using which, we're asking Waitress to run `wsgi` from the `hello` module. Hupper is useful during development, for hot-reloads.\n\nKindly refer to Waitress/Hupper's docs for more.\n\n#### Using Gunicorn:\n\n+ Without hot-reloads: `gunicorn hello:wsgi`\n+ With `--reload` for dev: `gunicorn hello:wsgi --reload`\n\nKindly refer to Gunicorn's docs for more.\n\n\nVilo vs Flask/Bottle\n------------------------\n#### No Magic Globals:\n\nFlask and Bottle both employ globals like `request`, `g` and/or `response`. Vilo instead, like Express, supplies `req` and `res` as arguments to each route handler. \n\nIf you're building a single app, globals can be very handy. But if you're looking to build and deploy multiple (entirely isolated) apps, having to deal with automatically context-aware globals can seem *eerily magical*.\n\nWith Vilo, `req` and `res` arguments passed to route handlers are *entirely isolated*. Even with regard to a single app, each route handler receives fully isolated `req` and `res` objects.\n\n**No Built-In Templating**:  \nBottle and Flask both include built-in templating engines. As an un-opinionated framework, Vilo doesn't. You may pick any templating engine you want. For illustration only, our examples will use [Qree](https://github.com/polydojo/qree) (read 'Curie'), installable via `pip install qree`.\n\n**No Built-In Development Server:**  \nBottle and Flask include a development server with hot-reloads for local testing and development, but recommend *against* using it in production. Instead of pre-packaging a development server, we recommend using Gunicorn with the `--reload` flag, or Waitress atop Hupper.\n\n\nBasic Routes \u0026 Static Files\n--------------------------------\n\nWorking with fixed routes is fairly straightforward. To handle the route `\"/foo/bar\"`, just supply that path to `app.route(.)` For example:\n\n```py\n# import vilo, define app etc.\n\n@app.route(\"GET\", \"/foo/bar\")\ndef get_foo_bar (req, res):\n    return \"You sent a GET request to path: /foo/bar\";\n\n@app.route(\"GET\", \"/task/1\")\ndef get_listTasks (req, res):\n    return yourLogic_showTask(1);\n\n@app.route(\"GET\", \"/task/2\")\ndef get_listTasks (req, res):\n    return yourLogic_showTask(2);\n\n@app.route(\"POST\", \"/newTask\")\ndef post_newTask (req, res):\n    return \"You sent a POST request at path: /newTask\";\n```\n\nIf you have static CSS, JS, image or other files, serve them using `res.staticFile(filepath, [mimeType])`. For example, let's say you have the following directory structure:\n```\n- app.py\n- config.py\n- utils.py\n- static/\n        - jquery.js\n        - logo.png\n```\nIn such a case, you could serve jquery and your logo as follows:\n```py\n# import vilo, define app etc.\n\n@app.route(\"GET\", \"/static/jquery.js\")\ndef get_jquery (req, res):\n    return res.staticFile(\"./static/jquery.js\");\n\n@app.route(\"GET\", \"/static/logo.png\")\ndef get_bootstrap (req, res):\n    return res.staticFile(\"./static/logo.png\");\n```\nOptionally, you may pass `mimeType` to `res.staticFile(.)`. If not passed, it'll be guessed.\n\n**_DRY_ = Don't Repeat Yourself**\n\nYou would've noticed that routes `/task/1` and `/task/2`, and similarly the routes `/static/jquery.js` and `/static/logo.png` are essentially the same. Repeating them twice not very DRY.\n\nRight now, there seem to be just two tasks and two static files. But what if there were 10 each? 100 each? Instead of tediously repeating ourselves, we can use wildcard routes. That's next.\n\n\nWildcard Routes\n--------------------\n\nThe URL path has multiple slash-separated *segments*. Use `*` for wildcard segment matching. *Exclusively at the end* of the URL, using `**` instead of a single `*` will match multiple segments, including slashes therebetween.\n\nWith the sole exception of `**` at the end of the URL, each `*` matches a *single but complete segment*. Thus, except at the end, each `*` must be sandwiched between two slashes: `/*/`.\n\nThe list of matched wildcards is available via `req.wildcards`.\n\n**Examples:**\n1. `/category/*/page/*/edit`\n    - WILL match `/category/Food/page/Pasta/edit`\n    with `req.wildcards = ['Food', 'Pasta']`.\n    - but will NOT match `/category/Fo/od/page/Pasta/edit`\n    as `Fo/od` is not a single segment.\n\n2. `/cart/add-item/*`\n    - will match `/cart/add-item/123`\n      with `req.wildcards = ['123']`.\n    - but will NOT match `/cart/add-item/12/3`\n    as `12/3` is not a single segment.\n\n3. `/static/**`\n    - will match `/static/lib/js/jquery.js`\n      with `req.wildcards = 'lib/javascript/jquery.js`\n    - but will NOT match `/StaTiC/lib/js/jquery.js`\n    because `StaTiC` and `static` don't match.\n\nNow, using wildcard routes, we could write:\n```py\n# import vilo, define app etc.\n\n@app.route(\"GET\", \"/task/*\")\ndef get_task (req, res):\n    taskId = int(req.wildcards[0]);\n    return yourLogic_showTask(1)\n\n@app.route(\"GET\", \"/static/**\")\ndef get_static (req, res):\n    \"Serves static files from the `static/` dir.\";\n    relpath = req.wildcards[0];\n    return res.staticFile(\"./static/\" + relpath);\n\n@app.route(\"GET\", \"/category/*/season/*\")\ndef get_category_season (req, res):\n    \"Serve product info by category and season.\"\n    cateogry, season = req.wildcards;\n    productsPage = doSomething(category, season);\n    return productsPage;\n\n``` \n\nRegular Expression Mode\n-------------------------------\n\nWildcard patterns are powerful, but don't necessarily cover all use cases. For greater flexibility, you can rely on regular expressions.\n\nLet's say we want to match routes like the following:\n- `\"/show-posts/from-2018-to-2020\"`\n- `\"/show-posts/from-1915-to-2015\"`\n\nAs each `*` matches an entire segment, we *CANNOT* use:\n- `\"/show-posts/from-*-to-*\"`\n\nInstead, we can use the following regular expression:\n- `r\"/show-posts/from-(\\d+)-to-(\\d+)\"`\n\nFor using regular expression mode, pass `mode=\"re\"` to `app.route(.)`. The resultant match-object is available as `req.matched`. For example:\n\n```py\n# import vilo, define app, etc.\n\n@app.route(\"GET\", r\"/show-posts/from-(\\d+)-to-(\\d+)\", mode=\"re\")\ndef get_showPosts (req, res):\n    fromYear, toYear = req.matched.groups();\n    resultPage = yourLogic(fromYear, toYear);\n    return resultPage;\n```\n\n**The `mode` parameter to `app.route(.)`:**\n\nGenerally speaking, there's *no need* to explicitly pass `mode`, as `app.route(.)` can auto-detect it. If passed explicitly, it accepts one of three values:\n`[\"re\", \"wildcard\", \"exact\"]`.\n- `\"re\"`: regular expression matching, based on `re.match(.)`.\n- `\"wildcard\":` wildcard segment matching, explained above.\n- `\"exact\"`: exact path matching, based on `==` operator.\n\nDot-Accessible Dictionary (`dotsi.Dict`)\n-----------------------------------------------\n\nVilo uses [Dotsi](https://github.com/polydojo/dotsi) for dot-accessible dictionaries. In fact, in all previous examples, `app`, `req` and `res` are all dot-accessible dictionaries!\n\n**`dotsi` Usage:**\n```py\nimport dotsi\n\nd = dotsi.fy({\"foo\": \"bar\"})\nprint(d.foo);\t# Same as d['foo']\n# Output: bar\n\nd.baz = [{\"key\":\"a\"}, {\"key\":\"b\"}] # Like d['baz'] = ..\nprint(d.baz[0].key)\n# Output: a\n```\nFor more examples, check out Dotsi's docs, linked above.\n\n **Sidebar: Functional vs OOP**  \n The Polydojo team strongly favours functional programming over classical OOP. *As far as possible*, we avoid writing classes. This is super-easy in JavaScript, especially because object properties are dot-accessible; Dotsi allows us to bring the same ease to Python.\n\nHTML Escaping \u0026 `%s`-Formatting\n-----------------------------------------------\n\nUse `vilo.esc(string)` for escaping HTML:\n- `vilo.esc('foo')` =\u003e `'foo'`\n- `vilo.esc('\u003cb\u003e Hi \u003c/b\u003e')`\n  =\u003e `'\u0026lt;b\u0026gt; Hi \u0026lt;/b\u0026gt;'`\n- `vilo.esc('\u003cscript\u003e xss() \u003c/script\u003e')`\n  =\u003e `'\u0026lt;script\u0026gt; xss() \u0026lt;/script\u0026gt;'`\n\nUse `vilo.escfmt(string, data)` for escape-wrapped, `%s`-based formatting. Or better yet, try [**Qree**](https://github.com/polydojo/qree), our tiny but might templating engine.\n\nWorking With Forms\n--------------------------\n- Use `req.qdata` to access *q*uery string parameters.\n-  Use `req.fdata` to access POSTed *f*orm data.\n- POSTed multipart/form-data is also available via `req.fdata`.\n- Both `req.qdata` and `req.fdata` are of type `dotsi.Dict`.\n\n**Factorial Form Example:**\n```py\nimport vilo; app = vilo.buildApp(); wsgi = app.wsgi;\n\n@app.route(\"GET\", \"/\")\ndef get_homepage (req, res):\n    return \"\"\"\n        \u003cform method=\"GET\" action=\"/factorial\"\u003e\n            \u003cinput type=\"text\" name=\"n\" placeholder=\"Enter N:\"\u003e\n            \u003cbutton\u003eSubmit\u003c/button\u003e\n        \u003c/form\u003e\n    \"\"\";\n\n# Factorial helper:\nfacto = lambda n: 1 if n == 0 else n * facto(n - 1);\n\n@app.route(\"GET\", \"/factorial\")\ndef get_factorial (req, res):\n    n = int(req.qdata.get(\"n\") or 1);\n    return vilo.escfmt(\"\"\"\n        \u003ch2\u003e%s! = %s\u003c/h2\u003e\n        \u003ca href=\"javascript: history.back();\"\u003eBack\u003c/a\u003e\n    \"\"\", [n, facto(n)]);\n```\n\nErrors \u0026 Redirects\n------------------------\n**Redirects:**  \nReturn `res.redirect(.)` for redirecting to another URL:\n```py\n@app.route(\"GET\", \"/foo\")\ndef redirect_from_foo_to_bar (req, res):\n\treturn res.redirect(\"/bar\");\n\n@app.route(\"GET\", \"/go/to/boardbell\")\ndef redirect_to_boardbell_dot_com (req, res):\n\treturn res.redirect(\"https://www.boardbell.com/\");\n```\n\n*NB:* Please be sure to `return res.staticFile(.)`. Skipping **`return`** will likely produce unintended consequences.\n\n**Errors:**\nRaise `vilo.HttpError(.)` to produce a non-200 response. (Or use `vilo.error(.)`, which is a short alias for the same.)\n```py\n@app.route(\"GET\", \"/post/*\")\ndef get_post (req, res):\n\tpostId = req.wildcards[0];\n\tif not yourLogic_postId_found(postId):\n\t\traise vilo.error(\"\u003ch2\u003eNo such post.\u003c/h2\u003e\");\n\t# otherwise ...\n\treturn yourLogic_showPost(postId);\n```\n`vilo.error(.)` takes two parameters:\n- `body` (required): The response body. Similar to the return value for non-error responses. If it's a `dict` or `list`, a JSON response is produced.\n- `statusLine` (optional): A status line like \"404 Not Found\" or \"403 Forbidden\"; alternatively, an integer code like 404 or 405. (Defaults to 404.)\n\n*NB:* Please be sure to `raise vilo.error(.)`. If **`raise`** isn't used, no error will be produced; it'll be as if no error ever occurred.\n\n**Framework-Level Errors:**\n\nWhen you explicitly raise a `vilo.HttpError(.)`, it is sent directly to the client, as described above. But sometimes, Vilo itself needs to raise errors; for example, when a route is not found. Other error-types are caught, producing a 500-response.\n\nFramework-level error-handling can be customized via `app.frameworkError(.)`, which is a bit like `app.route(.)`.\n\nTo override the default route-not-found behavior:\n```py\n@app.frameworkError(\"route_not_found\")\ndef error_routeNotFound (req, res, err):\n\treturn \"Custom Msg: That route wasn't found.\";\n```\nComparing `app.frameworkError(.)` with `app.route(.)`:\n1. Instead of passing a route, you pass a framework-error-code, such as `\"route_not_found\"`. (Codes are listed below.)\n2. The handler accepts an extra parameter, `err`, which is simply the exception raised (or caught) by Vilo.\n\nFramework-error codes:\n- `route_not_found` (illustrated above)\n- `file_not_found` (with regard to `res.staticFile(.)`)\n- `request_too_large` (this is yet to be documented.)\n- `unexpected_error` (more on this below)\n\n**Handling Unexpected Errors:**\n\nWhen your application produces an unexpected error, by default, Vilo produces a simple `500 Internal Server Error` response. The default error response may be customized as follows:\n```py\n# Sample error producer:\n@app.get(\"/err\")\ndef get_zeroDivErr (req, res):\n\treturn 1/0;\n\n# Unexpected error handler:\n@app.frameworkError(\"unexpected_error\")\ndef handle_unexpectedError (req, res, err):\n\treturn vilo.esc(repr(err));\n```\n\nOn navigating to `\"/err\"`, instead of the default error message, you should see: `ZeroDivisionError('division by zero')`\n\n**Debug Mode:**\nIn debug mode, which can be enabled via `app.setDebug(True)`, the default 500-error handler includes a stack traceback. While this obviously helps with debugging, it can cause security concerns in production. **Caution:** Do not enable debug-mode in production.\n\n\nQuick Plug\n--------------\nVilo is built and maintained by the folks at [Polydojo, Inc.](https://www.polydojo.com/), led by [Sumukh Barve](https://www.sumukhbarve.com/). If your team is looking for a simple project management tool, please check out our latest product: [**BoardBell.com**](https://www.boardbell.com/).\n\nHeaders\n-----------\n\n**Set Response Headers:**\n\nUse `res.setHeader(name, value)` for setting a response header. (Params `name` and `value` respectively correspond to the name and value of the header.)\n\nOr use `res.setHeaders(.)` for setting multiple headers at once by passing either a dict or a list of `(name, value)` pairs.\n\n```py\n@app.route(\"GET\", \"/foo.txt\")\ndef get_fooTxt (req, res):\n\tres.setHeaders({\n\t\t\"Content-Type\": \"text/plain\",\n\t\t\"Cache-Control\": \"no-store\",\n\t});\n\treturn \"Here's some foo text.\";\n```\n\nHeader Shortcuts:  \n- Use `res.contentType = someValue` instead of `res.setHeader(\"Content-Type\", someValue)`.\n- Use `res.setCookie(.)` for setting cookies, instead of setting the `\"Set-Cookie\"` header. More on this below.\n\n**Get Request Headers:**\n\nUse `req.getHeader(name)` to get a request header. (Param `name` corresponds to the header's name.) If there's no such header, `None` is returned.\n\n```py\n@app.route(\"GET\", \"/api/foo\")\ndef get_apiFoo (req, res):\n\treqType = req.getHeader(\"Content-Type\");\n\tif reqType != \"application/json\":\n\t\traise vilo.error(\"Only JSON requsts are valid.\");\n\t# otherwise ...\n\treturn yourLogic_doFoo();\n```\n\n*Note:* Use `req.getCookie(.)` (documented below) for getting request cookies. No need to deal with the `\"Cookie\"` header directly.\n\nCookies\n----------\n\n**Set Response Cookies:**\n\nUse `res.setCookie(name, value, [secret, opt])` for setting response cookies. Params `name` and `value` respectively correspond to the name and value of the cookie.\n- Optional param `secret` may be a string; and is used to sign the cookie, if passed.\n- Optional param `opt` may be a dict of cookie options compatible with Python's [`http.cookies.Morsel`](https://docs.python.org/3/library/http.cookies.html#http.cookies.Morsel).\n\n```py\n@app.route(\"GET\", \"/home\")\ndef get_home (req, res):\n\tres.setCookie(\"visitedHome\", \"Yes\");\n\treturn \"\u003ch1\u003eYou're Home!\u003c/h1\u003e\";\n```\n\n**Note:** By default, Vilo sets `HttpOnly` cookies with `Path=\"/\"`. For custom behavior, pass `opt={\"httponly\": False, \"path\": \"/custom\"}`. Of course, you may also pass other Morsel-compatible options.\n\n**Warning:**  Even when `secret` is passed, it is only used to *sign* the cookie. The cookie is **NOT** *encrypted*. Signing **DOES NOT** hide or obscure the cookie in any way. As such, one must **NEVER** store confidential information in cookies.\n\n**Get Request Cookies:**\n\nUse `req.getCookie(name, [secret])` for getting request cookies. Param `name` is the cookie name while `secret` should match the secret used while setting the cookie.\n- If the named cookie exists (and is valid), it's value is returned, else `None`.\n- If `secret` is passed but the signature-check fails, `None` is returned (regardless of whether the cookie exists).\n\n```py\n@app.route(\"GET\", \"/visitCounter\")\ndef get_visitCounter (req, res):\n\tcount = int(req.getCookie(\"visitCount\") or 0);\n\tres.setCookie(\"visitCount\", str(count + 1))\n\treturn \"Number of visits: %s\" % (count+1);\n```\n\nWorking With JSON\n-------------------------\nVilo makes it easy to consume and produce JSON. In route handlers:  \n- **JSON Requests:**  POSTed `application/json` data is available as `req.fdata`.\n- **JSON Responses:** Returning a `dict` or `list` produces an `application/json` response.\n\n```py\n@app.route(\"GET\", \"/hello_json\")\ndef get_sample_json (req, res):\n\treturn {\"hello\": \"json\"};\n\t# Will produce JSON response, with:\n\t#\tContent-Type: application/json\n\n```\n\nPlugins (Universal Route Decorators)\n--------------------------------------------\n\nIf you'd like certain action to be performed across *all* routes, it's best to define a plugin for such behavior. (Plugins are just decorators that are applied to all routes.)\n\n#### Example: `X-Exec-Time` Header\n\nTo add the `X-Exec-Time` header to each response, we can write a plugin that computes the execution time and adds the header.\n\n```py\nimport time;\n\ndef plugin_xExecTime (fn):\n    def wrapper (req, res, *args, **kwargs):\n        t0 = time.time();\n        result = fn(req, res, *args, **kwargs);\n        tDiff = time.time() - t0;\n        res.setHeader(\"X-Exec-Time\", str(tDiff));\n        return result;\n    return wrapper;\n```\nThen, install the plugin onto your app via:\n```py\napp.install(plugin_xExecTime)\n```\n\n#### Example: Enforcing HTTPS\n```py\ndef plugin_enforceHttps (fn):\n    def wrapper (req, res, *args, **kwargs):\n        scheme = req.splitUrl.scheme;\n        if scheme != \"https\":\n            newUrl = req.url.replace(scheme, \"https\", 1);\n            return res.redirect(newUrl);\n        # otherwise ...\n        return fn(req, res, *args, **kwargs);\n```\nThen, as before, install onto you app via:\n```py\napp.install(plugin_enforceHttps)\n```\n\n#### Plugin Application Order\n\nIdeally, plugins should be written *independently* of all other plugins. That is, each plugin should assume that other plugins may exist, and that plugins may be applied in any order.\n\nHowever, it may be useful to know that plugins that are installed first are applied first by Vilo. That is if you `app.install(X)`, then install `Y`, and then `Z`; then effectively `Z(Y(X(.)))` should be expected. That is, `X(.)` completes first, then `Y(.)`, and then `Z(.)`.\n\nTestBin: In-Memory Pastebin App\n-----------------------------------------\n\nCheck out [`testbin.py`](https://github.com/polydojo/vilo/blob/master/testing.py) a super-simple, in-memory [pastebin](https://en.wikipedia.org/wiki/Pastebin) app. Instead of connecting to a database or file-storage system, the app uses a `dict` for (temporarily) storing pastes.\n\n**Running:**  \nThe `testbin.py` module is included in Vilo's GitHub repo. The app may be run as follows:\n```\ngunicorn testbin:wsgi --reload\n```\nThen, head over to `localhost:8080` in your favorite browser.\n\nViloLog: DB-Backed Blogging Engine\n---------------------------------------------\nFor a bit more in-depth example, check out [ViloLog](https://github.com/polydojo/vilolog): a blogging engine built atop Vilo and [PogoDB](https://github.com/polydojo/pogodb).\n\nLicensing\n------------\nCopyright (c) 2020 Polydojo, Inc.\n\n**Software Licensing:**  \nThe software is released \"AS IS\" under the **Apache License 2.0**, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Kindly see [LICENSE.txt](https://github.com/polydojo/vilo/blob/master/LICENSE.txt) for more details.\n\n**No Trademark Rights:**  \nThe above software licensing terms **do not** grant any right in the trademarks, service marks, brand names or logos of Polydojo, Inc.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolydojo%2Fvilo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpolydojo%2Fvilo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolydojo%2Fvilo/lists"}