From 02bd7ebffdeda14110cff3f1bb7472a77a17f73b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 29 Jul 2023 12:32:47 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20Deprecate=20Python=203.?= =?UTF-8?q?6=20and=20upgrade=20Poetry=20and=20Poetry=20Version=20Plugin=20?= =?UTF-8?q?(#627)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-docs.yml | 32 +++++++++++------------ .github/workflows/publish.yml | 16 +++++------- .github/workflows/test.yml | 42 ++++++++++++++++++++----------- README.md | 2 +- docs/contributing.md | 4 --- docs/features.md | 2 +- docs/index.md | 2 +- docs/tutorial/index.md | 6 +---- pyproject.toml | 12 ++++----- scripts/lint.sh | 2 -- scripts/test.sh | 1 + sqlmodel/main.py | 3 ++- sqlmodel/sql/expression.py | 37 ++++++--------------------- sqlmodel/sql/expression.py.jinja2 | 40 ++++++----------------------- 14 files changed, 77 insertions(+), 124 deletions(-) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 6400691..3bcc78d 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -4,26 +4,28 @@ on: branches: - main pull_request: - types: [opened, synchronize] + types: + - opened + - synchronize workflow_dispatch: inputs: debug_enabled: - description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' required: false default: false jobs: build-docs: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v3.1.0 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.7" + python-version: "3.11" # Allow debugging with tmate - name: Setup tmate session uses: mxschmitt/action-tmate@v3 @@ -34,34 +36,30 @@ jobs: id: cache with: path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-docs + key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-docs-v2 - name: Install poetry if: steps.cache.outputs.cache-hit != 'true' - # TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2 - # once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6 - # Ref: https://github.com/python-poetry/poetry-core/pull/188 run: | python -m pip install --upgrade pip - python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2 - python -m pip install "poetry==1.2.0a2" - python -m poetry plugin add poetry-version-plugin + python -m pip install "poetry" + python -m poetry self add poetry-version-plugin - name: Configure poetry run: python -m poetry config virtualenvs.create false - name: Install Dependencies if: steps.cache.outputs.cache-hit != 'true' run: python -m poetry install - name: Install Material for MkDocs Insiders - if: github.event.pull_request.head.repo.fork == false && steps.cache.outputs.cache-hit != 'true' - run: python -m poetry run pip install git+https://${{ secrets.ACTIONS_TOKEN }}@github.com/squidfunk/mkdocs-material-insiders.git + if: ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false ) && steps.cache.outputs.cache-hit != 'true' + run: python -m poetry run pip install git+https://${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}@github.com/squidfunk/mkdocs-material-insiders.git - uses: actions/cache@v3 with: key: mkdocs-cards-${{ github.ref }} path: .cache - name: Build Docs - if: github.event.pull_request.head.repo.fork == true + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true run: python -m poetry run mkdocs build - name: Build Docs with Insiders - if: github.event.pull_request.head.repo.fork == false + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false run: python -m poetry run mkdocs build --config-file mkdocs.insiders.yml - name: Zip docs run: python -m poetry run bash ./scripts/zip-docs.sh @@ -70,7 +68,7 @@ jobs: name: docs-zip path: ./site/docs.zip - name: Deploy to Netlify - uses: nwtgck/actions-netlify@v1.1.5 + uses: nwtgck/actions-netlify@v2.0.0 with: publish-dir: './site' production-branch: main diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f3c1e98..d788411 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -7,15 +7,15 @@ on: workflow_dispatch: inputs: debug_enabled: - description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' required: false default: false jobs: publish: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.1.0 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: @@ -30,17 +30,13 @@ jobs: id: cache with: path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root + key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-v2 - name: Install poetry if: steps.cache.outputs.cache-hit != 'true' - # TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2 - # once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6 - # Ref: https://github.com/python-poetry/poetry-core/pull/188 run: | python -m pip install --upgrade pip - python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2 - python -m pip install "poetry==1.2.0a2" - python -m poetry plugin add poetry-version-plugin + python -m pip install "poetry" + python -m poetry self add poetry-version-plugin - name: Configure poetry run: python -m poetry config virtualenvs.create false - name: Install Dependencies diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 585ffc0..c743532 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,24 +5,30 @@ on: branches: - main pull_request: - types: [opened, synchronize] + types: + - opened + - synchronize workflow_dispatch: inputs: debug_enabled: - description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' required: false default: false jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.6.15", "3.7", "3.8", "3.9", "3.10"] + python-version: + - "3.7" + - "3.8" + - "3.9" + - "3.10" fail-fast: false steps: - - uses: actions/checkout@v3.1.0 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: @@ -37,24 +43,19 @@ jobs: id: cache with: path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root + key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-v2 - name: Install poetry if: steps.cache.outputs.cache-hit != 'true' - # TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2 - # once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6 - # Ref: https://github.com/python-poetry/poetry-core/pull/188 run: | python -m pip install --upgrade pip - python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2 - python -m pip install "poetry==1.2.0a2" - python -m poetry plugin add poetry-version-plugin + python -m pip install "poetry" + python -m poetry self add poetry-version-plugin - name: Configure poetry run: python -m poetry config virtualenvs.create false - name: Install Dependencies if: steps.cache.outputs.cache-hit != 'true' run: python -m poetry install - name: Lint - if: ${{ matrix.python-version != '3.6.15' }} run: python -m poetry run bash scripts/lint.sh - run: mkdir coverage - name: Test @@ -68,7 +69,8 @@ jobs: name: coverage path: coverage coverage-combine: - needs: [test] + needs: + - test runs-on: ubuntu-latest steps: @@ -96,3 +98,15 @@ jobs: with: name: coverage-html path: htmlcov + + # https://github.com/marketplace/actions/alls-green#why + alls-green: # This job does nothing and is only used for the branch protection + if: always() + needs: + - coverage-combine + runs-on: ubuntu-latest + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/README.md b/README.md index 5721f1c..f85bb97 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as ## Requirements -A recent and currently supported version of Python (right now, Python supports versions 3.6 and above). +A recent and currently supported version of Python Python. As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel. diff --git a/docs/contributing.md b/docs/contributing.md index f2964fb..1cd62d4 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -6,10 +6,6 @@ First, you might want to see the basic ways to [help SQLModel and get help](help If you already cloned the repository and you know that you need to deep dive in the code, here are some guidelines to set up your environment. -### Python - -SQLModel supports Python 3.6 and above, but for development you should have at least **Python 3.7**. - ### Poetry **SQLModel** uses Poetry to build, package, and publish the project. diff --git a/docs/features.md b/docs/features.md index 09de0c1..102edef 100644 --- a/docs/features.md +++ b/docs/features.md @@ -12,7 +12,7 @@ Nevertheless, SQLModel is completely **independent** of FastAPI and can be used ## Just Modern Python -It's all based on standard modern **Python** type annotations. No new syntax to learn. Just standard modern Python. +It's all based on standard modern **Python** type annotations. No new syntax to learn. Just standard modern Python. If you need a 2 minute refresher of how to use Python types (even if you don't use SQLModel or FastAPI), check the FastAPI tutorial section: Python types intro. diff --git a/docs/index.md b/docs/index.md index 5721f1c..f85bb97 100644 --- a/docs/index.md +++ b/docs/index.md @@ -50,7 +50,7 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as ## Requirements -A recent and currently supported version of Python (right now, Python supports versions 3.6 and above). +A recent and currently supported version of Python Python. As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel. diff --git a/docs/tutorial/index.md b/docs/tutorial/index.md index 33cf622..79fa670 100644 --- a/docs/tutorial/index.md +++ b/docs/tutorial/index.md @@ -64,15 +64,13 @@ $ cd sqlmodel-tutorial Make sure you have an officially supported version of Python. -Currently it is **Python 3.6** and above (Python 3.5 was already deprecated). - You can check which version you have with:
```console $ python3 --version -Python 3.6.9 +Python 3.11 ```
@@ -84,8 +82,6 @@ You might want to try with the specific versions, for example with: * `python3.10` * `python3.9` * `python3.8` -* `python3.7` -* `python3.6` The code would look like this: diff --git a/pyproject.toml b/pyproject.toml index e3b1d3c..e402727 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,10 +17,10 @@ classifiers = [ "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Topic :: Database", "Topic :: Database :: Database Engines/Servers", "Topic :: Internet", @@ -30,7 +30,7 @@ classifiers = [ ] [tool.poetry.dependencies] -python = "^3.6.1" +python = "^3.7" SQLAlchemy = ">=1.4.17,<=1.4.41" pydantic = "^1.8.2" sqlalchemy2-stubs = {version = "*", allow-prereleases = true} @@ -39,19 +39,17 @@ sqlalchemy2-stubs = {version = "*", allow-prereleases = true} pytest = "^7.0.1" mypy = "0.971" flake8 = "^5.0.4" -black = {version = "^22.10.0", python = "^3.7"} +black = "^22.10.0" mkdocs = "^1.2.1" mkdocs-material = "^8.1.4" -pillow = {version = "^9.3.0", python = "^3.7"} -cairosvg = {version = "^2.5.2", python = "^3.7"} +pillow = "^9.3.0" +cairosvg = "^2.5.2" mdx-include = "^1.4.1" coverage = {extras = ["toml"], version = "^6.2"} fastapi = "^0.68.1" requests = "^2.26.0" autoflake = "^1.4" isort = "^5.9.3" -async_generator = {version = "*", python = "~3.6"} -async-exit-stack = {version = "*", python = "~3.6"} [build-system] requires = ["poetry-core"] diff --git a/scripts/lint.sh b/scripts/lint.sh index 02568cd..4191d90 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -7,5 +7,3 @@ mypy sqlmodel flake8 sqlmodel tests docs_src black sqlmodel tests docs_src --check isort sqlmodel tests docs_src scripts --check-only -# TODO: move this to test.sh after deprecating Python 3.6 -CHECK_JINJA=1 python scripts/generate_select.py diff --git a/scripts/test.sh b/scripts/test.sh index 9b758bd..1460a9c 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -3,6 +3,7 @@ set -e set -x +CHECK_JINJA=1 python scripts/generate_select.py coverage run -m pytest tests coverage combine coverage report --show-missing diff --git a/sqlmodel/main.py b/sqlmodel/main.py index d95c498..5b5950a 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -11,6 +11,7 @@ from typing import ( Callable, ClassVar, Dict, + ForwardRef, List, Mapping, Optional, @@ -29,7 +30,7 @@ from pydantic.fields import SHAPE_SINGLETON from pydantic.fields import FieldInfo as PydanticFieldInfo from pydantic.fields import ModelField, Undefined, UndefinedType from pydantic.main import ModelMetaclass, validate_model -from pydantic.typing import ForwardRef, NoArgAnyCallable, resolve_annotations +from pydantic.typing import NoArgAnyCallable, resolve_annotations from pydantic.utils import ROOT_KEY, Representation from sqlalchemy import Boolean, Column, Date, DateTime from sqlalchemy import Enum as sa_Enum diff --git a/sqlmodel/sql/expression.py b/sqlmodel/sql/expression.py index 31c0bc1..264e39c 100644 --- a/sqlmodel/sql/expression.py +++ b/sqlmodel/sql/expression.py @@ -1,6 +1,5 @@ # WARNING: do not modify this code, it is generated by expression.py.jinja2 -import sys from datetime import datetime from typing import ( TYPE_CHECKING, @@ -12,7 +11,6 @@ from typing import ( Type, TypeVar, Union, - cast, overload, ) from uuid import UUID @@ -24,36 +22,17 @@ from sqlalchemy.sql.expression import Select as _Select _TSelect = TypeVar("_TSelect") -# Workaround Generics incompatibility in Python 3.6 -# Ref: https://github.com/python/typing/issues/449#issuecomment-316061322 -if sys.version_info.minor >= 7: - class Select(_Select, Generic[_TSelect]): - inherit_cache = True +class Select(_Select, Generic[_TSelect]): + inherit_cache = True - # This is not comparable to sqlalchemy.sql.selectable.ScalarSelect, that has a different - # purpose. This is the same as a normal SQLAlchemy Select class where there's only one - # entity, so the result will be converted to a scalar by default. This way writing - # for loops on the results will feel natural. - class SelectOfScalar(_Select, Generic[_TSelect]): - inherit_cache = True -else: - from typing import GenericMeta # type: ignore - - class GenericSelectMeta(GenericMeta, _Select.__class__): # type: ignore - pass - - class _Py36Select(_Select, Generic[_TSelect], metaclass=GenericSelectMeta): - inherit_cache = True - - class _Py36SelectOfScalar(_Select, Generic[_TSelect], metaclass=GenericSelectMeta): - inherit_cache = True - - # Cast them for editors to work correctly, from several tricks tried, this works - # for both VS Code and PyCharm - Select = cast("Select", _Py36Select) # type: ignore - SelectOfScalar = cast("SelectOfScalar", _Py36SelectOfScalar) # type: ignore +# This is not comparable to sqlalchemy.sql.selectable.ScalarSelect, that has a different +# purpose. This is the same as a normal SQLAlchemy Select class where there's only one +# entity, so the result will be converted to a scalar by default. This way writing +# for loops on the results will feel natural. +class SelectOfScalar(_Select, Generic[_TSelect]): + inherit_cache = True if TYPE_CHECKING: # pragma: no cover diff --git a/sqlmodel/sql/expression.py.jinja2 b/sqlmodel/sql/expression.py.jinja2 index 51f04a2..26d12a0 100644 --- a/sqlmodel/sql/expression.py.jinja2 +++ b/sqlmodel/sql/expression.py.jinja2 @@ -1,4 +1,3 @@ -import sys from datetime import datetime from typing import ( TYPE_CHECKING, @@ -10,7 +9,6 @@ from typing import ( Type, TypeVar, Union, - cast, overload, ) from uuid import UUID @@ -22,37 +20,15 @@ from sqlalchemy.sql.expression import Select as _Select _TSelect = TypeVar("_TSelect") -# Workaround Generics incompatibility in Python 3.6 -# Ref: https://github.com/python/typing/issues/449#issuecomment-316061322 -if sys.version_info.minor >= 7: - - class Select(_Select, Generic[_TSelect]): - inherit_cache = True - - # This is not comparable to sqlalchemy.sql.selectable.ScalarSelect, that has a different - # purpose. This is the same as a normal SQLAlchemy Select class where there's only one - # entity, so the result will be converted to a scalar by default. This way writing - # for loops on the results will feel natural. - class SelectOfScalar(_Select, Generic[_TSelect]): - inherit_cache = True - -else: - from typing import GenericMeta # type: ignore - - class GenericSelectMeta(GenericMeta, _Select.__class__): # type: ignore - pass - - class _Py36Select(_Select, Generic[_TSelect], metaclass=GenericSelectMeta): - inherit_cache = True - - class _Py36SelectOfScalar(_Select, Generic[_TSelect], metaclass=GenericSelectMeta): - inherit_cache = True - - # Cast them for editors to work correctly, from several tricks tried, this works - # for both VS Code and PyCharm - Select = cast("Select", _Py36Select) # type: ignore - SelectOfScalar = cast("SelectOfScalar", _Py36SelectOfScalar) # type: ignore +class Select(_Select, Generic[_TSelect]): + inherit_cache = True +# This is not comparable to sqlalchemy.sql.selectable.ScalarSelect, that has a different +# purpose. This is the same as a normal SQLAlchemy Select class where there's only one +# entity, so the result will be converted to a scalar by default. This way writing +# for loops on the results will feel natural. +class SelectOfScalar(_Select, Generic[_TSelect]): + inherit_cache = True if TYPE_CHECKING: # pragma: no cover from ..main import SQLModel