Skip to content

Add progress callback support to Payload#12340

Draft
mib1185 wants to merge 4 commits intoaio-libs:masterfrom
mib1185:add-progress-callback-to-payload-writer
Draft

Add progress callback support to Payload#12340
mib1185 wants to merge 4 commits intoaio-libs:masterfrom
mib1185:add-progress-callback-to-payload-writer

Conversation

@mib1185
Copy link
Copy Markdown

@mib1185 mib1185 commented Apr 8, 2026

What do these changes do?

This adds the possibility to provide a callback to the Payload class, which is used by their writer methods to report back the already written bytes. The intention is, that a consuming application can get a progress indication - in my specific case, I want to add the backup upload progress to the Synology DSM integration in Home Assistant. The Synology DSM uses the aiohttp client with the MultipartWriter to perform the upload. The goal is to provide the callback like:

  with MultipartWriter("form-data", boundary=boundary) as mp:
      part = mp.append(path)

      if progress_callback is not None:
          part.set_progress_callback(progress_callback)

todo:

  • add tests
  • add docs

Are there changes in behavior for the user?

No, as the callback is set to None by default

Is it a substantial burden for the maintainers to support this?

I don't think so.

Related issue number

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes
  • If you provide code modification, please add yourself to CONTRIBUTORS.txt
    • The format is <Name> <Surname>.
    • Please keep alphabetical order, the file is sorted by names.
  • Add a new news fragment into the CHANGES/ folder
    • name it <issue_or_pr_num>.<type>.rst (e.g. 588.bugfix.rst)

    • if you don't have an issue number, change it to the pull request
      number after creating the PR

      • .bugfix: A bug fix for something the maintainers deemed an
        improper undesired behavior that got corrected to match
        pre-agreed expectations.
      • .feature: A new behavior, public APIs. That sort of stuff.
      • .deprecation: A declaration of future API removals and breaking
        changes in behavior.
      • .breaking: When something public is removed in a breaking way.
        Could be deprecated in an earlier release.
      • .doc: Notable updates to the documentation structure or build
        process.
      • .packaging: Notes for downstreams about unobvious side effects
        and tooling. Changes in the test invocation considerations and
        runtime assumptions.
      • .contrib: Stuff that affects the contributor experience. e.g.
        Running tests, building the docs, setting up the development
        environment.
      • .misc: Changes that are hard to assign to any of the above
        categories.
    • Make sure to use full sentences with correct case and punctuation,
      for example:

      Fixed issue with non-ascii contents in doctest text files
      -- by :user:`contributor-gh-handle`.

      Use the past tense or the present tense a non-imperative mood,
      referring to what's changed compared to the last released version
      of this project.

@psf-chronographer psf-chronographer bot added the bot:chronographer:provided There is a change note present in this PR label Apr 8, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 8, 2026

❌ 3 Tests Failed:

