Skip to content

Commit

Permalink
Merge pull request #49 from mdekstrand/tweak/pyright
Browse files Browse the repository at this point in the history
Use Pyright for typechecking
  • Loading branch information
mdekstrand authored Dec 18, 2023
2 parents c696fdd + 37812b7 commit 5caa916
Show file tree
Hide file tree
Showing 14 changed files with 170 additions and 111 deletions.
3 changes: 2 additions & 1 deletion .copier-answers.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Changes here will be overwritten by Copier
_commit: ccc299c
_commit: 8d75d6c
_src_path: https://github.com/lenskit/lk-project-template
package_name: binpickle
project_descr: Optimized format for pickling binary data.
project_name: binpickle
project_title: BinPickle
require_lint: true
start_year: 2020
typecheck: true
114 changes: 61 additions & 53 deletions .github/workflows/check-sources.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,64 +11,72 @@ concurrency:

jobs:
lint:
name: Check Source Style
name: Check Source Code
runs-on: ubuntu-latest

steps:
- name: 📥 Check out source code
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: 📥 Check out source code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: 🐍 Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
cache: 'pip'
- name: 🐍 Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
cache: "pip"

- name: 🛠️ Install tools
run: |
pip install ruff
- name: 🛠️ Install development tools and dependencies
run: |
pip install -e .[dev]
- name: 🪮 Check source code formatting
id: format
run: |
if pipx run ruff format --diff $PKG_DIR; then
echo passed=yes >>"$GITHUB_OUTPUT"
else
echo passed=no >>"$GITHUB_OUTPUT"
echo "::error::source code not formatted"
fi
env:
PKG_DIR: binpickle
- name: 🪮 Check source code formatting
id: format
run: |
if ruff format --diff $PKG_DIR; then
echo passed=yes >>"$GITHUB_OUTPUT"
else
echo passed=no >>"$GITHUB_OUTPUT"
echo "::error::source code not formatted"
fi
env:
PKG_DIR: binpickle

- name: 🐜 Check source code lint rules
id: lint
run: |
if pipx run ruff check --output-format=github $PKG_DIR; then
echo passed=yes >>"$GITHUB_OUTPUT"
else
echo passed=no >>"$GITHUB_OUTPUT"
echo "::error::source code lint check failed"
fi
env:
PKG_DIR: binpickle
- name: 🐜 Check source code lint rules
id: lint
run: |
if ruff check --output-format=github $PKG_DIR; then
echo passed=yes >>"$GITHUB_OUTPUT"
else
echo passed=no >>"$GITHUB_OUTPUT"
echo "::error::source code lint check failed"
fi
env:
PKG_DIR: binpickle

- name: 🧾 Checking results
run: |
if [ "$FMT_PASSED" = no ]; then
echo "::error::format failed, failing build"
exit 1
fi
if [ "$LINT_PASSED" = no ]; then
if [ "$LINT_REQUIRED" = true ]; then
echo "::error::lint failed, failing build"
exit 2
else
echo "::error::lint failed but non-mandatory"
fi
fi
env:
FMT_PASSED: ${{ steps.fmt.outputs.passed }}
LINT_PASSED: ${{ steps.lint.outputs.passed }}
LINT_REQUIRED: True
- name: 🧾 Checking lint and format results
run: |
if [ "$FMT_PASSED" = no ]; then
echo "::error::format failed, failing build"
exit 1
fi
if [ "$LINT_PASSED" = no ]; then
if [ "$LINT_REQUIRED" = true ]; then
echo "::error::lint failed, failing build"
exit 2
else
echo "::error::lint failed but non-mandatory"
fi
fi
env:
FMT_PASSED: ${{ steps.fmt.outputs.passed }}
LINT_PASSED: ${{ steps.lint.outputs.passed }}
LINT_REQUIRED: True


- name: 📐 Check types
id: typecheck
uses: jakebailey/pyright-action@v1
with:
extra-args: binpickle

15 changes: 14 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
{
"mypy-type-checker.reportingScope": "workspace",
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"editor.formatOnSave": true,
},
"[jsonc]": {
"editor.formatOnSave": true,
},
"[yaml]": {
"editor.defaultFormatter": "redhat.vscode-yaml",
"editor.formatOnSave": true,
},
"[toml]": {
"editor.defaultFormatter": "tamasfe.even-better-toml",
"editor.formatOnSave": true,
},
}
6 changes: 3 additions & 3 deletions binpickle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
Optimized format for pickling binary data.
"""

from importlib.metadata import version, PackageNotFoundError
from importlib.metadata import PackageNotFoundError, version

from .write import dump, BinPickler
from .read import load, BinPickleFile, file_info
from .read import BinPickleFile, file_info, load
from .write import BinPickler, dump

try:
__version__ = version("binpickle")
Expand Down
4 changes: 3 additions & 1 deletion binpickle/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
Internal utility functions for Binpickle.
"""
from __future__ import annotations
from typing import Optional, Any

import hashlib
from typing import Any, Optional

from typing_extensions import Buffer

naturalsize: Optional[Any]
Expand Down
5 changes: 3 additions & 2 deletions binpickle/encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
"""

from __future__ import annotations
from typing import Optional, TypeAlias, Callable, overload
from typing_extensions import Buffer

from typing import Callable, Optional, TypeAlias, overload

from numcodecs.abc import Codec
from numcodecs.registry import get_codec
from typing_extensions import Buffer

from binpickle.format import CodecSpec

Expand Down
17 changes: 10 additions & 7 deletions binpickle/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
Constants and functions defining the binpickle format.
"""

