Repository: pydantic/pydantic

Issue:
Class variables?
Is it possible to have class variables that are not part of the schema that is parsed by pydantic?

ie:

```
class MyModel(BaseModel):

     # This is part of pydantic
     group: str = None

     # This is not.
     ACTIONS = ['a', 'b', 'c']```


Discussion:
https://github.com/samuelcolvin/pydantic/blob/0698384788e78b3353877bc44340a00089ea1d3f/pydantic/main.py#L121

Start the name with `_` or use a function with the `@property` decorator.
Thanks. Would you accept a PR that also ignored all uppercase class variable names?

I didn't see this in the documentation, perhaps I missed it?
Probably not excluding all uppercase variables, but I would accept a PR which either:
* `exclude_uppercase: bool` which when `True` caused uppercase variables to be excluded from validation
* `exclude_vars: Set[str]` where you could provide a bunch of variables names which you wished to exclude
Being able to define attributes which are not being part of the schema being part is also something I'm looking for.

However, I have the suspicion that doing so based on the case of the variable name could be too inflexible. Thus, I would prefer the `exclude_vars` approach much more.

But how about we add a new type, which marks variables as internal?

```py
class MyModel(BaseModel):
     # This is part of pydantic
     group: str = None

     # This is not.
     ACTIONS: Internal[List[str] = ['a', 'b', 'c']```
```

`Internal` makes a lot of sense to me.

I guess it might upset mypy, but that doesn't both me much.
I think I've found a way to trick mypy. We can abuse a `Union`, where one case is an internal field. That way, we can check whether that var is internal and still have the mypy guarantees.

```py
class _internal:
    pass

T = TypeVar('T')

Internal = Union[_internal, T]

# accepted by mypy
x: Internal[int] = 42
```
Makes sense to me, I neither know nor care whether it's an abuse of typing.
Humm, maybe we need to think harder about using types to indicate things that don't relate to type. Could be very confusing.
> Humm, maybe we need to think harder about using types to indicate things that don't relate to type. Could be very confusing.

I agree.

I think what we are looking for is a way to annotate settings for each field. The problem is that we are limited in ways to do so:

    a) variable name (e.g. variables starting with an `_` are currently ignored)
    b) type annotation
    c) default value the field is set to
    d) using external annotations, such as the `Config` class

In Rust for example, one can also annotate things using the `#[...]` syntax. But we don't have that option in Python.

Out of the ones listed, I would currently prefer c) :

```python
class MyModel(BaseModel):
    x: int = Internal(42)
```

What I am currently worried about, if there are other annotations we would like to add which would make this approach unfeasable. Maybe something like this would be better than:

```python
class MyModel(BaseModel):
    x: int = Field(42, internal=True, allow_mutation=False)
```


That's already work going on. See #190, #129. 

Problem is that internal, as it works now couldn't be used with anything else, eg. `allow_mutaion=False`. Because the variables are just ignored by pydantic. 

Also, internal is a confusing name as it indicates they can only be used inside the class. Maybe it should be called "ignored". And I'd rather just keep it in config for cleanness. 
> That's already work going on. See #190, #129.

Oh, that looks nice. I think that would probably be the right place to add the feature then.

> Also, internal is a confusing name as it indicates they can only be used inside the class. Maybe it should be called "ignored". And I'd rather just keep it in config for cleanness.

Agreed `internal` is just what first came to mind. Although I'm not sure if `ignored` is unambigious either. But I guess that's a discussion for another time.
First of all, I would also like this feature.

As a workaround I am trying to use the `_foo` declaration syntax. But with this I am not allowed to explicitly set the value of the object.
```python
o = Foo(**data)
o._bar = 3
```
Then I get
```
ValueError: "Foo" object has no field "_bar"
```
I just want to exclude it from the external data parameterization.
Would taking advantage of `typing.ClassVar` be a good candidate for this? Seems like `pydantic` doesn't currently allow those.

```
from typing import ClassVar
from pydantic import BaseModel

class MyModel(BaseModel):
    foo: ClassVar[int] = 3
    bar: float

Traceback (most recent call last):
  File "python3.6/site-packages/pydantic/validators.py", line 262, in find_validators
    if issubclass(type_, val_type):
TypeError: issubclass() arg 1 must be a class
```


Just stumbled over this error. I would love to see support for `typing.ClassVar`.
As a workaround:

You can always create a class inside of the model class.

And store there any class-level constants you need.

Pydantic won't interact with it:

