Skip to content

Conversation

@DrorSh
Copy link
Contributor

@DrorSh DrorSh commented Nov 22, 2025

Fix TypeError with Click Sentinel in Python 3.14

Closes #722

Problem

When running pytask 0.5.6 with Python 3.14 and Click 8.3.1, users encounter:

TypeError: object of type 'Sentinel' has no len()

This occurs during dependency resolution when no -k or -m flags are provided.

Root Cause

In Click 8.3.1, when a click.Option doesn't have an explicit default parameter, it uses Sentinel.UNSET (an enum) instead of None.

The code path:

  1. session.config["expression"] retrieves Sentinel.UNSET when no -k flag is given
  2. select_by_keyword() passes this to Expression.compile_(keywordexpr)
  3. Scanner.lex() tries to call len(input_) on the Sentinel
  4. TypeError is raised because Sentinel objects don't support len()

Stack trace shows error at:

_pytask/mark/expression.py:92 in lex
    while pos < len(input_):

Solution

Add explicit default=None to both click.Option definitions in src/_pytask/mark/__init__.py:

  • Line 95: -m/--marker_expression
  • Line 102: -k/--expression

The existing if not keywordexpr: checks (lines 157, 212) already handle None correctly.

Changes

  • src/_pytask/mark/__init__.py: Added default=None to two click.Option definitions

Testing

Tested with:

  • Python 3.14.0
  • Click 8.3.1
  • pytask 0.5.6

Before fix:

TypeError: object of type 'Sentinel' has no len()

After fix:

✓ 1 Collected task
✓ 1 Succeeded (100.0%)
Succeeded in 0.03 seconds

Also verified:

  • -k expression flag works correctly when provided
  • -m marker flag works correctly when provided
  • Tasks execute and produce expected output

Compatibility

This fix maintains backward compatibility while adding support for:

  • Python 3.14
  • Click 8.3.1+

This is my first contribution to pytask. Happy to make any changes you'd like!

Add explicit default=None to click.Option definitions for -m and -k flags
to prevent Click 8.3.1 from using Sentinel.UNSET as default value.

When Click options don't have an explicit default, Click 8.3.1 uses
Sentinel.UNSET (an enum). This causes a TypeError when the code tries
to call len() on the sentinel value in mark/expression.py:92.

The fix ensures both marker_expression and expression options default
to None, which the existing 'if not keywordexpr:' checks handle correctly.

Fixes compatibility with Python 3.14 and Click 8.3.1.

Tested with the minimal reproduction case - pytask now runs successfully.
@codecov
Copy link

codecov bot commented Nov 22, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.19%. Comparing base (ad3680e) to head (a92d4ff).
⚠️ Report is 143 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #723      +/-   ##
==========================================
- Coverage   97.80%   97.19%   -0.62%     
==========================================
  Files         106      114       +8     
  Lines        8710     9273     +563     
==========================================
+ Hits         8519     9013     +494     
- Misses        191      260      +69     
Flag Coverage Δ
end_to_end ?
integration ?
unit ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tobiasraabe
Copy link
Member

@DrorSh, thanks a lot for the quick fix!

@tobiasraabe tobiasraabe merged commit fc8c697 into pytask-dev:main Nov 22, 2025
19 of 20 checks passed
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

Successfully merging this pull request may close these issues.

BUG: object of type 'Sentinel' has no len()

2 participants