Fix pylinters
Change-Id: I3b17d87f821cadab39b9bf4e82043e79f20456ef
This commit is contained in:
parent
26719e3c48
commit
ebbb899953
@ -19,15 +19,24 @@
|
|||||||
parent: vexxhost-promote-docker-image
|
parent: vexxhost-promote-docker-image
|
||||||
vars: *atmosphere_images
|
vars: *atmosphere_images
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: atmosphere:linters:tox
|
||||||
|
parent: tox-linters
|
||||||
|
vars:
|
||||||
|
python_version: 3.7
|
||||||
|
|
||||||
- project:
|
- project:
|
||||||
check:
|
check:
|
||||||
jobs:
|
jobs:
|
||||||
|
- atmosphere:linters:tox
|
||||||
- tox-py37
|
- tox-py37
|
||||||
- atmosphere:image:build
|
- atmosphere:image:build
|
||||||
gate:
|
gate:
|
||||||
jobs:
|
jobs:
|
||||||
|
- atmosphere:linters:tox
|
||||||
- tox-py37
|
- tox-py37
|
||||||
- atmosphere:image:upload
|
- atmosphere:image:upload
|
||||||
promote:
|
promote:
|
||||||
jobs:
|
jobs:
|
||||||
|
- atmosphere:linters:tox
|
||||||
- atmosphere:image:promote
|
- atmosphere:image:promote
|
||||||
|
@ -12,11 +12,14 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Ingress
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask import abort
|
from flask import abort
|
||||||
from flask import jsonify
|
from flask import jsonify
|
||||||
from dateutil.relativedelta import relativedelta
|
|
||||||
|
|
||||||
from atmosphere.app import create_app
|
from atmosphere.app import create_app
|
||||||
from atmosphere import exceptions
|
from atmosphere import exceptions
|
||||||
@ -27,6 +30,7 @@ blueprint = Blueprint('ingress', __name__)
|
|||||||
|
|
||||||
|
|
||||||
def init_application(config=None):
|
def init_application(config=None):
|
||||||
|
"""init_application"""
|
||||||
app = create_app(config)
|
app = create_app(config)
|
||||||
app.register_blueprint(blueprint)
|
app.register_blueprint(blueprint)
|
||||||
return app
|
return app
|
||||||
@ -34,20 +38,17 @@ def init_application(config=None):
|
|||||||
|
|
||||||
@blueprint.route('/v1/event', methods=['POST'])
|
@blueprint.route('/v1/event', methods=['POST'])
|
||||||
def event():
|
def event():
|
||||||
|
"""event"""
|
||||||
if request.json is None:
|
if request.json is None:
|
||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
for event in request.json:
|
for event_data in request.json:
|
||||||
print(jsonify(event).get_data(True))
|
print(jsonify(event_data).get_data(True))
|
||||||
event = utils.normalize_event(event)
|
event_data = utils.normalize_event(event_data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resource = models.Resource.get_or_create(event)
|
models.Resource.get_or_create(event_data)
|
||||||
except (exceptions.EventTooOld, exceptions.IgnoredEvent):
|
except (exceptions.EventTooOld, exceptions.IgnoredEvent):
|
||||||
return '', 202
|
return '', 202
|
||||||
|
|
||||||
# TODO(mnaser): Drop this logging eventually...
|
|
||||||
print(jsonify(event).get_data(True))
|
|
||||||
print(jsonify(resource.serialize).get_data(True))
|
|
||||||
|
|
||||||
return '', 204
|
return '', 204
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""App
|
||||||
|
|
||||||
|
"""
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
@ -20,6 +23,7 @@ from atmosphere import models
|
|||||||
|
|
||||||
|
|
||||||
def create_app(config=None):
|
def create_app(config=None):
|
||||||
|
"""create_app"""
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
if config is not None:
|
if config is not None:
|
||||||
@ -39,5 +43,3 @@ def create_app(config=None):
|
|||||||
models.migrate.init_app(app, models.db, directory=migrations_path)
|
models.migrate.init_app(app, models.db, directory=migrations_path)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,20 +12,27 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Exceptions
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
from werkzeug import exceptions
|
from werkzeug import exceptions
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedEventType(exceptions.BadRequest):
|
class UnsupportedEventType(exceptions.BadRequest):
|
||||||
|
"""UnsupportedEventType"""
|
||||||
description = 'Unsupported event type'
|
description = 'Unsupported event type'
|
||||||
|
|
||||||
|
|
||||||
class MultipleOpenPeriods(exceptions.Conflict):
|
class MultipleOpenPeriods(exceptions.Conflict):
|
||||||
|
"""MultipleOpenPeriods"""
|
||||||
description = 'Multiple open periods'
|
description = 'Multiple open periods'
|
||||||
|
|
||||||
|
|
||||||
class IgnoredEvent(Exception):
|
class IgnoredEvent(Exception):
|
||||||
|
"""IgnoredEvent"""
|
||||||
description = 'Ignored event type'
|
description = 'Ignored event type'
|
||||||
|
|
||||||
|
|
||||||
class EventTooOld(Exception):
|
class EventTooOld(Exception):
|
||||||
pass
|
"""EventTooOld"""
|
||||||
|
@ -12,18 +12,23 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Models
|
||||||
|
|
||||||
|
"""
|
||||||
|
# pylint: disable=R0903
|
||||||
|
# pylint: disable=W0223
|
||||||
|
# pylint: disable=no-member
|
||||||
|
# pylint: disable=not-an-iterable
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from sqlalchemy import func
|
|
||||||
from sqlalchemy import exc
|
from sqlalchemy import exc
|
||||||
from sqlalchemy.orm import exc as orm_exc
|
from sqlalchemy.orm import exc as orm_exc
|
||||||
from dateutil.relativedelta import relativedelta
|
|
||||||
from sqlalchemy.types import TypeDecorator
|
from sqlalchemy.types import TypeDecorator
|
||||||
|
|
||||||
from atmosphere import exceptions
|
from atmosphere import exceptions
|
||||||
from atmosphere import utils
|
|
||||||
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
migrate = Migrate()
|
migrate = Migrate()
|
||||||
@ -32,11 +37,44 @@ migrate = Migrate()
|
|||||||
MONTH_START = relativedelta(day=1, hour=0, minute=0, second=0, microsecond=0)
|
MONTH_START = relativedelta(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
|
def get_model_type_from_event(event):
|
||||||
|
"""get_model_type_from_event"""
|
||||||
|
if event.startswith('compute.instance'):
|
||||||
|
return Instance, InstanceSpec
|
||||||
|
if event.startswith('aggregate.'):
|
||||||
|
raise exceptions.IgnoredEvent
|
||||||
|
if event.startswith('compute_task.'):
|
||||||
|
raise exceptions.IgnoredEvent
|
||||||
|
if event.startswith('compute.'):
|
||||||
|
raise exceptions.IgnoredEvent
|
||||||
|
if event.startswith('flavor.'):
|
||||||
|
raise exceptions.IgnoredEvent
|
||||||
|
if event.startswith('keypair.'):
|
||||||
|
raise exceptions.IgnoredEvent
|
||||||
|
if event.startswith('libvirt.'):
|
||||||
|
raise exceptions.IgnoredEvent
|
||||||
|
if event.startswith('metrics.'):
|
||||||
|
raise exceptions.IgnoredEvent
|
||||||
|
if event.startswith('scheduler.'):
|
||||||
|
raise exceptions.IgnoredEvent
|
||||||
|
if event.startswith('server_group.'):
|
||||||
|
raise exceptions.IgnoredEvent
|
||||||
|
if event.startswith('service.'):
|
||||||
|
raise exceptions.IgnoredEvent
|
||||||
|
if event == 'volume.usage':
|
||||||
|
raise exceptions.IgnoredEvent
|
||||||
|
|
||||||
|
raise exceptions.UnsupportedEventType
|
||||||
|
|
||||||
|
|
||||||
class GetOrCreateMixin:
|
class GetOrCreateMixin:
|
||||||
|
"""GetOrCreateMixin"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_or_create(self, event):
|
def get_or_create(cls, event):
|
||||||
query = self.query_from_event(event)
|
"""get_or_create"""
|
||||||
new_instance = self.from_event(event)
|
query = cls.query_from_event(event)
|
||||||
|
new_instance = cls.from_event(event)
|
||||||
|
|
||||||
db_instance = query.first()
|
db_instance = query.first()
|
||||||
if db_instance is None:
|
if db_instance is None:
|
||||||
@ -54,6 +92,8 @@ class GetOrCreateMixin:
|
|||||||
|
|
||||||
|
|
||||||
class Resource(db.Model, GetOrCreateMixin):
|
class Resource(db.Model, GetOrCreateMixin):
|
||||||
|
"""Resource"""
|
||||||
|
|
||||||
uuid = db.Column(db.String(36), primary_key=True)
|
uuid = db.Column(db.String(36), primary_key=True)
|
||||||
type = db.Column(db.String(32), nullable=False)
|
type = db.Column(db.String(32), nullable=False)
|
||||||
project = db.Column(db.String(32), nullable=False)
|
project = db.Column(db.String(32), nullable=False)
|
||||||
@ -66,8 +106,9 @@ class Resource(db.Model, GetOrCreateMixin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_event(self, event):
|
def from_event(cls, event):
|
||||||
cls, _ = utils.get_model_type_from_event(event['event_type'])
|
"""from_event"""
|
||||||
|
cls, _ = get_model_type_from_event(event['event_type'])
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
uuid=event['traits']['resource_id'],
|
uuid=event['traits']['resource_id'],
|
||||||
@ -76,8 +117,9 @@ class Resource(db.Model, GetOrCreateMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def query_from_event(self, event):
|
def query_from_event(cls, event):
|
||||||
cls, _ = utils.get_model_type_from_event(event['event_type'])
|
"""query_from_event"""
|
||||||
|
cls, _ = get_model_type_from_event(event['event_type'])
|
||||||
|
|
||||||
return cls.query.filter_by(
|
return cls.query.filter_by(
|
||||||
uuid=event['traits']['resource_id'],
|
uuid=event['traits']['resource_id'],
|
||||||
@ -85,8 +127,9 @@ class Resource(db.Model, GetOrCreateMixin):
|
|||||||
).with_for_update()
|
).with_for_update()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_or_create(self, event):
|
def get_or_create(cls, event):
|
||||||
resource = super(Resource, self).get_or_create(event)
|
"""get_or_create"""
|
||||||
|
resource = super(Resource, cls).get_or_create(event)
|
||||||
|
|
||||||
# If the last update is newer than our last update, we assume that
|
# If the last update is newer than our last update, we assume that
|
||||||
# another event has been processed that is newer (so we should ignore
|
# another event has been processed that is newer (so we should ignore
|
||||||
@ -137,6 +180,7 @@ class Resource(db.Model, GetOrCreateMixin):
|
|||||||
return resource
|
return resource
|
||||||
|
|
||||||
def get_open_period(self):
|
def get_open_period(self):
|
||||||
|
"""get_open_period"""
|
||||||
open_periods = list(filter(lambda p: p.ended_at is None, self.periods))
|
open_periods = list(filter(lambda p: p.ended_at is None, self.periods))
|
||||||
if len(open_periods) > 1:
|
if len(open_periods) > 1:
|
||||||
raise exceptions.MultipleOpenPeriods
|
raise exceptions.MultipleOpenPeriods
|
||||||
@ -158,12 +202,15 @@ class Resource(db.Model, GetOrCreateMixin):
|
|||||||
|
|
||||||
|
|
||||||
class Instance(Resource):
|
class Instance(Resource):
|
||||||
|
"""Instance"""
|
||||||
|
|
||||||
__mapper_args__ = {
|
__mapper_args__ = {
|
||||||
'polymorphic_identity': 'OS::Nova::Server'
|
'polymorphic_identity': 'OS::Nova::Server'
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_event_ignored(self, event):
|
def is_event_ignored(cls, event):
|
||||||
|
"""is_event_ignored"""
|
||||||
vm_state_is_deleted = (event['traits']['state'] == 'deleted')
|
vm_state_is_deleted = (event['traits']['state'] == 'deleted')
|
||||||
no_deleted_at = ('deleted_at' not in event['traits'])
|
no_deleted_at = ('deleted_at' not in event['traits'])
|
||||||
|
|
||||||
@ -174,21 +221,27 @@ class Instance(Resource):
|
|||||||
|
|
||||||
|
|
||||||
class BigIntegerDateTime(TypeDecorator):
|
class BigIntegerDateTime(TypeDecorator):
|
||||||
|
"""BigIntegerDateTime"""
|
||||||
|
|
||||||
impl = db.BigInteger
|
impl = db.BigInteger
|
||||||
|
|
||||||
def process_bind_param(self, value, _):
|
def process_bind_param(self, value, _):
|
||||||
|
"""process_bind_param"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
assert isinstance(value, datetime)
|
assert isinstance(value, datetime)
|
||||||
return value.timestamp() * 1000
|
return value.timestamp() * 1000
|
||||||
|
|
||||||
def process_result_value(self, value, _):
|
def process_result_value(self, value, _):
|
||||||
|
"""process_result_value"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
return datetime.fromtimestamp(value / 1000)
|
return datetime.fromtimestamp(value / 1000)
|
||||||
|
|
||||||
|
|
||||||
class Period(db.Model):
|
class Period(db.Model):
|
||||||
|
"""Period"""
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
resource_uuid = db.Column(db.String(36), db.ForeignKey('resource.uuid'),
|
resource_uuid = db.Column(db.String(36), db.ForeignKey('resource.uuid'),
|
||||||
nullable=False)
|
nullable=False)
|
||||||
@ -200,6 +253,7 @@ class Period(db.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def seconds(self):
|
def seconds(self):
|
||||||
|
"""seconds"""
|
||||||
ended_at = self.ended_at
|
ended_at = self.ended_at
|
||||||
if ended_at is None:
|
if ended_at is None:
|
||||||
ended_at = datetime.now()
|
ended_at = datetime.now()
|
||||||
@ -218,6 +272,8 @@ class Period(db.Model):
|
|||||||
|
|
||||||
|
|
||||||
class Spec(db.Model, GetOrCreateMixin):
|
class Spec(db.Model, GetOrCreateMixin):
|
||||||
|
"""Spec"""
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
type = db.Column(db.String(32))
|
type = db.Column(db.String(32))
|
||||||
|
|
||||||
@ -226,16 +282,18 @@ class Spec(db.Model, GetOrCreateMixin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_event(self, event):
|
def from_event(cls, event):
|
||||||
_, cls = utils.get_model_type_from_event(event['event_type'])
|
"""from_event"""
|
||||||
|
_, cls = get_model_type_from_event(event['event_type'])
|
||||||
spec = {c.name: event['traits'][c.name]
|
spec = {c.name: event['traits'][c.name]
|
||||||
for c in cls.__table__.columns if c.name != 'id'}
|
for c in cls.__table__.columns if c.name != 'id'}
|
||||||
|
|
||||||
return cls(**spec)
|
return cls(**spec)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def query_from_event(self, event):
|
def query_from_event(cls, event):
|
||||||
_, cls = utils.get_model_type_from_event(event['event_type'])
|
"""query_from_event"""
|
||||||
|
_, cls = get_model_type_from_event(event['event_type'])
|
||||||
spec = {c.name: event['traits'][c.name]
|
spec = {c.name: event['traits'][c.name]
|
||||||
for c in cls.__table__.columns if c.name != 'id'}
|
for c in cls.__table__.columns if c.name != 'id'}
|
||||||
|
|
||||||
@ -243,6 +301,8 @@ class Spec(db.Model, GetOrCreateMixin):
|
|||||||
|
|
||||||
|
|
||||||
class InstanceSpec(Spec):
|
class InstanceSpec(Spec):
|
||||||
|
"""InstanceSpec"""
|
||||||
|
|
||||||
id = db.Column(db.Integer, db.ForeignKey('spec.id'), primary_key=True)
|
id = db.Column(db.Integer, db.ForeignKey('spec.id'), primary_key=True)
|
||||||
instance_type = db.Column(db.String(255))
|
instance_type = db.Column(db.String(255))
|
||||||
state = db.Column(db.String(255))
|
state = db.Column(db.String(255))
|
||||||
|
@ -44,18 +44,18 @@ class TestNormalizeEvent:
|
|||||||
|
|
||||||
class TestModelTypeDetection:
|
class TestModelTypeDetection:
|
||||||
def test_compute_instance(self):
|
def test_compute_instance(self):
|
||||||
assert utils.get_model_type_from_event('compute.instance.exists') == \
|
assert models.get_model_type_from_event('compute.instance.exists') == \
|
||||||
(models.Instance, models.InstanceSpec)
|
(models.Instance, models.InstanceSpec)
|
||||||
|
|
||||||
def test_ignored_resource(self, ignored_event):
|
def test_ignored_resource(self, ignored_event):
|
||||||
with pytest.raises(exceptions.IgnoredEvent) as e:
|
with pytest.raises(exceptions.IgnoredEvent) as e:
|
||||||
utils.get_model_type_from_event(ignored_event)
|
models.get_model_type_from_event(ignored_event)
|
||||||
|
|
||||||
assert e.value.description == "Ignored event type"
|
assert e.value.description == "Ignored event type"
|
||||||
|
|
||||||
def test_unknown_resource(self):
|
def test_unknown_resource(self):
|
||||||
with pytest.raises(exceptions.UnsupportedEventType) as e:
|
with pytest.raises(exceptions.UnsupportedEventType) as e:
|
||||||
utils.get_model_type_from_event('foobar')
|
models.get_model_type_from_event('foobar')
|
||||||
|
|
||||||
assert e.value.code == 400
|
assert e.value.code == 400
|
||||||
assert e.value.description == "Unsupported event type"
|
assert e.value.description == "Unsupported event type"
|
||||||
|
@ -12,14 +12,16 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Utils
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
from ceilometer.event import models as ceilometer_models
|
from ceilometer.event import models as ceilometer_models
|
||||||
from dateutil import parser
|
from dateutil import parser
|
||||||
|
|
||||||
from atmosphere import exceptions
|
|
||||||
from atmosphere import models
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_event(event):
|
def normalize_event(event):
|
||||||
|
"""normalize_event"""
|
||||||
event['generated'] = parser.parse(event['generated'])
|
event['generated'] = parser.parse(event['generated'])
|
||||||
event['traits'] = {
|
event['traits'] = {
|
||||||
k: ceilometer_models.Trait.convert_value(t, v)
|
k: ceilometer_models.Trait.convert_value(t, v)
|
||||||
@ -27,34 +29,3 @@ def normalize_event(event):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
|
||||||
def get_model_type_from_event(event):
|
|
||||||
if event.startswith('compute.instance'):
|
|
||||||
return models.Instance, models.InstanceSpec
|
|
||||||
if event.startswith('aggregate.'):
|
|
||||||
raise exceptions.IgnoredEvent
|
|
||||||
if event.startswith('compute_task.'):
|
|
||||||
raise exceptions.IgnoredEvent
|
|
||||||
if event.startswith('compute.'):
|
|
||||||
raise exceptions.IgnoredEvent
|
|
||||||
if event.startswith('flavor.'):
|
|
||||||
raise exceptions.IgnoredEvent
|
|
||||||
if event.startswith('keypair.'):
|
|
||||||
raise exceptions.IgnoredEvent
|
|
||||||
if event.startswith('libvirt.'):
|
|
||||||
raise exceptions.IgnoredEvent
|
|
||||||
if event.startswith('metrics.'):
|
|
||||||
raise exceptions.IgnoredEvent
|
|
||||||
if event.startswith('scheduler.'):
|
|
||||||
raise exceptions.IgnoredEvent
|
|
||||||
if event.startswith('server_group.'):
|
|
||||||
raise exceptions.IgnoredEvent
|
|
||||||
if event.startswith('service.'):
|
|
||||||
raise exceptions.IgnoredEvent
|
|
||||||
if event == 'volume.usage':
|
|
||||||
raise exceptions.IgnoredEvent
|
|
||||||
|
|
||||||
raise exceptions.UnsupportedEventType
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@ before_after
|
|||||||
flake8
|
flake8
|
||||||
freezegun
|
freezegun
|
||||||
pylint
|
pylint
|
||||||
|
pylint-flask
|
||||||
|
pylint-flask-sqlalchemy
|
||||||
pytest
|
pytest
|
||||||
pytest-cov
|
pytest-cov
|
||||||
pytest-flask
|
pytest-flask
|
||||||
|
7
tox.ini
7
tox.ini
@ -21,8 +21,11 @@ commands = {posargs}
|
|||||||
|
|
||||||
[testenv:linters]
|
[testenv:linters]
|
||||||
commands =
|
commands =
|
||||||
pylint atmosphere
|
pylint atmosphere \
|
||||||
flake8 atmosphere
|
--load-plugins pylint_flask,pylint_flask_sqlalchemy \
|
||||||
|
--ignore migrations,tests
|
||||||
|
flake8 atmosphere \
|
||||||
|
--exclude .tox,atmosphere/migrations,atmosphere/tests
|
||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
deps =
|
deps =
|
||||||
|
Loading…
Reference in New Issue
Block a user