diff --git a/.editorconfig b/.editorconfig index 22fb1f90..77f690c9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,5 +10,9 @@ insert_final_newline = true charset = utf-8 end_of_line = lf +[{*.yml,*.yaml}] +indent_style = space +indent_size = 2 + [Makefile] indent_style = tab diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/deploy-docs.yml similarity index 66% rename from .github/workflows/gh-pages.yml rename to .github/workflows/deploy-docs.yml index 68f1475b..7876651e 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/deploy-docs.yml @@ -8,28 +8,34 @@ on: release: types: [created] branches: - - 'master' + - master jobs: build: - name: "Build docs" + name: Build docs runs-on: ubuntu-latest steps: - - uses: actions/setup-python@v4 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # otherwise, you will failed to push refs to dest repo - - name: "Install runtime dependencies in order to get package metadata" - run: "scripts/install" - - name: "Install deps and build with Sphinx" - run: make docs - - name: "Upload artifacts" - uses: actions/upload-pages-artifact@v1 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Build docs + run: scripts/build-docs.sh + + - name: Upload artifacts + uses: actions/upload-pages-artifact@v3 with: # Upload built docs - path: "./Documentation" + path: "./site" deploy: - name: "Deploy docs" + name: Deploy docs if: github.event_name == 'release' && github.event.action == 'published' needs: build runs-on: ubuntu-latest @@ -42,6 +48,6 @@ jobs: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - - uses: actions/deploy-pages@v1 + - uses: actions/deploy-pages@v4 id: deployment name: "Deploy to GitHub Pages" diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml deleted file mode 100644 index 52fe3ba1..00000000 --- a/.github/workflows/dist.yml +++ /dev/null @@ -1,47 +0,0 @@ -# vim:ts=2:sw=2:et:ai:sts=2 -name: 'Build distribution' - -on: - # Only run when release is created in the master branch - release: - types: [created] - branches: - - 'master' - -jobs: - build: - name: 'Build distributable files' - runs-on: 'ubuntu-latest' - steps: - - uses: actions/checkout@v3 - name: 'Checkout source repository' - with: - fetch-depth: 0 - - - uses: actions/setup-python@v4 - - - name: 'Build sdist and wheel' - run: python3 setup.py sdist bdist_wheel - - - uses: actions/upload-artifact@v2 - name: 'Upload build artifacts' - with: - path: 'dist/*' - - upload_pypi: - name: 'Upload packages' - needs: ['build'] - runs-on: 'ubuntu-latest' - if: github.event_name == 'release' && github.event.action == 'created' - steps: - - uses: actions/download-artifact@v3 - name: 'Download artifacts' - with: - name: 'artifact' - path: 'dist' - - - uses: pypa/gh-action-pypi-publish@release/v1 - name: "Publish package to PyPI" - with: - user: '__token__' - password: '${{ secrets.PYPI_API_TOKEN }}' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..54021005 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,49 @@ +name: Publish Package + +on: + # Only run when release is created in the master branch + release: + types: [created] + branches: + - 'master' + +jobs: + build: + name: Build distributable files + runs-on: 'ubuntu-latest' + steps: + - name: 'Checkout source repository' + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v5 + + - name: Install build dependencies + run: pip install build twine + + - name: 'Build package' + run: scripts/build.sh + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + path: 'dist/*' + + upload_pypi: + name: Upload packages + needs: ['build'] + runs-on: 'ubuntu-latest' + if: github.event_name == 'release' && github.event.action == 'created' + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: artifact + path: dist + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: '__token__' + password: '${{ secrets.PYPI_API_TOKEN }}' diff --git a/.github/workflows/python-package.yml b/.github/workflows/tests.yml similarity index 63% rename from .github/workflows/python-package.yml rename to .github/workflows/tests.yml index fbeb126d..0a5bda48 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/tests.yml @@ -1,7 +1,7 @@ # This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python -name: Run Python tests +name: Python package on: push: @@ -31,24 +31,30 @@ jobs: experimental: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: "actions/setup-python@v4" + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 with: - python-version: "${{ matrix.python-version }}" - cache: "pip" + python-version: ${{ matrix.python-version }} + cache: pip cache-dependency-path: | - requirements/*.txt - requirements/**/*.txt - - name: "Install dependencies" - run: "scripts/install" - - name: "Run linting checks" - run: "scripts/check" - - name: "Run tests" - run: "scripts/tests" - - name: "Enforce coverage" - uses: codecov/codecov-action@v3 + requirements-*.txt + pyproject.toml + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Run linting checks + run: scripts/lint.sh + + - name: Run tests + run: scripts/tests.sh + + - name: Enforce coverage + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 15d6d113..42c8326d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,10 @@ repos: - id: check-yaml - id: check-toml - id: check-added-large-files - + - repo: https://github.com/commitizen-tools/commitizen + rev: v3.18.0 + hooks: + - id: commitizen - repo: https://github.com/charliermarsh/ruff-pre-commit rev: v0.3.0 hooks: diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..b80da178 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ + +## v0.2.0 (2024-03-02) + +## 0.1.0 (2023-01-10) diff --git a/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.md similarity index 98% rename from CODE_OF_CONDUCT.rst rename to CODE_OF_CONDUCT.md index 7141c712..b5d27c3a 100644 --- a/CODE_OF_CONDUCT.rst +++ b/CODE_OF_CONDUCT.md @@ -1,5 +1,4 @@ -Code of Conduct -=============== +# Code of Conduct Everyone interacting in the project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Mode Code of Conduct. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..f1b7210c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,131 @@ +# Contributing + +Contributions to mode are very welcome, feel free to open an issue to propose your ideas. + +To make a contribution please create a pull request. + +## Developing + +First it is recommended to fork this repository into your personal Github account then clone your freshly generated fork locally. + +### Setup environment + +Here are some guidelines to set up your environment: + +```sh +$ cd mode/ +$ python -m venv env # Create python virtual environment, a `env/` has been created +$ source env/bin/active # Activate the environment +(venv) $ which pip # Ensure everything is well configured +/some/directory/mode/env/bin/pip +``` + +### Install project dependencies + +```sh +(venv) $ pip install -r requirements.txt +Obtaining file:///some/directory/mode + Installing build dependencies ... done + Checking if build backend supports build_editable ... done + Getting requirements to build editable ... done + Installing backend dependencies ... done + Preparing editable metadata (pyproject.toml) ... done +Ignoring pre-commit: markers 'python_version < "3.9"' don't match your environment +... +``` + +This project apply some quality rules on code and commit, to enforce them at commit time you should install the [pre-commit](https://pre-commit.com/) hook: + +```sh +(venv) $ pre-commit install +pre-commit installed at .git/hooks/pre-commit +``` + +### Format & lint the code + +You can run the format script to make your change compliant: + +```sh +(venv) $ ./script/format.sh ++ ruff format mode tests +79 files left unchanged ++ ruff check mode tests --fix +``` + +_The script uses [ruff](https://github.com/astral-sh/ruff) & [mypy](https://mypy-lang.org/)._ + +### Run tests + +A script is also available to run them: + +``` +(venv) $ ./scripts/tests.sh ++ pytest tests --cov=mode +Test session starts (platform: linux, Python 3.12.2, pytest 8.1.1, pytest-sugar 1.0.0) +... +``` + +_The script uses [pytest](https://docs.pytest.org/en/8.0.x/contents.html)._ + +### Commit format + +Commit should be formatted following [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). + +You can commit manually and respect the convention or you can use the cli to help you formatting correctly: + +```sh +(venv) $ cz commit +? Select the type of change you are committing docs: Documentation only changes +? What is the scope of this change? (class or file name): (press [enter] to skip) + README +? Write a short and imperative summary of the code changes: (lower case and no period) + correct spelling of README +? Provide additional contextual information about the code changes: (press [enter] to skip) + +? Is this a BREAKING CHANGE? Correlates with MAJOR in SemVer No +? Footer. Information about Breaking Changes and reference issues that this commit closes: (press [enter] to skip) + + +docs(README): correct spelling of README +``` + +## Documentation + +To be able to run the documentation locally you should setup your environment and install dependencies, if not already you can read the two first part of the Developing section. + +```sh +(venv) $ mkdocs serve +INFO - Building documentation... +INFO - Cleaning site directory +INFO - Documentation built in 1.78 seconds +INFO - [19:38:48] Watching paths for changes: 'docs', 'mkdocs.yml' +INFO - [19:38:48] Serving on http://127.0.0.1:8000/ +``` + +Then, you can browse the documentation on http://127.0.0.1:8000. + +## Maintainers + +### Publish a new release + +1. First create a new tag and update changelog + +```sh +(venv) $ ./scripts/bump.sh ++ cz bump --changelog +bump: version 0.2.0 → 0.2.1 +tag to create: 0.2.1 +increment detected: PATCH + +[master b35722f] bump: version 0.2.0 → 0.2.1 + 2 files changed, 2 insertions(+), 1 deletion(-) + +... + +Done! +``` + +!!! note + If this pass it will automatically push the commit and tags to the remote server, you may want to use `cz bump --changelog --dry-run` to check generated changes. + +2. Then after ensuring github pass you could publish the release via the Github interface. (An action will triggered and will publish the package to pypi and documentation to the Github pages). diff --git a/Changelog b/Changelog deleted file mode 100644 index 234e0da0..00000000 --- a/Changelog +++ /dev/null @@ -1,35 +0,0 @@ -.. _changelog: - -================ - Change history -================ - -.. version-0.2.0: - -0.2.0 -===== -:release-date: 2021-10-14 -:release-by: Taybin Rutkin (:github_user:`taybin`) - -- Support python-3.10 - -- format with black and isort - -- add crontab timer from Faust (:github_user:`lqhuang`) - -.. version-0.1.0: - -0.1.0 -===== -:release-date: 2020-12-17 14:00 P.M CET -:release-by: Thomas Sarboni (:github_user:`max-k`) - -- Friendly fork of ask/mode : Initial release - -- Move to new travis-ci.com domain - -- Add tests on Python 3.8.1-3.8.6 - -- Fix broken tests - -- Add Python 3.9 support diff --git a/Makefile b/Makefile deleted file mode 100644 index 58801857..00000000 --- a/Makefile +++ /dev/null @@ -1,147 +0,0 @@ -PROJ ?= mode -PGPIDENT ?= "Faust Security Team" -PYTHON ?= python -PYTEST ?= py.test -PIP ?= pip -GIT ?= git -TOX ?= tox -NOSETESTS ?= nosetests -ICONV ?= iconv -MYPY ?= mypy - -TESTDIR ?= t -README ?= README.rst -README_SRC ?= "docs/templates/readme.txt" -CONTRIBUTING ?= CONTRIBUTING.rst -CONTRIBUTING_SRC ?= "docs/contributing.rst" -COC ?= CODE_OF_CONDUCT.rst -COC_SRC ?= "docs/includes/code-of-conduct.txt" -DOCUMENTATION=Documentation - -all: help - -help: - @echo "docs - Build documentation." - @echo "test-all - Run tests for all supported python versions." - @echo "develop - Install all dependencies into current virtualenv." - @echo "distcheck ---------- - Check distribution for problems." - @echo " test - Run unittests using current python." - @echo " lint ------------ - Check codebase for problems." - @echo " apicheck - Check API reference coverage." - @echo " readmecheck - Check README.rst encoding." - @echo " contribcheck - Check CONTRIBUTING.rst encoding" - @echo " ruff - Check code for syntax and style errors." - @echo "readme - Regenerate README.rst file." - @echo "contrib - Regenerate CONTRIBUTING.rst file" - @echo "coc - Regenerate CODE_OF_CONDUCT.rst file" - @echo "clean-dist --------- - Clean all distribution build artifacts." - @echo " clean-git-force - Remove all uncomitted files." - @echo " clean ------------ - Non-destructive clean" - @echo " clean-pyc - Remove .pyc/__pycache__ files" - @echo " clean-docs - Remove documentation build artifacts." - @echo " clean-build - Remove setup artifacts." - @echo "release - Make PyPI release." - -clean: clean-docs clean-pyc clean-build - -clean-dist: clean clean-git-force - -release: - $(PYTHON) register sdist bdist_wheel upload --sign --identity="$(PGPIDENT)" - -. PHONY: deps-default -deps-default: - $(PIP) install -U -e "." - -. PHONY: deps-docs -deps-docs: - $(PIP) install -U -r requirements-docs.txt - -. PHONY: deps-test -deps-test: - $(PIP) install -U -r requirements-test.txt - -. PHONY: deps-extras -deps-extras: - $(PIP) install -U -r requirements/extras/eventlet.txt - $(PIP) install -U -r requirements/extras/uvloop.txt - -. PHONY: develop -develop: deps-default deps-dist deps-docs deps-test deps-extras - $(PYTHON) develop - -. PHONY: Documentation -Documentation: - mkdocs build - -. PHONY: docs -docs: Documentation - -. PHONE: serve-docs -serve-docs: - mkdocs serve - -clean-docs: - -rm -rf "$(SPHINX_BUILDDIR)" - -ruff: - ruff check . --fix - -lint: ruff apicheck readmecheck - -clean-readme: - -rm -f $(README) - -readmecheck: - $(ICONV) -f ascii -t ascii $(README) >/dev/null - -readme: clean-readme $(README) readmecheck - -clean-contrib: - -rm -f "$(CONTRIBUTING)" - -contrib: clean-contrib $(CONTRIBUTING) - -clean-coc: - -rm -f "$(COC)" - -coc: clean-coc $(COC) - -clean-pyc: - -find . -type f -a \( -name "*.pyc" -o -name "*$$py.class" \) | xargs rm - -find . -type d -name "__pycache__" | xargs rm -r - -removepyc: clean-pyc - -clean-build: - rm -rf build/ dist/ .eggs/ *.egg-info/ .tox/ .coverage cover/ - -clean-git: - $(GIT) clean -xdn - -clean-git-force: - $(GIT) clean -xdf - -test-all: clean-pyc - $(TOX) - -test: - $(PYTEST) . - -cov: - $(PYTEST) -x --cov="$(PROJ)" --cov-report=html - -build: - $(PYTHON) sdist bdist_wheel - -distcheck: lint test clean - -dist: readme contrib clean-dist build - -typecheck: - $(PYTHON) -m $(MYPY) -p $(PROJ) - -.PHONY: requirements -requirements: - $(PIP) install --upgrade pip;\ - $(PIP) install -r requirements.txt diff --git a/README.md b/README.md new file mode 100644 index 00000000..f50b41a7 --- /dev/null +++ b/README.md @@ -0,0 +1,492 @@ +# AsyncIO Services Fork + +

