Skip to content

model

mlte/user/model.py

Model implementation for a User.

RESOURCE_ALL_VALUES = '*' module-attribute

Special character used to identify all values of a certain permission.

BasicUser

Bases: BaseModel

A model class representing a user of the system

Source code in mlte/user/model.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class BasicUser(BaseModel):
    """A model class representing a user of the system"""

    username: str
    """The username to uniquely identify a user."""

    email: Optional[str] = None
    """An optional email associated to the user."""

    full_name: Optional[str] = None
    """The full name of the user."""

    disabled: bool = False
    """Whether the user is disabled."""

    role: RoleType = RoleType.REGULAR
    """The role associated to the user."""

    groups: List[Group] = []
    """The groups the user is in."""

    def is_equal_to(
        self,
        user: BasicUser,
        only_group_names: bool = True,
        ignore_groups: bool = False,
    ) -> bool:
        """Compares users at the BasicUser level."""
        user1 = BasicUser(**self.to_json())
        user2 = BasicUser(**user.to_json())

        if only_group_names:
            user1.groups = Group.get_group_names(user1.groups)
            user2.groups = Group.get_group_names(user2.groups)

        if ignore_groups:
            user1.groups = []
            user2.groups = []

        return user1 == user2

disabled = False class-attribute instance-attribute

Whether the user is disabled.

email = None class-attribute instance-attribute

An optional email associated to the user.

full_name = None class-attribute instance-attribute

The full name of the user.

groups = [] class-attribute instance-attribute

The groups the user is in.

role = RoleType.REGULAR class-attribute instance-attribute

The role associated to the user.

username instance-attribute

The username to uniquely identify a user.

is_equal_to(user, only_group_names=True, ignore_groups=False)

Compares users at the BasicUser level.

Source code in mlte/user/model.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def is_equal_to(
    self,
    user: BasicUser,
    only_group_names: bool = True,
    ignore_groups: bool = False,
) -> bool:
    """Compares users at the BasicUser level."""
    user1 = BasicUser(**self.to_json())
    user2 = BasicUser(**user.to_json())

    if only_group_names:
        user1.groups = Group.get_group_names(user1.groups)
        user2.groups = Group.get_group_names(user2.groups)

    if ignore_groups:
        user1.groups = []
        user2.groups = []

    return user1 == user2

Group

Bases: BaseModel

A user group to which permissions are associated.

Source code in mlte/user/model.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
class Group(BaseModel):
    """A user group to which permissions are associated."""

    name: str
    """The name of the group."""

    permissions: List[Permission] = []
    """The permissions associated to the group."""

    @staticmethod
    def get_group_names(groups: List[Group]) -> List[Group]:
        """Given a list of groups, returns a similar list with groups that only contain their names."""
        group_names: List[Group] = []
        for group in groups:
            group_names.append(Group(name=group.name))
        return group_names

name instance-attribute

The name of the group.

permissions = [] class-attribute instance-attribute

The permissions associated to the group.

get_group_names(groups) staticmethod

Given a list of groups, returns a similar list with groups that only contain their names.

Source code in mlte/user/model.py
180
181
182
183
184
185
186
@staticmethod
def get_group_names(groups: List[Group]) -> List[Group]:
    """Given a list of groups, returns a similar list with groups that only contain their names."""
    group_names: List[Group] = []
    for group in groups:
        group_names.append(Group(name=group.name))
    return group_names

MethodType

Bases: StrEnum

Types of methods for permissions.

Source code in mlte/user/model.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
class MethodType(StrEnum):
    """Types of methods for permissions."""

    GET = "get"
    """Get or read action."""

    POST = "post"
    """Creation action."""

    PUT = "put"
    """Action to edit."""

    DELETE = "delete"
    """Deletion action."""

    ANY = "any"
    """Special action to represent all/any of them."""

ANY = 'any' class-attribute instance-attribute

Special action to represent all/any of them.

DELETE = 'delete' class-attribute instance-attribute

Deletion action.

GET = 'get' class-attribute instance-attribute

Get or read action.

POST = 'post' class-attribute instance-attribute

Creation action.

PUT = 'put' class-attribute instance-attribute

Action to edit.

Permission

Bases: BaseModel

Permissions for manipulating resources.

