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..4f666c5e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +## 0.2.1 (2024-03-09) + +### Fix + +- working on fixing github actions pipeline + +## 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..e30a397c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,105 @@ +# 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. 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/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..fd1ece80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ build-backend = "setuptools.build_meta" [project] name = "mode-streaming" +version = "0.2.1" 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 = [ @@ -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/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 100644 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