+ + Latest release + + + Coverage + + + BSD License + + + Supported Python versions + +

+ +--- + +**Documentation**: https://faust-streaming.github.io/mode/ + +**Source Code**: https://github.com/faust-streaming/mode + +--- + +## Why the fork + +We have decided to fork the original *Mode* project because there is a critical process of releasing new versions which causes uncertainty in the community. Everybody is welcome to contribute to this *fork*, and you can be added as a maintainer. + +We want to: + +- Ensure continues release +- Code quality +- Support latest Python versions +- Update the documentation + +and more... + +## What is Mode? + +Mode is a very minimal Python library built-on top of AsyncIO that makes +it much easier to use. + +In Mode your program is built out of services that you can start, stop, +restart and supervise. + +A service is just a class: + +```python +class PageViewCache(Service): + redis: Redis = None + + async def on_start(self) -> None: + self.redis = connect_to_redis() + + async def update(self, url: str, n: int = 1) -> int: + return await self.redis.incr(url, n) + + async def get(self, url: str) -> int: + return await self.redis.get(url) +``` + +Services are started, stopped and restarted and have +callbacks for those actions. + +It can start another service: + +```python +class App(Service): + page_view_cache: PageViewCache = None + + async def on_start(self) -> None: + await self.add_runtime_dependency(self.page_view_cache) + + @cached_property + def page_view_cache(self) -> PageViewCache: + return PageViewCache() +``` + +It can include background tasks: + +```python +class PageViewCache(Service): + + @Service.timer(1.0) + async def _update_cache(self) -> None: + self.data = await cache.get('key') +``` + +Services that depends on other services actually form a graph +that you can visualize. + +### Worker + +Mode optionally provides a worker that you can use to start the program, +with support for logging, blocking detection, remote debugging and more. + +To start a worker add this to your program: + + +```python +if __name__ == '__main__': + from mode import Worker + Worker(Service(), loglevel="info").execute_from_commandline() +``` + +Then execute your program to start the worker: + +```log +$ python examples/tutorial.py +[2018-03-27 15:47:12,159: INFO]: [^Worker]: Starting... +[2018-03-27 15:47:12,160: INFO]: [^-AppService]: Starting... +[2018-03-27 15:47:12,160: INFO]: [^--Websockets]: Starting... +STARTING WEBSOCKET SERVER +[2018-03-27 15:47:12,161: INFO]: [^--UserCache]: Starting... +[2018-03-27 15:47:12,161: INFO]: [^--Webserver]: Starting... +[2018-03-27 15:47:12,164: INFO]: [^--Webserver]: Serving on port 8000 +REMOVING EXPIRED USERS +REMOVING EXPIRED USERS +``` + +To stop it hit `Control-c`: + +```log +[2018-03-27 15:55:08,084: INFO]: [^Worker]: Stopping on signal received... +[2018-03-27 15:55:08,084: INFO]: [^Worker]: Stopping... +[2018-03-27 15:55:08,084: INFO]: [^-AppService]: Stopping... +[2018-03-27 15:55:08,084: INFO]: [^--UserCache]: Stopping... +REMOVING EXPIRED USERS +[2018-03-27 15:55:08,085: INFO]: [^Worker]: Gathering service tasks... +[2018-03-27 15:55:08,085: INFO]: [^--UserCache]: -Stopped! +[2018-03-27 15:55:08,085: INFO]: [^--Webserver]: Stopping... +[2018-03-27 15:55:08,085: INFO]: [^Worker]: Gathering all futures... +[2018-03-27 15:55:08,085: INFO]: [^--Webserver]: Closing server +[2018-03-27 15:55:08,086: INFO]: [^--Webserver]: Waiting for server to close handle +[2018-03-27 15:55:08,086: INFO]: [^--Webserver]: Shutting down web application +[2018-03-27 15:55:08,086: INFO]: [^--Webserver]: Waiting for handler to shut down +[2018-03-27 15:55:08,086: INFO]: [^--Webserver]: Cleanup +[2018-03-27 15:55:08,086: INFO]: [^--Webserver]: -Stopped! +[2018-03-27 15:55:08,086: INFO]: [^--Websockets]: Stopping... +[2018-03-27 15:55:08,086: INFO]: [^--Websockets]: -Stopped! +[2018-03-27 15:55:08,087: INFO]: [^-AppService]: -Stopped! +[2018-03-27 15:55:08,087: INFO]: [^Worker]: -Stopped! +``` + +### Beacons + +The `beacon` object that we pass to services keeps track of the services +in a graph. + +They are not strictly required, but can be used to visualize a running +system, for example we can render it as a pretty graph. + +This requires you to have the `pydot` library and GraphViz +installed: + +```sh +$ pip install pydot +``` + +Let's change the app service class to dump the graph to an image +at startup: + +```python +class AppService(Service): + + async def on_start(self) -> None: + print('APP STARTING') + import pydot + import io + o = io.StringIO() + beacon = self.app.beacon.root or self.app.beacon + beacon.as_graph().to_dot(o) + graph, = pydot.graph_from_dot_data(o.getvalue()) + print('WRITING GRAPH TO image.png') + with open('image.png', 'wb') as fh: + fh.write(graph.create_png()) +``` + +## Creating a Service + +To define a service, simply subclass and fill in the methods +to do stuff as the service is started/stopped etc.: + + +```python +class MyService(Service): + + async def on_start(self) -> None: + print('Im starting now') + + async def on_started(self) -> None: + print('Im ready') + + async def on_stop(self) -> None: + print('Im stopping now') +``` + +To start the service, call `await service.start()`: + +```python +await service.start() +``` + +Or you can use `mode.Worker` (or a subclass of this) to start your +services-based asyncio program from the console: + +```python +if __name__ == '__main__': + import mode + worker = mode.Worker( + MyService(), + loglevel='INFO', + logfile=None, + daemon=False, + ) + worker.execute_from_commandline() +``` + +## It's a Graph! + +Services can start other services, coroutines, and background tasks. + +1) Starting other services using `add_dependency`: + +```python +class MyService(Service): + def __post_init__(self) -> None: + self.add_dependency(OtherService(loop=self.loop)) +``` + +1) Start a list of services using `on_init_dependencies`: + +```python +class MyService(Service): + + def on_init_dependencies(self) -> None: + return [ + ServiceA(loop=self.loop), + ServiceB(loop=self.loop), + ServiceC(loop=self.loop), + ] +``` + +1) Start a future/coroutine (that will be waited on to complete on stop): + +```python +class MyService(Service): + + async def on_start(self) -> None: + self.add_future(self.my_coro()) + + async def my_coro(self) -> None: + print('Executing coroutine') +``` + +1) Start a background task: + +```python +class MyService(Service): + + @Service.task + async def _my_coro(self) -> None: + print('Executing coroutine') +``` + +1) Start a background task that keeps running: + +```python +class MyService(Service): + + @Service.task + async def _my_coro(self) -> None: + while not self.should_stop: + # NOTE: self.sleep will wait for one second, or + # until service stopped/crashed. + await self.sleep(1.0) + print('Background thread waking up') +``` + +## Installation + +You can install Mode either via the Python Package Index (PyPI) +or from source. + +To install using `pip`: + +```sh +$ pip install -U mode-streaming +``` + +Downloading and installing from source: http://pypi.org/project/mode-streaming + +You can install it by doing the following: + +```sh +$ tar xvfz mode-streaming-0.2.1.tar.gz +$ cd mode-0.2.1 +$ python -m build . +# python install +``` + +The last command must be executed as a privileged user if +you are not currently using a virtualenv. + + +Using the development version: + +With pip: + +You can install the latest snapshot of Mode using the following +pip command: + +```sh +$ pip install mode-streaming +``` + +## Developing + +The guideline and associated information are stored in [CONTRIBUTING.md](./CONTRIBUTING.md) + +## FAQ + +#### Can I use Mode with Django/Flask/etc.? + +Yes! Use gevent/eventlet as a bridge to integrate with asyncio. + +Using `gevent`: + +This works with any blocking Python library that can work with gevent. + +Using gevent requires you to install the `aiogevent` module, +and you can install this as a bundle with Mode: + +```sh +$ pip install -U mode-streaming[gevent] +``` + +Then to actually use gevent as the event loop you have to +execute the following in your entrypoint module (usually where you +start the worker), before any other third party libraries are imported: + + +```python +#!/usr/bin/env python3 +import mode.loop +mode.loop.use('gevent') +# execute program +``` + +REMEMBER: This must be located at the very top of the module, +in such a way that it executes before you import other libraries. + + +Using `eventlet`: + +This works with any blocking Python library that can work with eventlet. + +Using eventlet requires you to install the `aioeventlet` module, +and you can install this as a bundle with Mode: + +```sh +$ pip install -U mode-streaming[eventlet] +``` + +Then to actually use eventlet as the event loop you have to +execute the following in your entrypoint module (usually where you +start the worker), before any other third party libraries are imported: + +```python +#!/usr/bin/env python3 +import mode.loop +mode.loop.use('eventlet') +# execute program +``` + +REMEMBER: It's very important this is at the very top of the module, +and that it executes before you import libraries. + +#### Can I use Mode with Tornado? + +Yes! Use the `tornado.platform.asyncio` bridge: http://www.tornadoweb.org/en/stable/asyncio.html + +#### Can I use Mode with Twisted? + +Yes! Use the asyncio reactor implementation: +https://twistedmatrix.com/documents/17.1.0/api/twisted.internet.asyncioreactor.html + +#### Will you support Python 3.5 or earlier? + +There are no immediate plans to support Python 3.5, but you are welcome to +contribute to the project. + +Here are some of the steps required to accomplish this: + +- Source code transformation to rewrite variable annotations to comments for example, the code: + +```python +class Point: + x: int = 0 + y: int = 0 +``` +must be rewritten into: + +```python +class Point: + x = 0 # type: int + y = 0 # type: int +``` + +- Source code transformation to rewrite async functions for example, the code: + +```python +async def foo(): + await asyncio.sleep(1.0) +``` + +must be rewritten into: + +```python +@coroutine +def foo(): + yield from asyncio.sleep(1.0) +``` + +#### Will you support Python 2? + +There are no plans to support Python 2, but you are welcome to contribute to +the project (details in question above is relevant also for Python 2). + + +### At Shutdown I get lots of warnings, what is this about? + +If you get warnings such as this at shutdown: + +```log +Task was destroyed but it is pending! +task: wait_for=()]>> +Task was destroyed but it is pending! +task: wait_for=()]>> +Task was destroyed but it is pending! +task: wait_for=()]>> +Task was destroyed but it is pending! +task: cb=[_release_waiter(()]>)() at /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/tasks.py:316]> +Task was destroyed but it is pending! + task: cb=[_release_waiter(()]>)() at /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/tasks.py:316]> +``` + +It usually means you forgot to stop a service before the process exited. + +## Code of Conduct + +Everyone interacting in the project's codebases, issue trackers, chat rooms, +and mailing lists is expected to follow the Mode Code of Conduct. + +As contributors and maintainers of these projects, and in the interest of fostering +an open and welcoming community, we pledge to respect all people who contribute +through reporting issues, posting feature requests, updating documentation, +submitting pull requests or patches, and other activities. + +We are committed to making participation in these projects a harassment-free +experience for everyone, regardless of level of experience, gender, +gender identity and expression, sexual orientation, disability, +personal appearance, body size, race, ethnicity, age, +religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical + or electronic addresses, without explicit permission +* Other unethical or unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct. By adopting this Code of Conduct, +project maintainers commit themselves to fairly and consistently applying +these principles to every aspect of managing this project. Project maintainers +who do not follow or enforce the Code of Conduct may be permanently removed from +the project team. + +This code of conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by opening an issue or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the Contributor Covenant, +version 1.2.0 available at http://contributor-covenant.org/version/1/2/0/. diff --git a/README.rst b/README.rst deleted file mode 100644 index b7ec20c5..00000000 --- a/README.rst +++ /dev/null @@ -1,534 +0,0 @@ -===================================================================== - AsyncIO Services Fork -===================================================================== - -|release| |coverage| |license| |wheel| |pyversion| - -:Web: https://faust-streaming.github.io/mode/ -:Download: https://pypi.org/project/mode-streaming -:Source: https://github.com/faust-streaming/mode -:Keywords: async, service, framework, actors, bootsteps, graph - - -Why the fork -============ - -We have decided to fork the original *Mode* project because there is a critical process of releasing new versions which causes uncertainty in the community. Everybody is welcome to contribute to this *fork*, and you can be added as a maintainer. - -We want to: - -- Ensure continues release -- Code quality -- Support latest Python versions -- Update the documentation - -and more... - -What is Mode? -============= - -Mode is a very minimal Python library built-on top of AsyncIO that makes -it much easier to use. - -In Mode your program is built out of services that you can start, stop, -restart and supervise. - -A service is just a class: - -.. code-block:: python - - class PageViewCache(Service): - redis: Redis = None - - async def on_start(self) -> None: - self.redis = connect_to_redis() - - async def update(self, url: str, n: int = 1) -> int: - return await self.redis.incr(url, n) - - async def get(self, url: str) -> int: - return await self.redis.get(url) - - -Services are started, stopped and restarted and have -callbacks for those actions. - -It can start another service: - -.. code-block:: python - - class App(Service): - page_view_cache: PageViewCache = None - - async def on_start(self) -> None: - await self.add_runtime_dependency(self.page_view_cache) - - @cached_property - def page_view_cache(self) -> PageViewCache: - return PageViewCache() - -It can include background tasks: - -.. code-block:: python - - class PageViewCache(Service): - - @Service.timer(1.0) - async def _update_cache(self) -> None: - self.data = await cache.get('key') - -Services that depends on other services actually form a graph -that you can visualize. - -Worker - Mode optionally provides a worker that you can use to start the program, - with support for logging, blocking detection, remote debugging and more. - - To start a worker add this to your program: - - .. code-block:: python - - if __name__ == '__main__': - from mode import Worker - Worker(Service(), loglevel="info").execute_from_commandline() - - Then execute your program to start the worker: - - .. code-block:: console - - $ python examples/tutorial.py - [2018-03-27 15:47:12,159: INFO]: [^Worker]: Starting... - [2018-03-27 15:47:12,160: INFO]: [^-AppService]: Starting... - [2018-03-27 15:47:12,160: INFO]: [^--Websockets]: Starting... - STARTING WEBSOCKET SERVER - [2018-03-27 15:47:12,161: INFO]: [^--UserCache]: Starting... - [2018-03-27 15:47:12,161: INFO]: [^--Webserver]: Starting... - [2018-03-27 15:47:12,164: INFO]: [^--Webserver]: Serving on port 8000 - REMOVING EXPIRED USERS - REMOVING EXPIRED USERS - - To stop it hit ``Control-c``: - - .. code-block:: console - - [2018-03-27 15:55:08,084: INFO]: [^Worker]: Stopping on signal received... - [2018-03-27 15:55:08,084: INFO]: [^Worker]: Stopping... - [2018-03-27 15:55:08,084: INFO]: [^-AppService]: Stopping... - [2018-03-27 15:55:08,084: INFO]: [^--UserCache]: Stopping... - REMOVING EXPIRED USERS - [2018-03-27 15:55:08,085: INFO]: [^Worker]: Gathering service tasks... - [2018-03-27 15:55:08,085: INFO]: [^--UserCache]: -Stopped! - [2018-03-27 15:55:08,085: INFO]: [^--Webserver]: Stopping... - [2018-03-27 15:55:08,085: INFO]: [^Worker]: Gathering all futures... - [2018-03-27 15:55:08,085: INFO]: [^--Webserver]: Closing server - [2018-03-27 15:55:08,086: INFO]: [^--Webserver]: Waiting for server to close handle - [2018-03-27 15:55:08,086: INFO]: [^--Webserver]: Shutting down web application - [2018-03-27 15:55:08,086: INFO]: [^--Webserver]: Waiting for handler to shut down - [2018-03-27 15:55:08,086: INFO]: [^--Webserver]: Cleanup - [2018-03-27 15:55:08,086: INFO]: [^--Webserver]: -Stopped! - [2018-03-27 15:55:08,086: INFO]: [^--Websockets]: Stopping... - [2018-03-27 15:55:08,086: INFO]: [^--Websockets]: -Stopped! - [2018-03-27 15:55:08,087: INFO]: [^-AppService]: -Stopped! - [2018-03-27 15:55:08,087: INFO]: [^Worker]: -Stopped! - -Beacons - The ``beacon`` object that we pass to services keeps track of the services - in a graph. - - They are not stricly required, but can be used to visualize a running - system, for example we can render it as a pretty graph. - - This requires you to have the ``pydot`` library and GraphViz - installed: - - .. code-block:: console - - $ pip install pydot - - Let's change the app service class to dump the graph to an image - at startup: - - .. code-block:: python - - class AppService(Service): - - async def on_start(self) -> None: - print('APP STARTING') - import pydot - import io - o = io.StringIO() - beacon = self.app.beacon.root or self.app.beacon - beacon.as_graph().to_dot(o) - graph, = pydot.graph_from_dot_data(o.getvalue()) - print('WRITING GRAPH TO image.png') - with open('image.png', 'wb') as fh: - fh.write(graph.create_png()) - - -Creating a Service -================== - -To define a service, simply subclass and fill in the methods -to do stuff as the service is started/stopped etc.: - - -.. code-block:: python - - class MyService(Service): - - async def on_start(self) -> None: - print('Im starting now') - - async def on_started(self) -> None: - print('Im ready') - - async def on_stop(self) -> None: - print('Im stopping now') - -To start the service, call ``await service.start()``: - -.. code-block:: python - - await service.start() - -Or you can use ``mode.Worker`` (or a subclass of this) to start your -services-based asyncio program from the console: - -.. code-block:: python - - if __name__ == '__main__': - import mode - worker = mode.Worker( - MyService(), - loglevel='INFO', - logfile=None, - daemon=False, - ) - worker.execute_from_commandline() - -It's a Graph! -============= - -Services can start other services, coroutines, and background tasks. - -1) Starting other services using ``add_depenency``: - -.. code-block:: python - - class MyService(Service): - - def __post_init__(self) -> None: - self.add_dependency(OtherService(loop=self.loop)) - -2) Start a list of services using ``on_init_dependencies``: - -.. code-block:: python - - class MyService(Service): - - def on_init_dependencies(self) -> None: - return [ - ServiceA(loop=self.loop), - ServiceB(loop=self.loop), - ServiceC(loop=self.loop), - ] - -3) Start a future/coroutine (that will be waited on to complete on stop): - -.. code-block:: python - - class MyService(Service): - - async def on_start(self) -> None: - self.add_future(self.my_coro()) - - async def my_coro(self) -> None: - print('Executing coroutine') - -4) Start a background task: - -.. code-block:: python - - class MyService(Service): - - @Service.task - async def _my_coro(self) -> None: - print('Executing coroutine') - - -5) Start a background task that keeps running: - -.. code-block:: python - - class MyService(Service): - - @Service.task - async def _my_coro(self) -> None: - while not self.should_stop: - # NOTE: self.sleep will wait for one second, or - # until service stopped/crashed. - await self.sleep(1.0) - print('Background thread waking up') - -.. _installation: - -Installation -============ - -You can install Mode either via the Python Package Index (PyPI) -or from source. - -To install using `pip`: - -.. code-block:: console - - $ pip install -U mode-streaming - -.. _installing-from-source: - -Downloading and installing from source --------------------------------------- - -Download the latest version of Mode from -http://pypi.org/project/mode-streaming - -You can install it by doing the following: - - .. code-block:: console - - $ tar xvfz mode-streaming-0.2.1.tar.gz - $ cd mode-0.2.1 - $ python build - # python install - -The last command must be executed as a privileged user if -you are not currently using a virtualenv. - -.. _installing-from-git: - -Using the development version ------------------------------ - -With pip -~~~~~~~~ - -You can install the latest snapshot of Mode using the following -pip command: - -.. code-block:: console - - $ pip install mode-streaming - -FAQ -=== - -Can I use Mode with Django/Flask/etc.? --------------------------------------- - -Yes! Use gevent/eventlet as a bridge to integrate with asyncio. - -Using ``gevent`` -~~~~~~~~~~~~~~~~ - -This works with any blocking Python library that can work with gevent. - -Using gevent requires you to install the ``aiogevent`` module, -and you can install this as a bundle with Mode: - -.. code-block:: console - - $ pip install -U mode-streaming[gevent] - -Then to actually use gevent as the event loop you have to -execute the following in your entrypoint module (usually where you -start the worker), before any other third party libraries are imported: - -.. code-block:: python - - #!/usr/bin/env python3 - import mode.loop - mode.loop.use('gevent') - # execute program - -REMEMBER: This must be located at the very top of the module, -in such a way that it executes before you import other libraries. - - -Using ``eventlet`` -~~~~~~~~~~~~~~~~~~ - -This works with any blocking Python library that can work with eventlet. - -Using eventlet requires you to install the ``aioeventlet`` module, -and you can install this as a bundle with Mode: - -.. code-block:: console - - $ pip install -U mode-streaming[eventlet] - -Then to actually use eventlet as the event loop you have to -execute the following in your entrypoint module (usually where you -start the worker), before any other third party libraries are imported: - -.. code-block:: python - - #!/usr/bin/env python3 - import mode.loop - mode.loop.use('eventlet') - # execute program - -REMEMBER: It's very important this is at the very top of the module, -and that it executes before you import libraries. - -Can I use Mode with Tornado? ----------------------------- - -Yes! Use the ``tornado.platform.asyncio`` bridge: -http://www.tornadoweb.org/en/stable/asyncio.html - -Can I use Mode with Twisted? ------------------------------ - -Yes! Use the asyncio reactor implementation: -https://twistedmatrix.com/documents/17.1.0/api/twisted.internet.asyncioreactor.html - -Will you support Python 3.5 or earlier? ---------------------------------------- - -There are no immediate plans to support Python 3.5, but you are welcome to -contribute to the project. - -Here are some of the steps required to accomplish this: - -- Source code transformation to rewrite variable annotations to comments - - for example, the code: - - .. code-block:: python - - class Point: - x: int = 0 - y: int = 0 - - must be rewritten into: - - .. code-block:: python - - class Point: - x = 0 # type: int - y = 0 # type: int - -- Source code transformation to rewrite async functions - - for example, the code: - - .. code-block:: python - - async def foo(): - await asyncio.sleep(1.0) - - must be rewritten into: - - .. code-block:: python - - @coroutine - def foo(): - yield from asyncio.sleep(1.0) - -Will you support Python 2? --------------------------- - -There are no plans to support Python 2, but you are welcome to contribute to -the project (details in question above is relevant also for Python 2). - - -At Shutdown I get lots of warnings, what is this about? -------------------------------------------------------- - -If you get warnings such as this at shutdown: - -.. code-block:: text - - Task was destroyed but it is pending! - task: wait_for=()]>> - Task was destroyed but it is pending! - task: wait_for=()]>> - Task was destroyed but it is pending! - task: wait_for=()]>> - Task was destroyed but it is pending! - task: cb=[_release_waiter(()]>)() at /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/tasks.py:316]> - Task was destroyed but it is pending! - task: cb=[_release_waiter(()]>)() at /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/tasks.py:316]> - -It usually means you forgot to stop a service before the process exited. - -Code of Conduct -=============== - -Everyone interacting in the project's codebases, issue trackers, chat rooms, -and mailing lists is expected to follow the Mode Code of Conduct. - -As contributors and maintainers of these projects, and in the interest of fostering -an open and welcoming community, we pledge to respect all people who contribute -through reporting issues, posting feature requests, updating documentation, -submitting pull requests or patches, and other activities. - -We are committed to making participation in these projects a harassment-free -experience for everyone, regardless of level of experience, gender, -gender identity and expression, sexual orientation, disability, -personal appearance, body size, race, ethnicity, age, -religion, or nationality. - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery -* Personal attacks -* Trolling or insulting/derogatory comments -* Public or private harassment -* Publishing other's private information, such as physical - or electronic addresses, without explicit permission -* Other unethical or unprofessional conduct. - -Project maintainers have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct. By adopting this Code of Conduct, -project maintainers commit themselves to fairly and consistently applying -these principles to every aspect of managing this project. Project maintainers -who do not follow or enforce the Code of Conduct may be permanently removed from -the project team. - -This code of conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by opening an issue or contacting one or more of the project maintainers. - -This Code of Conduct is adapted from the Contributor Covenant, -version 1.2.0 available at http://contributor-covenant.org/version/1/2/0/. - -.. |build-status| image:: https://travis-ci.com/faust-streaming/mode.png?branch=master - :alt: Build status - :target: https://travis-ci.com/faust-streaming/mode - -.. |coverage| image:: https://codecov.io/github/faust-streaming/mode/coverage.svg?branch=master - :target: https://codecov.io/github/faust-streaming/mode?branch=master - -.. |license| image:: https://img.shields.io/pypi/l/mode-streaming.svg - :alt: BSD License - :target: https://opensource.org/licenses/BSD-3-Clause - -.. |wheel| image:: https://img.shields.io/pypi/wheel/mode-streaming.svg - :alt: Mode can be installed via wheel - :target: http://pypi.org/project/mode-streaming/ - -.. |pyversion| image:: https://img.shields.io/pypi/pyversions/mode-streaming.svg - :alt: Supported Python versions. - :target: http://pypi.org/project/mode-streaming/ - -.. |pyimp| image:: https://img.shields.io/pypi/implementation/mode-streaming.svg - :alt: Supported Python implementations. - :target: http://pypi.org/project/mode-streaming/ - -.. |release| image:: https://img.shields.io/pypi/v/mode-streaming.svg - :alt: Latest release - :target: https://pypi.python.org/pypi/mode-streaming/ diff --git a/mode/loop/gevent.py b/mode/loop/gevent.py index fce643ae..057a4da6 100644 --- a/mode/loop/gevent.py +++ b/mode/loop/gevent.py @@ -31,7 +31,7 @@ psycogreen.gevent.patch_psycopg() try: - import aiogevent + import asyncio_gevent except ImportError: raise raise ImportError( @@ -43,13 +43,13 @@ raise RuntimeError("Event loop created before importing gevent loop!") -class Policy(aiogevent.EventLoopPolicy): # type: ignore +class Policy(asyncio_gevent.EventLoopPolicy): # type: ignore """Custom gevent event loop policy.""" _loop: Optional[asyncio.AbstractEventLoop] = None def get_event_loop(self) -> asyncio.AbstractEventLoop: - # aiogevent raises an error here current_thread() is not MainThread, + # asyncio_gevent raises an error here current_thread() is not MainThread, # but gevent monkey patches current_thread, so it's not a good check. loop = self._loop if loop is None: diff --git a/mode/utils/times.py b/mode/utils/times.py index d22341a4..fce228f9 100644 --- a/mode/utils/times.py +++ b/mode/utils/times.py @@ -283,9 +283,7 @@ def humanize_seconds( for unit, divider, formatter in TIME_UNITS: if secs >= divider: w = secs / float(divider) - return "{}{}{} {}{}".format( - prefix, sep, formatter(w), pluralize(int(w), unit), suffix - ) + return f"{prefix}{sep}{formatter(w)} {pluralize(int(w), unit)}{suffix}" if microseconds and secs > 0.0: return f"{prefix}{sep}{secs:.2f} seconds{suffix}" return now diff --git a/mode/utils/tracebacks.py b/mode/utils/tracebacks.py index b12067b3..eccaa3e7 100644 --- a/mode/utils/tracebacks.py +++ b/mode/utils/tracebacks.py @@ -273,9 +273,7 @@ def _get_coroutine_frame( @classmethod def _what_is_this(cls, obj: Any) -> AttributeError: return AttributeError( - "WHAT IS THIS? str={} repr={!r} typ={!r} dir={}".format( - obj, obj, type(obj), dir(obj) - ) + f"WHAT IS THIS? str={obj} repr={obj!r} typ={type(obj)!r} dir={dir(obj)}" ) @classmethod diff --git a/pyproject.toml b/pyproject.toml index 07266c45..a205260c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ build-backend = "setuptools.build_meta" [project] name = "mode-streaming" +version = "0.3.5" description = "AsyncIO Service-based programming" readme = "README.rst" requires-python = ">=3.8" @@ -37,7 +38,6 @@ dependencies = [ "croniter>=2.0.0,<3.0.0", "mypy_extensions", ] -dynamic = ["version"] [project.optional-dependencies] eventlet = [ @@ -45,7 +45,7 @@ eventlet = [ "dnspython", ] gevent = [ - "aiogevent~=0.2", + "asyncio-gevent~=0.2", ] uvloop = [ "uvloop>=0.19.0", @@ -211,3 +211,6 @@ skip-magic-trailing-comma = true quote-style = "double" indent-style = "space" line-ending = "auto" + +[tool.commitizen] +version_provider = "pep621" diff --git a/requirements-docs.txt b/requirements-docs.txt index 2a5a7446..a3cca4e4 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,3 +1,3 @@ mkdocs>=1.5.3 mkdocs-material>=9.5.13 -mkdocstrings>=0.24.1 +mkdocstrings[python]>=0.24.1 diff --git a/requirements-tests.txt b/requirements-tests.txt index ac161e9c..370fe4d2 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -1,7 +1,6 @@ freezegun>=0.3.11 hypothesis>=3.31 mypy>=1.8.0 -pre-commit>=3.6.2 pytest-aiofiles>=0.2.0 pytest-asyncio==0.21.1 pytest-base-url>=2.1.0 @@ -15,3 +14,7 @@ pytz ruff>=0.3.0 vulture yarl + +# Conditional +pre-commit>=3.6.2; python_version >= '3.9' +pre-commit>=3.5.0; python_version < '3.9' diff --git a/requirements.txt b/requirements.txt index 39b931ef..c6117e37 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ --e .[eventlet] +-e .[uvloop] -r requirements-tests.txt -r requirements-docs.txt diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index 2ba18d56..00000000 --- a/scripts/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Development Scripts - -* `scripts/test` - Run the test suite. -* `scripts/format` - Run the automated code linting/formatting tools. -* `scripts/check` - Run the code linting, checking that it passes. -* `scripts/build` - Build source and wheel packages. -* `scripts/publish` - Publish the latest version to PyPI. - -Styled after GitHub's ["Scripts to Rule Them All"](https://github.com/github/scripts-to-rule-them-all). diff --git a/scripts/build b/scripts/build deleted file mode 100755 index 5d5e71a1..00000000 --- a/scripts/build +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -e - -if [ -d 'venv' ] ; then - PREFIX="venv/bin/" -else - PREFIX="" -fi - -set -x - -${PREFIX}python -m build -${PREFIX}twine check dist/* -# ${PREFIX}mkdocs build diff --git a/scripts/build-docs.sh b/scripts/build-docs.sh new file mode 100755 index 00000000..f2a8f681 --- /dev/null +++ b/scripts/build-docs.sh @@ -0,0 +1,5 @@ +#!/bin/sh -e + +set -x + +mkdocs build diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 00000000..43c74ec5 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,6 @@ +#!/bin/sh -e + +set -x + +python3 -m build . --wheel +twine check dist/* diff --git a/scripts/bump.sh b/scripts/bump.sh new file mode 100755 index 00000000..dedda4e8 --- /dev/null +++ b/scripts/bump.sh @@ -0,0 +1,5 @@ +#!/bin/sh -e + +set -x + +cz bump --changelog diff --git a/scripts/check b/scripts/check deleted file mode 100755 index 67409901..00000000 --- a/scripts/check +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -e - -export PREFIX="" -if [ -d 'venv' ] ; then - export PREFIX="venv/bin/" -fi -export SOURCE_FILES="mode tests" - -set -x - -${PREFIX}ruff check $SOURCE_FILES --diff -${PREFIX}ruff format $SOURCE_FILES --check --diff -# ${PREFIX}mypy $SOURCE_FILES diff --git a/scripts/clean b/scripts/clean.sh similarity index 100% rename from scripts/clean rename to scripts/clean.sh diff --git a/scripts/docs b/scripts/docs deleted file mode 100755 index 4ac3beb7..00000000 --- a/scripts/docs +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -e - -export PREFIX="" -if [ -d 'venv' ] ; then - export PREFIX="venv/bin/" -fi - -set -x - -${PREFIX}mkdocs serve diff --git a/scripts/format b/scripts/format deleted file mode 100755 index f305fc43..00000000 --- a/scripts/format +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -e - -export PREFIX="" -if [ -d 'venv' ] ; then - export PREFIX="venv/bin/" -fi - -set -x - -${PREFIX}ruff format mode tests -${PREFIX}ruff check mode tests --fix diff --git a/scripts/format.sh b/scripts/format.sh new file mode 100755 index 00000000..d0a3f36f --- /dev/null +++ b/scripts/format.sh @@ -0,0 +1,6 @@ +#!/bin/sh -e + +set -x + +ruff format mode tests +ruff check mode tests --fix diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100755 index 00000000..58b5dc05 --- /dev/null +++ b/scripts/lint.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e +set -x + +# mypy mode +ruff check mode tests +ruff format mode tests --check diff --git a/scripts/publish b/scripts/publish deleted file mode 100755 index 0cf25fb8..00000000 --- a/scripts/publish +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -e - -VERSION_FILE="mode/__init__.py" - -if [ -d 'venv' ] ; then - PREFIX="venv/bin/" -else - PREFIX="" -fi - -VERSION=`grep __version__ ${VERSION_FILE} | grep -o '[0-9][^"]*'` - -set -x - -${PREFIX}twine upload dist/* -# ${PREFIX}mkdocs gh-deploy --force - -git tag -a v${VERSION} -m "release v${VERSION}" -git push origin v${VERSION} diff --git a/scripts/test b/scripts/test deleted file mode 100755 index e2fdcb29..00000000 --- a/scripts/test +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -export PREFIX="" -if [ -d 'venv' ] ; then - export PREFIX="venv/bin/" -fi - -set -ex - -if [ -z $GITHUB_ACTIONS ]; then - scripts/check -fi - -${PREFIX}pytest tests/unit tests/functional --cov=mode diff --git a/scripts/tests.sh b/scripts/tests.sh new file mode 100755 index 00000000..5adcdec5 --- /dev/null +++ b/scripts/tests.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -e +set -x + +pytest tests --cov=mode