diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 00000000..683c85be --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,16 @@ +name: pre-commit +on: + pull_request: + push: + branches: [main, '*.x'] +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + with: + python-version: 3.x + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + - uses: pre-commit-ci/lite-action@9d882e7a565f7008d4faf128f27d1cb6503d4ebf # v1.0.2 + if: ${{ !cancelled() }} diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 00000000..4f2a6ae6 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,72 @@ +name: Publish +on: + push: + tags: + - '*' +jobs: + build: + runs-on: ubuntu-latest + outputs: + hash: ${{ steps.hash.outputs.hash }} + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + with: + python-version: '3.x' + cache: pip + - run: pip install -e . + # 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@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + 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@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + - 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/wtforms/${{ github.ref_name }} + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + - uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0 + with: + repository-url: https://test.pypi.org/legacy/ + packages-dir: artifact/ + - uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0 + with: + packages-dir: artifact/ diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b71a289c..b1f9216b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,66 +1,37 @@ ---- -name: tests +name: Tests on: push: branches: - main - '*.x' + paths-ignore: + - 'docs/**' + - '*.md' + - '*.rst' pull_request: - branches: - - main - - '*.x' + paths-ignore: + - 'docs/**' + - '*.md' + - '*.rst' jobs: tests: - name: ${{ matrix.python }} - runs-on: ubuntu-latest + name: ${{ matrix.name || matrix.python }} + runs-on: ${{ matrix.os || 'ubuntu-latest' }} strategy: fail-fast: false matrix: - python: - - '3.13' - - '3.12' - - '3.11' - - '3.10' - - '3.9' - - 'pypy-3.10' + include: + - {python: '3.13'} + - {python: '3.12'} + - {python: '3.11'} + - {python: '3.10'} + - {python: '3.9'} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 with: python-version: ${{ matrix.python }} - - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: pip|${{ hashFiles('setup.py') }}|${{ hashFiles('tox.ini') }} - - run: pip install tox - - run: tox -e py - style: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: '3.13' - - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: pip|${{ hashFiles('setup.py') }}|${{ hashFiles('tox.ini') }} - - uses: actions/cache@v1 - with: - path: ~/.cache/pre-commit - key: pre-commit|${{ hashFiles('.pre-commit-config.yaml') }} - - run: pip install tox - - run: tox -e style - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: '3.13' - - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: pip|${{ hashFiles('setup.py') }}|${{ hashFiles('tox.ini') }} + allow-prereleases: true + cache: pip - run: pip install tox - - run: tox -e docs + - run: tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }} diff --git a/src/wtforms/fields/datetime.py b/src/wtforms/fields/datetime.py index 63e32d77..ac22951b 100644 --- a/src/wtforms/fields/datetime.py +++ b/src/wtforms/fields/datetime.py @@ -34,7 +34,8 @@ def __init__( def _value(self): if self.raw_data: return " ".join(self.raw_data) - return self.data and self.data.strftime(self.format[0]) or "" + format = self.format[0] + return self.data and self.data.strftime(format) or "" def process_formdata(self, valuelist): if not valuelist: diff --git a/src/wtforms/utils.py b/src/wtforms/utils.py index ff6597c5..51bbe8a9 100644 --- a/src/wtforms/utils.py +++ b/src/wtforms/utils.py @@ -1,8 +1,11 @@ +import os import re +_LEADING_SYMBOL = "#" if os.name == "nt" else "-" + # https://docs.python.org/3/library/datetime.html#technical-detail (see NOTE #9) _DATETIME_STRIP_ZERO_PADDING_FORMATS_RE = re.compile( - "%-[" + f"%{_LEADING_SYMBOL}[" "d" # day of month "m" # month "H" # hour (24-hour) @@ -25,7 +28,7 @@ def clean_datetime_format_for_strptime(formats): return [ re.sub( _DATETIME_STRIP_ZERO_PADDING_FORMATS_RE, - lambda m: m[0].replace("-", ""), + lambda m: m[0].replace(_LEADING_SYMBOL, ""), format, ) for format in formats diff --git a/tests/fields/test_datetime.py b/tests/fields/test_datetime.py index 08a3f970..4997e54c 100644 --- a/tests/fields/test_datetime.py +++ b/tests/fields/test_datetime.py @@ -1,3 +1,4 @@ +import os from datetime import datetime from tests.common import DummyPostData @@ -12,27 +13,29 @@ def make_form(name="F", **fields): class F(Form): a = DateTimeField() b = DateTimeField(format="%Y-%m-%d %H:%M") - c = DateTimeField(format="%-m/%-d/%Y %-I:%M") + c = DateTimeField( + format="%#m/%#d/%Y %#I:%M" if os.name == "nt" else "%-m/%-d/%Y %-I:%M" + ) def test_basic(): d = datetime(2008, 5, 5, 4, 30, 0, 0) # Basic test with both inputs - form = F( - DummyPostData( - a=["2008-05-05", "04:30:00"], b=["2008-05-05 04:30"], c=["5/5/2008 4:30"] - ) - ) + form = F(DummyPostData(a=["2008-05-05", "04:30:00"])) assert form.a.data == d assert ( form.a() == """""" ) + + form = F(DummyPostData(b=["2008-05-05 04:30"])) assert form.b.data == d assert ( form.b() == """""" ) + + form = F(DummyPostData(c=["5/5/2008 4:30"])) assert form.c.data == d assert ( form.c() == """""" diff --git a/tests/fields/test_datetimelocal.py b/tests/fields/test_datetimelocal.py index 4be529fc..8c73dd9c 100644 --- a/tests/fields/test_datetimelocal.py +++ b/tests/fields/test_datetimelocal.py @@ -1,3 +1,4 @@ +import os from datetime import datetime from tests.common import DummyPostData @@ -12,7 +13,9 @@ def make_form(name="F", **fields): class F(Form): a = DateTimeLocalField() b = DateTimeLocalField(format="%Y-%m-%d %H:%M") - c = DateTimeLocalField(format="%-m/%-d/%Y %-I:%M") + c = DateTimeLocalField( + format="%#m/%#d/%Y %#I:%M" if os.name == "nt" else "%-m/%-d/%Y %-I:%M" + ) def test_basic(): diff --git a/tox.ini b/tox.ini index 85c8a789..6127485f 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ deps = babel email_validator commands = - pytest --tb=short --basetemp={envtmpdir} {posargs} + pytest --tb=short -l --basetemp={envtmpdir} {posargs} [testenv:coverage] deps =