Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4559587
feat: OIDC device flow
glasstiger Jun 15, 2026
c7c269f
fix: pandas 3 string dtype in test_parquet_roundtrip
glasstiger Jun 15, 2026
e394fbd
ci: keep 32-bit wheel tests on the pandas 2 / numpy 1 path
glasstiger Jun 15, 2026
ae92178
test: silence mock server tracebacks on Windows client disconnect
glasstiger Jun 15, 2026
88308d5
ci: skip readonly AZP_ENHANCED agent var in Windows wheel build
glasstiger Jun 17, 2026
d5df186
fix: harden OIDC device-flow auth; drop on-disk FileCache backend
glasstiger Jun 17, 2026
5ef892b
ci: build questdb master's -SNAPSHOT java client via local-client pro…
glasstiger Jun 18, 2026
ae8baa7
ci: build/run questdb master on JDK 25
glasstiger Jun 18, 2026
a4b41c3
ci: invoke Maven directly to build questdb master on JDK 25
glasstiger Jun 18, 2026
61abd4f
ci: pass JDK 25 module access flags to questdb master server
glasstiger Jun 18, 2026
23fb823
do not follow redirects
glasstiger Jun 18, 2026
c13cf69
fix: require IdP pin for plaintext /settings
glasstiger Jun 18, 2026
bb9147c
fix: clamp device-flow poll timing fields
glasstiger Jun 18, 2026
0edf9dc
fix: map malformed inputs to typed OidcError
glasstiger Jun 18, 2026
35c3fbb
fix: sanitize device-flow terminal output
glasstiger Jun 18, 2026
f6321c3
docs: correct questdb.auth changelog and API reference
glasstiger Jun 18, 2026
e043561
fix: make TokenSet immutable and keep tokens out of repr
glasstiger Jun 18, 2026
4e62938
style: sort questdb.auth __all__ to satisfy Ruff RUF022
glasstiger Jun 18, 2026
4a67cc5
docs: make review-pr level-0/1 Step 2.5 rules consistent
glasstiger Jun 18, 2026
124d4c2
docs: exclude Agent 10 from review-pr Step 2.5 input contract
glasstiger Jun 18, 2026
a062a0a
fix: ignore user-writable /settings preferences
glasstiger Jun 18, 2026
fe34ffa
fix: reject conf metachars in QuestDB host
glasstiger Jun 18, 2026
ae4c5ba
fix: harden questdb.auth untrusted-input handling
glasstiger Jun 19, 2026
afbd808
fix: bound IdP timeout; broaden auth edge tests
glasstiger Jun 19, 2026
63edfce
fix: address 3 minor questdb.auth review nits
glasstiger Jun 19, 2026
c869e31
fix: address 4 follow-up auth review findings
glasstiger Jun 19, 2026
bcfebd9
fix: address 4 moderate questdb.auth review findings
glasstiger Jun 19, 2026
8f61ff2
fix: make deeply-nested-JSON auth test robust on Python 3.14
glasstiger Jun 19, 2026
a9c58c8
fix: make questdb.auth clear() reliable across shared-cache instances
glasstiger Jun 19, 2026
7ed6f05
fix: tolerate non-string acl.oidc.* from QuestDB /settings and IdP di…
glasstiger Jun 19, 2026
63a3f7a
fix: keep device-flow poll alive through transient IdP errors
glasstiger Jun 21, 2026
954f8a8
fix: map malformed IdP discovery / /exec payloads to OidcError
glasstiger Jun 21, 2026
6e2970b
fix: sanitize untrusted device fields on the Jupyter prompt path
glasstiger Jun 21, 2026
c31eb0f
fix: bind discovery_url pin to the IdP origin; close auth test gaps
glasstiger Jun 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
377 changes: 377 additions & 0 deletions .claude/skills/review-pr/SKILL.md

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,55 @@ Changelog

=========

Unreleased
----------

Features
~~~~~~~~

OIDC Authentication (:mod:`questdb.auth`)
************************************************

New :mod:`questdb.auth` module to sign in interactively to OIDC-secured
QuestDB Enterprise from Python — including from **remote** kernels
(JupyterHub, SageMaker, Colab, VS Code-remote) that have no local browser.

