Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A basic support for dataclasses #1

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 20 additions & 35 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,28 @@ name: test
on: [push, pull_request]

jobs:
build:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8]
python-version: ["3.8", "3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: Install poetry
run: |
curl -sSL \
"https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py" | python

# Adding `poetry` to `$PATH`:
echo "$HOME/.poetry/bin" >> $GITHUB_PATH

- name: Set up cache
uses: actions/cache@v2
with:
path: .venv
key: venv-${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}
- name: Install dependencies
run: |
poetry config virtualenvs.in-project true
poetry install

- name: Run checks
run: |
make test

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
file: ./coverage.xml

- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- uses: actions/cache@v2
with:
path: .venv
key: venv-${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}
- uses: arduino/setup-task@v1
with:
repo-token: ${{ github.token }}
- run: task install-poetry
- run: poetry config virtualenvs.in-project true
- run: task lint
- run: task test
- uses: codecov/codecov-action@v1
with:
file: ./coverage.xml
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ $RECYCLE.BIN/
__pycache__/
*.py[cod]
*$py.class
.*_cache/
/.task/

# C extensions
*.so
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ We use `mypy` to run type checks on our code.
To use it:

```bash
mypy mappings tests/**/*.py
mypy typed_dict tests/**/*.py
```

This step is mandatory during the CI.
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of mappings nor the names of its contributors
* Neither the name of typed_dict nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ SHELL:=/usr/bin/env bash

.PHONY: lint
lint:
poetry run mypy mappings tests/**/*.py
poetry run mypy
poetry run flake8 .
poetry run doc8 -q docs

Expand Down
57 changes: 51 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,66 @@
# mappings
# typed_dict

[![Build Status](https://travis-ci.com/dry-python/mappings.svg?branch=master)](https://travis-ci.com/dry-python/mappings)
[![Coverage](https://coveralls.io/repos/github/dry-python/mappings/badge.svg?branch=master)](https://coveralls.io/github/dry-python/mappings?branch=master)
[![Python Version](https://img.shields.io/pypi/pyversions/mappings.svg)](https://pypi.org/project/mappings/)
[![Python Version](https://img.shields.io/pypi/pyversions/typed-dict.svg)](https://pypi.org/project/typed-dict/)
[![wemake-python-styleguide](https://img.shields.io/badge/style-wemake-000000.svg)](https://github.com/wemake-services/wemake-python-styleguide)

Like attrs, but for dicts

What it can do:

## Features
+ Generate [TypedDict](https://docs.python.org/3/library/typing.html#typing.TypedDict) from pydantic models, attrs schemas, and dataclasses.
+ Validate dicts using TypedDict at runtime.

- Fully typed with annotations and checked with mypy, [PEP561 compatible](https://www.python.org/dev/peps/pep-0561/)
- Add yours!
Why it's cool:

+ Supports multiple schema formats.
+ 100% type safe.

## Installation

Install:

```bash
pip install mappings
python3 -m pip install typed-dict
```

And then add in `plugins` section of the [mypy config](https://mypy.readthedocs.io/en/stable/config_file.html) (`pyproject.toml`):

```toml
[tool.mypy]
plugins = ["typed_dict"]
```

## Usage

Generate a TypedDict from a [dataclass](https://docs.python.org/3/library/dataclasses.html):

```python
from dataclasses import dataclass
import typed_dict

@dataclass
class User:
name: str
age: int = 99

UserDict = typed_dict.from_dataclass(User)
```

Now, you can use it in type annotations (and [mypy](https://mypy-lang.org/) will understand it):

```python
user: UserDict = {'name': 'aragorn'}
```

Or with runtime type checkers, like [pydantic](https://github.com/pydantic/pydantic):

```python
import pydantic

user = pydantic.parse_obj_as(UserDict, {'name': 'Aragorn'})
assert user == {'name': 'Aragorn'}
```

See [examples](./examples/) directory for more code.
89 changes: 89 additions & 0 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# https://taskfile.dev

version: "3"

tasks:
install-poetry:
status:
- which poetry
cmds:
- curl -sSL https://install.python-poetry.org | python3 -

install:
sources:
- pyproject.toml
- poetry.lock
deps:
- install-poetry
cmds:
- poetry install

pytest:
deps:
- install
cmds:
- poetry run pytest {{.CLI_ARGS}}

mypy:
deps:
- install
cmds:
- >
poetry run mypy
--show-traceback
--enable-incomplete-feature=Unpack
{{.CLI_ARGS}}

flake8:
deps:
- install
cmds:
- poetry run flake8 {{.CLI_ARGS}} .

isort:
deps:
- install
cmds:
- poetry run isort {{.CLI_ARGS}} .

poetry-check:
sources:
- pyproject.toml
deps:
- install-poetry
cmds:
- poetry check

pip-check:
sources:
- pyproject.toml
- poetry.lock
deps:
- install
cmds:
- poetry run pip check

format:
desc: run all code formatters
cmds:
- task: isort

lint:
desc: run all linters
cmds:
- task: mypy
- task: flake8
- task: poetry-check
- task: pip-check

test:
desc: run all tests
cmds:
- task: pytest

all:
desc: run all code formatters, linters, and tests
cmds:
- task: format
- task: lint
- task: test
7 changes: 3 additions & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import os
import sys
from pathlib import Path

import tomlkit

Expand All @@ -23,9 +24,8 @@
# -- Project information -----------------------------------------------------

def _get_project_meta():
with open('../pyproject.toml') as pyproject:
file_contents = pyproject.read()

path = Path(__file__).parent.parent / 'pyproject.toml'
file_contents = path.read_text()
return tomlkit.parse(file_contents)['tool']['poetry']


Expand Down Expand Up @@ -117,7 +117,6 @@ def _get_project_meta():

# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'

# Theme options are theme-specific and customize the look and feel of a theme
Expand Down
34 changes: 34 additions & 0 deletions examples/safe_dataclass_replace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Type-safe `dataclass.replace`.

This example shows how you can make a type-safe version of `dataclass.replace`
that will report if you try passing an argument that isn't a valid dataclass field
or has invalid type.
"""
from __future__ import annotations

import dataclasses

from typing_extensions import Unpack

import typed_dict


@dataclasses.dataclass
class _User:
name: str
age: int = 99


UserDict = typed_dict.from_dataclass(_User)


class User(_User):
# The `Unpack` support in mypy is experimental, so you need to run mypy with
# `--enable-incomplete-feature=Unpack` for it to work.
def evolve(self, **kwargs: Unpack[UserDict]) -> User:
"""Create a copy of User with the given fields replaced."""
return dataclasses.replace(self, **kwargs)


user = User(name='Aragorn')
user = user.evolve(age=88) # noqa: WPS432
19 changes: 19 additions & 0 deletions examples/use_pydantic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from __future__ import annotations

from dataclasses import dataclass

import pydantic

import typed_dict


@dataclass
class User:
name: str
age: int = 99


UserDict = typed_dict.from_dataclass(User)

res = pydantic.parse_obj_as(UserDict, {'name': 'Aragorn'})
assert res == {'name': 'Aragorn'}
1 change: 0 additions & 1 deletion mappings/__init__.py

This file was deleted.

17 changes: 0 additions & 17 deletions mappings/example.py

This file was deleted.

Loading