Compare commits

...

8 Commits

Author SHA1 Message Date
3f0e3db427 👷 Use own workflow for creating and publishing new releases 2023-04-26 17:04:12 +02:00
862a517018 🚨 Set explicit stacklevel when warning about schema-wide many setting depending on initialization state.
This should help users see the exact line they wrote that triggered the warning.
2023-04-21 15:52:33 +02:00
3c16b4ebd6 📌 Restrict main dependency marshmallow>=3.12.0;
pin all development dependencies to specific versions
2023-04-21 15:47:03 +02:00
0e443f08b9 👷 Use own Python testing workflow from reusable-workflows repository 2023-04-21 15:14:37 +02:00
2ca7ccde4a 🔨 Rework and refactor development scripts 2023-04-21 15:09:09 +02:00
a7caa876c4 🔧 Fix minor cosmetic issues in project config 2023-04-21 15:08:55 +02:00
da244e15ba 👷 Add Github CI workflow for unit testing and linting 2023-03-13 20:59:42 +01:00
485aea005e 🔖 v1.0.0 2023-03-13 20:27:09 +01:00
12 changed files with 137 additions and 60 deletions

34
.github/workflows/ci.yaml vendored Normal file
View File

@ -0,0 +1,34 @@
name: CI
on:
push:
branches: master
tags: 'v*.*.*'
jobs:
test:
name: Test
uses: daniil-berg/reusable-workflows/.github/workflows/python-test.yaml@v0.2.1
with:
versions: '["3.9", "3.10", "3.11"]'
unittest-command: 'scripts/test.sh'
coverage-command: 'scripts/cov.sh'
unittest-requirements: "-e '.[dev]'"
typecheck-command: 'scripts/typecheck.sh'
typecheck-requirements: '-Ur requirements/dev.txt'
typecheck-all-versions: true
lint-command: 'scripts/lint.sh'
lint-requirements: '-Ur requirements/dev.txt'
release:
name: Release
if: ${{ github.ref_type == 'tag' }}
needs: test
uses: daniil-berg/reusable-workflows/.github/workflows/python-release.yaml@v0.2.1
with:
git-ref: ${{ github.ref_name }}
secrets:
release-token: ${{ secrets.TOKEN_GITHUB_CREATE_RELEASE }}
publish-token: ${{ secrets.TOKEN_PYPI_PROJECT }}
permissions:
contents: write

View File

