cosmetic changes & make linter happy

renamed single letter variables (m => match)
renamed classmethod arguments to cls (model)
removed shadowing of variables (hash, context)
shortened unneeded lambda functions (id)
converted type ... is to isinstance(...)
removed unneded imports (flask)
This commit is contained in:
Alexander Graf
2021-01-06 16:45:55 +01:00
parent 7229c89de1
commit 4c258f5a6b
2 changed files with 69 additions and 71 deletions

View File

@@ -1,9 +1,8 @@
from mailu import models
from flask import current_app as app
from flask import cli as flask_cli
from flask.cli import FlaskGroup, with_appcontext
import flask
import os
import socket
import uuid
@@ -15,14 +14,14 @@ import sys
db = models.db
@click.group()
def mailu(cls=flask_cli.FlaskGroup):
@click.group(cls=FlaskGroup)
def mailu():
""" Mailu command line
"""
@mailu.command()
@flask_cli.with_appcontext
@with_appcontext
def advertise():
""" Advertise this server against statistic services.
"""
@@ -45,7 +44,7 @@ def advertise():
@click.argument('domain_name')
@click.argument('password')
@click.option('-m', '--mode')
@flask_cli.with_appcontext
@with_appcontext
def admin(localpart, domain_name, password, mode='create'):
""" Create an admin user
'mode' can be:
@@ -89,7 +88,7 @@ def admin(localpart, domain_name, password, mode='create'):
@click.argument('domain_name')
@click.argument('password')
@click.argument('hash_scheme', required=False)
@flask_cli.with_appcontext
@with_appcontext
def user(localpart, domain_name, password, hash_scheme=None):
""" Create a user
"""
@@ -114,7 +113,7 @@ def user(localpart, domain_name, password, hash_scheme=None):
@click.argument('domain_name')
@click.argument('password')
@click.argument('hash_scheme', required=False)
@flask_cli.with_appcontext
@with_appcontext
def password(localpart, domain_name, password, hash_scheme=None):
""" Change the password of an user
"""
@@ -134,7 +133,7 @@ def password(localpart, domain_name, password, hash_scheme=None):
@click.option('-u', '--max-users')
@click.option('-a', '--max-aliases')
@click.option('-q', '--max-quota-bytes')
@flask_cli.with_appcontext
@with_appcontext
def domain(domain_name, max_users=-1, max_aliases=-1, max_quota_bytes=0):
""" Create a domain
"""
@@ -151,7 +150,7 @@ def domain(domain_name, max_users=-1, max_aliases=-1, max_quota_bytes=0):
@click.argument('domain_name')
@click.argument('password_hash')
@click.argument('hash_scheme')
@flask_cli.with_appcontext
@with_appcontext
def user_import(localpart, domain_name, password_hash, hash_scheme = None):
""" Import a user along with password hash.
"""
@@ -183,7 +182,7 @@ yaml_sections = [
@click.option('-v', '--verbose', is_flag=True, help='Increase verbosity')
@click.option('-d', '--delete-objects', is_flag=True, help='Remove objects not included in yaml')
@click.option('-n', '--dry-run', is_flag=True, help='Perform a trial run with no changes made')
@flask_cli.with_appcontext
@with_appcontext
def config_update(verbose=False, delete_objects=False, dry_run=False, file=None):
"""sync configuration with data from YAML-formatted stdin"""
@@ -303,7 +302,7 @@ def config_update(verbose=False, delete_objects=False, dry_run=False, file=None)
@click.option('-s', '--secrets', is_flag=True, help='Include secrets (dkim-key, plain-text / not hashed)')
@click.option('-d', '--dns', is_flag=True, help='Include dns records')
@click.argument('sections', nargs=-1)
@flask_cli.with_appcontext
@with_appcontext
def config_dump(full=False, secrets=False, dns=False, sections=None):
"""dump configuration as YAML-formatted data to stdout
@@ -343,7 +342,7 @@ def config_dump(full=False, secrets=False, dns=False, sections=None):
@mailu.command()
@click.argument('email')
@flask_cli.with_appcontext
@with_appcontext
def user_delete(email):
"""delete user"""
user = models.User.query.get(email)
@@ -354,7 +353,7 @@ def user_delete(email):
@mailu.command()
@click.argument('email')
@flask_cli.with_appcontext
@with_appcontext
def alias_delete(email):
"""delete alias"""
alias = models.Alias.query.get(email)
@@ -368,7 +367,7 @@ def alias_delete(email):
@click.argument('domain_name')
@click.argument('destination')
@click.option('-w', '--wildcard', is_flag=True)
@flask_cli.with_appcontext
@with_appcontext
def alias(localpart, domain_name, destination, wildcard=False):
""" Create an alias
"""
@@ -392,7 +391,7 @@ def alias(localpart, domain_name, destination, wildcard=False):
@click.argument('max_users')
@click.argument('max_aliases')
@click.argument('max_quota_bytes')
@flask_cli.with_appcontext
@with_appcontext
def setlimits(domain_name, max_users, max_aliases, max_quota_bytes):
""" Set domain limits
"""
@@ -407,7 +406,7 @@ def setlimits(domain_name, max_users, max_aliases, max_quota_bytes):
@mailu.command()
@click.argument('domain_name')
@click.argument('user_name')
@flask_cli.with_appcontext
@with_appcontext
def setmanager(domain_name, user_name='manager'):
""" Make a user manager of a domain
"""

