Skip to content

Conversation

@gsoldatov
Copy link

@gsoldatov gsoldatov commented Nov 19, 2025

What do these changes do?

  • added RequestKey & ResponseKey classes, which enable static typing checks in corresponding classes with dict-like storages, similarly to AppKey;
  • moved common logic of key classes into a super-class;
  • added tests for new classes & updated docs;
  • fixed a few flake8 errors.

Are there changes in behavior for the user?

Users may now use RequestKey & ResponseKey in request & response objects, in addition to string keys.

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

Related issue number

This issue should close #11762.

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.

@codspeed-hq
Copy link

codspeed-hq bot commented Nov 19, 2025

CodSpeed Performance Report

Merging #11766 will not alter performance

Comparing gsoldatov:request_response_storage_typing (0b9df9d) with master (72fadb8)

Summary

✅ 59 untouched

@psf-chronographer psf-chronographer bot added the bot:chronographer:provided There is a change note present in this PR label Nov 19, 2025
@gsoldatov
Copy link
Author

I believe this one's ready for review, or, at the very least, tests are passed locally.

Also, I didn't add warnings for string keys usage, since it'd be too spammy to raise them on a per-request basis.

@gsoldatov gsoldatov marked this pull request as ready for review November 20, 2025 19:18
@Dreamsorcerer Dreamsorcerer added the backport-3.14 Trigger automatic backporting to the 3.14 release branch by Patchback robot label Nov 21, 2025
@codecov
Copy link

codecov bot commented Nov 21, 2025

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
4265 1 4264 48
View the full list of 1 ❄️ flaky test(s)
tests.test_run_app.TestShutdown::test_shutdown_close_websockets

Flake rate in main: 5.00% (Passed 19 times, Failed 1 times)

