Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSError: [Errno 9] Bad file descriptor in the fake filesystem #697

Closed
AlessandroLollo opened this issue Jul 25, 2022 · 17 comments
Closed

OSError: [Errno 9] Bad file descriptor in the fake filesystem #697

AlessandroLollo opened this issue Jul 25, 2022 · 17 comments

Comments

@AlessandroLollo
Copy link

Hey folks 👋

I'm facing an issue that seems to be related to pyfakefs, but I'm not 100% sure.
While implementing an integration between MetricFlow and Prefect 2.0, I'm using pyfakefs to mock some files I need.

I was able to reproduce the issue with this minimal script:

from prefect import flow


def test_example(fs):

    msg = "This is an example flow"
    
    @flow
    def test_flow():
        return msg
    

    response = test_flow()

    assert response == msg

I'm using pyfakefs==4.6.3 and prefect==2.0b12

Below you can find the trace back generated by pytest:

platform darwin -- Python 3.9.10, pytest-7.1.2, pluggy-1.0.0 -- /Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/bin/python3.9
cachedir: .pytest_cache
rootdir: /Users/alessandro.lollo/dev/prefect-collections/prefect-metricflow, configfile: setup.cfg
plugins: anyio-3.6.1, pyfakefs-4.6.3, asyncio-0.19.0
asyncio: mode=auto
collected 1 item                                                                                                                

tests/test_example.py::test_example FAILED                                                                                [100%]

=========================================================== FAILURES ============================================================
_________________________________________________________ test_example __________________________________________________________

fs = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x10fe72eb0>

    def test_example(fs):
    
        msg = "This is an example flow"
    
        @flow
        def test_flow():
            return msg
    
    
>       response = test_flow()

