{"id":28407619,"url":"https://github.com/level12/keg","last_synced_at":"2025-09-12T05:40:10.599Z","repository":{"id":29428288,"uuid":"32964113","full_name":"level12/keg","owner":"level12","description":"Keg: more than Flask","archived":false,"fork":false,"pushed_at":"2024-12-17T13:14:20.000Z","size":50440,"stargazers_count":13,"open_issues_count":44,"forks_count":9,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-07-09T14:07:34.281Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/level12.png","metadata":{"files":{"readme":"readme.rst","changelog":"changelog-archive.rst","contributing":null,"funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-03-27T02:41:04.000Z","updated_at":"2025-06-02T14:54:31.000Z","dependencies_parsed_at":"2024-12-16T22:23:07.541Z","dependency_job_id":"d8d3fdf0-62fd-4b25-9726-fb48b0a9f7ba","html_url":"https://github.com/level12/keg","commit_stats":{"total_commits":323,"total_committers":12,"mean_commits":"26.916666666666668","dds":0.5882352941176471,"last_synced_commit":"dab8e5780acb9aa472d35dbac8dacd8a0873a4ef"},"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/level12/keg","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/level12%2Fkeg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/level12%2Fkeg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/level12%2Fkeg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/level12%2Fkeg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/level12","download_url":"https://codeload.github.com/level12/keg/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/level12%2Fkeg/sbom","scorecard":{"id":586098,"data":{"date":"2025-08-11","repo":{"name":"github.com/level12/keg","commit":"90b9f57c803ce7be4039ce3f7c001f23eea910a6"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.1,"checks":[{"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":"Code-Review","score":0,"reason":"Found 2/21 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":"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":"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":"Maintained","score":1,"reason":"0 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"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":"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":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: license.txt:0","Warn: project license file does not contain an FSF or OSI license."],"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":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"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"}},{"name":"Vulnerabilities","score":0,"reason":"17 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2023-62 / GHSA-m2qf-hxjv-5gpq","Warn: Project is vulnerable to: GHSA-cpwx-vrp4-4pq7","Warn: Project is vulnerable to: GHSA-gmj6-6f8f-6699","Warn: Project is vulnerable to: GHSA-h5c8-rqwp-cp95","Warn: Project is vulnerable to: GHSA-h75v-3vvj-5mfj","Warn: Project is vulnerable to: GHSA-q2x7-8rv6-6q7h","Warn: Project is vulnerable to: PYSEC-2022-42969","Warn: Project is vulnerable to: PYSEC-2024-211 / GHSA-3f84-rpwh-47g6","Warn: Project is vulnerable to: PYSEC-2024-210 / GHSA-9298-4cf8-g4wj","Warn: Project is vulnerable to: PYSEC-2024-188 / GHSA-mg3v-6m49-jhp3","Warn: Project is vulnerable to: GHSA-2g68-c3qc-8985","Warn: Project is vulnerable to: GHSA-f9vj-2wh5-fj8j","Warn: Project is vulnerable to: PYSEC-2023-221 / GHSA-hrfv-mqp8-q5rw","Warn: Project is vulnerable to: PYSEC-2023-57 / GHSA-px8h-6qxv-m22q","Warn: Project is vulnerable to: GHSA-q34m-jh98-gwm2","Warn: Project is vulnerable to: PYSEC-2023-58 / GHSA-xg9f-g7g7-2323","Warn: Project is vulnerable to: GHSA-jfmj-5v4g-7637"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 14 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T20:33:09.885Z","repository_id":29428288,"created_at":"2025-08-20T20:33:09.885Z","updated_at":"2025-08-20T20:33:09.885Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274759115,"owners_count":25343872,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-12T02:00:09.324Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2025-06-02T01:30:27.453Z","updated_at":"2025-09-12T05:40:10.576Z","avatar_url":"https://github.com/level12.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":".. default-role:: code\n\nKeg: more than Flask\n####################\n\n.. image:: https://codecov.io/github/level12/keg/coverage.svg?branch=master\n    :target: https://codecov.io/github/level12/keg?branch=master\n\n.. image:: https://img.shields.io/pypi/v/Keg.svg\n    :target: https://img.shields.io/pypi/v/Keg.svg\n\n.. image:: https://img.shields.io/pypi/l/keg.svg\n    :target: https://img.shields.io/pypi/l/keg.svg\n\n.. image:: https://img.shields.io/pypi/pyversions/keg.svg\n    :target: https://img.shields.io/pypi/pyversions/keg.svg\n\n.. image:: https://img.shields.io/pypi/status/Keg.svg\n    :target: https://img.shields.io/pypi/status/Keg.svg\n\n.. image:: https://ci.appveyor.com/api/projects/status/wm35hheykxs8851r\n    :alt: AppVeyor Build\n    :target: https://ci.appveyor.com/project/level12/keg-6gnlh\n\nKeg is an opinionated but flexible web framework built on Flask and SQLAlchemy.\n\n\nKeg's Goal\n==========\n\nThe goal for this project is to encapsulate Flask best practices and libraries so devs can avoid\nboilerplate and work on the important stuff.\n\nWe will lean towards being opinionated on the big things (like SQLAlchemy as our ORM) while\nsupporting hooks and customizations as much as possible.\n\nThink North of Flask but South of Django.\n\nFeatures\n========\n\nDefault Logging Configuration\n-----------------------------\n\nWe highly recommend good logging practices and, as such, a Keg application does basic setup of the\nPython logging system:\n\n- Sets the log level on the root logger to INFO\n- Creates two handlers and assigns them to the root logger:\n\n  - outputs to stderr\n  - outputs to syslog\n\n- Provides an optional json formatter\n\nThe thinking behind that is:\n\n- In development, a developer will see log messages on stdout and doesn't have to monitor a file.\n- Log messages will be in syslog by default and available for review there if no other action is\n  taken by the developer or sysadmin.  This avoids the need to manage log placement, permissions,\n  rotation, etc.\n- It's easy to configure syslog daemons to forward log messages to different files or remote log\n  servers and it's better to handle that type of need at the syslog level than in the app.\n- Structured log files (json) provide metadata details in a easy-to-parse format and should be\n  easy to generate.\n- The options and output should be easily configurable from the app to account for different needs\n  in development and deployed scenarios.\n- Keg's logging setup should be easy to turn off and/or completely override for situations where it\n  hurts more than it helps.\n\nInstallation\n============\n\n`pip install keg`\n\n\nApp Configuration\n=================\n\nCLI Command\n-----------\n\nThe command `\u003cmyapp\u003e develop config` will give detailed information about the files and objects\nbeing used to configure an application.\n\nProfile Priority\n----------------\n\nAll configuration classes with the name `DefaultProfile` will be applied to the app's config\nfirst.\n\nThen, the configuration classes that match the \"selected\" profile will be applied on top of the\napp's existing configuration. This makes the settings from the \"selected\" profile override any\nsettings from the `DefaultProfile.`\n\nPractically speaking, any configuration that applies to the entire app regardless of what context\nit is being used in will generally go in `myapp.config` in the `DefaultProfile` class.\n\nSelecting a Configuration Profile\n---------------------------------\n\nThe \"selected\" profile is the name of the objects that the Keg configuration handling code will\nlook for.  It should be a string.\n\nA Keg app considers the \"selected\" profile as follows:\n\n* If `config_profile` was passed into `myapp.init()` as an argument, use it as the\n  selected profile.  The `--profile` cli option uses this method to set the selected profile and\n  therefore has the highest priority.\n* Look in the app's environment namespace for \"CONFIG_PROFILE\".  If found, use it.\n* If running tests, use \"TestProfile\".  Whether or not the app is operating in this mode is\n  controlled by the use of:\n\n  - `myapp.init(use_test_profile=True)` which is used by `MyApp.testing_prep()`\n  - looking in the app's environment namespace for \"USE_TEST_PROFILE\" which is used by\n    `keg.testing.invoke_command()`\n\n* Look in the app's main config file (`app.config`) and all it's other\n  config files for the variable `DEFAULT_PROFILE`.  If found, use the value from the file with\n  highest priority.\n\n\nViews\n=====\n\nWhile generic Flask views will certainly work in this framework, Keg provides a BaseView that\napplies a certain amount of magic around route and blueprint setup. BaseView is based on Flask's\nMethodView. Best practice is to set up a blueprint and attach the views to it via the `blueprint`\nattribute. Be aware, BaseView will set up some route, endpoint, and template location defaults,\nbut these can be configured if needed.\n\nBlueprint Setup\n---------------\n\nAdding views to a blueprint is accomplished via the `blueprint` attribute on the view. Note,\nBaseView magic kicks in when the class is created, so assigning the blueprint later on will not\ncurrently have the desired effect.\n\n    import flask\n    from keg.web import BaseView\n\n    blueprint = flask.Blueprint('routing', __name__)\n\n\n    class VerbRouting(BaseView):\n        blueprint = blueprint\n\n        def get(self):\n            return 'method get'\n\nOnce the blueprint is created, you must attach it to the app via the `use_blueprints` app attribute:\n\n    from keg.app import Keg\n    from my_app.views import blueprint\n\n\n    class MyApp(Keg):\n        import_name = 'myapp'\n        use_blueprints = (blueprint, )\n\nBlueprints take some parameters for URL prefix and template path. BaseView will respect these when\ngenerating URLs and finding templates:\n\n    blueprint = flask.Blueprint(\n        'custom',\n        __name__,\n        template_folder='../templates/specific-path',\n        url_prefix='/tanagra')\n\n    class BlueprintTest(BaseView):\n        # template \"blueprint_test.html\" will be expected in specific-path\n        # endpoint is custom.blueprint-test\n        # URL is /tanagra/blueprint-test\n        blueprint = blueprint\n\n        def get(self):\n            return self.render()\n\nTemplate Discovery\n------------------\n\nTo avoid requiring the developer to configure all the things, BaseView will attempt to discover the\ncorrect template for a view, based on the view class name. Generally, this is a camel-case to\nunderscore-notation conversion. Blueprint name is included in the path, unless the blueprint has\nits own `template_path` defined.\n\n* `class MyBestView` in blueprint named \"public\" -\u003e `\u003capp\u003e/templates/public/my_best_view.html`\n* `class View2` in blueprint named \"other\" with template path \"foo\" -\u003e `\u003capp\u003e/foo/view2.html`\n\nA view may be given a `template_name` attribute to override the default filename, although the same\npath is used for discovery:\n\n    class TemplateOverride(BaseView):\n        blueprint = blueprint\n        template_name = 'my-special-template.html'\n\n        def get(self):\n            return self.render()\n\nURL and Endpoint Calculation\n----------------------------\n\nBaseView has `calc_url` and `calc_endpoint` class methods which will allow the developer to avoid\nhard-coding those types of values throughout the code. These methods will both produce the full\nURL/endpoint, including the blueprint prefix (if any).\n\nRoute Generation\n----------------\n\nBaseView will, by default, create rules for views on their respective blueprints. Generally, this\nis based on the view class name as a camel-case to dash-notation conversion:\n\n* `class MyBestView` in blueprint named \"public\": `/my-best-view` -\u003e `public.my-best-view`\n* `class View2` in blueprint named \"other\" with URL prefix \"foo\": `/foo/view2` -\u003e `other.view2`\n\nNote that BaseView is a MethodView implementation, so methods named `get`, `post`, etc. will be\nrespected as the appropriate targets in the request/response cycle.\n\nA view may be given a `url` attribute to override the default:\n\n    class RouteOverride(BaseView):\n        blueprint = blueprint\n        url = '/something-other-than-the-default'\n\n        def get(self):\n            return self.render()\n\nSee `keg_apps/web/views/routing.py` for other routing possibilities that BaseView supports.\n\n\nComponents\n==========\n\nKeg components follow the paradigm of flask extensions, and provide some defaults for the\npurpose of setting up model/view structure. Using components, a project may be broken down into\nlogical blocks, each having their own entities, blueprints, templates, tests, etc.\n\n* Components need to be registered in config at `KEG_REGISTERED_COMPONENTS`\n\n  * The path given here should be a full dotted path to the top level of the component\n\n    * e.g. `my_app.components.blog`\n\n  * At the top level of the component, `__component__` must be defined as an instance of KegComponent\n\n    * Depending on the needs of the component, model and view discovery may be driven by the subclasses\n      of KegComponent that have path defaults\n    * Examples:\n\n      * `__component__ = KegModelComponent('blog')`\n      * `__component__ = KegViewComponent('blog')`\n      * `__component__ = KegModelViewComponent('blog')`\n\n* Component discovery\n\n  * A component will attempt to load model and blueprints on app init\n  * The default paths relative to the component may be modified or extended on the component's definition\n  * Default model path in \"model\" components: `.model.entities`\n\n    * Override via the component's `db_visit_modules` list of relative import paths\n\n  * Default blueprint path for \"view\" components: `.views.component_bp`\n\n    * Use the `create_named_blueprint` or `create_blueprint` helpers on the component's `__component__`\n      to create blueprints with configured template folders\n    * Override via the component's `load_blueprints` list\n\n      * List elements are a tuple of the relative import path and the name of the blueprint attribute\n\n    * Components have their own template stores, in a `templates` folder\n\n      * Override the component's template path via the `template_folder` attribute\n\n  * Paths may also be supplied to the constructor\n\n    * e.g. `__component__ = KegComponent('blog', db_visit_modules=('.somewhere.else', ))`\n\n\nKeg Development\n===============\n\nTo develop on keg, begin by installing dependencies and running the tests::\n\n    git clone https://github.com/level12/keg keg-src\n    cd keg-src\n\n    docker-compose up -d\n\n    # Create a virtualenv with a supported python version.  Activate it.\n    vex -m keg\n\n    # Install editable keg and test dependencies\n    pip install -e .[tests]\n\n    # Run tests\n    pytest keg\n\nYou can then examine tox.ini for insights into our development process.  In particular, we:\n\n* use `pytest` for testing (and coverage analysis)\n* use `flake8` for linting\n\nPreview Readme\n--------------\n\nWhen updating the readme, use `restview --long-description` to preview changes.\n\nLinks\n=====\n\n* Documentation: https://keg.readthedocs.io/en/stable/index.html\n* Releases: https://pypi.org/project/Keg/\n* Code: https://github.com/level12/keg\n* Issue tracker: https://github.com/level12/keg/issues\n* Questions \u0026 comments: http://groups.google.com/group/blazelibs\n\nCurrent Status\n==============\n\n* Stable in a relatively small number of production environments.\n* API is likely to change with smaller compatibility breaks happening more frequently than larger ones.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flevel12%2Fkeg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flevel12%2Fkeg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flevel12%2Fkeg/lists"}