Tests completed Failed Passed Skipped
4425 3 4422 106
View the top 1 failed test(s) by shortest run time
tests.test_cookie_helpers::test_parse_set_cookie_headers_uses_unquote_with_octal[complex="\\042quoted\\042 text with \\012 newline"-complex-"quoted" text with \n newline-"\\042quoted\\042 text with \\012 newline"]
Stack Traces | 0.007s run time
header = 'complex="\\042quoted\\042 text with \\012 newline"'
expected_name = 'complex', expected_value = '"quoted" text with \n newline'
expected_coded = '"\\042quoted\\042 text with \\012 newline"'

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[90m#x1B[39;49;00m
        (#x1B[33m"#x1B[39;49;00m#x1B[33mheader#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mexpected_name#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mexpected_value#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mexpected_coded#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
        [#x1B[90m#x1B[39;49;00m
            #x1B[90m# Test cookie values with octal escape sequences#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            (#x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33mname=#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012newline#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mname#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33mnewline#x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012newline#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            (#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33mtab=#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m011separated#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m011values#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mtab#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33m\t#x1B[39;49;00m#x1B[33mseparated#x1B[39;49;00m#x1B[33m\t#x1B[39;49;00m#x1B[33mvalues#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m011separated#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m011values#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            ),#x1B[90m#x1B[39;49;00m
            (#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33mmixed=#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mhello#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m040world#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m041#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mmixed#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mhello world!#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mhello#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m040world#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m041#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            ),#x1B[90m#x1B[39;49;00m
            (#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33mcomplex=#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m042quoted#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m042 text with #x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012 newline#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mcomplex#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mquoted#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m text with #x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33m newline#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m042quoted#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m042 text with #x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012 newline#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            ),#x1B[90m#x1B[39;49;00m
        ],#x1B[90m#x1B[39;49;00m
    )#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_parse_set_cookie_headers_uses_unquote_with_octal#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
        header: #x1B[96mstr#x1B[39;49;00m, expected_name: #x1B[96mstr#x1B[39;49;00m, expected_value: #x1B[96mstr#x1B[39;49;00m, expected_coded: #x1B[96mstr#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    ) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Test that parse_set_cookie_headers correctly unquotes values with octal sequences and preserves coded_value."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>       result = parse_set_cookie_headers([header])#x1B[90m#x1B[39;49;00m
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

expected_coded = '"\\042quoted\\042 text with \\012 newline"'
expected_name = 'complex'
expected_value = '"quoted" text with \n newline'
header     = 'complex="\\042quoted\\042 text with \\012 newline"'

#x1B[1m#x1B[31mtests/test_cookie_helpers.py#x1B[0m:1124: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31maiohttp/_cookie_helpers.py#x1B[0m:330: in parse_set_cookie_headers
    #x1B[0mcurrent_morsel.__setstate__(  #x1B[90m# type: ignore[attr-defined]#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        current_morsel = <Morsel: None=None>
        header     = 'complex="\\042quoted\\042 text with \\012 newline"'
        headers    = ['complex="\\042quoted\\042 text with \\012 newline"']
        i          = 47
        key        = 'complex'
        lower_key  = 'complex'
        match      = <re.Match object; span=(0, 47), match='complex="\\042quoted\\042 text with \\012 newline>
        morsel_seen = False
        n          = 47
        parsed_cookies = []
        value      = '"\\042quoted\\042 text with \\012 newline"'
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Morsel: None=None>
state = {'coded_value': '"\\042quoted\\042 text with \\012 newline"', 'key': 'complex', 'value': '"quoted" text with \n newline'}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m__setstate__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, state):#x1B[90m#x1B[39;49;00m
        key = state[#x1B[33m'#x1B[39;49;00m#x1B[33mkey#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        value = state[#x1B[33m'#x1B[39;49;00m#x1B[33mvalue#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        coded_value = state[#x1B[33m'#x1B[39;49;00m#x1B[33mcoded_value#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m _has_control_character(key, value, coded_value):#x1B[90m#x1B[39;49;00m
>           #x1B[94mraise#x1B[39;49;00m CookieError(#x1B[33m"#x1B[39;49;00m#x1B[33mControl characters are not allowed in cookies #x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                              #x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mkey#x1B[33m!r}#x1B[39;49;00m#x1B[33m #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mvalue#x1B[33m!r}#x1B[39;49;00m#x1B[33m #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mcoded_value#x1B[33m!r}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           http.cookies.CookieError: Control characters are not allowed in cookies 'complex' '"quoted" text with \n newline' '"\\042quoted\\042 text with \\012 newline"'#x1B[0m

coded_value = '"\\042quoted\\042 text with \\012 newline"'
key        = 'complex'
self       = <Morsel: None=None>
state      = {'coded_value': '"\\042quoted\\042 text with \\012 newline"', 'key': 'complex', 'value': '"quoted" text with \n newline'}
value      = '"quoted" text with \n newline'

#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.4.../x64-freethreaded/lib/python3.14t/http/cookies.py#x1B[0m:379: CookieError
View the full list of 2 ❄️ flaky test(s)
tests.test_cookie_helpers::test_parse_set_cookie_headers_uses_unquote_with_octal[name="\\012newline\\012"-name-\nnewline\n-"\\012newline\\012"]

Flake rate in main: 9.09% (Passed 10 times, Failed 1 times)

Stack Traces | 0.006s run time
header = 'name="\\012newline\\012"', expected_name = 'name'
expected_value = '\nnewline\n', expected_coded = '"\\012newline\\012"'

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[90m#x1B[39;49;00m
        (#x1B[33m"#x1B[39;49;00m#x1B[33mheader#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mexpected_name#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mexpected_value#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mexpected_coded#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
        [#x1B[90m#x1B[39;49;00m
            #x1B[90m# Test cookie values with octal escape sequences#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            (#x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33mname=#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012newline#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mname#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33mnewline#x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012newline#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            (#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33mtab=#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m011separated#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m011values#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mtab#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33m\t#x1B[39;49;00m#x1B[33mseparated#x1B[39;49;00m#x1B[33m\t#x1B[39;49;00m#x1B[33mvalues#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m011separated#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m011values#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            ),#x1B[90m#x1B[39;49;00m
            (#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33mmixed=#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mhello#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m040world#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m041#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mmixed#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mhello world!#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mhello#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m040world#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m041#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            ),#x1B[90m#x1B[39;49;00m
            (#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33mcomplex=#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m042quoted#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m042 text with #x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012 newline#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mcomplex#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mquoted#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m text with #x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33m newline#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m042quoted#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m042 text with #x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012 newline#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            ),#x1B[90m#x1B[39;49;00m
        ],#x1B[90m#x1B[39;49;00m
    )#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_parse_set_cookie_headers_uses_unquote_with_octal#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
        header: #x1B[96mstr#x1B[39;49;00m, expected_name: #x1B[96mstr#x1B[39;49;00m, expected_value: #x1B[96mstr#x1B[39;49;00m, expected_coded: #x1B[96mstr#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    ) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Test that parse_set_cookie_headers correctly unquotes values with octal sequences and preserves coded_value."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>       result = parse_set_cookie_headers([header])#x1B[90m#x1B[39;49;00m
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

expected_coded = '"\\012newline\\012"'
expected_name = 'name'
expected_value = '\nnewline\n'
header     = 'name="\\012newline\\012"'

#x1B[1m#x1B[31mtests/test_cookie_helpers.py#x1B[0m:1124: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31maiohttp/_cookie_helpers.py#x1B[0m:330: in parse_set_cookie_headers
    #x1B[0mcurrent_morsel.__setstate__(  #x1B[90m# type: ignore[attr-defined]#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        current_morsel = <Morsel: None=None>
        header     = 'name="\\012newline\\012"'
        headers    = ['name="\\012newline\\012"']
        i          = 22
        key        = 'name'
        lower_key  = 'name'
        match      = <re.Match object; span=(0, 22), match='name="\\012newline\\012"'>
        morsel_seen = False
        n          = 22
        parsed_cookies = []
        value      = '"\\012newline\\012"'
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Morsel: None=None>
state = {'coded_value': '"\\012newline\\012"', 'key': 'name', 'value': '\nnewline\n'}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m__setstate__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, state):#x1B[90m#x1B[39;49;00m
        key = state[#x1B[33m'#x1B[39;49;00m#x1B[33mkey#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        value = state[#x1B[33m'#x1B[39;49;00m#x1B[33mvalue#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        coded_value = state[#x1B[33m'#x1B[39;49;00m#x1B[33mcoded_value#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m _has_control_character(key, value, coded_value):#x1B[90m#x1B[39;49;00m
>           #x1B[94mraise#x1B[39;49;00m CookieError(#x1B[33m"#x1B[39;49;00m#x1B[33mControl characters are not allowed in cookies #x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                              #x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mkey#x1B[33m!r}#x1B[39;49;00m#x1B[33m #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mvalue#x1B[33m!r}#x1B[39;49;00m#x1B[33m #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mcoded_value#x1B[33m!r}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           http.cookies.CookieError: Control characters are not allowed in cookies 'name' '\nnewline\n' '"\\012newline\\012"'#x1B[0m

coded_value = '"\\012newline\\012"'
key        = 'name'
self       = <Morsel: None=None>
state      = {'coded_value': '"\\012newline\\012"', 'key': 'name', 'value': '\nnewline\n'}
value      = '\nnewline\n'

#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.4.../x64-freethreaded/lib/python3.14t/http/cookies.py#x1B[0m:379: CookieError
tests.test_cookie_helpers::test_parse_set_cookie_headers_uses_unquote_with_octal[tab="\\011separated\\011values"-tab-\tseparated\tvalues-"\\011separated\\011values"]

Flake rate in main: 4.76% (Passed 20 times, Failed 1 times)

Stack Traces | 0.007s run time
header = 'tab="\\011separated\\011values"', expected_name = 'tab'
expected_value = '\tseparated\tvalues'
expected_coded = '"\\011separated\\011values"'

    #x1B[0m#x1B[37m@pytest#x1B[39;49;00m.mark.parametrize(#x1B[90m#x1B[39;49;00m
        (#x1B[33m"#x1B[39;49;00m#x1B[33mheader#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mexpected_name#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mexpected_value#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mexpected_coded#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
        [#x1B[90m#x1B[39;49;00m
            #x1B[90m# Test cookie values with octal escape sequences#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            (#x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33mname=#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012newline#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mname#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33mnewline#x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012newline#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m),#x1B[90m#x1B[39;49;00m
            (#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33mtab=#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m011separated#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m011values#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mtab#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33m\t#x1B[39;49;00m#x1B[33mseparated#x1B[39;49;00m#x1B[33m\t#x1B[39;49;00m#x1B[33mvalues#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m011separated#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m011values#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            ),#x1B[90m#x1B[39;49;00m
            (#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33mmixed=#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mhello#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m040world#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m041#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mmixed#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mhello world!#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mhello#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m040world#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m041#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            ),#x1B[90m#x1B[39;49;00m
            (#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33mcomplex=#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m042quoted#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m042 text with #x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012 newline#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m"#x1B[39;49;00m#x1B[33mcomplex#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mquoted#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m text with #x1B[39;49;00m#x1B[33m\n#x1B[39;49;00m#x1B[33m newline#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
                #x1B[33mr#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m042quoted#x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m042 text with #x1B[39;49;00m#x1B[33m\#x1B[39;49;00m#x1B[33m012 newline#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
            ),#x1B[90m#x1B[39;49;00m
        ],#x1B[90m#x1B[39;49;00m
    )#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_parse_set_cookie_headers_uses_unquote_with_octal#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
        header: #x1B[96mstr#x1B[39;49;00m, expected_name: #x1B[96mstr#x1B[39;49;00m, expected_value: #x1B[96mstr#x1B[39;49;00m, expected_coded: #x1B[96mstr#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    ) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Test that parse_set_cookie_headers correctly unquotes values with octal sequences and preserves coded_value."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>       result = parse_set_cookie_headers([header])#x1B[90m#x1B[39;49;00m
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m

expected_coded = '"\\011separated\\011values"'
expected_name = 'tab'
expected_value = '\tseparated\tvalues'
header     = 'tab="\\011separated\\011values"'

#x1B[1m#x1B[31mtests/test_cookie_helpers.py#x1B[0m:1124: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31maiohttp/_cookie_helpers.py#x1B[0m:330: in parse_set_cookie_headers
    #x1B[0mcurrent_morsel.__setstate__(  #x1B[90m# type: ignore[attr-defined]#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        current_morsel = <Morsel: None=None>
        header     = 'tab="\\011separated\\011values"'
        headers    = ['tab="\\011separated\\011values"']
        i          = 29
        key        = 'tab'
        lower_key  = 'tab'
        match      = <re.Match object; span=(0, 29), match='tab="\\011separated\\011values"'>
        morsel_seen = False
        n          = 29
        parsed_cookies = []
        value      = '"\\011separated\\011values"'
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Morsel: None=None>
state = {'coded_value': '"\\011separated\\011values"', 'key': 'tab', 'value': '\tseparated\tvalues'}

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m__setstate__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, state):#x1B[90m#x1B[39;49;00m
        key = state[#x1B[33m'#x1B[39;49;00m#x1B[33mkey#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        value = state[#x1B[33m'#x1B[39;49;00m#x1B[33mvalue#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        coded_value = state[#x1B[33m'#x1B[39;49;00m#x1B[33mcoded_value#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m _has_control_character(key, value, coded_value):#x1B[90m#x1B[39;49;00m
>           #x1B[94mraise#x1B[39;49;00m CookieError(#x1B[33m"#x1B[39;49;00m#x1B[33mControl characters are not allowed in cookies #x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                              #x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mkey#x1B[33m!r}#x1B[39;49;00m#x1B[33m #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mvalue#x1B[33m!r}#x1B[39;49;00m#x1B[33m #x1B[39;49;00m#x1B[33m{#x1B[39;49;00mcoded_value#x1B[33m!r}#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           http.cookies.CookieError: Control characters are not allowed in cookies 'tab' '\tseparated\tvalues' '"\\011separated\\011values"'#x1B[0m

coded_value = '"\\011separated\\011values"'
key        = 'tab'
self       = <Morsel: None=None>
state      = {'coded_value': '"\\011separated\\011values"', 'key': 'tab', 'value': '\tseparated\tvalues'}
value      = '\tseparated\tvalues'

#x1B[1m#x1B[.../hostedtoolcache/Python/3.14.4.../x64-freethreaded/lib/python3.14t/http/cookies.py#x1B[0m:379: CookieError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 8, 2026

Merging this PR will not alter performance

✅ 61 untouched benchmarks
⏩ 4 skipped benchmarks1


Comparing mib1185:add-progress-callback-to-payload-writer (a27ac60) with master (fc67cfd)

Open in CodSpeed

Footnotes

  1. 4 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

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

Labels

bot:chronographer:provided There is a change note present in this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant