Skip to content

Commit

Permalink
Add type checks and typing
Browse files Browse the repository at this point in the history
  • Loading branch information
fizyk committed Nov 27, 2020
1 parent 6fc559c commit fef7600
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 20 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/linters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,21 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: psf/black@stable

mypy:
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-lint.txt
- name: Run pydocstyle
run: |
mypy src/ tests/
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ __pycache__
# coverage
.coverage
coverage.xml

#typing
.pytype
26 changes: 26 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[mypy]
check_untyped_defs = True
show_error_codes = True
mypy_path = src
warn_unused_ignores = True
warn_redundant_casts = True
warn_unused_configs = True
disallow_untyped_calls = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
no_implicit_optional = False

[mypy-pytest.*]
ignore_missing_imports = True

[mypy-zope.*]
ignore_missing_imports = True

[mypy-sqlalchemy.*]
ignore_missing_imports = True

[mypy-pyramid.*]
ignore_missing_imports = True

[mypy-inflect.*]
ignore_missing_imports = True
1 change: 1 addition & 0 deletions requirements-lint.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
black==20.8b1
pydocstyle==5.1.1
pycodestyle==2.6.0
mypy==0.790
61 changes: 61 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,64 @@ ignore = D203,D212
[pycodestyle]
max-line-length = 120
exclude = docs/*,build/*,venv/*
# NOTE: All relative paths are relative to the location of this file.

[pytype]

# Space-separated list of files or directories to exclude.
exclude =
**/*_test.py
**/test_*.py

# Space-separated list of files or directories to process.
inputs =
src/

# Keep going past errors to analyze as many files as possible.
keep_going = False

# Run N jobs in parallel. When 'auto' is used, this will be equivalent to the
# number of CPUs on the host system.
jobs = 4

# All pytype output goes here.
output = .pytype

# Paths to source code directories, separated by ':'.
pythonpath =
.

# Python version (major.minor) of the target code.
python_version = 3.8

# Check attribute values against their annotations. This flag is temporary and
# will be removed once this behavior is enabled by default.
check_attribute_types = False

# Check container mutations against their annotations. This flag is temporary
# and will be removed once this behavior is enabled by default.
check_container_types = False

# Check parameter defaults and assignments against their annotations. This flag
# is temporary and will be removed once this behavior is enabled by default.
check_parameter_types = False

# Check variable values against their annotations. This flag is temporary and
# will be removed once this behavior is enabled by default.
check_variable_types = False

# Comma or space separated list of error names to ignore.
disable =
pyi-error

# Don't report errors.
report_errors = True

# Experimental: Infer precise return types even for invalid function calls.
precise_return = False

# Experimental: solve unknown types to label with structural types.
protocols = False

# Experimental: Only load submodules that are explicitly imported.
strict_import = False
12 changes: 7 additions & 5 deletions src/pyramid_basemodel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class and ``bind_engine`` function.
"bind_engine",
]

from typing import Any, Type, Callable

import inflect
from datetime import datetime

Expand All @@ -52,17 +54,17 @@ class and ``bind_engine`` function.
classImplements(Base, IDeclarativeBase)


class classproperty(object):
class classproperty:
"""A basic [class property](http://stackoverflow.com/a/3203659)."""

def __init__(self, getter):
def __init__(self, getter: Callable[..., Any]) -> None:
self.getter = getter

def __get__(self, instance, owner):
def __get__(self, instance: "BaseMixin", owner: Type["BaseMixin"]) -> Any:
return self.getter(owner)


class BaseMixin(object):
class BaseMixin:
"""
Default Base Model Mixin.
Expand All @@ -85,7 +87,7 @@ class BaseMixin(object):
query = Session.query_property()

@classproperty
def class_name(cls):
def class_name(cls) -> str:
"""
Determine class name based on the _class_name or the __tablename__.
Expand Down
25 changes: 10 additions & 15 deletions src/pyramid_basemodel/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,39 @@
import os
import logging
from binascii import hexlify
from typing import Callable, Union, Any, Optional

from sqlalchemy import schema
from sqlalchemy import schema, Column
from sqlalchemy.orm import Query

logger = logging.getLogger(__name__)


def generate_random_digest(num_bytes=28, urandom=None, to_hex=None):
UrandFuncT = Callable[[int], bytes]
HexlifyFuncT = Callable[..., bytes]

def generate_random_digest(num_bytes: int=28, urandom: UrandFuncT=os.urandom, to_hex: HexlifyFuncT=hexlify) -> str:
"""
Generate a random hash and returns the hex digest as a unicode string.
:param num_bytes: number of bytes to random(select)
:param urandom: urandom function
:param to_hex: hexifying function
"""
# Compose.
if urandom is None:
urandom = os.urandom
if to_hex is None:
to_hex = hexlify

# Get random bytes.
r = urandom(num_bytes)

# Return as a unicode string.
# Return as a string.
return to_hex(r).decode("utf-8")


def ensure_unique(self, query, property_, value, max_iter=30, gen_digest=None):
def ensure_unique(self, query: Query, property_: Column, value: str, max_iter: int=30, gen_digest=generate_random_digest) -> str:
"""
Make sure slug is unique.
Takes a ``candidate`` value for a unique ``property_`` and iterates,
appending an incremented integer until unique.
"""
# Compose.
if gen_digest is None:
gen_digest = generate_random_digest

# Unpack
candidate = value

Expand All @@ -58,7 +53,7 @@ def ensure_unique(self, query, property_, value, max_iter=30, gen_digest=None):
if instance != self:
existing = instance
break
if existing and n < 30:
if existing and n < max_iter:
n += 1
# If we've tried 1, 2 ... all the way to ``max_iter``, then
# fallback on appending a random digest rather than a sequential
Expand Down

0 comments on commit fef7600

Please sign in to comment.