Thanks to visit codestin.com
Credit goes to github.com

Skip to content

pathlib.Path is not serializable #563

@devtud

Description

@devtud

Describe the bug
If the api response contains fields of type pathlib.Path, an error is raised:

ValueError: [KeyError(<class 'pathlib.PosixPath'>,), TypeError("'PosixPath' object is not iterable",), TypeError('vars() argument must have __dict__ attribute',)]

To Reproduce
I wrote a test for this bug which fails with the above ^ error.

#tests/test_serialize_path.py

from pathlib import Path

from pydantic import BaseModel
from starlette.testclient import TestClient

from fastapi import FastAPI


class ContainsPathProperty(BaseModel):
    path: Path


app = FastAPI()


@app.get("/", response_model=ContainsPathProperty)
async def get_main():
    obj = ContainsPathProperty(path=Path('/some/random/path'))
    return obj


client = TestClient(app)


def test_serialize_path():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {'path': '/some/random/path'}

Expected behavior
The test should pass, which means a 200 response is returned and the response is:

{
    "path": "/some/random/path"
}

Environment:

  • OS: Linux / Windows

  • FastAPI Version 0.38.1

  • Python version 3.6.9

Additional context

Solution 1

A possible fix would be importing and using the encoder from pydantic which knows to encode Path objects:

# pydantic/json.py

def pydantic_encoder(obj: Any) -> Any:
    ......
    elif isinstance(obj, Path):
        return str(obj)
    ......

The Fastapi's jsonable_encoder(....) would be changed from this:

# fastapi/encoder.py
....
from pydantic.json import ENCODERS_BY_TYPE
....

def jsonable_encoder(....) -> Any:
    .......
    try:
        if custom_encoder and type(obj) in custom_encoder:
            encoder = custom_encoder[type(obj)]
        else:
            encoder = ENCODERS_BY_TYPE[type(obj)]
        return encoder(obj)
    except KeyError as e:
        .....
    ......

to this:

# fastapi/encoder.py
....
from pydantic.json import pydantic_encoder
....

def jsonable_encoder(....) -> Any:
    .......
    try:
        if custom_encoder and type(obj) in custom_encoder:
            encoder = custom_encoder[type(obj)]
        else:
            encoder = pydantic_encoder
        return encoder(obj)
    except KeyError as e:
        .....
    ......

The test passes but pydantic_encoder() has a downside: the string obtained by str(Path(...)) is not the same on Windows (eg: \some\random\path) vs Linux (/some/random/path).

** Solution 2 **

As I think it is very important that a Fastapi app return the same responses no matter the platform on which it's deployed, another solution is handling the pathlib.Path encoding directly in Fastapi's jsonable_encoder(), using Path(...).as_posix() instead of str(Path(...)) like:

# fastapi/encoders.py

def jsonable_encoder(.....) -> Any:
    ......
    if isinstance(obj, Path):
        return obj.as_posix()
    .......

What do you guys think?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions