Skip to content

Commit b0ddceb

Browse files
authored
add raw headers to headers (#4958)
* add raw headers to headers * only set attr for known fields * fix unit tests * add more stuff to pyproject * remove pyvirtualdisplay * run app harness headless
1 parent c32f1a8 commit b0ddceb

File tree

13 files changed

+325
-82
lines changed

13 files changed

+325
-82
lines changed

.github/workflows/check_node_latest.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ jobs:
3232
- uses: actions/setup-node@v4
3333
with:
3434
node-version: ${{ matrix.node-version }}
35-
- run: |
36-
uv pip install pyvirtualdisplay pillow pytest-split
37-
uv run playwright install --with-deps
35+
- run: uv run playwright install --with-deps
3836
- run: |
3937
uv run pytest tests/test_node_version.py
4038
uv run pytest tests/integration --splits 2 --group ${{matrix.split_index}}

.github/workflows/check_outdated_dependencies.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ jobs:
5656
- name: Install Requirements for reflex-web
5757
working-directory: ./reflex-web
5858
run: uv pip install $(grep -ivE "reflex " requirements.txt)
59-
- name: Install additional dependencies for DB access
60-
run: uv pip install psycopg
6159
- name: Init Website for reflex-web
6260
working-directory: ./reflex-web
6361
run: uv run reflex init

.github/workflows/integration_app_harness.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ on:
1313
branches: ["main"]
1414
paths-ignore:
1515
- "**/*.md"
16+
env:
17+
APP_HARNESS_HEADLESS: 1
1618

1719
permissions:
1820
contents: read
@@ -47,7 +49,6 @@ jobs:
4749
python-version: ${{ matrix.python-version }}
4850
run-uv-sync: true
4951

50-
- run: uv pip install pyvirtualdisplay pillow pytest-split pytest-retry
5152
- name: Run app harness tests
5253
env:
5354
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}

.github/workflows/integration_tests.yml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ jobs:
7272
working-directory: ./reflex-examples/counter
7373
run: |
7474
uv pip install -r requirements.txt
75-
- name: Install additional dependencies for DB access
76-
run: uv pip install psycopg
7775
- name: Check export --backend-only before init for counter example
7876
working-directory: ./reflex-examples/counter
7977
run: |
@@ -98,8 +96,6 @@ jobs:
9896
working-directory: ./reflex-examples/nba-proxy
9997
run: |
10098
uv pip install -r requirements.txt
101-
- name: Install additional dependencies for DB access
102-
run: uv pip install psycopg
10399
- name: Check export --backend-only before init for nba-proxy example
104100
working-directory: ./reflex-examples/nba-proxy
105101
run: |
@@ -144,8 +140,6 @@ jobs:
144140
- name: Install Requirements for reflex-web
145141
working-directory: ./reflex-web
146142
run: uv pip install $(grep -ivE "reflex " requirements.txt)
147-
- name: Install additional dependencies for DB access
148-
run: uv pip install psycopg
149143
- name: Init Website for reflex-web
150144
working-directory: ./reflex-web
151145
run: uv run reflex init
@@ -207,8 +201,6 @@ jobs:
207201
- name: Install Requirements for reflex-web
208202
working-directory: ./reflex-web
209203
run: uv pip install -r requirements.txt
210-
- name: Install additional dependencies for DB access
211-
run: uv pip install psycopg
212204
- name: Init Website for reflex-web
213205
working-directory: ./reflex-web
214206
run: uv run reflex init

.github/workflows/pre-commit.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ jobs:
2727
run-uv-sync: true
2828

2929
# TODO pre-commit related stuff can be cached too (not a bottleneck yet)
30-
- run: |
31-
uv pip install pre-commit
30+
- run:
3231
uv run pre-commit run --all-files
3332
env:
3433
SKIP: update-pyi-files

pyproject.toml

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,23 +128,28 @@ ignore-words-list = "te, TreeE"
128128

