#188 install linters, fix some warnings
Closed 2 years ago by kparal. Opened 2 years ago by kparal.

file modified
+1
@@ -5,6 +5,7 @@ 

  

  # cache files

  /.cache/

+ /.mypy_cache/

  /.pytest_cache/

  .sass-cache/

  *__pycache__*

@@ -11,7 +11,6 @@ 

  down_revision = 'c09a519d5c50'

  

  from alembic import op

- import sqlalchemy as sa

  

  

  def upgrade():

@@ -11,7 +11,6 @@ 

  down_revision = '1162fb4d4358'

  

  from alembic import op

- import sqlalchemy as sa

  from sqlalchemy.sql import column

  

  

file modified
+11 -7
@@ -1,9 +1,9 @@ 

- import logging

  import logging.handlers

  import os

  

  from flask import Flask, render_template

  from flask_sqlalchemy import SQLAlchemy

+ from flask_sqlalchemy.model import DefaultMeta

  

  from . import config

  
@@ -11,7 +11,7 @@ 

  __version__ = "1.3.1"

  

  # Flask App

- app = Flask(__name__)

+ app: Flask = Flask(__name__)

  # Is this an OpenShift deployment?

  openshift = os.getenv('OPENSHIFT_PROD')

  
@@ -103,13 +103,15 @@ 

  # database

  if app.config['SHOW_DB_URI']:

      app.logger.debug('using DBURI: %s' % app.config['SQLALCHEMY_DATABASE_URI'])

- db = SQLAlchemy(app)

+ db: SQLAlchemy = SQLAlchemy(app)

+ # https://stackoverflow.com/questions/63542818/mypy-and-inheriting-from-a-class-that-is-an-attribute-on-an-instance

+ BaseModel: DefaultMeta = db.Model

  

  # "Hotfix" for proxy handling on current deployment, my guess is that the proxy

  # server is set differently than it was, but what do I know...

  if app.config["BEHIND_PROXY"]:

      from werkzeug.middleware.proxy_fix import ProxyFix

-     app.wsgi_app = ProxyFix(app.wsgi_app, x_host=1)

+     app.wsgi_app = ProxyFix(app.wsgi_app, x_host=1)  # type: ignore[assignment]

  

  

  # === Flask views and stuff ===
@@ -176,6 +178,7 @@ 

  

      return ''.join(label)

  

+ 

  @app.template_filter('datetime')

  def datetime_format(value, format='%Y-%m-%d %H:%M:%S UTC'):

      if value is not None:
@@ -184,14 +187,15 @@ 

  

  

  # register blueprints

- from blockerbugs.controllers.main import main

+ from blockerbugs.controllers.main import main  # noqa: E402

  app.register_blueprint(main)

  

- from blockerbugs.controllers.admin import admin

+ from blockerbugs.controllers.admin import admin  # noqa: F401,E402

  

- from blockerbugs.controllers.api import api_v0

+ from blockerbugs.controllers.api import api_v0  # noqa: E402

  app.register_blueprint(api_v0)

  

+ 

  # setup error handling

  @app.errorhandler(404)

  def page_not_found(e):

@@ -1,1 +1,1 @@ 

- from .api import api_v0 

\ No newline at end of file

+ from .api import api_v0  # noqa: F401

@@ -22,11 +22,7 @@ 

  #   Ilgiz Islamgulov <ilgizisl@gmail.com>

  #

  

- # First, try to import modern http.client and support also old one on EPEL 7/Python 2

- try:

-     import http.client as httplib

- except ImportError:

-     import httplib

+ import http.client as httplib

  

  

  class RestApiError(Exception):

@@ -94,7 +94,7 @@ 

  

  

  class IntegerValidator(NumericValidator):

-     numeric_type = int

+     numeric_type = int  # type: ignore[assignment]

  

  class StringValidator(BaseValidator):

      def _check_data(self):

@@ -21,8 +21,9 @@ 

  #   Tim Flink <tflink@redhat.com>

  

  

- from sqlalchemy.types import SchemaType, TypeDecorator, Enum

+ from sqlalchemy.types import SchemaType, TypeDecorator, Enum  # type: ignore[attr-defined]

  import re

+ from typing import Any

  

  

  class EnumSymbol(object):
