diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1dba170 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,117 @@ +name: CI + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +on: + workflow_dispatch: + push: + release: + types: + - created + # Sequence of patterns matched against refs/tags + tags: + - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 + +jobs: + test: + name: test py=${{ matrix.py }} ${{ matrix.os }} + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: + - Ubuntu + # MacOs is commented out because: 1. For some reason it's very slow. 2. It never had OS-specific issues + # - MacOs + py: + - "3.11" + - "3.10" + - "3.9" + - "3.8" + steps: + - name: Setup python for test ${{ matrix.py }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.py }} + - uses: actions/checkout@v2 + - name: Install and configure Poetry + uses: snok/install-poetry@v1 + with: + version: 1.4.2 + virtualenvs-create: false + virtualenvs-in-project: false + installer-parallel: true + - name: Install dependencies + run: poetry install --all-extras --sync + - name: Run tests + run: pytest -v . + + lint: + name: lint py=${{ matrix.py }} ${{ matrix.os }} + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: + - Ubuntu + py: + - "3.11" + - "3.10" + - "3.9" + - "3.8" + steps: + - name: Setup python for test ${{ matrix.py }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.py }} + - uses: actions/checkout@v2 + - name: Install and configure Poetry + uses: snok/install-poetry@v1 + with: + version: 1.4.2 + virtualenvs-create: false + virtualenvs-in-project: false + installer-parallel: true + - name: Install dependencies + run: poetry install --all-extras --sync + - name: Run pre-commit hooks + run: pre-commit run --all-files + + qa_success: + name: QA Success + needs: [test, lint] + runs-on: ubuntu-latest + steps: + - name: Success + run: echo "QA Succeeded!" + + publish: + needs: qa_success + if: contains(github.ref, 'refs/tags/') + name: Publish to PyPI + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - uses: actions/checkout@v2 + - name: Install and configure Poetry + uses: snok/install-poetry@v1 + with: + version: 1.4.2 + virtualenvs-create: false + virtualenvs-in-project: false + installer-parallel: true + - name: Install poetry-dynamic-versioning + run: poetry self add poetry-dynamic-versioning@0.21.4 + - name: Publish to PyPI + env: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + run: | + poetry config pypi-token.pypi $PYPI_TOKEN + poetry publish --build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 674ae28..525a21d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,9 +36,9 @@ repos: language: system pass_filenames: false # formatter - - id: black - name: black - entry: black . + - id: ruff-format + name: ruff format + entry: ruff format . language: system pass_filenames: false # typecheck diff --git a/README.md b/README.md index 8b26804..e21f6e3 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ curl -X 'PATCH' \ Because Freak is using `FastAPI`, it's possible to use auto-generated documentation to interact with the Freak server. The interactive documentation can be accessed at Freak's main endpoint, which by default is `localhost:4444`. -The following screenshot shows the generated endpoints for the ML [example](https://github.com/danielgafni/freak/blob/master/examples/dl_example.py). Warning: making ML pipelines less reproducible isn't the brightest idea! +The following screenshot shows the generated endpoints for the ML [example](https://github.com/danielgafni/freak/blob/master/examples/dl_example.py). Warning: making ML pipelines less reproducible isn't the brightest idea! But it's convenient to use Freak for stopping a training. ![Sample Generated Docs](https://raw.githubusercontent.com/danielgafni/freak/master/resources/swagger.png) diff --git a/examples/dl_example.py b/examples/dl_example.py index cdb3b9f..d89e9ab 100644 --- a/examples/dl_example.py +++ b/examples/dl_example.py @@ -1,10 +1,10 @@ +from logging import basicConfig from time import sleep from typing import List from pydantic import BaseModel from freak import control -from logging import basicConfig class Head(BaseModel): @@ -22,6 +22,7 @@ class Checkpoints(BaseModel): class State(BaseModel): + training_stopped: bool = False lr: float = 1e-3 checkpoints: Checkpoints = Checkpoints() model: Model = Model() @@ -39,11 +40,14 @@ def epoch_loop(config: State, current_epoch: int): basicConfig(level="INFO") state = State() - control(state) + server = control(state) current_epoch = 0 - while True: + while not state.training_stopped: print(f"state: {state}") epoch_loop(state, current_epoch) current_epoch += 1 + else: + print("Training stopped!") + server.stop() diff --git a/freak/__init__.py b/freak/__init__.py index a602919..036642a 100644 --- a/freak/__init__.py +++ b/freak/__init__.py @@ -1,4 +1,4 @@ -from freak.freak import control, Freak -from freak.__version__ import __version__ +from freak._version import __version__ +from freak.freak import Freak, control __all__ = ["control", "Freak", "__version__"] diff --git a/freak/__version__.py b/freak/__version__.py deleted file mode 100644 index f102a9c..0000000 --- a/freak/__version__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.0.1" diff --git a/freak/_version.py b/freak/_version.py new file mode 100644 index 0000000..6c8e6b9 --- /dev/null +++ b/freak/_version.py @@ -0,0 +1 @@ +__version__ = "0.0.0" diff --git a/freak/freak.py b/freak/freak.py index 779caba..c3a3697 100644 --- a/freak/freak.py +++ b/freak/freak.py @@ -1,16 +1,14 @@ -from typing import TypeVar, Any, Optional, List +import operator +from logging import getLogger +from typing import Any, List, Optional, TypeVar -from fastapi import FastAPI, APIRouter +from fastapi import APIRouter, FastAPI, Query from pydantic import BaseModel - +from starlette.requests import Request from starlette.responses import JSONResponse from uvicorn import Config -from logging import getLogger -from starlette.requests import Request -from fastapi import Query -import operator -from freak.uvicorn_threaded import UvicornServer +from freak.uvicorn_threaded import ThreadedUvicorn logger = getLogger(__name__) @@ -83,6 +81,8 @@ def __init__( self.port = port self.uvicorn_log_level = uvicorn_log_level + self.should_stop = False + def control(self, state: T, serve: bool = True): if not state.Config.allow_mutation: state.Config.allow_mutation = True @@ -96,11 +96,19 @@ def control(self, state: T, serve: bool = True): self.serve() def serve(self): - server = UvicornServer( + self.server = ThreadedUvicorn( config=Config(app=self.app, host=self.host, port=self.port, log_level=self.uvicorn_log_level) ) - server.run_in_thread() - # logger.info(f"Running Freak on http://{self.host}:{self.port}") + self.server.start() + logger.info(f"Running Freak at {self.host}:{self.port}") + + def stop(self): + logger.info("Stopping Freak Server") + self.server.stop() + + @property + def running(self) -> bool: + return self.server.thread.is_alive() def add_routes(self, app: FastAPI, state: T) -> FastAPI: init_state = state.copy(deep=True) diff --git a/freak/uvicorn_threaded.py b/freak/uvicorn_threaded.py index 61974a0..cdf7619 100644 --- a/freak/uvicorn_threaded.py +++ b/freak/uvicorn_threaded.py @@ -1,67 +1,26 @@ +# taken from https://github.com/encode/uvicorn/discussions/1103#discussioncomment-6187606 + +import asyncio import threading -import time import uvicorn -# this code is taken from freqtrade - - -def asyncio_setup() -> None: # pragma: no cover - # Set eventloop for win32 setups - # Reverts a change done in uvicorn 0.15.0 - which now sets the eventloop - # via policy. - import sys - - if sys.version_info >= (3, 8) and sys.platform == "win32": - import asyncio - import selectors - - selector = selectors.SelectSelector() - loop = asyncio.SelectorEventLoop(selector) - asyncio.set_event_loop(loop) - - -class UvicornServer(uvicorn.Server): - """ - Multithreaded server - as found in https://github.com/encode/uvicorn/issues/742 +class ThreadedUvicorn: + def __init__(self, config: uvicorn.Config): + self.server = uvicorn.Server(config) + self.thread = threading.Thread(daemon=True, target=self.server.run) - Removed install_signal_handlers() override based on changes from this commit: - https://github.com/encode/uvicorn/commit/ce2ef45a9109df8eae038c0ec323eb63d644cbc6 - - Cannot rely on asyncio.get_event_loop() to create new event loop because of this check: - https://github.com/python/cpython/blob/4d7f11e05731f67fd2c07ec2972c6cb9861d52be/Lib/asyncio/events.py#L638 - - Fix by overriding run() and forcing creation of new event loop if uvloop is available - """ - - def run(self, sockets=None): - import asyncio - - """ - Parent implementation calls self.config.setup_event_loop(), - but we need to create uvloop event loop manually - """ - try: - import uvloop # pyright: ignore[reportMissingImports] - except ImportError: # pragma: no cover - asyncio_setup() - else: - asyncio.set_event_loop(uvloop.new_event_loop()) - try: - loop = asyncio.get_running_loop() - except RuntimeError: - # When running in a thread, we'll not have an eventloop yet. - loop = asyncio.new_event_loop() - - loop.run_until_complete(self.serve(sockets=sockets)) - - def run_in_thread(self): - self.thread = threading.Thread(target=self.run) + def start(self): self.thread.start() - while not self.started: - time.sleep(1e-3) + asyncio.run(self.wait_for_started()) + + async def wait_for_started(self): + while not self.server.started: + await asyncio.sleep(0.1) - def cleanup(self): - self.should_exit = True - self.thread.join() + def stop(self): + if self.thread.is_alive(): + self.server.should_exit = True + while self.thread.is_alive(): + continue diff --git a/poetry.lock b/poetry.lock index 9fe5dd5..4c266df 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "anyio" version = "3.6.2" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false python-versions = ">=3.6.2" files = [ @@ -25,7 +24,6 @@ trio = ["trio (>=0.16,<0.22)"] name = "attrs" version = "22.2.0" description = "Classes Without Boilerplate" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -44,7 +42,6 @@ tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy name = "black" version = "23.1.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -94,7 +91,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "certifi" version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -106,7 +102,6 @@ files = [ name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -118,7 +113,6 @@ files = [ name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -133,7 +127,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -145,7 +138,6 @@ files = [ name = "distlib" version = "0.3.6" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -157,7 +149,6 @@ files = [ name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -172,7 +163,6 @@ test = ["pytest (>=6)"] name = "fastapi" version = "0.95.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -194,7 +184,6 @@ test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6 name = "filelock" version = "3.10.6" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -210,7 +199,6 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.2)", "diff-cover (>=7.5)", "p name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -222,7 +210,6 @@ files = [ name = "httpcore" version = "0.16.3" description = "A minimal low-level HTTP client." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -234,17 +221,16 @@ files = [ anyio = ">=3.0,<5.0" certifi = "*" h11 = ">=0.13,<0.15" -sniffio = ">=1.0.0,<2.0.0" +sniffio = "==1.*" [package.extras] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "httpx" version = "0.23.3" description = "The next generation HTTP client." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -260,15 +246,14 @@ sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "identify" version = "2.5.22" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -283,7 +268,6 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -295,7 +279,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -307,7 +290,6 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -319,7 +301,6 @@ files = [ name = "nodeenv" version = "1.7.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -334,7 +315,6 @@ setuptools = "*" name = "packaging" version = "23.0" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -346,7 +326,6 @@ files = [ name = "pathspec" version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -358,7 +337,6 @@ files = [ name = "platformdirs" version = "3.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -374,7 +352,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest- name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -390,7 +367,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "3.2.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -409,7 +385,6 @@ virtualenv = ">=20.10.0" name = "pydantic" version = "1.10.7" description = "Data validation and settings management using python type hints" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -462,7 +437,6 @@ email = ["email-validator (>=1.0.3)"] name = "pyright" version = "1.1.300" description = "Command line wrapper for pyright" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -481,7 +455,6 @@ dev = ["twine (>=3.4.1)"] name = "pytest" version = "7.2.2" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -505,7 +478,6 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2. name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -555,7 +527,6 @@ files = [ name = "rfc3986" version = "1.5.0" description = "Validating URI References per RFC 3986" -category = "dev" optional = false python-versions = "*" files = [ @@ -571,36 +542,34 @@ idna2008 = ["idna"] [[package]] name = "ruff" -version = "0.0.259" -description = "An extremely fast Python linter, written in Rust." -category = "dev" +version = "0.1.4" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.259-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:f3938dc45e2a3f818e9cbd53007265c22246fbfded8837b2c563bf0ebde1a226"}, - {file = "ruff-0.0.259-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:22e1e35bf5f12072cd644d22afd9203641ccf258bc14ff91aa1c43dc14f6047d"}, - {file = "ruff-0.0.259-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2fb20e89e85d147c85caa807707a1488bccc1f3854dc3d53533e89b52a0c5ff"}, - {file = "ruff-0.0.259-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49e903bcda19f6bb0725a962c058eb5d61f40d84ef52ed53b61939b69402ab4e"}, - {file = "ruff-0.0.259-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71f0ef1985e9a6696fa97da8459917fa34bdaa2c16bd33bd5edead585b7d44f7"}, - {file = "ruff-0.0.259-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7cfef26619cba184d59aa7fa17b48af5891d51fc0b755a9bc533478a10d4d066"}, - {file = "ruff-0.0.259-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79b02fa17ec1fd8d306ae302cb47fb614b71e1f539997858243769bcbe78c6d9"}, - {file = "ruff-0.0.259-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:428507fb321b386dda70d66cd1a8aa0abf51d7c197983d83bb9e4fa5ee60300b"}, - {file = "ruff-0.0.259-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5fbaea9167f1852757f02133e5daacdb8c75b3431343205395da5b10499927a"}, - {file = "ruff-0.0.259-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:40ae87f2638484b7e8a7567b04a7af719f1c484c5bf132038b702bb32e1f6577"}, - {file = "ruff-0.0.259-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:29e2b77b7d5da6a7dd5cf9b738b511355c5734ece56f78e500d4b5bffd58c1a0"}, - {file = "ruff-0.0.259-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b3c1beacf6037e7f0781d4699d9a2dd4ba2462f475be5b1f45cf84c4ba3c69d"}, - {file = "ruff-0.0.259-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:daaea322e7e85f4c13d82be9536309e1c4b8b9851bb0cbc7eeb15d490fd46bf9"}, - {file = "ruff-0.0.259-py3-none-win32.whl", hash = "sha256:38704f151323aa5858370a2f792e122cc25e5d1aabe7d42ceeab83da18f0b456"}, - {file = "ruff-0.0.259-py3-none-win_amd64.whl", hash = "sha256:aa9449b898287e621942cc71b9327eceb8f0c357e4065fecefb707ef2d978df8"}, - {file = "ruff-0.0.259-py3-none-win_arm64.whl", hash = "sha256:e4f39e18702de69faaaee3969934b92d7467285627f99a5b6ecd55a7d9f5d086"}, - {file = "ruff-0.0.259.tar.gz", hash = "sha256:8b56496063ab3bfdf72339a5fbebb8bd46e5c5fee25ef11a9f03b208fa0562ec"}, + {file = "ruff-0.1.4-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:864958706b669cce31d629902175138ad8a069d99ca53514611521f532d91495"}, + {file = "ruff-0.1.4-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:9fdd61883bb34317c788af87f4cd75dfee3a73f5ded714b77ba928e418d6e39e"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4eaca8c9cc39aa7f0f0d7b8fe24ecb51232d1bb620fc4441a61161be4a17539"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a9a1301dc43cbf633fb603242bccd0aaa34834750a14a4c1817e2e5c8d60de17"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e8db8ab6f100f02e28b3d713270c857d370b8d61871d5c7d1702ae411df683"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:80fea754eaae06335784b8ea053d6eb8e9aac75359ebddd6fee0858e87c8d510"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bc02a480d4bfffd163a723698da15d1a9aec2fced4c06f2a753f87f4ce6969c"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862811b403063765b03e716dac0fda8fdbe78b675cd947ed5873506448acea4"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58826efb8b3efbb59bb306f4b19640b7e366967a31c049d49311d9eb3a4c60cb"}, + {file = "ruff-0.1.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fdfd453fc91d9d86d6aaa33b1bafa69d114cf7421057868f0b79104079d3e66e"}, + {file = "ruff-0.1.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e8791482d508bd0b36c76481ad3117987301b86072158bdb69d796503e1c84a8"}, + {file = "ruff-0.1.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01206e361021426e3c1b7fba06ddcb20dbc5037d64f6841e5f2b21084dc51800"}, + {file = "ruff-0.1.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:645591a613a42cb7e5c2b667cbefd3877b21e0252b59272ba7212c3d35a5819f"}, + {file = "ruff-0.1.4-py3-none-win32.whl", hash = "sha256:99908ca2b3b85bffe7e1414275d004917d1e0dfc99d497ccd2ecd19ad115fd0d"}, + {file = "ruff-0.1.4-py3-none-win_amd64.whl", hash = "sha256:1dfd6bf8f6ad0a4ac99333f437e0ec168989adc5d837ecd38ddb2cc4a2e3db8a"}, + {file = "ruff-0.1.4-py3-none-win_arm64.whl", hash = "sha256:d98ae9ebf56444e18a3e3652b3383204748f73e247dea6caaf8b52d37e6b32da"}, + {file = "ruff-0.1.4.tar.gz", hash = "sha256:21520ecca4cc555162068d87c747b8f95e1e95f8ecfcbbe59e8dd00710586315"}, ] [[package]] name = "setuptools" version = "67.6.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -617,7 +586,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -629,7 +597,6 @@ files = [ name = "starlette" version = "0.26.1" description = "The little ASGI library that shines." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -648,7 +615,6 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyam name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -660,7 +626,6 @@ files = [ name = "typing-extensions" version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -672,7 +637,6 @@ files = [ name = "uvicorn" version = "0.21.1" description = "The lightning-fast ASGI server." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -691,7 +655,6 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", name = "virtualenv" version = "20.21.0" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -711,4 +674,4 @@ test = ["covdefaults (>=2.2.2)", "coverage (>=7.1)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "ff2c145f00f9aa317350ed0407c65d94b171cc9f34a3515fba29e0eaf42d4940" +content-hash = "a5a2eeceb6e3ac849cc8bae984d4fcc157d405bf63aad7949aa9d8c88805b08c" diff --git a/pyproject.toml b/pyproject.toml index 85978f8..55713f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,10 @@ keywords = ["state", "control", "remote", "application"] classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Utilities", - "Programming Language :: Python" + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ] [tool.poetry.dependencies] @@ -22,14 +25,31 @@ uvicorn = "*" pytest = "^7.2.2" httpx = "^0.23.3" black = "^23.1.0" -ruff = "^0.0.259" pre-commit = "^3.2.1" pyright = "^1.1.300" +ruff = "^0.1.4" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + +[tool.poetry-dynamic-versioning] +enable = true +strict = false +vcs = "git" +style = "pep440" +dirty = true +bump = true +metadata = false + +[tool.poetry-dynamic-versioning.substitution] +files = [ + "pyproject.toml", + "freak/_version.py" +] + + [tool.pytest.ini_options] addopts = "--doctest-modules" log_cli = true @@ -55,6 +75,7 @@ exclude = ''' ''' [tool.ruff] +extend-select = ["I"] target-version = "py38" line-length = 120 src = [ diff --git a/tests/test_freak.py b/tests/test_freak.py index 01037eb..5b19bc0 100644 --- a/tests/test_freak.py +++ b/tests/test_freak.py @@ -1,10 +1,12 @@ -from typing import List, Tuple import json +from typing import List, Tuple + import pytest from fastapi import FastAPI +from fastapi.testclient import TestClient from pydantic import BaseModel + from freak import control -from fastapi.testclient import TestClient class Head(BaseModel):