├── .coveragerc ├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── CHANGELOG ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── TODO ├── codecov.yml ├── decorating ├── __init__.py ├── animation.py ├── asciiart.py ├── base.py ├── color.py ├── debugging.py ├── decorator.py ├── general.py ├── monitor.py └── stream.py ├── docs ├── Makefile ├── requirements.txt └── source │ ├── conf.py │ ├── decorating.rst │ ├── description.rst │ └── index.rst ├── requirements.txt ├── setup.py └── tests ├── __main__.py ├── test_animation.py ├── test_color.py ├── test_debugging.py ├── test_decorator.py ├── test_general.py └── test_monitor.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | exclude_lines = 3 | pragma: no cover 4 | def __repr__ 5 | raise AssertionError 6 | raise NotImplementedError 7 | if __name__ == .__main__.: 8 | unittest.main() 9 | pass 10 | def __getattr__(self, attr): 11 | 12 | [run] 13 | omit = 14 | # omit anything in a .local directory anywhere 15 | */.local/* 16 | # omit everything in /usr 17 | /usr/* 18 | # omit this single file 19 | utils/tirefire.py 20 | setup.py 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | # Virtual env 61 | *.env -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | - repo: git://github.com/pre-commit/pre-commit-hooks 2 | sha: adbb569fe9a64ad9bce3b53a77f1bc39ef31f682 3 | hooks: 4 | - id: trailing-whitespace 5 | - id: autopep8-wrapper 6 | - id: check-added-large-files 7 | - id: check-merge-conflict 8 | - id: flake8 9 | - id: requirements-txt-fixer 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | - "3.5" 5 | - "3.4" 6 | - "2.7" 7 | 8 | sudo: true 9 | 10 | before_install: 11 | - pip install coverage 12 | - pip install nose2 'nose2[coverage_plugin]>=0.6.5' 13 | 14 | install: 15 | - python setup.py install 16 | 17 | script: nose2 --with-coverage 18 | 19 | after_success: 20 | - bash <(curl -s https://codecov.io/bash) 21 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | - v0.1-alpha 2 | * add a animated decorator for slow functions 3 | - v0.1.1-alpha 4 | * add optional argument for decorator animated, more descriptive messages 5 | - v0.2-beta 6 | * support context manager for decorator @animated 7 | * add basics decorators: cache, count_time, debug, counter and 8 | * handle sigint signal when user send ctrl+c (stop thread and animation) for animated 9 | * for the complex animated decorator, use a class instead a simple function 10 | * refactoring the _spinner function for more clearness 11 | * added tests for the general.py, the basic decorators 12 | - v0.2.1-beta 13 | * use animated as class instead a function wrapper (equal to AnimatedDecorator) 14 | * fixed tests for count_time decorator, use sleep to avoid time almost 0 15 | * remove requirements-dev because is useless and the build was breaking with this 16 | - v0.3 17 | * closes #2: fix usage of function without args 18 | * add support for nested context managers 19 | * add inital tests for animated decorator, whose will be more hard to break the things 20 | * correct setup of CI with coverage using codecov (96% now!) 21 | * fix problem with logging in animated.py to not progagate the logging when is desactivated (affects ryukinix/mal) 22 | * in nested @animated executions, correct handle animation context 23 | - v0.4 24 | * general refactoring of lib on a better idiomatic way 25 | * added more asciiarts on asciiart.py 26 | * added the awesome @writing decorator 27 | * wrote the docstrings for all functions and modules (in a simple way, BTW) 28 | * added stream.py as colection of streams 29 | * added a base class for Decorator, useful for new decorators 30 | * split debugging decorators (debug, counter, count_time) on debugging.py 31 | * fix the problem of double-lines! beauty use of @animated decorator without pain of internal prints 32 | * added the decorator base class 33 | * added the @writing decorator on animation.py 34 | * added new tests from hell: color, decorator and review of debugging. 35 | * added .coveragerc in the repostiory based on you own specification. 36 | - v0.5 37 | * add autoreset param to colorize for win32 plataform 38 | * add fill with spaces & write backspaces methods on stream.Animation 39 | * refactored animation.space_wave function, we can pass now a sequence of frames 40 | * fixed misspeling on LOL-zone of decorating.decorator 41 | * updated all header of files based a new pattern 42 | - v0.5.1 43 | * fixed bug for keyword arguments on decorating.debugging.debug 44 | * add more tests for debug decorator 45 | * simplification of code on base.DecoratorManager & decorator.Decorator 46 | - v0.5.2 47 | * use ANSI escape codes to erase lines, fix weird inverse behavior in some terminals 48 | - v0.6 49 | * add support for Python2.7 50 | * add proper docs using sphinx on decorating.readthedocs.io 51 | - v0.6.1 52 | * add support for disable/enable decorating.animated 53 | * add support for customize spinners and colors of decorating.animated 54 | * implement the decorating.monitor_stdout decorator 55 | * fix tests about __main__.py function 56 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | ## Development install 5 | 6 | I recommend using a virtualenv and installing the package in the `development` 7 | mode. There is a set steps to accomplish that 8 | 9 | ```Bash 10 | git clone https://www.github.com/ryukinix/decorating 11 | cd decorating 12 | virtualenv env && source env/bin/active 13 | make develop 14 | ``` 15 | 16 | The develop mode creates a .egg-info (egg-link) as a symlink in your 17 | standard `site-packages`/`dist-packages` directory. Don't worry with 18 | the `decorating.egg-info`, it's only information for the package egg 19 | to link with your `PYTHONPATH`. For that, the usage is dynamic, you 20 | can modify the code in test on the command line always using absolute 21 | imports in anywhere (like the first example) 22 | 23 | ### Make a feature branch 24 | 25 | You can create a new branch `git checkout -b feature` based on the 26 | `dev` and send a pull-request to me! If you just want to know 27 | something or give me a suggestion, create a new issue! 28 | 29 | ### Before send a PR 30 | 31 | Please make sure that lint code is passing without warning. Run the tests before 32 | sending a pull request. If you add something new, please, create new tests! 33 | 34 | ### Pre-commit (optional) 35 | 36 | You can set up pre-commit to make testing + linting per commit more easy. 37 | In the root of git repository, run these commands after cloning the project: 38 | 39 | ```shell 40 | sudo pip install pre-commit pylint nose2 41 | pre-commit install 42 | ``` 43 | 44 | If you don't know about pre-commit, check 45 | the [pre-commit](http://pre-commit.com) website. 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2017 Manoel Vilela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include requirements.txt 3 | include README.md 4 | include CHANGELOG 5 | exclude MANIFEST.in -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PYTHON = python3 2 | INSTALL = install --single-version-externally-managed --root=/ 3 | DEVELOP = develop 4 | TARGET = setup.py 5 | TEST_DEPLOY = sdist bdist_wheel upload --repository pypitest --sign 6 | TEST_REGISTER = register --repository pypitest 7 | DEPLOY = sdist bdist_wheel upload --repository pypi --sign 8 | REGISTER = register --repository pypi 9 | BUILD_GARBAGE = build/ dist/ 10 | CHECK = check --metadata --restructuredtext --strict 11 | CLEAN = clean 12 | UNINSTALL = --uninstall 13 | BUILD = sdist bdist_wheel 14 | ENVIROMENT = *.egg-info *.env 15 | 16 | all: install 17 | @make clean 18 | 19 | check: 20 | @echo "+===============+" 21 | @echo "| CHECK |" 22 | @echo "+===============+" 23 | $(PYTHON) $(TARGET) $(CHECK) 24 | @echo "ok!" 25 | 26 | clean: 27 | @make clean-build 28 | @make clean-enviroment 29 | 30 | clean-environment: 31 | @echo "+===============+" 32 | @echo "| CLEAN ENV |" 33 | @echo "+===============+" 34 | rm -rfv $(ENVIROMENT) 35 | 36 | clean-build: 37 | @echo "+===============+" 38 | @echo "| CLEAN BUILD |" 39 | @echo "+===============+" 40 | $(PYTHON) $(TARGET) $(CLEAN) 41 | find . -name __pycache__ -or -name *.pyc| xargs rm -rfv; 42 | rm -rfv $(BUILD_GARBAGE) 43 | 44 | install: 45 | @echo "+===============+" 46 | @echo "| INSTALL |" 47 | @echo "+===============+" 48 | $(PYTHON) $(TARGET) $(INSTALL) 49 | 50 | build: 51 | @echo "+===============+" 52 | @echo "| BUILD |" 53 | @echo "+===============+" 54 | $(PYTHON) $(TARGET) $(BUILD) 55 | 56 | 57 | develop: 58 | @echo "+===============+" 59 | @echo "| DEVELOP |" 60 | @echo "+===============+" 61 | $(PYTHON) $(TARGET) $(DEVELOP) 62 | 63 | test-register: 64 | @make check 65 | @echo "+===============+" 66 | @echo "| TEST-REGISTER |" 67 | @echo "+===============+" 68 | $(PYTHON) $(TARGET) $(TEST_REGISTER) 69 | 70 | develop-uninstall: 71 | @echo "+===============+" 72 | @echo "| DEV UNINSTALL |" 73 | @echo "+===============+" 74 | $(PYTHON) $(TARGET) $(DEVELOP) $(UNINSTALL) 75 | @make clean 76 | 77 | test-deploy: 78 | @make check 79 | @echo "+===============+" 80 | @echo "| TEST-DEPLOY |" 81 | @echo "+===============+" 82 | $(PYTHON) $(TARGET) $(TEST_DEPLOY) 83 | 84 | deploy: 85 | @make check 86 | @echo "+===============+" 87 | @echo "| DEPLOY |" 88 | @echo "+===============+" 89 | $(PYTHON) $(TARGET) $(DEPLOY) 90 | 91 | 92 | register: 93 | @make check 94 | @echo "+===============+" 95 | @echo "| REGISTER |" 96 | @echo "+===============+" 97 | $(PYTHON) $(TARGET) $(REGISTER) 98 | 99 | help: 100 | @echo "+=============================================+" 101 | @echo "| H E L P |" 102 | @echo "+=============================================+" 103 | @echo "----------------------------------------------" 104 | @echo "make check:" 105 | @echo " check the build pass on PyPI" 106 | @echo 107 | @echo "make clean:" 108 | @echo " clean the build (build/ __pyache__, sdist/)" 109 | @echo 110 | @echo "make install:" 111 | @echo " install the package in your system" 112 | @echo 113 | @echo "make build:" 114 | @echo " build the package as egg-file" 115 | @echo "make develop:" 116 | @echo " install in develop mode (symlink)" 117 | @echo 118 | @echo "make develop-uninstall:" 119 | @echo " uninstall develop files and clean build" 120 | @echo 121 | @echo "make test-register:" 122 | @echo " test register using the testPyPI server " 123 | @echo 124 | @echo "test-deploy:" 125 | @echo " test deploy using the testPyPI server" 126 | @echo 127 | @echo "deploy:" 128 | @echo " deploy to PyPY" 129 | @echo 130 | @echo "register:" 131 | @echo " register to PyPI" 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Decorating: A Meta Repo To Decorators 2 | ================= 3 | 4 | [![Build Status](https://travis-ci.org/ryukinix/decorating.svg?branch=master)](https://travis-ci.org/ryukinix/decorating) 5 | [![codecov](https://codecov.io/gh/ryukinix/decorating/branch/master/graph/badge.svg)](https://codecov.io/gh/ryukinix/decorating) 6 | [![Requirements Status](https://requires.io/github/ryukinix/decorating/requirements.svg?branch=master)](https://requires.io/github/ryukinix/decorating/requirements/?branch=master) 7 | [![PyPi version](https://img.shields.io/pypi/v/decorating.svg)](https://pypi.python.org/pypi/decorating/) 8 | [![PyPI pyversions](https://img.shields.io/pypi/pyversions/decorating.svg)](https://pypi.python.org/pypi/decorating/) 9 | [![PyPI status](https://img.shields.io/pypi/status/decorating.svg)](https://pypi.python.org/pypi/decorating/) 10 | [![HitCount](https://hitt.herokuapp.com/ryukinix/decorating.svg)](https://github.com/ryukinix/decorating) 11 | 12 | # Abstract 13 | 14 | This project encourages an exploration into the limits of decorators 15 | in `Python`. While decorators might by new to beginners, they are an 16 | extremely useful feature of the language. They can be similar to Lisp 17 | Macros, but without changes to the AST. Great decorators from this 18 | packages are `@animated` and `@writing`. This repository is made from 19 | scratch, just using Python's Standard Library, no dependency! 20 | 21 | 22 | # Examples 23 | 24 | ## Animated 25 | 26 | *Using as decorator and mixed with context-managers* 27 | ![animation](https://i.imgur.com/hjkNvEE.gif) 28 | 29 | *Using with nested context-managers* 30 | ![context-manager](https://i.imgur.com/EeVnDyy.gif) 31 | 32 | 33 | ## Writing 34 | 35 | Another project mine called [MAL] uses the decorating package —- 36 | basically a command line interface for [MyAnimeList]. The decorator 37 | @writing can be used by just adding 3 lines of code! The behavior is a 38 | retro typing-like computer. Check out the awesome effect: 39 | 40 | [![asciicast](https://asciinema.org/a/ctt1rozymvsqmeipc1zrqhsxb.png)](https://asciinema.org/a/ctt1rozymvsqmeipc1zrqhsxb) 41 | 42 | [MAL]: https://www.github.com/ryukinix/mal 43 | [MyAnimeList]: https://myanimelist.net/ 44 | 45 | More examples are covered on my personal blog post about [decorating](http://manoel.tk/decorating). 46 | 47 | # Decorators & Usage 48 | 49 | Currently public decorators on the API of decorators `decorating`: 50 | 51 | * **decorating.debug** 52 | * **decorating.cache** 53 | * **decorating.counter** 54 | * **decorating.count_time** 55 | * **decorating.animated** 56 | * **decorating.writing** 57 | 58 | Mostly decorators has a pretty consistent usage, but for now only `animated` 59 | and `writing` has support to use as `contextmanagers` using the `with` syntax. 60 | 61 | # Installation 62 | 63 | Supported Python versions: 64 | 65 | * Python3.4+ 66 | * Python2.7 67 | 68 | You can install the last release on [PyPI] by calling: 69 | 70 | ```shell 71 | pip install --user decorating 72 | ``` 73 | 74 | If you want get the last development version install directly by the git 75 | repository: 76 | 77 | ```shell 78 | pip install --user git+https://www.github.com/ryukinix/decorating 79 | ``` 80 | 81 | We have a published package on [Arch Linux],which you can install 82 | using your favorite AUR Helper, like `pacaur` or `yaourt`: 83 | 84 | ```shell 85 | yaourt -S python-decorating 86 | ``` 87 | 88 | [Arch Linux]: https://aur.archlinux.org/packages/python-decorating/ 89 | [PyPI]: https://pypi.python.org/pypi/decorating/ 90 | 91 | Though since the version `0.6` we have support for Python2.7, an AUR 92 | package for Python2 was not made yet. Fill a issue if you have 93 | interest on that :). Thanks to [Maxim Kuznetsov] 94 | which implemented the necessary changes to make compatible with Python2! 95 | 96 | [Maxim Kuznetsov]: https://github.com/mkuznets 97 | 98 | 99 | ## License 100 | [![PyPi License](https://img.shields.io/pypi/l/decorating.svg)](https://pypi.python.org/pypi/decorating/) 101 | 102 | [MIT](LICENSE) 103 | 104 | Because good things need to be free. 105 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | # feature request vs tasks to do 2 | ✔ basic setup of the repository tree @done (16-05-17 17:55) v0.1-alpha 3 | ✔ first submit on PyPI @done (16-05-17 18:37) v0.1-alpha 4 | ✔ write a properly readme.md with gifs showing the basic usage @done (16-05-19 12:42) v0.1-alpha 5 | ✔ setup CI configuration with travis @done (16-05-17 22:07) v0.1-alpha 6 | ✔ add a animated decorator for slow functions @done (16-05-18 05:17) 7 | ✔ add a optional argument for decorator descritive, like @animated('message') @done (16-05-18 17:44) v0.1.1-alpha 8 | ✔ support context manager for decorator @animated @done (16-05-19 12:30) v0.1.2-beta 9 | ✔ add basics decorators: cache, time_count, debug e etc @done (16-05-19 12:30) v0.2-beta 10 | ✔ @animted: handle signal when user send ctrl+c, stop thread and animation @done (16-05-19 12:33) v0.2.1-beta 11 | ✔ provide a wheel package @done (16-05-19 12:41) v0.1.2-beta 12 | ✔ support nested context managers @done (16-05-20 03:30) v0.2 13 | ✔ setup useful hooks to avoid linting problems using pre-commit (and other utilities) @done (16-05-20 06:55) 14 | ✔ allow coverage report on travis and badge on readme.md @done (16-05-20 07:03) v0.3 15 | ✔ add tests @done (16-05-20 07:03) v0.3 16 | ✔ animated @done (16-05-20 07:03) v0.3 17 | ✔ cache @done (16-05-19 13:50) v0.3 18 | ✔ count_time @done (16-05-19 13:50) v0.3 19 | ✔ counter @done (16-05-19 13:50) v0.3 20 | ✔ debug @done (16-05-19 13:50) v0.3 21 | ✔ writing @done (16-05-22 07:42) v0.4 22 | ✔ write the docstrings for all functions: @done (16-05-21 08:23) v0.4 23 | ✔ animated & AnimatedDecorator @done (16-05-21 08:23) v0.4 24 | ✔ AnimationStream @done (16-05-21 08:23) v0.4 25 | ✔ cache @done (16-05-21 08:23) v0.4 26 | ✔ count_time @done (16-05-21 08:23) v0.4 27 | ✔ counter @done (16-05-21 08:23) v0.4 28 | ✔ debug @done (16-05-21 08:23) v0.4 29 | ✔ add a linting checker to .travis.yml (using flake8) (use pre-commit hooks instead that) @done (16-05-21 08:23) v0.4 30 | ✔ support @animate print something without breaking the animation @done (16-05-21 19:21) v0.4 31 | ✔ fix problem with the double-newlines added with print() in inside of @animated @done (16-05-22 07:40) v0.4 32 | ✔ add the awesome @writing decorator @done (16-05-22 07:41) 33 | ✔ generate a read-the-docs page using sphinx 34 | ☐ animated: create params for padding function & the spinnners (left and right) 35 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | token: 88f5bc79-2be2-4226-bfbf-d4f6630d66a0 3 | branch: master 4 | bot: null 5 | ci: null 6 | strict_yaml_branch: null 7 | max_report_age: null 8 | notify: 9 | after_n_builds: null 10 | countdown: null 11 | require_ci_to_pass: yes 12 | archive: 13 | uploads: yes 14 | 15 | coverage: 16 | precision: 2 17 | round: down 18 | range: "70...100" 19 | 20 | notify: 21 | slack: 22 | default: 23 | url: null 24 | threshold: null 25 | branches: null 26 | attachments: "sunburst, diff" 27 | only_pulls: null 28 | message: null 29 | flags: null 30 | paths: null 31 | 32 | hipchat: 33 | default: 34 | url: null 35 | notify: no 36 | threshold: null 37 | branches: null 38 | card: yes 39 | only_pulls: null 40 | message: null 41 | flags: null 42 | paths: null 43 | 44 | gitter: 45 | default: 46 | url: null 47 | threshold: null 48 | branches: null 49 | message: null 50 | only_pulls: null 51 | flags: null 52 | paths: null 53 | 54 | webhook: 55 | default: 56 | url: null 57 | threshold: null 58 | branches: null 59 | only_pulls: null 60 | flags: null 61 | paths: null 62 | 63 | irc: 64 | default: 65 | server: null 66 | branches: null 67 | threshold: null 68 | message: null 69 | flags: null 70 | paths: null 71 | 72 | status: 73 | project: 74 | default: 75 | target: auto 76 | threshold: null 77 | branches: null 78 | base: auto 79 | set_pending: yes 80 | if_no_uploads: error 81 | if_not_found: success 82 | if_ci_failed: error 83 | only_pulls: null 84 | flags: null 85 | paths: null 86 | 87 | patch: 88 | default: 89 | target: auto 90 | branches: null 91 | set_pending: yes 92 | base: auto 93 | if_no_uploads: success 94 | if_not_found: success 95 | if_ci_failed: error 96 | only_pulls: null 97 | flags: null 98 | paths: null 99 | 100 | changes: 101 | default: 102 | branches: null 103 | base: auto 104 | set_pending: yes 105 | if_no_uploads: error 106 | if_not_found: success 107 | if_ci_failed: error 108 | only_pulls: null 109 | flags: null 110 | paths: null 111 | flags: 112 | default: 113 | branches: null 114 | ignore: null 115 | 116 | fixes: null 117 | 118 | ignore: null 119 | 120 | comment: 121 | layout: "header, diff, changes, sunburst, uncovered" 122 | branches: null 123 | behavior: default 124 | flags: null 125 | paths: null 126 | -------------------------------------------------------------------------------- /decorating/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | 11 | """ 12 | DECORATING: A MODULE OF DECORATORS FROM HELL 13 | 14 | You have a collection of decorators, like thesexg: 15 | 16 | * animated: create animations on terminal until the result's returns 17 | * cache: returns without reprocess if the give input was already processed 18 | * counter: count the number of times whose the decorated function is called 19 | * debug: when returns, print this pattern: @function(args) -> result 20 | * count_time: count the time of the function decorated did need to return 21 | """ 22 | 23 | from decorating.animation import animated, writing 24 | from decorating.debugging import count_time, counter, debug 25 | from decorating.general import cache 26 | from decorating.monitor import monitor_stdout 27 | 28 | __version__ = '0.6.1' 29 | __author__ = 'Manoel Vilela' 30 | __email__ = 'manoel_vilela@engineer.com' 31 | __url__ = 'https://github.com/ryukinix/decorating' 32 | 33 | 34 | __all__ = [ 35 | 'animated', 36 | 'cache', 37 | 'counter', 38 | 'debug', 39 | 'count_time', 40 | 'writing', 41 | 'monitor_stdout' 42 | ] 43 | -------------------------------------------------------------------------------- /decorating/animation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | # pylint: disable=no-member 11 | # pylint: disable=C0103 12 | # pylint: disable=too-few-public-methods 13 | 14 | """ 15 | 16 | This module was be done to handle the beautiful animation using 17 | the sin function (whose cause a pulse in the stdout). 18 | 19 | Some examples of using is here: 20 | 21 | @animated 22 | def slow(): 23 | heavy_stuff() 24 | 25 | As well with custom messages 26 | @animated('WOOOOW') 27 | def download_the_universe(): 28 | while True: 29 | pass 30 | 31 | with animated('loool'): 32 | stuff_from_hell() 33 | 34 | @writing 35 | def printer(): 36 | lot_of_messages() 37 | 38 | with writing(delay=0.5): 39 | print("L O L => IS NOT THE STUPID GAME LOL, LOL.") 40 | 41 | 42 | """ 43 | from __future__ import unicode_literals 44 | import signal 45 | import sys 46 | import threading 47 | from math import sin 48 | from functools import partial 49 | from itertools import cycle 50 | from . import decorator, color, stream, asciiart 51 | from .general import zip 52 | 53 | 54 | # THIS IS A LOL ZONE 55 | # /\O | _O | O 56 | # /\/ | //|_ | /_ 57 | # /\ | | | |\ 58 | # / \ | /| | / | 59 | # LOL LOL | LLOL | LOLLOL 60 | 61 | 62 | 63 | # HACK: Global variables to customize behavior of spinner 64 | 65 | horizontal = asciiart.WAVE 66 | vertical1 = ''.join(x * 5 for x in asciiart.BRAILY) 67 | vertical2 = asciiart.VPULSE 68 | animation_color = { 69 | 'message': 'red', 70 | 'padding': 'blue', 71 | 'start': 'cyan', 72 | 'end': 'cyan' 73 | } 74 | 75 | 76 | 77 | class SpinnerController(object): 78 | """Variables to controlling the spinner in parallel 79 | 80 | Bias: the initial value of the padding function 81 | is used here because after a animation stop 82 | and other is started in sequence, the padding 83 | for a semantic view need be in the same place. 84 | 85 | running: variable signal-like to stop the thread 86 | on the main loop doing the animation 87 | 88 | message: the actual messaging on the spinner 89 | 90 | stream: the stream to do the animation, needs 91 | implement the AbstractClass stream.Stream 92 | 93 | """ 94 | 95 | bias = 0 96 | running = False 97 | message = '' 98 | stream = stream.Animation(sys.stderr) 99 | fpadding = None 100 | 101 | 102 | class AnimationController(object): 103 | """Used to controlling thread & running 104 | 105 | context: the context level added +1 at each nested 'with' 106 | running: the object running in the actual moment 107 | 108 | """ 109 | context = 0 110 | thread = None 111 | messages = [] 112 | 113 | 114 | def space_wave(phase, amplitude=12, frequency=0.1): 115 | """ 116 | Function: space_wave 117 | Summary: This function is used to generate a wave-like padding 118 | spacement based on the variable lambda 119 | Examples: >>> print('\n'.join(space_wave(x) for x in range(100)) 120 | █ 121 | ███ 122 | ████ 123 | ██████ 124 | ███████ 125 | ████████ 126 | █████████ 127 | ██████████ 128 | ██████████ 129 | ██████████ 130 | ██████████ 131 | ██████████ 132 | ██████████ 133 | █████████ 134 | ████████ 135 | ███████ 136 | █████ 137 | ████ 138 | ██ 139 | █ 140 | 141 | Attributes: 142 | @param (phase): your positive variable, can be a int or float 143 | @param (char) default='█': the char to construct the space_wave 144 | @param (amplitude) default=10: a float/int number to describe 145 | how long is the space_wave max 146 | @param (frequency) default=0.1: the speed of change 147 | Returns: a unique string of a sequence of 'char' 148 | """ 149 | wave = cycle(horizontal) 150 | return ''.join((next(wave) for x in range 151 | (int((amplitude + 1) * abs(sin(frequency * (phase))))))) 152 | 153 | 154 | def _spinner(control): 155 | if not sys.stdout.isatty(): # not send to pipe/redirection 156 | return # pragma: no cover 157 | 158 | colorize_no_reset = partial(color.colorize, autoreset=False) 159 | 160 | template = '{padding} {start} {message} {end}' 161 | iterator = zip(cycle(vertical1), cycle(vertical2)) 162 | for i, (start, end) in enumerate(iterator): 163 | padding = control.fpadding(i + control.bias) 164 | message = '\r' + template.format( 165 | message=colorize_no_reset(control.message, animation_color['message']), 166 | padding=colorize_no_reset(padding, animation_color['padding']), 167 | start=colorize_no_reset(start, animation_color['start']), 168 | end=color.colorize(end, animation_color['end']) 169 | ) 170 | with control.stream.lock: 171 | control.stream.write(message) 172 | if not control.running: 173 | control.bias = i 174 | break 175 | control.stream.erase(message) 176 | 177 | # D 178 | # E 179 | # C 180 | # O 181 | # R 182 | # A 183 | # T 184 | # O 185 | # R 186 | # S 187 | 188 | 189 | # deal with it 190 | class AnimatedDecorator(decorator.Decorator): 191 | 192 | """The animated decorator from hell 193 | 194 | You can use this these way: 195 | 196 | @animated 197 | def slow(): 198 | heavy_stuff() 199 | 200 | As well with custom messages 201 | @animated('WOOOOW') 202 | def download_the_universe(): 203 | while True: 204 | pass 205 | 206 | with animated('loool'): 207 | stuff_from_hell() 208 | """ 209 | 210 | # if nothing is passed, so this is the message 211 | default_message = 'loading' 212 | # to handle various decorated functions 213 | spinner = SpinnerController() 214 | # to know if some instance of this class is running 215 | # and proper handle that, like ctrl + c and exits 216 | animation = AnimationController() 217 | 218 | _enabled = True 219 | 220 | def __init__(self, message=None, fpadding=space_wave): 221 | super(AnimatedDecorator, self).__init__() 222 | self.message = message 223 | self.spinner.fpadding = fpadding 224 | 225 | @property 226 | def enabled(self): 227 | """True if animation is enabled, false otherwise""" 228 | return AnimatedDecorator._enabled 229 | 230 | @enabled.setter 231 | def enabled(self, state): 232 | """Set a state on AnimatedDecorator._enabled""" 233 | AnimatedDecorator._enabled = state 234 | 235 | def start(self, autopush=True): 236 | """Start a new animation instance""" 237 | if self.enabled: 238 | if autopush: 239 | self.push_message(self.message) 240 | self.spinner.message = ' - '.join(self.animation.messages) 241 | if not self.spinner.running: 242 | self.animation.thread = threading.Thread(target=_spinner, 243 | args=(self.spinner,)) 244 | self.spinner.running = True 245 | self.animation.thread.start() 246 | sys.stdout = stream.Clean(sys.stdout, self.spinner.stream) 247 | 248 | @classmethod 249 | def stop(cls): 250 | """Stop the thread animation gracefully and reset_message""" 251 | if AnimatedDecorator._enabled: 252 | if cls.spinner.running: 253 | cls.spinner.running = False 254 | cls.animation.thread.join() 255 | 256 | if any(cls.animation.messages): 257 | cls.pop_message() 258 | 259 | sys.stdout = sys.__stdout__ 260 | 261 | def __enter__(self): 262 | if self.enabled: 263 | self.animation.context += 1 264 | self.start() 265 | 266 | def __exit__(self, *args): 267 | # if the context manager doesn't running yet 268 | if self.enabled: 269 | self.animation.context -= 1 270 | self.pop_message() 271 | if self.animation.context == 0: 272 | self.stop() 273 | else: 274 | self.start(autopush=False) 275 | 276 | @classmethod 277 | def push_message(cls, message): 278 | """Push a new message for the public messages stack""" 279 | return cls.animation.messages.append(message) 280 | 281 | @classmethod 282 | def pop_message(cls): 283 | """Pop a new message (last) from the public message stack""" 284 | return cls.animation.messages.pop(-1) 285 | 286 | @classmethod 287 | def __call__(cls, *args, **kwargs): 288 | obj = super(AnimatedDecorator, cls).__call__(*args, **kwargs) 289 | if any(cls.instances): 290 | last_instance = cls.instances[-1] 291 | last_instance.message = last_instance.auto_message(args) 292 | elif isinstance(obj, cls): 293 | obj.message = obj.auto_message(args) 294 | return obj 295 | 296 | def auto_message(self, args): 297 | """Try guess the message by the args passed 298 | 299 | args: a set of args passed on the wrapper __call__ in 300 | the definition above. 301 | 302 | if the object already have some message (defined in __init__), 303 | we don't change that. If the first arg is a function, so is decorated 304 | without argument, use the func name as the message. 305 | 306 | If not self.message anyway, use the default_message global, 307 | another else use the default self.message already 308 | 309 | """ 310 | if any(args) and callable(args[0]) and not self.message: 311 | return args[0].__name__ 312 | elif not self.message: 313 | return self.default_message 314 | else: 315 | return self.message 316 | 317 | 318 | class WritingDecorator(decorator.Decorator): 319 | 320 | """A writing class context to simulate a delayed stream 321 | 322 | You can do something like that: 323 | 324 | with writing(delay=0.3): 325 | print('LOL!!! This is so awesome!') 326 | 327 | Or, as expected for this lib, using as decorator! 328 | 329 | @writing 330 | def somebody_talking(): 331 | print("Oh man... I'm so sad. Why I even exists?") 332 | print("I don't meeting anybody") 333 | print("I don't want answer my phone") 334 | print("I don't even to live") 335 | print("But dying is so hassle.") 336 | print("I'd wish just disappears.") 337 | 338 | delay: the down speed of writing, more bigger, more slow. 339 | 340 | 341 | """ 342 | 343 | # to handle nested streams 344 | streams = [] 345 | enabled = True 346 | 347 | def __init__(self, delay=0.05): 348 | super(WritingDecorator, self).__init__() 349 | self.stream = stream.Writting(sys.stdout, delay=delay) 350 | if not self.enabled: 351 | self.stream = sys.__stdout__ 352 | 353 | def start(self): 354 | """Activate the TypingStream on stdout""" 355 | self.streams.append(sys.stdout) 356 | sys.stdout = self.stream 357 | 358 | @classmethod 359 | def stop(cls): 360 | """Change back the normal stdout after the end""" 361 | if any(cls.streams): 362 | sys.stdout = cls.streams.pop(-1) 363 | else: 364 | sys.stdout = sys.__stdout__ 365 | 366 | 367 | def _killed(): # pragma: no cover 368 | AnimatedDecorator.stop() 369 | WritingDecorator.stop() 370 | AnimatedDecorator.spinner.stream.dump.close() 371 | raise KeyboardInterrupt 372 | 373 | signal.signal(signal.SIGINT, lambda x, y: _killed()) 374 | 375 | animated = AnimatedDecorator('loading') 376 | writing = WritingDecorator() 377 | 378 | 379 | __all__ = [ 380 | 'animated', 381 | 'writing' 382 | ] 383 | -------------------------------------------------------------------------------- /decorating/asciiart.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | # 11 | # .. .;-:!!>>!!:--;.. . 12 | # . .-:!>7?CO$QQQQ$OC?:--::-..... 13 | # . .-!7?CO$QQQQQ$$$OCCCC?!:--:>>:;;;... 14 | # . .->CCCC>?OO$$OO$$$$$$O$$$O?7>:>C$C7!:-;;. . 15 | # . .!?O$Q$$OOCC?CCO$QQQQQQ$$QQQ$$$OO$QNHH$C?>!:; . 16 | # . .!OQQQHHHQQQQQ$$QQQQHQHQQQQQHQQHHHHHHHNNNHHQ$OC>. . 17 | # . ;CQHHHHHHHHHHHHHHHHHHHHHHHQHHHHHHNNNNNNNNHNHNNHHH$!; 18 | # :OHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHNNNNNNNNNHHHHNNHNNO:-. 19 | # !OHNNNNNHHHHHHHHHHHHHHHHHHHHHHHHNNHHNNNNNNHHHHHHNHNNNH>;;. 20 | # . !OHNHHHHNHHHHHHHHHHHHHHHNNNNNNNHHHNNNNNNNNHNMNNNNHNNNHNN7 . 21 | # -OQNHNNNNNNHNHHNNHHNNNNNNNNNNNNNNHHHNNNNNHHNQ>!>OHNNHNNHHN$; 22 | # .?$HHNNNNNNNNNNHHNHHNNNNNNNNNNNNNNNNNNNNNNHNQ.;>:::CNNHNNHHNQ!. 23 | # . :$HNNNNNNHHNNHHNNNNHNNNNNNNNNNHHNHNNNNNNHHHN? !; . -ONHNNHNO!-.... 24 | # .?QHNNNNNNNNNNNNNNNNNNNNNNNNNHHNNHNHHNNNNNNNNC .;->!:..QNHHHN$!:-;. 25 | # . :$HNNNHNNNNNNNNNNNNNNNNNNNNHHNM$QNHNNHNNNHQQNO :$O>!- CNHHHHN$O7... 26 | # . !QHNNNNNNNNNNNNNNNNNNNHHNNHNNO>;;?:7QNNNQQHQQH! .O7!; :HHHHHNNNH:... 27 | # . !HHNNNNNNNNNNNNNNNNNNNHHHHH$>;.-!7C?O$HOHNNNNO?; -... CMHNNNHQH> ... 28 | # . !HHNNNNNNNNNNNNNNNNNNNHHNN$>77OHNNHHQ7OC$QHHQ>;.. ..;$QOCOQOO!..... 29 | # . !NNNNNNNNNNNNNNNNHHHHNHNNHC$CHO?Q$QQ? ?HQHQ$O$- .. .:??!-. :>- ..... 30 | # .QNHNNNNNNNNNNNNNNNNNHHHN$!:-?; :7!. ;>7NNNHQ7 . .;;;..;:....... 31 | # ONHNNNNNNNNNNNNHO$HHMNNM? . .!HNHQO. ;-.-:;.... 32 | # . 7NHHNHNNNNNNNNHN?:!;>7:!!. .. .. 7NHQQ: . ;;::>:;;..... 33 | # -!HNHHNNNNNNNNNNQ?>:;. . . .QNQQ? . ;>7C?:::......... 34 | # 7NHNNHNNNNNHNQCCO$Q$O:. . ?MHQ$. >QO>!>7;......... 35 | # . .QHNNNNNHHHHHH$QHQNQ$>... . >NHHN7 ;QO!>CC-.......... 36 | # :-$HO$ONHHNHNNHO->CC; .;. . 7NHHH$;?$>7O$:....... .. 37 | # -$:--QHNQN>CMNQ:;--;;;.... .... ?NHNQ!OHCOOQ>;;;..... . 38 | # . 7: ;OH>?;.77- ;;..:- ..... ONHNQCHHHQHQ!--...... .. 39 | # .. . --.;;. . ;O>:; .;........>$NNHNNNHHHH?-.... . .. 40 | # ;.. ......;.;->>:::--;; .;..!>CNNQQNHNHNQ!.... 41 | # . ;:. .......... .;:->? -: .-.!HHHN7>NHHNH>;... . 42 | # .!; ... ........;!ONC>>C$7?O>7QNHNQ?HNNNO:... . 43 | # :C!.;. .... ...;-!?$NNNNNNNNNNNHHNHNNHHNQ7;... .... 44 | # 7$O-.. . ...!$QNNHHHHHHHHHHHHNNHHNNQC-... ...... 45 | # .?Q$:...... ...-$NNNNHHHHNHNNNHHNNNNNQC>;.. ....... 46 | 47 | 48 | """ 49 | This is another LOL-zone 50 | 51 | LOOOOOOOOOOOOOL ART 52 | """ 53 | from __future__ import unicode_literals 54 | 55 | # START 56 | # /\O | _O | O 57 | # /\/ | //|_ | /_ 58 | # /\ | | | |\ 59 | # / \ | /| | / | 60 | # LOL LOL | LLOL | LOLLOL 61 | # -----------+----------+----------- 62 | 63 | 64 | ARROWS = "←↖↑↗→↘↓↙" 65 | BALLS = ".oO@*" 66 | BRAILY = "⣾⣽⣻⢿⡿⣟⣯" 67 | CIRCLE1 = "◐◓◑◒" 68 | CIRCLE2 = "◴◷◶◵" 69 | CYCLE = "|/-\\" 70 | EYES = ["◡◡" "⊙⊙" "◠◠"] 71 | HPULSE = "▉▊▋▌▍▎▏▎▍▌▋▊▉" 72 | PIPES = "┤┘┴└├┌┬┐" 73 | ROTATING = "▖▘▝▗" 74 | SQUARES = "◰◳◲◱" 75 | TRIANGULES = "◢◣◤◥" 76 | VPULSE = '▁▂▃▄▅▆▇▆▅▄▃▁' 77 | WAVE = "⠁⠂⠄⡀⢀⠠⠐⠈" 78 | 79 | 80 | # END 81 | # /\O | _O | O 82 | # /\/ | //|_ | /_ 83 | # /\ | | | |\ 84 | # / \ | /| | / | 85 | # LOL LOL | LLOL | LOLLOL 86 | # -----------+----------+----------- 87 | -------------------------------------------------------------------------------- /decorating/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | # pylint: disable=no-member 11 | # pylint: disable=too-few-public-methods 12 | 13 | 14 | """ 15 | Abstract Classes to do composition by inheterince 16 | and some other utitities from base clases 17 | 18 | * Stream: Abstract Class for implementation of a Stream 19 | 20 | * Decorator: Abstract Class for creating new decorators 21 | 22 | """ 23 | from __future__ import unicode_literals 24 | from abc import abstractmethod, ABCMeta 25 | from .general import with_metaclass 26 | 27 | 28 | class Stream(with_metaclass(ABCMeta)): 29 | """A base class whose is specify a Stream is 30 | 31 | We need at least a stream on init and a 32 | message param on write method 33 | 34 | """ 35 | 36 | @abstractmethod 37 | def __init__(self, stream, **kargs): 38 | pass 39 | 40 | @abstractmethod 41 | def write(self, message, optional=None): 42 | """a write method interfacing sys.stdout or sys.stderr""" 43 | pass 44 | 45 | 46 | class DecoratorManager(with_metaclass(ABCMeta)): 47 | """Decorator-Context-Manager base class to keep easy creating more decorators 48 | 49 | argument: can be empty or a callable object (function or class) 50 | """ 51 | 52 | @abstractmethod 53 | def __call__(self, function): 54 | """Base class to handle all the implementation of decorators""" 55 | pass 56 | 57 | @abstractmethod 58 | def start(self): 59 | """You active here your pre-fucking crazy feature""" 60 | pass 61 | 62 | @abstractmethod 63 | def stop(self): 64 | """You can deactivate any behavior re-writing your method here""" 65 | pass 66 | 67 | def __enter__(self): 68 | """Activated when enter in a context-manager (with keyword)""" 69 | self.start() 70 | 71 | def __exit__(self, *args): 72 | """Triggered when exit from a block of context-manager""" 73 | self.stop() 74 | -------------------------------------------------------------------------------- /decorating/color.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | 11 | """ 12 | Module focused in termcolor operations 13 | 14 | If the exection is not attatched in any tty, 15 | so colored is disabled 16 | """ 17 | from __future__ import unicode_literals 18 | 19 | import sys 20 | 21 | COLORED = True 22 | if not sys.stdout.isatty() or sys.platform == 'win32': 23 | COLORED = False # pragma: no cover 24 | 25 | COLOR_MAP = { 26 | 'brown': '\033[{style};30m', 27 | 'red': '\033[{style};31m', 28 | 'green': '\033[{style};32m', 29 | 'yellow': '\033[{style};33m', 30 | 'blue': '\033[{style};34m', 31 | 'pink': '\033[{style};35m', 32 | 'cyan': '\033[{style};36m', 33 | 'gray': '\033[{style};37m', 34 | 'white': '\033[{style};40m', 35 | 'reset': '\033[00;00m' 36 | } 37 | 38 | STYLE_MAP = { 39 | 'normal': '00', 40 | 'bold': '01', 41 | 'underline': '04', 42 | } 43 | 44 | 45 | def colorize(printable, color, style='normal', autoreset=True): 46 | """Colorize some message with ANSI colors specification 47 | 48 | :param printable: interface whose has __str__ or __repr__ method 49 | :param color: the colors defined in COLOR_MAP to colorize the text 50 | :style: can be 'normal', 'bold' or 'underline' 51 | 52 | :returns: the 'printable' colorized with style 53 | """ 54 | if not COLORED: # disable color 55 | return printable 56 | if color not in COLOR_MAP: 57 | raise RuntimeError('invalid color set, no {}'.format(color)) 58 | 59 | return '{color}{printable}{reset}'.format( 60 | printable=printable, 61 | color=COLOR_MAP[color].format(style=STYLE_MAP[style]), 62 | reset=COLOR_MAP['reset'] if autoreset else '' 63 | ) 64 | -------------------------------------------------------------------------------- /decorating/debugging.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | """ 11 | An collection of usefull decorators for debug 12 | and time evaluation of functions flow 13 | """ 14 | from __future__ import unicode_literals 15 | 16 | from functools import wraps 17 | from time import time 18 | 19 | 20 | def debug(function): 21 | """ 22 | Function: debug 23 | Summary: decorator to debug a function 24 | Examples: at the execution of the function wrapped, 25 | the decorator will allows to print the 26 | input and output of each execution 27 | Attributes: 28 | @param (function): function 29 | Returns: wrapped function 30 | """ 31 | 32 | @wraps(function) 33 | def _wrapper(*args, **kwargs): 34 | result = function(*args, **kwargs) 35 | for key, value in kwargs.items(): 36 | args += tuple(['{}={!r}'.format(key, value)]) 37 | if len(args) == 1: 38 | args = '({})'.format(args[0]) 39 | print('@{0}{1} -> {2}'.format(function.__name__, args, result)) 40 | _wrapper.last_output = [function.__name__, str(args), result] 41 | return result 42 | _wrapper.last_output = [] 43 | return _wrapper 44 | 45 | 46 | def counter(function): 47 | """ 48 | Function: counter 49 | Summary: Decorator to count the number of a function is executed each time 50 | Examples: You can use that to had a progress of heally heavy 51 | computation without progress feedback 52 | Attributes: 53 | @param (function): function 54 | Returns: wrapped function 55 | """ 56 | 57 | @wraps(function) 58 | def _wrapper(*args, **kwargs): 59 | _wrapper.count += 1 60 | res = function(*args, **kwargs) 61 | funcname = function.__name__ 62 | count = _wrapper.count 63 | print("{} has been used: {}x".format(funcname, count)) 64 | return res 65 | _wrapper.count = 0 66 | return _wrapper 67 | 68 | 69 | def count_time(function): 70 | """ 71 | Function: count_time 72 | Summary: get the time to finish a function 73 | print at the end that time to stdout 74 | Examples: 75 | Attributes: 76 | @param (function): function 77 | Returns: wrapped function 78 | """ 79 | @wraps(function) 80 | def _wrapper(*args, **kwargs): 81 | before = time() 82 | result = function(*args, **kwargs) 83 | diff = time() - before 84 | funcname = function.__name__ 85 | print("{!r} func leave it {:.2f} ms to finish".format(funcname, diff)) 86 | _wrapper.time = diff 87 | return result 88 | 89 | _wrapper.time = 0 90 | return _wrapper 91 | -------------------------------------------------------------------------------- /decorating/decorator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | # pylint: disable=no-member 11 | # pylint: disable=too-few-public-methods 12 | 13 | 14 | """ 15 | The base class for creating new Decorators 16 | 17 | * Decorator: A base class for creating new decorators 18 | 19 | """ 20 | from __future__ import unicode_literals 21 | 22 | from functools import wraps 23 | from warnings import warn 24 | from .base import DecoratorManager 25 | 26 | 27 | # A UNINTENDED LOL-ZONE: SORRY FOR THIS 28 | # -----------+----------+----------- 29 | # /\O | _O | O 30 | # /\/ | //|_ | /_ 31 | # /\ | | | |\ 32 | # / \ | /| | / | 33 | # LOL LOL | LLOL | LOLLOL 34 | # -----------+----------+----------- 35 | # FULL FEATURED BLACK MAGICK ENABLED 36 | 37 | 38 | class Decorator(DecoratorManager): 39 | 40 | """Decorator base class to keep easy creating more decorators 41 | 42 | triggers: 43 | self.start 44 | self.stop 45 | 46 | context_manager: 47 | self.__enter__ 48 | self.__exit__ 49 | 50 | Only this is in generall necessary to implement the class you are writing, 51 | like this: 52 | 53 | class Wired(Decorator): 54 | def __init__(self, user='Lain') 55 | self.user = user 56 | def start(self): 57 | self.login() 58 | 59 | def stop(self): 60 | self.logoff() 61 | 62 | def login(self): 63 | print('Welcome to the Wired, {user}!'.format(user=self.user)) 64 | 65 | def logoff(self): 66 | print('Close this world, open the next!'.) 67 | 68 | 69 | 70 | And all the black magic is done for you behind the scenes. In theory, 71 | you can use the decorator in these way: 72 | 73 | @Wired('lain') 74 | def foo(): 75 | pass 76 | 77 | @Wired(argument='banana') 78 | def bar(): 79 | pass 80 | 81 | @Wired 82 | def lain(): 83 | pass 84 | 85 | @Wired() 86 | def death(): 87 | pass 88 | 89 | And all are okay! As well, natively, you have support to use as 90 | context managers. 91 | 92 | So that you can handle that way: 93 | 94 | with Wired: 95 | print("Download the Knight files...") 96 | 97 | with Wired(): 98 | print("Underlying bugs not anymore") 99 | 100 | with Wired("Lerax"): 101 | print("I'm exists?") 102 | 103 | with Wired(user="Lerax"): 104 | print("I don't have the real answer.") 105 | 106 | And all occurs be fine like you thinks this do. 107 | 108 | 109 | """ 110 | 111 | # a map of instances to handle between the various forms 112 | # of using decorators, like @foo() or @foo. 113 | instances = [] 114 | 115 | @classmethod 116 | def __call__(cls, *args, **kwargs): 117 | instance = cls.recreate(*args, **kwargs) 118 | cls.instances.append(instance) 119 | if any(args) and callable(args[0]): # pass a function/class 120 | return instance._over_wrapper(args[0]) 121 | 122 | return instance 123 | 124 | def _over_wrapper(self, function): 125 | @wraps(function) 126 | def _wrapper(*args, **kargs): 127 | self.start() 128 | result = function(*args, **kargs) 129 | self.stop() 130 | return result 131 | return _wrapper 132 | 133 | @classmethod 134 | def default_arguments(cls): 135 | """Returns the available kwargs of the called class""" 136 | func = cls.__init__ 137 | args = func.__code__.co_varnames 138 | defaults = func.__defaults__ 139 | index = -len(defaults) 140 | return {k: v for k, v in zip(args[index:], defaults)} 141 | 142 | @classmethod 143 | def recreate(cls, *args, **kwargs): 144 | """Recreate the class based in your args, multiple uses""" 145 | cls.check_arguments(kwargs) 146 | first_is_callable = True if any(args) and callable(args[0]) else False 147 | signature = cls.default_arguments() 148 | allowed_arguments = {k: v for k, v in kwargs.items() if k in signature} 149 | if (any(allowed_arguments) or any(args)) and not first_is_callable: 150 | if any(args) and not first_is_callable: 151 | return cls(args[0], **allowed_arguments) 152 | elif any(allowed_arguments): 153 | return cls(**allowed_arguments) 154 | 155 | return cls.instances[-1] if any(cls.instances) else cls() 156 | 157 | @classmethod 158 | def check_arguments(cls, passed): 159 | """Put warnings of arguments whose can't be handle by the class""" 160 | defaults = list(cls.default_arguments().keys()) 161 | template = ("Pass arg {argument:!r} in {cname:!r}, can be a typo? " 162 | "Supported key arguments: {defaults}") 163 | fails = [] 164 | for arg in passed: 165 | if arg not in defaults: 166 | warn(template.format(argument=arg, 167 | cname=cls.__name__, 168 | defaults=defaults)) 169 | fails.append(arg) 170 | 171 | return any(fails) 172 | -------------------------------------------------------------------------------- /decorating/general.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | # pylint: disable=redefined-builtin 11 | # pylint: disable=invalid-name 12 | 13 | """ 14 | An collection of usefull decorators for debug 15 | and time evaluation of functions flow 16 | """ 17 | 18 | # stdlib 19 | from functools import wraps 20 | import sys 21 | 22 | PY2 = sys.version_info[0] == 2 23 | PY3 = sys.version_info[0] == 3 24 | 25 | 26 | if PY2: # pragma: no cover 27 | from itertools import izip 28 | zip = izip 29 | else: # pragma: no cover 30 | zip = zip 31 | 32 | 33 | def with_metaclass(meta, *bases): 34 | """Create a base class with a metaclass.""" 35 | # This requires a bit of explanation: the basic idea is to make a dummy 36 | # metaclass for one level of class instantiation that replaces itself with 37 | # the actual metaclass. 38 | 39 | # Copied from `six' library. 40 | # Copyright (c) 2010-2015 Benjamin Peterson 41 | # License: MIT 42 | 43 | class metaclass(meta): 44 | """Dummy metaclass""" 45 | def __new__(cls, name, this_bases, d): 46 | return meta(name, bases, d) 47 | return type.__new__(metaclass, 'temporary_class', (), {}) 48 | 49 | 50 | def cache(function): 51 | """ 52 | Function: cache 53 | Summary: Decorator used to cache the input->output 54 | Examples: An fib memoized executes at O(1) time 55 | instead O(e^n) 56 | Attributes: 57 | @param (function): function 58 | Returns: wrapped function 59 | 60 | TODO: Give support to functions with kwargs 61 | """ 62 | 63 | memory = {} 64 | miss = object() 65 | 66 | @wraps(function) 67 | def _wrapper(*args): 68 | result = memory.get(args, miss) 69 | if result is miss: 70 | _wrapper.call += 1 71 | result = function(*args) 72 | memory[args] = result 73 | return result 74 | _wrapper.call = 0 75 | return _wrapper 76 | -------------------------------------------------------------------------------- /decorating/monitor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | # pylint: disable=no-member 11 | # pylint: disable=C0103 12 | # pylint: disable=too-few-public-methods 13 | 14 | 15 | from __future__ import unicode_literals 16 | from decorating.stream import Unbuffered 17 | from decorating.decorator import Decorator 18 | import sys 19 | 20 | 21 | class MonitorStream(Unbuffered): 22 | 23 | def __init__(self, stream): 24 | self.stream = stream 25 | self.data = [] 26 | 27 | def write(self, message): 28 | self.stream.write(message) 29 | self.data.append(message) 30 | 31 | 32 | class MonitorStdout(Decorator): 33 | 34 | def start(self): 35 | self.stream = MonitorStream(sys.stdout) 36 | sys.stdout = self.stream 37 | 38 | def stop(self): 39 | sys.stdout = sys.__stdout__ 40 | 41 | def clear(self): 42 | self.data = [] 43 | 44 | @property 45 | def data(self): 46 | return self.stream.data 47 | 48 | @data.setter 49 | def data(self, data): 50 | self.stream.data = data 51 | 52 | @classmethod 53 | def __call__(cls, func): 54 | import warnings 55 | warnings.warn(("MonitorStdout doesn't works as decorator. " 56 | "Use it as contextmanager by 'with' syntax instead")) 57 | return func 58 | 59 | 60 | monitor_stdout = MonitorStdout() 61 | 62 | 63 | def test(): # pragma: no cover 64 | with monitor_stdout: 65 | print('test') 66 | 67 | print(monitor_stdout.data) 68 | -------------------------------------------------------------------------------- /decorating/stream.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | # pylint: disable=too-few-public-methods 11 | 12 | """ 13 | 14 | This module have a collection of Streams class 15 | used to implement: 16 | 17 | * Unbuffered(Stream) :: stream wrapper auto flushured 18 | * Animation(Unbuferred) :: stream with erase methods 19 | * Clean(Unbuffered) :: stream with handling paralell conflicts 20 | * Writing(Unbuffered) :: stream for writing delayed typing 21 | 22 | """ 23 | from __future__ import unicode_literals 24 | 25 | import time 26 | import re 27 | from threading import Lock 28 | from decorating.base import Stream 29 | 30 | 31 | class Unbuffered(Stream): 32 | 33 | """Unbuffered whose flush automaticly 34 | 35 | That way we don't need flush after a write. 36 | 37 | """ 38 | 39 | lock = Lock() 40 | 41 | def __init__(self, stream): 42 | super(Unbuffered, self).__init__(stream) 43 | self.stream = stream 44 | 45 | def write(self, message, flush=True): 46 | """ 47 | Function: write 48 | Summary: write method on the default stream 49 | Examples: >>> stream.write('message') 50 | 'message' 51 | Attributes: 52 | @param (message): str-like content to send on stream 53 | @param (flush) default=True: flush the stdout after write 54 | Returns: None 55 | """ 56 | self.stream.write(message) 57 | if flush: 58 | self.stream.flush() 59 | 60 | def __getattr__(self, attr): 61 | return getattr(self.stream, attr) 62 | 63 | 64 | class Animation(Unbuffered): 65 | 66 | """A stream unbuffered whose write & erase at interval 67 | 68 | After you write something, you can easily clean the buffer 69 | and restart the points of the older message. 70 | stream = Animation(stream, delay=0.5) 71 | self.write('message') 72 | 73 | """ 74 | 75 | last_message = '' 76 | ansi_escape = re.compile(r'\x1b[^m]*m') 77 | 78 | def __init__(self, stream, interval=0.05): 79 | super(Animation, self).__init__(stream) 80 | self.interval = interval 81 | 82 | def write(self, message, autoerase=True): 83 | """Send something for stdout and erased after delay""" 84 | super(Animation, self).write(message) 85 | self.last_message = message 86 | if autoerase: 87 | time.sleep(self.interval) 88 | self.erase(message) 89 | 90 | def erase(self, message=None): 91 | """Erase something whose you write before: message""" 92 | if not message: 93 | message = self.last_message 94 | # Move cursor to the beginning of line 95 | super(Animation, self).write("\033[G") 96 | # Erase in line from cursor 97 | super(Animation, self).write("\033[K") 98 | 99 | def __getattr__(self, attr): 100 | return getattr(self.stream, attr) 101 | 102 | 103 | class Clean(Unbuffered): 104 | """A stream wrapper to prepend '\n' in each write 105 | 106 | This is used to not break the animations when he is activated 107 | 108 | So in the start_animation we do: 109 | sys.stdout = Clean(sys.stdout, ) 110 | 111 | In the stop_animation we do: 112 | sys.stdout = sys.__stdout__Whose paralell_stream is a Animation object. 113 | 114 | """ 115 | 116 | def __init__(self, stream, paralell_stream): 117 | super(Clean, self).__init__(stream) 118 | self.paralell_stream = paralell_stream 119 | 120 | def write(self, message, flush=False): 121 | """Write something on the default stream with a prefixed message""" 122 | # this need be threadsafe because the concurrent spinning running on 123 | # the stderr 124 | with self.lock: 125 | self.paralell_stream.erase() 126 | super(Clean, self).write(message, flush) 127 | # for some reason I need to put a '\n' here to correct 128 | # print of the message, if don't put this, the print internal 129 | # during the animation is not printed. 130 | # however, this create a other problem: excess of newlines 131 | 132 | 133 | class Writting(Unbuffered): 134 | 135 | """ 136 | The Writting stream is a delayed stream whose 137 | simulate an user Writting something. 138 | 139 | The base class is the AnimationStream 140 | 141 | 142 | """ 143 | 144 | def __init__(self, stream, delay=0.08): 145 | super(Writting, self).__init__(stream) 146 | self.delay = delay 147 | 148 | def write(self, message, flush=True): 149 | if isinstance(message, bytes): # pragma: no cover 150 | message = message.decode('utf-8') 151 | 152 | """A Writting like write method, delayed at each char""" 153 | for char in message: 154 | time.sleep(self.delay * (4 if char == '\n' else 1)) 155 | super(Writting, self).write(char, flush) 156 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = decorating 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | pandoc 4 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # decorating documentation build configuration file, created by 5 | # sphinx-quickstart on Thu Sep 14 05:55:39 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('./../')) 23 | 24 | import decorating 25 | 26 | 27 | # -- General configuration ------------------------------------------------ 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # 31 | # needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = [ 37 | 'sphinx.ext.autodoc', 38 | 'sphinx.ext.todo', 39 | 'sphinx.ext.coverage', 40 | 'sphinx.ext.viewcode', 41 | 'sphinx.ext.githubpages'] 42 | 43 | # Add any paths that contain templates here, relative to this directory. 44 | templates_path = ['_templates'] 45 | 46 | # The suffix(es) of source filenames. 47 | # You can specify multiple suffix as a list of string: 48 | # 49 | # source_suffix = ['.rst', '.md'] 50 | source_suffix = '.rst' 51 | 52 | # The master toctree document. 53 | master_doc = 'index' 54 | 55 | # General information about the project. 56 | project = 'decorating' 57 | copyright = '2017, Manoel Vilela' 58 | author = 'Manoel Vilela' 59 | 60 | # The version info for the project you're documenting, acts as replacement for 61 | # |version| and |release|, also used in various other places throughout the 62 | # built documents. 63 | # 64 | # The short X.Y version. 65 | version = decorating.__version__ 66 | # The full version, including alpha/beta/rc tags. 67 | release = decorating.__version__ 68 | 69 | # The language for content autogenerated by Sphinx. Refer to documentation 70 | # for a list of supported languages. 71 | # 72 | # This is also used if you do content translation via gettext catalogs. 73 | # Usually you set "language" from the command line for these cases. 74 | language = None 75 | 76 | # List of patterns, relative to source directory, that match files and 77 | # directories to ignore when looking for source files. 78 | # This patterns also effect to html_static_path and html_extra_path 79 | exclude_patterns = [] 80 | 81 | # The name of the Pygments (syntax highlighting) style to use. 82 | pygments_style = 'sphinx' 83 | 84 | # If true, `todo` and `todoList` produce output, else they produce nothing. 85 | todo_include_todos = True 86 | 87 | 88 | # -- Options for HTML output ---------------------------------------------- 89 | 90 | # The theme to use for HTML and HTML Help pages. See the documentation for 91 | # a list of builtin themes. 92 | # 93 | html_theme = 'sphinx_rtd_theme' 94 | 95 | # Theme options are theme-specific and customize the look and feel of a theme 96 | # further. For a list of options available for each theme, see the 97 | # documentation. 98 | # 99 | # html_theme_options = {} 100 | 101 | # Add any paths that contain custom static files (such as style sheets) here, 102 | # relative to this directory. They are copied after the builtin static files, 103 | # so a file named "default.css" will overwrite the builtin "default.css". 104 | html_static_path = ['_static'] 105 | 106 | # Custom sidebar templates, must be a dictionary that maps document names 107 | # to template names. 108 | # 109 | # This is required for the alabaster theme 110 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 111 | html_sidebars = { 112 | '**': [ 113 | 'about.html', 114 | 'navigation.html', 115 | 'relations.html', # needs 'show_related': True theme option to display 116 | 'searchbox.html', 117 | 'donate.html', 118 | ] 119 | } 120 | 121 | 122 | # -- Options for HTMLHelp output ------------------------------------------ 123 | 124 | # Output file base name for HTML help builder. 125 | htmlhelp_basename = 'decoratingdoc' 126 | 127 | 128 | # -- Options for LaTeX output --------------------------------------------- 129 | 130 | latex_elements = { 131 | # The paper size ('letterpaper' or 'a4paper'). 132 | # 133 | # 'papersize': 'letterpaper', 134 | 135 | # The font size ('10pt', '11pt' or '12pt'). 136 | # 137 | # 'pointsize': '10pt', 138 | 139 | # Additional stuff for the LaTeX preamble. 140 | # 141 | # 'preamble': '', 142 | 143 | # Latex figure (float) alignment 144 | # 145 | # 'figure_align': 'htbp', 146 | } 147 | 148 | # Grouping the document tree into LaTeX files. List of tuples 149 | # (source start file, target name, title, 150 | # author, documentclass [howto, manual, or own class]). 151 | latex_documents = [ 152 | (master_doc, 'decorating.tex', 'decorating Documentation', 153 | 'Manoel Vilela', 'manual'), 154 | ] 155 | 156 | 157 | # -- Options for manual page output --------------------------------------- 158 | 159 | # One entry per manual page. List of tuples 160 | # (source start file, name, description, authors, manual section). 161 | man_pages = [ 162 | (master_doc, 'decorating', 'decorating Documentation', 163 | [author], 1) 164 | ] 165 | 166 | 167 | # -- Options for Texinfo output ------------------------------------------- 168 | 169 | # Grouping the document tree into Texinfo files. List of tuples 170 | # (source start file, target name, title, author, 171 | # dir menu entry, description, category) 172 | texinfo_documents = [ 173 | (master_doc, 'decorating', 'decorating Documentation', 174 | author, 'decorating', 'Decorators for Python focused on Animation.', 175 | 'Miscellaneous'), 176 | ] 177 | -------------------------------------------------------------------------------- /docs/source/decorating.rst: -------------------------------------------------------------------------------- 1 | decorating package 2 | ================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | decorating\.animation module 8 | ---------------------------- 9 | 10 | .. automodule:: decorating.animation 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | decorating\.asciiart module 16 | --------------------------- 17 | 18 | .. automodule:: decorating.asciiart 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | decorating\.base module 24 | ----------------------- 25 | 26 | .. automodule:: decorating.base 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | decorating\.color module 32 | ------------------------ 33 | 34 | .. automodule:: decorating.color 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | decorating\.debugging module 40 | ---------------------------- 41 | 42 | .. automodule:: decorating.debugging 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | decorating\.decorator module 48 | ---------------------------- 49 | 50 | .. automodule:: decorating.decorator 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | decorating\.general module 56 | -------------------------- 57 | 58 | .. automodule:: decorating.general 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | decorating\.stream module 64 | ------------------------- 65 | 66 | .. automodule:: decorating.stream 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | 71 | 72 | Module contents 73 | --------------- 74 | 75 | .. automodule:: decorating 76 | :members: 77 | :undoc-members: 78 | :show-inheritance: 79 | -------------------------------------------------------------------------------- /docs/source/description.rst: -------------------------------------------------------------------------------- 1 | Decorating: A Meta Repo To Decorators 2 | ===================================== 3 | 4 | |Build Status| |codecov| |Requirements Status| |PyPi version| |PyPI 5 | pyversions| |PyPI status| |HitCount| 6 | 7 | Abstract 8 | ======== 9 | 10 | This project encourages an exploration into the limits of decorators in 11 | ``Python``. While decorators might by new to beginners, they are an 12 | extremely useful feature of the language. They can be similar to Lisp 13 | Macros, but without changes to the AST. Great decorators from this 14 | packages are ``@animated`` and ``@writing``. This repository is made 15 | from scratch, just using Python's Standard Library, no dependency! 16 | 17 | Examples 18 | ======== 19 | 20 | Animated 21 | -------- 22 | 23 | *Using as decorator and mixed with context-managers* |animation| 24 | 25 | *Using with nested context-managers* |context-manager| 26 | 27 | Writing 28 | ------- 29 | 30 | Another project mine called 31 | `MAL `__ uses the decorating 32 | package —- basically a command line interface for 33 | `MyAnimeList `__. The decorator @writing can 34 | be used by just adding 3 lines of code! The behavior is a retro 35 | typing-like computer. Check out the awesome effect: 36 | 37 | |asciicast| 38 | 39 | More examples are covered on my personal blog post about 40 | `decorating `__. 41 | 42 | Decorators & Usage 43 | ================== 44 | 45 | Currently public decorators on the API of decorators ``decorating``: 46 | 47 | - **decorating.debug** 48 | - **decorating.cache** 49 | - **decorating.counter** 50 | - **decorating.count\_time** 51 | - **decorating.animated** 52 | - **decorating.writing** 53 | 54 | Mostly decorators has a pretty consistent usage, but for now only 55 | ``animated`` and ``writing`` has support to use as ``contextmanagers`` 56 | using the ``with`` syntax. 57 | 58 | Installation 59 | ============ 60 | 61 | Supported Python versions: 62 | 63 | - Python3.4+ 64 | - Python2.7 65 | 66 | You can install the last release on 67 | `PyPI `__ by calling: 68 | 69 | .. code:: shell 70 | 71 | pip install --user decorating 72 | 73 | If you want get the last development version install directly by the git 74 | repository: 75 | 76 | .. code:: shell 77 | 78 | pip install --user git+https://www.github.com/ryukinix/decorating 79 | 80 | We have a published package on `Arch 81 | Linux `__,which 82 | you can install using your favorite AUR Helper, like ``pacaur`` or 83 | ``yaourt``: 84 | 85 | .. code:: shell 86 | 87 | yaourt -S python-decorating 88 | 89 | Though since the version ``0.6`` we have support for Python2.7, an AUR 90 | package for Python2 was not made yet. Fill a issue if you have interest 91 | on that :). Thanks to `Maxim Kuznetsov ` 92 | which implemented the necessary changes to make compatible with Python2! 93 | 94 | License 95 | ------- 96 | 97 | |PyPi License| 98 | 99 | `MIT `__ 100 | 101 | Because good things need to be free. 102 | 103 | .. |Build Status| image:: https://travis-ci.org/ryukinix/decorating.svg?branch=master 104 | :target: https://travis-ci.org/ryukinix/decorating 105 | .. |codecov| image:: https://codecov.io/gh/ryukinix/decorating/branch/master/graph/badge.svg 106 | :target: https://codecov.io/gh/ryukinix/decorating 107 | .. |Requirements Status| image:: https://requires.io/github/ryukinix/decorating/requirements.svg?branch=master 108 | :target: https://requires.io/github/ryukinix/decorating/requirements/?branch=master 109 | .. |PyPi version| image:: https://img.shields.io/pypi/v/decorating.svg 110 | :target: https://pypi.python.org/pypi/decorating/ 111 | .. |PyPI pyversions| image:: https://img.shields.io/pypi/pyversions/decorating.svg 112 | :target: https://pypi.python.org/pypi/decorating/ 113 | .. |PyPI status| image:: https://img.shields.io/pypi/status/decorating.svg 114 | :target: https://pypi.python.org/pypi/decorating/ 115 | .. |HitCount| image:: https://hitt.herokuapp.com/ryukinix/decorating.svg 116 | :target: https://github.com/ryukinix/decorating 117 | .. |animation| image:: https://i.imgur.com/hjkNvEE.gif 118 | .. |context-manager| image:: https://i.imgur.com/EeVnDyy.gif 119 | .. |asciicast| image:: https://asciinema.org/a/ctt1rozymvsqmeipc1zrqhsxb.png 120 | :target: https://asciinema.org/a/ctt1rozymvsqmeipc1zrqhsxb 121 | .. |PyPi License| image:: https://img.shields.io/pypi/l/decorating.svg 122 | :target: https://pypi.python.org/pypi/decorating/ 123 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. decorating documentation master file, created by 2 | sphinx-quickstart on Thu Sep 14 05:55:39 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to decorating's documentation! 7 | ====================================== 8 | 9 | .. toctree:: 10 | :caption: Table of Contents 11 | :maxdepth: 2 12 | 13 | description 14 | decorating 15 | 16 | 17 | 18 | Indices and tables 19 | ================== 20 | 21 | * :ref:`genindex` 22 | * :ref:`modindex` 23 | * :ref:`search` 24 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryukinix/decorating/df78c3f87800205701704c0bc0fb9b6bb908ba7e/requirements.txt -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | 11 | from setuptools import setup, find_packages 12 | from codecs import open # To use a consistent encoding 13 | from os import path 14 | from warnings import warn 15 | import decorating 16 | 17 | here = path.abspath(path.dirname(__file__)) 18 | readme = path.join(here, 'README.md') 19 | 20 | try: 21 | import pypandoc 22 | long_description = pypandoc.convert(readme, 'rst', format='markdown') 23 | except ImportError: 24 | warn("Only-for-developers: you need pypandoc for upload " 25 | "correct reStructuredText into PyPI home page") 26 | # Get the long description from the relevant file 27 | with open(readme, encoding='utf-8') as f: 28 | long_description = f.read() 29 | 30 | 31 | with open('requirements.txt') as f: 32 | install_requires = list(map(str.strip, f.readlines())) 33 | 34 | setup( 35 | name=decorating.__name__, 36 | version=decorating.__version__, 37 | description="A useful collection of decorators (focused in animation)", 38 | long_description=long_description, 39 | classifiers=[ 40 | "Environment :: Console", 41 | "Development Status :: 4 - Beta", 42 | "Topic :: Utilities", 43 | "Operating System :: Unix", 44 | "Programming Language :: Python :: 2.7", 45 | "Programming Language :: Python :: 3", 46 | "Programming Language :: Python :: 3.4", 47 | "Programming Language :: Python :: 3.5", 48 | "Programming Language :: Python :: 3.6" 49 | ], 50 | # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers 51 | keywords='decorating animation decorators decorator', 52 | author=decorating.__author__, 53 | author_email=decorating.__email__, 54 | url=decorating.__url__, 55 | download_url="{u}/archive/v{v}.tar.gz".format(u=decorating.__url__, 56 | v=decorating.__version__), 57 | zip_safe=False, 58 | license='MIT', 59 | packages=find_packages(exclude=['ez_setup', 'examples', 60 | 'tests', 'docs', '__pycache__']), 61 | platforms='unix', 62 | install_requires=install_requires, 63 | extras_require={ 64 | "Requires-Dist": ["pypandoc"] 65 | }, 66 | 67 | entry_points={ # no entry-points yet 68 | # 'console_scripts': [ 69 | # 'decorating = decorating.cli:main' 70 | # ] 71 | }, 72 | ) 73 | -------------------------------------------------------------------------------- /tests/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | 11 | import os 12 | import unittest 13 | tests = unittest.defaultTestLoader.discover(os.path.dirname(__file__)) 14 | suite = unittest.defaultTestLoader.suiteClass(tests) 15 | unittest.TextTestRunner(verbosity=2).run(suite) 16 | -------------------------------------------------------------------------------- /tests/test_animation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | 11 | """ 12 | Tests for the animation module! 13 | 14 | For now covers: 15 | 16 | [x] - AnimatedDecorator @ animated 17 | [x] - WritingDecorator @ writing 18 | [x] - _spacewave (running based on the class above) 19 | [x] - _spinner (running based on the class above) 20 | 21 | """ 22 | 23 | import unittest 24 | import time 25 | from decorating import debug, animated, writing 26 | 27 | 28 | class TestAnimatedDecorator(unittest.TestCase): 29 | 30 | """General test for the class AnimatedDecorator — @animated""" 31 | 32 | args = ('banana', 'choque') 33 | 34 | def test1(self): 35 | """Test Decorator Function With Args""" 36 | @debug 37 | @animated('with args') 38 | def _with_args(*args): 39 | for i in range(10): 40 | print(i) 41 | time.sleep(0.5) 42 | return args 43 | 44 | self.assertEqual(_with_args(*self.args), self.args, 45 | 'need returns the same I/O') 46 | 47 | def test2(self): 48 | """Test Decorator Function Without Args""" 49 | @debug 50 | @animated() 51 | def _without_args(*args): 52 | time.sleep(0.5) 53 | return args 54 | 55 | self.assertEqual(_without_args(*self.args), self.args, 56 | 'need returns the same I/O') 57 | 58 | def test3(self): 59 | """Test Decorator Function Decorated""" 60 | @debug 61 | @animated 62 | def _decorated(*args): 63 | time.sleep(0.5) 64 | return args 65 | 66 | self.assertEqual(_decorated(*self.args), self.args, 67 | 'need returns the same I/O') 68 | 69 | def test4(self): 70 | """Test Decorator With Context Manager""" 71 | @debug 72 | @animated 73 | def _animation(*args): # pylint: disable=unused-argument 74 | time.sleep(1) 75 | 76 | with animated('testing something'): 77 | self.assertIsNone(_animation(*self.args), 78 | 'need returns the same I/O') 79 | 80 | def test5(self): 81 | """Test Decorator With Nested Context Managers""" 82 | @debug 83 | @animated('with args') 84 | def _with_args(*args): 85 | time.sleep(1) 86 | return args 87 | 88 | with animated('layer-01'): 89 | with animated('layer-02'): 90 | with animated('level-03'): 91 | level1 = _with_args(*self.args) 92 | level2 = _with_args(*self.args) 93 | 94 | for level in (level1, level2): 95 | self.assertEqual(level, self.args, 'need returns the same I/O') 96 | 97 | def test6(self): 98 | """Try disable the animation decorated""" 99 | print("Testing the enabled/disabled o animated") 100 | animated.enabled = False 101 | print("animated.enabled = False") 102 | self.test4() 103 | print("animated.enabled = True") 104 | animated.enabled = True 105 | self.test5() 106 | 107 | class TestWritingDecorator(unittest.TestCase): 108 | 109 | """Tests covering the @writing decorator""" 110 | 111 | def test1(self): 112 | """Test writing as simple decorator""" 113 | @writing 114 | def _printer(): 115 | print("Quantidade infinita de dor no rabo") 116 | return True 117 | 118 | self.assertTrue(_printer(), 'need be True') 119 | 120 | def test2(self): 121 | """Test writing as decorator with args""" 122 | @writing(delay=0.05) 123 | def _printer(): 124 | print("Banana doce com pé de feijão") 125 | return True 126 | 127 | self.assertTrue(_printer(), 'need be True') 128 | 129 | @staticmethod 130 | def test3(): 131 | """Test writing as context manager""" 132 | with writing(): 133 | print("LOOOOOL") 134 | 135 | @staticmethod 136 | def test4(): 137 | """Test writing as context manager with args""" 138 | with writing(delay=0.05): 139 | print("WOOOOOW") 140 | 141 | 142 | if __name__ == '__main__': 143 | unittest.main() 144 | -------------------------------------------------------------------------------- /tests/test_color.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | 11 | import unittest 12 | from decorating import color 13 | 14 | 15 | class TestColorize(unittest.TestCase): 16 | 17 | def test_colorize(self): 18 | string = 'lain' 19 | color.COLORED = True 20 | colored_string = color.colorize(string, 'cyan') 21 | 22 | self.assertNotEqual(string, colored_string, "Must be different") 23 | 24 | def test_colorize_disabled(self): 25 | string = 'test' 26 | color.COLORED = False 27 | colored_string = color.colorize(string, 'cyan') 28 | self.assertEqual(string, colored_string, "Disabled; must be the same") 29 | 30 | def test_failing(self): 31 | string = 'test' 32 | color.COLORED = True 33 | fails = False 34 | try: 35 | color.colorize(string, 'sua mae', 'eu e vc') 36 | except RuntimeError: 37 | fails = True 38 | 39 | self.assertTrue(fails, "Must fails with RunTime if fails") 40 | 41 | if __name__ == '__main__': 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /tests/test_debugging.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | 11 | import unittest 12 | from decorating import debug, counter, count_time 13 | 14 | 15 | class TestDebugDecorator(unittest.TestCase): 16 | 17 | def test_debug_output(self): 18 | @debug 19 | def add(x, y): 20 | return x + y 21 | 22 | @debug 23 | def lol(x): 24 | return '?' 25 | 26 | @debug 27 | def lain(wired='suicide'): 28 | return wired 29 | 30 | table_tests = { 31 | 'test1': { 32 | 'func': add, 33 | 'input': [(1, 2), {}], 34 | 'output': ["add", "(1, 2)", 3], 35 | }, 36 | 'test2': { 37 | 'func': lol, 38 | 'input': [(1,), {}], 39 | 'output': ["lol", "(1)", '?'], 40 | }, 41 | 'test3': { 42 | 'func': lain, 43 | 'input': [(), {'wired': 'suicide'}], 44 | 'output': ['lain', "(wired='suicide')", 'suicide'], 45 | }, 46 | } 47 | 48 | for index, test in table_tests.items(): 49 | func = test['func'] 50 | args, kwargs = test['input'] 51 | expected = test['output'] 52 | result = func(*args, **kwargs) 53 | test_message = "Result of call wrong at {!r}".format(index) 54 | self.assertEqual(result, expected[-1], test_message) 55 | self.assertEqual(func.last_output, expected, 56 | "debug doesn't returns correct values") 57 | 58 | 59 | class TestCountTimeDecorator(unittest.TestCase): 60 | 61 | def test_count_time_decorator(self): 62 | from time import sleep 63 | 64 | @count_time 65 | def test(x): 66 | sleep(0.01) 67 | return "output" 68 | 69 | for x in range(10): 70 | call = test(x) 71 | self.assertEqual(call, "output", "output incorrect for decorator") 72 | self.assertNotEqual(0, test.time, "execution times must > 0") 73 | 74 | 75 | class TestCounterDecorator(unittest.TestCase): 76 | 77 | def test_counter_decorator(self): 78 | @counter 79 | def lol(): 80 | return 'lol' 81 | 82 | output = lol() 83 | self.assertEqual(output, 'lol', "the output are incorrect") 84 | self.assertEqual(lol.count, 1, ) 85 | 86 | 87 | if __name__ == '__main__': 88 | unittest.main() 89 | -------------------------------------------------------------------------------- /tests/test_decorator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | 11 | """ 12 | These tests cover the basic usage to create new decorators 13 | 14 | using the base class decorator.Decorator easily, only 15 | setuping the self.start() & self.stop() trigers, as well 16 | the optionals __exit__ and __enter__. 17 | 18 | 19 | """ 20 | 21 | import unittest 22 | from decorating import writing 23 | from decorating import decorator 24 | 25 | 26 | class Wired(decorator.Decorator): 27 | 28 | """Wired pre-pos hook connection printer""" 29 | 30 | def __init__(self, user='Lain'): 31 | self.user = user 32 | 33 | def start(self): 34 | """Trigger the start hook""" 35 | self.login() 36 | 37 | def stop(self): 38 | """Trigger the stop hook""" 39 | self.logoff() 40 | 41 | def login(self): 42 | """Login in the wired with the default user""" 43 | print('Welcome to the Wired, {user}!'.format(user=self.user)) 44 | 45 | def logoff(self): 46 | """Exits of this world, open the next""" 47 | print('Close this world, open the next!') 48 | print('Goodbye, {user}...'.format(user=self.user)) 49 | del self.user 50 | 51 | 52 | class TestWiredDecorator(unittest.TestCase): 53 | """Basic tests of DecoratorManager class 54 | 55 | The usage is filled into: 56 | as deco: 57 | * @Wired 58 | * @Wired() 59 | * @Wired(user='Chisa') 60 | as context-manager: 61 | * with Wired: 62 | * with Wired(): 63 | * with Wired(user='Lain') 64 | 65 | Consistency is all!!! 66 | """ 67 | 68 | wired = Wired() 69 | 70 | def test_deco_layer1(self): 71 | """Test decorator with basic usage""" 72 | with writing(0.01): 73 | @self.wired('Chisa') 74 | def _knights(): 75 | with writing(0.03): 76 | print('suiciding...') 77 | 78 | _knights() 79 | 80 | def test_deco_layer2(self): 81 | """Testing using with context-manager""" 82 | with writing(0.05): 83 | with self.wired('Manoel'): 84 | with writing(0.01): 85 | print("I'm losing of my self.") 86 | print("I'm exists, really?") 87 | 88 | def test_deco_layer3(self): 89 | """Mixed tests, nested func-decorated and context-manager""" 90 | @self.wired(user='Rafael From Hell') 91 | def _lain(): 92 | print("FUCK YOURSELF") 93 | print("[#] lambda-shell") 94 | print(("λ- (def open-world (next)\n" 95 | " (eval next))")) 96 | print("λ- (open-world 'next-life)") 97 | 98 | with self.wired("Lerax"): 99 | with writing(0.01): 100 | print("I don't ever exists") 101 | print("But I'm exists.") 102 | print("hacking... hacking...") 103 | 104 | with writing(0.03): 105 | _lain() 106 | 107 | 108 | if __name__ == '__main__': 109 | unittest.main() 110 | -------------------------------------------------------------------------------- /tests/test_general.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2016 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | 11 | 12 | import unittest 13 | 14 | from decorating import cache 15 | 16 | 17 | class TestCacheDecorator(unittest.TestCase): 18 | 19 | """Test for the cache decorator""" 20 | 21 | def test_cache(self): 22 | """Basic test for cache using fibonacci""" 23 | @cache 24 | def _fib(x): 25 | if x < 2: 26 | return x 27 | else: 28 | return _fib(x - 1) + _fib(x - 2) 29 | 30 | # First call gives a call count of 1 31 | self.assertEqual(_fib(1), 1, "output invalid") 32 | self.assertEqual(_fib.call, 1, "initial count wrong") 33 | 34 | # Second call keeps the call count at 1 - the cached value is used 35 | self.assertEqual(_fib(1), 1, "output invalid") 36 | self.assertEqual(_fib.call, 1, 'the second equal count must be 1') 37 | 38 | # Subsequent call with a new value increased the call count 39 | self.assertEqual(_fib(5), 5, "output invalid") 40 | self.assertEqual(_fib.call, 6, 'the other count must be 6') 41 | 42 | 43 | if __name__ == '__main__': 44 | unittest.main() 45 | -------------------------------------------------------------------------------- /tests/test_monitor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright © Manoel Vilela 2017 5 | # 6 | # @project: Decorating 7 | # @author: Manoel Vilela 8 | # @email: manoel_vilela@engineer.com 9 | # 10 | 11 | """ 12 | These tests cover the basic usage of the decorator monitor_stdout 13 | 14 | """ 15 | 16 | import unittest 17 | from decorating import monitor_stdout 18 | 19 | 20 | class TestMonitorStdout(unittest.TestCase): 21 | 22 | def test1(self): 23 | """Test using a context manager""" 24 | test = "Cancer" 25 | expected = ["Cancer", "\n"] 26 | with monitor_stdout: 27 | print(test) 28 | self.assertListEqual(monitor_stdout.data, expected) 29 | 30 | # TODO: fix this test! 31 | # Description: this crash the decorating.decorator.Decorator.__call__ 32 | # procedure! Why??? 33 | # def test2(self): 34 | # """Test using a function decorated""" 35 | # monitor_stdout.clear() 36 | # test = "This!" 37 | # expected = ["This", "\n"] 38 | 39 | # @monitor_stdout() 40 | # def test(): 41 | # print(test) 42 | 43 | # test() 44 | # self.assertListEqual(monitor_stdout.data, expected) 45 | 46 | 47 | if __name__ == '__main__': 48 | unittest.main() 49 | --------------------------------------------------------------------------------