It runs the OAuth 2.0 Device Authorization Grant (RFC 8628) client-side: you
authorize in any browser (laptop or phone), and the token is presented to
QuestDB over the auth paths it already supports (HTTP ``Bearer`` / PG-wire
``_sso``). No server change is required.

.. code-block:: python

from questdb.auth import OidcDeviceAuth, connect

# Just the token (use it with PG-wire, HTTP, or any client):
auth = OidcDeviceAuth.from_questdb("https://questdb.example.com:9000")
token = auth.token()

# Or the integrated session (query to a DataFrame, feed adapters):
qdb = connect("https://questdb.example.com:9000")
df = qdb.sql("SELECT * FROM trades LIMIT 10")

Highlights:

* Auto-discovery of OIDC config from the QuestDB ``/settings`` endpoint, with a
fallback to the IdP ``.well-known`` document.
* In-process token cache with silent refresh (tokens are never written to
disk).
* Adapters for pandas (REST ``/exec``), SQLAlchemy, psycopg and the ingestion
``Sender``.
* ``token()`` / ``headers()`` require no dependencies beyond the standard
library; ``pandas`` / ``sqlalchemy`` / ``psycopg`` / ``qrcode`` / ``IPython``
are imported lazily.

See the :ref:`OIDC authentication guide <oidc_auth>` for details.

Python Version Support
~~~~~~~~~~~~~~~~~~~~~~~~

* Raised the minimum supported Python version to 3.10.

4.1.0 (2025-11-28)
------------------

Expand Down
4 changes: 2 additions & 2 deletions ci/cibuildwheel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ stages:
cmd /c "call `"$vsPath`" && set > env_vars.txt"

Get-Content env_vars.txt | ForEach-Object {
if ($_ -match "^([^=]+?)=(.*)$" -and $matches[1] -notmatch '^(SYSTEM|AGENT|BUILD|RELEASE|VSTS|TASK|USE_|FAIL_|MSDEPLOY|AZP_75787|AZP_AGENT|AZP_ENABLE|AZURE_HTTP|COPYFILESOVERSSHV0|ENABLE_ISSUE_SOURCE_VALIDATION|MODIFY_NUMBER_OF_RETRIES_IN_ROBOCOPY|MSBUILDHELPERS_ENABLE_TELEMETRY|RETIRE_AZURERM_POWERSHELL_MODULE|ROSETTA2_WARNING|AZP_PS_ENABLE)') {
if ($_ -match "^([^=]+?)=(.*)$" -and $matches[1] -notmatch '^(SYSTEM|AGENT|BUILD|RELEASE|VSTS|TASK|USE_|FAIL_|MSDEPLOY|AZP_75787|AZP_AGENT|AZP_ENABLE|AZP_ENHANCED|AZURE_HTTP|COPYFILESOVERSSHV0|ENABLE_ISSUE_SOURCE_VALIDATION|MODIFY_NUMBER_OF_RETRIES_IN_ROBOCOPY|MSBUILDHELPERS_ENABLE_TELEMETRY|RETIRE_AZURERM_POWERSHELL_MODULE|ROSETTA2_WARNING|AZP_PS_ENABLE)') {
[System.Environment]::SetEnvironmentVariable($matches[1], $matches[2], "Process")
Write-Host "##vso[task.setvariable variable=$($matches[1])]$($matches[2])"
}
Expand Down Expand Up @@ -137,7 +137,7 @@ stages:
cmd /c "call `"$vsPath`" && set > env_vars.txt"

