diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..2ff985a67 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +charset = utf-8 +max_line_length = 88 + +[*.{css,html,js,json,jsx,scss,ts,tsx,yaml,yml}] +indent_size = 2 diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 000000000..f1377a89c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,29 @@ +--- +name: Bug report +about: Report a bug in Flask-Admin (not other projects which depend on Flask-Admin) +--- + + + + + + + +Environment: + +- Python version: +- Flask version: +- Flask-Admin version: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..4e64f3d22 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Questions on Discussions + url: https://github.com/pallets-eco/flask-admin/discussions/ + about: Ask questions about your own code on the Discussions tab. + - name: Questions on Chat + url: https://discord.gg/pallets + about: Ask questions about your own code on our Discord chat. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 000000000..39c8f0875 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,15 @@ +--- +name: Feature request +about: Suggest a new feature for Flask-Admin +--- + + + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..1f47f125e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + groups: + github-actions: + patterns: + - '*' + - package-ecosystem: pip + directory: /requirements/ + schedule: + interval: monthly + groups: + python-requirements: + patterns: + - '*' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..dbb67e35f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,25 @@ + + + + + diff --git a/.github/workflows/lock.yaml b/.github/workflows/lock.yaml new file mode 100644 index 000000000..22228a1cd --- /dev/null +++ b/.github/workflows/lock.yaml @@ -0,0 +1,23 @@ +name: Lock inactive closed issues +# Lock closed issues that have not received any further activity for two weeks. +# This does not close open issues, only humans may do that. It is easier to +# respond to new issues with fresh examples rather than continuing discussions +# on old issues. + +on: + schedule: + - cron: '0 0 * * *' +permissions: + issues: write + pull-requests: write +concurrency: + group: lock +jobs: + lock: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 + with: + issue-inactive-days: 14 + pr-inactive-days: 14 + discussion-inactive-days: 14 diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 000000000..6d496bf65 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,73 @@ +name: Publish +on: + push: + tags: + - '*' +jobs: + build: + runs-on: ubuntu-latest + outputs: + hash: ${{ steps.hash.outputs.hash }} + steps: + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: '3.x' + cache: pip + cache-dependency-path: requirements*/*.txt + - run: pip install -r requirements/build.txt + # Use the commit date instead of the current date during the build. + - run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV + - run: python -m build + # Generate hashes used for provenance. + - name: generate hash + id: hash + run: cd dist && echo "hash=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + path: ./dist + provenance: + needs: [build] + permissions: + actions: read + id-token: write + contents: write + # Can't pin with hash due to how this workflow works. + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 + with: + base64-subjects: ${{ needs.build.outputs.hash }} + create-release: + # Upload the sdist, wheels, and provenance to a GitHub release. They remain + # available as build artifacts for a while as well. + needs: [provenance] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + - name: create release + run: > + gh release create --draft --repo ${{ github.repository }} + ${{ github.ref_name }} + *.intoto.jsonl/* artifact/* + env: + GH_TOKEN: ${{ github.token }} + publish-pypi: + needs: [provenance] + # Wait for approval before attempting to upload to PyPI. This allows reviewing the + # files in the draft release. + environment: + name: publish + url: https://pypi.org/project/Flask-Admin/${{ github.ref_name }} + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + - uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14 + with: + repository-url: https://test.pypi.org/legacy/ + packages-dir: artifact/ + - uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14 + with: + packages-dir: artifact/ diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml deleted file mode 100644 index 0545bd528..000000000 --- a/.github/workflows/test.yaml +++ /dev/null @@ -1,67 +0,0 @@ -name: Run tests -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - test-job: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] - tox-version: [ 'WTForms2' ] - include: - - python-version: 3.11 - tox-version: flake8 - - python-version: 3.11 - tox-version: docs-html - services: - # Label used to access the service container - postgres: - # Docker Hub image - image: postgis/postgis:12-master # postgres with postgis installed - # Provide the password for postgres - env: - POSTGRES_PASSWORD: postgres - POSTGRES_DB: flask_admin_test - ports: - - 5432:5432 - # Set health checks to wait until postgres has started - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - mongo: - image: mongo:5.0.14-focal - ports: - - 27017:27017 - azurite: - image: arafato/azurite:2.6.5 - env: - executable: blob - ports: - - 10000:10000 - steps: - - name: Install Ubuntu packages - run: | - sudo apt-get update - sudo apt-get install -y libgeos-c1v5 - - name: Check out repository code - uses: actions/checkout@v3 - - name: Set up PostgreSQL hstore module - env: - PGPASSWORD: postgres - run: psql -U postgres -h localhost -c 'CREATE EXTENSION hstore;' flask_admin_test - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install tox - run: pip install tox - - name: Run tests - run: tox -e ${{ matrix.tox-version }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 000000000..5c5bf7515 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,92 @@ +name: Tests +on: + push: + branches: + - master + - '*.x' + paths-ignore: + - 'docs/**' + - '*.md' + - '*.rst' + pull_request: + paths-ignore: + - 'docs/**' + - '*.md' + - '*.rst' +jobs: + tests: + name: ${{ format('py{0}-wtforms{1}', matrix.python, matrix.wtforms) }} + runs-on: ${{ matrix.os || 'ubuntu-latest' }} + strategy: + fail-fast: false + matrix: + python: ['3.7', '3.8', '3.9', '3.10', '3.11'] + wtforms: ['2'] + services: + # Label used to access the service container + postgres: + # Docker Hub image + image: postgis/postgis:12-master # postgres with postgis installed + # Provide the password for postgres + env: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: flask_admin_test + ports: + - 5432:5432 + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + mongo: + image: mongo:5.0.14-focal + ports: + - 27017:27017 + azurite: + image: arafato/azurite:2.6.5 + env: + executable: blob + ports: + - 10000:10000 + steps: + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: ${{ matrix.python }} + allow-prereleases: true + cache: pip + cache-dependency-path: requirements*/*.txt + - name: Install Ubuntu packages + run: | + sudo apt-get update + sudo apt-get install -y libgeos-c1v5 + - name: Check out repository code + uses: actions/checkout@v3 + - name: Set up PostgreSQL hstore module + env: + PGPASSWORD: postgres + run: psql -U postgres -h localhost -c 'CREATE EXTENSION hstore;' flask_admin_test + - run: pip install tox + - run: tox run -e ${{ matrix.tox || format('py{0}-wtforms{1}', matrix.python, matrix.wtforms) }} + not_tests: + name: ${{ matrix.tox }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + tox: ['docs', 'typing'] + steps: + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: '3.x' + cache: pip + cache-dependency-path: requirements*/*.txt + - name: cache mypy + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + path: ./.mypy_cache + key: mypy|${{ hashFiles('pyproject.toml') }} + - run: pip install tox + - run: tox run -e ${{ matrix.tox }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..0b0bdf927 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +ci: + autoupdate_schedule: monthly +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.7 + hooks: + - id: ruff + - id: ruff-format + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-merge-conflict + - id: debug-statements + - id: fix-byte-order-marker + - id: trailing-whitespace + - id: end-of-file-fixer diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..49c6e5e2f --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 +build: + os: ubuntu-22.04 + tools: + python: '3.11' +python: + install: + - requirements: requirements/docs.txt + - method: pip + path: . +sphinx: + builder: dirhtml + fail_on_warning: true diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..c76f9036c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,24 @@ +Copyright 2011 Pallets Community Ecosystem + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index ac26d4a7c..4f1b839e6 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ by doing: git clone git@github.com:pallets-eco/flask-admin.git cd flask-admin - python setup.py install + pip install . ## Tests @@ -105,8 +105,7 @@ Test are run with *pytest*. If you are not familiar with this package you can get some more info from [their website](https://pytest.org/). To run the tests, from the project directory, simply: - - pip install -r requirements-dev.txt + pip install --use-pep517 -r requirements/dev.txt pytest You should see output similar to: @@ -117,8 +116,11 @@ You should see output similar to: OK -For all the tests to pass successfully, you\'ll need Postgres & MongoDB -to be running locally. For Postgres: +*NOTE*: For all the tests to pass successfully, you\'ll need Postgres (with +the postgis and hstore extension) & MongoDB to be running locally. You'll +also need libgeos available. + +For Postgres: > psql postgres CREATE DATABASE flask_admin_test; @@ -130,8 +132,10 @@ to be running locally. For Postgres: If you\'re using Homebrew on MacOS, you might need this: - # install postgis + # install postgis and geos > brew install postgis + > brew install geos + > export DYLD_LIBRARY_PATH=/opt/homebrew/opt/geos/lib/ # set up postgresql user > createuser -s postgresql diff --git a/flask_admin/__init__.py b/flask_admin/__init__.py index ee6b8767f..a074a308c 100644 --- a/flask_admin/__init__.py +++ b/flask_admin/__init__.py @@ -1,6 +1,6 @@ __version__ = '1.6.1' __author__ = 'Flask-Admin team' -__email__ = 'serge.koval+github@gmail.com' +__email__ = 'contact@palletsproject.com' from .base import expose, expose_plugview, Admin, BaseView, AdminIndexView # noqa: F401 diff --git a/flask_admin/contrib/__init__.py b/flask_admin/contrib/__init__.py index 42e33a76c..24608e116 100644 --- a/flask_admin/contrib/__init__.py +++ b/flask_admin/contrib/__init__.py @@ -1,4 +1,4 @@ try: - __import__('pkg_resources').declare_namespace(__name__) + __path__ = __import__('pkgutil').extend_path(__path__, __name__) except ImportError: pass diff --git a/flask_admin/tests/geoa/test_basic.py b/flask_admin/tests/geoa/test_basic.py index 11c7fa602..7009107b2 100644 --- a/flask_admin/tests/geoa/test_basic.py +++ b/flask_admin/tests/geoa/test_basic.py @@ -2,6 +2,7 @@ import json import re +import pytest from flask_admin.contrib.geoa import ModelView from flask_admin.contrib.geoa.fields import GeoJSONField @@ -26,6 +27,7 @@ def __unicode__(self): return GeoModel +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_model(): app, db, admin = setup() GeoModel = create_models(db) diff --git a/flask_admin/tests/mongoengine/test_basic.py b/flask_admin/tests/mongoengine/test_basic.py index b977d23b5..20fbb61ab 100644 --- a/flask_admin/tests/mongoengine/test_basic.py +++ b/flask_admin/tests/mongoengine/test_basic.py @@ -1,3 +1,4 @@ +import pytest from wtforms import fields, validators from flask_admin import form diff --git a/flask_admin/tests/peeweemodel/test_basic.py b/flask_admin/tests/peeweemodel/test_basic.py index fa7f97645..ea1ce8253 100644 --- a/flask_admin/tests/peeweemodel/test_basic.py +++ b/flask_admin/tests/peeweemodel/test_basic.py @@ -1,3 +1,5 @@ +import pytest + from flask_admin._compat import as_unicode import peewee @@ -174,6 +176,7 @@ def test_model(): assert Model1.select().count() == 0 +# @pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_column_editable_list(): app, db, admin = setup() diff --git a/flask_admin/tests/sqla/test_basic.py b/flask_admin/tests/sqla/test_basic.py index 981903f4a..65bd3d0cf 100644 --- a/flask_admin/tests/sqla/test_basic.py +++ b/flask_admin/tests/sqla/test_basic.py @@ -155,6 +155,7 @@ def fill_db(db, Model1, Model2): db.session.commit() +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_model(): app, db, admin = setup() @@ -485,6 +486,7 @@ def test_extra_args_filter(): assert '' in data +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_complex_searchable_list(): app, db, admin = setup() @@ -521,6 +523,7 @@ def test_complex_searchable_list(): assert 'model1-test2-val' not in data +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_complex_searchable_list_missing_children(): app, db, admin = setup() @@ -542,6 +545,7 @@ def test_complex_searchable_list_missing_children(): assert 'magic string' in data +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_column_editable_list(): app, db, admin = setup() @@ -705,6 +709,7 @@ def __init__(self, id=None, val1=None): assert 'change-success-1' in data +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_column_filters(): app, db, admin = setup() @@ -1698,6 +1703,7 @@ class Model2(db.Model): assert 'Jim Smith' in data +@pytest.mark.filterwarnings("ignore:Please update your type formatter:UserWarning") def test_url_args(): app, db, admin = setup() diff --git a/flask_admin/tests/sqla/test_form_rules.py b/flask_admin/tests/sqla/test_form_rules.py index 86959ee0a..faae6521b 100644 --- a/flask_admin/tests/sqla/test_form_rules.py +++ b/flask_admin/tests/sqla/test_form_rules.py @@ -1,9 +1,12 @@ +import pytest + from . import setup from .test_basic import CustomModelView, create_models from flask_admin.form import rules +@pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") def test_form_rules(): app, db, admin = setup() @@ -30,6 +33,7 @@ def test_form_rules(): assert pos3 == -1 +@pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") def test_rule_macro(): app, db, admin = setup() @@ -53,6 +57,7 @@ def test_rule_macro(): assert 'Hello another_test' in data +@pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") def test_rule_container(): app, db, admin = setup() @@ -80,6 +85,7 @@ def test_rule_container(): assert pos1 < pos2 < pos3 +@pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") def test_rule_header(): app, db, admin = setup() with app.app_context(): @@ -99,6 +105,7 @@ def test_rule_header(): assert '

