Skip to content

Add optional requests session for connection reuse - #1493

Merged
jshcodes merged 1 commit into
CrowdStrike:devfrom
Davack:feature/optional-request-session
Aug 5, 2026
Merged

Add optional requests session for connection reuse#1493
jshcodes merged 1 commit into
CrowdStrike:devfrom
Davack:feature/optional-request-session

Conversation

@Davack

@Davack Davack commented Aug 3, 2026

Copy link
Copy Markdown

feat: Add optional requests session for connection reuse

Adds an optional session: Optional[requests.Session] = None keyword argument, accepted by OAuth2, APIHarnessV2, the legacy APIHarness, and every Service Class, allowing callers to reuse a single HTTP connection across login, every API call, token renewal, and logout — instead of opening a new connection for every request.

  • Added session keyword to OAuth2, UberInterface/APIHarnessV2, ServiceClass/BaseServiceClass, and the legacy APIHarness

  • Every request path funnels through a single chokepoint (perform_request() in _util/_functions.py), which now dispatches to session.request when a session is supplied and to the existing module-level requests.request otherwise — so default (no session) behavior is byte-for-byte unchanged

  • session is propagated through the same existing pattern already used for proxy/timeout/user_agent (InterfaceConfigurationFalconInterfaceUberInterface/OAuth2ServiceClass/BaseServiceClass), including ServiceClass.override() custom routes

  • FalconPy never calls .close() on a caller-provided session; the caller retains full ownership of its lifecycle (e.g. via a with requests.Session() as session: block)

  • No global/module-level session is introduced — session is scoped to the auth object / harness instance the caller constructs

  • Added tests/test_session_support.py (18 tests) and tests/test_session_connection_reuse.py (2 tests), both fully mocked / local-only — no live credentials or internet access required

  • Added util/session_benchmark.py, a local loopback benchmark demonstrating the connection-reuse mechanism without requiring API credentials

  • Added samples/authentication/session_reuse.py and a corresponding section in samples/authentication/README.md

  • Enhancement

  • Updated unit tests

  • Documentation

  • Code sample

Unit test coverage

$ coverage run --rcfile=util/coverage.config -m pytest -q tests/test_session_support.py tests/test_session_connection_reuse.py
....................                                                     [100%]
20 passed in 1.25s

Coverage of every file touched by this change (new session tests only, in isolation):
Name                                                 Stmts   Miss  Cover
------------------------------------------------------------------------
src/falconpy/_api_request/_request.py                  116     15    87%
src/falconpy/_api_request/_request_connection.py        10      0   100%
src/falconpy/_auth_object/_falcon_interface.py         286     93    67%
src/falconpy/_auth_object/_interface_config.py          49      5    90%
src/falconpy/_auth_object/_uber_interface.py            52     22    58%
src/falconpy/_service_class/_base_service_class.py     123     32    74%
src/falconpy/_service_class/_service_class.py          117     37    68%
src/falconpy/_util/_functions.py                       461    217    53%
src/falconpy/_util/_service.py                           3      0   100%
src/falconpy/_util/_uber.py                             53     29    45%
src/falconpy/api_complete/_advanced.py                  62     21    66%
src/falconpy/api_complete/_legacy.py                   204     70    66%
src/falconpy/oauth2.py                                  33      8    76%
------------------------------------------------------------------------
TOTAL                                                 1569    549    65%

(These files carry substantial pre-existing coverage from the rest of the suite; the numbers above reflect only the two new session-focused test files run in isolation, not full project coverage. Every new branch introduced by this change — session vs. no-session in perform_request, login/logout with/without session, APIHarnessV2, legacy APIHarness, ServiceClass.override() — is exercised.)

I do not currently have CrowdStrike API credentials available to run the full live-API test suite (util/run-tests.sh) from this environment. I ran it against placeholder credentials purely to validate that this change introduces no collection/import errors and no new failures: all 577 non-manual tests still collect cleanly, and I diff-checked the full failure set against an unmodified main checkout under identical placeholder-credential conditions — byte-for-byte identical failures (confirmed on the 8 files most relevant to this change: test_authentications.py, test_authorization.py, test_uber.py, test_uber_api_complete.py, test_service_class.py, test_hosts.py, test_timeout.py, test_result_object.py — 82/82 identical failures, all attributable to the absence of real API responses, not this change). Happy to have a maintainer re-run against live credentials as an extra check.