Stack Traces | 1.02s run time
self = <test_run_app.TestShutdown object at 0x1058d8b90>
unused_port_socket = <socket.socket [closed] fd=-1, family=2, type=1, proto=0>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest_shutdown_close_websockets#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, unused_port_socket: socket.socket) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        sock = unused_port_socket#x1B[90m#x1B[39;49;00m
        port = sock.getsockname()[#x1B[94m1#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        WS = web.AppKey(#x1B[33m"#x1B[39;49;00m#x1B[33mws#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[96mset#x1B[39;49;00m[web.WebSocketResponse])#x1B[90m#x1B[39;49;00m
        client_finished = server_finished = #x1B[94mFalse#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        t = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94masync#x1B[39;49;00m #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mws_handler#x1B[39;49;00m(request: web.Request) -> web.WebSocketResponse:#x1B[90m#x1B[39;49;00m
            ws = web.WebSocketResponse()#x1B[90m#x1B[39;49;00m
            #x1B[94mawait#x1B[39;49;00m ws.prepare(request)#x1B[90m#x1B[39;49;00m
            request.app[WS].add(ws)#x1B[90m#x1B[39;49;00m
            #x1B[94masync#x1B[39;49;00m #x1B[94mfor#x1B[39;49;00m msg #x1B[95min#x1B[39;49;00m ws:#x1B[90m#x1B[39;49;00m
                #x1B[94mpass#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mnonlocal#x1B[39;49;00m server_finished#x1B[90m#x1B[39;49;00m
            server_finished = #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m ws#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94masync#x1B[39;49;00m #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mclose_websockets#x1B[39;49;00m(app: web.Application) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mfor#x1B[39;49;00m ws #x1B[95min#x1B[39;49;00m app[WS]:#x1B[90m#x1B[39;49;00m
                #x1B[94mawait#x1B[39;49;00m ws.close(code=WSCloseCode.GOING_AWAY)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94masync#x1B[39;49;00m #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mtest#x1B[39;49;00m() -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mawait#x1B[39;49;00m asyncio.sleep(#x1B[94m1#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            #x1B[94masync#x1B[39;49;00m #x1B[94mwith#x1B[39;49;00m ClientSession() #x1B[94mas#x1B[39;49;00m sess:#x1B[90m#x1B[39;49;00m
                #x1B[94masync#x1B[39;49;00m #x1B[94mwith#x1B[39;49;00m sess.ws_connect(#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mhttp://127.0.0.1:#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mport#x1B[33m}#x1B[39;49;00m#x1B[33m/ws#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m) #x1B[94mas#x1B[39;49;00m ws:#x1B[90m#x1B[39;49;00m
                    #x1B[94masync#x1B[39;49;00m #x1B[94mwith#x1B[39;49;00m sess.get(#x1B[33mf#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m#x1B[33mhttp://127.0.0.1:#x1B[39;49;00m#x1B[33m{#x1B[39;49;00mport#x1B[33m}#x1B[39;49;00m#x1B[33m/stop#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
                        #x1B[94mpass#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
                    #x1B[94masync#x1B[39;49;00m #x1B[94mfor#x1B[39;49;00m msg #x1B[95min#x1B[39;49;00m ws:#x1B[90m#x1B[39;49;00m
                        #x1B[94mpass#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
                    #x1B[94mnonlocal#x1B[39;49;00m client_finished#x1B[90m#x1B[39;49;00m
                    client_finished = #x1B[94mTrue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94masync#x1B[39;49;00m #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mrun_test#x1B[39;49;00m(app: web.Application) -> AsyncIterator[#x1B[94mNone#x1B[39;49;00m]:#x1B[90m#x1B[39;49;00m
            #x1B[94mnonlocal#x1B[39;49;00m t#x1B[90m#x1B[39;49;00m
            t = asyncio.create_task(test())#x1B[90m#x1B[39;49;00m
            #x1B[94myield#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            t.cancel()#x1B[90m#x1B[39;49;00m
            #x1B[94mwith#x1B[39;49;00m contextlib.suppress(asyncio.CancelledError):#x1B[90m#x1B[39;49;00m
                #x1B[94mawait#x1B[39;49;00m t#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        app = web.Application()#x1B[90m#x1B[39;49;00m
        app[WS] = #x1B[96mset#x1B[39;49;00m()#x1B[90m#x1B[39;49;00m
        app.on_shutdown.append(close_websockets)#x1B[90m#x1B[39;49;00m
        app.cleanup_ctx.append(run_test)#x1B[90m#x1B[39;49;00m
        app.router.add_get(#x1B[33m"#x1B[39;49;00m#x1B[33m/ws#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, ws_handler)#x1B[90m#x1B[39;49;00m
        app.router.add_get(#x1B[33m"#x1B[39;49;00m#x1B[33m/stop#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[96mself#x1B[39;49;00m.stop)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        start = time.time()#x1B[90m#x1B[39;49;00m
        web.run_app(app, sock=sock, shutdown_timeout=#x1B[94m10#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
        #x1B[94massert#x1B[39;49;00m time.time() - start < #x1B[94m5#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>       #x1B[94massert#x1B[39;49;00m client_finished#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE       assert False#x1B[0m

WS         = <AppKey(__channelexec__.ws, type=set[aiohttp.web_ws.WebSocketResponse])>
app        = <Application 0x105a67690>
client_finished = False
close_websockets = <function TestShutdown.test_shutdown_close_websockets.<locals>.close_websockets at 0x1075b9e40>
port       = 50711
run_test   = <function TestShutdown.test_shutdown_close_websockets.<locals>.run_test at 0x1075b9d00>
self       = <test_run_app.TestShutdown object at 0x1058d8b90>
server_finished = True
sock       = <socket.socket [closed] fd=-1, family=2, type=1, proto=0>
start      = 1764021439.856759
t          = <Task cancelled name='Task-4062' coro=<TestShutdown.test_shutdown_close_websockets.<locals>.test() done, defined at .../aiohttp/tests/test_run_app.py:1271>>
test       = <function TestShutdown.test_shutdown_close_websockets.<locals>.test at 0x1075b9b20>
unused_port_socket = <socket.socket [closed] fd=-1, family=2, type=1, proto=0>
ws_handler = <function TestShutdown.test_shutdown_close_websockets.<locals>.ws_handler at 0x1071ef2e0>

#x1B[1m#x1B[31mtests/test_run_app.py#x1B[0m:1301: AssertionError

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

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

Labels

backport-3.14 Trigger automatic backporting to the 3.14 release branch by Patchback robot 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.

Typing support for Request & Response storages

2 participants