Skip to content

Commit

Permalink
feat(pydantic#Define fields to exclude from exporting at config level…
Browse files Browse the repository at this point in the history
…): Define fields to exclude from exporting at config level

Eagerly waiting for the release of the version 1.9 because you can [define the fields to exclude in the `Config` of the model](pydantic/pydantic#660) using something like:

```python
class User(BaseModel):
    id: int
    username: str
    password: str

class Transaction(BaseModel):
    id: str
    user: User
    value: int

    class Config:
        fields = {
            'value': {
                'alias': 'Amount',
                'exclude': ...,
            },
            'user': {
                'exclude': {'username', 'password'}
            },
            'id': {
                'dump_alias': 'external_id'
            }
        }
```

The release it's taking its time because [the developer's gremlin and salaried work are sucking his time off](pydantic/pydantic#3228).

feat(type_hints#Define a TypeVar with restrictions): Define a TypeVar with restrictions

```python
from typing import TypeVar

AnyStr = TypeVar('AnyStr', str, bytes)
```

feat(type_hints#Use a constrained TypeVar in the definition of a class attributes): Use a constrained TypeVar in the definition of a class attributes.

If you try to use a `TypeVar` in the definition of a class attribute:

```python
class File:
    """Model a computer file."""

    path: str
    content: Optional[AnyStr] = None # mypy error!
```

[mypy](mypy.md) will complain with `Type variable AnyStr is unbound
[valid-type]`, to solve it, you need to make the class inherit from the
`Generic[AnyStr]`.

```python
class File(Generic[AnyStr]):
    """Model a computer file."""

    path: str
    content: Optional[AnyStr] = None
```

feat(python_properties): Give an overview on Python's @Property decorator

fix(vim): Correct vim snippet to remember the folds when saving a file
  • Loading branch information
lyz-code committed Nov 18, 2021
1 parent 0d6fd56 commit a05fc16
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 2 deletions.
36 changes: 36 additions & 0 deletions docs/coding/python/pydantic.md
Expand Up @@ -495,6 +495,42 @@ print(m._secret_value)
#> 5
```

### Define fields to exclude from exporting at config level

This won't be necessary once they release the version 1.9 because you can [define
the fields to exclude in the `Config` of the
model](https://github.com/samuelcolvin/pydantic/issues/660) using something
like:

```python
class User(BaseModel):
id: int
username: str
password: str

class Transaction(BaseModel):
id: str
user: User
value: int

class Config:
fields = {
'value': {
'alias': 'Amount',
'exclude': ...,
},
'user': {
'exclude': {'username', 'password'}
},
'id': {
'dump_alias': 'external_id'
}
}
```

The release it's taking its time because [the developer's gremlin and salaried
work are sucking his time off](https://github.com/samuelcolvin/pydantic/discussions/3228).

## [Update entity attributes with a dictionary](https://pydantic-docs.helpmanual.io/usage/exporting_models/#modelcopy)

To update a model with the data of a dictionary you can create a new object with
Expand Down
83 changes: 83 additions & 0 deletions docs/coding/python/type_hints.md
Expand Up @@ -328,6 +328,89 @@ def new_users(user_class: UserTypes[UserType]) -> UserType: # OK!
pass
```

## [Define a TypeVar with restrictions](https://mypy.readthedocs.io/en/stable/generics.html#type-variables-with-value-restriction)

By default, a type variable can be replaced with any type. However, sometimes
it’s useful to have a type variable that can only have some specific types as
its value. A typical example is a type variable that can only have values `str`
and `bytes`:

```python
from typing import TypeVar

AnyStr = TypeVar('AnyStr', str, bytes)
```
This is actually such a common type variable that `AnyStr` is defined in typing
and we don’t need to define it ourselves.


We can use `AnyStr` to define a function that can concatenate two strings or
bytes objects, but it can’t be called with other argument types:

```python
from typing import AnyStr

def concat(x: AnyStr, y: AnyStr) -> AnyStr:
return x + y

concat('a', 'b') # Okay
concat(b'a', b'b') # Okay
concat(1, 2) # Error!
```

Note that this is different from a union type, since combinations of `str` and
`bytes` are not accepted:

```python
concat('string', b'bytes') # Error!
```

In this case, this is exactly what we want, since it’s not possible to
concatenate a string and a bytes object! The type checker will reject this
function:

```python
def union_concat(x: Union[str, bytes], y: Union[str, bytes]) -> Union[str, bytes]:
return x + y # Error: can't concatenate str and bytes
```

### Use a constrained TypeVar in the definition of a class attributes.

If you try to use a `TypeVar` in the definition of a class attribute:

```python
class File:
"""Model a computer file."""

path: str
content: Optional[AnyStr] = None # mypy error!
```

[mypy](mypy.md) will complain with `Type variable AnyStr is unbound
[valid-type]`, to solve it, you need to make the class inherit from the
`Generic[AnyStr]`.

```python
class File(Generic[AnyStr]):
"""Model a computer file."""

path: str
content: Optional[AnyStr] = None
```

Why you ask? I have absolutely no clue. I've asked that question in the
[gitter python typing channel](https://gitter.im/python/typing#) but the kind
answer that @ktbarrett gave me sounded like Chinese.

> You can't just use a type variable for attributes or variables, you have to
> create some generic context, whether that be a function or a class, so that
> you can instantiate the generic context (or the analyzer can infer it) (i.e.
> context[var]). That's not possible if you don't specify that the class is
> a generic context. It also ensure than all uses of that variable in the
> context resolve to the same type.
If you don't mind helping me understand it, please [contact me](contact.md).

## [Specify the type of the class in it's method and attributes](https://stackoverflow.com/questions/33533148/how-do-i-specify-that-the-return-type-of-a-method-is-the-same-as-the-class-itsel)

If you are using Python 3.10 or later, it just works.
Expand Down
172 changes: 172 additions & 0 deletions docs/python_properties.md
@@ -0,0 +1,172 @@
---
title: Python Properties
date: 20211118
author: Lyz
---

The `@property` is the pythonic way to use getters and setters in
object-oriented programming. It can be used to make methods look like attributes.

The `property` decorator returns an object that proxies any request to set or
access the attribute value through the methods we have specified.

```python
class Foo:
@property
def foo(self):
return 'bar'
```

We can specify a setter function for the new property

```python
class Foo:
@property
def foo(self):
return self._foo

@foo.setter
def foo(self, value):
self._foo = value
```

We first decorate the `foo` method a as getter. Then we decorate a second method
with exactly the same name by applying the `setter` attribute of the originally
decorated `foo` method. The `property` function returns an object; this object
always comes with its own `setter` attribute, which can then be applied as
a decorator to other functions. Using the same name for the get and set methods
is not required, but it does help group the multiple methods that access one
property together.

We can also specify a deletion function with `@foo.deleter`. We cannot specify
a docstring using `property` decorators, so we need to rely on the property
copying the docstring from the initial getter method

```python
class Silly:
@property
def silly(self):
"This is a silly property"
print("You are getting silly")
return self._silly

@silly.setter
def silly(self, value):
print("You are making silly {}".format(value))
self._silly = value

@silly.deleter
def silly(self):
print("Whoah, you kicked silly!")
del self.silly
```

```python
>>> s = Silly()
>>> s.silly = "funny"
You are making silly funny
>>> s.silly
You are getting silly
'funny'
>>> del s.silly
Whoah, you kicked silly!
```

# When to use properties

The most common use of a property is when we have some data on a class that we
later want to add behavior to.

The fact that methods are just callable attributes, and properties are just
customizable attributes can help us make the decision. Methods should typically
represent actions; things that can be done to, or performed by, the object. When
you call a method, even with only one argument, it should *do* something. Method
names a generally verbs.

Once confirming that an attribute is not an action, we need to decide between
standard data attributes and properties. In general, always use a standard
attribute until you need to control access to that property in some way. In
either case, your attribute is usually a noun . The only difference between an
attribute and a property is that we can invoke custom actions automatically when
a property is retrieved, set, or deleted

## Cache expensive data

A common need for custom behavior is caching a value that is difficult to
calculate or expensive to look up.

We can do this with a custom getter on the property. The first time the value is
retrieved, we perform the lookup or calculation. Then we could locally cache the
value as a private attribute on our object, and the next time the value is
requested, we return the stored data.

```python
from urlib.request import urlopen

class Webpage:
def __init__(self, url):
self.url = url
self._content = None

@property
def content(self):
if not self._content:
print("Retrieving New Page..")
self._content = urlopen(self.url).read()
return self._content
```

```python
>>> import time
>>> webpage = Webpage("http://ccphillips.net/")
>>> now = time.time()
>>> content1 = webpage.content
Retrieving new Page...
>>> time.time() - now
22.43316
>>> now = time.time()
>>> content2 = webpage.content
>>> time.time() -now
1.926645
>>> content1 == content2
True
```

## Attributes calculated on the fly

Custom getters are also useful for attributes that need to be calculated on the
fly, based on other object attributes.

```python
clsas AverageList(list):
@property
def average(self):
return sum(self) / len(self)
```
```python
>>> a = AverageList([1,2,3,4])
>>> a.average
2.5
```

Of course we could have made this a method instead, but then we should call it
`calculate_average()`, since methods represent actions. But a property called
`average` is more suitable, both easier to type, and easier to read.

# [Abstract properties](https://stackoverflow.com/questions/5960337/how-to-create-abstract-properties-in-python-abstract-classes)

Sometimes you want to define properties in your abstract classes, to do that, use:

```python
from abc import ABC, abstractmethod

class C(ABC):
@property
@abstractmethod
def my_abstract_property(self):
...
```

If you want to use an abstract setter, you'll encounter the mypy `Decorated
property not supported` error, you'll need to add a `# type: ignore` until [this
issue is solved](https://github.com/python/mypy/issues/1362).
4 changes: 2 additions & 2 deletions docs/vim.md
Expand Up @@ -228,8 +228,8 @@ the following snippet to your vimrc file
```Vim
augroup remember_folds
autocmd!
autocmd BufWinLeave * mkview
autocmd BufWinEnter * silent! loadview
autocmd BufLeave * mkview
autocmd BufEnter * silent! loadview
augroup END
```

Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Expand Up @@ -125,6 +125,7 @@ nav:
- Logging: python_logging.md
- Code Styling: coding/python/python_code_styling.md
- Docstrings: coding/python/docstrings.md
- Properties: python_properties.md
- Lazy loading: lazy_loading.md
- Plugin System: python_plugin_system.md
- Profiling: python_profiling.md
Expand Down

0 comments on commit a05fc16

Please sign in to comment.