Skip to content

FilePath cannot be used like Path in default or default_factory #5064

@chbndrhnns

Description

@chbndrhnns

Initial Checks

  • I have searched GitHub for a duplicate issue and I'm sure this is something new
  • I have searched Google & StackOverflow for a solution and couldn't find anything
  • I have read and followed the docs and still think this is a bug
  • I am confident that the issue is with pydantic (not my code, or another library in the ecosystem like FastAPI or mypy)

Description

Subclassing Path seems to be not possible directly.
However, the documentation states that FilePath is

like Path, but the path must exist and be a file

That's not correct when it comes to defaults in models. See the example.
The instantiation of FilePath fails here.

I am not aware of an immediate solution and I think that, at least, the documentation should have a note about that special behaviour.

This is the error:

def test_takes_default():
>       assert str(DefaultExists().cert) == exists

test_.py:21: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
pydantic/main.py:340: in pydantic.main.BaseModel.__init__
    ???
pydantic/main.py:1067: in pydantic.main.validate_model
    ???
pydantic/fields.py:439: in pydantic.fields.ModelField.get_default
    ???
test_.py:12: in <lambda>
    default_factory=lambda: p.FilePath(exists),
../../../../../.pyenv/versions/3.9.15/lib/python3.9/pathlib.py:1082: in __new__
    self = cls._from_parts(args, init=False)
../../../../../.pyenv/versions/3.9.15/lib/python3.9/pathlib.py:707: in _from_parts
    drv, root, parts = self._parse_args(args)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'pydantic.types.FilePath'>, args = ('/etc/hosts',)

    @classmethod
    def _parse_args(cls, args):
        # This is useful when you don't want to create an instance, just
        # canonicalize some constructor arguments.
        parts = []
        for a in args:
            if isinstance(a, PurePath):
                parts += a._parts
            else:
                a = os.fspath(a)
                if isinstance(a, str):
                    # Force-cast str subclasses to str (issue #21127)
                    parts.append(str(a))
                else:
                    raise TypeError(
                        "argument should be a str object or an os.PathLike "
                        "object returning str, not %r"
                        % type(a))
>       return cls._flavour.parse_parts(parts)
E       AttributeError: type object 'FilePath' has no attribute '_flavour'

../../../../../.pyenv/versions/3.9.15/lib/python3.9/pathlib.py:700: AttributeError

Example Code

import typing as t

import pydantic as p
import pytest

exists = "/etc/hosts"
exists_not = "/etc/blahosts"


class DefaultExists(p.BaseModel):
    cert: t.Optional[p.FilePath] = p.Field(
        default_factory=lambda: p.FilePath(exists),
    )


def test_can_pass_value():
    assert str(DefaultExists(cert=exists).cert) == exists


def test_takes_default():
    assert str(DefaultExists().cert) == exists


def test_fails_if_not_exists():
    with pytest.raises(ValueError):
        DefaultExists(cert=exists_not)

Python, Pydantic & OS Version

pydantic version: 1.10.5
            pydantic compiled: True
                 install path: /Users/cleancoder/Library/Caches/pypoetry/virtualenvs/juice-auth-v2-3il_93qV-py3.9/lib/python3.9/site-packages/pydantic
               python version: 3.9.15 (main, Nov 14 2022, 08:33:16)  [Clang 14.0.0 (clang-1400.0.29.201)]
                     platform: macOS-12.6-arm64-arm-64bit
     optional deps. installed: ['dotenv', 'email-validator', 'typing-extensions']

Affected Components

Metadata

Metadata

Assignees

Labels

bug V1Bug related to Pydantic V1.XpendingIs unconfirmed

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions