question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Inspect fails to retrieve module inside frozen app

See original GitHub issue

Description of the issue

when freezing playwright with pyinstaller the assertion in get_file_dirname fails

  File "main.py", line 6, in <module>
    with sync_playwright() as p:
  File "playwright/__init__.py", line 34, in sync_playwright
  File "playwright/main.py", line 81, in __init__
  File "playwright/main.py", line 76, in run_driver
  File "asyncio/base_events.py", line 587, in run_until_complete
  File "playwright/main.py", line 44, in run_driver_async
  File "playwright/main.py", line 36, in compute_driver_executable
  File "playwright/path_utils.py", line 23, in get_file_dirname
AssertionError

The reason is that the inspect module fails to find the module on the following lines inside get_file_dirname

frame = inspect.stack()[1]
module = inspect.getmodule(frame[0])

Context information

  • version of python: 3.7.9
  • version of pyinstaller: 4.1
  • version of playwright-python: Version 0.162.1
  • platform: GNU/Linux (Ubuntu 18.04 LTS)

Reproducing the bug

install packages

$ pip install playwright pyinstaller

install the browsers inside playwright

$ PLAYWRIGHT_BROWSERS_PATH=0 python -m playwright install

create main.py

# main.py
import sys
from pathlib import Path
from playwright import sync_playwright

with sync_playwright() as p:
    for browser_type in [p.chromium]:
        browser = browser_type.launch(
            headless=False,
            executablePath=Path(sys.modules['playwright'].__file__).parent / 'driver' / '.local-browsers' / 'chromium-827102' / 'chrome-linux' / 'chrome')
        page = browser.newPage()
        page.goto('http://whatsmyuseragent.org/')
        page.screenshot(path=f'example-{browser_type.name}.png')
        browser.close()

freeze into single binary file with pyinstaller

$ pyinstaller -F main.py --add-data /path/to/lib/python3.7/site-packages/playwright/driver:playwright/driver

execute the binary file

$ ./dist/main 
Traceback (most recent call last):
  File "main.py", line 6, in <module>
    with sync_playwright() as p:
  File "playwright/__init__.py", line 34, in sync_playwright
  File "playwright/main.py", line 81, in __init__
  File "playwright/main.py", line 76, in run_driver
  File "asyncio/base_events.py", line 587, in run_until_complete
  File "playwright/main.py", line 44, in run_driver_async
  File "playwright/main.py", line 36, in compute_driver_executable
  File "playwright/path_utils.py", line 23, in get_file_dirname
AssertionError

Proposed solution

The problem can be fixed by changing get_file_dirname with

import inspect
import sys
from pathlib import Path

def get_file_dirname() -> Path:
    """Returns the callee (`__file__`) directory name"""
    module_name = inspect.currentframe().f_back.f_globals["__name__"]
    module = sys.modules[module_name]
    assert module
    return Path(module.__file__).parent.absolute()

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

5reactions
mbalslowcommented, Jun 18, 2021

Hopefully this can help anyone struggling to compile a Playwright script with Pyinstaller.

Here is how I have managed to make a template for compiling a Playwright script with Pyinstaller on Windows.

A basic main.py:

from sys import modules
from os import listdir, path
from pathlib import Path
from typing import Union
from playwright import sync_api


def get_executable_path() -> Union[str, None]:
    parent_folder = Path(modules['playwright'].__file__).parent / 'driver' / 'package' / '.local-browsers'

    if not path.exists(parent_folder):
        return None

    child_folders = [name for name in listdir(parent_folder) if path.isdir(parent_folder / name) and name.strip().lower().startswith('chromium')]

    if len(child_folders) != 1:
        return None

    chromium_folder = child_folders[0]

    return parent_folder / chromium_folder / 'chrome-win' / 'chrome.exe'


with sync_api.sync_playwright() as p:
    executable_path = get_executable_path()

    if executable_path:
        browser = p.chromium.launch(
            headless=False,
            executable_path=executable_path
        )
        page = browser.new_page()
        page.goto("http://playwright.dev")
        print(page.title())
        browser.close()

A compile.bat to automate the install and Pyinstaller process:

CALL py -m venv venv
CALL venv\scripts\activate
CALL pip install playwright pyinstaller
CALL SET PLAYWRIGHT_BROWSERS_PATH=0
CALL playwright install chromium
CALL pyinstaller -y -F main.py --add-data "venv/lib/site-packages/playwright/driver;playwright/driver"

Cheers.

1reaction
ThomasTrovoncommented, Feb 14, 2022

I PROPOSE ANOTHER SOLUTION

I use Windows 10. I tried pavelfeldman solution…

image

Did not work! I don’t think I understand the correct way to do this in Windows.

I tried the mbalslow solution and it worked! However, the basic file, which was 8.52 MB, ended up being 137 MB, which, of course, I was not satisfied with.

I have a lot of professional applications (that I make to sell) that I use selenium. And they are on average 14 MB in size. Because, the way that I make, my application does not load the binaries. She installs the binaries on the client’s computer, and uses them. That’s why I get such a lean application, using the following method:

image

That’s when I thought, “What if, like with selenium, I didn’t need to load the binaries into my executable? What if I could just install them on first run on my client’s computer, and then use it in cache like me do with selenium?”

So I started looking for a way to install the binaries through playwright, in a folder on my client, and use that in my application as a cache. But I didn’t find such a solution (Maybe there is, and I’ve just been unable to find it).

And then came another idea: “I currently only develop applications for Windows. Practically every Windows nowadays has: either a Microsoft Edge, or a Google Chrome, or a Firefox. What if I just take the directory of those browsers, and use them?”

So, I developed a way to get, from the windows registry, one of the directories of the browsers binaries, installed on the computer, and then, tell playwright to use it (not done in firefox yet, but it is possible to do):

image

This is an amateur solution made by me, to try to get around the problem. Maybe later it will be possible to do in a more optimized way, this idea of taking the binaries that already exist in the system, and just using them. Don’t take into account the form made, but the idea behind it.

So, putting in focus, the heart the idea is: Use the binaries already existing in the system, just pointing to them!

CONCLUSION: When I run this code through PyCharm it works perfectly! When I compile with Pyinstaller, and run it, I get the following error in my log.log file (because I catch the error, and Write it in a file)

image

I can’t understand why this works in PyCharm, and not with PyInstaller, since the directory, as you can see in the log.log, is correct! Why doesn’t it work in PyInstaller, if I’m pointing correctly to the binary directory?

I believe it is a great idea/solution to be officially applied to the tool: to give the option to use the binaries that already exist on the computer. I just haven’t been able to execute it perfectly yet. Does anyone have any ideas? Did you see my mistake?

Sources Files: source.zip

Thanks for your patience and attention…

Read more comments on GitHub >

github_iconTop Results From Across the Web

Inspect module issues in pyinstaller frozen app
I have frozen pwrapper.py using pyinstaller. When I launch pwrapper.exe from command line, everything works fine. I get the output
Read more >
When Things Go Wrong — PyInstaller 5.7.0 documentation
When you run the bundled app and it terminates with an ImportError, that is the time to examine the warning file. Then see...
Read more >
Troubleshooting guide
Use the Access Gateway troubleshooting guide to resolve issues with your organization's Access Gateway deployment.
Read more >
TorchScript — PyTorch 1.13 documentation
Module will inspect the source code, compile it as TorchScript code using the TorchScript compiler, and return a ScriptModule or ScriptFunction .
Read more >
Command-line API | Node.js v19.3.0 Documentation
This does not affect the Node.js node:vm module. ... --frozen-intrinsics # ... specifies the path to the blob that is used to restore...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found