129129
[dependency-groups]
130130
dev = [
131-
"asynctest <1.0,>=0.13.0",
132-
"darglint <2.0,>=1.8.1",
131+
"asynctest >=0.13.0,<1.0",
132+
"darglint >=1.8.1,<2.0",
133133
"dill >=0.3.8",
134-
"pandas <3.0,>=2.1.1",
135-
"pillow <12.0,>=10.0.0",
134+
"pandas >=2.1.1,<3.0",
135+
"pillow >=10.0.0,<12.0",
136136
"playwright >=1.46.0",
137-
"plotly <6.0,>=5.13.0",
137+
"plotly >=5.13.0,<6.0",
138138
"pre-commit >=3.2.1",
139-
"pyright <1.2,>=1.1.394",
140-
"pytest <9.0,>=7.1.2",
139+
"pyright >=1.1.394,<1.2",
140+
"pytest >=7.1.2,<9.0",
141141
"pytest-asyncio >=0.24.0",
142-
"pytest-mock <4.0,>=3.10.0",
143-
"pytest-cov <7.0,>=4.0.0",
144-
"pytest-benchmark <6.0,>=4.0.0",
142+
"pytest-mock >=3.10.0,<4.0",
143+
"pytest-cov >=4.0.0,<7.0",
144+
"pytest-benchmark >=4.0.0,<6.0",
145145
"pytest-playwright >=0.5.1",
146-
"pytest-codspeed <4.0.0,>=3.1.2",
146+
"pytest-codspeed >=3.1.2,<4.0.0",
147147
"ruff ==0.9.10",
148-
"selenium <5.0,>=4.11.0",
149-
"toml <1.0,>=0.10.2",
148+
"selenium >=4.11.0,<5.0",
149+
"toml >=0.10.2,<1.0",
150+
"granian[reload] >= 2.0.0",
151+
"psycopg[binary] >=3.2.6,<4.0",
152+
"pytest-split >=0.10.0,<1.0",
153+
"pytest-retry >=1.7.0,<2.0",
154+
"pre-commit >=4.1.0,<5.0",
150155
]

reflex/istate/data.py

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,32 @@
11
"""This module contains the dataclasses representing the router object."""
22

33
import dataclasses
4+
from typing import Mapping
45

56
from reflex import constants
67
from reflex.utils import format
8+
from reflex.utils.serializers import serializer
79

810

9-
@dataclasses.dataclass(frozen=True)
10-
class HeaderData:
11-
"""An object containing headers data."""
11+
@dataclasses.dataclass(frozen=True, init=False)
12+
class _FrozenDictStrStr(Mapping[str, str]):
13+
_data: tuple[tuple[str, str], ...]
14+
15+
def __init__(self, **kwargs):
16+
object.__setattr__(self, "_data", tuple(sorted(kwargs.items())))
17+
18+
def __getitem__(self, key: str) -> str:
19+
return dict(self._data)[key]
20+
21+
def __iter__(self):
22+
return (x[0] for x in self._data)
1223

24+
def __len__(self):
25+
return len(self._data)
26+
27+
28+
@dataclasses.dataclass(frozen=True)
29+
class _HeaderData:
1330
host: str = ""
1431
origin: str = ""
1532
upgrade: str = ""
@@ -23,19 +40,54 @@ class HeaderData:
2340
sec_websocket_extensions: str = ""
2441
accept_encoding: str = ""
2542
accept_language: str = ""
43+
raw_headers: Mapping[str, str] = dataclasses.field(
44+
default_factory=_FrozenDictStrStr
45+
)
46+
47+
48+
@dataclasses.dataclass(frozen=True, init=False)
49+
class HeaderData(_HeaderData):
50+
"""An object containing headers data."""
2651

