Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Handle most of the ruff errors in src/
  • Loading branch information
Archmonger committed Dec 2, 2024
commit bbe906b7a222aa70c02dc2ccdc227827fd323c9b
15 changes: 15 additions & 0 deletions .github/workflows/test-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,18 @@ jobs:
run: hatch test --python ${{ matrix.python-version }} --ds=test_app.settings_single_db -v
- name: Run Multi-DB Tests
run: hatch test --python ${{ matrix.python-version }} --ds=test_app.settings_multi_db -v

python-formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Install Python Dependencies
run: pip install --upgrade pip hatch uv
- name: Check Python formatting
run: hatch fmt src tests --check
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ Don't forget to remove deprecated code on each major release!
### Changed

- Set upper limit on ReactPy version to `<2.0.0`.
- ReactPy web modules are now streamed in chunks.
- ReactPy web modules are now streamed using asychronous file reading to improve performance.
- Performed refactoring to utilize `ruff` as this repository's linter.

## [5.1.0] - 2024-11-24

Expand Down
8 changes: 7 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@ lint.extend-ignore = [
"SLF001", # Private member accessed
"E501", # Line too long
"PLC0415", # `import` should be at the top-level of a file
"BLE001", # Do not catch blind exception: `Exception`
"PLW0603", # Using global statement is discouraged
"PLR6301", # Method could be a function, class method, or static method
"S403", # `dill` module is possibly insecure
"S301", # `dill` deserialization is possibly insecure unless using trusted data
]
lint.preview = true
lint.isort.known-first-party = ["src", "tests"]
lint.isort.known-first-party = ["reactpy_django", "test_app"]
lint.isort.known-third-party = ["js"]
3 changes: 1 addition & 2 deletions src/build_scripts/copy_dir.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# ruff: noqa: INP001
import shutil
import sys
from pathlib import Path
Expand All @@ -17,15 +18,13 @@ def copy_files(source: Path, destination: Path) -> None:

if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python copy_dir.py <source_dir> <destination>")
sys.exit(1)

root_dir = Path(__file__).parent.parent.parent
src = Path(root_dir / sys.argv[1])
dest = Path(root_dir / sys.argv[2])

if not src.exists():
print(f"Source directory {src} does not exist")
sys.exit(1)

copy_files(src, dest)
6 changes: 3 additions & 3 deletions src/reactpy_django/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
__version__ = "5.1.0"
__all__ = [
"REACTPY_WEBSOCKET_ROUTE",
"html",
"hooks",
"components",
"decorators",
"hooks",
"html",
"router",
"types",
"utils",
"router",
]

# Fixes bugs with REACTPY_BACKHAUL_THREAD + built-in asyncio event loops.
Expand Down
98 changes: 27 additions & 71 deletions src/reactpy_django/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,16 @@ def reactpy_warnings(app_configs, **kwargs):
from reactpy_django.config import REACTPY_FAILED_COMPONENTS

warnings = []
INSTALLED_APPS: list[str] = getattr(settings, "INSTALLED_APPS", [])
installed_apps: list[str] = getattr(settings, "INSTALLED_APPS", [])

