Why I cannot create standalone object of HttpURL in pydantic? - python-3.x

from pydantic import BaseModel, Field, HttpUrl
from typing import Optional
class TestClass(BaseModel):
url:Optional[HttpUrl] = None
Creating object TestClass with url="https://www.test.com" works.
Here the imported HttpUrl or BaseModel are class. When I try to create httpurl object standalone it gives typeerror e.g. below.
from pydantic import HttpUrl
myurl = HttpUrl("https://www.test.com")
Why it cannot be used to convert string to http object like above. It results
errors like: need keyword-only args if that is provided then 2 positional provided required 3

You can use parse_obj_as method. This way there will be fewer dependencies:
from pydantic import parse_obj_as, HttpUrl
url=parse_obj_as(HttpUrl, "https://www.test.com")

pydantic types are valid only when used as a class variables inside BaseModel derivatives.
Under the hood, pydantic fires validate method of AnyUrl class which is inconvenient for external usage.
Maybe urllib library will come handy for you?
from urllib.parse import urlparse
myurl = urlparse("https://www.test.com")

Related

Trouble creating DefaultDict in Dataclass

I am having trouble setting up a simple dataclass using a new defaultdict(dict).
If I tell the factory to use 'dict' as below , instantiation fails with typerror collection.defaultdict object is not callable
from collections import defaultdict
from dataclasses import dataclass, field
#dataclass
class ResultSet:
changed: bool = False
mqttdata: defaultdict(dict) = field(default_factory=defaultdict(dict)) # does not work!
It does sorta work using field(default_factory=defaultdict) but then my code will fail later when it encounters missing keys - presumably because defaultdict was not set up for dict.
How do I properly set up a new defaultdict(dict) in a dataclass?
You have a few problems with the code and how you are using dataclasses currently:
Type generics in annotations need to be specified through square brackets [] generally, so default[dict] instead of defaultdict(dict) for example.
The default_factory argument to dataclasses.field() needs to be a no-arg callable which returns a new object with default values set. For example, assuming you have a nested dataclass Inner which specifies defaults for all fields, you could use default_factory=Inner to create a new Inner object each time the main dataclass is instantiated.
Note that the default_factory argument is mainly useful for mutable types
such as set, list, and dict, so that the same object isn't
shared (and potentially mutated) between dataclass instances.
Putting it all together, here is the working code which sets a default value for a field of type defaultdict[dict]:
from collections import defaultdict
from dataclasses import dataclass, field
#dataclass
class ResultSet:
changed: bool = False
mqttdata: defaultdict[dict] = field(default_factory=lambda: defaultdict(dict)) # works!
print(ResultSet())
In Python versions earlier than 3.9, which is when PEP 585 was introduced, you'll need to add the following import at the top so that any type annotations are lazy-evaluated:
from __future__ import annotations

Can't use Pydantic model attributes on type hinting