Bandit analysis

$ bandit -r src
Test results:
	No issues identified.

Code scanned:
	Total lines of code: 135632
	Total lines skipped (#nosec): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0

flake8, pylint (10.00/10), and pydocstyle all pass with zero findings against src/falconpy.

Added features and functionality

  • Added session keyword argument to OAuth2, APIHarnessV2 (via UberInterface), the legacy APIHarness, and every Service Class (via ServiceClass/BaseServiceClass) — reuses a requests.Session across login, every API call, token renewal, and logout.
    • _util/_functions.py
    • _api_request/_request.py
    • _api_request/_request_connection.py
    • _auth_object/_interface_config.py
    • _auth_object/_falcon_interface.py
    • _auth_object/_uber_interface.py
    • oauth2.py
    • _util/_service.py
    • _util/_uber.py
    • _service_class/_base_service_class.py
    • api_complete/_legacy.py

    Unit testing expanded to complete code coverage of every new branch.

    • tests/test_session_support.py
    • tests/test_session_connection_reuse.py

Issues resolved

  • No existing issue filed. This is a proactively contributed enhancement, not a bug fix.

Other

  • Benchmark: I measured ~948 ms/request against the live CrowdStrike API without a session, versus ~273 ms/request with a persistent session — comparable to the ~269 ms/request already achieved by the existing NG-SIEM pooled-session client (_ngsiem/_session_manager.py), which was the inspiration for exposing this same pattern more broadly. Those numbers come from earlier testing against the live API and aren't reproduced in this PR's CI-equivalent run (no credentials available there). To back that up with something independently reproducible by any reviewer, I added util/session_benchmark.py, a local loopback benchmark requiring no credentials or internet access, which produced a real, reproducible 2.18x speedup from connection reuse alone (isolating the TCP-handshake-avoidance mechanism, without TLS or real network latency, so it understates the live-API improvement):
    Without session (requests.request per call):  mean 0.72 ms
    With session (requests.Session reused):        mean 0.33 ms
    Mean speedup: 2.18x
    
  • Backward compatibility: no breaking changes. Default behavior (no session=) is unchanged — verified explicitly in TestSessionDefaultBehaviorUnchanged, which asserts the module-level requests.request is still used and requests.Session is never instantiated internally when the keyword is omitted.
  • Design note: the alternative of always using an internally-managed Session was considered and rejected — it would silently change behavior for every existing caller (connection pooling, cookie persistence, trust_env interactions). Making this strictly opt-in with an unchanged default is the more conservative and appropriate choice for a widely-used SDK.
  • Usage example:
    with requests.Session() as session:
        falcon = APIHarnessV2(client_id=client_id, client_secret=client_secret, session=session)
        response = falcon.command("query_devices_by_filter")
    # session is closed here by the caller's `with` block, not by FalconPy
  • Author's note: I was a CrowdStrike engineer until last month, and I'm contributing this from an external position now. This idea came directly from watching high-volume automation workloads pay a repeated TCP/TLS handshake cost on every single call — the NG-SIEM ingestion path already solves this internally with a pooled SessionManager, and this PR makes the same capability available generically across the SDK's main request path, opt-in and fully backward compatible.

Add an optional session (requests.Session) keyword argument, accepted by
OAuth2, APIHarnessV2, the legacy APIHarness, and every Service Class,
allowing callers to reuse a persistent HTTP connection across login, every
API call, token renewal, and logout. Default (no session) behavior is
unchanged, FalconPy never closes a caller-provided session, and no global
session is introduced.

@jshcodes jshcodes left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution!! 🙇

@jshcodes
jshcodes merged commit 304eaa4 into CrowdStrike:dev Aug 5, 2026
5 of 6 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.

2 participants