# Check if REACTPY_DATABASE is not an in-memory database.
if (
getattr(settings, "DATABASES", {})
.get(getattr(settings, "REACTPY_DATABASE", "default"), {})
.get("NAME", None)
getattr(settings, "DATABASES", {}).get(getattr(settings, "REACTPY_DATABASE", "default"), {}).get("NAME", None)
== ":memory:"
):
warnings.append(
Warning(
"Using ReactPy with an in-memory database can cause unexpected "
"behaviors.",
"Using ReactPy with an in-memory database can cause unexpected behaviors.",
hint="Configure settings.py:DATABASES[REACTPY_DATABASE], to use a "
"multiprocessing and thread safe database.",
id="reactpy_django.W001",
Expand All @@ -52,14 +49,12 @@ def reactpy_warnings(app_configs, **kwargs):
)

# Warn if REACTPY_BACKHAUL_THREAD is set to True with Daphne
if (
sys.argv[0].endswith("daphne")
or ("runserver" in sys.argv and "daphne" in INSTALLED_APPS)
) and getattr(settings, "REACTPY_BACKHAUL_THREAD", False):
if (sys.argv[0].endswith("daphne") or ("runserver" in sys.argv and "daphne" in installed_apps)) and getattr(
settings, "REACTPY_BACKHAUL_THREAD", False
):
warnings.append(
Warning(
"Unstable configuration detected. REACTPY_BACKHAUL_THREAD is enabled "
"and you running with Daphne.",
"Unstable configuration detected. REACTPY_BACKHAUL_THREAD is enabled and you running with Daphne.",
hint="Set settings.py:REACTPY_BACKHAUL_THREAD to False or use a different web server.",
id="reactpy_django.W003",
)
Expand All @@ -79,10 +74,8 @@ def reactpy_warnings(app_configs, **kwargs):
if REACTPY_FAILED_COMPONENTS:
warnings.append(
Warning(
"ReactPy failed to register the following components:\n\t+ "
+ "\n\t+ ".join(REACTPY_FAILED_COMPONENTS),
hint="Check if these paths are valid, or if an exception is being "
"raised during import.",
"ReactPy failed to register the following components:\n\t+ " + "\n\t+ ".join(REACTPY_FAILED_COMPONENTS),
hint="Check if these paths are valid, or if an exception is being raised during import.",
id="reactpy_django.W005",
)
)
Expand All @@ -106,10 +99,8 @@ def reactpy_warnings(app_configs, **kwargs):

# Check if REACTPY_URL_PREFIX is being used properly in our HTTP URLs
with contextlib.suppress(NoReverseMatch):
full_path = reverse("reactpy:web_modules", kwargs={"file": "example"}).strip(
"/"
)
reactpy_http_prefix = f'{full_path[: full_path.find("web_module/")].strip("/")}'
full_path = reverse("reactpy:web_modules", kwargs={"file": "example"}).strip("/")
reactpy_http_prefix = f"{full_path[: full_path.find('web_module/')].strip('/')}"
if reactpy_http_prefix != config.REACTPY_URL_PREFIX:
warnings.append(
Warning(
Expand Down Expand Up @@ -138,9 +129,7 @@ def reactpy_warnings(app_configs, **kwargs):
)

# Check if `daphne` is not in installed apps when using `runserver`
if "runserver" in sys.argv and "daphne" not in getattr(
settings, "INSTALLED_APPS", []
):
if "runserver" in sys.argv and "daphne" not in getattr(settings, "INSTALLED_APPS", []):
warnings.append(
Warning(
"You have not configured the `runserver` command to use ASGI. "
Expand All @@ -153,10 +142,7 @@ def reactpy_warnings(app_configs, **kwargs):
# DELETED W013: Check if deprecated value REACTPY_RECONNECT_MAX exists

# Check if REACTPY_RECONNECT_INTERVAL is set to a large value
if (
isinstance(config.REACTPY_RECONNECT_INTERVAL, int)
and config.REACTPY_RECONNECT_INTERVAL > 30000
):
if isinstance(config.REACTPY_RECONNECT_INTERVAL, int) and config.REACTPY_RECONNECT_INTERVAL > 30000:
warnings.append(
Warning(
"REACTPY_RECONNECT_INTERVAL is set to >30 seconds. Are you sure this is intentional? "
Expand All @@ -167,10 +153,7 @@ def reactpy_warnings(app_configs, **kwargs):
)

# Check if REACTPY_RECONNECT_MAX_RETRIES is set to a large value
if (
isinstance(config.REACTPY_RECONNECT_MAX_RETRIES, int)
and config.REACTPY_RECONNECT_MAX_RETRIES > 5000
):
if isinstance(config.REACTPY_RECONNECT_MAX_RETRIES, int) and config.REACTPY_RECONNECT_MAX_RETRIES > 5000:
warnings.append(
Warning(
"REACTPY_RECONNECT_MAX_RETRIES is set to a very large value "
Expand Down Expand Up @@ -204,18 +187,12 @@ def reactpy_warnings(app_configs, **kwargs):
and config.REACTPY_RECONNECT_MAX_INTERVAL > 0
and config.REACTPY_RECONNECT_MAX_RETRIES > 0
and config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER > 1
and (
config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER
** config.REACTPY_RECONNECT_MAX_RETRIES
)
and (config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER**config.REACTPY_RECONNECT_MAX_RETRIES)
* config.REACTPY_RECONNECT_INTERVAL
< config.REACTPY_RECONNECT_MAX_INTERVAL
):
max_value = math.floor(
(
config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER
** config.REACTPY_RECONNECT_MAX_RETRIES
)
(config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER**config.REACTPY_RECONNECT_MAX_RETRIES)
* config.REACTPY_RECONNECT_INTERVAL
)
warnings.append(
Expand All @@ -229,13 +206,10 @@ def reactpy_warnings(app_configs, **kwargs):

# Check if 'reactpy_django' is in the correct position in INSTALLED_APPS
position_to_beat = 0
for app in INSTALLED_APPS:
for app in installed_apps:
if app.startswith("django.contrib."):
position_to_beat = INSTALLED_APPS.index(app)
if (
"reactpy_django" in INSTALLED_APPS
and INSTALLED_APPS.index("reactpy_django") < position_to_beat
):
position_to_beat = installed_apps.index(app)
if "reactpy_django" in installed_apps and installed_apps.index("reactpy_django") < position_to_beat:
warnings.append(
Warning(
"The position of 'reactpy_django' in INSTALLED_APPS is suspicious.",
Expand Down Expand Up @@ -276,17 +250,13 @@ def reactpy_errors(app_configs, **kwargs):
)

# DATABASE_ROUTERS is properly configured when REACTPY_DATABASE is defined
if getattr(
settings, "REACTPY_DATABASE", None
) and "reactpy_django.database.Router" not in getattr(
if getattr(settings, "REACTPY_DATABASE", None) and "reactpy_django.database.Router" not in getattr(
settings, "DATABASE_ROUTERS", []
):
errors.append(
Error(
"ReactPy database has been changed but the database router is "
"not configured.",
hint="Set settings.py:DATABASE_ROUTERS to "
"['reactpy_django.database.Router', ...]",
"ReactPy database has been changed but the database router is not configured.",
hint="Set settings.py:DATABASE_ROUTERS to ['reactpy_django.database.Router', ...]",
id="reactpy_django.E002",
)
)
Expand Down Expand Up @@ -336,9 +306,7 @@ def reactpy_errors(app_configs, **kwargs):
)

# Check if REACTPY_DEFAULT_QUERY_POSTPROCESSOR is a valid data type
if not isinstance(
getattr(settings, "REACTPY_DEFAULT_QUERY_POSTPROCESSOR", ""), (str, type(None))
):
if not isinstance(getattr(settings, "REACTPY_DEFAULT_QUERY_POSTPROCESSOR", ""), (str, type(None))):
errors.append(
Error(
"Invalid type for REACTPY_DEFAULT_QUERY_POSTPROCESSOR.",
Expand Down Expand Up @@ -397,10 +365,7 @@ def reactpy_errors(app_configs, **kwargs):
)

# Check if REACTPY_RECONNECT_INTERVAL is a positive integer
if (
isinstance(config.REACTPY_RECONNECT_INTERVAL, int)
and config.REACTPY_RECONNECT_INTERVAL < 0
):
if isinstance(config.REACTPY_RECONNECT_INTERVAL, int) and config.REACTPY_RECONNECT_INTERVAL < 0:
errors.append(
Error(
"Invalid value for REACTPY_RECONNECT_INTERVAL.",
Expand All @@ -420,10 +385,7 @@ def reactpy_errors(app_configs, **kwargs):
)

# Check if REACTPY_RECONNECT_MAX_INTERVAL is a positive integer
if (
isinstance(config.REACTPY_RECONNECT_MAX_INTERVAL, int)
and config.REACTPY_RECONNECT_MAX_INTERVAL < 0
):
if isinstance(config.REACTPY_RECONNECT_MAX_INTERVAL, int) and config.REACTPY_RECONNECT_MAX_INTERVAL < 0:
errors.append(
Error(
"Invalid value for REACTPY_RECONNECT_MAX_INTERVAL.",
Expand Down Expand Up @@ -457,10 +419,7 @@ def reactpy_errors(app_configs, **kwargs):
)

# Check if REACTPY_RECONNECT_MAX_RETRIES is a positive integer
if (
isinstance(config.REACTPY_RECONNECT_MAX_RETRIES, int)
and config.REACTPY_RECONNECT_MAX_RETRIES < 0
):
if isinstance(config.REACTPY_RECONNECT_MAX_RETRIES, int) and config.REACTPY_RECONNECT_MAX_RETRIES < 0:
errors.append(
Error(
"Invalid value for REACTPY_RECONNECT_MAX_RETRIES.",
Expand Down Expand Up @@ -523,10 +482,7 @@ def reactpy_errors(app_configs, **kwargs):
)

# Check if REACTPY_CLEAN_INTERVAL is a positive integer
if (
isinstance(config.REACTPY_CLEAN_INTERVAL, int)
and config.REACTPY_CLEAN_INTERVAL < 0
):
if isinstance(config.REACTPY_CLEAN_INTERVAL, int) and config.REACTPY_CLEAN_INTERVAL < 0:
errors.append(
Error(
"Invalid value for REACTPY_CLEAN_INTERVAL.",
Expand Down
26 changes: 9 additions & 17 deletions src/reactpy_django/clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@
if TYPE_CHECKING:
from reactpy_django.models import Config

CLEAN_NEEDED_BY: datetime = datetime(
year=1, month=1, day=1, tzinfo=timezone.now().tzinfo
)
CLEAN_NEEDED_BY: datetime = datetime(year=1, month=1, day=1, tzinfo=timezone.now().tzinfo)


def clean(
Expand All @@ -36,8 +34,8 @@ def clean(
user_data = REACTPY_CLEAN_USER_DATA

if args:
sessions = any(value in args for value in {"sessions", "all"})
user_data = any(value in args for value in {"user_data", "all"})
sessions = any(value in args for value in ("sessions", "all"))
user_data = any(value in args for value in ("user_data", "all"))

if sessions:
clean_sessions(verbosity)
Expand All @@ -54,16 +52,14 @@ def clean_sessions(verbosity: int = 1):
from reactpy_django.models import ComponentSession

if verbosity >= 2:
print("Cleaning ReactPy component sessions...")
_logger.info("Cleaning ReactPy component sessions...")

start_time = timezone.now()
expiration_date = timezone.now() - timedelta(seconds=REACTPY_SESSION_MAX_AGE)
session_objects = ComponentSession.objects.filter(
last_accessed__lte=expiration_date
)
session_objects = ComponentSession.objects.filter(last_accessed__lte=expiration_date)

if verbosity >= 2:
print(f"Deleting {session_objects.count()} expired component sessions...")
_logger.info("Deleting %d expired component sessions...", session_objects.count())

session_objects.delete()

Expand All @@ -83,7 +79,7 @@ def clean_user_data(verbosity: int = 1):
from reactpy_django.models import UserDataModel

if verbosity >= 2:
print("Cleaning ReactPy user data...")
_logger.info("Cleaning ReactPy user data...")

start_time = timezone.now()
user_model = get_user_model()
Expand All @@ -97,9 +93,7 @@ def clean_user_data(verbosity: int = 1):
user_data_objects = UserDataModel.objects.exclude(user_pk__in=all_user_pks)

if verbosity >= 2:
print(
f"Deleting {user_data_objects.count()} user data objects not associated with an existing user..."
)
_logger.info("Deleting %d user data objects not associated with an existing user...", user_data_objects.count())

user_data_objects.delete()

Expand Down Expand Up @@ -129,9 +123,7 @@ def inspect_clean_duration(start_time: datetime, task_name: str, verbosity: int)
clean_duration = timezone.now() - start_time

if verbosity >= 3:
print(
f"Cleaned ReactPy {task_name} in {clean_duration.total_seconds()} seconds."
)
_logger.info("Cleaned ReactPy %s in %s seconds.", task_name, clean_duration.total_seconds())

if clean_duration.total_seconds() > 1:
_logger.warning(
Expand Down
Loading