```Python
from pydantic import BaseModel

class User(BaseModel):
    username: str
   
    class Meta:
        model_type = "user"

user = User(username="john")

print(user)
print(user.dict())
print(user.Meta.model_type)
```

prints:

```
User username='john'
{'username': 'john'}
user
```
That's a fair workaround. I just had some dataclasses already defined and then got bitten by two things:

1. Can't nest dataclasses using pydantic (yet) #273.
2. Can't use `typing.ClassVar` (this issue).
@tiangolo's suggestion is definitely the most sensible approach and will make sense to anyone coming from django or similar.

However we should definitely implement `ClassVar`.
Anyone wanting to contribute, `ClassVar` shouldn't be too hard to implement. Help much appreciated.

Resolving patch:
diff --git a/pydantic/fields.py b/pydantic/fields.py
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -143,7 +143,7 @@ def prepare(self):
         self._populate_sub_fields()
         self._populate_validators()
 
-    def _populate_sub_fields(self):  # noqa: C901 (ignore complexity)
+    def _populate_sub_fields(self):
         # typing interface is horrible, we have to do some ugly checks
         if lenient_issubclass(self.type_, JsonWrapper):
             self.type_ = self.type_.inner_type
@@ -232,7 +232,7 @@ def _prep_vals(self, v_funcs):
             v.append((_get_validator_signature(f), f))
         return tuple(v)
 
-    def validate(self, v, values, *, loc, cls=None):  # noqa: C901 (ignore complexity)
+    def validate(self, v, values, *, loc, cls=None):
         if self.allow_none and v is None:
             return None, None
 
diff --git a/pydantic/main.py b/pydantic/main.py
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -6,7 +6,7 @@
 from itertools import chain
 from pathlib import Path
 from types import FunctionType
-from typing import Any, Callable, Dict, Set, Type, Union
+from typing import Any, Callable, ClassVar, Dict, Set, Type, Union
 
 from .error_wrappers import ErrorWrapper, ValidationError
 from .errors import ConfigError, ExtraError, MissingError
@@ -133,9 +133,12 @@ def __new__(mcs, name, bases, namespace):
                 f.prepare()
 
         annotations = namespace.get('__annotations__', {})
+        class_vars = set()
         # annotation only fields need to come first in fields
         for ann_name, ann_type in annotations.items():
-            if not ann_name.startswith('_') and ann_name not in namespace:
+            if ann_type == ClassVar:
+                class_vars.add(ann_name)
+            elif not ann_name.startswith('_') and ann_name not in namespace:
                 validate_field_name(bases, ann_name)
                 fields[ann_name] = Field.infer(
                     name=ann_name,
@@ -146,7 +149,7 @@ def __new__(mcs, name, bases, namespace):
                 )
 
         for var_name, value in namespace.items():
-            if not var_name.startswith('_') and not isinstance(value, TYPE_BLACKLIST):
+            if not var_name.startswith('_') and not isinstance(value, TYPE_BLACKLIST) and var_name not in class_vars:
                 validate_field_name(bases, var_name)
                 fields[var_name] = Field.infer(
                     name=var_name,
@@ -353,7 +356,7 @@ def __get_validators__(cls):
     def validate(cls, value):
         return cls(**value)
 
-    def _process_values(self, input_data: dict) -> Dict[str, Any]:  # noqa: C901 (ignore complexity)
+    def _process_values(self, input_data: dict) -> Dict[str, Any]:
         return validate_model(self, input_data)
 
     @classmethod
diff --git a/pydantic/schema.py b/pydantic/schema.py
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -376,7 +376,7 @@ def get_long_model_name(model: Type['main.BaseModel']):
     return f'{model.__module__}__{model.__name__}'.replace('.', '__')
 
 
-def field_type_schema(  # noqa: C901 (ignore complexity)
+def field_type_schema(
     field: Field,
     *,
     by_alias: bool,


Test patch:
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -1,5 +1,5 @@
 from enum import Enum
-from typing import Any, List
+from typing import Any, ClassVar, List
 
 import pytest
 
@@ -463,3 +463,12 @@ def test_value_field_name_shadows_attribute():
 
         class BadModel(BaseModel):
             schema = 'abc'  # This conflicts with the BaseModel's schema() class method
+
+
+def test_class_var():
+    class MyModel(BaseModel):
+        a: ClassVar
+        b: ClassVar = 1
+        c: int = 2
+
+    assert list(MyModel.__fields__.keys()) == ['c']