Like I used to do with FastAPI routes, I want to make a function that is expecting a dict. I want to type hint like in FastAPI with a Pydantic model.
Note that I am just using FastAPI as a reference here and this app serves a total different purpose.
What I did:
models.py
from pydantic import BaseModel
class Mymodel(BaseModel):
name:str
age:int
main.py
def myfunc(m:Mymodel):
print(m)
print(m.name)
myfunc({"name":"abcd","age":3})
It prints m as a normal dict and not Mymodel and m.name just throws an AttributeError.
I don't understand why it is behaving like this because the same code would work in FastAPI. Am I missing something here? What should I do to make this work.
I am expecting a dict arg in the func, I want to type hint with a class inherited from pydantic BaseModel. Then I want to acccess the attributes of that class.
I don't want to do:
def myfunc(m):
m = Mymodel(**m)
Thank You.
from pydantic import BaseModel
from pydantic import validate_arguments
class Mymodel(BaseModel):
name:str
age:int
#validate_arguments
def myfunc(m:Mymodel):
print(m)
print(m.name)
myfunc({"name":"abcd","age":3})
This might be what you are looking for: https://pydantic-docs.helpmanual.io/usage/validation_decorator/
Since you pass a dict to your custom function, the attribute should be accessed in the following way:
print(m['name'])
# or
print(m.get('name'))
Otherwise, to use m.name instead, you need to parse the dict to the corresponding Pydantic model, before passing it to the function, as shwon below:
data = {"name":"abcd", "age":3}
myfunc(Mymodel(**data))
# or
myfunc(Mymodel.parse_obj(data))
The reason that passing {"name":"abcd", "age":3} in FastAPI and later accessing the attributes using the dot operator (e.g., m.name) works, is that FastAPI does the above parsing and validation internally, as soon as a request arrives. This is the reason that you can then convert it back to a dictionary in your endpoint, using m.dict(). Try, for example, passing an incorrect key, e.g., myfunc(Mymodel(**{"name":"abcd","MYage":3}))—you would get a field required (type=value_error.missing) error (as part of Pydantic's Error Handling), similar to what FastAPI would return (as shown below), if a similar request attempted to go through (you could also test that through Swagger UI autodocs at http://127.0.0.1:8000/docs). Otherwise, any dictionary passed by the user (in the way you show in the question) would go through without throwing an error, in case it didn't match the Pydantic model.
{
"detail": [
{
"loc": [
"body",
"age"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
You could alternatively use Pydantic's validation decorator (i.e., #validate_arguments) on your custom function. As per the documentation:
The validate_arguments decorator allows the arguments passed to a
function to be parsed and validated using the function's annotations
before the function is called. While under the hood this uses the same
approach of model creation and initialisation; it provides an
extremely easy way to apply validation to your code with minimal
boilerplate.
Example:
from pydantic import validate_arguments
from pydantic import BaseModel
class Model(BaseModel):
name: str
age: int
#validate_arguments
def myfunc(m: Model):
print(m)
print(m.name)
myfunc({"name":"abcd","age":3})

default_factory not being called on dataclasses' fields

Here is an example of what happens:
#dataclass
class D:
prop1: str
prop2: dict = field(default_factory=lambda: defaultdict(set))
d = D("spam")
print(d)
# D(prop1='spam', prop2=Field(name=None,type=None,default=<dataclasses._MISSING_TYPE object at 0x10274c650>,default_factory=<function D.<lambda> at 0x103ad3a70>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=None))
As you can see, the prop2 was not initialized using a default value from the default_factory, it is still a field. And if I try to do d.prop2["some key"] I get TypeError: 'Field' object is not subscriptable.
You probably have imported the dataclass decorator from the wrong module. It may happen if you use automatic imports in your IDE.
The behaviour described happens when you import dataclass from attr (from attr import dataclass).
If you do from dataclasses import dataclass everything will work as expected (the default_factory will be called to generate the value of the field).

How can I specify the package when instantiating a class?

I have many classes with the same name in different packages. Take the following directory structure as an example:
From within the constructor of the templates/Person class, how can I declare an instance of rules/Person?
The code I expected to work (but has an error on line 10) is as follows:
from Template import Template
import rules
class Person(Template):
def __init__(self):
super(Person, self).__init__('Person')
self.rules = [
rules.Person() #this is an error
]
PEP328 has the answer, I think.
from .myfolder import Template as Template
from .myfolder1 import Template as Template1
from .myfolder2 import Template as Template2
from pip_installed_library1 import Template as Template3
from pip_installed_library2 import Template as Template4
Using this pattern they never share names.

Replace package import in a module

I use a module that imports a function as a package import using relative import dot notation:
from .utils import target_func
class ClassINeed:
def function_i_call(self):
return target_func()
I want to import ClassINeed with from classineed import ClassINeed but replace target_func with a function of my own. Problem is, target_func is not part of the class I am importing. Therefore I do not see a way to access it. What would be a way to accomplish this?
On top of from classineed import ClassINeed, also do a import classineed then override the target_func as needed via classineed.target_func = lambda : 'hello!' for example.
P.S. Referring to the class ClassINeed with classineed.ClassINeed might be cleaner if you already have import classineed.

Resources