@@ -68,7 +69,7 @@ 

      """Declarative enumeration."""

  

      __metaclass__ = EnumMeta

-     _reg = {}

+     _reg: dict[Any, Any] = {}

  

      @classmethod

      def from_string(cls, value):

file modified
+2 -2
@@ -19,7 +19,7 @@ 

  

  """bug.py - ORM class for bug information stored in the database"""

  

- from blockerbugs import db

+ from blockerbugs import db, BaseModel

  import datetime

  import json

  from typing import Any, Optional
@@ -28,7 +28,7 @@ 

  from blockerbugs.models.milestone import Milestone

  

  

- class Bug(db.Model):

+ class Bug(BaseModel):

      """A representation of a Bugzilla ticket, with some additional attributes.

  

      Note: While a particular bug (with a given `bugid`) can exist only once in Bugzilla, a matching

file modified
+2 -2
@@ -24,11 +24,11 @@ 

  from sqlalchemy.schema import UniqueConstraint

  import koji

  

- from blockerbugs import db

+ from blockerbugs import db, BaseModel

  from blockerbugs.util.koji_interface import get_build_data

  

  

- class Build(db.Model):

+ class Build(BaseModel):

      koji_id = db.Column(db.Integer, primary_key=True)

      pkg_name = db.Column(db.Text)

      version = db.Column(db.Text)

@@ -20,11 +20,11 @@ 

  # Authors:

  #   Tim Flink <tflink@redhat.com>

  

- from blockerbugs import db

+ from blockerbugs import db, BaseModel

  import datetime

  

  

- class Criterion(db.Model):

+ class Criterion(BaseModel):

      id = db.Column(db.Integer, primary_key=True)

      date_created = db.Column(db.DateTime, unique=False)

      criterion = db.Column(db.String(4096), unique=False)

@@ -19,7 +19,7 @@ 

  

  """milestone.py - ORM class for release milestone info stored in the database"""

  

- from blockerbugs import db

+ from blockerbugs import db, BaseModel

  from typing import Any, Optional

  

  # unused imports, but required by dynamic relationships:
@@ -27,7 +27,7 @@ 

  from blockerbugs.models.release import Release

  

  

- class Milestone(db.Model):

+ class Milestone(BaseModel):

      """A `Milestone` represents e.g. a Beta or a Final milestone, in relation to a certain

      `Release` (like 35).

      """

@@ -19,12 +19,12 @@ 

  

  """release.py - database model for releases"""

  

- from blockerbugs import db

+ from blockerbugs import db, BaseModel

  from datetime import datetime

  from typing import Optional

  

  

- class Release(db.Model):

+ class Release(BaseModel):

      """This represents an OS release, primarily determined by its number."""

  

      id = db.Column(db.Integer, primary_key=True)

@@ -21,10 +21,10 @@ 

  #   Tim Flink <tflink@redhat.com>

  

  

- from blockerbugs import db

+ from blockerbugs import db, BaseModel

  

  

- class Settings(db.Model):

+ class Settings(BaseModel):

      id = db.Column(db.Integer, primary_key=True)

      name = db.Column(db.String(80))

      value = db.Column(db.String(80))

file modified
+6 -7
@@ -20,13 +20,12 @@ 

  # Authors:

  #   Tim Flink <tflink@redhat.com>

  

- from blockerbugs import db

- from blockerbugs.models.bug import Bug

+ from blockerbugs import db, BaseModel

  

- # unused imports, but required by dynamic relationships:

+ # imports required by dynamic relationships:

  from blockerbugs.models.bug import Bug

- from blockerbugs.models.release import Release

- from blockerbugs.models.milestone import Milestone

+ from blockerbugs.models.release import Release  # noqa: F401

+ from blockerbugs.models.milestone import Milestone  # noqa: F401

  

  

  update_fixes = db.Table(
@@ -42,7 +41,7 @@ 

  )

  

  

- class Update(db.Model):

+ class Update(BaseModel):

      id = db.Column(db.Integer, primary_key=True)

      title = db.Column(db.Text, unique=False)

      url = db.Column(db.Text, unique=False)
@@ -105,7 +104,7 @@ 

                  if not (bug.bugid, bug.milestone) in current_bugkeys:

                      self.bugs.append(bug)

                      current_bugkeys.append((bug.bugid, bug.milestone))

-                 if bug.milestone and not bug.milestone in self.milestones:

+                 if bug.milestone and bug.milestone not in self.milestones:

                      self.milestones.append(bug.milestone)

  

      @classmethod

file modified
+2 -2
@@ -20,7 +20,7 @@ 

  # Authors:

  #   Tim Flink <tflink@redhat.com>

  

- from blockerbugs import db

+ from blockerbugs import db, BaseModel

  from werkzeug.security import generate_password_hash, check_password_hash

  

  ############################################################
@@ -28,7 +28,7 @@ 

  # It is a pretty dirty hack, but it does work well enough for now

  ############################################################

  

- class User(db.Model):

+ class User(BaseModel):

      id = db.Column(db.Integer, primary_key=True)

      username = db.Column(db.String(80), unique=True)

      pw_hash = db.Column(db.String(120), unique=False)

@@ -23,10 +23,10 @@ 

  import random

  import crypt

  

- from blockerbugs import db

+ from blockerbugs import db, BaseModel

  

  

- class UserInfo(db.Model):

+ class UserInfo(BaseModel):

      id = db.Column(db.Integer, primary_key=True)

      fas_login = db.Column(db.String(255), unique=True)

      bz_user = db.Column(db.String(255), unique=True)

file modified
+10 -4
@@ -106,6 +106,8 @@ 

              return milestone.fe_tracker

          elif tracker_type == 'PrioritizedBug':

              return None

+         else:

+             raise ValueError(f'Invalid tracker_type: {tracker_type}')

  

      def update_milestone(self, milestone: Milestone, tracker_type: str,

                           lastupdate: Optional[datetime] = None) -> None:
@@ -119,11 +121,13 @@ 

              trackerid = self.get_tracker_id(milestone, tracker_type)

              assert trackerid

              self.log.info('Updating %s for Fedora %d %s (%d)' % (

-                 tracker_type, milestone.release.number, milestone.version, trackerid))

+                 tracker_type, milestone.release.number if milestone.release else -1,

+                 milestone.version, trackerid))

              bugs = self.bzinterface.query_tracker(trackerid, last_update=lastupdate)

          elif tracker_type == 'PrioritizedBug':

              self.log.info('Updating %s for Fedora %d %s' % (

-                 tracker_type, milestone.release.number, milestone.version))

+                 tracker_type, milestone.release.number if milestone.release else -1,

+                 milestone.version))

              bugs = self.bzinterface.query_prioritized()

          else:

              raise ValueError(f'Invalid tracker_type: {tracker_type}')
@@ -163,12 +167,14 @@ 

              trackerid = self.get_tracker_id(milestone, tracker_type)

              assert trackerid

              self.log.info('Cleaning up %s for Fedora %d %s (%d)' % (

-                 tracker_type, milestone.release.number, milestone.version, trackerid))

+                 tracker_type, milestone.release.number if milestone.release else -1,

+                 milestone.version, trackerid))

              tracker_depends = set(self.bzinterface.get_deps(trackerid))

              self.log.debug("Current dependencies for %d: %s" % (trackerid, str(tracker_depends)))

          elif tracker_type == 'PrioritizedBug':

              self.log.info('Cleaning up %s for Fedora %d %s' % (

-                 tracker_type, milestone.release.number, milestone.version))

+                 tracker_type, milestone.release.number if milestone.release else -1,

+                 milestone.version))

              tracker_depends = set()

          else:

              raise ValueError(f'Invalid tracker_type: {tracker_type}')

@@ -21,7 +21,7 @@ 

  

  import logging

  import datetime

- from typing import Optional

+ from typing import Optional, Any

  

  import bugzilla

  from bugzilla.bug import Bug as bzBug
@@ -60,7 +60,7 @@ 

             :param logger: A custom `Logger` instance. Otherwise a default Logger is created.

          """

          self.logger = logger or logging.getLogger('bz_interface')