from dataclasses import dataclass, field, fields
import struct
from typing import TypeAlias
from __future__ import annotations

import enum
import io
import struct
from dataclasses import dataclass, field, fields
from typing import Any, TypeAlias

from binpickle.errors import FormatError

Expand Down Expand Up @@ -71,7 +74,7 @@ def encode(self):
return HEADER_FORMAT.pack(MAGIC, self.version, self.flags._value_, self.length)

@classmethod
def decode(cls, buf: bytes, *, verify=True):
def decode(cls, buf: bytes | bytearray | memoryview, *, verify: bool = True) -> FileHeader:
"Decode a file header from bytes."
if len(buf) != HEADER_FORMAT.size:
raise FormatError("incorrect header length")
Expand All @@ -88,7 +91,7 @@ def decode(cls, buf: bytes, *, verify=True):
return cls(v, flags, off)

@classmethod
def read(cls, file, **kwargs):
def read(cls, file: io.FileIO | io.BufferedReader, **kwargs: bool) -> FileHeader:
buf = file.read(HEADER_FORMAT.size)
return cls.decode(buf, **kwargs)

Expand Down Expand Up @@ -125,7 +128,7 @@ def encode(self):
return TRAILER_FORMAT.pack(self.offset, self.length, self.hash)

@classmethod
def decode(cls, buf, *, verify=True):
def decode(cls, buf: bytes | bytearray | memoryview, *, verify: bool = True) -> FileTrailer:
"Decode a file trailer from bytes."
off, len, ck = TRAILER_FORMAT.unpack(buf)
return cls(off, len, ck)
Expand Down Expand Up @@ -155,7 +158,7 @@ def to_repr(self):
return dict((f.name, getattr(self, f.name)) for f in fields(self))

@classmethod
def from_repr(cls, repr):
def from_repr(cls, repr: dict[str, Any]):
"Convert an index entry from its MsgPack-compatible representation"
if not isinstance(repr, dict):
raise TypeError("IndexEntry representation must be a dict")
Expand Down
35 changes: 19 additions & 16 deletions binpickle/read.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import sys
from dataclasses import dataclass
from enum import Enum
import hashlib
import mmap
import logging
import io
from os import PathLike
from typing import Optional
from typing_extensions import Buffer
import logging
import mmap
import pickle
import sys
import warnings
from dataclasses import dataclass
from enum import Enum
from os import PathLike
from typing import Any, Optional

import msgpack
from typing_extensions import Buffer

from binpickle.encode import resolve_codec
from binpickle.errors import BinPickleError, FormatError, FormatWarning, IntegrityError

from .format import FileHeader, Flags, IndexEntry, FileTrailer
from ._util import hash_buffer
from .format import FileHeader, FileTrailer, Flags, IndexEntry

_log = logging.getLogger(__name__)

Expand Down Expand Up @@ -54,7 +55,7 @@ class BinPickleFile:
If ``True`` (the default), verify file checksums while reading.
"""

filename: str | PathLike
filename: str | PathLike[str]
direct: bool | str
verify: bool
header: FileHeader
Expand All @@ -64,7 +65,9 @@ class BinPickleFile:
_index_buf: Optional[memoryview]
entries: list[IndexEntry]

def __init__(self, filename, *, direct: bool | str = False, verify: bool = True):
def __init__(
self, filename: str | PathLike[str], *, direct: bool | str = False, verify: bool = True
):
self.filename = filename
self.direct = direct
self.verify = verify
Expand All @@ -77,7 +80,7 @@ def __init__(self, filename, *, direct: bool | str = False, verify: bool = True)
def __enter__(self):
return self

def __exit__(self, *args):
def __exit__(self, *args: Any):
self.close()
return False

Expand Down Expand Up @@ -110,7 +113,7 @@ def find_errors(self) -> list[str]:
invalid msgpack formats in the index won't be detected here. This method checks
buffer hashes, offset overlaps, and such.
"""
errors = []
errors: list[str] = []
assert self._index_buf is not None, "file not loaded"

i_sum = hashlib.sha256(self._index_buf).digest()
Expand Down Expand Up @@ -173,7 +176,7 @@ def _read_index(self) -> None:
self._index_buf = None
raise e

self.entries = [IndexEntry.from_repr(e) for e in msgpack.unpackb(self._index_buf)]
self.entries = [IndexEntry.from_repr(e) for e in msgpack.unpackb(self._index_buf)] # type: ignore
_log.debug("read %d entries from file", len(self.entries))

def _read_buffer(
Expand Down Expand Up @@ -219,7 +222,7 @@ def _verify_buffer(self, buf: memoryview, hash: bytes, msg: str = "buffer"):
raise IntegrityError(f"{msg} has incorrect hash, corrupt file?")


def load(file: str | PathLike) -> object:
def load(file: str | PathLike[str]) -> object:
"""
Load an object from a BinPickle file.
Expand All @@ -231,7 +234,7 @@ def load(file: str | PathLike) -> object:
return bpf.load()


def file_info(file: str | PathLike) -> BPKInfo:
def file_info(file: str | PathLike[str]) -> BPKInfo:
"""
Test whether a file is a BinPickle file, and if so, return basic information
about it.
Expand Down
Loading

0 comments on commit 5caa916

Please sign in to comment.