diff --git a/.pylintrc b/.pylintrc
index c83f5478..c9abc2dc 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -36,10 +36,6 @@ load-plugins=
# Pickle collected data for later comparisons.
persistent=yes
-# When enabled, pylint would attempt to guess common misconfiguration and emit
-# user-friendly hints instead of false-positive error messages.
-suggestion-mode=yes
-
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 22f3f94d..cc54be48 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -8,7 +8,7 @@ version: 2
build:
os: ubuntu-22.04
tools:
- python: "3.11"
+ python: "3.13"
# Build documentation in the docs/ directory with Sphinx
diff --git a/common/testUtils.py b/common/testUtils.py
index a0d327af..d28a0640 100644
--- a/common/testUtils.py
+++ b/common/testUtils.py
@@ -15,6 +15,7 @@
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
+from selenium.webdriver.chrome.webdriver import WebDriver as Chrome
from scraper.models import MultiScraper, Scraper
from visualizer.models import JsonConfig
from visualizer.tests import filenames
@@ -76,9 +77,9 @@ def get_headless_browser(cls):
if 'CHROMEDRIVER_PATH' in os.environ:
chromeOptions.add_argument("--remote-debugging-port=9222")
service = ChromeService(executable_path=os.environ["CHROMEDRIVER_PATH"])
- return webdriver.Chrome(service=service, options=chromeOptions)
+ return Chrome(service=service, options=chromeOptions)
- return webdriver.Chrome(options=chromeOptions)
+ return Chrome(options=chromeOptions)
# Or, Firefox
# firefoxOptions = webdriver.FirefoxOptions()
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 0c799425..6b0168b1 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -15,7 +15,15 @@
sys.path.insert(0, os.path.abspath('../../'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'rcvis.settings'
+
+# Sphinx 9's autodoc calls repr() on class attributes such as the REST views'
+# `queryset = Model.objects.all()`, which queries the database. Read the Docs has
+# no database, so give the docs build an empty in-memory one with every table.
+from django.conf import settings # noqa: E402 pylint: disable=wrong-import-position
+settings.DATABASES['default']['NAME'] = ':memory:'
django.setup()
+from django.core.management import call_command # noqa: E402 pylint: disable=wrong-import-position
+call_command('migrate', verbosity=0)
# -- Project information -----------------------------------------------------
diff --git a/infra/requirements-core.txt b/infra/requirements-core.txt
index 006ad907..7c10b326 100644
--- a/infra/requirements-core.txt
+++ b/infra/requirements-core.txt
@@ -1,42 +1,42 @@
-boto3==1.38.38
+boto3==1.43.90
csscompressor==0.9.5
-django-admin-cursor-paginator==0.1.6
-django-compressor==4.5.1
+django-admin-cursor-paginator==0.1.7
+django-compressor==4.6.0
django-extra-views==0.16.0
-django-node-assets==0.9.15
+django-node-assets==0.9.16
django-registration==5.2.1
django-social-share==2.3.0
django-sortedm2m==4.0.0
django-storages==1.14.6
-Django==5.2.16
+Django==6.1.1
rcvformats==0.0.46
-selenium==4.31.0 # Stay below 4.32.0 until this is fixed: https://github.com/SeleniumHQ/selenium/issues/15772
-requests==2.33.0
-whitenoise==6.4.0
+selenium==4.48.0
+requests==2.34.2
+whitenoise==6.12.0
# For tests
mock==5.2.0
-requests-mock==1.12.0
+requests-mock==1.12.1
django_extensions==4.1
# For coverage
autopep8==2.3.2
-coverage==7.9.1
-pylint==3.3.7
-pylint-django==2.6.1
-pycodestyle==2.13.0
+coverage==7.16.0
+pylint==4.0.8
+pylint-django==2.8.0
+pycodestyle==2.14.0
# For django-rest
-djangorestframework==3.17.2
+djangorestframework==3.18.1
drf-api-tracking==1.8.4
-markdown==3.8.1
+markdown==3.10.3
# For movie creation
-celery==5.5.3
+celery==5.6.3
django-cleanup==9.0.0
moviepy==2.2.1
-pillow<12.0.0 # Highest version supported by moviepy
+pillow<13.0.0 # Highest version supported by moviepy
# For Heroku
-gunicorn==23.0.0
+gunicorn==26.2.0
django-on-heroku==1.1.2
diff --git a/infra/requirements-for-readthedocs.txt b/infra/requirements-for-readthedocs.txt
index 840facc7..d494101f 100644
--- a/infra/requirements-for-readthedocs.txt
+++ b/infra/requirements-for-readthedocs.txt
@@ -1,7 +1,7 @@
-r requirements-core.txt
# For docs
-sphinx<7
-sphinx-mdinclude==0.5.3
-sphinx-rtd-theme==1.2.2
-mistune<3
+sphinx<10
+sphinx-mdinclude==0.6.2
+sphinx-rtd-theme==3.1.0
+mistune<4
diff --git a/infra/requirements-not-for-readthedocs.txt b/infra/requirements-not-for-readthedocs.txt
index c5aa2163..2a0bb02c 100644
--- a/infra/requirements-not-for-readthedocs.txt
+++ b/infra/requirements-not-for-readthedocs.txt
@@ -1,2 +1,2 @@
# For movie creation
-pycurl==7.45.4
+pycurl==7.47.0
diff --git a/movie/tasks.py b/movie/tasks.py
index 1b00403e..209e5dec 100644
--- a/movie/tasks.py
+++ b/movie/tasks.py
@@ -10,6 +10,7 @@
import requests
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
+from selenium.webdriver.chrome.webdriver import WebDriver as Chrome
from movie.creation.movieCreator import MovieCreationFactory
from visualizer.models import JsonConfig, MovieGenerationStatuses
@@ -59,9 +60,9 @@ def create_movie_task(pk, domain):
if 'CHROMEDRIVER_PATH' in os.environ:
chromeOptions.add_argument("--remote-debugging-port=9222")
service = ChromeService(executable_path=os.environ["CHROMEDRIVER_PATH"])
- browser = webdriver.Chrome(service=service, options=chromeOptions)
+ browser = Chrome(service=service, options=chromeOptions)
else:
- browser = webdriver.Chrome(options=chromeOptions)
+ browser = Chrome(options=chromeOptions)
browser.implicitly_wait(10)
diff --git a/rcvis/celeryconfig.py b/rcvis/celeryconfig.py
index 8eb02f7a..3ea14d86 100644
--- a/rcvis/celeryconfig.py
+++ b/rcvis/celeryconfig.py
@@ -11,7 +11,7 @@
# No backend - we don't care about the results, we'll update the database
result_backend = None # pylint: disable=invalid-name
-task_annotations = {
+task_annotations = { # pylint: disable=invalid-name
'tasks.create_movie_task': {'rate_limit': '1/s'}
}
@@ -20,7 +20,7 @@
# Otherwise we get a cryptic error message
raise NotImplementedError("No queue name set. Set SQS_QUEUE_NAME.")
-broker_transport_options = {
+broker_transport_options = { # pylint: disable=invalid-name
'queue_name_prefix': sqs_queue_name
}
diff --git a/visualizer/tasks.py b/visualizer/tasks.py
index e150d4d3..fda78736 100644
--- a/visualizer/tasks.py
+++ b/visualizer/tasks.py
@@ -11,6 +11,7 @@ def create_headless_browser():
"""Create a browser with internal imports for use in process pool executor"""
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
+ from selenium.webdriver.chrome.webdriver import WebDriver as Chrome
chromeOptions = webdriver.ChromeOptions()
for arg in [
@@ -24,11 +25,11 @@ def create_headless_browser():
chromedriverPath = os.getenv('CHROMEDRIVER_PATH')
if chromedriverPath:
chromeOptions.add_argument("--remote-debugging-port=9222")
- return webdriver.Chrome(
+ return Chrome(
service=ChromeService(
executable_path=chromedriverPath),
options=chromeOptions)
- return webdriver.Chrome(options=chromeOptions)
+ return Chrome(options=chromeOptions)
def generate_json_config_title_image(pk, domain):
diff --git a/visualizer/tests/__init__.py b/visualizer/tests/__init__.py
index 8d8fdd07..5640b9c5 100644
--- a/visualizer/tests/__init__.py
+++ b/visualizer/tests/__init__.py
@@ -6,7 +6,7 @@
from unittest.mock import MagicMock, patch
-executor_mock = MagicMock()
+executor_mock = MagicMock() # pylint: disable=invalid-name
patcher = patch("visualizer.executor.get_executor", MagicMock(return_value=executor_mock))
patcher.start()
diff --git a/visualizer/tests/liveServerTestBaseClass.py b/visualizer/tests/liveServerTestBaseClass.py
index ca9f90d9..74acefcf 100644
--- a/visualizer/tests/liveServerTestBaseClass.py
+++ b/visualizer/tests/liveServerTestBaseClass.py
@@ -12,6 +12,8 @@
from selenium import webdriver
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.common.by import By
+from selenium.webdriver.remote.command import Command
+from selenium.webdriver.remote.webdriver import WebDriver as Remote
from selenium.webdriver.support.ui import Select
from common.testUtils import TestHelpers
@@ -59,7 +61,7 @@ def setUp(self):
options.set_capability('sauce:options', sauceOptions)
seleniumEndpoint = f"https://{username}:{accessKey}@ondemand.saucelabs.com:443/wd/hub"
- self.browser = webdriver.Remote(command_executor=seleniumEndpoint, options=options)
+ self.browser = Remote(command_executor=seleniumEndpoint, options=options)
else:
self.browser = TestHelpers.get_headless_browser()
self.browser.set_window_size(1280, 1024)
@@ -108,7 +110,9 @@ def _has_test_failed(self):
def _get_log(self):
""" Returns and clears the console log """
- return self.browser.get_log('browser')
+ # Selenium 4.32+ removed get_log() from webdriver.Remote (used for SauceLabs),
+ # but the underlying command is still registered on every driver, so call it directly.
+ return self.browser.execute(Command.GET_LOG, {'type': 'browser'})['value']
def _assert_log_len(self, num):
""" Asserts the log contains num elements, or prints out what's in the log.
diff --git a/visualizer/tests/testSidecar.py b/visualizer/tests/testSidecar.py
index 45442b6a..8e773e10 100644
--- a/visualizer/tests/testSidecar.py
+++ b/visualizer/tests/testSidecar.py
@@ -109,7 +109,7 @@ def _test_ballotpedia_text_with_config(self, uploadConfig, testConfigFuncs):
'incumbentsBolded': b'Incumbents are '
b'bolded and underlined',
'noIncumbents': b'There were no incumbents in this race',
- 'source': b'Source [test.com]'
+ 'source': b'Source [test.com]'
}
# Look through the test config to see if the text should be in or not in the HTML
@@ -174,7 +174,7 @@ def test_ballotpedia_text(self):
self._test_ballotpedia_text_with_config({
'jsonFile': jsonFile,
'candidateSidecarFile': sidecarFile,
- 'dataSourceURL': 'test.com', # note: http:// is auto-added
+ 'dataSourceURL': 'test.com', # note: https:// is auto-added
'areResultsCertified': True
}, {
'title': self.assertIn,