-         self.bz = bz

+         self.bz: bugzilla.Bugzilla = bz

          if not bz:

              if not (user and password):

                  self.bz = bugzilla.Bugzilla(url=url,
@@ -77,7 +77,7 @@ 

      # https://partner-bugzilla.redhat.com/buglist.cgi?bug_status=NEW&bug_status=ASSIGNED

      # &bug_status=POST&bug_status=MODIFIED&classification=Fedora&component=anaconda&f1=component

      # &o1=changedafter&product=Fedora&query_format=advanced&v1=2013-03-21%2012%3A25&version=19

-     def get_bz_query(self, tracker: int, last_update: datetime.datetime = None) -> dict[str, str]:

+     def get_bz_query(self, tracker: int, last_update: datetime.datetime = None) -> dict[str, Any]:

          """Build a Bugzilla query to retrieve all necessary info about all bugs which block the

          `tracker` bug.

  

@@ -46,6 +46,10 @@ 

  

    pip install -r requirements.txt

  

+ Then install development helper packages, which are not required to run or test the project, but help when writing the code::

+ 

+   pip install -r requirements-development.txt

+ 

  

  Configuring dev environment

  ===========================
@@ -132,3 +136,21 @@ 

  run from the root project directory with::

  

    pytest

+ 

+ 

+ Configuring and running linters

+ ===============================

+ 

+ As part of the ``requirements-development.txt`` file, there are various source

+ code linters. The integration part for most of them is the

+ ``python-language-server``, which can run the available linters automatically

+ and then provide the results to your editor/IDE. Check your editor

+ configuration or plugins, whether you can make use of the language-server (or

+ at least use the installed linters directly). Make sure to start your editor

+ from the virtualenv, though, so that it can access those packages.

+ 

+ If your editor doesn't support linter integration, you can still run them from

+ the command line manually (inside the virtualenv)::

+ 

+   flake8

+   mypy

file removed
-8
@@ -1,8 +0,0 @@ 

- [pytest]

- minversion = 2.0

- python_functions=test should

- python_files=test_* testfunc_*

- testpaths = testing

- filterwarnings =

-     ignore:the imp module is deprecated in favour of importlib:DeprecationWarning:koji

-     ignore:defusedxml.cElementTree is deprecated, import from defusedxml.ElementTree instead:DeprecationWarning:openid

@@ -0,0 +1,11 @@ 

+ ## linters

+ # this automatically also pulls rope and flake8 (which pulls pyflakes, pycodestyle and mccabe)

+ python-language-server[rope,flake8]

+ mypy

+ 

+ ## additional type hints for mypy

+ sqlalchemy-stubs

+ types-Flask

+ munch-stubs

+ types-requests

+ types-mock

file added
+45
@@ -0,0 +1,45 @@ 

+ [flake8]

+ extend-exclude = alembic/, conf/, docs/

+ max-line-length = 100

+ 

+ [mypy]

+ files = blockerbugs/, testing/, *.py

+ # make sure to install the stubs from requirements-development.txt

+ plugins = sqlmypy

+ 

+ # There seem to be no typing stubs available for these libraries:

+ [mypy-flask_sqlalchemy.*]

+ ignore_missing_imports = True

+ [mypy-flask_wtf.*]

+ ignore_missing_imports = True

+ [mypy-flask_fas_openid.*]

+ ignore_missing_imports = True

+ [mypy-flask_admin.*]

+ ignore_missing_imports = True

+ [mypy-alembic.*]

+ ignore_missing_imports = True

+ [mypy-wtforms.*]

+ ignore_missing_imports = True

+ [mypy-iso8601.*]

+ ignore_missing_imports = True

+ [mypy-koji.*]

+ ignore_missing_imports = True

+ [mypy-bugzilla.*]

+ ignore_missing_imports = True

+ [mypy-fedora.*]

+ ignore_missing_imports = True

+ [mypy-bodhi.*]

+ ignore_missing_imports = True

+ 

+ [tool:pytest]

+ minversion = 2.0

+ python_functions=test should

+ python_files=test_* testfunc_*

+ testpaths = testing

+ filterwarnings =

+     ignore:the imp module is deprecated in favour of importlib:DeprecationWarning:koji

+     ignore:defusedxml.cElementTree is deprecated, import from defusedxml.ElementTree instead:DeprecationWarning:openid

+     ignore:Importing 'itsdangerous.json' is deprecated and will be removed in ItsDangerous 2.1. Use Python's 'json' module instead.:DeprecationWarning:flask

+     ignore:'contextfunction' is renamed to 'pass_context', the old name will be removed in Jinja 3.1.:DeprecationWarning:flask_admin

+     ignore:The 'autoescape' extension is deprecated and will be removed in Jinja 3.1. This is built in now.:DeprecationWarning:jinja2

+     ignore:The 'with' extension is deprecated and will be removed in Jinja 3.1. This is built in now.:DeprecationWarning:jinja2

file modified
+2 -3
@@ -2,20 +2,19 @@ 

  import re

  import os

  

- from setuptools import setup, Command

+ from setuptools import setup, Command  # type: ignore

  

  here = os.path.abspath(os.path.dirname(__file__))

  

  

  class PyTest(Command):

-     user_options = []

+     user_options = []  # type: ignore

      def initialize_options(self):

          pass

      def finalize_options(self):

          pass

      def run(self):

          import subprocess

-         import sys

          errno = subprocess.call(['pytest-3'])

          raise SystemExit(errno)

  

file modified
+1 -5
@@ -1,11 +1,7 @@ 

  from datetime import datetime

  import json

  import copy

- # First, try to import modern http.client and support also old one on EPEL 7/Python 2

- try:

-     import http.client as httplib

- except ImportError:

-     import httplib

+ import http.client as httplib

  

  import mock

  

file modified
+1 -7
@@ -1,11 +1,6 @@ 

  import mock

  from datetime import datetime

- 

- # First, try to import modern xmlrpc.client and support also old one on EPEL 7/Python 2

- try:

-     from xmlrpc.client import Fault

- except ImportError:

-     from xmlrpclib import Fault

+ from xmlrpc.client import Fault

  

  import pytest

  
@@ -259,4 +254,3 @@ 

              test_bz.check_proposed_bug()

  

          assert excinfo.value.msg == 'bug does not exist'

- 

file modified
+1 -4
@@ -1,7 +1,5 @@ 

  import datetime

  

- from flask import render_template

- 

  from blockerbugs.models.milestone import Milestone

  from blockerbugs.models.release import Release

  from blockerbugs.models.bug import Bug
@@ -122,8 +120,7 @@ 

          db.drop_all()

          db.create_all()

          release = add_release(99)

-         final_milestone = add_milestone(release, 'final', 100, 101,

-                                         '99-final', True)

+         add_milestone(release, 'final', 100, 101, '99-final', True)

          add_milestone(release, 'beta', 200, 201, '99-beta')

  

      @classmethod

@@ -230,7 +230,7 @@ 

          assert update.pending

  

      def test_no_updates_for_bug(self):

-         bug1 = add_bug(4000, 'testbug1', self.test_milestone99alpha)

+         add_bug(4000, 'testbug1', self.test_milestone99alpha)

          self.update_sync.sync_updates(self.test_release99)

          updates = Update.query.all()

          assert len(updates) == 0

Please review per commit. These commits are included:

commit b9f69e6fdd2534f082fe4ffe53c4b9bc6eb6d047 (HEAD -> feature/linters, origin/feature/linters)
Author: Kamil Páral <kparal@redhat.com>
Date:   Wed Jun 30 10:35:58 2021 +0200

    fix some linter warnings

    This fixes all reported mypy warnings, and some flake8 warnings.


commit f5872809a4a5b72c8eda31209bd7f66ea16a0414
Author: Kamil Páral <kparal@redhat.com>
Date:   Wed Jun 30 10:10:39 2021 +0200

    install code linters (flake8, mypy)

    Add new requirements-development.txt, which installs code linters. These linters
    often have editor/IDE integration, or can be run manually. Update
    development.rst to include some guidance.

    Create setup.cfg to have a centralized tool configuration. Move pytest.ini
    content into setup.cfg. (Also add a few more deprecation warning filters for
    our libraries).

 delete mode 100644 pytest.ini
 create mode 100644 requirements-development.txt
 create mode 100644 setup.cfg

rebased onto 4d7f041

2 years ago

Build failed. More information on how to proceed and troubleshoot errors available at https://fedoraproject.org/wiki/Zuul-based-ci

rebased onto 702d7d4

2 years ago

Build failed. More information on how to proceed and troubleshoot errors available at https://fedoraproject.org/wiki/Zuul-based-ci

Build succeeded.

Merged into develop as e537250..2db58aa

Pull-Request has been closed by kparal

2 years ago