Source code in mlte/user/model.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
class Permission(BaseModel):
    """Permissions for manipulating resources."""

    SEPARATOR: ClassVar[str] = "-"
    SEPARATOR_REPLACEMENT: ClassVar[str] = "___"
    """Used when serializing to string."""

    resource_type: ResourceType
    """The type of resource resource."""

    resource_id: Optional[str] = None
    """The specific resource id to give permissions to, if any."""

    method: MethodType = MethodType.ANY
    """The HTTP method applied on the resource."""

    def to_str(self) -> str:
        """Serialize the permission to a string"""
        serialized = f"{self.resource_type}{Permission.SEPARATOR}{self.method}"
        if self.resource_id is not None:
            serialized = f"{serialized}{Permission.SEPARATOR}{self.resource_id.replace(Permission.SEPARATOR, Permission.SEPARATOR_REPLACEMENT)}"
        return serialized

    @staticmethod
    def from_str(permission_str: str) -> Permission:
        """Creates a permission from its string serialization."""
        # Source str can have 2 or 3 parts, depending if resource id was None.
        parts = permission_str.split(Permission.SEPARATOR)
        type = parts[0]
        method = parts[1]
        if len(parts) > 2:
            resource_id = parts[2].replace(
                Permission.SEPARATOR_REPLACEMENT, Permission.SEPARATOR
            )
        else:
            resource_id = None

        return Permission(
            resource_type=ResourceType(type),
            resource_id=resource_id,
            method=MethodType(method),
        )

    def grants_access(self, request: Permission):
        """Checks if this permission grants access to the recieved request."""
        if self.to_str() == request.to_str():
            # If both are exactly the same, they match.
            return True

        if self.resource_type != request.resource_type:
            # If they point to different resource types, they will never match.
            return False
        else:
            if (
                self.method == request.method
                or self.method == MethodType.ANY
                or request.method == MethodType.ANY
            ):
                # If methods match, or either refer to any method, check resource id.
                if self.resource_id is None:
                    # "None" means all, that means we apply to any request resource id.
                    return True
                elif self.resource_id == request.resource_id:
                    # If the resource ids are the same, we match.
                    return True
                else:
                    # If we have a specific resource id, and they have a different one or "all", we don't match.
                    return False
            else:
                # The methods don't match in some way.
                return False

SEPARATOR_REPLACEMENT = '___' class-attribute

Used when serializing to string.

method = MethodType.ANY class-attribute instance-attribute

The HTTP method applied on the resource.

resource_id = None class-attribute instance-attribute

The specific resource id to give permissions to, if any.

resource_type instance-attribute

The type of resource resource.

from_str(permission_str) staticmethod

Creates a permission from its string serialization.

Source code in mlte/user/model.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
@staticmethod
def from_str(permission_str: str) -> Permission:
    """Creates a permission from its string serialization."""
    # Source str can have 2 or 3 parts, depending if resource id was None.
    parts = permission_str.split(Permission.SEPARATOR)
    type = parts[0]
    method = parts[1]
    if len(parts) > 2:
        resource_id = parts[2].replace(
            Permission.SEPARATOR_REPLACEMENT, Permission.SEPARATOR
        )
    else:
        resource_id = None

    return Permission(
        resource_type=ResourceType(type),
        resource_id=resource_id,
        method=MethodType(method),
    )

grants_access(request)

Checks if this permission grants access to the recieved request.

Source code in mlte/user/model.py
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def grants_access(self, request: Permission):
    """Checks if this permission grants access to the recieved request."""
    if self.to_str() == request.to_str():
        # If both are exactly the same, they match.
        return True

    if self.resource_type != request.resource_type:
        # If they point to different resource types, they will never match.
        return False
    else:
        if (
            self.method == request.method
            or self.method == MethodType.ANY
            or request.method == MethodType.ANY
        ):
            # If methods match, or either refer to any method, check resource id.
            if self.resource_id is None:
                # "None" means all, that means we apply to any request resource id.
                return True
            elif self.resource_id == request.resource_id:
                # If the resource ids are the same, we match.
                return True
            else:
                # If we have a specific resource id, and they have a different one or "all", we don't match.
                return False
        else:
            # The methods don't match in some way.
            return False

to_str()

Serialize the permission to a string

Source code in mlte/user/model.py
205
206
207
208
209
210
def to_str(self) -> str:
    """Serialize the permission to a string"""
    serialized = f"{self.resource_type}{Permission.SEPARATOR}{self.method}"
    if self.resource_id is not None:
        serialized = f"{serialized}{Permission.SEPARATOR}{self.resource_id.replace(Permission.SEPARATOR, Permission.SEPARATOR_REPLACEMENT)}"
    return serialized

ResourceType

Bases: StrEnum

Supported resource types.

