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 =