View File

@@ -1,7 +1,6 @@
from mailu import dkim
from sqlalchemy.ext import declarative
from passlib import context, hash
from datetime import datetime, date
from email.mime import text
from flask import current_app as app
@@ -12,6 +11,7 @@ import sqlalchemy
import re
import time
import os
import passlib
import glob
import smtplib
import idna
@@ -113,8 +113,8 @@ class Base(db.Model):
comment = db.Column(db.String(255), nullable=True)
@classmethod
def _dict_pkey(model):
return model.__mapper__.primary_key[0].name
def _dict_pkey(cls):
return cls.__mapper__.primary_key[0].name
def _dict_pval(self):
return getattr(self, self._dict_pkey())
@@ -187,57 +187,57 @@ class Base(db.Model):
return res
@classmethod
def from_dict(model, data, delete=False):
def from_dict(cls, data, delete=False):
changed = []
pkey = model._dict_pkey()
pkey = cls._dict_pkey()
# handle "primary key" only
if type(data) is not dict:
if isinstance(data, dict):
data = {pkey: data}
# modify input data
if hasattr(model, '_dict_input'):
if hasattr(cls, '_dict_input'):
try:
model._dict_input(data)
cls._dict_input(data)
except Exception as reason:
raise ValueError(f'{reason}', model, None, data)
raise ValueError(f'{reason}', cls, None, data)
# check for primary key (if not recursed)
if not getattr(model, '_dict_recurse', False):
if not getattr(cls, '_dict_recurse', False):
if not pkey in data:
raise KeyError(f'primary key {model.__table__}.{pkey} is missing', model, pkey, data)
raise KeyError(f'primary key {cls.__table__}.{pkey} is missing', cls, pkey, data)
# check data keys and values
for key in list(data.keys()):
# check key
if not hasattr(model, key) and not key in model.__mapper__.relationships:
raise KeyError(f'unknown key {model.__table__}.{key}', model, key, data)
if not hasattr(cls, key) and not key in cls.__mapper__.relationships:
raise KeyError(f'unknown key {cls.__table__}.{key}', cls, key, data)
# check value type
value = data[key]
col = model.__mapper__.columns.get(key)
col = cls.__mapper__.columns.get(key)
if col is not None:
if not ((value is None and col.nullable) or (type(value) is col.type.python_type)):
raise TypeError(f'{model.__table__}.{key} {value!r} has invalid type {type(value).__name__!r}', model, key, data)
if not ((value is None and col.nullable) or (isinstance(value, col.type.python_type))):
raise TypeError(f'{cls.__table__}.{key} {value!r} has invalid type {type(value).__name__!r}', cls, key, data)
else:
rel = model.__mapper__.relationships.get(key)
rel = cls.__mapper__.relationships.get(key)
if rel is None:
itype = getattr(model, '_dict_types', {}).get(key)
itype = getattr(cls, '_dict_types', {}).get(key)
if itype is not None:
if itype is False: # ignore value. TODO: emit warning?
del data[key]
continue
elif not isinstance(value, itype):
raise TypeError(f'{model.__table__}.{key} {value!r} has invalid type {type(value).__name__!r}', model, key, data)
raise TypeError(f'{cls.__table__}.{key} {value!r} has invalid type {type(value).__name__!r}', cls, key, data)
else:
raise NotImplementedError(f'type not defined for {model.__table__}.{key}')
raise NotImplementedError(f'type not defined for {cls.__table__}.{key}')
# handle relationships
if key in model.__mapper__.relationships:
rel_model = model.__mapper__.relationships[key].argument
if key in cls.__mapper__.relationships:
rel_model = cls.__mapper__.relationships[key].argument
if not isinstance(rel_model, sqlalchemy.orm.Mapper):
add = rel_model.from_dict(value, delete)
assert len(add) == 1
@@ -247,24 +247,24 @@ class Base(db.Model):
# create item if necessary
created = False
item = model.query.get(data[pkey]) if pkey in data else None
item = cls.query.get(data[pkey]) if pkey in data else None
if item is None:
# check for mandatory keys
missing = getattr(model, '_dict_mandatory', set()) - set(data.keys())
missing = getattr(cls, '_dict_mandatory', set()) - set(data.keys())
if missing:
raise ValueError(f'mandatory key(s) {", ".join(sorted(missing))} for {model.__table__} missing', model, missing, data)
raise ValueError(f'mandatory key(s) {", ".join(sorted(missing))} for {cls.__table__} missing', cls, missing, data)
# remove mapped relationships from data
mapped = {}
for key in list(data.keys()):
if key in model.__mapper__.relationships:
if isinstance(model.__mapper__.relationships[key].argument, sqlalchemy.orm.Mapper):
if key in cls.__mapper__.relationships:
if isinstance(cls.__mapper__.relationships[key].argument, sqlalchemy.orm.Mapper):
mapped[key] = data[key]
del data[key]
# create new item
item = model(**data)
item = cls(**data)
created = True
# and update mapped relationships (below)
@@ -278,14 +278,14 @@ class Base(db.Model):
if key == pkey:
continue
if key in model.__mapper__.relationships:
if key in cls.__mapper__.relationships:
# update relationship
rel_model = model.__mapper__.relationships[key].argument
rel_model = cls.__mapper__.relationships[key].argument
if isinstance(rel_model, sqlalchemy.orm.Mapper):
rel_model = rel_model.class_
# add (and create) referenced items
cur = getattr(item, key)
old = sorted(cur, key=lambda i:id(i))
old = sorted(cur, key=id)
new = []
for rel_data in value:
# get or create related item
@@ -331,16 +331,16 @@ class Base(db.Model):
break
# remember changes
new = sorted(new, key=lambda i:id(i))
new = sorted(new, key=id)
if new != old:
updated.append((key, old, new))
else:
# update key
old = getattr(item, key)
if type(old) is list:
if isinstance(old, list):
# deduplicate list value
assert type(value) is list
assert isinstance(value, list)
value = set(value)
old = set(old)
if not delete:
@@ -408,19 +408,19 @@ class Domain(Base):
if 'dkim_key' in data:
key = data['dkim_key']
if key is not None:
if type(key) is list:
if isinstance(key, list):
key = ''.join(key)
if type(key) is str:
if isinstance(key, str):
key = ''.join(key.strip().split()) # removes all whitespace
if key == 'generate':
data['dkim_key'] = dkim.gen_key()
elif key:
m = re.match('^-----BEGIN (RSA )?PRIVATE KEY-----', key)
if m is not None:
key = key[m.end():]
m = re.search('-----END (RSA )?PRIVATE KEY-----$', key)
if m is not None:
key = key[:m.start()]
match = re.match('^-----BEGIN (RSA )?PRIVATE KEY-----', key)
if match is not None:
key = key[match.end():]
match = re.search('-----END (RSA )?PRIVATE KEY-----$', key)
if match is not None:
key = key[:match.start()]
key = '\n'.join(wrap(key, 64))
key = f'-----BEGIN PRIVATE KEY-----\n{key}\n-----END PRIVATE KEY-----\n'.encode('ascii')
try:
@@ -428,7 +428,7 @@ class Domain(Base):
except:
raise ValueError('invalid dkim key')
else:
data['dkim_key'] = key
data['dkim_key'] = key
else:
data['dkim_key'] = None
@@ -505,8 +505,7 @@ class Domain(Base):
for email in self.users + self.aliases:
if email.localpart == localpart:
return True
else:
return False
return False
def check_mx(self):
try:
@@ -519,7 +518,7 @@ class Domain(Base):
return False
def __str__(self):
return self.name
return str(self.name)
def __eq__(self, other):
try:
@@ -541,7 +540,7 @@ class Alternative(Base):
backref=db.backref('alternatives', cascade='all, delete-orphan'))
def __str__(self):
return self.name
return str(self.name)
class Relay(Base):
@@ -557,7 +556,7 @@ class Relay(Base):
smtp = db.Column(db.String(80), nullable=True)
def __str__(self):
return self.name
return str(self.name)
class Email(object):
@@ -571,7 +570,7 @@ class Email(object):
if 'email' in data:
if 'localpart' in data or 'domain' in data:
raise ValueError('ambigous key email and localpart/domain')
elif type(data['email']) is str:
elif isinstance(data['email'], str):
data['localpart'], data['domain'] = data['email'].rsplit('@', 1)
else:
data['email'] = f'{data["localpart"]}@{data["domain"]}'
@@ -653,7 +652,7 @@ class Email(object):
return pure_alias.destination
def __str__(self):
return self.email
return str(self.email)
class User(Base, Email):
@@ -750,7 +749,7 @@ class User(Base, Email):
'CRYPT': 'des_crypt'}
def get_password_context(self):
return context.CryptContext(
return passlib.context.CryptContext(
schemes=self.scheme_dict.values(),
default=self.scheme_dict[app.config['PASSWORD_SCHEME']],
)
@@ -818,7 +817,7 @@ class Alias(Base, Email):
Email._dict_input(data)
# handle comma delimited string for backwards compability
dst = data.get('destination')
if type(dst) is str:
if isinstance(dst, str):
data['destination'] = list([adr.strip() for adr in dst.split(',')])
domain = db.relationship(Domain,
@@ -888,10 +887,10 @@ class Token(Base):
ip = db.Column(db.String(255))
def check_password(self, password):
return hash.sha256_crypt.verify(password, self.password)
return passlib.hash.sha256_crypt.verify(password, self.password)
def set_password(self, password):
self.password = hash.sha256_crypt.using(rounds=1000).hash(password)
self.password = passlib.hash.sha256_crypt.using(rounds=1000).hash(password)
def __str__(self):
return self.comment or self.ip