Source code in mlte/user/model.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
class ResourceType(StrEnum):
    """Supported resource types."""

    MODEL = "model"
    """Model and all related artifacts."""

    USER = "user"
    """User resources."""

    GROUP = "group"
    """Group resources."""

    CATALOG = "catalog"
    """Test catalogs."""

    CUSTOM_LIST = "custom_list"
    """Custom lists."""

    @staticmethod
    def get_type_from_url(url: str) -> Optional[ResourceType]:
        """Returns the resource type for the given URL."""
        for resource_type in ResourceType:
            if url.startswith(f"/{resource_type.value}"):
                return resource_type

        # Return none if the URL did not match any known resource type.
        return None

CATALOG = 'catalog' class-attribute instance-attribute

Test catalogs.

CUSTOM_LIST = 'custom_list' class-attribute instance-attribute

Custom lists.

GROUP = 'group' class-attribute instance-attribute

Group resources.

MODEL = 'model' class-attribute instance-attribute

Model and all related artifacts.

USER = 'user' class-attribute instance-attribute

User resources.

get_type_from_url(url) staticmethod

Returns the resource type for the given URL.

Source code in mlte/user/model.py
160
161
162
163
164
165
166
167
168
@staticmethod
def get_type_from_url(url: str) -> Optional[ResourceType]:
    """Returns the resource type for the given URL."""
    for resource_type in ResourceType:
        if url.startswith(f"/{resource_type.value}"):
            return resource_type

    # Return none if the URL did not match any known resource type.
    return None

RoleType

Bases: StrEnum

Roles for users.

Source code in mlte/user/model.py
24
25
26
27
28
29
30
31
class RoleType(StrEnum):
    """Roles for users."""

    ADMIN = "admin"
    """An admin role, access to everything."""

    REGULAR = "regular"
    """A role with access to artifacts only."""

ADMIN = 'admin' class-attribute instance-attribute

An admin role, access to everything.

REGULAR = 'regular' class-attribute instance-attribute

A role with access to artifacts only.

User

Bases: BasicUser

User with additional information only used locally when stored.

Source code in mlte/user/model.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class User(BasicUser):
    """User with additional information only used locally when stored."""

    hashed_password: str
    """The hashed password of the user."""

    def update_user_data(self, new_user_data: BasicUser) -> User:
        """Update this user, but keeping existing hashed password."""
        hashed_password = self.hashed_password
        updated_user = User(
            **new_user_data.to_json(),
            hashed_password=hashed_password,
        )
        return updated_user

hashed_password instance-attribute

The hashed password of the user.

update_user_data(new_user_data)

Update this user, but keeping existing hashed password.

Source code in mlte/user/model.py
82
83
84
85
86
87
88
89
def update_user_data(self, new_user_data: BasicUser) -> User:
    """Update this user, but keeping existing hashed password."""
    hashed_password = self.hashed_password
    updated_user = User(
        **new_user_data.to_json(),
        hashed_password=hashed_password,
    )
    return updated_user

UserWithPassword

Bases: BasicUser

User with additional information only used when creating a user.

Source code in mlte/user/model.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
class UserWithPassword(BasicUser):
    """User with additional information only used when creating a user."""

    password: str
    """The plain password of the user."""

    def to_hashed_user(self) -> User:
        """Converts a UserWithPassword model with plain password into a User with a hashed one."""
        # Hash password and create a user with hashed passwords.
        hashed_password = passwords.hash_password(self.password)
        user = User(hashed_password=hashed_password, **self.to_json())
        return user

password instance-attribute

The plain password of the user.

to_hashed_user()

Converts a UserWithPassword model with plain password into a User with a hashed one.

Source code in mlte/user/model.py
 98
 99
100
101
102
103
def to_hashed_user(self) -> User:
    """Converts a UserWithPassword model with plain password into a User with a hashed one."""
    # Hash password and create a user with hashed passwords.
    hashed_password = passwords.hash_password(self.password)
    user = User(hashed_password=hashed_password, **self.to_json())
    return user

update_user_data(curr_user, new_user_data)

Get updated user depending on the type, keeping hashed password if no new password is received.

Source code in mlte/user/model.py
106
107
108
109
110
111
112
113
114
115
def update_user_data(
    curr_user: User, new_user_data: Union[UserWithPassword, BasicUser]
) -> User:
    """Get updated user depending on the type, keeping hashed password if no new password is received."""
    if type(new_user_data) is UserWithPassword:
        return new_user_data.to_hashed_user()
    elif type(new_user_data) is BasicUser:
        return curr_user.update_user_data(new_user_data)
    else:
        raise Exception(f"Invalid user type received: {type(new_user_data)}")