├── .dockerignore ├── .github ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build_and_test.yml │ └── pdm.yml ├── .gitignore ├── .pylintrc ├── .readthedocs.yaml ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTORS.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── Vagrantfile ├── cement ├── __init__.py ├── cli │ ├── __init__.py │ ├── controllers │ │ ├── __init__.py │ │ └── base.py │ ├── main.py │ └── templates │ │ ├── __init__.py │ │ └── generate │ │ ├── extension │ │ ├── .generate.yml │ │ └── ext_{{ label }}.py │ │ ├── plugin │ │ ├── .generate.yml │ │ └── {{ label }} │ │ │ ├── __init__.py │ │ │ ├── controllers │ │ │ ├── __init__.py │ │ │ └── {{ label }}.py │ │ │ └── templates │ │ │ ├── __init__.py │ │ │ └── plugins │ │ │ └── {{ label }} │ │ │ └── command1.jinja2 │ │ ├── project │ │ ├── .generate.yml │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── Dockerfile │ │ ├── LICENSE.md │ │ ├── MANIFEST.in │ │ ├── Makefile │ │ ├── README.md │ │ ├── config │ │ │ └── {{ label }}.yml.example │ │ ├── docs │ │ │ └── .gitkeep │ │ ├── requirements-dev.txt │ │ ├── requirements.txt │ │ ├── setup.cfg │ │ ├── setup.py │ │ ├── tests │ │ │ ├── conftest.py │ │ │ └── test_{{ label }}.py │ │ └── {{ label }} │ │ │ ├── __init__.py │ │ │ ├── controllers │ │ │ ├── __init__.py │ │ │ └── base.py │ │ │ ├── core │ │ │ ├── __init__.py │ │ │ ├── exc.py │ │ │ └── version.py │ │ │ ├── ext │ │ │ └── __init__.py │ │ │ ├── main.py │ │ │ ├── plugins │ │ │ └── __init__.py │ │ │ └── templates │ │ │ ├── __init__.py │ │ │ └── command1.jinja2 │ │ ├── script │ │ ├── .generate.yml │ │ └── {{ label }}.py │ │ └── todo-tutorial │ │ ├── .generate.yml │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── Dockerfile │ │ ├── LICENSE.md │ │ ├── MANIFEST.in │ │ ├── Makefile │ │ ├── README.md │ │ ├── config │ │ └── todo.yml.example │ │ ├── docs │ │ └── .gitkeep │ │ ├── requirements-dev.txt │ │ ├── requirements.txt │ │ ├── setup.cfg │ │ ├── setup.py │ │ ├── tests │ │ ├── conftest.py │ │ └── test_main.py │ │ └── todo │ │ ├── __init__.py │ │ ├── controllers │ │ ├── __init__.py │ │ ├── base.py │ │ └── items.py │ │ ├── core │ │ ├── __init__.py │ │ ├── exc.py │ │ └── version.py │ │ ├── ext │ │ └── __init__.py │ │ ├── main.py │ │ ├── plugins │ │ └── __init__.py │ │ └── templates │ │ ├── __init__.py │ │ ├── command1.jinja2 │ │ └── items │ │ └── list.jinja2 ├── core │ ├── __init__.py │ ├── arg.py │ ├── backend.py │ ├── cache.py │ ├── config.py │ ├── controller.py │ ├── deprecations.py │ ├── exc.py │ ├── extension.py │ ├── foundation.py │ ├── handler.py │ ├── hook.py │ ├── interface.py │ ├── log.py │ ├── mail.py │ ├── meta.py │ ├── output.py │ ├── plugin.py │ └── template.py ├── ext │ ├── __init__.py │ ├── ext_alarm.py │ ├── ext_argparse.py │ ├── ext_colorlog.py │ ├── ext_configparser.py │ ├── ext_daemon.py │ ├── ext_dummy.py │ ├── ext_generate.py │ ├── ext_jinja2.py │ ├── ext_json.py │ ├── ext_logging.py │ ├── ext_memcached.py │ ├── ext_mustache.py │ ├── ext_plugin.py │ ├── ext_print.py │ ├── ext_redis.py │ ├── ext_scrub.py │ ├── ext_smtp.py │ ├── ext_tabulate.py │ ├── ext_watchdog.py │ └── ext_yaml.py ├── py.typed └── utils │ ├── __init__.py │ ├── fs.py │ ├── misc.py │ ├── shell.py │ ├── test.py │ └── version.py ├── docker-compose.yml ├── docker ├── Dockerfile.dev ├── Dockerfile.dev-py310 ├── Dockerfile.dev-py311 ├── Dockerfile.dev-py312 ├── Dockerfile.dev-py313 ├── Dockerfile.dev-py38 ├── Dockerfile.dev-py39 ├── bashrc ├── compose-services-only.yml ├── mailpit │ ├── dev-cert.pem │ └── dev-key.pem └── vimrc ├── docs └── source │ ├── _static │ ├── .placeholder │ ├── logo-rhino.png │ ├── logo-text.png │ └── logo.png │ ├── _templates │ ├── .placeholder │ ├── globaltoc.html │ ├── layout.html │ └── logo-text.html │ ├── _themes │ └── .placeholder │ ├── api │ ├── core │ │ ├── arg.rst │ │ ├── backend.rst │ │ ├── cache.rst │ │ ├── config.rst │ │ ├── controller.rst │ │ ├── exc.rst │ │ ├── extension.rst │ │ ├── foundation.rst │ │ ├── handler.rst │ │ ├── hook.rst │ │ ├── index.rst │ │ ├── interface.rst │ │ ├── log.rst │ │ ├── mail.rst │ │ ├── meta.rst │ │ ├── output.rst │ │ ├── plugin.rst │ │ └── template.rst │ ├── ext │ │ ├── ext_alarm.rst │ │ ├── ext_argparse.rst │ │ ├── ext_colorlog.rst │ │ ├── ext_configparser.rst │ │ ├── ext_daemon.rst │ │ ├── ext_dummy.rst │ │ ├── ext_generate.rst │ │ ├── ext_jinja2.rst │ │ ├── ext_json.rst │ │ ├── ext_logging.rst │ │ ├── ext_memcached.rst │ │ ├── ext_mustache.rst │ │ ├── ext_plugin.rst │ │ ├── ext_print.rst │ │ ├── ext_redis.rst │ │ ├── ext_scrub.rst │ │ ├── ext_smtp.rst │ │ ├── ext_tabulate.rst │ │ ├── ext_watchdog.rst │ │ ├── ext_yaml.rst │ │ └── index.rst │ ├── index.rst │ └── utils │ │ ├── fs.rst │ │ ├── index.rst │ │ ├── misc.rst │ │ ├── shell.rst │ │ └── test.rst │ ├── conf.py │ └── index.rst ├── lgtm.yml ├── pdm.lock ├── pyproject.toml ├── scripts ├── cli-smoke-test.sh ├── devtools.py ├── travis.sh └── vagrant │ ├── bootstrap.ps1 │ └── bootstrap.sh ├── tests ├── __init__.py ├── bootstrap.py ├── cli │ ├── __init__.py │ └── test_main.py ├── conftest.py ├── core │ ├── __init__.py │ ├── test_arg.py │ ├── test_backend.py │ ├── test_cache.py │ ├── test_config.py │ ├── test_controller.py │ ├── test_deprecations.py │ ├── test_exc.py │ ├── test_extension.py │ ├── test_foundation.py │ ├── test_handler.py │ ├── test_hook.py │ ├── test_interface.py │ ├── test_log.py │ ├── test_mail.py │ ├── test_meta.py │ ├── test_output.py │ ├── test_plugin.py │ └── test_template.py ├── data │ ├── __init__.py │ ├── config │ │ ├── config.json │ │ └── config.yml │ └── templates │ │ ├── __init__.py │ │ ├── bad_template.genshi │ │ ├── bad_template.handlebars │ │ ├── bad_template.jinja2 │ │ ├── bad_template.mustache │ │ ├── generate │ │ ├── test1 │ │ │ ├── .generate.yml │ │ │ ├── exclude-me │ │ │ ├── ignore-me │ │ │ └── take-me │ │ ├── test2 │ │ │ ├── .generate.yml │ │ │ └── take-me │ │ ├── test3 │ │ │ ├── .generate.yml │ │ │ └── take-me │ │ ├── test4 │ │ │ ├── .generate.yml │ │ │ ├── exclude-me │ │ │ │ └── take-me │ │ │ ├── ignore-me │ │ │ │ └── take-me │ │ │ └── take-me │ │ │ │ ├── exclude-me │ │ │ │ ├── ignore-me │ │ │ │ └── take-me │ │ └── test5 │ │ │ ├── .generate.yml │ │ │ └── take-me │ │ ├── test_base_template.handlebars │ │ ├── test_base_template.mustache │ │ ├── test_partial_template.handlebars │ │ ├── test_partial_template.mustache │ │ ├── test_template.genshi │ │ ├── test_template.handlebars │ │ ├── test_template.jinja2 │ │ ├── test_template.mustache │ │ ├── test_template_child.jinja2 │ │ ├── test_template_parent.jinja2 │ │ └── test_template_utf8.jinja2 ├── ext │ ├── __init__.py │ ├── test_ext_alarm.py │ ├── test_ext_argparse.py │ ├── test_ext_colorlog.py │ ├── test_ext_configparser.py │ ├── test_ext_daemon.py │ ├── test_ext_dummy.py │ ├── test_ext_generate.py │ ├── test_ext_jinja2.py │ ├── test_ext_json.py │ ├── test_ext_logging.py │ ├── test_ext_memcached.py │ ├── test_ext_mustache.py │ ├── test_ext_plugin.py │ ├── test_ext_print.py │ ├── test_ext_redis.py │ ├── test_ext_scrub.py │ ├── test_ext_smtp.py │ ├── test_ext_tabulate.py │ ├── test_ext_watchdog.py │ └── test_ext_yaml.py ├── test_init.py └── utils │ ├── __init__.py │ ├── test_fs.py │ ├── test_misc.py │ ├── test_shell.py │ └── test_version.py └── tmp └── .gitkeep /.dockerignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | .venv 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: datafolklabs 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Cement Issue Reporting and Feature Requests 2 | 3 | Please include any of the following in bug/issue reports where it is relevant: 4 | 5 | **System Information** 6 | 7 | - Cement Version: 8 | - Python Version: 9 | - Operating System and Version: 10 | 11 | **Steps to Reproduce (Bugs Only)** 12 | 13 | - Step 1... 14 | - Step 2... 15 | - Step 3... 16 | - Etc 17 | 18 | **Example** 19 | 20 | The following is an [MCVE] (*Minimal Complete 21 | Verifiable Example*) that reproduces the issue: 22 | 23 | ```python 24 | # INSERT EXAMPLE CODE HERE 25 | ``` 26 | 27 | [MCVE]: https://stackoverflow.com/help/mcve 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | **NOTICE:** Prior to submitting a pull request, please ensure you abide by the 3 | [Guidelines for Code Contributions], including but not limited to: 4 | 5 | - PR is associated with at least one issue 6 | - Submit from a **topic** branch, not a pre-existing Cement branch 7 | - Make every effort to comply with [PEP8] 8 | - All tests pass successfully 9 | - Coverage reports 100% code coverage 10 | 11 | --- 12 | 13 | **Issue:** # 14 | 15 | 16 | [Guidelines for Code Contributions]: https://github.com/datafolklabs/cement/blob/master/.github/CONTRIBUTING.md#guidelines-for-code-contributions 17 | [PEP8]: http://www.python.org/dev/peps/pep-0008/ 18 | -------------------------------------------------------------------------------- /.github/workflows/build_and_test.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | permissions: 4 | contents: read 5 | pull-requests: write 6 | 7 | on: [pull_request] 8 | 9 | env: 10 | SMTP_HOST: localhost 11 | SMTP_PORT: 1025 12 | MEMCACHED_HOST: localhost 13 | REDIS_HOST: localhost 14 | 15 | jobs: 16 | comply: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: ConorMacBride/install-package@v1 22 | with: 23 | apt: libmemcached-dev 24 | - name: Set up Python 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: "3.x" 28 | architecture: "x64" 29 | - name: Setup PDM 30 | uses: pdm-project/setup-pdm@v4 31 | - name: Install dependencies 32 | run: pdm install 33 | - name: Make Comply 34 | run: make comply 35 | 36 | test: 37 | needs: comply 38 | runs-on: ubuntu-latest 39 | 40 | steps: 41 | - uses: actions/checkout@v4 42 | - uses: ConorMacBride/install-package@v1 43 | with: 44 | apt: libmemcached-dev 45 | - name: Set up Python 46 | uses: actions/setup-python@v5 47 | with: 48 | python-version: "3.x" 49 | architecture: "x64" 50 | - uses: hoverkraft-tech/compose-action@v2.0.1 51 | with: 52 | compose-file: "./docker/compose-services-only.yml" 53 | - name: Setup PDM 54 | uses: pdm-project/setup-pdm@v4 55 | - name: Install dependencies 56 | run: pdm install 57 | - name: Make Test 58 | run: make test 59 | 60 | test-all: 61 | needs: test 62 | runs-on: ${{ matrix.os }} 63 | strategy: 64 | matrix: 65 | # FIXME ? 66 | # os: [ubuntu-latest, macos-latest, windows-latest] 67 | os: [ubuntu-latest] 68 | python-version: ["3.8", "3.9", "3.11", "3.12", "3.13", "pypy3.10"] 69 | 70 | steps: 71 | - uses: actions/checkout@v4 72 | - uses: ConorMacBride/install-package@v1 73 | with: 74 | apt: libmemcached-dev 75 | - uses: hoverkraft-tech/compose-action@v2.0.1 76 | with: 77 | compose-file: "./docker/compose-services-only.yml" 78 | - name: Setup PDM 79 | uses: pdm-project/setup-pdm@v4 80 | - name: Install dependencies 81 | run: pdm install 82 | - name: Make Test 83 | run: make test 84 | 85 | cli-smoke-test: 86 | needs: test-all 87 | runs-on: ubuntu-latest 88 | 89 | steps: 90 | - uses: actions/checkout@v4 91 | - uses: hoverkraft-tech/compose-action@v2.0.1 92 | with: 93 | compose-file: "./docker-compose.yml" 94 | - name: CLI Smoke Tests 95 | run: ./scripts/cli-smoke-test.sh 96 | - if: always() 97 | name: Review Output 98 | run: cat ./tmp/cli-smoke-test.out 99 | -------------------------------------------------------------------------------- /.github/workflows/pdm.yml: -------------------------------------------------------------------------------- 1 | name: Update dependencies 2 | 3 | on: 4 | schedule: 5 | - cron: "5 3 * * 1" 6 | 7 | jobs: 8 | update-dependencies: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Update dependencies 14 | uses: pdm-project/update-deps-action@main 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *egg* 2 | env/ 3 | env-windows/ 4 | var/ 5 | log/ 6 | dist 7 | __pycache__/ 8 | .cache/ 9 | tmp/ 10 | 11 | # Gitignores from https://github.com/github/gitignore 12 | 13 | # Vi 14 | *.swp 15 | *.swo 16 | 17 | 18 | # Vim 19 | .*.sw[a-z] 20 | *.un~ 21 | 22 | 23 | # OSX 24 | .DS_Store? 25 | Icon? 26 | 27 | # Thumbnails 28 | ._* 29 | 30 | # Files that might appear on external disk 31 | .Spotlight-V100 32 | .Trashes 33 | 34 | 35 | # Python 36 | *.py[co] 37 | 38 | # Packages 39 | *.egg 40 | *.egg-info 41 | dist 42 | build 43 | eggs 44 | parts 45 | bin 46 | develop-eggs 47 | .installed.cfg 48 | 49 | # Installer logs 50 | pip-log.txt 51 | 52 | # Documentation 53 | doc/build 54 | # Unit test / coverage reports 55 | .coverage 56 | htmlcov 57 | coverage-report 58 | .tox 59 | 60 | # TextMate 61 | *.tmproj 62 | *.tmproject 63 | tmtags 64 | 65 | test.log 66 | 67 | example.py 68 | extensions 69 | .env 70 | .vagrant 71 | myapp*.py 72 | 73 | # redis test artifacts 74 | dump.rdb 75 | 76 | # testing 77 | .pytest_cache 78 | 79 | # VS Code 80 | .vscode/ 81 | 82 | # PDM 83 | .venv 84 | __pypackages__ 85 | .pdm.toml 86 | .pdm-python 87 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.12" 13 | apt_packages: 14 | - libmemcached-dev 15 | 16 | # Build documentation in the docs/ directory with Sphinx 17 | sphinx: 18 | configuration: docs/source/conf.py 19 | 20 | # We recommend specifying your dependencies to enable reproducible builds: 21 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 22 | python: 23 | install: 24 | - method: pip 25 | path: . 26 | extra_requirements: 27 | - docs 28 | 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | before_install: 4 | - sudo apt-get -y install pipx python3-venv 5 | - pipx ensurepath 6 | - pipx install pdm 7 | script: ./scripts/travis.sh 8 | os: 9 | - linux 10 | 11 | # env's are redundant, but at global scope additional jobs are created for 12 | # each env var which I'm sure has a purpose but don't like 13 | matrix: 14 | include: 15 | - python: "3.8" 16 | dist: "focal" 17 | sudo: true 18 | env: 19 | - DOCKER_COMPOSE_VERSION=v2.17.3 20 | - SMTP_HOST=localhost 21 | - SMTP_PORT=1025 22 | - python: "3.9" 23 | dist: "focal" 24 | sudo: true 25 | env: 26 | - DOCKER_COMPOSE_VERSION=v2.17.3 27 | - SMTP_HOST=localhost 28 | - SMTP_PORT=1025 29 | - python: "3.10" 30 | dist: "focal" 31 | sudo: true 32 | env: 33 | - DOCKER_COMPOSE_VERSION=v2.17.3 34 | - SMTP_HOST=localhost 35 | - SMTP_PORT=1025 36 | - python: "3.11" 37 | dist: "focal" 38 | sudo: true 39 | env: 40 | - DOCKER_COMPOSE_VERSION=v2.17.3 41 | - SMTP_HOST=localhost 42 | - SMTP_PORT=1025 43 | - python: "3.12" 44 | dist: "jammy" 45 | sudo: true 46 | env: 47 | - DOCKER_COMPOSE_VERSION=v2.17.3 48 | - SMTP_HOST=localhost 49 | - SMTP_PORT=1025 50 | 51 | - python: "3.13" 52 | dist: "jammy" 53 | sudo: true 54 | env: 55 | - DOCKER_COMPOSE_VERSION=v2.17.3 56 | - SMTP_HOST=localhost 57 | - SMTP_PORT=1025 58 | # below is a workaround due to invalid travis Python version 59 | - PDM_IGNORE_ACTIVE_VENV=true 60 | - PYTHON_VERSION=3.13 61 | 62 | services: 63 | - memcached 64 | - redis-server 65 | - docker 66 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | The following people have contributed to Cement, either by way of source code, 4 | documentation, or testing: 5 | 6 | - BJ Dierkes (derks) - Creator, Primary Maintainer 7 | - Dan Liberatori (garroadran) 8 | - Kyle Rockman (rocktavious) 9 | - Tomasz Czyż (spinus) 10 | - Ildar Akhmetgaleev (akhilman) 11 | - Nicolas Brisac (zacbri) 12 | - Subhash Bhushan (subhashb) 13 | - Sam Likins (samlikins) 14 | - Dakota Blair (dakotablair) 15 | - Luca Crea (lcrea) 16 | - Xavier RENE-CORAIL (xcorail) 17 | - Lucas Clay (mlclay) 18 | - Adam Hodges (ajhodges) 19 | - Stelios Tymvios (namedLambda) 20 | - Spyros Vlachos (devspyrosv) 21 | - Joe Roberts (jjroberts) 22 | - Mudassir Chapra (muddi900) 23 | - Christian Hengl (rednar) 24 | - sigma67 25 | - Blake Jameson (blakejameson) 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13-alpine 2 | LABEL MAINTAINER="BJ Dierkes " 3 | ENV PS1="\[\e[0;33m\]|> cement <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# " 4 | ENV PATH="${PATH}:/root/.local/bin" 5 | WORKDIR /src 6 | COPY . /src 7 | COPY docker/vimrc /root/.vimrc 8 | COPY docker/bashrc /root/.bashrc 9 | 10 | RUN apk update \ 11 | && apk add pipx vim \ 12 | && ln -sf /usr/bin/vim /usr/bin/vi \ 13 | && pipx install pdm 14 | RUN pdm build 15 | RUN pip install `ls dist/cement-*.tar.gz`[cli] 16 | 17 | WORKDIR / 18 | ENTRYPOINT ["/usr/local/bin/cement"] 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2009 Data Folk Labs, LLC 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | * Neither the name of Data Folk Labs, LLC. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dev test test-core comply-fix docs clean dist dist-upload docker docker-push 2 | 3 | dev: 4 | docker compose up -d 5 | docker compose exec cement pdm install 6 | docker compose exec cement-py38 pdm install 7 | docker compose exec cement-py39 pdm install 8 | docker compose exec cement-py310 pdm install 9 | docker compose exec cement-py311 pdm install 10 | docker compose exec cement-py312 pdm install 11 | docker compose exec cement /bin/bash 12 | 13 | test: comply 14 | pdm run pytest --cov=cement tests 15 | 16 | test-core: comply 17 | pdm run pytest --cov=cement.core tests/core 18 | 19 | virtualenv: 20 | pdm venv create 21 | pdm install 22 | @echo 23 | @echo "VirtualENV Setup Complete. Now run: eval $(pdm venv activate)" 24 | @echo 25 | 26 | comply: comply-ruff comply-mypy 27 | 28 | comply-ruff: 29 | pdm run ruff check cement/ tests/ 30 | 31 | comply-ruff-fix: 32 | pdm run ruff check --fix cement/ tests/ 33 | 34 | comply-mypy: 35 | pdm run mypy 36 | 37 | docs: 38 | cd docs; pdm run sphinx-build ./source ./build; cd .. 39 | @echo 40 | @echo DOC: "file://"$$(echo `pwd`/docs/build/html/index.html) 41 | @echo 42 | 43 | clean: 44 | find . -name '*.py[co]' -delete 45 | rm -rf doc/build 46 | 47 | dist: 48 | pdm build 49 | 50 | docker: 51 | docker build -t datafolklabs/cement:latest . 52 | 53 | docker-push: 54 | docker push datafolklabs/cement:latest 55 | 56 | remove-merged-branches: 57 | git branch --merged | grep -v -e 'main\|stable/*\|dev/*' | xargs git branch -d 58 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | vagrantDir = File.expand_path("scripts/vagrant", File.dirname(__FILE__)) + "/" 5 | 6 | Vagrant.configure("2") do |config| 7 | config.vm.synced_folder ".", "/vagrant", disabled: false 8 | 9 | config.vm.define "linux" do |this| 10 | this.vm.box = "trueability/ubuntu-20.04" 11 | this.vm.hostname = "linux" 12 | # this.vm.provision "shell", path: "scripts/vagrant/bootstrap.sh" 13 | end 14 | 15 | config.vm.define "windows" do |this| 16 | this.vm.box = "senglin/win-10-enterprise-vs2015community" 17 | this.vm.hostname = "windows" 18 | # this.vm.provision "shell", path: "scripts/vagrant/bootstrap.ps1" 19 | end 20 | 21 | config.vm.provider "virtualbox" do |v| 22 | v.memory = "4192" 23 | v.cpus = "4" 24 | v.gui = true 25 | end 26 | 27 | config.vm.provider "vmware_desktop" do |v| 28 | v.vmx["memsize"] = "4192" 29 | v.vmx["numvcpus"] = "4" 30 | v.gui = true 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /cement/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | # flake8: noqa 3 | 4 | from .core.foundation import App, TestApp 5 | from .core.interface import Interface 6 | from .core.handler import Handler 7 | from .core.exc import FrameworkError, InterfaceError, CaughtSignal 8 | from .ext.ext_argparse import ArgparseController as Controller 9 | from .ext.ext_argparse import expose as ex 10 | from .utils.misc import init_defaults, minimal_logger 11 | from .utils import misc, fs, shell 12 | from .utils.version import get_version 13 | 14 | __all__ = [ 15 | "App", 16 | "TestApp", 17 | "Interface", 18 | "Handler", 19 | "FrameworkError", 20 | "InterfaceError", 21 | "CaughtSignal", 22 | "Controller", 23 | "ex", 24 | "init_defaults", 25 | "minimal_logger", 26 | "misc", 27 | "fs", 28 | "shell", 29 | "get_version", 30 | ] -------------------------------------------------------------------------------- /cement/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/__init__.py -------------------------------------------------------------------------------- /cement/cli/controllers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/controllers/__init__.py -------------------------------------------------------------------------------- /cement/cli/controllers/base.py: -------------------------------------------------------------------------------- 1 | 2 | from cement import Controller 3 | from cement.utils.version import get_version_banner 4 | 5 | BANNER = get_version_banner() 6 | 7 | 8 | class Base(Controller): 9 | class Meta: 10 | label = 'base' 11 | description = 'Cement Framework Developer Tools' 12 | epilog = 'Example: cement generate project /path/to/myapp' 13 | 14 | arguments = [ 15 | (['-v', '--version'], {'action': 'version', 'version': BANNER}), 16 | ] 17 | 18 | def _default(self) -> None: 19 | self.app.args.print_help() 20 | -------------------------------------------------------------------------------- /cement/cli/main.py: -------------------------------------------------------------------------------- 1 | 2 | from __future__ import annotations 3 | from typing import Optional, List 4 | from cement import App, CaughtSignal # noqa: E402 5 | from .controllers.base import Base # noqa: E402 6 | from cement.core.exc import FrameworkError 7 | 8 | 9 | class CementApp(App): 10 | class Meta: 11 | label = 'cement' 12 | controller = 'base' 13 | template_module = 'cement.cli.templates' 14 | template_handler = 'jinja2' 15 | config_handler = 'yaml' 16 | config_file_suffix = '.yml' 17 | 18 | extensions = [ 19 | 'generate', 20 | 'yaml', 21 | 'jinja2', 22 | ] 23 | 24 | handlers = [ 25 | Base, 26 | ] 27 | 28 | 29 | class CementTestApp(CementApp): 30 | class Meta: 31 | argv: List[str] = [] 32 | config_files: List[str] = [] 33 | exit_on_close = False 34 | 35 | 36 | def main(argv: Optional[List[str]] = None) -> None: 37 | # Issue #679: https://github.com/datafolklabs/cement/issues/679 38 | try: 39 | import yaml, jinja2 # type: ignore # noqa: F401 E401 40 | except ModuleNotFoundError: # pragma: nocover 41 | raise FrameworkError('Cement CLI Dependencies are missing! Install cement[cli] extras ' + 42 | 'package to resolve -> pip install cement[cli]') 43 | 44 | with CementApp() as app: 45 | try: 46 | app.run() 47 | except AssertionError as e: # pragma: nocover 48 | print(f'AssertionError > {e.args[0]}') # pragma: nocover 49 | app.exit_code = 1 # pragma: nocover 50 | except CaughtSignal as e: # pragma: nocover 51 | print(f'\n{e}') # pragma: nocover 52 | app.exit_code = 0 # pragma: nocover 53 | 54 | 55 | if __name__ == '__main__': 56 | main() # pragma: nocover 57 | -------------------------------------------------------------------------------- /cement/cli/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/extension/.generate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | exclude: 4 | - '^(.*)[\/\\\\]extension[\/\\\\]{{ label }}[\/\\\\]templates[\/\\\\](.*)$' 5 | 6 | ignore: 7 | - '^(.*)pyc(.*)$' 8 | - '^(.*)pyo(.*)$' 9 | - '^(.*)__pycache__(.*)$' 10 | 11 | variables: 12 | - name: label 13 | prompt: "Extension Label" 14 | default: "myextension" 15 | case: lower 16 | validate: "^[a-zA-Z0-9]+$" 17 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/extension/ext_{{ label }}.py: -------------------------------------------------------------------------------- 1 | 2 | from cement import minimal_logger 3 | 4 | LOG = minimal_logger(__name__) 5 | 6 | def {{ label }}_pre_run_hook(app): 7 | # do something with app 8 | LOG.debug('Inside {{ label }}_pre_run_hook!') 9 | 10 | def load(app): 11 | # do something to extend cement 12 | app.hook.register('pre_run', {{ label }}_pre_run_hook) 13 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/plugin/.generate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | exclude: 4 | - '^(.*)[\/\\\\]plugin[\/\\\\]{{ label }}[\/\\\\]templates[\/\\\\]plugins[\/\\\\]{{ label }}[\/\\\\]command1.jinja2$' 5 | 6 | ignore: 7 | - '^(.*)pyc(.*)$' 8 | - '^(.*)pyo(.*)$' 9 | - '^(.*)__pycache__(.*)$' 10 | 11 | variables: 12 | - name: label 13 | prompt: "Plugin Label" 14 | case: "lower" 15 | default: "myplugin" 16 | 17 | - name: class_name 18 | prompt: "Plugin Class Name" 19 | validate: "^[a-zA-Z0-9]+$" 20 | default: "MyPlugin" 21 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/plugin/{{ label }}/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from .controllers.{{ label }} import {{ class_name }} 4 | 5 | def add_template_dir(app): 6 | path = os.path.join(os.path.dirname(__file__), 'templates') 7 | app.add_template_dir(path) 8 | 9 | def load(app): 10 | app.handler.register({{ class_name }}) 11 | app.hook.register('post_setup', add_template_dir) 12 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/plugin/{{ label }}/controllers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/plugin/{{ label }}/controllers/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/plugin/{{ label }}/controllers/{{ label }}.py: -------------------------------------------------------------------------------- 1 | 2 | from cement import Controller, ex 3 | 4 | 5 | class {{ class_name }}(Controller): 6 | 7 | class Meta: 8 | label = '{{ label }}' 9 | stacked_on = 'base' 10 | stacked_type = 'nested' 11 | 12 | def _default(self): 13 | self._parser.print_help() 14 | 15 | @ex( 16 | help='example sub command1', 17 | arguments=[ 18 | ### add a sample foo option under subcommand namespace 19 | ( [ '-f', '--foo' ], 20 | { 'help' : 'notorious foo option', 21 | 'action' : 'store', 22 | 'dest' : 'foo' } ), 23 | ], 24 | ) 25 | def command1(self): 26 | """Example sub-command.""" 27 | 28 | data = { 29 | 'foo' : 'bar', 30 | } 31 | 32 | ### do something with arguments 33 | if self.app.pargs.foo is not None: 34 | data['foo'] = self.app.pargs.foo 35 | 36 | self.app.render(data, 'plugins/{{ label }}/command1.jinja2') 37 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/plugin/{{ label }}/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/plugin/{{ label }}/templates/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/plugin/{{ label }}/templates/plugins/{{ label }}/command1.jinja2: -------------------------------------------------------------------------------- 1 | Example template: Foo => {{ foo }} 2 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/.generate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | exclude: 4 | - '^(.*)[\/\\\\]project[\/\\\\]{{ label }}[\/\\\\]templates[\/\\\\](.*)$' 5 | 6 | ignore: 7 | - '^(.*)pyc(.*)$' 8 | - '^(.*)pyo(.*)$' 9 | - '^(.*)__pycache__(.*)$' 10 | 11 | variables: 12 | - name: label 13 | prompt: "App Label" 14 | case: "lower" 15 | default: "myapp" 16 | 17 | - name: name 18 | prompt: "App Name" 19 | default: "My Application" 20 | 21 | - name: class_name 22 | prompt: "App Class Name" 23 | validate: "^[a-zA-Z0-9]+$" 24 | default: "MyApp" 25 | 26 | - name: description 27 | prompt: "App Description" 28 | default: "MyApp Does Amazing Things!" 29 | 30 | - name: creator 31 | prompt: "Creator Name" 32 | default: "John Doe" 33 | 34 | - name: creator_email 35 | prompt: "Creator Email" 36 | default: "john.doe@example.com" 37 | 38 | - name: url 39 | prompt: "Project URL" 40 | default: "https://github.com/johndoe/myapp/" 41 | 42 | - name: license 43 | prompt: "License" 44 | default: "unlicensed" 45 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | coverage-report/ 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # {{ name }} Change History 2 | 3 | ## 0.0.1 4 | 5 | Initial release. 6 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-alpine 2 | LABEL MAINTAINER="{{ creator }} <{{ creator_email }}>" 3 | ENV PS1="\[\e[0;33m\]|> {{ label }} <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# " 4 | 5 | WORKDIR /src 6 | COPY . /src 7 | RUN pip install --no-cache-dir -r requirements.txt \ 8 | && python setup.py install 9 | WORKDIR / 10 | ENTRYPOINT ["{{ label }}"] 11 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include *.py 2 | include setup.cfg 3 | include README.md CHANGELOG.md LICENSE.md 4 | include *.txt 5 | recursive-include {{ label }}/templates * 6 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean virtualenv test docker dist dist-upload 2 | 3 | clean: 4 | find . -name '*.py[co]' -delete 5 | 6 | virtualenv: 7 | virtualenv --prompt '|> {{ label }} <| ' env 8 | env/bin/pip install -r requirements-dev.txt 9 | env/bin/python setup.py develop 10 | @echo 11 | @echo "VirtualENV Setup Complete. Now run: source env/bin/activate" 12 | @echo 13 | 14 | test: 15 | python -m pytest \ 16 | -v \ 17 | --cov={{ label }} \ 18 | --cov-report=term \ 19 | --cov-report=html:coverage-report \ 20 | tests/ 21 | 22 | docker: clean 23 | docker build -t {{ label }}:latest . 24 | 25 | dist: clean 26 | rm -rf dist/* 27 | python setup.py sdist 28 | python setup.py bdist_wheel 29 | 30 | dist-upload: 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/README.md: -------------------------------------------------------------------------------- 1 | # {{ description }} 2 | 3 | ## Installation 4 | 5 | ``` 6 | $ pip install -r requirements.txt 7 | 8 | $ python setup.py install 9 | ``` 10 | 11 | ## Development 12 | 13 | This project includes a number of helpers in the `Makefile` to streamline common development tasks. 14 | 15 | ### Environment Setup 16 | 17 | The following demonstrates setting up and working with a development environment: 18 | 19 | ``` 20 | ### create a virtualenv for development 21 | 22 | $ make virtualenv 23 | 24 | $ source env/bin/activate 25 | 26 | 27 | ### run {{ label }} cli application 28 | 29 | $ {{ label }} --help 30 | 31 | 32 | ### run pytest / coverage 33 | 34 | $ make test 35 | ``` 36 | 37 | 38 | ### Releasing to PyPi 39 | 40 | Before releasing to PyPi, you must configure your login credentials: 41 | 42 | **~/.pypirc**: 43 | 44 | ``` 45 | [pypi] 46 | username = YOUR_USERNAME 47 | password = YOUR_PASSWORD 48 | ``` 49 | 50 | Then use the included helper function via the `Makefile`: 51 | 52 | ``` 53 | $ make dist 54 | 55 | $ make dist-upload 56 | ``` 57 | 58 | ## Deployments 59 | 60 | ### Docker 61 | 62 | Included is a basic `Dockerfile` for building and distributing `{{ name }}`, 63 | and can be built with the included `make` helper: 64 | 65 | ``` 66 | $ make docker 67 | 68 | $ docker run -it {{ label }} --help 69 | ``` 70 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/config/{{ label }}.yml.example: -------------------------------------------------------------------------------- 1 | ### {{ name }} Configuration Settings 2 | --- 3 | 4 | {{ label }}: 5 | 6 | ### Toggle application level debug (does not toggle framework debugging) 7 | # debug: false 8 | 9 | ### Where external (third-party) plugins are loaded from 10 | # plugin_dir: /var/lib/{{ label }}/plugins/ 11 | 12 | ### Where all plugin configurations are loaded from 13 | # plugin_config_dir: /etc/{{ label }}/plugins.d/ 14 | 15 | ### Where external templates are loaded from 16 | # template_dir: /var/lib/{{ label }}/templates/ 17 | 18 | ### The log handler label 19 | # log_handler: colorlog 20 | 21 | ### The output handler label 22 | # output_handler: jinja2 23 | 24 | ### sample foo option 25 | # foo: bar 26 | 27 | 28 | log.colorlog: 29 | 30 | ### Where the log file lives (no log file by default) 31 | # file: null 32 | 33 | ### The level for which to log. One of: info, warning, error, fatal, debug 34 | # level: info 35 | 36 | ### Whether or not to log to console 37 | # to_console: true 38 | 39 | ### Whether or not to rotate the log file when it reaches `max_bytes` 40 | # rotate: false 41 | 42 | ### Max size in bytes that a log file can grow until it is rotated. 43 | # max_bytes: 512000 44 | 45 | ### The maximum number of log files to maintain when rotating 46 | # max_files: 4 47 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/docs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/project/docs/.gitkeep -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | pytest 4 | pytest-cov 5 | coverage 6 | twine>=1.11.0 7 | setuptools>=38.6.0 8 | wheel>=0.31.0 9 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/requirements.txt: -------------------------------------------------------------------------------- 1 | cement=={{ cement.version }} 2 | cement[jinja2] 3 | cement[yaml] 4 | cement[colorlog] 5 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/setup.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/project/setup.cfg -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/setup.py: -------------------------------------------------------------------------------- 1 | 2 | from setuptools import setup, find_packages 3 | from {{ label }}.core.version import get_version 4 | 5 | VERSION = get_version() 6 | 7 | f = open('README.md', 'r') 8 | LONG_DESCRIPTION = f.read() 9 | f.close() 10 | 11 | setup( 12 | name='{{ label }}', 13 | version=VERSION, 14 | description='{{ description }}', 15 | long_description=LONG_DESCRIPTION, 16 | long_description_content_type='text/markdown', 17 | author='{{ creator }}', 18 | author_email='{{ creator_email }}', 19 | url='{{ url }}', 20 | license='{{ license }}', 21 | packages=find_packages(exclude=['ez_setup', 'tests*']), 22 | package_data={'{{ label }}': ['templates/*']}, 23 | include_package_data=True, 24 | entry_points=""" 25 | [console_scripts] 26 | {{ label }} = {{ label }}.main:main 27 | """, 28 | ) 29 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/tests/conftest.py: -------------------------------------------------------------------------------- 1 | """ 2 | PyTest Fixtures. 3 | """ 4 | 5 | import pytest 6 | from cement import fs 7 | 8 | @pytest.fixture(scope="function") 9 | def tmp(request): 10 | """ 11 | Create a `tmp` object that geneates a unique temporary directory, and file 12 | for each test function that requires it. 13 | """ 14 | t = fs.Tmp() 15 | yield t 16 | t.remove() 17 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/tests/test_{{ label }}.py: -------------------------------------------------------------------------------- 1 | 2 | from pytest import raises 3 | from {{ label }}.main import {{ class_name }}Test 4 | 5 | def test_{{ label }}(): 6 | # test {{ label }} without any subcommands or arguments 7 | with {{ class_name }}Test() as app: 8 | app.run() 9 | assert app.exit_code == 0 10 | 11 | 12 | def test_{{ label }}_debug(): 13 | # test that debug mode is functional 14 | argv = ['--debug'] 15 | with {{ class_name }}Test(argv=argv) as app: 16 | app.run() 17 | assert app.debug is True 18 | 19 | 20 | def test_command1(): 21 | # test command1 without arguments 22 | argv = ['command1'] 23 | with {{ class_name }}Test(argv=argv) as app: 24 | app.run() 25 | data,output = app.last_rendered 26 | assert data['foo'] == 'bar' 27 | assert output.find('Foo => bar') 28 | 29 | 30 | # test command1 with arguments 31 | argv = ['command1', '--foo', 'not-bar'] 32 | with {{ class_name }}Test(argv=argv) as app: 33 | app.run() 34 | data,output = app.last_rendered 35 | assert data['foo'] == 'not-bar' 36 | assert output.find('Foo => not-bar') 37 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/{{ label }}/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/project/{{ label }}/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/{{ label }}/controllers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/project/{{ label }}/controllers/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/{{ label }}/controllers/base.py: -------------------------------------------------------------------------------- 1 | 2 | from cement import Controller, ex 3 | from cement.utils.version import get_version_banner 4 | from ..core.version import get_version 5 | 6 | VERSION_BANNER = """ 7 | {{ description }} %s 8 | %s 9 | """ % (get_version(), get_version_banner()) 10 | 11 | 12 | class Base(Controller): 13 | class Meta: 14 | label = 'base' 15 | 16 | # text displayed at the top of --help output 17 | description = '{{ description }}' 18 | 19 | # text displayed at the bottom of --help output 20 | epilog = 'Usage: {{ label }} command1 --foo bar' 21 | 22 | # controller level arguments. ex: '{{ label }} --version' 23 | arguments = [ 24 | ### add a version banner 25 | ( [ '-v', '--version' ], 26 | { 'action' : 'version', 27 | 'version' : VERSION_BANNER } ), 28 | ] 29 | 30 | 31 | def _default(self): 32 | """Default action if no sub-command is passed.""" 33 | 34 | self.app.args.print_help() 35 | 36 | 37 | @ex( 38 | help='example sub command1', 39 | 40 | # sub-command level arguments. ex: '{{ label }} command1 --foo bar' 41 | arguments=[ 42 | ### add a sample foo option under subcommand namespace 43 | ( [ '-f', '--foo' ], 44 | { 'help' : 'notorious foo option', 45 | 'action' : 'store', 46 | 'dest' : 'foo' } ), 47 | ], 48 | ) 49 | def command1(self): 50 | """Example sub-command.""" 51 | 52 | data = { 53 | 'foo' : 'bar', 54 | } 55 | 56 | ### do something with arguments 57 | if self.app.pargs.foo is not None: 58 | data['foo'] = self.app.pargs.foo 59 | 60 | self.app.render(data, 'command1.jinja2') 61 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/{{ label }}/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/project/{{ label }}/core/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/{{ label }}/core/exc.py: -------------------------------------------------------------------------------- 1 | 2 | class {{ class_name }}Error(Exception): 3 | """Generic errors.""" 4 | pass 5 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/{{ label }}/core/version.py: -------------------------------------------------------------------------------- 1 | 2 | from cement.utils.version import get_version as cement_get_version 3 | 4 | VERSION = (0, 0, 1, 'alpha', 0) 5 | 6 | def get_version(version=VERSION): 7 | return cement_get_version(version) 8 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/{{ label }}/ext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/project/{{ label }}/ext/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/{{ label }}/main.py: -------------------------------------------------------------------------------- 1 | 2 | from cement import App, TestApp, init_defaults 3 | from cement.core.exc import CaughtSignal 4 | from .core.exc import {{ class_name }}Error 5 | from .controllers.base import Base 6 | 7 | # configuration defaults 8 | CONFIG = init_defaults('{{ label }}') 9 | CONFIG['{{ label }}']['foo'] = 'bar' 10 | 11 | 12 | class {{ class_name }}(App): 13 | """{{ name }} primary application.""" 14 | 15 | class Meta: 16 | label = '{{ label }}' 17 | 18 | # configuration defaults 19 | config_defaults = CONFIG 20 | 21 | # call sys.exit() on close 22 | exit_on_close = True 23 | 24 | # load additional framework extensions 25 | extensions = [ 26 | 'yaml', 27 | 'colorlog', 28 | 'jinja2', 29 | ] 30 | 31 | # configuration handler 32 | config_handler = 'yaml' 33 | 34 | # configuration file suffix 35 | config_file_suffix = '.yml' 36 | 37 | # set the log handler 38 | log_handler = 'colorlog' 39 | 40 | # set the output handler 41 | output_handler = 'jinja2' 42 | 43 | # register handlers 44 | handlers = [ 45 | Base 46 | ] 47 | 48 | 49 | class {{ class_name }}Test(TestApp,{{ class_name }}): 50 | """A sub-class of {{ class_name }} that is better suited for testing.""" 51 | 52 | class Meta: 53 | label = '{{ label }}' 54 | 55 | 56 | def main(): 57 | with {{ class_name }}() as app: 58 | try: 59 | app.run() 60 | 61 | except AssertionError as e: 62 | print('AssertionError > %s' % e.args[0]) 63 | app.exit_code = 1 64 | 65 | if app.debug is True: 66 | import traceback 67 | traceback.print_exc() 68 | 69 | except {{ class_name }}Error as e: 70 | print('{{ class_name }}Error > %s' % e.args[0]) 71 | app.exit_code = 1 72 | 73 | if app.debug is True: 74 | import traceback 75 | traceback.print_exc() 76 | 77 | except CaughtSignal as e: 78 | # Default Cement signals are SIGINT and SIGTERM, exit 0 (non-error) 79 | print('\n%s' % e) 80 | app.exit_code = 0 81 | 82 | 83 | if __name__ == '__main__': 84 | main() 85 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/{{ label }}/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/project/{{ label }}/plugins/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/{{ label }}/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/project/{{ label }}/templates/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/project/{{ label }}/templates/command1.jinja2: -------------------------------------------------------------------------------- 1 | 2 | Example Template (templates/command1.jinja2) 3 | 4 | Foo => {{ foo }} 5 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/script/.generate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | exclude: 4 | - '^(.*)[\/\\\\]script[\/\\\\]{{ label }}[\/\\\\]templates[\/\\\\](.*)$' 5 | 6 | ignore: 7 | - '^(.*)pyc(.*)$' 8 | - '^(.*)pyo(.*)$' 9 | - '^(.*)__pycache__(.*)$' 10 | 11 | 12 | variables: 13 | - name: label 14 | prompt: "Script Name" 15 | default: "myscript" 16 | validate: "^[a-zA-Z0-9-_]+$" 17 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/script/{{ label }}.py: -------------------------------------------------------------------------------- 1 | 2 | from cement import App, CaughtSignal, Controller, ex, get_version 3 | 4 | VERSION = (0, 0, 1, 'alpha', 0) 5 | 6 | VERSION_BANNER = """ 7 | {{ label }} v%s 8 | """ % get_version(VERSION) 9 | 10 | 11 | class Base(Controller): 12 | class Meta: 13 | label = 'base' 14 | 15 | arguments = [ 16 | ### add a version banner 17 | ( [ '-v', '--version' ], 18 | { 'action' : 'version', 19 | 'version' : VERSION_BANNER } ), 20 | ] 21 | 22 | 23 | def _default(self): 24 | """Default action if no sub-command is passed.""" 25 | 26 | self.app.args.print_help() 27 | 28 | 29 | @ex( 30 | help='example sub command1', 31 | arguments=[ 32 | ### add a sample foo option under subcommand namespace 33 | ( [ '-f', '--foo' ], 34 | { 'help' : 'notorious foo option', 35 | 'action' : 'store', 36 | 'dest' : 'foo' } ), 37 | ], 38 | ) 39 | def command1(self): 40 | """Example sub-command.""" 41 | 42 | print('Inside Base.command1') 43 | 44 | ### do something with arguments 45 | if self.app.pargs.foo is not None: 46 | print(f'Foo Argument > {self.app.pargs.foo}') 47 | 48 | 49 | class MyApp(App): 50 | 51 | class Meta: 52 | # application label 53 | label = '{{ label }}' 54 | 55 | # register handlers 56 | handlers = [ 57 | Base 58 | ] 59 | 60 | # call sys.exit() on close 61 | close_on_exit = True 62 | 63 | 64 | def main(): 65 | with MyApp() as app: 66 | try: 67 | app.run() 68 | except CaughtSignal as e: 69 | # Default Cement signals are SIGINT and SIGTERM, exit 0 (non-error) 70 | print(f'\n{e}') 71 | app.exit_code = 0 72 | 73 | 74 | if __name__ == '__main__': 75 | main() 76 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/.generate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | exclude: 4 | - '(.*)' 5 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | coverage-report/ 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # My TODO Application Change History 2 | 3 | ## 0.0.1 4 | 5 | Initial release. 6 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-alpine 2 | LABEL MAINTAINER="Your Name " 3 | ENV PS1="\[\e[0;33m\]|> todo <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# " 4 | 5 | WORKDIR /src 6 | COPY . /src 7 | RUN apk update && \ 8 | apk add git && \ 9 | pip install --no-cache-dir -r requirements.txt \ 10 | && rm -f /usr/local/lib/python3.6/site-packages/cement.egg-link \ 11 | && cd src/cement \ 12 | && python setup.py install \ 13 | && cd /src \ 14 | && rm -rf src/cement \ 15 | && python setup.py install 16 | RUN rm -rf /src 17 | WORKDIR / 18 | ENTRYPOINT ["todo"] 19 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include *.py 2 | include setup.cfg 3 | include README.md CHANGELOG.md LICENSE.md 4 | include *.txt 5 | recursive-include todo/templates * 6 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean virtualenv test docker dist dist-upload 2 | 3 | clean: 4 | find . -name '*.py[co]' -delete 5 | 6 | virtualenv: 7 | virtualenv --prompt '|> todo <| ' env 8 | env/bin/pip install -r requirements-dev.txt 9 | env/bin/python setup.py develop 10 | @echo 11 | @echo "VirtualENV Setup Complete. Now run: source env/bin/activate" 12 | @echo 13 | 14 | test: 15 | python -m pytest \ 16 | -v \ 17 | --cov=todo \ 18 | --cov-report=term \ 19 | --cov-report=html:coverage-report \ 20 | tests/ 21 | 22 | docker: clean 23 | docker build -t todo:latest . 24 | 25 | dist: clean 26 | rm -rf dist/* 27 | python setup.py sdist 28 | python setup.py bdist_wheel 29 | 30 | dist-upload: 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/README.md: -------------------------------------------------------------------------------- 1 | # A Simple TODO Application 2 | 3 | ## Installation 4 | 5 | ``` 6 | $ pip install -r requirements.txt 7 | 8 | $ python setup.py install 9 | ``` 10 | 11 | ## Development 12 | 13 | This project includes a number of helpers in the `Makefile` to streamline common development tasks. 14 | 15 | ### Environment Setup 16 | 17 | The following demonstrates setting up and working with a development environment: 18 | 19 | ``` 20 | ### create a virtualenv for development 21 | 22 | $ make virtualenv 23 | 24 | $ source env/bin/activate 25 | 26 | 27 | ### run todo cli application 28 | 29 | $ todo --help 30 | 31 | 32 | ### run pytest / coverage 33 | 34 | $ make test 35 | ``` 36 | 37 | 38 | ### Releasing to PyPi 39 | 40 | Before releasing to PyPi, you must configure your login credentials: 41 | 42 | **~/.pypirc**: 43 | 44 | ``` 45 | [pypi] 46 | username = YOUR_USERNAME 47 | password = YOUR_PASSWORD 48 | ``` 49 | 50 | Then use the included helper function via the `Makefile`: 51 | 52 | ``` 53 | $ make dist 54 | 55 | $ make dist-upload 56 | ``` 57 | 58 | ## Deployments 59 | 60 | ### Docker 61 | 62 | Included is a basic `Dockerfile` for building and distributing `My TODO Application`, 63 | and can be built with the included `make` helper: 64 | 65 | ``` 66 | $ make docker 67 | 68 | $ docker run -it todo --help 69 | ``` 70 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/config/todo.yml.example: -------------------------------------------------------------------------------- 1 | ### My TODO Application Configuration Settings 2 | --- 3 | 4 | todo: 5 | 6 | ### Database file path 7 | # db_file: ~/.todo/db.json 8 | 9 | ### Toggle application level debug (does not toggle framework debugging) 10 | # debug: false 11 | 12 | ### Where external (third-party) plugins are loaded from 13 | # plugin_dir: /var/lib/todo/plugins/ 14 | 15 | ### Where all plugin configurations are loaded from 16 | # plugin_config_dir: /etc/todo/plugins.d/ 17 | 18 | ### Where external templates are loaded from 19 | # template_dir: /var/lib/todo/templates/ 20 | 21 | ### The log handler label 22 | # log_handler: colorlog 23 | 24 | ### The output handler label 25 | # output_handler: jinja2 26 | 27 | ### sample foo option 28 | # foo: bar 29 | 30 | 31 | log.colorlog: 32 | 33 | ### Where the log file lives (no log file by default) 34 | # file: null 35 | 36 | ### The level for which to log. One of: info, warning, error, fatal, debug 37 | # level: info 38 | 39 | ### Whether or not to log to console 40 | # to_console: true 41 | 42 | ### Whether or not to rotate the log file when it reaches `max_bytes` 43 | # rotate: false 44 | 45 | ### Max size in bytes that a log file can grow until it is rotated. 46 | # max_bytes: 512000 47 | 48 | ### The maximum number of log files to maintain when rotating 49 | # max_files: 4 50 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/docs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/todo-tutorial/docs/.gitkeep -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | pytest 4 | pytest-cov 5 | coverage 6 | twine>=1.11.0 7 | setuptools>=38.6.0 8 | wheel>=0.31.0 9 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/requirements.txt: -------------------------------------------------------------------------------- 1 | cement=={{ cement.version }} 2 | cement[jinja2] 3 | cement[yaml] 4 | cement[colorlog] 5 | tinydb 6 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/setup.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/todo-tutorial/setup.cfg -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/setup.py: -------------------------------------------------------------------------------- 1 | 2 | from setuptools import setup, find_packages 3 | from todo.core.version import get_version 4 | 5 | VERSION = get_version() 6 | 7 | f = open('README.md', 'r') 8 | LONG_DESCRIPTION = f.read() 9 | f.close() 10 | 11 | setup( 12 | name='todo', 13 | version=VERSION, 14 | description='A Simple TODO Application', 15 | long_description=LONG_DESCRIPTION, 16 | long_description_content_type='text/markdown', 17 | author='Your Name', 18 | author_email='you@yourdomain.com', 19 | url='https://github.com/yourname/todo', 20 | license='unlicensed', 21 | packages=find_packages(exclude=['ez_setup', 'tests*']), 22 | package_data={'todo': ['templates/*']}, 23 | include_package_data=True, 24 | entry_points=""" 25 | [console_scripts] 26 | todo = todo.main:main 27 | """, 28 | ) 29 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/tests/conftest.py: -------------------------------------------------------------------------------- 1 | """ 2 | PyTest Fixtures. 3 | """ 4 | 5 | import os 6 | import shutil 7 | import pytest 8 | from tempfile import mkstemp, mkdtemp 9 | 10 | 11 | @pytest.fixture(scope="function") 12 | def tmp(request): 13 | """ 14 | Create a `tmp` object that geneates a unique temporary directory, and file 15 | for each test function that requires it. 16 | """ 17 | 18 | class Tmp(object): 19 | cleanup = True 20 | 21 | def __init__(self): 22 | self.dir = mkdtemp() 23 | _, self.file = mkstemp(dir=self.dir) 24 | 25 | t = Tmp() 26 | yield t 27 | 28 | # cleanup 29 | if os.path.exists(t.dir) and cleanup is True: 30 | shutil.rmtree(t.dir) 31 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/tests/test_main.py: -------------------------------------------------------------------------------- 1 | 2 | from todo.main import TodoTest 3 | 4 | def test_todo(tmp): 5 | with TodoTest() as app: 6 | res = app.run() 7 | print(res) 8 | raise Exception 9 | 10 | def test_command1(tmp): 11 | argv = ['command1'] 12 | with TodoTest(argv=argv) as app: 13 | app.run() 14 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/todo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/todo-tutorial/todo/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/todo/controllers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/todo-tutorial/todo/controllers/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/todo/controllers/base.py: -------------------------------------------------------------------------------- 1 | 2 | from cement import Controller, ex 3 | from cement.utils.version import get_version_banner 4 | from ..core.version import get_version 5 | 6 | VERSION_BANNER = f""" 7 | A Simple TODO Application {get_version()} 8 | {get_version_banner()} 9 | """ 10 | 11 | 12 | class Base(Controller): 13 | class Meta: 14 | label = 'base' 15 | 16 | # text displayed at the top of --help output 17 | description = 'A Simple TODO Application' 18 | 19 | # text displayed at the bottom of --help output 20 | epilog = 'Usage: todo command1 --foo bar' 21 | 22 | # controller level arguments. ex: 'todo --version' 23 | arguments = [ 24 | ### add a version banner 25 | ( [ '-v', '--version' ], 26 | { 'action' : 'version', 27 | 'version' : VERSION_BANNER } ), 28 | ] 29 | 30 | 31 | def _default(self): 32 | """Default action if no sub-command is passed.""" 33 | 34 | self.app.args.print_help() 35 | 36 | 37 | @ex( 38 | help='example sub command1', 39 | 40 | # sub-command level arguments. ex: 'todo command1 --foo bar' 41 | arguments=[ 42 | ### add a sample foo option under subcommand namespace 43 | ( [ '-f', '--foo' ], 44 | { 'help' : 'notorious foo option', 45 | 'action' : 'store', 46 | 'dest' : 'foo' } ), 47 | ], 48 | ) 49 | def command1(self): 50 | """Example sub-command.""" 51 | 52 | data = { 53 | 'foo' : 'bar', 54 | } 55 | 56 | ### do something with arguments 57 | if self.app.pargs.foo is not None: 58 | data['foo'] = self.app.pargs.foo 59 | 60 | self.app.render(data, 'command1.jinja2') 61 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/todo/controllers/items.py: -------------------------------------------------------------------------------- 1 | 2 | from time import strftime 3 | from cement import Controller, ex 4 | 5 | 6 | class Items(Controller): 7 | class Meta: 8 | label = 'items' 9 | stacked_type = 'embedded' 10 | stacked_on = 'base' 11 | 12 | @ex(help='list items') 13 | def list(self): 14 | data = {} 15 | data['items'] = self.app.db.all() 16 | self.app.render(data, 'items/list.jinja2') 17 | 18 | @ex( 19 | help='create an item', 20 | arguments=[ 21 | ( ['item_text'], 22 | {'help': 'todo item text', 23 | 'action': 'store' } ) 24 | ], 25 | ) 26 | def create(self): 27 | text = self.app.pargs.item_text 28 | now = strftime("%Y-%m-%d %H:%M:%S") 29 | self.app.log.info(f'creating todo item: {text}') 30 | 31 | item = { 32 | 'timestamp': now, 33 | 'state': 'pending', 34 | 'text': text, 35 | } 36 | 37 | self.app.db.insert(item) 38 | 39 | @ex( 40 | help='update an existing item', 41 | arguments=[ 42 | ( ['item_id'], 43 | {'help': 'todo item database id', 44 | 'action': 'store' } ), 45 | ( ['--text'], 46 | {'help': 'todo item text', 47 | 'action': 'store' , 48 | 'dest': 'item_text' } ), 49 | ], 50 | ) 51 | def update(self): 52 | id = int(self.app.pargs.item_id) 53 | text = self.app.pargs.item_text 54 | now = strftime("%Y-%m-%d %H:%M:%S") 55 | self.app.log.info(f'updating todo item: {id} - {text}') 56 | 57 | item = { 58 | 'timestamp': now, 59 | 'text': text, 60 | } 61 | 62 | self.app.db.update(item, doc_ids=[id]) 63 | 64 | @ex( 65 | help='complete an item', 66 | arguments=[ 67 | ( ['item_id'], 68 | {'help': 'todo item database id', 69 | 'action': 'store' } ), 70 | ], 71 | ) 72 | def complete(self): 73 | id = int(self.app.pargs.item_id) 74 | now = strftime("%Y-%m-%d %H:%M:%S") 75 | item = self.app.db.get(doc_id=id) 76 | item['timestamp'] = now 77 | item['state'] = 'complete' 78 | 79 | self.app.log.info(f"completing item id: {id} - {item['text']}") 80 | self.app.db.update(item, doc_ids=[id]) 81 | 82 | msg = f""" 83 | Congratulations! The following item has been completed: 84 | 85 | {id} - {item['text']} 86 | """ 87 | 88 | self.app.mail.send(msg, 89 | subject='TODO Item Complete', 90 | to=[self.app.config.get('todo', 'email')], 91 | from_addr='noreply@localhost', 92 | ) 93 | 94 | @ex( 95 | help='delete an item', 96 | arguments=[ 97 | ( ['item_id'], 98 | {'help': 'todo item database id', 99 | 'action': 'store' } ), 100 | ], 101 | ) 102 | def delete(self): 103 | id = int(self.app.pargs.item_id) 104 | self.app.log.info(f'deleting todo item id: {id}') 105 | self.app.db.remove(doc_ids=[id]) 106 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/todo/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/todo-tutorial/todo/core/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/todo/core/exc.py: -------------------------------------------------------------------------------- 1 | 2 | class TodoError(Exception): 3 | """Generic errors.""" 4 | 5 | def __init__(self, msg): 6 | Exception.__init__(self) 7 | self.msg = msg 8 | 9 | def __str__(self): 10 | return self.msg 11 | 12 | def __repr__(self): 13 | return f"" 14 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/todo/core/version.py: -------------------------------------------------------------------------------- 1 | 2 | from cement.utils.version import get_version as cement_get_version 3 | 4 | VERSION = (0, 0, 1, 'alpha', 0) 5 | 6 | def get_version(version=VERSION): 7 | return cement_get_version(version) 8 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/todo/ext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/todo-tutorial/todo/ext/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/todo/main.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from tinydb import TinyDB 4 | from cement import App, TestApp, init_defaults 5 | from cement.core.exc import CaughtSignal 6 | from cement.utils import fs 7 | from .core.exc import TodoError 8 | from .controllers.base import Base 9 | from .controllers.items import Items 10 | 11 | 12 | # configuration defaults 13 | CONFIG = init_defaults('todo') 14 | CONFIG['todo']['foo'] = 'bar' 15 | CONFIG['todo']['db_file'] = '~/.todo/db.json' 16 | CONFIG['todo']['email'] = 'you@yourdomain.com' 17 | 18 | 19 | def extend_tinydb(app): 20 | app.log.info('extending todo application with tinydb') 21 | db_file = app.config.get('todo', 'db_file') 22 | 23 | # ensure that we expand the full path 24 | db_file = fs.abspath(db_file) 25 | app.log.info(f'tinydb database file is: {db_file}') 26 | 27 | # ensure our parent directory exists 28 | db_dir = os.path.dirname(db_file) 29 | if not os.path.exists(db_dir): 30 | os.makedirs(db_dir) 31 | 32 | app.extend('db', TinyDB(db_file)) 33 | 34 | 35 | class Todo(App): 36 | """My TODO Application primary application.""" 37 | 38 | class Meta: 39 | label = 'todo' 40 | 41 | # configuration defaults 42 | config_defaults = CONFIG 43 | 44 | # call sys.exit() on close 45 | close_on_exit = True 46 | 47 | # load additional framework extensions 48 | extensions = [ 49 | 'yaml', 50 | 'colorlog', 51 | 'jinja2', 52 | ] 53 | 54 | # configuration handler 55 | config_handler = 'yaml' 56 | 57 | # configuration file suffix 58 | config_file_suffix = '.yml' 59 | 60 | # set the log handler 61 | log_handler = 'colorlog' 62 | 63 | # set the output handler 64 | output_handler = 'jinja2' 65 | 66 | # register handlers 67 | handlers = [ 68 | Base, 69 | Items, 70 | ] 71 | 72 | # register hooks 73 | hooks = [ 74 | ('post_setup', extend_tinydb), 75 | ] 76 | 77 | 78 | class TodoTest(TestApp,Todo): 79 | """A sub-class of Todo that is better suited for testing.""" 80 | 81 | class Meta: 82 | label = 'todo' 83 | 84 | 85 | def main(): 86 | with Todo() as app: 87 | try: 88 | app.run() 89 | 90 | except AssertionError as e: 91 | print(f'AssertionError > {e.args[0]}') 92 | app.exit_code = 1 93 | 94 | if app.debug is True: 95 | import traceback 96 | traceback.print_exc() 97 | 98 | except TodoError: 99 | print(f'TodoError > {e.args[0]}') 100 | app.exit_code = 1 101 | 102 | if app.debug is True: 103 | import traceback 104 | traceback.print_exc() 105 | 106 | except CaughtSignal as e: 107 | # Default Cement signals are SIGINT and SIGTERM, exit 0 (non-error) 108 | print(f'\n{e}') 109 | app.exit_code = 0 110 | 111 | 112 | if __name__ == '__main__': 113 | main() 114 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/todo/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/todo-tutorial/todo/plugins/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/todo/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/cli/templates/generate/todo-tutorial/todo/templates/__init__.py -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/todo/templates/command1.jinja2: -------------------------------------------------------------------------------- 1 | 2 | Example Template (templates/command1.jinja2) 3 | 4 | Foo => {{ foo }} 5 | -------------------------------------------------------------------------------- /cement/cli/templates/generate/todo-tutorial/todo/templates/items/list.jinja2: -------------------------------------------------------------------------------- 1 | {% for item in items %} 2 | {{ item.doc_id }} [{% if item.state == 'complete' %}X{% else %} {% endif %}] {{ item.text }} 3 | {% endfor %} 4 | -------------------------------------------------------------------------------- /cement/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/core/__init__.py -------------------------------------------------------------------------------- /cement/core/arg.py: -------------------------------------------------------------------------------- 1 | """ 2 | Cement core argument module. 3 | 4 | """ 5 | 6 | from abc import abstractmethod 7 | from typing import Any, List 8 | from ..core.interface import Interface 9 | from ..core.handler import Handler 10 | from ..utils.misc import minimal_logger 11 | 12 | LOG = minimal_logger(__name__) 13 | 14 | 15 | class ArgumentInterface(Interface): 16 | 17 | """ 18 | This class defines the Argument Interface. Handlers that implement this 19 | interface must provide the methods and attributes defined below. In 20 | general, most implementations should sub-class from the provided 21 | :class:`ArgumentHandler` base class as a starting point. 22 | """ 23 | 24 | class Meta(Interface.Meta): 25 | 26 | """Interface meta-data options.""" 27 | 28 | #: The string identifier of the interface. 29 | interface = 'argument' 30 | 31 | @abstractmethod 32 | def add_argument(self, *args: str, **kw: Any) -> None: 33 | """Add arguments to the parser. 34 | 35 | This should be ``-o/--option`` or positional. Note that the interface 36 | defines the following parameters so that at the very least, external 37 | extensions can guarantee that they can properly add command line 38 | arguments when necessary. The implementation itself should, and will 39 | provide and support many more options than those listed here. That 40 | said, the implementation must support the following: 41 | 42 | Args: 43 | args (list): List of option arguments. Generally something like 44 | ``['-h', '--help']``. 45 | 46 | Keyword Args: 47 | dest (str): The destination name (variable). Default: `args[0]` 48 | help (str): The help text for ``--help`` output (for that 49 | argument). 50 | action (str): Must support: ``['store', 'store_true', 51 | 'store_false', 'store_const']`` 52 | choices (list): A list of valid values that can be passed to an 53 | option whose action is ``store``. 54 | const (str): The value stored if ``action == 'store_const'``. 55 | default (str): The default value. 56 | 57 | Returns: 58 | None 59 | 60 | """ 61 | pass # pragma: nocover 62 | 63 | @abstractmethod 64 | def parse(self, *args: List[str]) -> object: 65 | """ 66 | Parse the argument list (i.e. ``sys.argv``). Can return any object as 67 | long as its' members contain those of the added arguments. For 68 | example, if adding a ``-v/--version`` option that stores to the dest of 69 | ``version``, then the member must be callable as ``Object().version``. 70 | 71 | Args: 72 | args (list): A list of command line arguments 73 | 74 | Returns: 75 | object: A callable object whose members reprisent the available 76 | arguments 77 | 78 | """ 79 | pass # pragma: nocover 80 | 81 | 82 | class ArgumentHandler(ArgumentInterface, Handler): 83 | 84 | """Argument handler implementation""" 85 | 86 | class Meta(Handler.Meta): 87 | pass # pragma: nocover 88 | -------------------------------------------------------------------------------- /cement/core/backend.py: -------------------------------------------------------------------------------- 1 | """Cement core backend module.""" 2 | 3 | VERSION = (3, 0, 15, 'final', 0) # pragma: nocover 4 | -------------------------------------------------------------------------------- /cement/core/cache.py: -------------------------------------------------------------------------------- 1 | """Cement core cache module.""" 2 | 3 | from abc import abstractmethod 4 | from typing import Any, Optional 5 | from ..core.interface import Interface 6 | from ..core.handler import Handler 7 | from ..utils.misc import minimal_logger 8 | 9 | LOG = minimal_logger(__name__) 10 | 11 | 12 | class CacheInterface(Interface): 13 | 14 | """ 15 | This class defines the Cache Interface. Handlers that implement this 16 | interface must provide the methods and attributes defined below. In 17 | general, most implementations should sub-class from the provided 18 | :class:`CacheHandler` base class as a starting point. 19 | """ 20 | 21 | class Meta(Interface.Meta): 22 | 23 | """Handler meta-data.""" 24 | 25 | #: The string identifier of the interface. 26 | interface = 'cache' 27 | 28 | @abstractmethod 29 | def get(self, key: str, fallback: Any = None) -> Any: 30 | """ 31 | Get the value for a key in the cache. 32 | 33 | If the key does not exist or the key/value in cache is expired, this 34 | functions must return ``fallback`` (which in turn must default to 35 | ``None``). 36 | 37 | Args: 38 | key (str): The key of the value stored in cache 39 | 40 | Keyword Args: 41 | fallback: Optional value that is returned if the cache is 42 | expired or the key does not exist. 43 | 44 | Returns: 45 | Unknown: Whatever the value is in the cache, or the ``fallback`` 46 | 47 | """ 48 | pass # pragma: nocover 49 | 50 | @abstractmethod 51 | def set(self, key: str, value: Any, time: Optional[int] = None) -> None: 52 | """ 53 | Set the key/value in the cache for a set amount of ``time``. 54 | 55 | Args: 56 | key (str): The key of the value to store in cache 57 | value (unknown): The value of that key to store in cache 58 | 59 | Keyword Args: 60 | time (int): A one-off expire time in seconds (or ``None``. If no 61 | time is given, then a default value is used (determined by the 62 | implementation). 63 | 64 | Returns: None 65 | 66 | """ 67 | pass # pragma: nocover 68 | 69 | @abstractmethod 70 | def delete(self, key: str) -> bool: 71 | """ 72 | Deletes a key/value from the cache. 73 | 74 | Args: 75 | key: The key in the cache to delete 76 | 77 | Returns: 78 | bool: ``True`` if the key is successfully deleted, ``False`` 79 | otherwise 80 | 81 | """ 82 | pass # pragma: nocover 83 | 84 | @abstractmethod 85 | def purge(self) -> None: 86 | """ 87 | Clears all data from the cache. 88 | 89 | """ 90 | pass # pragma: nocover 91 | 92 | 93 | class CacheHandler(CacheInterface, Handler): 94 | 95 | """ 96 | Cache handler implementation. 97 | 98 | """ 99 | class Meta(Handler.Meta): 100 | pass # pragma: nocover 101 | -------------------------------------------------------------------------------- /cement/core/controller.py: -------------------------------------------------------------------------------- 1 | """Cement core controller module.""" 2 | 3 | from __future__ import annotations 4 | from abc import abstractmethod 5 | from typing import Any, Union 6 | from ..core.interface import Interface 7 | from ..core.handler import Handler 8 | from ..utils.misc import minimal_logger 9 | 10 | LOG = minimal_logger(__name__) 11 | 12 | 13 | class ControllerInterface(Interface): 14 | 15 | """ 16 | This class defines the Controller Interface. Handlers that implement this 17 | interface must provide the methods and attributes defined below. In 18 | general, most implementations should sub-class from the provided 19 | :class:`ControllerHandler` base class as a starting point. 20 | """ 21 | 22 | class Meta(Interface.Meta): 23 | 24 | """Interface meta-data.""" 25 | 26 | #: The string identifier of the interface. 27 | interface = 'controller' 28 | 29 | @abstractmethod 30 | def _dispatch(self) -> Union[Any | None]: 31 | """ 32 | Reads the application object's data to dispatch a command from this 33 | controller. For example, reading ``self.app.pargs`` to determine what 34 | command was passed, and then executing that command function. 35 | 36 | Note that Cement does *not* parse arguments when calling 37 | ``_dispatch()`` on a controller, as it expects the controller to 38 | handle parsing arguments (I.e. ``self.app.args.parse()``). 39 | 40 | Returns: 41 | unknown: The result of the executed controller function, or 42 | ``None`` if no controller function is called. 43 | 44 | """ 45 | pass # pragma: nocover 46 | 47 | 48 | class ControllerHandler(ControllerInterface, Handler): 49 | """Controller handler implementation.""" 50 | class Meta(Handler.Meta): 51 | pass # pragma: nocover 52 | -------------------------------------------------------------------------------- /cement/core/deprecations.py: -------------------------------------------------------------------------------- 1 | 2 | from warnings import warn 3 | 4 | DEPRECATIONS = { 5 | '3.0.8-1': "Environment variable CEMENT_FRAMEWORK_LOGGING is deprecated in favor of CEMENT_LOG, and will be removed in Cement v3.2.0", # noqa: E501 6 | '3.0.8-2': "App.Meta.framework_logging will be changed or removed in Cement v3.2.0", # noqa: E501 7 | '3.0.10-1': "The FATAL logging facility is deprecated in favor of CRITICAL, and will be removed in future versions of Cement.", # noqa: E501 8 | } 9 | 10 | 11 | def deprecate(deprecation_id: str) -> None: 12 | deprecation_id = str(deprecation_id) 13 | msg = DEPRECATIONS[deprecation_id] 14 | total_msg = f"{msg}. See: https://docs.builtoncement.com/release-information/deprecations#{deprecation_id}" # noqa: E501 15 | warn(total_msg, DeprecationWarning, stacklevel=2) 16 | -------------------------------------------------------------------------------- /cement/core/exc.py: -------------------------------------------------------------------------------- 1 | """Cement core exceptions module.""" 2 | 3 | from typing import Any 4 | 5 | 6 | class FrameworkError(Exception): 7 | 8 | """ 9 | General framework (non-application) related errors. 10 | 11 | Args: 12 | msg (str): The error message 13 | 14 | """ 15 | 16 | def __init__(self, msg: str) -> None: 17 | Exception.__init__(self) 18 | self.msg = msg 19 | 20 | def __str__(self) -> str: 21 | return self.msg 22 | 23 | 24 | class InterfaceError(FrameworkError): 25 | 26 | """Interface related errors.""" 27 | pass 28 | 29 | 30 | class CaughtSignal(FrameworkError): 31 | 32 | """ 33 | Raised when a defined signal is caught. For more information regarding 34 | signals, reference the 35 | `signal `_ library. 36 | 37 | Args: 38 | signum (int): The signal number 39 | frame: The signal frame object 40 | 41 | """ 42 | 43 | def __init__(self, signum: int, frame: Any) -> None: 44 | msg = f'Caught signal {signum}' 45 | super(CaughtSignal, self).__init__(msg) 46 | self.signum = signum 47 | self.frame = frame 48 | -------------------------------------------------------------------------------- /cement/core/log.py: -------------------------------------------------------------------------------- 1 | """ 2 | Cement core log module. 3 | 4 | """ 5 | 6 | # from ..core import interface 7 | from abc import abstractmethod 8 | from ..core.interface import Interface 9 | from ..core.handler import Handler 10 | 11 | 12 | class LogInterface(Interface): 13 | 14 | """ 15 | This class defines the Log Interface. Handlers that implement this 16 | interface must provide the methods and attributes defined below. In 17 | general, most implementations should sub-class from the provided 18 | :class:`LogHandler` base class as a starting point. 19 | """ 20 | 21 | class Meta(Interface.Meta): 22 | 23 | """Handler meta-data.""" 24 | 25 | #: The string identifier of the interface. 26 | interface = 'log' 27 | 28 | @abstractmethod 29 | def set_level(self, level: str) -> None: 30 | """ 31 | Set the log level. Must except atleast one of: 32 | ``['INFO', 'WARNING', 'ERROR', 'DEBUG', or 'FATAL']``. 33 | 34 | """ 35 | pass # pragma: nocover 36 | 37 | @abstractmethod 38 | def get_level(self) -> str: 39 | """Return a string representation of the log level.""" 40 | pass # pragma: nocover 41 | 42 | @abstractmethod 43 | def info(self, msg: str) -> None: 44 | """ 45 | Log to the ``INFO`` facility. 46 | 47 | Args: 48 | msg (str): The message to log. 49 | 50 | """ 51 | pass # pragma: nocover 52 | 53 | @abstractmethod 54 | def warning(self, msg: str) -> None: 55 | """ 56 | Log to the ``WARNING`` facility. 57 | 58 | Args: 59 | msg (str): The message to log. 60 | 61 | """ 62 | pass # pragma: nocover 63 | 64 | @abstractmethod 65 | def error(self, msg: str) -> None: 66 | """ 67 | Log to the ``ERROR`` facility. 68 | 69 | Args: 70 | msg (str): The message to log. 71 | 72 | """ 73 | pass # pragma: nocover 74 | 75 | @abstractmethod 76 | def fatal(self, msg: str) -> None: 77 | """ 78 | Log to the ``FATAL`` facility. 79 | 80 | Args: 81 | msg (str): The message to log. 82 | 83 | """ 84 | pass # pragma: nocover 85 | 86 | @abstractmethod 87 | def debug(self, msg: str) -> None: 88 | """ 89 | Log to the ``DEBUG`` facility. 90 | 91 | Args: 92 | msg (str): The message to log. 93 | 94 | """ 95 | pass # pragma: nocover 96 | 97 | 98 | class LogHandler(LogInterface, Handler): 99 | 100 | """ 101 | Log handler implementation. 102 | 103 | """ 104 | 105 | class Meta(Handler.Meta): 106 | pass # pragma: nocover 107 | -------------------------------------------------------------------------------- /cement/core/meta.py: -------------------------------------------------------------------------------- 1 | """Cement core meta functionality.""" 2 | 3 | from typing import Any, Dict 4 | 5 | 6 | class Meta(object): 7 | 8 | """ 9 | Container class for meta attributes of a larger class. Keyword arguments 10 | are set as attributes of ``self``. 11 | 12 | """ 13 | 14 | def __init__(self, **kwargs: Any) -> None: 15 | self._merge(kwargs) 16 | 17 | def _merge(self, dict_obj: Dict[str, Any]) -> None: 18 | for key in dict_obj.keys(): 19 | setattr(self, key, dict_obj[key]) 20 | 21 | 22 | class MetaMixin(object): 23 | 24 | """ 25 | Mixin that provides the meta class support to add settings to instances 26 | of objects. Meta keys cannot start with a ``_``. 27 | 28 | """ 29 | 30 | def __init__(self, *args: Any, **kwargs: Any) -> None: 31 | # Get a List of all the Classes we in our MRO, find any attribute named 32 | # Meta on them, and then merge them together in order of MRO 33 | metas = reversed([x.Meta for x in self.__class__.mro() 34 | if hasattr(x, "Meta")]) 35 | final_meta = {} 36 | 37 | # Merge the Meta classes into one dict 38 | for meta in metas: 39 | final_meta.update(dict([x for x in meta.__dict__.items() 40 | if not x[0].startswith("_")])) 41 | 42 | # Update the final Meta with any kwargs passed in 43 | for key in final_meta.keys(): 44 | if key in kwargs: 45 | final_meta[key] = kwargs.pop(key) 46 | 47 | self._meta = Meta(**final_meta) 48 | 49 | # FIX ME: object.__init__() doesn't take params without exception 50 | super(MetaMixin, self).__init__() 51 | -------------------------------------------------------------------------------- /cement/core/output.py: -------------------------------------------------------------------------------- 1 | """Cement core output module.""" 2 | 3 | from abc import abstractmethod 4 | from typing import Any, Dict, Union 5 | from ..core.interface import Interface 6 | from ..core.handler import Handler 7 | from ..utils.misc import minimal_logger 8 | 9 | LOG = minimal_logger(__name__) 10 | 11 | 12 | class OutputInterface(Interface): 13 | 14 | """ 15 | This class defines the Output Interface. Handlers that implement this 16 | interface must provide the methods and attributes defined below. In 17 | general, most implementations should sub-class from the provided 18 | :class:`OutputHandler` base class as a starting point. 19 | """ 20 | 21 | class Meta(Interface.Meta): 22 | 23 | """Handler meta-data.""" 24 | 25 | #: The string identifier of the interface 26 | interface = 'output' 27 | 28 | @abstractmethod 29 | def render(self, data: Dict[str, Any], *args: Any, **kwargs: Any) -> Union[str, None]: 30 | """ 31 | Render the ``data`` dict into output in some fashion. This function 32 | must accept both ``*args`` and ``**kwargs`` to allow an application to 33 | mix output handlers that support different features. 34 | 35 | Args: 36 | data (dict): The dictionary whose data we need to render into 37 | output. 38 | 39 | Returns: 40 | str, None: The rendered output string, or ``None`` if no output is 41 | rendered 42 | 43 | """ 44 | pass # pragma: nocover 45 | 46 | 47 | class OutputHandler(OutputInterface, Handler): 48 | 49 | """Output handler implementation.""" 50 | 51 | class Meta(Handler.Meta): 52 | pass # pragma: nocover 53 | -------------------------------------------------------------------------------- /cement/core/plugin.py: -------------------------------------------------------------------------------- 1 | """Cement core plugins module.""" 2 | 3 | from abc import abstractmethod 4 | from typing import List 5 | from ..core.interface import Interface 6 | from ..core.handler import Handler 7 | from ..utils.misc import minimal_logger 8 | 9 | LOG = minimal_logger(__name__) 10 | 11 | 12 | class PluginInterface(Interface): 13 | 14 | """ 15 | This class defines the Plugin Interface. Handlers that implement this 16 | interface must provide the methods and attributes defined below. In 17 | general, most implementations should sub-class from the provided 18 | :class:`PluginHandler` base class as a starting point. 19 | """ 20 | 21 | class Meta(Interface.Meta): 22 | 23 | #: String identifier of the interface. 24 | interface = 'plugin' 25 | 26 | @abstractmethod 27 | def load_plugin(self, plugin_name: str) -> None: 28 | """ 29 | Load a plugin whose name is ``plugin_name``. 30 | 31 | Args: 32 | plugin_name (str): The name of the plugin to load. 33 | 34 | """ 35 | pass # pragma: nocover 36 | 37 | @abstractmethod 38 | def load_plugins(self, plugins: List[str]) -> None: 39 | """ 40 | Load all plugins from ``plugins``. 41 | 42 | Args: 43 | plugins (list): A list of plugin names to load. 44 | 45 | """ 46 | pass # pragma: nocover 47 | 48 | @abstractmethod 49 | def get_loaded_plugins(self) -> List[str]: 50 | """Returns a list of plugins that have been loaded.""" 51 | pass # pragma: nocover 52 | 53 | @abstractmethod 54 | def get_enabled_plugins(self) -> List[str]: 55 | """Returns a list of plugins that are enabled in the config.""" 56 | pass # pragma: nocover 57 | 58 | @abstractmethod 59 | def get_disabled_plugins(self) -> List[str]: 60 | """Returns a list of plugins that are disabled in the config.""" 61 | pass # pragma: nocover 62 | 63 | 64 | class PluginHandler(PluginInterface, Handler): 65 | 66 | """ 67 | Plugin handler implementation. 68 | 69 | """ 70 | 71 | class Meta(Handler.Meta): 72 | pass # pragma: nocover 73 | -------------------------------------------------------------------------------- /cement/ext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/ext/__init__.py -------------------------------------------------------------------------------- /cement/ext/ext_alarm.py: -------------------------------------------------------------------------------- 1 | """ 2 | Cement alarm extension module. 3 | """ 4 | 5 | from __future__ import annotations 6 | import signal 7 | from typing import Any, TYPE_CHECKING 8 | from ..utils.misc import minimal_logger 9 | 10 | if TYPE_CHECKING: 11 | from ..core.foundation import App # pragma: nocover 12 | 13 | LOG = minimal_logger(__name__) 14 | 15 | 16 | def alarm_handler(app: App, signum: int, frame: Any) -> None: 17 | if signum == signal.SIGALRM: 18 | app.log.error(app.alarm.msg) 19 | 20 | 21 | class AlarmManager(object): 22 | """ 23 | Lets the developer easily set and stop an alarm. If the 24 | alarm exceeds the given time it will raise ``signal.SIGALRM``. 25 | 26 | """ 27 | 28 | def __init__(self, *args: Any, **kw: Any) -> None: 29 | super(AlarmManager, self).__init__(*args, **kw) 30 | self.msg: str = None # type: ignore 31 | 32 | def set(self, time: int, msg: str) -> None: 33 | """ 34 | Set the application alarm to ``time`` seconds. If the time is 35 | exceeded ``signal.SIGALRM`` is raised. 36 | 37 | Args: 38 | time (int): The time in seconds to set the alarm to. 39 | msg (str): The message to display if the alarm is triggered. 40 | """ 41 | 42 | LOG.debug(f'setting application alarm for {time} seconds') 43 | self.msg = msg 44 | signal.alarm(int(time)) 45 | 46 | def stop(self) -> None: 47 | """ 48 | Stop the application alarm. 49 | """ 50 | LOG.debug('stopping application alarm') 51 | signal.alarm(0) 52 | 53 | 54 | def load(app: App) -> None: 55 | app.catch_signal(signal.SIGALRM) 56 | app.extend('alarm', AlarmManager()) 57 | app.hook.register('signal', alarm_handler) 58 | -------------------------------------------------------------------------------- /cement/ext/ext_print.py: -------------------------------------------------------------------------------- 1 | """ 2 | Cement print extension module. 3 | """ 4 | 5 | from __future__ import annotations 6 | from typing import Any, Dict, Union, TYPE_CHECKING 7 | from ..core import output 8 | from ..utils.misc import minimal_logger 9 | 10 | if TYPE_CHECKING: 11 | from ..core.foundation import App # pragma: nocover 12 | 13 | LOG = minimal_logger(__name__) 14 | 15 | 16 | def extend_print(app: App) -> None: 17 | def _print(text: str) -> None: 18 | app.render({'out': text}, handler='print') 19 | app.extend('print', _print) 20 | 21 | 22 | class PrintOutputHandler(output.OutputHandler): 23 | 24 | """ 25 | This class implements the :ref:`Output ` Handler 26 | interface. It takes a dict and only prints out the ``out`` key. It is 27 | primarily used by the ``app.print()`` extended function in order to replace 28 | ``print()`` so that framework features like ``pre_render`` and 29 | ``post_render`` hooks are honored. Please see the developer documentation 30 | on :cement:`Output Handling `. 31 | 32 | """ 33 | class Meta(output.OutputHandler.Meta): 34 | 35 | """Handler meta-data""" 36 | 37 | label = 'print' 38 | """The string identifier of this handler.""" 39 | 40 | #: Whether or not to include ``json`` as an available choice 41 | #: to override the ``output_handler`` via command line options. 42 | overridable = False 43 | 44 | _meta: Meta # type: ignore 45 | 46 | def render(self, data: Dict[str, Any], *args: Any, **kw: Any) -> Union[str, None]: 47 | """ 48 | Take a data dictionary and render only the ``out`` key as text output. 49 | Note that the template option is received here per the interface, 50 | however this handler just ignores it. 51 | 52 | Args: 53 | data (dict): The data dictionary to render. 54 | 55 | Returns: 56 | str: A text string. 57 | 58 | """ 59 | if 'out' in data.keys(): 60 | LOG.debug(f"rendering content as text via {self.__module__}") 61 | return data['out'] + '\n' # type: ignore 62 | else: 63 | LOG.debug("no 'out' key found in data dict. " 64 | "not rendering content via %s" % self.__module__) 65 | return None 66 | 67 | 68 | class PrintDictOutputHandler(output.OutputHandler): 69 | 70 | """ 71 | This class implements the :ref:`Output ` Handler 72 | interface. It is intended primarily for development where printing out a 73 | string reprisentation of the data dictionary would be useful. Please see 74 | the developer documentation on :cement:`Output Handling `. 75 | 76 | """ 77 | class Meta(output.OutputHandler.Meta): 78 | 79 | """Handler meta-data""" 80 | 81 | label = 'print_dict' 82 | """The string identifier of this handler.""" 83 | 84 | #: Whether or not to include ``json`` as an available choice 85 | #: to override the ``output_handler`` via command line options. 86 | overridable = False 87 | 88 | _meta: Meta # type: ignore 89 | 90 | def render(self, data: Dict[str, Any], *args: Any, **kw: Any) -> str: 91 | """ 92 | Take a data dictionary and render it as text output. Note that the 93 | template option is received here per the interface, however this 94 | handler just ignores it. 95 | 96 | Args: 97 | data (dict): The data dictionary to render. 98 | 99 | Returns: 100 | str: A text string. 101 | 102 | """ 103 | LOG.debug(f"rendering content as text via {self.__module__}") 104 | out = '' 105 | for key, val in data.items(): 106 | out = out + f'{key}: {val}\n' 107 | 108 | return out 109 | 110 | 111 | def load(app: App) -> None: 112 | app.handler.register(PrintDictOutputHandler) 113 | app.handler.register(PrintOutputHandler) 114 | app.hook.register('pre_argument_parsing', extend_print) 115 | -------------------------------------------------------------------------------- /cement/ext/ext_scrub.py: -------------------------------------------------------------------------------- 1 | """ 2 | Cement scrub extension module. 3 | """ 4 | 5 | from __future__ import annotations 6 | import re 7 | from typing import TYPE_CHECKING 8 | from .. import Controller 9 | from ..utils.misc import minimal_logger 10 | 11 | if TYPE_CHECKING: 12 | from ..core.foundation import App # pragma: nocover 13 | 14 | LOG = minimal_logger(__name__) 15 | 16 | 17 | def scrub_output(app: App, text: str) -> str: 18 | if app.pargs.scrub: 19 | text = app.scrub(text) 20 | return text 21 | 22 | 23 | def extend_scrub(app: App) -> None: 24 | def scrub(text: str) -> str: 25 | if not hasattr(app._meta, 'scrub') or app._meta.scrub is None: 26 | return text # pragma: nocover 27 | elif isinstance(text, str): 28 | for regex, replace in app._meta.scrub: 29 | text = re.sub(regex, replace, text) 30 | else: 31 | LOG.debug(f'text is not str > {type(text)}') 32 | return text 33 | 34 | app.extend('scrub', scrub) 35 | 36 | 37 | class ScrubController(Controller): 38 | """ 39 | Add embedded options to the base controller to support scrubbing output. 40 | """ 41 | 42 | class Meta(Controller.Meta): 43 | #: Controller label 44 | label = 'scrub' 45 | 46 | #: Parent controller to stack ontop of 47 | stacked_on = 'base' 48 | 49 | #: Stacking method 50 | stacked_type = 'embedded' 51 | 52 | #: Command line argument options 53 | argument_options = ['--scrub'] 54 | 55 | #: Command line argument options help 56 | argument_help = 'obfuscate sensitive data from rendered output' 57 | 58 | _meta: Meta # type: ignore 59 | 60 | def _pre_argument_parsing(self) -> None: 61 | if self._meta.argument_options is not None: 62 | assert isinstance(self._meta.argument_options, list), \ 63 | "ScrubController.Meta.argument_options must be a list" 64 | self.app.args.add_argument(*self._meta.argument_options, 65 | help=self._meta.argument_help, 66 | action='store_true', 67 | dest='scrub') 68 | 69 | 70 | def load(app: App) -> None: 71 | app.handler.register(ScrubController) 72 | app.hook.register('post_render', scrub_output) 73 | app.hook.register('pre_argument_parsing', extend_scrub) 74 | -------------------------------------------------------------------------------- /cement/ext/ext_tabulate.py: -------------------------------------------------------------------------------- 1 | """ 2 | Cement Tabulate extension module. 3 | 4 | **Note** This extension has an external dependency on ``tabulate``. Cement 5 | explicitly does **not** include external dependencies for optional 6 | extensions. 7 | 8 | * In Cement ``>=3.0.8`` you must include ``cement[tabulate]`` in your 9 | applications dependencies. 10 | * In Cement ``<3.0.8`` you must include ``tabulate`` in your applications 11 | dependencies. 12 | """ 13 | 14 | from __future__ import annotations 15 | from tabulate import tabulate # type: ignore 16 | from typing import Any, List, Dict, TYPE_CHECKING 17 | from ..core import output 18 | from ..utils.misc import minimal_logger 19 | 20 | if TYPE_CHECKING: 21 | from ..core.foundation import App # pragma: nocover 22 | 23 | LOG = minimal_logger(__name__) 24 | 25 | 26 | class TabulateOutputHandler(output.OutputHandler): 27 | 28 | """ 29 | This class implements the :ref:`Output ` Handler 30 | interface. It provides tabularized text output using the 31 | `Tabulate `_ module. Please 32 | see the developer documentation on 33 | :cement:`Output Handling `. 34 | """ 35 | 36 | class Meta(output.OutputHandler.Meta): 37 | 38 | """Handler meta-data.""" 39 | 40 | label = 'tabulate' 41 | 42 | #: Whether or not to pad the output with an extra pre/post '\n' 43 | padding = True 44 | 45 | #: Default template format. See the ``tabulate`` documentation for 46 | #: all supported template formats. 47 | format = 'orgtbl' 48 | 49 | #: Default headers to use. 50 | headers: List[str] = [] 51 | 52 | #: Default alignment for string columns. See the ``tabulate`` 53 | #: documentation for all supported ``stralign`` options. 54 | string_alignment = 'left' 55 | 56 | #: Default alignment for numeric columns. See the ``tabulate`` 57 | #: documentation for all supported ``numalign`` options. 58 | numeric_alignment = 'decimal' 59 | 60 | #: String format to use for float values. 61 | float_format = 'g' 62 | 63 | #: Default replacement for missing value. 64 | missing_value = '' 65 | 66 | #: Whether or not to include ``tabulate`` as an available to choice 67 | #: to override the ``output_handler`` via command line options. 68 | overridable = False 69 | 70 | _meta: Meta # type: ignore 71 | 72 | def render(self, data: Dict[str, Any], **kw: Any) -> str: 73 | """ 74 | Take a data dictionary and render it into a table. Additional 75 | keyword arguments are passed directly to ``tabulate.tabulate``. 76 | 77 | Args: 78 | data_dict (dict): The data dictionary to render. 79 | 80 | Returns: 81 | str: The rendered template text 82 | 83 | """ 84 | headers = kw.get('headers', self._meta.headers) 85 | 86 | out = tabulate(data, headers, 87 | tablefmt=kw.get('tablefmt', self._meta.format), 88 | stralign=kw.get( 89 | 'stralign', self._meta.string_alignment), 90 | numalign=kw.get( 91 | 'numalign', self._meta.numeric_alignment), 92 | missingval=kw.get( 93 | 'missingval', self._meta.missing_value), 94 | floatfmt=kw.get('floatfmt', self._meta.float_format), 95 | ) 96 | out = out + '\n' 97 | 98 | if self._meta.padding is True: 99 | out = '\n' + out + '\n' 100 | 101 | return out # type: ignore 102 | 103 | 104 | def load(app: App) -> None: 105 | app.handler.register(TabulateOutputHandler) 106 | -------------------------------------------------------------------------------- /cement/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/py.typed -------------------------------------------------------------------------------- /cement/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/cement/utils/__init__.py -------------------------------------------------------------------------------- /cement/utils/test.py: -------------------------------------------------------------------------------- 1 | """Cement testing utilities.""" 2 | 3 | # flake8: noqa 4 | 5 | import os 6 | import shutil 7 | from pytest import raises, skip 8 | from ..core.foundation import TestApp 9 | from ..core.exc import * 10 | from ..utils.misc import rando 11 | from ..utils import fs, shell 12 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | volumes: 3 | mailpit-data: 4 | 5 | services: 6 | redis: 7 | image: redis:latest 8 | hostname: redis 9 | 10 | memcached: 11 | image: memcached:latest 12 | hostname: memcached 13 | 14 | mailpit: 15 | image: axllent/mailpit 16 | hostname: mailpit 17 | restart: always 18 | volumes: 19 | - mailpit-data:/data 20 | - ./docker/mailpit:/certificates 21 | ports: 22 | - 8025:8025 23 | - 1025:1025 24 | environment: 25 | MP_MAX_MESSAGES: 5000 26 | MP_DATA_FILE: /data/mailpit.db 27 | MP_SMTP_AUTH_ACCEPT_ANY: 1 28 | MP_SMTP_AUTH_ALLOW_INSECURE: 1 29 | MP_SMTP_TLS_CERT: /certificates/dev-cert.pem 30 | MP_SMTP_TLS_KEY: /certificates/dev-key.pem 31 | 32 | cement: &DEFAULTS 33 | image: "cement:dev" 34 | build: 35 | context: . 36 | dockerfile: docker/Dockerfile.dev 37 | hostname: cement 38 | stdin_open: true 39 | tty: true 40 | volumes: 41 | - '.:/src' 42 | working_dir: '/src' 43 | links: 44 | - redis:redis 45 | - memcached:memcached 46 | environment: 47 | REDIS_HOST: redis 48 | MEMCACHED_HOST: memcached 49 | SMTP_HOST: mailpit 50 | SMTP_PORT: 1025 51 | depends_on: 52 | - redis 53 | - memcached 54 | - mailpit 55 | 56 | cement-py38: 57 | <<: *DEFAULTS 58 | image: "cement:dev-py38" 59 | build: 60 | context: . 61 | dockerfile: docker/Dockerfile.dev-py38 62 | 63 | cement-py39: 64 | <<: *DEFAULTS 65 | image: "cement:dev-py39" 66 | build: 67 | context: . 68 | dockerfile: docker/Dockerfile.dev-py39 69 | 70 | cement-py310: 71 | <<: *DEFAULTS 72 | image: "cement:dev-py310" 73 | build: 74 | context: . 75 | dockerfile: docker/Dockerfile.dev-py310 76 | 77 | cement-py311: 78 | <<: *DEFAULTS 79 | image: "cement:dev-py311" 80 | build: 81 | context: . 82 | dockerfile: docker/Dockerfile.dev-py311 83 | 84 | cement-py312: 85 | <<: *DEFAULTS 86 | image: "cement:dev-py312" 87 | build: 88 | context: . 89 | dockerfile: docker/Dockerfile.dev-py312 90 | -------------------------------------------------------------------------------- /docker/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM python:3.13-alpine 2 | LABEL MAINTAINER="BJ Dierkes " 3 | ENV PS1="\[\e[0;33m\]|> cement-py313 <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# " 4 | ENV PATH="${PATH}:/root/.local/bin" 5 | 6 | WORKDIR /src 7 | RUN apk update \ 8 | && apk add libmemcached-dev \ 9 | gcc \ 10 | musl-dev \ 11 | cyrus-sasl-dev \ 12 | zlib-dev \ 13 | make \ 14 | vim \ 15 | bash \ 16 | git \ 17 | libffi \ 18 | libffi-dev \ 19 | openssl-dev \ 20 | jq \ 21 | pipx \ 22 | && ln -sf /usr/bin/vim /usr/bin/vi 23 | RUN pipx install pdm 24 | COPY . /src 25 | COPY docker/vimrc /root/.vimrc 26 | COPY docker/bashrc /root/.bashrc 27 | RUN pdm install 28 | CMD ["/bin/bash"] 29 | -------------------------------------------------------------------------------- /docker/Dockerfile.dev-py310: -------------------------------------------------------------------------------- 1 | FROM python:3.10-alpine 2 | LABEL MAINTAINER="BJ Dierkes " 3 | ENV PS1="\[\e[0;33m\]|> cement-py310 <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# " 4 | ENV PATH="${PATH}:/root/.local/bin" 5 | 6 | WORKDIR /src 7 | RUN apk update \ 8 | && apk add libmemcached-dev \ 9 | gcc \ 10 | musl-dev \ 11 | cyrus-sasl-dev \ 12 | zlib-dev \ 13 | make \ 14 | vim \ 15 | bash \ 16 | git \ 17 | libffi \ 18 | libffi-dev \ 19 | openssl-dev \ 20 | jq \ 21 | pipx \ 22 | && ln -sf /usr/bin/vim /usr/bin/vi 23 | RUN pipx install pdm 24 | COPY . /src 25 | COPY docker/vimrc /root/.vimrc 26 | COPY docker/bashrc /root/.bashrc 27 | RUN pdm install 28 | CMD ["/bin/bash"] 29 | -------------------------------------------------------------------------------- /docker/Dockerfile.dev-py311: -------------------------------------------------------------------------------- 1 | FROM python:3.11-alpine 2 | LABEL MAINTAINER="BJ Dierkes " 3 | ENV PS1="\[\e[0;33m\]|> cement-py311 <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# " 4 | ENV PATH="${PATH}:/root/.local/bin" 5 | 6 | WORKDIR /src 7 | RUN apk update \ 8 | && apk add libmemcached-dev \ 9 | gcc \ 10 | musl-dev \ 11 | cyrus-sasl-dev \ 12 | zlib-dev \ 13 | make \ 14 | vim \ 15 | bash \ 16 | git \ 17 | libffi \ 18 | libffi-dev \ 19 | openssl-dev \ 20 | jq \ 21 | pipx \ 22 | && ln -sf /usr/bin/vim /usr/bin/vi 23 | RUN pipx install pdm 24 | COPY . /src 25 | COPY docker/vimrc /root/.vimrc 26 | COPY docker/bashrc /root/.bashrc 27 | RUN pdm install 28 | CMD ["/bin/bash"] 29 | -------------------------------------------------------------------------------- /docker/Dockerfile.dev-py312: -------------------------------------------------------------------------------- 1 | FROM python:3.12-alpine 2 | LABEL MAINTAINER="BJ Dierkes " 3 | ENV PS1="\[\e[0;33m\]|> cement-py312 <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# " 4 | ENV PATH="${PATH}:/root/.local/bin" 5 | 6 | WORKDIR /src 7 | RUN apk update \ 8 | && apk add libmemcached-dev \ 9 | gcc \ 10 | musl-dev \ 11 | cyrus-sasl-dev \ 12 | zlib-dev \ 13 | make \ 14 | vim \ 15 | bash \ 16 | git \ 17 | libffi \ 18 | libffi-dev \ 19 | openssl-dev \ 20 | jq \ 21 | pipx \ 22 | && ln -sf /usr/bin/vim /usr/bin/vi 23 | RUN pipx install pdm 24 | COPY . /src 25 | COPY docker/vimrc /root/.vimrc 26 | COPY docker/bashrc /root/.bashrc 27 | RUN pdm install 28 | CMD ["/bin/bash"] 29 | -------------------------------------------------------------------------------- /docker/Dockerfile.dev-py313: -------------------------------------------------------------------------------- 1 | FROM python:3.13-rc-alpine 2 | LABEL MAINTAINER="BJ Dierkes " 3 | ENV PS1="\[\e[0;33m\]|> cement-py313 <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# " 4 | ENV PATH="${PATH}:/root/.local/bin" 5 | 6 | WORKDIR /src 7 | RUN apk update \ 8 | && apk add libmemcached-dev \ 9 | gcc \ 10 | musl-dev \ 11 | cyrus-sasl-dev \ 12 | zlib-dev \ 13 | make \ 14 | vim \ 15 | bash \ 16 | git \ 17 | libffi \ 18 | libffi-dev \ 19 | openssl-dev \ 20 | jq \ 21 | pipx \ 22 | && ln -sf /usr/bin/vim /usr/bin/vi 23 | RUN pipx install pdm 24 | COPY . /src 25 | COPY ./docker/vimrc /root/.vimrc 26 | COPY ./docker/bashrc /root/.bashrc 27 | RUN pdm install 28 | CMD ["/bin/bash"] 29 | -------------------------------------------------------------------------------- /docker/Dockerfile.dev-py38: -------------------------------------------------------------------------------- 1 | FROM python:3.8-alpine 2 | LABEL MAINTAINER="BJ Dierkes " 3 | ENV PS1="\[\e[0;33m\]|> cement-py38 <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# " 4 | ENV PATH="${PATH}:/root/.local/bin" 5 | 6 | WORKDIR /src 7 | RUN apk update \ 8 | && apk add libmemcached-dev \ 9 | gcc \ 10 | musl-dev \ 11 | cyrus-sasl-dev \ 12 | zlib-dev \ 13 | make \ 14 | vim \ 15 | bash \ 16 | git \ 17 | libffi \ 18 | libffi-dev \ 19 | openssl-dev \ 20 | jq \ 21 | pipx \ 22 | && ln -sf /usr/bin/vim /usr/bin/vi 23 | RUN pipx install pdm 24 | COPY . /src 25 | COPY docker/vimrc /root/.vimrc 26 | COPY docker/bashrc /root/.bashrc 27 | RUN pdm install 28 | CMD ["/bin/bash"] 29 | -------------------------------------------------------------------------------- /docker/Dockerfile.dev-py39: -------------------------------------------------------------------------------- 1 | FROM python:3.9-alpine 2 | LABEL MAINTAINER="BJ Dierkes " 3 | ENV PS1="\[\e[0;33m\]|> cement-py39 <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# " 4 | ENV PATH="${PATH}:/root/.local/bin" 5 | 6 | WORKDIR /src 7 | RUN apk update \ 8 | && apk add libmemcached-dev \ 9 | gcc \ 10 | musl-dev \ 11 | cyrus-sasl-dev \ 12 | zlib-dev \ 13 | make \ 14 | vim \ 15 | bash \ 16 | git \ 17 | libffi \ 18 | libffi-dev \ 19 | openssl-dev \ 20 | jq \ 21 | pipx \ 22 | && ln -sf /usr/bin/vim /usr/bin/vi 23 | RUN pipx install pdm 24 | COPY . /src 25 | COPY docker/vimrc /root/.vimrc 26 | COPY docker/bashrc /root/.bashrc 27 | RUN pdm install 28 | CMD ["/bin/bash"] 29 | -------------------------------------------------------------------------------- /docker/bashrc: -------------------------------------------------------------------------------- 1 | 2 | export PATH="${PATH}:/root/.local/bin" 3 | alias cement='pdm run cement' 4 | -------------------------------------------------------------------------------- /docker/compose-services-only.yml: -------------------------------------------------------------------------------- 1 | volumes: 2 | mailpit-data: 3 | 4 | services: 5 | redis: 6 | image: redis:latest 7 | hostname: redis 8 | ports: 9 | - 6379:6379 10 | 11 | memcached: 12 | image: memcached:latest 13 | hostname: memcached 14 | ports: 15 | - 11211:11211 16 | 17 | mailpit: 18 | image: axllent/mailpit 19 | hostname: mailpit 20 | restart: always 21 | volumes: 22 | - mailpit-data:/data 23 | - ../docker/mailpit:/certificates 24 | ports: 25 | - 8025:8025 26 | - 1025:1025 27 | environment: 28 | MP_MAX_MESSAGES: 5000 29 | MP_DATA_FILE: /data/mailpit.db 30 | MP_SMTP_AUTH_ACCEPT_ANY: 1 31 | MP_SMTP_AUTH_ALLOW_INSECURE: 1 32 | MP_SMTP_TLS_CERT: /certificates/dev-cert.pem 33 | MP_SMTP_TLS_KEY: /certificates/dev-key.pem 34 | -------------------------------------------------------------------------------- /docker/mailpit/dev-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGDzCCA/egAwIBAgIULYOgE6JcXKzs9dpXdh0uDfYfdzUwDQYJKoZIhvcNAQEL 3 | BQAwgZYxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEUMBIGA1UEBwwLU2Fu 4 | IEFudG9uaW8xHDAaBgNVBAoME0RhdGEgRm9sayBMYWJzLCBMTEMxDzANBgNVBAsM 5 | BkNlbWVudDEQMA4GA1UEAwwHbWFpbHBpdDEgMB4GCSqGSIb3DQEJARYRbWFpbHBp 6 | dEBsb2NhbGhvc3QwHhcNMjQwMjI3MDIwMDQ0WhcNMzQwMjI0MDIwMDQ0WjCBljEL 7 | MAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRQwEgYDVQQHDAtTYW4gQW50b25p 8 | bzEcMBoGA1UECgwTRGF0YSBGb2xrIExhYnMsIExMQzEPMA0GA1UECwwGQ2VtZW50 9 | MRAwDgYDVQQDDAdtYWlscGl0MSAwHgYJKoZIhvcNAQkBFhFtYWlscGl0QGxvY2Fs 10 | aG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALlNBVk6Dgs0Z9MC 11 | lxPwdZLCOoTGEeKhjEkX1fc8MrBN1zHhCt1zrSHmgFmqX5d2+xSGCQTYlh731Dw+ 12 | 6w7gHkFEuSHr1pTVkLpBp+kxH+MnxtFXPOK82tEw7KRCbr0id2c4ejIz+iaGLjLq 13 | 7PRru5hgwlGDyfYLmhIC9aGd8i61qOnXrc0XpoImm0nCwPeUKWReICt4F7utSWC2 14 | dRR0drwn9W2T+yAEcyF3Q9lXUrQLrqcADjlWRu1fGQq6tobPa2ILGt6f2BqJz0Jn 15 | 1s/aud2CyeklA2x9rO5n342v6vYEHi6ZkR+hzATjQeJN3EaV748NYPrlV0X4WISe 16 | qKmI8YGxlH32OX9Zwdh+ItscebhlJNDYIV0UizrMPkP4RP8VuBrQFodODCY9/aYB 17 | tcOzJq42sj7UHjDC8YfLLIAoojmn7yxNxaq1ok7gNeHdsswkwohi/cjE5807xCO4 18 | XiNBsaiPS6rO0zxsihwkk/KTfOu6Li4peRsXnD5DrthJ4Ab5Gqr0jdSTD7y5O6hF 19 | VtMQJc1+VYpw0N5hh7+cbwU840m4E0EsTNUr40Y4JwP9NqpxDocaKw9GsZbiFYmP 20 | YEJhts1nQitKDEd630zvvSTzB4lRuNI1lbekSeTeby7LThOUx4/mXHsa1GMWZhWz 21 | xItumX/+pUmKUeMekpP8F3Tr7J4HAgMBAAGjUzBRMB0GA1UdDgQWBBRKVN6BwWcD 22 | oR0v+tW+wo/N1Is2fzAfBgNVHSMEGDAWgBRKVN6BwWcDoR0v+tW+wo/N1Is2fzAP 23 | BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAbJdR7XtENILWqOm8j 24 | zEiR8P3VrpIxQHx5z9vCl3alFsplTGF2/Yy2edVGIdpN6zd6q9tQwfq+dZes25L0 25 | jNRqZ+nrZJMgOpktCrP6GcX4BslwSOM3gAXp8nGzVcsHXkk6MBc9CmXMw7hPiBr1 26 | ZAsBlh0Ic+5vayEhnV3iurwdJYZTD9SWrPrhzMvRa+TkAkIK3THmD1TZ3HQJB+wx 27 | SWGtWrQbF2M76r8XoKVx3nyQOm2z9RpIo8Zxii5LJmEqzm5taLwNKDmR632LTnV6 28 | KG5z99JfMAO4NgF0kVQxhp6f/C0SK4dq0+TOt1iBlO6jUUVnkdIZKI2TIFA0DAq3 29 | dSp8m9HaQOoipXNSG2zcUoUuXM30oSSiVxmNgQm2LnwugTCtKL8yXWZvhmevu1mn 30 | 1zFMsDG4aJwNJ1uS4tNWseI8+l3hUap2wQ9v3dm1Z6MBZTv6hmKGzvBbMKfX7Agk 31 | 6IgXLf6y5cmq5YVkttq3V3jV2KCC+WQ4XuU3PvNK3Ki6YD4QEK7kh61erfoBuodh 32 | GkQI4LObAP7X/9Ha4g0ZEbeh4SnkhO7SQb+ZXzZNkadSmGSpMtKE+PyjgyV6fs/2 33 | j5Xu1u4+LDPr2qwFnp3rCVw8eoYESK3zHK5XW+U6Fm3xgE/VhsO+Hw/Tg9CQN+O3 34 | mjErtgV+K4j0gVg4kaci6kXx1g== 35 | -----END CERTIFICATE----- 36 | -------------------------------------------------------------------------------- /docker/mailpit/dev-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC5TQVZOg4LNGfT 3 | ApcT8HWSwjqExhHioYxJF9X3PDKwTdcx4Qrdc60h5oBZql+XdvsUhgkE2JYe99Q8 4 | PusO4B5BRLkh69aU1ZC6QafpMR/jJ8bRVzzivNrRMOykQm69IndnOHoyM/omhi4y 5 | 6uz0a7uYYMJRg8n2C5oSAvWhnfIutajp163NF6aCJptJwsD3lClkXiAreBe7rUlg 6 | tnUUdHa8J/Vtk/sgBHMhd0PZV1K0C66nAA45VkbtXxkKuraGz2tiCxren9gaic9C 7 | Z9bP2rndgsnpJQNsfazuZ9+Nr+r2BB4umZEfocwE40HiTdxGle+PDWD65VdF+FiE 8 | nqipiPGBsZR99jl/WcHYfiLbHHm4ZSTQ2CFdFIs6zD5D+ET/Fbga0BaHTgwmPf2m 9 | AbXDsyauNrI+1B4wwvGHyyyAKKI5p+8sTcWqtaJO4DXh3bLMJMKIYv3IxOfNO8Qj 10 | uF4jQbGoj0uqztM8bIocJJPyk3zrui4uKXkbF5w+Q67YSeAG+Rqq9I3Ukw+8uTuo 11 | RVbTECXNflWKcNDeYYe/nG8FPONJuBNBLEzVK+NGOCcD/TaqcQ6HGisPRrGW4hWJ 12 | j2BCYbbNZ0IrSgxHet9M770k8weJUbjSNZW3pEnk3m8uy04TlMeP5lx7GtRjFmYV 13 | s8SLbpl//qVJilHjHpKT/Bd06+yeBwIDAQABAoICAEZpqaAjyuVgFxncUJtvksXf 14 | T4xXlcFIQP4fdBt8QQi0s5LNIKtRAxewNtKbxrJQMI22dyPjx3viEcCI6hpfeK+1 15 | lSH6M7Kfyty0CUG3/JV9bnPrEgRY3k+Cp1GtutXdDIFpOSntjV9pOpH3qm4gqAr5 16 | ra17BlocQ4IXpM4yri4ospSVdAJMu+WWFQk828XYg7gTemb6Pg5/hTQecTQCI4JR 17 | LtZiVpluh29OmjYzFAa9r7Le9wi7q70Ul3f3xldI51W8wYuaMuy2tE7YFY0rYNh3 18 | FBGnknr98KIxT4ZQNFki7HMpwLdD7LpovwbnLk6WOA9kdpcwYe8BNWXDwnSVyKsp 19 | gFEiR3co7PsRJPypYMxIpWseCAkmdj8+xRgBa5HpaPL010XgZJwLWDxcx6rxo1HS 20 | 7diqFE/i0tvvLxiEYwOVxBsfBw3n0zR0FDuN7G7qpxfUOJtLiJbv75/qqcAygiXK 21 | 3WWnj99rCUwUjaeZRTaag247Qti8BifqKWOmdcRHBD+QEHay+Q7SlPpu1NwKIBIQ 22 | goBgLUJ+P6a5+cJMWK9K1QFvoiiS/dVqUTUk2qMB2crB0kQsAmr1GkHNZfu8fQ8t 23 | 1pe0Q4wMw96D57g2yRQ5uzxHNDkS9G39odjOMKSb/queq34vsGgI//PxU4HtxhbD 24 | uGodJiqeA7AnV1wR7dNBAoIBAQDnmJDaNr4rBLtWUDlZlgrGIF+fEi1DlLMjTRd8 25 | fn5C+sQzkaCAhy/vLFzqnEute3F/DTFbLiLgSsQ1Fl9md10oaG0zdERDcqNwSijS 26 | V8o9ts0QnchXPYTPFai3wfbIofkjJhNfoUmL+M2XsTSe6tfR41p3YLyVY2ITyek6 27 | lC1srHRjTiUxNLdy2javWhIivepqcL0Yz7U9KCvUMRZcMOVWyC/MC1lAXPVFyNnh 28 | FU6D1Apunzi9uXOfQl7j1f9kHSnfawfqwQoLscO1NPqO8GM0QAXCRGnk3J3+kE6S 29 | kGpLX1Axf+oCPt25f9ZWm3wDKHR7aIwhJv6cOSqv4Enip7xHAoIBAQDM055CFe6W 30 | yxgl57JqbuJLeOnZrYCUHl8wS9kVlZbOdvW6QtOid93RJ+US7PXt/fk73YiKn8fT 31 | 9D/kkkyd7RSrKA/AKyPQfFfLLT5/qkFP1OZtJ3vcNXmnVjcskcMIFDM31OQGBf+I 32 | TdcQ5muq030Y1nVcu1/E7udRFD97/+atjSd37KyJEeP3opyP4iFtklxXU+lFlkvr 33 | DBdTJsE4LVXtOOlEtd5Tas24TpNky2LsEFQz9mWygjBkoz5pHXCKSOkmm3Qz7S7N 34 | l6kbtgRR9D0t9fwGgybzTvEsE1a+9+MvhSYkf2yQuMM01vEQ3CfDpBvf7WM5/cB2 35 | LMu4X322c7BBAoIBAQDLFckn4Us/M+YHGVBBE8ac2HSha/IPSg0QTqDixZV4rKdy 36 | RShGrMVG6VMNVEM0fIQZEnuOZlWk80s89kJv+wnQzkm8Dh9yOcvCQvWrBdrN5UfL 37 | Y2Dzx3l3kpmhkdATPZ3XyaLBgBCbUnEOrRDkrjDU15ZUCps0MLMngS4o9RkkK9Hf 38 | 5v3MOVsItvuvJr+ygXFXJ0daw4E9gMV2TBk8fJAPWno3Zlg8jYdzS15r9yAjj8Qa 39 | HztFe6M9K5lEFzreEojOZu/JVr+1Y4unki6JO5jyj5W9NfrZ+u/885RDB6p+L7WF 40 | wpJ0p6YM7WIKDkxgBJCoSxReWfB5E9Qv5/FCdS4vAoIBAEqpZMmwFu0ukNnYUEfN 41 | rX1XUN7BCNp1C7ueGj5s7bDK2h2QGHbjfJ6uDSlN6QNcjYoN4aSuQ3f2U4fs8DKs 42 | 5djR3JPu5bosaRAtqNd+ZxpDf88QEm0drP+bRLdhVpdOTbEvUAMGErRLs3Z8l4iI 43 | WNRB8DviLTGq5/S9DbsUd7CRgG6NfgLk25U72Bf2lLrNHA3VD3YHKBtAqAvuV4Yz 44 | uFulYBpktOrxRpXFRqL6JE/qT9c1HLLqE9vLSYelbI1rsFkbV0tKTMIyYzkvqvl4 45 | rwhe3wQ8sGkGQJERZ5Bq6Yw728B4FknWn4lWRD8iEPiWjHaeoInV/l7VS1kkrb1h 46 | BEECggEAeCbHMrQyZGUtZ183KeOEgAyioJuJurrBTn4S+ZsP3kUVt7jx2eo7aYpg 47 | pG5XRWMlQlARyNAo2FXOfC3DRSs8e6OGQl41k4h0lxJtsw1JOXenm3AOqpEUd6IQ 48 | m4UrUoq4jkeAG7jwra1tv2TIWKYbjg1oFjlQwIQzBSKVTijAhTiBtqa/fXQNZwT+ 49 | 7B+FfNqMK6qo7UJ4rOTZtnJZFzudQjndx3ukOO/mf8Gh+6XMPYLw81QHEKm9e3m/ 50 | giK2jzQCnQUe49YfxNtwFJVLIvYptRnzPSDV76qcnRUQvvkgXh/NFBXl4mvWM9R8 51 | mDpNhf5uD/yZ9rYY6wjJ4nQYBTTJpQ== 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /docker/vimrc: -------------------------------------------------------------------------------- 1 | set expandtab 2 | set tabstop=4 3 | syntax on 4 | -------------------------------------------------------------------------------- /docs/source/_static/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/docs/source/_static/.placeholder -------------------------------------------------------------------------------- /docs/source/_static/logo-rhino.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/docs/source/_static/logo-rhino.png -------------------------------------------------------------------------------- /docs/source/_static/logo-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/docs/source/_static/logo-text.png -------------------------------------------------------------------------------- /docs/source/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/docs/source/_static/logo.png -------------------------------------------------------------------------------- /docs/source/_templates/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/docs/source/_templates/.placeholder -------------------------------------------------------------------------------- /docs/source/_templates/globaltoc.html: -------------------------------------------------------------------------------- 1 | 2 | 12 | -------------------------------------------------------------------------------- /docs/source/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {# Import the theme's layout. #} 2 | {% extends "!layout.html" %} 3 | 4 | {% block breadcrumbs %} 5 |
6 | 12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /docs/source/_templates/logo-text.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/source/_themes/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/docs/source/_themes/.placeholder -------------------------------------------------------------------------------- /docs/source/api/core/arg.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.arg: 2 | 3 | :mod:`cement.core.arg` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.arg 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/backend.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.backend: 2 | 3 | :mod:`cement.core.backend` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.backend 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/cache.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.cache: 2 | 3 | :mod:`cement.core.cache` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.cache 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/config.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.config: 2 | 3 | :mod:`cement.core.config` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.config 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/controller.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.controller: 2 | 3 | :mod:`cement.core.controller` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.controller 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/exc.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.exc: 2 | 3 | :mod:`cement.core.exc` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.exc 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/extension.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.extension: 2 | 3 | :mod:`cement.core.extension` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.extension 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/foundation.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.foundation: 2 | 3 | :mod:`cement.core.foundation` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.foundation 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/handler.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.handler: 2 | 3 | :mod:`cement.core.handler` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.handler 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/hook.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.hook: 2 | 3 | :mod:`cement.core.hook` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.hook 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/index.rst: -------------------------------------------------------------------------------- 1 | 2 | Core Modules 3 | ============================================================================== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | arg 9 | backend 10 | cache 11 | config 12 | controller 13 | exc 14 | extension 15 | foundation 16 | handler 17 | hook 18 | interface 19 | log 20 | mail 21 | meta 22 | output 23 | template 24 | plugin 25 | -------------------------------------------------------------------------------- /docs/source/api/core/interface.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.interface: 2 | 3 | :mod:`cement.core.interface` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.interface 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/log.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.log: 2 | 3 | :mod:`cement.core.log` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.log 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/mail.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.mail: 2 | 3 | :mod:`cement.core.mail` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.mail 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/meta.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.meta: 2 | 3 | :mod:`cement.core.meta` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.meta 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/output.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.output: 2 | 3 | :mod:`cement.core.output` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.output 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/plugin.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.plugin: 2 | 3 | :mod:`cement.core.plugin` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.core.plugin 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/core/template.rst: -------------------------------------------------------------------------------- 1 | .. _cement.core.template: 2 | 3 | :mod:`cement.core.template` 4 | =============================================================================== 5 | 6 | .. automodule:: cement.core.template 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_alarm.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_alarm: 2 | 3 | :mod:`cement.ext.ext_alarm` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_alarm 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_argparse.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_argparse: 2 | 3 | :mod:`cement.ext.ext_argparse` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_argparse 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_colorlog.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_colorlog: 2 | 3 | :mod:`cement.ext.ext_colorlog` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_colorlog 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_configparser.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_configparser: 2 | 3 | :mod:`cement.ext.ext_configparser` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_configparser 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_daemon.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_daemon: 2 | 3 | :mod:`cement.ext.ext_daemon` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_daemon 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_dummy.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_dummy: 2 | 3 | :mod:`cement.ext.ext_dummy` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_dummy 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_generate.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_generate: 2 | 3 | :mod:`cement.ext.ext_generate` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_generate 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_jinja2.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_jinja2: 2 | 3 | :mod:`cement.ext.ext_jinja2` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_jinja2 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_json.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_json: 2 | 3 | :mod:`cement.ext.ext_json` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_json 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_logging.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_logging: 2 | 3 | :mod:`cement.ext.ext_logging` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_logging 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_memcached.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_memcached: 2 | 3 | :mod:`cement.ext.ext_memcached` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_memcached 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_mustache.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_mustache: 2 | 3 | :mod:`cement.ext.ext_mustache` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_mustache 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_plugin.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_plugin: 2 | 3 | :mod:`cement.ext.ext_plugin` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_plugin 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_print.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_print: 2 | 3 | :mod:`cement.ext.ext_print` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_print 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_redis.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_redis: 2 | 3 | :mod:`cement.ext.ext_redis` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_redis 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_scrub.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_scrub: 2 | 3 | :mod:`cement.ext.ext_scrub` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_scrub 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_smtp.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_smtp: 2 | 3 | :mod:`cement.ext.ext_smtp` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_smtp 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_tabulate.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_tabulate: 2 | 3 | :mod:`cement.ext.ext_tabulate` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_tabulate 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_watchdog.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_watchdog: 2 | 3 | :mod:`cement.ext.ext_watchdog` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_watchdog 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/ext_yaml.rst: -------------------------------------------------------------------------------- 1 | .. _cement.ext.ext_yaml: 2 | 3 | :mod:`cement.ext.ext_yaml` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.ext.ext_yaml 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/ext/index.rst: -------------------------------------------------------------------------------- 1 | 2 | Extension Modules 3 | ============================================================================== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | ext_alarm 9 | ext_argparse 10 | ext_colorlog 11 | ext_configparser 12 | ext_daemon 13 | ext_dummy 14 | ext_generate 15 | ext_jinja2 16 | ext_json 17 | ext_logging 18 | ext_memcached 19 | ext_mustache 20 | ext_plugin 21 | ext_print 22 | ext_redis 23 | ext_scrub 24 | ext_smtp 25 | ext_tabulate 26 | ext_yaml 27 | ext_watchdog 28 | -------------------------------------------------------------------------------- /docs/source/api/index.rst: -------------------------------------------------------------------------------- 1 | 2 | API Reference 3 | ============================================================================== 4 | 5 | .. note:: This documentation is strictly for API reference. For more complete 6 | developer documentation, please visit the official site 7 | http://builtoncement.com. 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | 12 | core/index 13 | utils/index 14 | ext/index 15 | -------------------------------------------------------------------------------- /docs/source/api/utils/fs.rst: -------------------------------------------------------------------------------- 1 | .. _cement.utils.fs: 2 | 3 | :mod:`cement.utils.fs` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.utils.fs 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/utils/index.rst: -------------------------------------------------------------------------------- 1 | 2 | Utility Modules 3 | =============================================================================== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | fs 9 | shell 10 | misc 11 | test 12 | -------------------------------------------------------------------------------- /docs/source/api/utils/misc.rst: -------------------------------------------------------------------------------- 1 | .. _cement.utils.misc: 2 | 3 | :mod:`cement.utils.misc` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.utils.misc 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/utils/shell.rst: -------------------------------------------------------------------------------- 1 | .. _cement.utils.shell: 2 | 3 | :mod:`cement.utils.shell` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.utils.shell 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/api/utils/test.rst: -------------------------------------------------------------------------------- 1 | .. _cement.utils.test: 2 | 3 | :mod:`cement.utils.test` 4 | ============================================================================== 5 | 6 | .. automodule:: cement.utils.test 7 | :members: 8 | :private-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============================================================================== 3 | 4 | .. note:: This documentation is strictly for API reference. For more complete 5 | developer documentation, please visit the official site 6 | http://builtoncement.com. 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | api/core/index 12 | api/utils/index 13 | api/ext/index 14 | -------------------------------------------------------------------------------- /lgtm.yml: -------------------------------------------------------------------------------- 1 | path_classifiers: 2 | template: 3 | - cli/templates 4 | 5 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "cement" 3 | description = "Application Framework for Python" 4 | readme = "README.md" 5 | keywords = [ 6 | "python", 7 | "module", 8 | "cli framework" 9 | ] 10 | authors=[{name = "Data Folk Labs, LLC", email= "derks@datafolklabs.com"}] 11 | license={file="LICENSE"} 12 | classifiers = [ 13 | "Programming Language :: Python :: 3", 14 | "License :: OSI Approved :: BSD License" 15 | ] 16 | 17 | dynamic = ["version", "README"] 18 | 19 | requires-python = ">=3.8" 20 | dependencies = [] 21 | 22 | [project.optional-dependencies] 23 | alarm = [] 24 | argparse = [] 25 | colorlog = ["colorlog"] 26 | configparser = [] 27 | daemon = [] 28 | docs = ["sphinx", "sphinx_rtd_theme", "guzzle_sphinx_theme", "sphinxcontrib-napoleon"] 29 | dummy = [] 30 | generate = ["pyYaml"] 31 | jinja2 = ["jinja2"] 32 | json = [] 33 | logging = [] 34 | memcached = ["pylibmc"] 35 | mustache = ["pystache"] 36 | plugin = [] 37 | print = [] 38 | redis = ["redis"] 39 | scrub = [] 40 | smtp = [] 41 | tabulate = ["tabulate"] 42 | watchdog = ["watchdog"] 43 | yaml = ["pyYaml"] 44 | cli = ["cement[yaml,jinja2]"] 45 | 46 | [project.scripts] 47 | cement = "cement.cli.main:main" 48 | 49 | [project.urls] 50 | homepage = "https://builtoncement.com" 51 | documentation = "https://docs.builtoncement.com" 52 | repository = "https://github.com/datafolklabs/cement" 53 | 54 | [build-system] 55 | requires = ["pdm-backend"] 56 | build-backend = "pdm.backend" 57 | 58 | ############### 59 | # DEVELOPMENT # 60 | ############### 61 | 62 | [tool.pytest.ini_options] 63 | testpaths = ["tests"] 64 | addopts = "-v --cov-report=term --cov-report=html:coverage-report --capture=sys tests/" 65 | python_files= "test_*.py" 66 | 67 | [tool.coverage.report] 68 | precision = 2 69 | 70 | 71 | [tool.ruff] 72 | target-version = "py38" 73 | line-length = 100 74 | indent-width = 4 75 | exclude = [ 76 | ".git", 77 | "cement/cli/templates", 78 | "cement/cli/contrib" 79 | ] 80 | include = [ 81 | "cement/**/*.py", 82 | "tests/**/*.py" 83 | ] 84 | 85 | [tool.ruff.lint] 86 | preview = true 87 | extend-select = ["E", "F", "W"] 88 | ignore = [] 89 | fixable = ["ALL"] 90 | unfixable = [] 91 | 92 | # ignore = [ 93 | # "E402", 94 | # "E713", 95 | # "E721", 96 | # "E714", 97 | # "F841", 98 | # "F507" 99 | # ] 100 | 101 | # TBD 102 | # [tool.ruff.format] 103 | # quote-style = "single" 104 | 105 | # [[tool.mypy.overrides]] 106 | # module = "cement.*" 107 | # ignore_missing_imports = true 108 | 109 | [tool.mypy] 110 | python_version = "3.8" 111 | disallow_untyped_calls = true 112 | disallow_untyped_defs = true 113 | disallow_any_unimported = false 114 | disallow_incomplete_defs = true 115 | disallow_untyped_decorators = true 116 | no_implicit_optional = true 117 | strict_optional = true 118 | check_untyped_defs = true 119 | warn_return_any = true 120 | warn_unused_ignores = true 121 | show_error_codes = true 122 | 123 | disable_error_code = [ 124 | # disable as MetaMixin/Meta is used everywhere and triggers this 125 | "attr-defined", 126 | ] 127 | 128 | files = [ 129 | "cement/", 130 | # "tests/" 131 | ] 132 | exclude = """(?x)( 133 | ^cement/cli/templates | 134 | ^.git/ | 135 | ^tests 136 | )""" 137 | 138 | [tool.pdm.scripts] 139 | cement = {call = "cement.cli.main:main"} 140 | 141 | [tool.pdm.build] 142 | package-dir = "." 143 | includes = [ 144 | "cement/", 145 | "cement/cli/templates/generate/", 146 | "CONTRIBUTORS.md", 147 | "CHANGELOG.md" 148 | ] 149 | excludes = ["tests/"] 150 | 151 | [tool.pdm.version] 152 | source = "call" 153 | getter = "cement.utils.version:get_version" 154 | 155 | 156 | [dependency-groups] 157 | dev = [ 158 | "pytest>=4.3.1", 159 | "pytest-cov>=2.6.1", 160 | "coverage>=4.5.3", 161 | "mypy>=1.9.0", 162 | "ruff>=0.3.2", 163 | "mock>=5.1.0", 164 | "pypng>=0.20220715.0", 165 | ] 166 | -------------------------------------------------------------------------------- /scripts/cli-smoke-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | [ -z "$CEMENT_VERSION" ] && CEMENT_VERSION="3.0" 5 | [ -z "$PYTHON_VERSIONS" ] && PYTHON_VERSIONS="3.8 3.9 3.10 3.11 3.12 3.13" 6 | 7 | function smoke-test { 8 | pyver=$1 9 | 10 | echo "-------------------------------------------------------------------" 11 | echo "SMOKE TESTING CEMENT CLI ON PYTHON $pyver" 12 | echo "-------------------------------------------------------------------" 13 | docker stop cement-cli-smoke-test 2>&1 >/dev/null ||: 14 | docker rm -f cement-cli-smoke-test 2>&1 >/dev/null ||: 15 | docker run \ 16 | --name cement-cli-smoke-test \ 17 | -itd \ 18 | -v `pwd`:/src \ 19 | python:$pyver \ 20 | /bin/bash 21 | 22 | docker exec cement-cli-smoke-test /bin/bash -c "cd /src ; pip install `ls dist/cement-*.tar.gz`[cli]" 23 | tmp=$(docker exec -t cement-cli-smoke-test /bin/bash -c "mktemp -d") 24 | 25 | 26 | ### verify help output 27 | 28 | res=$(docker exec cement-cli-smoke-test /bin/bash -c "cement --version") 29 | echo "$res" | grep "Cement Framework $CEMENT_VERSION.[0-9]" 30 | echo "$res" | grep "Python $pyver.[0-9]" 31 | echo "$res" | grep "Platform Linux.*" 32 | res=$(docker exec cement-cli-smoke-test /bin/bash -c "cement --help") 33 | echo "$res" | grep "Cement Framework Developer Tools" 34 | echo "$res" | grep "generate" 35 | res=$(docker exec cement-cli-smoke-test /bin/bash -c "cement -h") 36 | echo "$res" | grep "Cement Framework Developer Tools" 37 | echo "$res" | grep "generate" 38 | res=$(docker exec cement-cli-smoke-test /bin/bash -c "cement generate -h") 39 | echo "$res" | grep "project" 40 | echo "$res" | grep "todo-tutorial" 41 | echo "$res" | grep "plugin" 42 | echo "$res" | grep "script" 43 | echo "$res" | grep "extension" 44 | res=$(docker exec cement-cli-smoke-test /bin/bash -c "cement generate project -h") 45 | echo "$res" | grep "destination directory path" 46 | echo "$res" | grep -- "-D, --defaults" 47 | 48 | 49 | ### generate a project 50 | 51 | docker exec cement-cli-smoke-test /bin/bash -c "cement generate project -D $tmp/myapp" 52 | docker exec cement-cli-smoke-test /bin/bash -c "cd $tmp/myapp ; pip install -r requirements.txt" 53 | docker exec cement-cli-smoke-test /bin/bash -c "cd $tmp/myapp ; pip install setuptools" 54 | docker exec cement-cli-smoke-test /bin/bash -c "cd $tmp/myapp ; python setup.py install" 55 | res=$(docker exec -t cement-cli-smoke-test /bin/bash -c "myapp --version") 56 | echo "$res" | grep "Cement Framework $CEMENT_VERSION\.[0-9]" 57 | echo "$res" | grep "Python $pyver.[0-9]" 58 | echo "$res" | grep "Platform Linux.*" 59 | 60 | 61 | ### generate a script 62 | 63 | docker exec cement-cli-smoke-test /bin/bash -c "cement generate script -D $tmp/myscript" 64 | res=$(docker exec -t cement-cli-smoke-test /bin/bash -c "python $tmp/myscript/myscript.py --version") 65 | echo "$res" | grep "myscript v0.0.1" 66 | 67 | 68 | ### generate an extension 69 | 70 | docker exec cement-cli-smoke-test /bin/bash -c "cement generate extension -D $tmp/myapp/myapp/ext" 71 | res=$(docker exec -t cement-cli-smoke-test /bin/bash -c "cat $tmp/myapp/myapp/ext/ext_myextension.py") 72 | echo "$res" | grep "myextension_pre_run_hook" 73 | 74 | 75 | ### generate a plugin 76 | 77 | docker exec cement-cli-smoke-test /bin/bash -c "cement generate plugin -D $tmp/myapp/myapp/plugins" 78 | res=$(docker exec -t cement-cli-smoke-test /bin/bash -c "cat $tmp/myapp/myapp/plugins/myplugin/controllers/myplugin.py") 79 | echo "$res" | grep "class MyPlugin(Controller)" 80 | 81 | ### finish 82 | 83 | echo "" 84 | echo "SMOKE TEST FOR PYTHON $pyver COMPLETED WITHOUT ERRORS" 85 | echo "" 86 | } 87 | 88 | rm -rf tmp/cli-smoke-test.out 89 | echo "output in tmp/cli-smoke-test.out" 90 | docker compose exec cement pdm build 91 | 92 | for pyver in $PYTHON_VERSIONS; do 93 | echo -n "python $pyver . . . " 94 | smoke-test $pyver 2>> tmp/cli-smoke-test.out 1>> tmp/cli-smoke-test.out 95 | echo "ok" 96 | done 97 | -------------------------------------------------------------------------------- /scripts/travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | docker-compose up -d mailpit 2>&1 >/dev/null 5 | 6 | sleep 10 7 | 8 | rm -f .coverage 9 | pdm venv create $PYTHON_VERSION 10 | pdm install 11 | make test 12 | -------------------------------------------------------------------------------- /scripts/vagrant/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -x 4 | 5 | cd /vagrant 6 | 7 | function bail { 8 | echo $1 9 | exit 1 10 | } 11 | 12 | ### first determine distro 13 | 14 | if [ -f "/etc/os-release" ]; then 15 | DISTRO=$(grep "^ID=" /etc/os-release | sed 's/ID=//' | sed 's/\"//g') 16 | elif [ -f "/etc/redhat-release" ]; then 17 | rel=$(cat /etc/redhat-release | awk {' print $1 '}) 18 | if [ "$rel" = "CentOS" ]; then 19 | DISTRO="centos" 20 | elif [ "$rel" = "Red" ]; then 21 | DISTRO='redhat' 22 | else 23 | bail "Unsupported Distro" 24 | fi 25 | else 26 | bail "Unsupported Distro" 27 | fi 28 | 29 | case "$DISTRO" in 30 | ubuntu|debian) 31 | export DEBIAN_FRONTEND=noninteractive 32 | DISTRO_VERSION=$(grep "^VERSION_ID=" /etc/os-release \ 33 | | sed 's/VERSION_ID=//' \ 34 | | sed 's/\"//g') 35 | DISTRO_MAJOR_VERSION=$(echo $DISTRO_VERSION | awk -F . {' print $1 '}) 36 | DISTRO_MINOR_VERSION=$(echo $DISTRO_VERSION | awk -F . {' print $2 '}) 37 | ;; 38 | 39 | centos|redhat|fedora) 40 | # Newer versions like CentOS 7+ 41 | if [ -f "/etc/os-release" ]; then 42 | DISTRO_VERSION=$(grep "^VERSION_ID=" /etc/os-release \ 43 | | sed 's/VERSION_ID=//' \ 44 | | sed 's/\"//g') 45 | DISTRO_MAJOR_VERSION=$(echo $DISTRO_VERSION | awk -F . {' print $1 '}) 46 | DISTRO_MINOR_VERSION=$(echo $DISTRO_VERSION | awk -F . {' print $2 '}) 47 | else 48 | DISTRO_VERSION=$(cat /etc/redhat-release | awk {' print $3 '}) 49 | DISTRO_MAJOR_VERSION=$(echo $DISTRO_VERSION | awk -F . {' print $1 '}) 50 | DISTRO_MINOR_VERSION=$(echo $DISTRO_VERSION | awk -F . {' print $2 '}) 51 | fi 52 | ;; 53 | 54 | *) 55 | DISTRO_VERSION=$(grep "^VERSION_ID=" /etc/os-release \ 56 | | sed 's/VERSION_ID=//' \ 57 | | sed 's/\"//g') 58 | DISTRO_MAJOR_VERSION=$(echo $DISTRO_VERSION | awk -F . {' print $1 '}) 59 | DISTRO_MINOR_VERSION=$(echo $DISTRO_VERSION | awk -F . {' print $2 '}) 60 | ;; 61 | esac 62 | 63 | export DISTRO_VERSION 64 | export DISTRO_MAJOR_VERSION 65 | export DISTRO_MINOR_VERSION 66 | 67 | 68 | ### do work 69 | 70 | case "$DISTRO" in 71 | ubuntu|debian) 72 | sudo apt-get update 73 | sudo apt-get install -y \ 74 | apt-transport-https \ 75 | ca-certificates \ 76 | software-properties-common 77 | 78 | sudo apt-get update 79 | sudo apt-get upgrade -y 80 | sudo apt-get dist-upgrade -y 81 | sudo apt-get install -y \ 82 | python3 \ 83 | python3-dev \ 84 | python3-pip \ 85 | redis-server \ 86 | redis-tools \ 87 | memcached \ 88 | libmemcached-dev \ 89 | zlib1g-dev 90 | 91 | sudo apt-get autoremove -y 92 | sudo pip3 install virtualenv 93 | ;; 94 | 95 | *) 96 | bail "Unsupported Distro" 97 | ;; 98 | 99 | esac 100 | 101 | # for tests 102 | 103 | sudo systemctl restart memcached 104 | sudo systemctl restart redis 105 | 106 | /vagrant/env/bin/pip install -r requirements-dev.txt 107 | /vagrant/env/bin/python setup.py develop 108 | 109 | cat >>~/.bashrc < {{ foo1 }} 2 | -------------------------------------------------------------------------------- /tests/data/templates/generate/test1/ignore-me: -------------------------------------------------------------------------------- 1 | foo1 => {{ foo1 }} 2 | -------------------------------------------------------------------------------- /tests/data/templates/generate/test1/take-me: -------------------------------------------------------------------------------- 1 | foo1 => {{ foo1 }} 2 | foo2 => {{ foo2 }} 3 | foo3 => {{ foo3 }} 4 | foo4 => {{ foo4 }} 5 | foo5 => {{ foo5 }} 6 | foo6 => {{ foo6 }} 7 | -------------------------------------------------------------------------------- /tests/data/templates/generate/test2/.generate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | variables: 4 | 5 | - name: foo1 6 | prompt: Variable One 7 | default: bar1 8 | validate: '.*not-bar1.*' 9 | -------------------------------------------------------------------------------- /tests/data/templates/generate/test2/take-me: -------------------------------------------------------------------------------- 1 | foo1 => {{ foo1 }} 2 | -------------------------------------------------------------------------------- /tests/data/templates/generate/test3/.generate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | variables: 4 | 5 | - name: foo1 6 | prompt: Variable One 7 | default: bar1 8 | case: invalid_case 9 | -------------------------------------------------------------------------------- /tests/data/templates/generate/test3/take-me: -------------------------------------------------------------------------------- 1 | foo1 => {{ foo1 }} 2 | -------------------------------------------------------------------------------- /tests/data/templates/generate/test4/.generate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ignore: 4 | - '.*ignore-me.*' 5 | 6 | exclude: 7 | - '.*exclude-me.*' 8 | 9 | -------------------------------------------------------------------------------- /tests/data/templates/generate/test4/exclude-me/take-me: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/tests/data/templates/generate/test4/exclude-me/take-me -------------------------------------------------------------------------------- /tests/data/templates/generate/test4/ignore-me/take-me: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/tests/data/templates/generate/test4/ignore-me/take-me -------------------------------------------------------------------------------- /tests/data/templates/generate/test4/take-me/exclude-me: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/tests/data/templates/generate/test4/take-me/exclude-me -------------------------------------------------------------------------------- /tests/data/templates/generate/test4/take-me/ignore-me: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/tests/data/templates/generate/test4/take-me/ignore-me -------------------------------------------------------------------------------- /tests/data/templates/generate/test4/take-me/take-me: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/tests/data/templates/generate/test4/take-me/take-me -------------------------------------------------------------------------------- /tests/data/templates/generate/test5/.generate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | variables: 4 | 5 | - name: foo1 6 | prompt: Variable One 7 | -------------------------------------------------------------------------------- /tests/data/templates/generate/test5/take-me: -------------------------------------------------------------------------------- 1 | foo1 => {{ foo1 }} 2 | -------------------------------------------------------------------------------- /tests/data/templates/test_base_template.handlebars: -------------------------------------------------------------------------------- 1 | {{> "test_partial_template.handlebars"}} -------------------------------------------------------------------------------- /tests/data/templates/test_base_template.mustache: -------------------------------------------------------------------------------- 1 | {{> test_partial_template.mustache}} -------------------------------------------------------------------------------- /tests/data/templates/test_partial_template.handlebars: -------------------------------------------------------------------------------- 1 | Inside partial > foo equals {{foo}} 2 | -------------------------------------------------------------------------------- /tests/data/templates/test_partial_template.mustache: -------------------------------------------------------------------------------- 1 | Inside partial > foo equals {{foo}} 2 | -------------------------------------------------------------------------------- /tests/data/templates/test_template.genshi: -------------------------------------------------------------------------------- 1 | foo equals ${foo} 2 | -------------------------------------------------------------------------------- /tests/data/templates/test_template.handlebars: -------------------------------------------------------------------------------- 1 | foo equals {{foo}} 2 | -------------------------------------------------------------------------------- /tests/data/templates/test_template.jinja2: -------------------------------------------------------------------------------- 1 | foo equals {{foo}} 2 | -------------------------------------------------------------------------------- /tests/data/templates/test_template.mustache: -------------------------------------------------------------------------------- 1 | foo equals {{foo}} 2 | -------------------------------------------------------------------------------- /tests/data/templates/test_template_child.jinja2: -------------------------------------------------------------------------------- 1 | {% extends 'test_template_parent.jinja2' %} 2 | {% block test %}foo equals{% endblock %} 3 | -------------------------------------------------------------------------------- /tests/data/templates/test_template_parent.jinja2: -------------------------------------------------------------------------------- 1 | {% block test %}{% endblock %} {{ foo }} 2 | -------------------------------------------------------------------------------- /tests/data/templates/test_template_utf8.jinja2: -------------------------------------------------------------------------------- 1 | foo est égal à {{foo}} 2 | -------------------------------------------------------------------------------- /tests/ext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/tests/ext/__init__.py -------------------------------------------------------------------------------- /tests/ext/test_ext_alarm.py: -------------------------------------------------------------------------------- 1 | import time 2 | import signal 3 | from pytest import raises 4 | from cement.core.exc import CaughtSignal 5 | from cement.core.foundation import TestApp 6 | 7 | 8 | class AlarmApp(TestApp): 9 | class Meta: 10 | extensions = ['alarm'] 11 | 12 | 13 | def test_alarm_timeout(): 14 | with AlarmApp() as app: 15 | with raises(CaughtSignal) as e: 16 | app.alarm.set(1, "The Timer Works!") 17 | time.sleep(3) 18 | 19 | assert e.value.signum == signal.SIGALRM 20 | 21 | # derks@2018-01-15: I think pytest is interrupting cement handling the 22 | # signal here, so we'll run the hooks manually 23 | for res in app.hook.run('signal', app, e.value.signum, e.value.frame): 24 | pass 25 | 26 | 27 | def test_alarm_no_timeout(): 28 | with AlarmApp() as app: 29 | app.alarm.set(2, "The Timer Works!") 30 | time.sleep(1) 31 | app.alarm.stop() 32 | time.sleep(1) 33 | # raises CaughtSignal if alarm.stop fails 34 | -------------------------------------------------------------------------------- /tests/ext/test_ext_colorlog.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from tempfile import mkstemp 4 | from cement.core.foundation import TestApp 5 | from cement.ext.ext_colorlog import ColoredFormatter 6 | from cement.utils.misc import init_defaults 7 | 8 | 9 | _, log_file = mkstemp() 10 | defaults = init_defaults() 11 | defaults['log.colorlog'] = dict( 12 | file=log_file, 13 | level='DEBUG', 14 | ) 15 | 16 | 17 | class ColorlogApp(TestApp): 18 | class Meta: 19 | config_defaults = defaults 20 | extensions = ['colorlog'] 21 | log_handler = 'colorlog' 22 | 23 | 24 | def test_colorlog(caplog): 25 | META = init_defaults('log.colorlog') 26 | META['log.colorlog']['propagate'] = True 27 | with ColorlogApp(meta_defaults=META) as app: 28 | app.run() 29 | app.log.info('this is an info message') 30 | app.log.warning('this is a warning message') 31 | app.log.error('this is an error message') 32 | app.log.fatal('this is a fatal message') 33 | app.log.debug('this is a debug message') 34 | logged = [log[2] for log in caplog.record_tuples] 35 | assert 'this is an info message' in logged 36 | assert 'this is a warning message' in logged 37 | assert 'this is an error message' in logged 38 | assert 'this is a debug message' in logged 39 | assert 'this is a fatal message' in logged 40 | 41 | 42 | def test_colorize_file_log(tmp): 43 | # first test with colorize_file_log=true 44 | log_file = os.path.join(tmp.dir, 'test.log') 45 | defaults = init_defaults() 46 | defaults['log.colorlog'] = dict( 47 | file=log_file, 48 | level='DEBUG', 49 | colorize_file_log=True, 50 | ) 51 | 52 | with ColorlogApp(config_defaults=defaults) as app: 53 | app.run() 54 | app.log.info('this is an info message') 55 | klass = app.log._get_file_formatter(app.log._meta.file_format) 56 | assert isinstance(klass, ColoredFormatter) 57 | 58 | # then test with colorize_file_log=false 59 | 60 | defaults['log.colorlog']['colorize_file_log'] = False 61 | 62 | with ColorlogApp(config_defaults=defaults) as app: 63 | app.run() 64 | app.log.info('this is an info message') 65 | klass = app.log._get_file_formatter(app.log._meta.file_format) 66 | assert isinstance(klass, logging.Formatter) 67 | 68 | 69 | def test_colorize_console_log(tmp): 70 | # first test with colorize_file_log=true 71 | log_file = os.path.join(tmp.dir, 'test.log') 72 | defaults = init_defaults() 73 | defaults['log.colorlog'] = dict( 74 | file=log_file, 75 | level='DEBUG', 76 | colorize_console_log=True, 77 | ) 78 | 79 | with ColorlogApp(config_defaults=defaults) as app: 80 | app.run() 81 | app.log.info('this is an info message') 82 | _format = app.log._meta.console_format 83 | klass = app.log._get_console_formatter(_format) 84 | assert isinstance(klass, ColoredFormatter) 85 | 86 | # then test with colorize_file_log=false 87 | defaults['log.colorlog']['colorize_console_log'] = False 88 | 89 | with ColorlogApp(config_defaults=defaults) as app: 90 | app.run() 91 | app.log.info('this is an info message') 92 | _format = app.log._meta.console_format 93 | klass = app.log._get_console_formatter(_format) 94 | assert isinstance(klass, logging.Formatter) 95 | -------------------------------------------------------------------------------- /tests/ext/test_ext_configparser.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from cement.core.foundation import TestApp 4 | from cement.ext.ext_configparser import ConfigParserConfigHandler 5 | 6 | 7 | # module tests 8 | 9 | class TestConfigParserConfigHandler(object): 10 | def test_subclassing(self): 11 | class MyConfigHandler(ConfigParserConfigHandler): 12 | class Meta: 13 | label = 'my_config_handler' 14 | 15 | h = MyConfigHandler() 16 | assert h._meta.interface == 'config' 17 | assert h._meta.label == 'my_config_handler' 18 | 19 | 20 | # app functionality and coverage tests 21 | 22 | def test_get_dict(): 23 | with TestApp() as app: 24 | assert isinstance(app.config.get_dict(), dict) 25 | _config = app.config.get_dict() 26 | assert _config['log.logging']['level'] == 'INFO' 27 | 28 | 29 | def test_env_var_override(): 30 | with TestApp(config_section='testapp') as app: 31 | app.config.set('testapp', 'foo', 'bar') 32 | 33 | env_var = "TESTAPP_FOO" 34 | assert app.config.get('testapp', 'foo') == 'bar' 35 | section_dict = app.config.get_section_dict('testapp') 36 | assert section_dict['foo'] == 'bar' 37 | 38 | os.environ[env_var] = 'not-bar' 39 | assert app.config.get('testapp', 'foo') == 'not-bar' 40 | section_dict = app.config.get_section_dict('testapp') 41 | assert section_dict['foo'] == 'not-bar' 42 | 43 | # do again but in another config namespace 44 | 45 | app.config.add_section('dummy') 46 | app.config.set('dummy', 'foo', 'bar') 47 | 48 | env_var = "TESTAPP_DUMMY_FOO" 49 | assert app.config.get('dummy', 'foo') == 'bar' 50 | section_dict = app.config.get_section_dict('dummy') 51 | assert section_dict['foo'] == 'bar' 52 | 53 | os.environ[env_var] = 'dummy-not-bar' 54 | assert app.config.get('dummy', 'foo') == 'dummy-not-bar' 55 | section_dict = app.config.get_section_dict('dummy') 56 | assert section_dict['foo'] == 'dummy-not-bar' 57 | 58 | # issue/590 - don't sub underscores 59 | 60 | app.config.set('testapp', '__foo__', 'bar') 61 | env_var = "TESTAPP___FOO__" 62 | 63 | os.environ[env_var] = 'not-bar' 64 | assert app.config.get('testapp', '__foo__') == 'not-bar' 65 | section_dict = app.config.get_section_dict('testapp') 66 | assert section_dict['__foo__'] == 'not-bar' 67 | 68 | 69 | def test_get_boolean(): 70 | with TestApp(config_section='testapp') as app: 71 | app.config.set('testapp', 'foobool', 'true') 72 | assert app.config['testapp'].getboolean('foobool') is True 73 | 74 | app.config.set('testapp', 'foobool', 'no') 75 | assert app.config['testapp'].getboolean('foobool') is False 76 | 77 | os.environ['TESTAPP_FOOBOOL'] = '1' 78 | assert app.config['testapp'].getboolean('foobool') is True 79 | -------------------------------------------------------------------------------- /tests/ext/test_ext_daemon.py: -------------------------------------------------------------------------------- 1 | # NOTE: A large portion of ext_daemon is tested, but not included in 2 | # Coverage report because nose/pytest/coverage lose sight of things after the 3 | # sub-process is forked. 4 | 5 | import os 6 | from unittest.mock import patch 7 | from pytest import raises 8 | from cement.core.foundation import TestApp 9 | from cement.core.exc import FrameworkError 10 | from cement.ext import ext_daemon 11 | 12 | 13 | class FakeUser(object): 14 | def __init__(self): 15 | self.pw_uid = 'BogusId' 16 | self.pw_dir = 'BogusDir' 17 | 18 | 19 | class FakeGroup(object): 20 | def __init__(self): 21 | self.gr_gid = 'BogusGroupId' 22 | 23 | 24 | @patch('os.chdir') 25 | @patch('os.setgid') 26 | @patch('os.setuid') 27 | def test_switch(set_uid, set_gid, chdir): 28 | assert not set_gid.called 29 | assert not chdir.called 30 | env = ext_daemon.Environment() 31 | env.user = FakeUser() 32 | env.group = FakeGroup() 33 | env.switch() 34 | set_uid.assert_called_once_with('BogusId') 35 | set_gid.assert_called_once_with('BogusGroupId') 36 | assert chdir.called 37 | 38 | 39 | def test_switch_with_pid(tmp): 40 | os.remove(tmp.file) 41 | env = ext_daemon.Environment(pid_file=tmp.file) 42 | env.switch() 43 | assert os.path.exists(tmp.file) 44 | 45 | # reset 46 | env = ext_daemon.Environment() 47 | env.switch() 48 | 49 | 50 | def test_pid_exists(tmp): 51 | with raises(FrameworkError, match="Process already running"): 52 | env = ext_daemon.Environment(pid_file=tmp.file) 53 | env.switch() 54 | 55 | # reset 56 | env = ext_daemon.Environment() 57 | env.switch() 58 | 59 | 60 | def test_bogus_user(rando): 61 | with raises(FrameworkError, match='Daemon user'): 62 | env = ext_daemon.Environment(user='cement_test_user%s' % rando) 63 | 64 | # reset 65 | env = ext_daemon.Environment() 66 | env.switch() 67 | 68 | # @test.raises(exc.FrameworkError) 69 | 70 | 71 | def test_bogus_group(rando): 72 | with raises(FrameworkError, match='Daemon group'): 73 | env = ext_daemon.Environment(group='cement_test_group%s' % rando) 74 | 75 | # reset 76 | env = ext_daemon.Environment() 77 | env.switch() 78 | 79 | 80 | def test_daemon(tmp): 81 | os.remove(tmp.file) 82 | from cement.utils import shell 83 | 84 | # Test in a sub-process to avoid hangup 85 | def target(): 86 | with TestApp(argv=['--daemon'], extensions=['daemon']) as app: 87 | app.config.set('daemon', 'pid_file', tmp.file) 88 | 89 | try: 90 | # FIX ME: Can't daemonize, because nose/pytest lose sight of it 91 | app.daemonize() 92 | app.run() 93 | finally: 94 | app.close() 95 | ext_daemon.cleanup(app) 96 | 97 | p = shell.spawn_process(target) 98 | p.join() 99 | assert p.exitcode == 0 100 | 101 | 102 | def test_daemon_not_passed(): 103 | with TestApp(extensions=['daemon']) as app: 104 | app.config.set('daemon', 'pid_file', None) 105 | 106 | try: 107 | app.run() 108 | finally: 109 | ext_daemon.cleanup(app) 110 | -------------------------------------------------------------------------------- /tests/ext/test_ext_dummy.py: -------------------------------------------------------------------------------- 1 | 2 | from cement.core.foundation import TestApp 3 | from cement.utils.misc import init_defaults 4 | 5 | 6 | def test_dummy_output(): 7 | with TestApp() as app: 8 | app.run() 9 | app.render({'foo': 'bar'}) 10 | assert app.last_rendered == ({'foo': 'bar'}, None) 11 | 12 | 13 | def test_dummy_template(tmp): 14 | with TestApp() as app: 15 | app.run() 16 | 17 | res = app.template.render('{{ foo }}', {'foo': 'bar'}) 18 | assert res is None 19 | 20 | app.template.copy('/path/to/src', '/path/to/dest', {}) 21 | 22 | 23 | def test_dummy_mail(): 24 | with TestApp() as app: 25 | app.run() 26 | res = app.mail.send("Test", 27 | to=['me@localhost'], 28 | from_addr='me@localhost') 29 | assert res 30 | 31 | 32 | def test_dummy_mail_with_subject_prefix(): 33 | defaults = init_defaults('mail.dummy') 34 | defaults['mail.dummy']['subject_prefix'] = 'TEST PREFIX' 35 | 36 | with TestApp(config_defaults=defaults) as app: 37 | app.run() 38 | res = app.mail.send("Test", 39 | to=['me@localhost'], 40 | from_addr='me@localhost', 41 | ) 42 | assert res 43 | -------------------------------------------------------------------------------- /tests/ext/test_ext_jinja2.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import jinja2 4 | from shutil import copyfile 5 | from cement.utils.test import TestApp, raises 6 | from cement.core.exc import FrameworkError 7 | from cement.utils import fs 8 | 9 | 10 | class Jinja2App(TestApp): 11 | class Meta: 12 | extensions = ['jinja2'] 13 | output_handler = 'jinja2' 14 | template_module = 'tests.data.templates' 15 | template_dirs = [] 16 | handlebars_helpers = {} 17 | 18 | 19 | def test_jinja2(rando): 20 | with Jinja2App() as app: 21 | res = app.render(dict(foo=rando), 'test_template.jinja2') 22 | jinja2_res = "foo equals %s\n" % rando 23 | assert res == jinja2_res 24 | 25 | 26 | def test_jinja2_utf8(rando): 27 | with Jinja2App() as app: 28 | res = app.render(dict(foo=rando), 'test_template_utf8.jinja2') 29 | jinja2_res = u"foo est égal à %s\n" % rando 30 | assert res == jinja2_res 31 | 32 | 33 | def test_jinja2_filesystemloader(tmp, rando): 34 | with Jinja2App() as app: 35 | app._meta.template_dirs = [tmp.dir] 36 | 37 | # make sure it doesn't load from the tests directory module regardless 38 | app._meta.template_module = 'some.bogus.module.path' 39 | 40 | tests_dir = os.path.dirname(os.path.dirname(__file__)) 41 | 42 | from_file = fs.join(tests_dir, 'data', 'templates', 43 | 'test_template_parent.jinja2') 44 | to_file = fs.join(tmp.dir, 'test_template_parent.jinja2') 45 | copyfile(from_file, to_file) 46 | 47 | from_file = fs.join(tests_dir, 'data', 'templates', 48 | 'test_template_child.jinja2') 49 | to_file = fs.join(tmp.dir, 'test_template_child.jinja2') 50 | copyfile(from_file, to_file) 51 | 52 | res = app.render(dict(foo=rando), 'test_template_child.jinja2') 53 | jinja2_res = "foo equals %s\n" % rando 54 | assert res == jinja2_res 55 | 56 | 57 | def test_jinja2_packageloader(rando): 58 | with Jinja2App() as app: 59 | app._meta.template_module = 'tests.data.templates' 60 | app._meta.template_dirs = [] 61 | res = app.render(dict(foo=rando), 'test_template_child.jinja2') 62 | jinja2_res = "foo equals %s\n" % rando 63 | assert res == jinja2_res 64 | 65 | 66 | def test_jinja2_bad_template(): 67 | with Jinja2App() as app: 68 | with raises(jinja2.exceptions.TemplateSyntaxError): 69 | app.render(dict(foo='bar'), 'bad_template.jinja2') 70 | 71 | 72 | def test_jinja2_nonexistent_template(): 73 | with Jinja2App() as app: 74 | msg = "Could not locate template: missing_template.jinja2" 75 | with raises(FrameworkError, match=msg): 76 | app.render(dict(foo='bar'), 'missing_template.jinja2') 77 | 78 | 79 | def test_jinja2_none_template(): 80 | with Jinja2App() as app: 81 | msg = "Invalid template path 'None'." 82 | with raises(FrameworkError, match=msg): 83 | app.render(dict(foo='bar'), None) 84 | 85 | 86 | def test_jinja2_bad_module(): 87 | with Jinja2App() as app: 88 | msg = "Could not locate template: bad_template.jinja2" 89 | with raises(FrameworkError, match=msg): 90 | app._meta.template_module = 'this_is_a_bogus_module' 91 | app.render(dict(foo='bar'), 'bad_template.jinja2') 92 | -------------------------------------------------------------------------------- /tests/ext/test_ext_json.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from unittest.mock import patch 4 | from cement.utils.test import TestApp 5 | from cement.utils import fs 6 | 7 | CONFIG_PARSED = dict( 8 | section=dict( 9 | subsection=dict( 10 | list=['item1', 'item2', 'item3', 'item4'], 11 | key='value'), 12 | key1='ok1', 13 | key2='ok2', 14 | ), 15 | ) 16 | 17 | 18 | CONFIG = fs.join(os.path.dirname(__file__), '..', 19 | 'data', 'config', 'config.json') 20 | 21 | 22 | class JsonApp(TestApp): 23 | class Meta: 24 | extensions = ['json'] 25 | output_handler = 'json' 26 | config_handler = 'json' 27 | config_files = [CONFIG] 28 | argv = ['-o', 'json'] 29 | meta_defaults = {'output.json': {'overridable': True}} 30 | 31 | 32 | def test_json(): 33 | with JsonApp() as app: 34 | app.run() 35 | res = app.render(dict(foo='bar')) 36 | json_res = json.dumps(dict(foo='bar')) 37 | assert res == json_res 38 | 39 | 40 | def test_has_section(): 41 | with JsonApp() as app: 42 | assert app.config.has_section('section') 43 | 44 | 45 | def test_keys(): 46 | with JsonApp() as app: 47 | assert 'subsection' in app.config.keys('section') 48 | 49 | 50 | @patch('cement.ext.ext_json.JsonConfigHandler._parse_file') 51 | def test_parse_file_bad_path(parser): 52 | with JsonApp(config_files=['./some_bogus_path']): 53 | assert not parser.called 54 | 55 | 56 | def test_parse_file(): 57 | with JsonApp() as app: 58 | assert app.config.get('section', 'key1') == 'ok1' 59 | assert app.config.get_section_dict('section') == \ 60 | CONFIG_PARSED['section'] 61 | 62 | 63 | def test_get_dict(): 64 | with JsonApp() as app: 65 | _config = app.config.get_dict() 66 | assert _config['log.logging']['level'] == 'INFO' 67 | -------------------------------------------------------------------------------- /tests/ext/test_ext_memcached.py: -------------------------------------------------------------------------------- 1 | import os 2 | from time import sleep 3 | from cement.utils.test import TestApp 4 | from cement.utils.misc import init_defaults 5 | 6 | if 'MEMCACHED_HOST' in os.environ.keys(): 7 | memcached_host = os.environ['MEMCACHED_HOST'] 8 | else: 9 | memcached_host = 'localhost' 10 | 11 | 12 | defaults = init_defaults('cache.memcached') 13 | defaults['cache.memcached']['hosts'] = [ 14 | memcached_host, 15 | memcached_host 16 | ] 17 | 18 | 19 | class MemcachedApp(TestApp): 20 | class Meta: 21 | extensions = ['memcached'] 22 | cache_handler = 'memcached' 23 | config_defaults = defaults 24 | 25 | 26 | def test_memcache_list_type_config(): 27 | with MemcachedApp() as app: 28 | assert app.config.get('cache.memcached', 'hosts') == \ 29 | [memcached_host, memcached_host] 30 | 31 | 32 | def test_memcache_str_type_config(): 33 | defaults = init_defaults('tests', 'cache.memcached') 34 | defaults['cache.memcached']['hosts'] = "%s,%s" % (memcached_host, 35 | memcached_host) 36 | with MemcachedApp(config_defaults=defaults) as app: 37 | assert app.config.get('cache.memcached', 'hosts') == \ 38 | [memcached_host, memcached_host] 39 | 40 | 41 | def test_memcached_set(key): 42 | with MemcachedApp() as app: 43 | app.cache.set(key, 1001) 44 | assert app.cache.get(key) == 1001 45 | app.cache.delete(key) 46 | 47 | 48 | def test_memcached_get(key): 49 | with MemcachedApp() as app: 50 | # get empty value 51 | app.cache.delete(key) 52 | assert app.cache.get(key) is None 53 | 54 | # get empty value with fallback 55 | assert app.cache.get(key, 1234) == 1234 56 | 57 | 58 | def test_memcached_delete(key): 59 | with MemcachedApp() as app: 60 | # When deleting a key that doesn't exist 61 | app.cache.delete(key) 62 | 63 | # When deleting a key that does exist 64 | app.cache.set(key, 4321) 65 | app.cache.delete(key) 66 | assert app.cache.get(key) is None 67 | 68 | 69 | def test_memcached_purge(key): 70 | with MemcachedApp() as app: 71 | app.cache.set(key, 1002) 72 | app.cache.purge() 73 | assert app.cache.get(key) is None 74 | 75 | 76 | def test_memcache_expire(key): 77 | with MemcachedApp() as app: 78 | app.cache.set(key, 1003, time=2) 79 | sleep(3) 80 | assert app.cache.get(key) is None 81 | -------------------------------------------------------------------------------- /tests/ext/test_ext_mustache.py: -------------------------------------------------------------------------------- 1 | 2 | from cement.core.foundation import TestApp 3 | from cement.utils.test import raises 4 | from cement.core.exc import FrameworkError 5 | 6 | 7 | class MustacheApp(TestApp): 8 | class Meta: 9 | extensions = ['mustache'] 10 | output_handler = 'mustache' 11 | template_module = 'tests.data.templates' 12 | # template_dirs = [] 13 | 14 | 15 | def test_mustache(rando): 16 | with MustacheApp() as app: 17 | res = app.render(dict(foo=rando), 'test_template.mustache') 18 | mustache_res = "foo equals %s\n" % rando 19 | assert res == mustache_res 20 | 21 | 22 | def test_mustache_partials(rando): 23 | with MustacheApp() as app: 24 | res = app.render(dict(foo=rando), 'test_base_template.mustache') 25 | mustache_res = "Inside partial > foo equals %s\n" % rando 26 | assert res == mustache_res 27 | 28 | # derks@20180116: FIXME > Mustache is no longer raising a SyntaxError? 29 | 30 | # def test_mustache_bad_template(): 31 | # with MustacheApp() as app: 32 | # app.render(dict(foo='bar'), 'bad_template.mustache') 33 | 34 | 35 | def test_mustache_nonexistent_template(): 36 | with MustacheApp() as app: 37 | msg = "Could not locate template: missing_template.mustache" 38 | with raises(FrameworkError, match=msg): 39 | app.render(dict(foo='bar'), 'missing_template.mustache') 40 | 41 | 42 | def test_mustache_none_template(): 43 | with MustacheApp() as app: 44 | msg = "Invalid template path 'None'." 45 | with raises(FrameworkError, match=msg): 46 | app.render(dict(foo='bar'), None) 47 | 48 | 49 | def test_mustache_bad_module(): 50 | with MustacheApp() as app: 51 | msg = "Could not locate template: bad_template.mustache" 52 | with raises(FrameworkError, match=msg): 53 | app._meta.template_module = 'this_is_a_bogus_module' 54 | app.render(dict(foo='bar'), 'bad_template.mustache') 55 | -------------------------------------------------------------------------------- /tests/ext/test_ext_plugin.py: -------------------------------------------------------------------------------- 1 | 2 | from cement.ext.ext_plugin import CementPluginHandler 3 | 4 | 5 | # module tests 6 | 7 | class TestCementPluginHandler(object): 8 | def test_subclassing(self): 9 | class MyPluginHandler(CementPluginHandler): 10 | class Meta: 11 | label = 'my_plugin_handler' 12 | 13 | h = MyPluginHandler() 14 | assert h._meta.interface == 'plugin' 15 | assert h._meta.label == 'my_plugin_handler' 16 | 17 | 18 | # app functionality and coverage tests 19 | -------------------------------------------------------------------------------- /tests/ext/test_ext_print.py: -------------------------------------------------------------------------------- 1 | 2 | from cement.utils.test import TestApp 3 | 4 | 5 | class PrintApp(TestApp): 6 | class Meta: 7 | extensions = ['print'] 8 | output_handler = 'print' 9 | 10 | 11 | class PrintDictApp(TestApp): 12 | class Meta: 13 | extensions = ['print'] 14 | output_handler = 'print_dict' 15 | 16 | 17 | def test_print_dict_output_handler(): 18 | with PrintDictApp() as app: 19 | app.run() 20 | res = app.render(dict(foo='bar')) 21 | assert res == 'foo: bar\n' 22 | 23 | 24 | def test_print_output_handler(): 25 | with PrintApp() as app: 26 | app.run() 27 | res = app.render(dict(out='this is the foo bar output')) 28 | assert res == 'this is the foo bar output\n' 29 | 30 | # coverage 31 | app.render({}) 32 | 33 | 34 | def test_print(): 35 | with PrintApp() as app: 36 | app.run() 37 | app.print('This is a simple output message') 38 | assert app.last_rendered[1] == 'This is a simple output message\n' 39 | -------------------------------------------------------------------------------- /tests/ext/test_ext_redis.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from time import sleep 4 | from cement.utils.test import TestApp 5 | from cement.utils.misc import init_defaults 6 | 7 | 8 | if 'REDIS_HOST' in os.environ.keys(): 9 | redis_host = os.environ['REDIS_HOST'] 10 | else: 11 | redis_host = 'localhost' 12 | 13 | 14 | defaults = init_defaults('cache.redis') 15 | defaults['cache.redis']['host'] = redis_host 16 | defaults['cache.redis']['port'] = 6379 17 | defaults['cache.redis']['db'] = 0 18 | 19 | 20 | class RedisApp(TestApp): 21 | class Meta: 22 | extensions = ['redis'] 23 | cache_handler = 'redis' 24 | config_defaults = defaults 25 | 26 | 27 | def test_redis_set(key): 28 | with RedisApp() as app: 29 | app.cache.set(key, 1001) 30 | assert int(app.cache.get(key)) == 1001 31 | 32 | 33 | def test_redis_get(key): 34 | with RedisApp() as app: 35 | # get empty value 36 | app.cache.delete(key) 37 | assert app.cache.get(key) is None 38 | 39 | # get empty value with fallback 40 | assert app.cache.get(key, 1234) == 1234 41 | 42 | 43 | def test_redis_delete(key): 44 | with RedisApp() as app: 45 | app.cache.set(key, 1001) 46 | assert int(app.cache.get(key)) == 1001 47 | app.cache.delete(key) 48 | assert app.cache.get(key) is None 49 | 50 | 51 | def test_redis_purge(key): 52 | with RedisApp() as app: 53 | app.cache.set(key, 1002) 54 | app.cache.purge() 55 | assert app.cache.get(key) is None 56 | 57 | 58 | def test_redis_expire(key): 59 | with RedisApp() as app: 60 | app.cache.set(key, 1003, time=2) 61 | sleep(3) 62 | assert app.cache.get(key) is None 63 | -------------------------------------------------------------------------------- /tests/ext/test_ext_scrub.py: -------------------------------------------------------------------------------- 1 | 2 | from cement import init_defaults 3 | from cement.utils.test import TestApp 4 | 5 | 6 | class ScrubApp(TestApp): 7 | class Meta: 8 | extensions = ['scrub', 'print', 'json'] 9 | scrub = [ 10 | ('foo', '$$$'), 11 | ('bar', '***'), 12 | ] 13 | 14 | 15 | def test_scrub(): 16 | with ScrubApp(argv=['--scrub']) as app: 17 | app.run() 18 | app.print('foobar foo bar') 19 | assert app.last_rendered[1] == '$$$*** $$$ ***\n' 20 | 21 | # coverage 22 | assert app.scrub(None) is None 23 | 24 | 25 | def test_argument(): 26 | META = init_defaults('controller.scrub') 27 | META['controller.scrub']['argument_options'] = ['--not-scrub'] 28 | META['controller.scrub']['argument_help'] = 'not scrub' 29 | 30 | class MyScrubApp(ScrubApp): 31 | class Meta: 32 | meta_defaults = META 33 | 34 | with MyScrubApp(argv=['--not-scrub']) as app: 35 | app.run() 36 | app.print('foobar foo bar') 37 | assert app.last_rendered[1] == '$$$*** $$$ ***\n' 38 | -------------------------------------------------------------------------------- /tests/ext/test_ext_tabulate.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from cement.utils.test import TestApp 4 | 5 | 6 | class TabulateApp(TestApp): 7 | class Meta: 8 | extensions = ['tabulate'] 9 | output_handler = 'tabulate' 10 | 11 | 12 | def test_tabulate(): 13 | with TabulateApp() as app: 14 | res = app.render([['John', 'Doe']], headers=['FOO', 'BAR']) 15 | assert res.find('FOO') 16 | -------------------------------------------------------------------------------- /tests/ext/test_ext_yaml.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | from unittest.mock import patch 4 | from cement.utils.test import TestApp 5 | from cement.utils import fs 6 | 7 | 8 | CONFIG_PARSED = dict( 9 | section=dict( 10 | subsection=dict( 11 | list=['item1', 'item2', 'item3', 'item4'], 12 | key='value'), 13 | key1='ok1', 14 | key2='ok2', 15 | ), 16 | ) 17 | 18 | CONFIG = fs.join(os.path.dirname(__file__), '..', 19 | 'data', 'config', 'config.yml') 20 | 21 | 22 | class YamlApp(TestApp): 23 | class Meta: 24 | extensions = ['yaml'] 25 | output_handler = 'yaml' 26 | config_handler = 'yaml' 27 | config_files = [CONFIG] 28 | argv = ['-o', 'yaml'] 29 | meta_defaults = {'output.yaml': {'overridable': True}} 30 | 31 | 32 | def test_yaml(): 33 | with YamlApp() as app: 34 | app.run() 35 | res = app.render(dict(foo='bar')) 36 | yaml_res = yaml.dump(dict(foo='bar')) 37 | assert res == yaml_res 38 | 39 | 40 | def test_has_section(): 41 | with YamlApp() as app: 42 | assert app.config.has_section('section') 43 | 44 | 45 | def test_keys(): 46 | with YamlApp() as app: 47 | assert 'subsection' in app.config.keys('section') 48 | 49 | 50 | def test_parse_file_bad_path(): 51 | with patch('cement.ext.ext_yaml.YamlConfigHandler._parse_file') as pf: 52 | with YamlApp(config_files=['./some_bogus_path']): 53 | assert not pf.called 54 | 55 | 56 | def test_parse_file(): 57 | with YamlApp() as app: 58 | assert app.config.get('section', 'key1') == 'ok1' 59 | assert app.config.get_section_dict('section') == \ 60 | CONFIG_PARSED['section'] 61 | 62 | 63 | def test_get_dict(): 64 | with YamlApp() as app: 65 | _config = app.config.get_dict() 66 | assert _config['log.logging']['level'] == 'INFO' 67 | -------------------------------------------------------------------------------- /tests/test_init.py: -------------------------------------------------------------------------------- 1 | 2 | # ensure we don't break imports from cement namespace 3 | 4 | 5 | def test_import(): 6 | from cement import App, Controller, ex, init_defaults # noqa: F401 7 | -------------------------------------------------------------------------------- /tests/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/tests/utils/__init__.py -------------------------------------------------------------------------------- /tests/utils/test_fs.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import re 4 | from pytest import raises 5 | from cement.utils import fs 6 | 7 | 8 | def test_abspath(tmp): 9 | path = fs.abspath('.') 10 | assert path.startswith('/') 11 | 12 | 13 | def test_join(tmp, rando): 14 | full_path = os.path.abspath(os.path.join(tmp.dir, rando)) 15 | assert fs.join(tmp.dir, rando) == full_path 16 | 17 | 18 | def test_join_exists(tmp, rando): 19 | full_path = os.path.abspath(os.path.join(tmp.dir, rando)) 20 | 21 | res = fs.join_exists(tmp.dir, rando) 22 | assert res[0] == full_path 23 | assert res[1] is False 24 | 25 | with open(full_path, 'w') as f: 26 | f.write('data') 27 | 28 | res = fs.join_exists(tmp.dir, rando) 29 | assert res[1] is True 30 | 31 | 32 | def test_ensure_dir_exists(tmp, rando): 33 | fs.ensure_dir_exists(fs.join(tmp.dir, rando)) 34 | assert os.path.exists(fs.join(tmp.dir, rando)) 35 | 36 | with raises(AssertionError, match='(.*)exists but is not a directory(.*)'): 37 | fs.ensure_dir_exists(tmp.file) 38 | 39 | 40 | def test_ensure_parent_dir_exists(tmp, rando): 41 | fs.ensure_parent_dir_exists(fs.join(tmp.dir, 'parent', rando)) 42 | assert os.path.exists(fs.join(tmp.dir, 'parent')) 43 | 44 | 45 | def test_tmp(tmp, rando): 46 | t1 = fs.Tmp() 47 | assert os.path.exists(t1.dir) 48 | assert os.path.exists(t1.file) 49 | 50 | with fs.Tmp() as t2: 51 | pass 52 | 53 | assert not os.path.exists(t2.dir) 54 | assert not os.path.exists(t2.file) 55 | 56 | 57 | def test_backup(tmp): 58 | bkfile = fs.backup(tmp.file) 59 | assert "%s.bak" % os.path.basename(tmp.file) == os.path.basename(bkfile) 60 | 61 | bkfile = fs.backup(tmp.file) 62 | assert "%s.bak.0" % os.path.basename(tmp.file) == os.path.basename(bkfile) 63 | 64 | bkfile = fs.backup(tmp.file) 65 | assert "%s.bak.1" % os.path.basename(tmp.file) == os.path.basename(bkfile) 66 | 67 | bkdir = fs.backup(tmp.dir) 68 | assert "%s.bak" % os.path.basename(tmp.dir) == os.path.basename(bkdir) 69 | 70 | assert fs.backup('someboguspath') is None 71 | 72 | 73 | def test_backup_dir_trailing_slash(tmp): 74 | # https://github.com/datafolklabs/cement/issues/610 75 | bkdir = fs.backup("%s/" % tmp.dir) 76 | assert "%s.bak" % os.path.basename(tmp.dir) == os.path.basename(bkdir) 77 | 78 | 79 | def test_backup_timestamp(tmp): 80 | bkfile = fs.backup(tmp.file, timestamp=True) 81 | assert re.match(r'(.*).bak-[\d]+-[\d]+-[\d]+(.*)', bkfile) # noqa: W605 82 | -------------------------------------------------------------------------------- /tests/utils/test_version.py: -------------------------------------------------------------------------------- 1 | 2 | from cement.utils import version 3 | 4 | 5 | def test_get_version(): 6 | ver = version.get_version() 7 | assert ver.startswith('3.0') 8 | 9 | ver = version.get_version((2, 1, 1, 'alpha', 1)) 10 | assert ver == '2.1.1a1' 11 | 12 | changeset = version.get_git_changeset() 13 | ver = version.get_version((2, 1, 1, 'alpha', 0)) 14 | assert ver == f'2.1.1.dev{changeset}' 15 | 16 | ver = version.get_version((2, 1, 2, 'beta', 2)) 17 | assert ver == '2.1.2b2' 18 | 19 | ver = version.get_version((2, 1, 2, 'rc', 3)) 20 | assert ver == '2.1.2c3' 21 | 22 | ver = version.get_version((2, 1, 3, 'final', 0)) 23 | assert ver == '2.1.3' 24 | -------------------------------------------------------------------------------- /tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datafolklabs/cement/9df6b3a3d3d1e044bc38377337d3380d013668b8/tmp/.gitkeep --------------------------------------------------------------------------------