2752
def __init__(self, router_data: dict | None = None):
2853
"""Initialize the HeaderData object based on router_data.
2954
3055
Args:
3156
router_data: the router_data dict.
3257
"""
58+
super().__init__()
3359
if router_data:
60+
fields_names = [f.name for f in dataclasses.fields(self)]
3461
for k, v in router_data.get(constants.RouteVar.HEADERS, {}).items():
35-
object.__setattr__(self, format.to_snake_case(k), v)
36-
else:
37-
for k in dataclasses.fields(self):
38-
object.__setattr__(self, k.name, "")
62+
snake_case_key = format.to_snake_case(k)
63+
if snake_case_key in fields_names:
64+
object.__setattr__(self, snake_case_key, v)
65+
object.__setattr__(
66+
self,
67+
"raw_headers",
68+
_FrozenDictStrStr(
69+
**{
70+
k: v
71+
for k, v in router_data.get(
72+
constants.RouteVar.HEADERS, {}
73+
).items()
74+
if v
75+
}
76+
),
77+
)
78+
79+
80+
@serializer(to=dict)
81+
def serialize_frozen_dict_str_str(obj: _FrozenDictStrStr) -> dict:
82+
"""Serialize a _FrozenDictStrStr object to a dict.
83+
84+
Args:
85+
obj: the _FrozenDictStrStr object.
86+
87+
Returns:
88+
A dict representation of the _FrozenDictStrStr object.
89+
"""
90+
return dict(obj._data)
3991

4092

4193
@dataclasses.dataclass(frozen=True)

reflex/testing.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,12 @@
5454
from reflex.utils import console
5555

5656
try:
57-
from selenium import webdriver # pyright: ignore [reportMissingImports]
58-
from selenium.webdriver.remote.webdriver import ( # pyright: ignore [reportMissingImports]
59-
WebDriver,
60-
)
57+
from selenium import webdriver
58+
from selenium.webdriver.remote.webdriver import WebDriver
6159

6260
if TYPE_CHECKING:
63-
from selenium.webdriver.common.options import (
64-
ArgOptions, # pyright: ignore [reportMissingImports]
65-
)
66-
from selenium.webdriver.remote.webelement import ( # pyright: ignore [reportMissingImports]
67-
WebElement,
68-
)
61+
from selenium.webdriver.common.options import ArgOptions
62+
from selenium.webdriver.remote.webelement import WebElement
6963

7064
has_selenium = True
7165
except ImportError:

reflex/utils/exec.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -332,11 +332,9 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
332332
"""
333333
console.debug("Using Granian for backend")
334334
try:
335-
from granian import Granian # pyright: ignore [reportMissingImports]
336-
from granian.constants import ( # pyright: ignore [reportMissingImports]
337-
Interfaces,
338-
)
339-
from granian.log import LogLevels # pyright: ignore [reportMissingImports]
335+
from granian.constants import Interfaces
336+
from granian.log import LogLevels
337+
from granian.server import Server as Granian
340338

341339
Granian(
342340
target=get_granian_target(),
@@ -466,9 +464,7 @@ def run_granian_backend_prod(host: str, port: int, loglevel: LogLevel):
466464
from reflex.utils import processes
467465

468466
try:
469-
from granian.constants import ( # pyright: ignore [reportMissingImports]
470-
Interfaces,
471-
)
467+
from granian.constants import Interfaces
472468

473469
command = [
474470
"granian",

tests/integration/conftest.py

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,10 @@
11
"""Shared conftest for all integration tests."""
22

3-
import os
4-
53
import pytest
64

75
import reflex.app
8-
from reflex.config import environment
96
from reflex.testing import AppHarness, AppHarnessProd
107

11-
DISPLAY = None
12-
XVFB_DIMENSIONS = (800, 600)
13-
14-
15-
@pytest.fixture(scope="session", autouse=True)
16-
def xvfb():
17-
"""Create virtual X display.
18-
19-
This function is a no-op unless GITHUB_ACTIONS is set in the environment.
20-
21-
Yields:
22-
the pyvirtualdisplay object that the browser will be open on
23-
"""
24-
if os.environ.get("GITHUB_ACTIONS") and not environment.APP_HARNESS_HEADLESS.get():
25-
from pyvirtualdisplay.smartdisplay import ( # pyright: ignore [reportMissingImports]
26-
SmartDisplay,
27-
)
28-
29-
global DISPLAY
30-
with SmartDisplay(visible=False, size=XVFB_DIMENSIONS) as DISPLAY:
31-
yield DISPLAY
32-
DISPLAY = None
33-
else:
34-
yield None
35-
368

379
@pytest.fixture(
3810
scope="session", params=[AppHarness, AppHarnessProd], ids=["dev", "prod"]

0 commit comments

Comments
 (0)