pyinfra icon indicating copy to clipboard operation
pyinfra copied to clipboard

facts.server.Mounts broken on 3.4

Open DtxdF opened this issue 5 months ago • 5 comments

Describe the bug

  1. Calling the facts.server.Mounts fact from the fact operation results in the AttributeError: 'gevent._gevent_clocal.local' object has no attribute 'module' exception.
  2. Calling the facts.server.Mounts fact from a deployment file results in the ValueError: not enough values to unpack (expected 2, got 1) exception.

To Reproduce

  1. AttributeError: 'gevent._gevent_clocal.local' object has no attribute 'module': console:

    $ pyinfra --limit control-r2 inventory.py fact server.Mounts
    ...
    AttributeError: 'gevent._gevent_clocal.local' object has no attribute 'module'
    
  2. ValueError: not enough values to unpack (expected 2, got 1) deployments/appjail.py (excerpt):

    if host.data.get("tmpfs"):             
      files.directory(                     
        name="Create directory for temporary directory",
        path="/usr/local/appjail/cache/tmp/.appjail"
      )                            
    
      files.line(
        name="Configure fstab to mount the in-memory temporary directory",
        line="tmpfs /usr/local/appjail/cache/tmp/.appjail tmpfs rw,late 0 0",      
        path="/etc/fstab"               
      )                                                                            
    
      server.mount(                  
        name="Mount tmpfs as the temporary directory",                             
        path="/usr/local/appjail/cache/tmp/.appjail",                              
        options=["rw"],                                                            
        device="tmpfs",
        fs_type="tmpfs"                    
      )
    

    console:

    $ pyinfra -y --limit control-r2 inventory.py deployments/appjail.py
    ...
    ValueError: not enough values to unpack (expected 2, got 1)
    