Get-Content env_vars.txt | ForEach-Object {
if ($_ -match "^([^=]+?)=(.*)$" -and $matches[1] -notmatch '^(SYSTEM|AGENT|BUILD|RELEASE|VSTS|TASK|USE_|FAIL_|MSDEPLOY|AZP_75787|AZP_AGENT|AZP_ENABLE|AZURE_HTTP|COPYFILESOVERSSHV0|ENABLE_ISSUE_SOURCE_VALIDATION|MODIFY_NUMBER_OF_RETRIES_IN_ROBOCOPY|MSBUILDHELPERS_ENABLE_TELEMETRY|RETIRE_AZURERM_POWERSHELL_MODULE|ROSETTA2_WARNING|AZP_PS_ENABLE)') {
if ($_ -match "^([^=]+?)=(.*)$" -and $matches[1] -notmatch '^(SYSTEM|AGENT|BUILD|RELEASE|VSTS|TASK|USE_|FAIL_|MSDEPLOY|AZP_75787|AZP_AGENT|AZP_ENABLE|AZP_ENHANCED|AZURE_HTTP|COPYFILESOVERSSHV0|ENABLE_ISSUE_SOURCE_VALIDATION|MODIFY_NUMBER_OF_RETRIES_IN_ROBOCOPY|MSBUILDHELPERS_ENABLE_TELEMETRY|RETIRE_AZURERM_POWERSHELL_MODULE|ROSETTA2_WARNING|AZP_PS_ENABLE)') {
[System.Environment]::SetEnvironmentVariable($matches[1], $matches[2], "Process")
Write-Host "##vso[task.setvariable variable=$($matches[1])]$($matches[2])"
}
Expand Down
12 changes: 9 additions & 3 deletions ci/pip_install_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,18 @@ def install_pandas3_and_numpy():
def should_use_pandas3(py_version=None):
if py_version is None:
py_version = sys.version_info[:2]
return py_version >= (3, 11)
# Pandas 3 ships no 32-bit wheels, so only take the pandas 3 / numpy 2
# path on 64-bit interpreters. On 32-bit (e.g. win32) the pandas 3 install
# would be silently skipped, fastparquet would then drag in a numpy-1-built
# pandas 2.0.3 alongside numpy 2, and importing pandas would crash.
is_64bits = sys.maxsize > 2 ** 32
return is_64bits and py_version >= (3, 11)


def install_default_pandas_and_numpy():
# Pandas 3 currently requires Python 3.11+, so keep 3.10 wheel tests on
# the pandas 2 / numpy 1.x-compatible path unless explicitly overridden.
# Pandas 3 requires Python 3.11+ and ships only 64-bit wheels, so keep
# 3.10 and all 32-bit wheel tests on the pandas 2 / numpy 1.x-compatible
# path unless explicitly overridden.
if should_use_pandas3():
install_pandas3_and_numpy()
else:
Expand Down
36 changes: 30 additions & 6 deletions ci/run_tests_pipeline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,24 @@ stages:
git clone --depth 1 https://github.com/questdb/questdb.git
displayName: git clone questdb master
condition: eq(variables.vsQuestDbMaster, true)
- task: Maven@3
# Decide whether to build java-questdb-client from the bundled
# submodule (-P local-client, for a -SNAPSHOT client not on Maven
# Central) or resolve it from Maven Central. Sets $(CLIENT_PROFILE).
- template: templates/detect-local-client.yml
parameters:
qdbRepoPath: questdb
condition: eq(variables.vsQuestDbMaster, true)
# The Maven@3 task crashes parsing JDK 25 ("Cannot read properties of
# null (reading 'major')") since its JDK support tops out at 21, so
# invoke Maven directly on the preinstalled JDK 25 instead. Mirrors the
# task's defaults: POM questdb/pom.xml, goal "package".
- bash: |
set -eu
export JAVA_HOME="$(JAVA_HOME_25_X64)"
export PATH="$JAVA_HOME/bin:$PATH"
java -version
mvn -B -f questdb/pom.xml package -DskipTests -Pbuild-web-console $(CLIENT_PROFILE)
displayName: "Compile QuestDB master"
inputs:
mavenPOMFile: "questdb/pom.xml"
jdkVersionOption: "1.17"
options: "-DskipTests -Pbuild-web-console"
condition: eq(variables.vsQuestDbMaster, true)
- script: python3 proj.py test 1
displayName: "Test vs released"
Expand All @@ -77,8 +89,20 @@ stages:
- script: python3 proj.py test 1
displayName: "Test vs master"
env:
JAVA_HOME: $(JAVA_HOME_17_X64)
JAVA_HOME: $(JAVA_HOME_25_X64)
QDB_REPO_PATH: "./questdb"
# QuestDB master runs as the io.questdb JPMS module and needs these
# JDK 25 access flags (mirrors questdb.sh). The test fixture launches
# questdb.jar directly rather than via questdb.sh, so feed them to the
# java launcher through JDK_JAVA_OPTIONS.
JDK_JAVA_OPTIONS: >-
--sun-misc-unsafe-memory-access=allow
--enable-native-access=io.questdb
--add-opens=java.base/java.lang=io.questdb
--add-opens=java.base/java.lang.reflect=io.questdb
--add-opens=java.base/java.nio=io.questdb
--add-opens=java.base/java.time.zone=io.questdb
--add-exports=java.base/jdk.internal.vm=io.questdb
condition: eq(variables.vsQuestDbMaster, true)
- job: TestsAgainstVariousNumpyVersion1x
pool:
Expand Down
36 changes: 36 additions & 0 deletions ci/templates/detect-local-client.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Adapted from questdb/questdb's ci/templates/detect-local-client.yml.
#
# Decide how a cloned QuestDB checkout resolves its java-questdb-client
# dependency: a -SNAPSHOT client version is not published to Maven Central, so
# build it from the bundled java-questdb-client submodule via the `local-client`
# profile; a released version is taken from Maven Central. Sets the
# CLIENT_PROFILE pipeline variable (``-P local-client`` or empty) for the
# following Maven build, and inits the submodule only when it is needed.
#
# Unlike the upstream template, QuestDB is cloned into a subdirectory here, so
# the repo path is a parameter; ``condition`` lets the caller gate this to the
# matrix leg that builds QuestDB master.
parameters:
- name: qdbRepoPath
type: string
default: questdb
- name: condition
type: string
default: succeeded()

steps:
- bash: |
set -eu
pom="${{ parameters.qdbRepoPath }}/core/pom.xml"
CLIENT_VERSION=$(sed -n 's/.*<questdb.client.version>\(.*\)<\/questdb.client.version>.*/\1/p' "$pom" | head -1)
echo "questdb.client.version=$CLIENT_VERSION"
if echo "$CLIENT_VERSION" | grep -q '\-SNAPSHOT$'; then
echo "SNAPSHOT client detected -> build it locally (local-client profile)"
git -C "${{ parameters.qdbRepoPath }}" submodule update --init java-questdb-client
echo "##vso[task.setvariable variable=CLIENT_PROFILE]-P local-client"
else
echo "Release client detected -> resolve from Maven Central"
echo "##vso[task.setvariable variable=CLIENT_PROFILE]"
fi
displayName: "Detect QuestDB local client profile"
condition: ${{ parameters.condition }}
63 changes: 63 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,66 @@ questdb.ingress
:members:
:undoc-members:
:show-inheritance:

questdb.auth
============

See the :ref:`oidc_auth` guide for an overview.

.. autofunction:: questdb.auth.connect

.. autoclass:: questdb.auth.QuestDB
:members:
:undoc-members:
:show-inheritance:

.. autoclass:: questdb.auth.OidcDeviceAuth
:members:
:undoc-members:
:show-inheritance:

.. autoclass:: questdb.auth.OidcConfig
:members:
:undoc-members:
:show-inheritance:

.. autoclass:: questdb.auth.TokenCache
:members:
:undoc-members:
:show-inheritance:

.. autoclass:: questdb.auth.TokenSet
:members:
:undoc-members:
:show-inheritance:

.. autoclass:: questdb.auth.MemoryCache
:members:
:undoc-members:
:show-inheritance:

.. autoclass:: questdb.auth.NullCache
:members:
:undoc-members:
:show-inheritance:

.. autoexception:: questdb.auth.OidcError
:show-inheritance:

.. autoexception:: questdb.auth.OidcConfigError
:show-inheritance:

.. autoexception:: questdb.auth.OidcInteractionRequired
:show-inheritance:

.. autoexception:: questdb.auth.OidcDeviceFlowError
:show-inheritance:

.. autoexception:: questdb.auth.OidcTimeoutError
:show-inheritance:

.. autoexception:: questdb.auth.OidcAuthError
:show-inheritance:

.. autoexception:: questdb.auth.OidcNetworkError
:show-inheritance:
Loading