Passed
Push — main ( ebd94b...5f90c7 )
by Jochen
05:14
created

create_user()   C

Complexity

Conditions 7

Size

Total Lines 77
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 7.2944

Importance

Changes 0
Metric Value
cc 7
eloc 59
nop 17
dl 0
loc 77
rs 6.9418
c 0
b 0
f 0
ccs 27
cts 33
cp 0.8182
crap 7.2944

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
"""
2
byceps.services.user.creation_service
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2006-2021 Jochen Kupperschmidt
6
:License: Revised BSD (see `LICENSE` file for details)
7
"""
8
9 1
from __future__ import annotations
10 1
from datetime import date, datetime
11 1
from typing import Any, Optional
12
13 1
from flask import current_app
14
15 1
from ...database import db
16 1
from ...events.user import UserAccountCreated
17 1
from ...typing import UserID
18
19 1
from ..authentication.password import service as password_service
20 1
from ..site.transfer.models import SiteID
21
22 1
from . import email_address_verification_service
23 1
from . import event_service
24 1
from .dbmodels.detail import UserDetail as DbUserDetail
25 1
from .dbmodels.user import User as DbUser
26 1
from . import service as user_service
27 1
from .transfer.models import User
28
29
30 1
class UserCreationFailed(Exception):
31 1
    pass
32
33
34 1
def create_user(
35
    screen_name: Optional[str],
36
    email_address: Optional[str],
37
    password: str,
38
    *,
39
    legacy_id: Optional[str] = None,
40
    first_names: Optional[str] = None,
41
    last_name: Optional[str] = None,
42
    date_of_birth: Optional[date] = None,
43
    country: Optional[str] = None,
44
    zip_code: Optional[str] = None,
45
    city: Optional[str] = None,
46
    street: Optional[str] = None,
47
    phone_number: Optional[str] = None,
48
    internal_comment: Optional[str] = None,
49
    extras: Optional[dict[str, Any]] = None,
50
    creator_id: Optional[UserID] = None,
51
    site_id: Optional[SiteID] = None,
52
) -> tuple[User, UserAccountCreated]:
53
    """Create a user account and related records."""
54
    creator: Optional[User]
55 1
    if creator_id is not None:
56
        creator = user_service.get_user(creator_id)
57
    else:
58 1
        creator = None
59
60 1
    created_at = datetime.utcnow()
61
62 1
    db_user = build_db_user(
63
        created_at, screen_name, email_address, legacy_id=legacy_id
64
    )
65
66 1
    db_user.detail.first_names = first_names
67 1
    db_user.detail.last_name = last_name
68 1
    db_user.detail.date_of_birth = date_of_birth
69 1
    db_user.detail.country = country
70 1
    db_user.detail.zip_code = zip_code
71 1
    db_user.detail.city = city
72 1
    db_user.detail.street = street
73 1
    db_user.detail.phone_number = phone_number
74 1
    db_user.detail.internal_comment = internal_comment
75 1
    db_user.detail.extras = extras
76
77 1
    db.session.add(db_user)
78
79 1
    try:
80 1
        db.session.commit()
81
    except Exception as e:
82
        current_app.logger.error('User creation failed: %s', e)
83
        db.session.rollback()
84
        raise UserCreationFailed()
85
86 1
    user = user_service._db_entity_to_user(db_user)
87
88
    # Create event in separate step as user ID is not available earlier.
89 1
    event_data = {}
90 1
    if creator is not None:
91
        event_data['initiator_id'] = str(creator.id)
92 1
    if site_id is not None:
93 1
        event_data['site_id'] = site_id
94 1
    event_service.create_event(
95
        'user-created', user.id, event_data, occurred_at=created_at
96
    )
97
98 1
    event = UserAccountCreated(
99
        occurred_at=db_user.created_at,
100
        initiator_id=creator.id if creator else None,
101
        initiator_screen_name=creator.screen_name if creator else None,
102
        user_id=user.id,
103
        user_screen_name=user.screen_name,
104
        site_id=site_id,
105
    )
106
107
    # password
108 1
    password_service.create_password_hash(user.id, password)
109
110 1
    return user, event
111
112
113 1
def build_db_user(
114
    created_at: datetime,
115
    screen_name: Optional[str],
116
    email_address: Optional[str],
117
    *,
118
    legacy_id: Optional[str] = None,
119
) -> DbUser:
120
    normalized_screen_name: Optional[str]
121 1
    if screen_name is not None:
122 1
        normalized_screen_name = _normalize_screen_name(screen_name)
123
    else:
124 1
        normalized_screen_name = None
125
126
    normalized_email_address: Optional[str]
127 1
    if email_address is not None:
128 1
        normalized_email_address = _normalize_email_address(email_address)
129
    else:
130
        normalized_email_address = None
131
132 1
    user = DbUser(
133
        created_at,
134
        normalized_screen_name,
135
        normalized_email_address,
136
        legacy_id=legacy_id,
137
    )
138
139 1
    detail = DbUserDetail(user=user)
140
141 1
    return user
142
143
144 1
def request_email_address_confirmation(
145
    user: User, email_address: str, site_id: SiteID
146
) -> None:
147
    """Send an e-mail to the user to request confirmation of the e-mail
148
    address.
149
    """
150 1
    normalized_email_address = _normalize_email_address(email_address)
151 1
    screen_name = user.screen_name if user.screen_name else 'UnknownUser'
152
153 1
    email_address_verification_service.send_email_address_confirmation_email(
154
        normalized_email_address, screen_name, user.id, site_id
155
    )
156
157
158 1
def _normalize_screen_name(screen_name: str) -> str:
159
    """Normalize the screen name, or raise an exception if invalid."""
160 1
    normalized = screen_name.strip()
161
162 1
    if not normalized or (' ' in normalized) or ('@' in normalized):
163
        raise ValueError(f"Invalid screen name: '{screen_name}'")
164
165 1
    return normalized
166
167
168 1
def _normalize_email_address(email_address: str) -> str:
169
    """Normalize the e-mail address, or raise an exception if invalid."""
170 1
    normalized = email_address.strip().lower()
171
172 1
    if not normalized or (' ' in normalized) or ('@' not in normalized):
173
        raise ValueError(f"Invalid email address: '{email_address}'")
174
175
    return normalized
176