Meta

  • Include output of pyinfra --support:
     /home/user/Devel/pyinfra/env/lib/python3.11/site-packages/paramiko/pkey.py:82: CryptographyDeprecationWarning: TripleDES has been moved to cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and will be removed from cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.
       "cipher": algorithms.TripleDES,
     /home/user/Devel/pyinfra/env/lib/python3.11/site-packages/paramiko/transport.py:253: CryptographyDeprecationWarning: TripleDES has been moved to cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and will be removed from cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.
       "class": algorithms.TripleDES,
    
         If you are having issues with pyinfra or wish to make feature requests, please
         check out the GitHub issues at https://github.com/Fizzadar/pyinfra/issues .
         When adding an issue, be sure to include the following:
    
         System: FreeBSD
           Platform: FreeBSD-14.3-RELEASE-amd64-64bit-ELF
           Release: 14.3-RELEASE
           Machine: amd64
         pyinfra: v3.4
           click: v8.2.1
           click: v8.2.1
           click: v8.2.1
           distro: v1.9.0
           gevent: v25.5.1
           jinja2: v3.1.6
           packaging: v25.0
           paramiko: v2.11.0
           python-dateutil: v2.9.0.post0
           pywinrm: v0.5.0
           typeguard: v4.4.4
           typing-extensions: v4.14.1
         Executable: /home/user/Devel/pyinfra/env/bin/pyinfra
         Python: 3.11.13 (CPython, Clang 18.1.6 (https://github.com/llvm/llvm-project.git llvmorg-18.1.6-0-g1118c2)
    
  • How was pyinfra installed (source/pip): virtualenv env && . env/bin/activate && pip install -e .
  • Include pyinfra-debug.log (if one was created)
    • ValueError: not enough values to unpack (expected 2, got 1):
     File "/home/user/Devel/pyinfra/pyinfra_cli/main.py", line 225, in cli
       _main(*args, **kwargs)
     File "/home/user/Devel/pyinfra/pyinfra_cli/main.py", line 394, in _main
       run_ops(state, serial=serial, no_wait=no_wait)
     File "/home/user/Devel/pyinfra/pyinfra/api/operations.py", line 334, in run_ops
       _run_single_op(state, op_hash)
     File "/home/user/Devel/pyinfra/pyinfra/api/operations.py", line 302, in _run_single_op
       if not greenlet.get():
              ^^^^^^^^^^^^^^
     File "src/gevent/greenlet.py", line 797, in gevent._gevent_cgreenlet.Greenlet.get
     File "src/gevent/greenlet.py", line 373, in gevent._gevent_cgreenlet.Greenlet._raise_exception
     File "/home/user/Devel/pyinfra/env/lib/python3.11/site-packages/gevent/_compat.py", line 51, in reraise
       raise value.with_traceback(tb)
     File "src/gevent/greenlet.py", line 900, in gevent._gevent_cgreenlet.Greenlet.run
     File "/home/user/Devel/pyinfra/pyinfra/api/operations.py", line 184, in _run_host_op_with_context
       return run_host_op(state, host, op_hash)
       ^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/api/operations.py", line 53, in run_host_op
       return _run_host_op(state, host, op_hash)
       ^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/api/operations.py", line 81, in _run_host_op
       for command in op_data.command_generator():
         ^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/api/operation.py", line 283, in command_generator
       for command in func(*args, **kwargs):
       ^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/operations/server.py", line 314, in mount
       mounts = host.get_fact(Mounts)
         ^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/api/host.py", line 367, in get_fact
       return get_fact(self.state, self, name_or_cls, args=args, kwargs=kwargs)
       ^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 185, in get_fact
       return _get_fact(
         ^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 269, in _get_fact
       data = fact.process(stdout_lines)
       ^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/facts/server.py", line 253, in process
       optional, line = line.split(sep=" ", maxsplit=1)
       ^^^^^^^^^^^^^^^^^
    ValueError: not enough values to unpack (expected 2, got 1)
    
    • AttributeError: 'gevent._gevent_clocal.local' object has no attribute 'module':
     File "/home/user/Devel/pyinfra/pyinfra_cli/main.py", line 225, in cli
       _main(*args, **kwargs)
     File "/home/user/Devel/pyinfra/pyinfra_cli/main.py", line 354, in _main
       can_diff, state, config = _handle_commands(
                                 ^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra_cli/main.py", line 656, in _handle_commands
       state, fact_data = _run_fact_operations(state, config, operations)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra_cli/main.py", line 697, in _run_fact_operations
       fact_data[fact_key] = get_facts(
                             ^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 159, in get_facts
       results[host] = greenlet.get()
                       ^^^^^^^^^^^^^^
     File "src/gevent/greenlet.py", line 797, in gevent._gevent_cgreenlet.Greenlet.get
     File "src/gevent/greenlet.py", line 373, in gevent._gevent_cgreenlet.Greenlet._raise_exception
     File "/home/user/Devel/pyinfra/env/lib/python3.11/site-packages/gevent/_compat.py", line 51, in reraise
       raise value.with_traceback(tb)
     File "src/gevent/greenlet.py", line 900, in gevent._gevent_cgreenlet.Greenlet.run
     File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 147, in get_host_fact
       return get_fact(state, host, *args, **kwargs)
       ^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 185, in get_fact
       return _get_fact(
         ^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 228, in _get_fact
       command = _make_command(fact.command, fact_kwargs)
         ^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 118, in _make_command
       return command_attribute(**host_args)
       ^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/facts/server.py", line 211, in command
       self._kernel = host.get_fact(Kernel)
       ^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/context.py", line 53, in __getattr__
       if self._get_module() is None:
       ^^^^^^^^^^^^^^^^^
     File "/home/user/Devel/pyinfra/pyinfra/context.py", line 35, in _get_module
       return self._container.module
       ^^^^^^^^^^^^^^^^^
     File "src/gevent/local.py", line 410, in gevent._gevent_clocal.local.__getattribute__
    AttributeError: 'gevent._gevent_clocal.local' object has no attribute 'module'
    
  • Consider including output with -vv and --debug
    • ValueError: not enough values to unpack (expected 2, got 1):
       /home/user/Devel/pyinfra/env/lib/python3.11/site-packages/paramiko/pkey.py:82: CryptographyDeprecationWarning: TripleDES has been moved to cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and will be removed from cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.
         "cipher": algorithms.TripleDES,
       /home/user/Devel/pyinfra/env/lib/python3.11/site-packages/paramiko/transport.py:253: CryptographyDeprecationWarning: TripleDES has been moved to cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and will be removed from cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.
         "class": algorithms.TripleDES,
       --> Loading config...
       --> Loading inventory...
           [pyinfra_cli.inventory] Creating fake inventory...
           [pyinfra_cli.inventory] Checking possible group_data at: /home/user/Automation.pyinfra/group_data
           [pyinfra_cli.inventory] Looking for group data in: /home/user/Automation.pyinfra/group_data/all.py
           [pyinfra_cli.inventory] Looking for group data in: /home/user/Automation.pyinfra/group_data/vm.py
           [pyinfra_cli.inventory] Adding data to group all: {'promtail_conf': 'templates/promtail.yaml.j2', 'dnsmasq_conf': 'files/dnsmasq.conf', 'tmpfs': False, 'pf_conf': 'files/pf.conf', 'appjail_conf': 'templates/appjail.conf.j2', 'appjail_resolv_conf': 'files/appjail-resolv.conf', 'enable_zfs': False, 'enable_debug': False, 'freebsd_version': '14.3', 'overlord_conf': 'files/overlord.yml', '_get_pty': True}
           [pyinfra_cli.inventory] Adding data to group vm: {'ext_if': 'vtnet0', 'tmpfs': True}
      
       --> Connecting to hosts...
           [pyinfra.connectors.ssh] Connecting to: control-r2 ({'allow_agent': True, 'look_for_keys': True, '_pyinfra_ssh_forward_agent': False, '_pyinfra_ssh_config_file': None, '_pyinfra_ssh_known_hosts_file': None, '_pyinfra_ssh_strict_host_key_checking': 'accept-new', '_pyinfra_ssh_paramiko_connect_kwargs': None, 'timeout': 10})
           [pyinfra.connectors.sshuserclient.client] Loading SSH config: None
           [control-r2]         Connected
           [pyinfra.api.state] Activating host: control-r2
      
       --> Preparing operation files...
           Loading: deployments/appjail.py
           [pyinfra.api.operation] Adding operation, {'Create directory for temporary directory'}, opOrder=(0, 10), opHash=3b2f90227d43a40a2288506c2a99bdc5e71f32b2
           [pyinfra.api.facts] Getting fact: files.Directory (path=/usr/local/appjail/cache/tmp/.appjail) (ensure_hosts: None)
           [pyinfra.connectors.ssh] Running command on control-r2: (pty=True) sh -c '! (test -e /usr/local/appjail/cache/tmp/.appjail || test -L /usr/local/appjail/cache/tmp/.appjail ) || ( stat -c '"'"'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N'"'"' /usr/local/appjail/cache/tmp/.appjail 2> /dev/null || stat -f '"'"'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY'"'"' /usr/local/appjail/cache/tmp/.appjail )'
       [control-r2]         >>> sh -c '! (test -e /usr/local/appjail/cache/tmp/.appjail || test -L /usr/local/appjail/cache/tmp/.appjail ) || ( stat -c '"'"'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N'"'"' /usr/local/appjail/cache/tmp/.appjail 2> /dev/null || stat -f '"'"'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY'"'"' /usr/local/appjail/cache/tmp/.appjail )'
           [pyinfra.connectors.ssh] Waiting for exit status...
           [pyinfra.connectors.ssh] Command exit status: 0
           [control-r2]         Loaded fact files.Directory (path=/usr/local/appjail/cache/tmp/.appjail)
           [control-r2]         noop: directory /usr/local/appjail/cache/tmp/.appjail already exists
           [pyinfra.api.operation] Adding operation, {'Configure fstab to mount the in-memory temporary directory'}, opOrder=(0, 15), opHash=51f6b23b4aa3365d626750a07950b82d1a301941
           [pyinfra.api.facts] Getting fact: files.FindInFile (interpolate_variables=False, path=/etc/fstab, pattern=^.*tmpfs /usr/local/appjail/cache/tmp/.appjail tmpfs rw,late 0 0.*$) (ensure_hosts: None)
           [pyinfra.connectors.ssh] Running command on control-r2: (pty=True) sh -c 'grep -e '"'"'^.*tmpfs /usr/local/appjail/cache/tmp/.appjail tmpfs rw,late 0 0.*$'"'"' /etc/fstab 2> /dev/null || ( find /etc/fstab -type f > /dev/null && echo __pyinfra_exists_/etc/fstab || true )'
       [control-r2]         >>> sh -c 'grep -e '"'"'^.*tmpfs /usr/local/appjail/cache/tmp/.appjail tmpfs rw,late 0 0.*$'"'"' /etc/fstab 2> /dev/null || ( find /etc/fstab -type f > /dev/null && echo __pyinfra_exists_/etc/fstab || true )'
           [pyinfra.connectors.ssh] Waiting for exit status...
           [pyinfra.connectors.ssh] Command exit status: 0
           [control-r2]         Loaded fact files.FindInFile (interpolate_variables=False, path=/etc/fstab, pattern=^.*tmpfs /usr/local/appjail/cache/tmp/.appjail tmpfs rw,late 0 0.*$)
           [control-r2]         noop: line "tmpfs /usr/local/appjail/cache/tmp/.appjail tmpfs rw,late 0 0" exists in /etc/fstab
           [pyinfra.api.operation] Adding operation, {'Mount tmpfs as the temporary directory'}, opOrder=(0, 21), opHash=b1d1da4808a9765a47e35b9cfb13e271e108f5f0
           [pyinfra.api.facts] Getting fact: server.Mounts () (ensure_hosts: None)
           [pyinfra.api.facts] Getting fact: server.Kernel () (ensure_hosts: None)
           [pyinfra.connectors.ssh] Running command on control-r2: (pty=True) sh -c 'uname -s'
       [control-r2]         >>> sh -c 'uname -s'
           [pyinfra.connectors.ssh] Waiting for exit status...
           [pyinfra.connectors.ssh] Command exit status: 0
           [control-r2]         Loaded fact server.Kernel
           [pyinfra.connectors.ssh] Running command on control-r2: (pty=True) sh -c 'mount -p --libxo json'
       [control-r2]         >>> sh -c 'mount -p --libxo json'
           [pyinfra.connectors.ssh] Waiting for exit status...
           [pyinfra.connectors.ssh] Command exit status: 0
      
       --> Disconnecting from hosts...
       --> An exception occurred in: deployments/appjail.py:
      
       Traceback (most recent call last):
         File "/home/user/Devel/pyinfra/pyinfra_cli/util.py", line 65, in exec_file
           exec(PYTHON_CODES[filename], data)
         File "deployments/appjail.py", line 21, in <module>
           server.mount(
         File "/home/user/Devel/pyinfra/pyinfra/api/operation.py", line 296, in decorated_func
           for _ in command_generator():
         File "/home/user/Devel/pyinfra/pyinfra/api/operation.py", line 283, in command_generator
           for command in func(*args, **kwargs):
         File "/home/user/Devel/pyinfra/pyinfra/operations/server.py", line 314, in mount
           mounts = host.get_fact(Mounts)
                    ^^^^^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/api/host.py", line 367, in get_fact
           return get_fact(self.state, self, name_or_cls, args=args, kwargs=kwargs)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 185, in get_fact
           return _get_fact(
                  ^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 269, in _get_fact
           data = fact.process(stdout_lines)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/facts/server.py", line 253, in process
           optional, line = line.split(sep=" ", maxsplit=1)
           ^^^^^^^^^^^^^^
       ValueError: not enough values to unpack (expected 2, got 1)
      
    • AttributeError: 'gevent._gevent_clocal.local' object has no attribute 'module':
       /home/user/Devel/pyinfra/env/lib/python3.11/site-packages/paramiko/pkey.py:82: CryptographyDeprecationWarning: TripleDES has been moved to cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and will be removed from cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.
         "cipher": algorithms.TripleDES,
       /home/user/Devel/pyinfra/env/lib/python3.11/site-packages/paramiko/transport.py:253: CryptographyDeprecationWarning: TripleDES has been moved to cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and will be removed from cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.
         "class": algorithms.TripleDES,
       --> Loading config...
       --> Loading inventory...
           [pyinfra_cli.inventory] Creating fake inventory...
           [pyinfra_cli.inventory] Checking possible group_data at: /home/user/Automation.pyinfra/group_data
           [pyinfra_cli.inventory] Looking for group data in: /home/user/Automation.pyinfra/group_data/all.py
           [pyinfra_cli.inventory] Looking for group data in: /home/user/Automation.pyinfra/group_data/vm.py
           [pyinfra_cli.inventory] Adding data to group all: {'promtail_conf': 'templates/promtail.yaml.j2', 'dnsmasq_conf': 'files/dnsmasq.conf', 'tmpfs': False, 'pf_conf': 'files/pf.conf', 'appjail_conf': 'templates/appjail.conf.j2', 'appjail_resolv_conf': 'files/appjail-resolv.conf', 'enable_zfs': False, 'enable_debug': False, 'freebsd_version': '14.3', 'overlord_conf': 'files/overlord.yml', '_get_pty': True}
           [pyinfra_cli.inventory] Adding data to group vm: {'ext_if': 'vtnet0', 'tmpfs': True}
      
       --> Connecting to hosts...
           [pyinfra.connectors.ssh] Connecting to: control-r2 ({'allow_agent': True, 'look_for_keys': True, '_pyinfra_ssh_forward_agent': False, '_pyinfra_ssh_config_file': None, '_pyinfra_ssh_known_hosts_file': None, '_pyinfra_ssh_strict_host_key_checking': 'accept-new', '_pyinfra_ssh_paramiko_connect_kwargs': None, 'timeout': 10})
           [pyinfra.connectors.sshuserclient.client] Loading SSH config: None
           [control-r2]         Connected
           [pyinfra.api.state] Activating host: control-r2
      
       --> Gathering facts...
           [pyinfra.api.facts] Getting fact: server.Mounts () (ensure_hosts: None)
       Traceback (most recent call last):
         File "src/gevent/greenlet.py", line 900, in gevent._gevent_cgreenlet.Greenlet.run
         File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 147, in get_host_fact
           return get_fact(state, host, *args, **kwargs)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 185, in get_fact
           return _get_fact(
                  ^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 228, in _get_fact
           command = _make_command(fact.command, fact_kwargs)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 118, in _make_command
           return command_attribute(**host_args)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/facts/server.py", line 211, in command
           self._kernel = host.get_fact(Kernel)
                          ^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/context.py", line 53, in __getattr__
           if self._get_module() is None:
              ^^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/context.py", line 35, in _get_module
           return self._container.module
                  ^^^^^^^^^^^^^^^^^^^^^^
         File "src/gevent/local.py", line 410, in gevent._gevent_clocal.local.__getattribute__
       AttributeError: 'gevent._gevent_clocal.local' object has no attribute 'module'
       2025-07-12T20:35:13Z <Greenlet at 0x2e43b58b04a0: get_host_fact(Host(control-r2), <class 'pyinfra.facts.server.Mounts'>, args=(), kwargs={}, apply_failed_hosts=False)> failed with AttributeError
      
      
       --> Disconnecting from hosts...
       --> An internal exception occurred:
      
         File "src/gevent/local.py", line 410, in gevent._gevent_clocal.local.__getattribute__
       AttributeError: 'gevent._gevent_clocal.local' object has no attribute 'module'
      
           [pyinfra_cli.exceptions]   File "/home/user/Devel/pyinfra/pyinfra_cli/main.py", line 225, in cli
           _main(*args, **kwargs)
         File "/home/user/Devel/pyinfra/pyinfra_cli/main.py", line 354, in _main
           can_diff, state, config = _handle_commands(
                                     ^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra_cli/main.py", line 656, in _handle_commands
           state, fact_data = _run_fact_operations(state, config, operations)
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra_cli/main.py", line 697, in _run_fact_operations
           fact_data[fact_key] = get_facts(
                                 ^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 159, in get_facts
           results[host] = greenlet.get()
                           ^^^^^^^^^^^^^^
         File "src/gevent/greenlet.py", line 797, in gevent._gevent_cgreenlet.Greenlet.get
         File "src/gevent/greenlet.py", line 373, in gevent._gevent_cgreenlet.Greenlet._raise_exception
         File "/home/user/Devel/pyinfra/env/lib/python3.11/site-packages/gevent/_compat.py", line 51, in reraise
           raise value.with_traceback(tb)
         File "src/gevent/greenlet.py", line 900, in gevent._gevent_cgreenlet.Greenlet.run
         File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 147, in get_host_fact
           return get_fact(state, host, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 185, in get_fact
           return _get_fact(
             ^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 228, in _get_fact
           command = _make_command(fact.command, fact_kwargs)
             ^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/api/facts.py", line 118, in _make_command
           return command_attribute(**host_args)
           ^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/facts/server.py", line 211, in command
           self._kernel = host.get_fact(Kernel)
           ^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/context.py", line 53, in __getattr__
           if self._get_module() is None:
           ^^^^^^^^^^^^^^^^^
         File "/home/user/Devel/pyinfra/pyinfra/context.py", line 35, in _get_module
           return self._container.module
           ^^^^^^^^^^^^^^^^^
         File "src/gevent/local.py", line 410, in gevent._gevent_clocal.local.__getattribute__
      
           [pyinfra_cli.exceptions] AttributeError: 'gevent._gevent_clocal.local' object has no attribute 'module'
      
       --> The full traceback has been written to pyinfra-debug.log
       --> If this is unexpected please consider submitting a bug report on GitHub, for more information run `pyinfra --support`.
      

DtxdF avatar Jul 12 '25 20:07 DtxdF

I believe this should be fixed by 3.4.1 (fix: https://github.com/pyinfra-dev/pyinfra/commit/8e2335415d6a48ce05dbbe086f932cec4648c61f)

Fizzadar avatar Aug 06 '25 09:08 Fizzadar

I believe this should be fixed by 3.4.1 (fix: 8e23354)

Tested again at that branch, but the same error occurred.

Can you reproduce the issue?

DtxdF avatar Aug 07 '25 06:08 DtxdF

I can reproduce this issue by running pyinfra @local fact server.Mounts (Fedora 42, Python 3.13.7, pyinfra @ eb0e683):

Traceback (most recent call last):
  File "src/gevent/greenlet.py", line 900, in gevent._gevent_cgreenlet.Greenlet.run
  File "/data/git/pyinfra/pyinfra/api/facts.py", line 147, in get_host_fact
    return get_fact(state, host, *args, **kwargs)
  File "/data/git/pyinfra/pyinfra/api/facts.py", line 203, in get_fact
    return _get_fact(
        state,
    ...<5 lines>...
        apply_failed_hosts,
    )
  File "/data/git/pyinfra/pyinfra/api/facts.py", line 246, in _get_fact
    command = _make_command(fact.command, fact_kwargs)
  File "/data/git/pyinfra/pyinfra/api/facts.py", line 118, in _make_command
    return command_attribute(**host_args)
  File "/data/git/pyinfra/pyinfra/facts/server.py", line 211, in command
    self._kernel = host.get_fact(Kernel)
                   ^^^^^^^^^^^^^
  File "/data/git/pyinfra/pyinfra/context.py", line 53, in __getattr__
    if self._get_module() is None:
       ~~~~~~~~~~~~~~~~^^
  File "/data/git/pyinfra/pyinfra/context.py", line 35, in _get_module
    return self._container.module
           ^^^^^^^^^^^^^^^^^^^^^^
  File "src/gevent/local.py", line 410, in gevent._gevent_clocal.local.__getattribute__
AttributeError: 'gevent._gevent_clocal.local' object has no attribute 'module'
2025-09-13T20:37:59Z <Greenlet at 0x7ff9d3f3c360: get_host_fact(Host(@local), <class 'pyinfra.facts.server.Mounts'>, args=(), kwargs={}, apply_failed_hosts=False)> failed with AttributeError

simonhammes avatar Sep 13 '25 20:09 simonhammes

Found it! It's due to the change in 3.4 to allow facts to call each other, but the context was never correctly set in the facts API, https://github.com/pyinfra-dev/pyinfra/commit/0c48515f324b1b73b5c5fd6df5291732be989baf will fix.

Fizzadar avatar Sep 16 '25 18:09 Fizzadar

0c48515 will fix.

I can confirm this 🎉

simonhammes avatar Sep 16 '25 19:09 simonhammes