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

Versions >=0.3.0 have argparse conflict with package pytest-playwright #1469

Closed
rkechols opened this issue Jan 28, 2025 · 8 comments
Closed

Comments

@rkechols
Copy link
Contributor

rkechols commented Jan 28, 2025

Problem Description

Langsmith Python SDK versions >=0.3.0 (latest is currently 0.3.2) seem to add command-line argument --output to pytest entrypoint (perhaps all CLI entrypoints?), which conflicts with pytest plugin pytest-playwright (for example) which adds the same command-line argument (see docs). When both packages are installed in the same python environment, this renders pytest completely unusable, giving the following error:

argparse.ArgumentError: argument --output: conflicting option string: --output

I must apologize that this may seem simply as a conflict between two packages, but I would say the core issue is here in the langsmith package because:

  • only direct entrypoints should interact with command-line arguments
  • only pytest plugins should directly modify the behavior of the pytest entrypoint

Additionally, even if we suppose the langsmith package is justified in interacting with command-line arguments:

  • the --output command-line argument is not documented by langsmith (at least not anywhere that I could find; correct me if I'm wrong)
  • pytest-playwright got to it first; they published releases using --output before langsmith did

Potential Solutions

  • Offer a separate python package such as pytest-langsmith which is a proper pytest plugin, and only modify pytest behavior if this plugin is installed; this allows the environment to have regular langsmith installed without it interfering with command-line arguments
  • Use an environment variable in place of a command-line argument
  • Rename the command-line argument from plain --output to --langsmith-output to reduce chance of collisions

Until an actual solution is implemented, I will be tied to using langsmith<0.3.0

Full Error Stack Trace

$ pytest
Traceback (most recent call last):
  File "/Users/rechols/temp/.venv/bin/pytest", line 10, in <module>
    sys.exit(console_main())
             ~~~~~~~~~~~~^^
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/_pytest/config/__init__.py", line 201, in console_main
    code = main()
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/_pytest/config/__init__.py", line 156, in main
    config = _prepareconfig(args, plugins)
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/_pytest/config/__init__.py", line 341, in _prepareconfig
    config = pluginmanager.hook.pytest_cmdline_parse(
        pluginmanager=pluginmanager, args=args
    )
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/pluggy/_callers.py", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ~~~~~~~~~~~~~~^^^^^^^^^^^
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/_pytest/helpconfig.py", line 105, in pytest_cmdline_parse
    config = yield
             ^^^^^
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/_pytest/config/__init__.py", line 1140, in pytest_cmdline_parse
    self.parse(args)
    ~~~~~~~~~~^^^^^^
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/_pytest/config/__init__.py", line 1494, in parse
    self._preparse(args, addopts=addopts)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/_pytest/config/__init__.py", line 1384, in _preparse
    self.known_args_namespace = self._parser.parse_known_args(
                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        args, namespace=copy.copy(self.known_args_namespace)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/_pytest/config/argparsing.py", line 158, in parse_known_args
    return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/_pytest/config/argparsing.py", line 172, in parse_known_and_unknown_args
    optparser = self._getparser()
  File "/Users/rechols/temp/.venv/lib/python3.13/site-packages/_pytest/config/argparsing.py", line 131, in _getparser
    arggroup.add_argument(*n, **a)
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/argparse.py", line 1483, in add_argument
    return self._add_action(action)
           ~~~~~~~~~~~~~~~~^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/argparse.py", line 1690, in _add_action
    action = super(_ArgumentGroup, self)._add_action(action)
  File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/argparse.py", line 1497, in _add_action
    self._check_conflict(action)
    ~~~~~~~~~~~~~~~~~~~~^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/argparse.py", line 1639, in _check_conflict
    conflict_handler(action, confl_optionals)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/argparse.py", line 1648, in _handle_conflict_error
    raise ArgumentError(action, message % conflict_string)
argparse.ArgumentError: argument --output: conflicting option string: --output

Environment

  • Machine: MacBook Pro with Apple M3 chip
  • OS: macOS 14.4.1 (23E224)
  • Python version: any currently active python version (3.9 through 3.13)

pyproject.toml:

[project]
name = "temp"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
    "langsmith~=0.3.0",
    "playwright==1.49.1",
]

[dependency-groups]
dev = [
    "pytest>=8.2.0,<9",
    "pytest-playwright~=0.6.2",
]

requirements.txt

# This file was autogenerated by uv via the following command:
#    uv export --locked --no-hashes --output-file requirements.txt
annotated-types==0.7.0
anyio==4.8.0
certifi==2024.12.14
cffi==1.17.1 ; platform_python_implementation == 'PyPy'
charset-normalizer==3.4.1
colorama==0.4.6 ; sys_platform == 'win32'
exceptiongroup==1.2.2 ; python_full_version < '3.11'
greenlet==3.1.1
h11==0.14.0
httpcore==1.0.7
httpx==0.28.1
idna==3.10
iniconfig==2.0.0
langsmith==0.3.2
orjson==3.10.15 ; platform_python_implementation != 'PyPy'
packaging==24.2
playwright==1.49.1
pluggy==1.5.0
pycparser==2.22 ; platform_python_implementation == 'PyPy'
pydantic==2.10.6
pydantic-core==2.27.2
pyee==12.0.0
pytest==8.3.4
pytest-base-url==2.1.0
pytest-playwright==0.6.2
python-slugify==8.0.4
requests==2.32.3
requests-toolbelt==1.0.0
sniffio==1.3.1
text-unidecode==1.3
tomli==2.2.1 ; python_full_version < '3.11'
typing-extensions==4.12.2
urllib3==2.3.0
zstandard==0.23.0
@alessioerosferri
Copy link

+1 to this, having the same issue

@asengupta
Copy link

+1 to the above, I have the same issue.

@rkechols
Copy link
Contributor Author

rkechols commented Jan 29, 2025

Upon inspection, it seems that the langsmith package does install itself as a pytest plugin:

This is not well documented.

If this pytest plugin were to be split into its own package (such as pytest-langsmith) then the conflicting --output behavior would become optional, allowing users to keep langsmith installed and up-to-date, and just forgoing pytest-langsmith because of the conflict.

If the langsmith team wishes to keep the pytest plugin embedded in the core langsmith package, then the --output command-line argument should be renamed to something such as --langsmith-output to avoid collisions with other pytest plugins.

@baskaryan you seem to be the one that implemented the pytest plugin? (PR here) What are your thoughts?

@rkechols
Copy link
Contributor Author

I've discovered that pytest plugins can be disabled: https://docs.pytest.org/en/stable/how-to/writing_plugins.html#plugin-discovery-order-at-tool-startup

If possible, I think this pytest plugin should be disabled by default, requiring opt-in, since it's included as part of the base langsmith package. I'm trying to find how to do so.

@rkechols
Copy link
Contributor Author

rkechols commented Jan 29, 2025

Here is a PR that should fix the issue: #1472

I also discovered that there is documentation regarding the pytest plugin (here), it was just very difficult to find.

@jacoblee93
Copy link
Collaborator

Hey folks, thanks for raising this and for the PR. Will look ASAP

@baskaryan
Copy link
Contributor

thanks for flagging! did not realize this would lead to conflicts.

would prefer one or both of:

  1. renaming the --output option to something less likely to have a collision
  2. adding an explicit check for the existing options in the pytest_plugin.py file

for 1. maybe we add a flag --langsmith-output?

for 2. believe we should be able to do something simple like

def pytest_addoption(parser):
    """Set a boolean flag for LangSmith output, but skip if --langsmith-output is already defined."""
    # 1) Check if --langsmith-output is already present:
    for action in parser._actions:
        if "--langsmith-output" in action.option_strings:
            logger.warning("LangSmith output flag cannot be added because ...")
            return

    # 2) If it's not found, register the flag
    group = parser.getgroup("langsmith", "LangSmith")
    group.addoption(
        "--langsmith-output",
        action="store_true",
        default=False,
        help="Use LangSmith output (requires 'rich')."
    )

and then updating the corresponding docs page to highlight the change: https://github.com/langchain-ai/langsmith-docs/blob/5260804cc21542370da306aeca461d59995e6537/docs/evaluation/how_to_guides/pytest.mdx?plain=1#L302

@jacoblee93
Copy link
Collaborator

Fixed in #1482 and shipped in langsmith@0.3.4.

Unfortunately required a breaking change - new flag is --langsmith-output. Will update docs shortly. Thanks all for reports!

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

No branches or pull requests

5 participants