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,