/Users/alessandro.lollo/dev/prefect-collections/prefect-metricflow/tests/test_example.py:13: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/flows.py:367: in __call__
    return enter_flow_run_engine_from_flow_call(
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/engine.py:110: in enter_flow_run_engine_from_flow_call
    setup_logging()
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/logging/configuration.py:66: in setup_logging
    config = load_logging_config(
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/logging/configuration.py:33: in load_logging_config
    template = string.Template(path.read_text())
/Users/alessandro.lollo/.pyenv/versions/3.9.10/lib/python3.9/pathlib.py:1266: in read_text
    with self.open(mode='r', encoding=encoding, errors=errors) as f:
/Users/alessandro.lollo/.pyenv/versions/3.9.10/lib/python3.9/pathlib.py:1252: in open
    return io.open(self, mode, buffering, encoding, errors, newline,
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:5036: in open
    return fake_open(file, mode, buffering, encoding, errors,
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:5721: in __call__
    return self.call(*args, **kwargs)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:5778: in call
    file_object, file_path, filedes, real_path = self._handle_file_arg(
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:5912: in _handle_file_arg
    wrapper = self.filesystem.get_open_file(filedes)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:1552: in get_open_file
    self.raise_os_error(errno.EBADF, str(file_des))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x10fe72eb0>, err_no = 9, filename = '11', winerror = None

    def raise_os_error(self, err_no: int,
                       filename: Optional[AnyString] = None,
                       winerror: Optional[int] = None) -> NoReturn:
        """Raises OSError.
        The error message is constructed from the given error code and shall
        start with the error string issued in the real system.
        Note: this is not true under Windows if winerror is given - in this
        case a localized message specific to winerror will be shown in the
        real file system.
    
        Args:
            err_no: A numeric error code from the C variable errno.
            filename: The name of the affected file, if any.
            winerror: Windows only - the specific Windows error code.
        """
        message = os.strerror(err_no) + ' in the fake filesystem'
        if (winerror is not None and sys.platform == 'win32' and
                self.is_windows_fs):
            raise OSError(err_no, message, filename, winerror)
>       raise OSError(err_no, message, filename)
E       OSError: [Errno 9] Bad file descriptor in the fake filesystem: '11'

/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:1122: OSError
==================================================== short test summary info ====================================================
FAILED tests/test_example.py::test_example - OSError: [Errno 9] Bad file descriptor in the fake filesystem: '11'
================================================= 1 failed, 8 warnings in 9.07s =================================================

My environment is Python 3.9.10 on MacOS 12.4

Anyone that can help me understanding what's going on?

Thanks! 🙌

@mrbean-bremen
Copy link
Member

At a first glance, this look like the code is trying to read a file in the real fs that is not present in the fake filesystem, specifically the logging config. As the fake filesystem does not use any real filesysystem contents by defaults, these have to be mapped into the fake filesystem on setup if needed. I may have a closer look at this tonight.

@mrbean-bremen
Copy link
Member

mrbean-bremen commented Jul 25, 2022

I had a closer look, and there is another issue here that may take some time to fix. As mentioned, prefect reads some files in the package itself (not only the logging config). This can be fixed by mapping the package path into the fake filesystem.
Unfortunately, this doesn't work either due to a regression issue related to the changes in pyfakefs 3.6.x (specifically, support for the opener argument in open) - seems to be some caching issue within pathlib. I will check what I can do here (maybe by skipping the build-in opener in pathlib), but this may take some time.
Reverting to an older pyfakefs version does not work because of a check in the prefect code that will only work with the latest pyfakefs.

I confirmed that disabling the opener argument will fix the problem given that the needed paths are mapped into the fake filesystem. The setup is still a bit tricky:

import os
import pytest
from prefect import flow, logging, orion
import pytzdata

@pytest.fixture
def fs_with_config(fs):
    module_paths = (logging, pytzdata, orion)
    for module_path in module_paths:
        data_path = os.path.dirname(getattr(module_path, "__file__"))
        fs.add_real_directory(data_path)


def test_example(fs_with_config):
    ...

Though you won't able to use this until the issue is fixed.

@mrbean-bremen
Copy link
Member

Ok, I added a commit to master that ignores the opener in pathlib (basically the same behavior regarding pathlib as in previous versions). I'm not sure yet if this is the best way to fix this, I will do a few more checks another time.
@AlessandroLollo - you could try with master and check if you can work with that version (and the additional setup shown above).

@AlessandroLollo
Copy link
Author

Hey @mrbean-bremen 👋
Thanks a lot for taking a closer look at the issue, much appreciated! 🙌

I will try your suggestion and let you know how it goes!

Thanks again 🙏

@mrbean-bremen
Copy link
Member

mrbean-bremen commented Aug 4, 2022

@AlessandroLollo - did you get a chance to test this?

quitterie-lcs added a commit to openfun/ralph that referenced this issue Aug 17, 2022
As there is a regression in the new package release, the dependency is
downgraded to the previous one.
(pytest-dev/pyfakefs#697)
quitterie-lcs added a commit to openfun/ralph that referenced this issue Aug 17, 2022
As there is a regression in the new package release, the dependency is
downgraded to the previous one.
(pytest-dev/pyfakefs#697)
@AlessandroLollo
Copy link
Author

Hey @mrbean-bremen 👋
I tried running the above test example with the master branch of pyfakefs.
Still getting an error, but a different one.

pytest tests/test_example.py --disable-warnings -v
=================================================== test session starts ===================================================
platform darwin -- Python 3.9.10, pytest-7.1.2, pluggy-1.0.0 -- /Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/bin/python3.9
cachedir: .pytest_cache
rootdir: /Users/alessandro.lollo/dev/prefect-collections/prefect-metricflow, configfile: setup.cfg
plugins: anyio-3.6.1, asyncio-0.19.0, pyfakefs-4.7.dev0
asyncio: mode=auto
collected 1 item                                                                                                          

tests/test_example.py::test_example FAILED                                                                          [100%]

======================================================== FAILURES =========================================================
______________________________________________________ test_example _______________________________________________________

fs = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x10c51e0d0>

    def test_example(fs):
    
        msg = "This is an example flow"
    
        @flow
        def test_flow():
            return msg
    
    
>       response = test_flow()

/Users/alessandro.lollo/dev/prefect-collections/prefect-metricflow/tests/test_example.py:13: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/flows.py:384: in __call__
    return enter_flow_run_engine_from_flow_call(
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/engine.py:112: in enter_flow_run_engine_from_flow_call
    setup_logging()
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/logging/configuration.py:66: in setup_logging
    config = load_logging_config(
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/logging/configuration.py:33: in load_logging_config
    template = string.Template(path.read_text())
/Users/alessandro.lollo/.pyenv/versions/3.9.10/lib/python3.9/pathlib.py:1266: in read_text
    with self.open(mode='r', encoding=encoding, errors=errors) as f:
/Users/alessandro.lollo/.pyenv/versions/3.9.10/lib/python3.9/pathlib.py:1252: in open
    return io.open(self, mode, buffering, encoding, errors, newline,
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:5038: in open
    return fake_open(file, mode, buffering, encoding, errors,
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:5723: in __call__
    return self.call(*args, **kwargs)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:5810: in call
    file_object = self._init_file_object(file_object,
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:5889: in _init_file_object
    self.filesystem.raise_os_error(errno.ENOENT, file_path)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x10c51e0d0>, err_no = 2
filename = PosixPath('/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/logging/logging.yml')
winerror = None

    def raise_os_error(self, err_no: int,
                       filename: Optional[AnyString] = None,
                       winerror: Optional[int] = None) -> NoReturn:
        """Raises OSError.
        The error message is constructed from the given error code and shall
        start with the error string issued in the real system.
        Note: this is not true under Windows if winerror is given - in this
        case a localized message specific to winerror will be shown in the
        real file system.
    
        Args:
            err_no: A numeric error code from the C variable errno.
            filename: The name of the affected file, if any.
            winerror: Windows only - the specific Windows error code.
        """
        message = os.strerror(err_no) + ' in the fake filesystem'
        if (winerror is not None and sys.platform == 'win32' and
                self.is_windows_fs):
            raise OSError(err_no, message, filename, winerror)
>       raise OSError(err_no, message, filename)
E       FileNotFoundError: [Errno 2] No such file or directory in the fake filesystem: PosixPath('/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/logging/logging.yml')

/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:1122: FileNotFoundError
================================================= short test summary info =================================================
FAILED tests/test_example.py::test_example - FileNotFoundError: [Errno 2] No such file or directory in the fake filesyst...
============================================= 1 failed, 8 warnings in 16.14s ==============================================
(prefect-metricflow) 
prefect-metricflow on 🌱 main [?] v3.9.10 (prefect-metricflow) took 18s 
❯ pip show prefect                                  
Name: prefect
Version: 2.1.1
Summary: Workflow orchestration and management.
Home-page: https://github.com/PrefectHQ/prefect/tree/orion
Author: Prefect Technologies, Inc.
Author-email: [email protected]
License: 
Location: /Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages
Requires: aiofiles, aiohttp, aiosqlite, alembic, anyio, asgi-lifespan, asyncpg, click, cloudpickle, coolname, croniter, cryptography, docker, fastapi, fsspec, griffe, httpx, importlib-metadata, jsonpatch, kubernetes, packaging, pathspec, pendulum, pydantic, python-slugify, pytz, pyyaml, readchar, rich, slack-sdk, sqlalchemy, toml, typer, typing-extensions, uvicorn
Required-by: prefect-metricflow
(prefect-metricflow) 
prefect-metricflow on 🌱 main [?] v3.9.10 (prefect-metricflow) took 3s 
➜ pytest tests/test_example.py --disable-warnings -v
=================================================== test session starts ===================================================
platform darwin -- Python 3.9.10, pytest-7.1.2, pluggy-1.0.0 -- /Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/bin/python3.9
cachedir: .pytest_cache
rootdir: /Users/alessandro.lollo/dev/prefect-collections/prefect-metricflow, configfile: setup.cfg
plugins: anyio-3.6.1, asyncio-0.19.0, pyfakefs-4.7.dev0
asyncio: mode=auto
collected 1 item                                                                                                          

tests/test_example.py::test_example PASSED                                                                          [100%]

==================================================== 1 passed in 4.50s ====================================================
(prefect-metricflow) 
prefect-metricflow on 🌱 main [?] v3.9.10 (prefect-metricflow) took 5s 
➜ pytest tests/test_example.py --disable-warnings -v
=================================================== test session starts ===================================================
platform darwin -- Python 3.9.10, pytest-7.1.2, pluggy-1.0.0 -- /Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/bin/python3.9
cachedir: .pytest_cache
rootdir: /Users/alessandro.lollo/dev/prefect-collections/prefect-metricflow, configfile: setup.cfg
plugins: anyio-3.6.1, asyncio-0.19.0, pyfakefs-4.7.dev0
asyncio: mode=auto
collected 1 item                                                                                                          

tests/test_example.py::test_example FAILED                                                                          [100%]

======================================================== FAILURES =========================================================
______________________________________________________ test_example _______________________________________________________

fs = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x106518be0>

    def test_example(fs):
    
        msg = "This is an example flow"
    
        @flow
        def test_flow():
            return msg
    
    
>       response = test_flow()

/Users/alessandro.lollo/dev/prefect-collections/prefect-metricflow/tests/test_example.py:13: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/flows.py:384: in __call__
    return enter_flow_run_engine_from_flow_call(
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/engine.py:112: in enter_flow_run_engine_from_flow_call
    setup_logging()
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/logging/configuration.py:66: in setup_logging
    config = load_logging_config(
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/logging/configuration.py:33: in load_logging_config
    template = string.Template(path.read_text())
/Users/alessandro.lollo/.pyenv/versions/3.9.10/lib/python3.9/pathlib.py:1266: in read_text
    with self.open(mode='r', encoding=encoding, errors=errors) as f:
/Users/alessandro.lollo/.pyenv/versions/3.9.10/lib/python3.9/pathlib.py:1252: in open
    return io.open(self, mode, buffering, encoding, errors, newline,
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:5038: in open
    return fake_open(file, mode, buffering, encoding, errors,
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:5723: in __call__
    return self.call(*args, **kwargs)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:5810: in call
    file_object = self._init_file_object(file_object,
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:5889: in _init_file_object
    self.filesystem.raise_os_error(errno.ENOENT, file_path)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x106518be0>, err_no = 2
filename = PosixPath('/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/logging/logging.yml')
winerror = None

    def raise_os_error(self, err_no: int,
                       filename: Optional[AnyString] = None,
                       winerror: Optional[int] = None) -> NoReturn:
        """Raises OSError.
        The error message is constructed from the given error code and shall
        start with the error string issued in the real system.
        Note: this is not true under Windows if winerror is given - in this
        case a localized message specific to winerror will be shown in the
        real file system.
    
        Args:
            err_no: A numeric error code from the C variable errno.
            filename: The name of the affected file, if any.
            winerror: Windows only - the specific Windows error code.
        """
        message = os.strerror(err_no) + ' in the fake filesystem'
        if (winerror is not None and sys.platform == 'win32' and
                self.is_windows_fs):
            raise OSError(err_no, message, filename, winerror)
>       raise OSError(err_no, message, filename)
E       FileNotFoundError: [Errno 2] No such file or directory in the fake filesystem: PosixPath('/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/logging/logging.yml')

/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pyfakefs/fake_filesystem.py:1122: FileNotFoundError
================================================= short test summary info =================================================
FAILED tests/test_example.py::test_example - FileNotFoundError: [Errno 2] No such file or directory in the fake filesyst...
============================================== 1 failed, 8 warnings in 8.70s ==============================================

I'm using pyfakefs==4.7.dev0 and prefect==2.1.1

@mrbean-bremen
Copy link
Member

Thanks, that's unfortunate - I will have a closer look tonight.

@AlessandroLollo
Copy link
Author

Thanks @mrbean-bremen 🙌
Let me know if I can help!

@mrbean-bremen
Copy link
Member

mrbean-bremen commented Aug 19, 2022

Hm, this looks like the error you get if you don't map the needed module paths into the fake filesystem.
Did you add the setup I described above (e.g. fs_with_config)?

@mrbean-bremen
Copy link
Member

Actually, I can see that you didn't change the setup, as that would show in the error output. Without that setup, it cannot work, as pyfakefs has no way of knowing which files in the real filesystem it needs to copy to the fake filesystem. Can you please retry using the setup mentioned above?

As for the actual problem, I have confimed that the error occurs, because open is called from a path that was initialized before the patching (during import), so the behavior is to be expected. The fix just takes into account this possibility and ignores the opener argument set from pathlib, as it would not change the behavior anyway.

@AlessandroLollo
Copy link
Author

Hey @mrbean-bremen 👋
You're right, I forgot to include the setup you provided, my bad 🙏

Here's the updated test:

import os
import pytest
from prefect import flow, logging, orion
import pytzdata


@pytest.fixture
def fs_with_config(fs):
    module_paths = (logging, pytzdata, orion)
    for module_path in module_paths:
        data_path = os.path.dirname(getattr(module_path, "__file__"))
        fs.add_real_directory(data_path)


def test_example(fs_with_config):

    msg = "This is an example flow"
    
    @flow
    def test_flow():
        return msg
    

    response = test_flow()

    assert response == msg

Running pytest tests/test_example.py yields the following:

=================================================================================================================================== test session starts ===================================================================================================================================
platform darwin -- Python 3.9.10, pytest-7.1.2, pluggy-1.0.0 -- /Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/bin/python3.9
cachedir: .pytest_cache
rootdir: /Users/alessandro.lollo/dev/prefect-collections/prefect-metricflow, configfile: setup.cfg
plugins: anyio-3.6.1, asyncio-0.19.0, pyfakefs-4.7.dev0
asyncio: mode=auto
collected 1 item                                                                                                                                                                                                                                                                          

tests/test_example.py::test_example FAILED                                                                                                                                                                                                                                          [100%]

======================================================================================================================================== FAILURES =========================================================================================================================================
______________________________________________________________________________________________________________________________________ test_example _______________________________________________________________________________________________________________________________________

fs_with_config = None

    def test_example(fs_with_config):
    
        msg = "This is an example flow"
    
        @flow
        def test_flow():
            return msg
    
    
>       response = test_flow()

/Users/alessandro.lollo/dev/prefect-collections/prefect-metricflow/tests/test_example.py:24: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/flows.py:384: in __call__
    return enter_flow_run_engine_from_flow_call(
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/engine.py:152: in enter_flow_run_engine_from_flow_call
    return anyio.run(begin_run)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/anyio/_core/_eventloop.py:70: in run
    return asynclib.run(func, *args, **backend_options)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/anyio/_backends/_asyncio.py:292: in run
    return native_run(wrapper(), debug=debug)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/lib/python3.9/asyncio/runners.py:44: in run
    return loop.run_until_complete(main)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/lib/python3.9/asyncio/base_events.py:642: in run_until_complete
    return future.result()
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/anyio/_backends/_asyncio.py:287: in wrapper
    return await func(*args)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/client.py:103: in with_injected_client
    async with client_context as client:
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/client.py:1991: in __aenter__
    self._ephemeral_lifespan = await self._exit_stack.enter_async_context(
/Users/alessandro.lollo/.pyenv/versions/3.9.10/lib/python3.9/contextlib.py:575: in enter_async_context
    result = await _cm_type.__aenter__(cm)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/lib/python3.9/contextlib.py:181: in __aenter__
    return await self.gen.__anext__()
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/client.py:187: in app_lifespan_context
    await context.__aenter__()
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/asgi_lifespan/_manager.py:92: in __aenter__
    await self._exit_stack.aclose()
/Users/alessandro.lollo/.pyenv/versions/3.9.10/lib/python3.9/contextlib.py:612: in aclose
    await self.__aexit__(None, None, None)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/lib/python3.9/contextlib.py:670: in __aexit__
    raise exc_details[1]
/Users/alessandro.lollo/.pyenv/versions/3.9.10/lib/python3.9/contextlib.py:653: in __aexit__
    cb_suppress = await cb(*exc_details)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/asgi_lifespan/_concurrency/asyncio.py:80: in __aexit__
    await self.task
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/asgi_lifespan/_manager.py:90: in __aenter__
    await self.startup()
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/asgi_lifespan/_manager.py:36: in startup
    raise self._app_exception
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/asgi_lifespan/_concurrency/asyncio.py:63: in run_and_silence_cancelled
    await self.coroutine()
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/asgi_lifespan/_manager.py:64: in run_app
    await self.app(scope, self.receive, self.send)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/fastapi/applications.py:269: in __call__
    await super().__call__(scope, receive, send)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/starlette/applications.py:124: in __call__
    await self.middleware_stack(scope, receive, send)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/starlette/middleware/errors.py:149: in __call__
    await self.app(scope, receive, send)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/starlette/middleware/cors.py:76: in __call__
    await self.app(scope, receive, send)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/starlette/exceptions.py:69: in __call__
    await self.app(scope, receive, send)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py:21: in __call__
    raise e
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/starlette/routing.py:659: in __call__
    await self.lifespan(scope, receive, send)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/starlette/routing.py:635: in lifespan
    async with self.lifespan_context(app):
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/starlette/routing.py:530: in __aenter__
    await self._router.startup()
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/starlette/routing.py:612: in startup
    await handler()
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/orion/api/server.py:271: in run_migrations
    await db.create_db()
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/orion/database/interface.py:53: in create_db
    await self.run_migrations_upgrade()
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/orion/database/interface.py:61: in run_migrations_upgrade
    await run_sync_in_worker_thread(alembic_upgrade)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/utilities/asyncutils.py:56: in run_sync_in_worker_thread
    return await anyio.to_thread.run_sync(call, cancellable=True)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/anyio/to_thread.py:31: in run_sync
    return await get_asynclib().run_sync_in_worker_thread(
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/anyio/_backends/_asyncio.py:937: in run_sync_in_worker_thread
    return await future
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/anyio/_backends/_asyncio.py:867: in run
    result = context.run(func, *args)
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/prefect/orion/database/alembic_commands.py:27: in alembic_upgrade
    import alembic.command
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/alembic/__init__.py:3: in <module>
    from . import context
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/alembic/context.py:1: in <module>
    from .runtime.environment import EnvironmentContext
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/alembic/runtime/environment.py:14: in <module>
    from .migration import MigrationContext
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/alembic/runtime/migration.py:29: in <module>
    from .. import ddl
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/alembic/ddl/__init__.py:1: in <module>
    from . import mssql
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/alembic/ddl/mssql.py:16: in <module>
    from .base import AddColumn
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/alembic/ddl/base.py:16: in <module>
    from ..util.sqla_compat import _columns_for_constraint  # noqa
/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/alembic/util/__init__.py:15: in <module>
    from .messaging import err
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    from __future__ import annotations
    
    from collections.abc import Iterable
    import logging
    import sys
    import textwrap
    from typing import Any
    from typing import Callable
    from typing import Optional
    from typing import TextIO
    from typing import Union
    import warnings
    
    from sqlalchemy.engine import url
    
    from . import sqla_compat
    
    log = logging.getLogger(__name__)
    
    # disable "no handler found" errors
    logging.getLogger("alembic").addHandler(logging.NullHandler())
    
    
    try:
        import fcntl
        import termios
        import struct
    
        ioctl = fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0))
>       _h, TERMWIDTH, _hp, _wp = struct.unpack("HHHH", ioctl)
E       TypeError: a bytes-like object is required, not 'int'

/Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/alembic/util/messaging.py:30: TypeError
==================================================================================================================================== warnings summary =====================================================================================================================================
tests/test_example.py::test_example
  /Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pandas/compat/numpy/__init__.py:10: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    _nlv = LooseVersion(_np_version)

tests/test_example.py::test_example
  /Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pandas/compat/numpy/__init__.py:11: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    np_version_under1p17 = _nlv < LooseVersion("1.17")

tests/test_example.py::test_example
  /Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pandas/compat/numpy/__init__.py:12: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    np_version_under1p18 = _nlv < LooseVersion("1.18")

tests/test_example.py::test_example
  /Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pandas/compat/numpy/__init__.py:13: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    _np_version_under1p19 = _nlv < LooseVersion("1.19")

tests/test_example.py::test_example
  /Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pandas/compat/numpy/__init__.py:14: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    _np_version_under1p20 = _nlv < LooseVersion("1.20")

tests/test_example.py::test_example
  /Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/setuptools/_distutils/version.py:346: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    other = LooseVersion(other)

tests/test_example.py::test_example
tests/test_example.py::test_example
  /Users/alessandro.lollo/.pyenv/versions/3.9.10/envs/prefect-metricflow/lib/python3.9/site-packages/pandas/compat/numpy/function.py:120: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    if LooseVersion(__version__) >= LooseVersion("1.17.0"):

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================================================================================================================= short test summary info =================================================================================================================================
FAILED tests/test_example.py::test_example - TypeError: a bytes-like object is required, not 'int'
============================================================================================================================= 1 failed, 8 warnings in 11.83s ==============================================================================================================================

I think warnings can be ignored, since they're related to Pandas (which is used by MetricFlow behind the scenes).

@mrbean-bremen
Copy link
Member

Ok, I could not have seen this, as I had tested under Windows, and this is Posix-specific code. We actually fake the fcntl.ioctl call, and the result of the faked call seems not to be what is expected. I'm not sure yet if we can fix this (I hope so), or if is one of these cases where pyfakefs won't work. I will have to take a closer look at this first - though I probably won't have time for that before the weekend.

Anyway, thanks for the investigation - this is the kind of information we need to improve pyfakefs.

@AlessandroLollo
Copy link
Author

Ok, I could not have seen this, as I had tested under Windows, and this is Posix-specific code. We actually fake the fcntl.ioctl call, and the result of the faked call seems not to be what is expected. I'm not sure yet if we can fix this (I hope so), or if is one of these cases where pyfakefs won't work. I will have to take a closer look at this first - though I probably won't have time for that before the weekend.

Anyway, thanks for the investigation - this is the kind of information we need to improve pyfakefs.

I'm glad I was able to help! 🙌

@mrbean-bremen
Copy link
Member

I now fixed the return value for fcntl.ioctl and fcntl.fcntl for this case (it depends on the argument types) in master. As before, these calls are just patched and don't do anything, as this is low-level OS stuff that we are not able to emulate.
Still, I had to adapt the setup for your use case even more, as some of the modules tries to read the system time zone information. This is the version that worked for me:

@pytest.fixture
def fs_with_config(fs):
    module_paths = (logging, pytzdata, orion)
    for module_path in module_paths:
        data_path = os.path.dirname(getattr(module_path, "__file__"))
        fs.add_real_directory(data_path)
    if fs.is_linux:
        fs.add_real_file("/etc/localtime")
    elif fs.is_macos:
        fs.add_real_symlink("/etc/localtime")

Note that there is no guarantee that this will still work of the test code gets more complicated and uses more functionalities from the involved libraries.

@mrbean-bremen
Copy link
Member

Closing as fixed, feel free to reopen if it does not work for you.

@allanlewis
Copy link

Is it possible to link the PR that fixed this? (Apologies if I missed it amongst the comments.)

@mrbean-bremen
Copy link
Member

My bad - forgot to mention the issue in the commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants