Compare commits

...

8 Commits

Author SHA1 Message Date
daniil-berg 3f0e3db427 👷 Use own workflow for creating and publishing new releases 2023-04-26 17:04:12 +02:00
daniil-berg 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
daniil-berg 3c16b4ebd6 📌 Restrict main dependency marshmallow>=3.12.0;
pin all development dependencies to specific versions
2023-04-21 15:47:03 +02:00
daniil-berg 0e443f08b9 👷 Use own Python testing workflow from reusable-workflows repository 2023-04-21 15:14:37 +02:00
daniil-berg 2ca7ccde4a 🔨 Rework and refactor development scripts 2023-04-21 15:09:09 +02:00
daniil-berg a7caa876c4 🔧 Fix minor cosmetic issues in project config 2023-04-21 15:08:55 +02:00
daniil-berg da244e15ba 👷 Add Github CI workflow for unit testing and linting 2023-03-13 20:59:42 +01:00
daniil-berg 485aea005e 🔖 v1.0.0 2023-03-13 20:27:09 +01:00
12 changed files with 137 additions and 60 deletions
+34
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
+13 -16
View File
@@ -20,7 +20,7 @@ keywords = [
] ]
license = { text = "Apache Software License Version 2.0" } license = { text = "Apache Software License Version 2.0" }
classifiers = [ classifiers = [
"Development Status :: 4 - Beta", "Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
@@ -37,28 +37,25 @@ dynamic = [
] ]
[project.optional-dependencies] [project.optional-dependencies]
full = [
]
dev = [ dev = [
"black", "black==23.3.0",
"build", "build==0.10.0",
"coverage[toml]", "coverage[toml]==7.2.3",
"isort", "isort==5.12.0",
"mkdocs-material", "mkdocs-material==9.1.6",
"mkdocstrings[python]", "mkdocstrings[python]==0.21.2",
"mypy", "mypy==1.2.0",
"ruff", "ruff==0.0.262",
] ]
[project.urls] [project.urls]
repository = "https://github.com/daniil-berg/marshmallow-generic" "Repository" = "https://github.com/daniil-berg/marshmallow-generic"
bug_tracker = "https://github.com/daniil-berg/marshmallow-generic/issues" "Issue Tracker" = "https://github.com/daniil-berg/marshmallow-generic/issues"
documentation = "http://daniil-berg.github.io/marshmallow-generic" "Documentation" = "http://daniil-berg.github.io/marshmallow-generic"
[tool.setuptools.dynamic] [tool.setuptools.dynamic]
dependencies = { file = "requirements/common.txt" } dependencies = { file = "requirements/common.txt" }
readme = { file = ["README.md"] } readme = { file = ["README.md"], content-type = "text/markdown" }
version = { attr = "marshmallow_generic.__version__" } version = { attr = "marshmallow_generic.__version__" }
######################### #########################
+1 -1
View File
@@ -1 +1 @@
marshmallow marshmallow>=3.12.0
+8 -8
View File
@@ -1,9 +1,9 @@
-r common.txt -r common.txt
black black==23.3.0
build build==0.10.0
coverage[toml] coverage[toml]==7.2.3
isort isort==5.12.0
mkdocs-material mkdocs-material==9.1.6
mkdocstrings[python] mkdocstrings[python]==0.21.2
mypy mypy==1.2.0
ruff ruff==0.0.262
Executable
+12
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}"
Executable
+10
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}'
+11 -12
View File
@@ -1,18 +1,17 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Runs type checker and linters. # Runs various linters.
# Ensure that we return to the current working directory source "$(dirname $(realpath $0))/util.sh"
# 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
echo 'Linting source and test files...' echo 'Linting source and test files...'
echo ' isort - consistent imports'
isort src/ tests/ --check-only isort src/ tests/ --check-only
echo ' ruff - extensive linting'
ruff src/ tests/ 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"
+9 -14
View File
@@ -1,17 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Runs unit tests and prints only coverage percentage, if successful. # Runs unit tests and reports coverage percentage.
# If an error occurs, prints the entire unit tests progress output.
# Ensure that we return to the current working directory in case of an error: source "$(dirname $(realpath $0))/util.sh"
trap "cd $(realpath ${PWD})" ERR
# Change into project root directory:
cd "$(dirname $(dirname $(realpath $0)))"
coverage erase echo 'Running unit tests...'
# Capture the test progression in a variable: coverage run
typeset progress typeset percentage
progress=$(coverage run 2>&1) typeset color
# If tests failed or produced errors, write progress/messages to stderr and exit: percentage="$(coverage report | awk '$1 == "TOTAL" {print $NF; exit}')"
[[ $? -eq 0 ]] || { >&2 echo "${progress}"; exit 1; } [[ $percentage == "100%" ]] && color="${bold_green}" || color="${yellow}"
# Otherwise extract the total coverage percentage from the produced report and write it to stdout: echo -e "${color}${percentage} coverage${color_reset}\n"
coverage report | awk '$1 == "TOTAL" {print $NF; exit}'
+8
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
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'
+2 -4
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 See the License for the specific language governing permissions and
limitations under the License.""" limitations under the License."""
__version__ = "0.0.1" __version__ = "1.0.0"
__doc__ = """ __doc__ = """
Generic schema with full typing support and minimal boilerplate. Generic schema with full typing support and minimal boilerplate.
@@ -42,10 +42,8 @@ __all__ = [
] ]
from marshmallow import fields from marshmallow import fields
from marshmallow.decorators import ( # `post_load` overloaded
from marshmallow.decorators import (
post_dump, post_dump,
# post_load, # overloaded
pre_dump, pre_dump,
pre_load, pre_load,
validates, validates,
+8 -4
View File
@@ -16,6 +16,11 @@ from .decorators import post_load
Model = TypeVar("Model") 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): class GenericSchema(GenericInsightMixin1[Model], Schema):
""" """
@@ -100,6 +105,7 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
[`load`][marshmallow_generic.GenericSchema.load]/ [`load`][marshmallow_generic.GenericSchema.load]/
[`loads`][marshmallow_generic.GenericSchema.loads]. [`loads`][marshmallow_generic.GenericSchema.loads].
""" """
self._pre_init = True
super().__init__( super().__init__(
only=only, only=only,
exclude=exclude, exclude=exclude,
@@ -110,6 +116,7 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
partial=partial, partial=partial,
unknown=unknown, unknown=unknown,
) )
self._pre_init = False
def __setattr__(self, name: str, value: Any) -> None: 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__). [`object.__setattr__`](https://docs.python.org/3/reference/datamodel.html#object.__setattr__).
""" """
if name == "many" and value is not False: if name == "many" and value is not False:
warn( warn(MANY_SCHEMA_UNSAFE, stacklevel=4 if self._pre_init else 2)
"Changing `many` schema-wide breaks type safety. Use the the "
"`many` parameter of specific methods (like `load`) instead."
)
super().__setattr__(name, value) super().__setattr__(name, value)
@post_load @post_load