hello

' in data +@pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") def test_rule_field_set(): app, db, admin = setup() with app.app_context(): @@ -125,6 +132,7 @@ def test_rule_field_set(): assert pos3 == -1 +@pytest.mark.filterwarnings("ignore:Fields missing:UserWarning") def test_rule_inlinefieldlist(): app, db, admin = setup() with app.app_context(): diff --git a/flask_admin/tests/test_base.py b/flask_admin/tests/test_base.py index ea0d69d2e..60b309528 100644 --- a/flask_admin/tests/test_base.py +++ b/flask_admin/tests/test_base.py @@ -217,6 +217,8 @@ def test_baseview_urls(): assert len(view._urls) == 2 +# +@pytest.mark.filterwarnings("ignore:unclosed file:ResourceWarning") def test_add_views(): app = Flask(__name__) admin = base.Admin(app) @@ -426,6 +428,7 @@ def test_root_mount(): # test static files when url='/' with app.test_request_context('/'): rv = client.get(url_for('admin.static', filename='bootstrap/bootstrap2/css/bootstrap.css')) + rv.close() assert rv.status_code == 200 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..9d9f0a8a7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,98 @@ +[project] +name = "Flask-Admin" +version = "1.6.1" +description = "Simple and extensible admin interface framework for Flask" +readme = "README.md" +license = { file = "LICENSE.txt" } +author = [{ name = "Flask-Admin team" }] +maintainers = [{ name = "Pallets Ecosystem", email = "contact@palletsprojects.com" }] +classifiers = [ + 'Development Status :: 4 - Beta', + 'Environment :: Web Environment', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries :: Python Modules', + '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', + 'Programming Language :: Python :: 3.11', +] +requires-python = ">=3.6" +dependencies = [ + "flask>=0.7", + "wtforms" +] + +[project.urls] +Documentation = "https://flask-admin.readthedocs.io" +Changes = "https://github.com/pallets-eco/flask-admin/releases/" +Source = "https://github.com/pallets-eco/flask-admin/" +Chat = "https://discord.gg/pallets" + +[build-system] +requires = ["flit_core<4"] +build-backend = "flit_core.buildapi" + +[tool.flit.module] +name = "flask_admin" + +[tool.pytest.ini_options] +testpaths = ["tests"] +filterwarnings = [ + "error", + # TODO: remove the ignored deprecation warning when support for WTForms 3 has been added. + "ignore:Flags should be stored in dicts and not in tuples. The next version of WTForms will abandon support for flags in tuples.:DeprecationWarning", + "ignore:'HTMLString' will be removed in WTForms 3.0. Use 'markupsafe.Markup' instead.:DeprecationWarning", + # TODO: remove when we drop Python 3.7 support + "ignore:pkg_resources is deprecated as an API:DeprecationWarning", +] + +[tool.coverage.run] +branch = true +source = ["flask_admin", "tests"] + +[tool.coverage.paths] +source = ["flask_admin", "*/site-packages"] + +[tool.mypy] +python_version = "3.6" +files = ["flask_admin"] +show_error_codes = true +pretty = true +strict = true + +[[tool.mypy.overrides]] +module = [ + "sqlparse.*" +] +ignore_missing_imports = true + +[tool.pyright] +pythonVersion = "3.6" +include = ["flask_admin", "tests"] +typeCheckingMode = "basic" + +[tool.ruff] +src = ["flask_admin"] +fix = true +show-fixes = true +output-format = "full" + +[tool.ruff.lint] +select = [ + "B", # flake8-bugbear + "E", # pycodestyle error + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "W", # pycodestyle warning +] +ignore-init-module-imports = true + +[tool.ruff.lint.isort] +force-single-line = true +order-by-type = false diff --git a/requirements/build.in b/requirements/build.in new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/requirements/build.in @@ -0,0 +1 @@ +build diff --git a/requirements/build.txt b/requirements/build.txt new file mode 100644 index 000000000..c21dafe4e --- /dev/null +++ b/requirements/build.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.7 +# by the following command: +# +# pip-compile build.in +# +build==1.1.1 + # via -r build.in +importlib-metadata==6.7.0 + # via build +packaging==24.0 + # via build +pyproject-hooks==1.1.0 + # via build +tomli==2.0.1 + # via build +typing-extensions==4.7.1 + # via importlib-metadata +zipp==3.15.0 + # via importlib-metadata diff --git a/requirements/dev.in b/requirements/dev.in new file mode 100644 index 000000000..6d176eff4 --- /dev/null +++ b/requirements/dev.in @@ -0,0 +1,5 @@ +-r docs.txt +-r tests.in +-r typing.txt +pre-commit +tox diff --git a/requirements/dev.txt b/requirements/dev.txt new file mode 100644 index 000000000..c86237c95 --- /dev/null +++ b/requirements/dev.txt @@ -0,0 +1,354 @@ +# +# This file is autogenerated by pip-compile with Python 3.7 +# by the following command: +# +# pip-compile --resolver=backtracking dev.in +# +alabaster==0.7.13 + # via + # -r docs.txt + # sphinx +arrow==0.13.2 + # via -r tests.in +astroid==2.15.8 + # via pylint +azure-core==1.30.1 + # via azure-storage-blob +azure-storage-blob==12.19.1 + # via -r tests.in +babel==2.9.1 + # via + # -r docs.txt + # -r tests.in + # flask-babelex + # sphinx +cachetools==5.3.3 + # via tox +certifi==2024.7.4 + # via + # -r docs.txt + # requests +cffi==1.15.1 + # via cryptography +cfgv==3.3.1 + # via pre-commit +chardet==5.2.0 + # via tox +charset-normalizer==3.3.2 + # via + # -r docs.txt + # requests +click==8.1.7 + # via flask +colorama==0.4.6 + # via tox +colour==0.1.5 + # via -r tests.in +coverage[toml]==6.5.0 + # via + # coverage + # coveralls + # pytest-cov +coveralls==3.3.1 + # via -r tests.in +cryptography==42.0.8 + # via azure-storage-blob +dill==0.3.7 + # via pylint +distlib==0.3.8 + # via virtualenv +dnspython==2.3.0 + # via email-validator +docopt==0.6.2 + # via coveralls +docutils==0.19 + # via + # -r docs.txt + # sphinx +email-validator==2.0.0.post2 + # via -r tests.in +exceptiongroup==1.2.1 + # via + # -r typing.txt + # pytest +filelock==3.12.2 + # via + # tox + # virtualenv +flake8==3.9.2 + # via -r tests.in +flask==1.1.2 + # via + # flask-babelex + # flask-mongoengine + # flask-sqlalchemy + # flask-wtf +flask-babelex==0.9.4 + # via -r tests.in +flask-mongoengine==0.8.2 + # via -r tests.in +flask-sqlalchemy==2.5.1 + # via -r tests.in +flask-wtf==1.1.1 + # via flask-mongoengine +geoalchemy2==0.15.1 + # via -r tests.in +identify==2.5.24 + # via pre-commit +idna==3.7 + # via + # -r docs.txt + # email-validator + # requests +imagesize==1.4.1 + # via + # -r docs.txt + # sphinx +importlib-metadata==6.7.0 + # via + # -r docs.txt + # -r typing.txt + # click + # flake8 + # pallets-sphinx-themes + # pluggy + # pre-commit + # pytest + # sphinx + # sqlalchemy + # sqlalchemy-utils + # tox + # virtualenv +iniconfig==2.0.0 + # via + # -r typing.txt + # pytest +isodate==0.6.1 + # via azure-storage-blob +isort==5.11.5 + # via pylint +itsdangerous==2.0.1 + # via + # -r tests.in + # flask + # flask-wtf +jinja2==3.0.0 + # via + # -r docs.txt + # -r tests.in + # flask + # flask-babelex + # sphinx +lazy-object-proxy==1.9.0 + # via astroid +markupsafe==2.0.1 + # via + # -r docs.txt + # -r tests.in + # jinja2 + # wtforms +mccabe==0.6.1 + # via + # flake8 + # pylint +mongoengine==0.21.0 + # via + # -r tests.in + # flask-mongoengine +mypy==1.4.1 + # via -r typing.txt +mypy-extensions==1.0.0 + # via + # -r typing.txt + # mypy +nodeenv==1.9.1 + # via + # -r typing.txt + # pre-commit + # pyright +packaging==24.0 + # via + # -r docs.txt + # -r typing.txt + # geoalchemy2 + # pallets-sphinx-themes + # pyproject-api + # pytest + # sphinx + # tox +pallets-sphinx-themes==2.0.3 + # via -r docs.txt +peewee==3.17.5 + # via + # -r tests.in + # wtf-peewee +pillow==9.5.0 + # via -r tests.in +platformdirs==4.0.0 + # via + # pylint + # tox + # virtualenv +pluggy==1.2.0 + # via + # -r typing.txt + # pytest + # tox +pre-commit==2.21.0 + # via -r dev.in +psycopg2==2.9.9 + # via -r tests.in +pycodestyle==2.7.0 + # via flake8 +pycparser==2.21 + # via cffi +pyflakes==2.3.1 + # via flake8 +pygments==2.17.2 + # via + # -r docs.txt + # sphinx +pylint==2.17.7 + # via -r tests.in +pymongo==3.13.0 + # via + # -r tests.in + # mongoengine +pyproject-api==1.5.3 + # via tox +pyright==1.1.370 + # via -r typing.txt +pytest==7.4.4 + # via + # -r tests.in + # -r typing.txt + # pytest-cov +pytest-cov==4.1.0 + # via -r tests.in +python-dateutil==2.9.0.post0 + # via arrow +pytz==2024.1 + # via + # -r docs.txt + # babel +pyyaml==6.0.1 + # via pre-commit +requests==2.31.0 + # via + # -r docs.txt + # azure-core + # coveralls + # sphinx +shapely==1.5.9 + # via -r tests.in +six==1.16.0 + # via + # azure-core + # flask-mongoengine + # isodate + # python-dateutil +snowballstemmer==2.2.0 + # via + # -r docs.txt + # sphinx +speaklater==1.3 + # via flask-babelex +sphinx==5.3.0 + # via + # -r docs.txt + # pallets-sphinx-themes + # sphinxcontrib-log-cabinet +sphinxcontrib-applehelp==1.0.2 + # via + # -r docs.txt + # sphinx +sphinxcontrib-devhelp==1.0.2 + # via + # -r docs.txt + # sphinx +sphinxcontrib-htmlhelp==2.0.0 + # via + # -r docs.txt + # sphinx +sphinxcontrib-jsmath==1.0.1 + # via + # -r docs.txt + # sphinx +sphinxcontrib-log-cabinet==1.0.1 + # via -r docs.txt +sphinxcontrib-qthelp==1.0.3 + # via + # -r docs.txt + # sphinx +sphinxcontrib-serializinghtml==1.1.5 + # via + # -r docs.txt + # sphinx +sqlalchemy==1.4.52 + # via + # -r tests.in + # flask-sqlalchemy + # geoalchemy2 + # sqlalchemy-citext + # sqlalchemy-utils +sqlalchemy-citext==1.8.0 + # via -r tests.in +sqlalchemy-utils==0.41.2 + # via -r tests.in +tomli==2.0.1 + # via + # -r typing.txt + # coverage + # mypy + # pylint + # pyproject-api + # pytest + # tox +tomlkit==0.12.5 + # via pylint +tox==4.8.0 + # via -r dev.in +typed-ast==1.5.5 + # via + # -r typing.txt + # astroid + # mypy +typing-extensions==4.7.1 + # via + # -r docs.txt + # -r typing.txt + # astroid + # azure-core + # azure-storage-blob + # importlib-metadata + # mypy + # platformdirs + # pylint + # pyright + # tox +urllib3==2.0.7 + # via + # -r docs.txt + # requests +virtualenv==20.26.3 + # via + # pre-commit + # tox +werkzeug==1.0.1 + # via + # -r tests.in + # flask +wrapt==1.16.0 + # via astroid +wtf-peewee==3.0.5 + # via -r tests.in +wtforms==3.0.1 + # via + # -r tests.in + # flask-wtf + # wtf-peewee +zipp==3.15.0 + # via + # -r docs.txt + # -r typing.txt + # importlib-metadata diff --git a/requirements/docs.in b/requirements/docs.in new file mode 100644 index 000000000..8fa5a39c6 --- /dev/null +++ b/requirements/docs.in @@ -0,0 +1,5 @@ +-c tests.in + +pallets-sphinx-themes +sphinx +sphinxcontrib-log-cabinet diff --git a/requirements/docs.txt b/requirements/docs.txt new file mode 100644 index 000000000..ac2e81e5b --- /dev/null +++ b/requirements/docs.txt @@ -0,0 +1,73 @@ +# +# This file is autogenerated by pip-compile with Python 3.7 +# by the following command: +# +# pip-compile docs.in +# +alabaster==0.7.13 + # via sphinx +babel==2.9.1 + # via + # -c tests.in + # sphinx +certifi==2024.7.4 + # via requests +charset-normalizer==3.3.2 + # via requests +docutils==0.19 + # via sphinx +idna==3.7 + # via requests +imagesize==1.4.1 + # via sphinx +importlib-metadata==6.7.0 + # via + # pallets-sphinx-themes + # sphinx +jinja2==3.0.0 + # via + # -c tests.in + # sphinx +markupsafe==2.0.1 + # via + # -c tests.in + # jinja2 +packaging==24.0 + # via + # pallets-sphinx-themes + # sphinx +pallets-sphinx-themes==2.0.3 + # via -r docs.in +pygments==2.17.2 + # via sphinx +pytz==2024.1 + # via babel +requests==2.31.0 + # via sphinx +snowballstemmer==2.2.0 + # via sphinx +sphinx==5.3.0 + # via + # -r docs.in + # pallets-sphinx-themes + # sphinxcontrib-log-cabinet +sphinxcontrib-applehelp==1.0.2 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==2.0.0 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-log-cabinet==1.0.1 + # via -r docs.in +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.5 + # via sphinx +typing-extensions==4.7.1 + # via importlib-metadata +urllib3==2.0.7 + # via requests +zipp==3.15.0 + # via importlib-metadata diff --git a/requirements-dev.txt b/requirements/tests.in similarity index 90% rename from requirements-dev.txt rename to requirements/tests.in index 5b48f73c8..b908912a5 100644 --- a/requirements-dev.txt +++ b/requirements/tests.in @@ -1,6 +1,5 @@ flake8 -Flask<2.0.0 -werkzeug<2.0.0 +werkzeug sqlalchemy<2.0 itsdangerous<2.1.0 MarkupSafe<2.1.0 @@ -27,4 +26,4 @@ azure-storage-blob arrow<0.14.0 colour email-validator -wtforms==2.3.3 +wtforms diff --git a/requirements/typing.in b/requirements/typing.in new file mode 100644 index 000000000..30febfdb8 --- /dev/null +++ b/requirements/typing.in @@ -0,0 +1,5 @@ +-c tests.in + +mypy +pyright +pytest diff --git a/requirements/typing.txt b/requirements/typing.txt new file mode 100644 index 000000000..02f370e38 --- /dev/null +++ b/requirements/typing.txt @@ -0,0 +1,43 @@ +# +# This file is autogenerated by pip-compile with Python 3.7 +# by the following command: +# +# pip-compile typing.in +# +exceptiongroup==1.2.1 + # via pytest +importlib-metadata==6.7.0 + # via + # pluggy + # pytest +iniconfig==2.0.0 + # via pytest +mypy==1.4.1 + # via -r typing.in +mypy-extensions==1.0.0 + # via mypy +nodeenv==1.9.1 + # via pyright +packaging==24.0 + # via pytest +pluggy==1.2.0 + # via pytest +pyright==1.1.370 + # via -r typing.in +pytest==7.4.4 + # via + # -c tests.in + # -r typing.in +tomli==2.0.1 + # via + # mypy + # pytest +typed-ast==1.5.5 + # via mypy +typing-extensions==4.7.1 + # via + # importlib-metadata + # mypy + # pyright +zipp==3.15.0 + # via importlib-metadata diff --git a/setup.py b/setup.py deleted file mode 100644 index 97c26a250..000000000 --- a/setup.py +++ /dev/null @@ -1,93 +0,0 @@ -# Fix for older setuptools -import re -import os - -from setuptools import setup, find_packages - - -def fpath(name): - return os.path.join(os.path.dirname(__file__), name) - - -def read(fname): - return open(fpath(fname)).read() - - -def desc(): - info = read('README.md') - try: - return info + '\n\n' + read('doc/changelog.rst') - except IOError: - return info - - -# grep flask_admin/__init__.py since python 3.x cannot import it before using 2to3 -file_text = read(fpath('flask_admin/__init__.py')) - - -def grep(attrname): - pattern = r"{0}\W*=\W*'([^']+)'".format(attrname) - strval, = re.findall(pattern, file_text) - return strval - - -extras_require = { - 'aws': ['boto'], - 'azure': ['azure-storage-blob'] -} - - -install_requires = [ - 'Flask>=0.7', - 'wtforms' -] - - -setup( - name='Flask-Admin', - version=grep('__version__'), - url='https://github.com/flask-admin/flask-admin/', - license='BSD', - python_requires='>=3.6', - author=grep('__author__'), - author_email=grep('__email__'), - description='Simple and extensible admin interface framework for Flask', - long_description=desc(), - long_description_content_type='text/markdown', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - platforms='any', - extras_require=extras_require, - install_requires=install_requires, - tests_require=[ - 'pytest', - 'pillow>=9.1.0', - 'mongoengine', - 'pymongo', - 'wtf-peewee', - 'sqlalchemy', - 'flask-mongoengine<=0.21.0', - 'flask-sqlalchemy', - 'flask-babelex', - 'shapely', - 'geoalchemy2', - 'psycopg2', - ], - classifiers=[ - 'Development Status :: 4 - Beta', - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Software Development :: Libraries :: Python Modules', - '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', - 'Programming Language :: Python :: 3.11', - ], - test_suite='flask_admin.tests' -) diff --git a/tox.ini b/tox.ini index 2580f9ed1..fd5cfd08b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,34 +1,62 @@ [tox] envlist = - WTForms{1,2} - py38-WTForms2 - flake8 - docs-html -skipsdist = true + py3{7,8,9,10,11}-flask{2}-wtforms{2} + style + typing + docs skip_missing_interpreters = true -[flake8] -max_line_length = 120 -ignore = E402,E722,W504 - [testenv] +package = wheel +wheel_build_env = .pkg +constrain_package_deps = true +use_frozen_constraints = true +passenv = DYLD_LIBRARY_PATH +# TODO: Remove SQLALCHEMY_SILENCE_UBER_WARNINGwhen Flask-Admin is compatible with SQLAlchemy>=2.0.0 setenv = + SQLALCHEMY_SILENCE_UBER_WARNING = 1 AZURE_STORAGE_CONNECTION_STRING = DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1; -usedevelop = true deps = - WTForms1: WTForms==1.0.5 - WTForms2: WTForms>=2.0 - -r requirements-dev.txt -commands = - pytest -v flask_admin/tests --cov=flask_admin --cov-report=html + -r requirements/tests.in + flask2: Flask>=2.0.0,<3 + wtforms1: WTForms==1.0.5 + wtforms2: WTForms>=2.0,<3 +commands = pytest -v --tb=short --basetemp={envtmpdir} flask_admin/tests {posargs} -[testenv:flake8] -deps = flake8 -commands = flake8 flask_admin +[testenv:style] +deps = pre-commit +skip_install = true +commands = pre-commit run --all-files -[testenv:docs-html] -deps = - sphinx - sphinx-intl - -r requirements-dev.txt +[testenv:typing] +deps = -r requirements/typing.txt +commands = + mypy --python-version 3.7 + mypy --python-version 3.11 + +[testenv:docs] +deps = -r requirements/docs.txt +# commands = sphinx-build -E -W -b dirhtml doc doc/_build/dirhtml +# TODO: Switch to the above command when docs have been migrated to use the Pallets theme. commands = sphinx-build -b html -d build/doctrees doc build/html + +[testenv:update-pre_commit] +labels = update +deps = pre-commit +skip_install = true +commands = pre-commit autoupdate -j4 + +[testenv:update-requirements] +labels = update +deps = pip-tools +skip_install = true +change_dir = requirements +commands = + pip-compile build.in -q {posargs:-U} + pip-compile docs.in -q {posargs:-U} +; pip-compile tests.in -q {posargs:-U} +; # TODO: remove? There are a lot of test +; dependencies; unsure if we can compile these in a way that would work for all +; python versions supported? + pip-compile typing.in -q {posargs:-U} + pip-compile dev.in -q {posargs:-U}