@ -20,7 +20,7 @@ keywords = [
]
license = { text = "Apache Software License Version 2.0" }
classifiers = [
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
@ -37,29 +37,26 @@ dynamic = [
]
[project.optional-dependencies]
full = [
]
dev = [
"black",
"build",
"coverage[toml]",
"isort",
"mkdocs-material",
"mkdocstrings[python]",
"mypy",
"ruff",
"black==23.3.0",
"build==0.10.0",
"coverage[toml]==7.2.3",
"isort==5.12.0",
"mkdocs-material==9.1.6",
"mkdocstrings[python]==0.21.2",
"mypy==1.2.0",
"ruff==0.0.262",
]
[project.urls]
repository = "https://github.com/daniil-berg/marshmallow-generic"
bug_tracker = "https://github.com/daniil-berg/marshmallow-generic/issues"
documentation = "http://daniil-berg.github.io/marshmallow-generic"
"Repository" = "https://github.com/daniil-berg/marshmallow-generic"
"Issue Tracker" = "https://github.com/daniil-berg/marshmallow-generic/issues"
"Documentation" = "http://daniil-berg.github.io/marshmallow-generic"
[tool.setuptools.dynamic]
dependencies = { file = "requirements/common.txt" }
readme = { file = ["README.md"] }
version = {attr = "marshmallow_generic.__version__"}
readme = { file = ["README.md"], content-type = "text/markdown" }
version = { attr = "marshmallow_generic.__version__" }
#########################
# Static type checking: #

View File

@ -1 +1 @@
marshmallow
marshmallow>=3.12.0

View File

@ -1,9 +1,9 @@
-r common.txt
black
build
coverage[toml]
isort
mkdocs-material
mkdocstrings[python]
mypy
ruff
black==23.3.0
build==0.10.0
coverage[toml]==7.2.3
isort==5.12.0
mkdocs-material==9.1.6
mkdocstrings[python]==0.21.2
mypy==1.2.0
ruff==0.0.262

12
scripts/ci.sh Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Runs full CI pipeline (test, typecheck, lint).
typeset scripts_dir="$(dirname $(realpath $0))"
source "${scripts_dir}/util.sh"
"${scripts_dir}/test.sh"
"${scripts_dir}/typecheck.sh"
"${scripts_dir}/lint.sh"
echo -e "${background_black}${bold_green}✅ 🎉 All checks passed!${color_reset}"

10
scripts/cov.sh Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Runs unit tests.
# If successful, prints only the coverage percentage.
# If an error occurs, prints the entire unit tests progress output.
source "$(dirname $(realpath $0))/util.sh"
coverage erase
run_and_capture coverage run
coverage report | awk '$1 == "TOTAL" {print $NF; exit}'

View File

@ -1,18 +1,17 @@
#!/usr/bin/env bash
# Runs type checker and linters.
# Runs various linters.
# Ensure that we return to the current working directory
# and exit the script immediately in case of an error:
trap "cd $(realpath ${PWD}); exit 1" ERR
# Change into project root directory:
cd "$(dirname $(dirname $(realpath $0)))"
echo 'Performing type checks...'
mypy
echo
source "$(dirname $(realpath $0))/util.sh"
echo 'Linting source and test files...'
echo ' isort - consistent imports'
isort src/ tests/ --check-only
echo ' ruff - extensive linting'
ruff src/ tests/
black src/ tests/ --check
echo -e 'No issues found.'
echo ' black - consistent style'
run_and_capture black src/ tests/ --check
echo -e "${bold_green}No issues found${color_reset}\n"

View File

@ -1,17 +1,12 @@
#!/usr/bin/env bash
# Runs unit tests and prints only coverage percentage, if successful.
# If an error occurs, prints the entire unit tests progress output.
# Runs unit tests and reports coverage percentage.
# Ensure that we return to the current working directory in case of an error:
trap "cd $(realpath ${PWD})" ERR
# Change into project root directory:
cd "$(dirname $(dirname $(realpath $0)))"
source "$(dirname $(realpath $0))/util.sh"
coverage erase
# Capture the test progression in a variable:
typeset progress
progress=$(coverage run 2>&1)
# If tests failed or produced errors, write progress/messages to stderr and exit:
[[ $? -eq 0 ]] || { >&2 echo "${progress}"; exit 1; }
# Otherwise extract the total coverage percentage from the produced report and write it to stdout:
coverage report | awk '$1 == "TOTAL" {print $NF; exit}'
echo 'Running unit tests...'
coverage run
typeset percentage
typeset color
percentage="$(coverage report | awk '$1 == "TOTAL" {print $NF; exit}')"
[[ $percentage == "100%" ]] && color="${bold_green}" || color="${yellow}"
echo -e "${color}${percentage} coverage${color_reset}\n"

8
scripts/typecheck.sh Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Runs type checker.
source "$(dirname $(realpath $0))/util.sh"
echo 'Performing type checks...'
mypy
echo

20
scripts/util.sh Normal file
View File

@ -0,0 +1,20 @@
run_and_capture() {
# Captures stderr of any command passed to it
# and releases it only if the command exits with a non-zero code.
typeset output
output=$($@ 2>&1)
typeset exit_status=$?
[[ $exit_status == 0 ]] || >&2 echo "${output}"
return $exit_status
}
# Ensure that we return to the current working directory
# and exit the script immediately in case of an error:
trap "cd $(realpath ${PWD}); exit 1" ERR
# Change into project root directory:
cd "$(dirname $(dirname $(realpath $0)))"
typeset background_black='\033[40m'
typeset bold_green='\033[1;92m'
typeset yellow='\033[0;33m'
typeset color_reset='\033[0m'

View File

@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License."""
__version__ = "0.0.1"
__version__ = "1.0.0"
__doc__ = """
Generic schema with full typing support and minimal boilerplate.
@ -42,10 +42,8 @@ __all__ = [
]
from marshmallow import fields
from marshmallow.decorators import (
from marshmallow.decorators import ( # `post_load` overloaded
post_dump,
# post_load, # overloaded
pre_dump,
pre_load,
validates,

View File

@ -16,6 +16,11 @@ from .decorators import post_load
Model = TypeVar("Model")
MANY_SCHEMA_UNSAFE = (
"Changing `many` schema-wide breaks type safety. "
"Use the the `many` parameter of specific methods (like `load`) instead."
)
class GenericSchema(GenericInsightMixin1[Model], Schema):
"""
@ -100,6 +105,7 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
[`load`][marshmallow_generic.GenericSchema.load]/
[`loads`][marshmallow_generic.GenericSchema.loads].
"""
self._pre_init = True
super().__init__(
only=only,
exclude=exclude,
@ -110,6 +116,7 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
partial=partial,
unknown=unknown,
)
self._pre_init = False
def __setattr__(self, name: str, value: Any) -> None:
"""
@ -119,10 +126,7 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
[`object.__setattr__`](https://docs.python.org/3/reference/datamodel.html#object.__setattr__).
"""
if name == "many" and value is not False:
warn(
"Changing `many` schema-wide breaks type safety. Use the the "
"`many` parameter of specific methods (like `load`) instead."
)
warn(MANY_SCHEMA_UNSAFE, stacklevel=4 if self._pre_init else 2)
super().__setattr__(name, value)
@post_load