-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: Add Group and Group Membership data models to restapi #329
Closed
Closed
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
614345f
feat: add group and group_membership tables
f0c95be
chore: ran black and isort
a3eba66
refactor: add docstrings and cleanup controller layer
48f60fc
chore: address comments and cleanup
8d91e82
chore: Restyled files with updated black formatter
0fb8175
chore: fix black caused mypy errors
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# This Software (Dioptra) is being made available as a public service by the | ||
# National Institute of Standards and Technology (NIST), an Agency of the United | ||
# States Department of Commerce. This software was developed in part by employees of | ||
# NIST and in part by NIST contractors. Copyright in portions of this software that | ||
# were developed by NIST contractors has been licensed or assigned to NIST. Pursuant | ||
# to Title 17 United States Code Section 105, works of NIST employees are not | ||
# subject to copyright protection in the United States. However, NIST may hold | ||
# international copyright in software created by its employees and domestic | ||
# copyright (or licensing rights) in portions of software that were assigned or | ||
# licensed to NIST. To the extent that NIST holds copyright in this software, it is | ||
# being made available under the Creative Commons Attribution 4.0 International | ||
# license (CC BY 4.0). The disclaimers of the CC BY 4.0 license apply to all parts | ||
# of the software developed or licensed by NIST. | ||
# | ||
# ACCESS THE FULL CC BY 4.0 LICENSE HERE: | ||
# https://creativecommons.org/licenses/by/4.0/legalcode | ||
"""The group endpoint subpackage.""" | ||
|
||
from .dependencies import bind_dependencies, register_providers | ||
from .errors import register_error_handlers | ||
from .routes import register_routes | ||
|
||
__all__ = [ | ||
"bind_dependencies", | ||
"register_error_handlers", | ||
"register_providers", | ||
"register_routes", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# This Software (Dioptra) is being made available as a public service by the | ||
# National Institute of Standards and Technology (NIST), an Agency of the United | ||
# States Department of Commerce. This software was developed in part by employees of | ||
# NIST and in part by NIST contractors. Copyright in portions of this software that | ||
# were developed by NIST contractors has been licensed or assigned to NIST. Pursuant | ||
# to Title 17 United States Code Section 105, works of NIST employees are not | ||
# subject to copyright protection in the United States. However, NIST may hold | ||
# international copyright in software created by its employees and domestic | ||
# copyright (or licensing rights) in portions of software that were assigned or | ||
# licensed to NIST. To the extent that NIST holds copyright in this software, it is | ||
# being made available under the Creative Commons Attribution 4.0 International | ||
# license (CC BY 4.0). The disclaimers of the CC BY 4.0 license apply to all parts | ||
# of the software developed or licensed by NIST. | ||
# | ||
# ACCESS THE FULL CC BY 4.0 LICENSE HERE: | ||
# https://creativecommons.org/licenses/by/4.0/legalcode | ||
"""The module defining the group endpoints.""" | ||
from __future__ import annotations | ||
|
||
import uuid | ||
from typing import Any, cast | ||
|
||
import structlog | ||
from flask import request | ||
from flask_accepts import accepts, responds | ||
from flask_restx import Namespace, Resource | ||
from injector import inject | ||
from structlog.stdlib import BoundLogger | ||
|
||
from .model import Group | ||
from .schema import GroupSchema, IdStatusResponseSchema | ||
from .service import GroupService | ||
|
||
LOGGER: BoundLogger = structlog.stdlib.get_logger() | ||
|
||
api: Namespace = Namespace( | ||
"Group", | ||
description="Group submission and management operations", | ||
) | ||
|
||
|
||
@api.route("/") | ||
class GroupResource(Resource): | ||
"""Shows a list of all Group, and lets you POST to create new groups.""" | ||
|
||
@inject | ||
def __init__( | ||
self, | ||
*args, | ||
group_service: GroupService, | ||
**kwargs, | ||
) -> None: | ||
self._group_service = group_service | ||
super().__init__(*args, **kwargs) | ||
|
||
@responds(schema=GroupSchema(many=True), api=api) | ||
def get(self) -> list[Group]: | ||
"""Gets a list of all groups.""" | ||
log: BoundLogger = LOGGER.new( | ||
request_id=str(uuid.uuid4()), resource="group", request_type="GET" | ||
) # noqa: F841 | ||
log.info("Request received") | ||
return self._group_service.get_all(log=log) | ||
|
||
@accepts(schema=GroupSchema, api=api) | ||
@responds(schema=GroupSchema, api=api) | ||
def post(self) -> Group: | ||
"""Creates a new Group via a group submission form with an attached file.""" | ||
log: BoundLogger = LOGGER.new( | ||
request_id=str(uuid.uuid4()), resource="group", request_type="POST" | ||
) # noqa: F841 | ||
|
||
log.info("Request received") | ||
|
||
parsed_obj = request.parsed_obj # type: ignore | ||
name = str(parsed_obj["group_name"]) | ||
return self._group_service.create(name=name, log=log) | ||
|
||
@accepts(schema=GroupSchema, api=api) | ||
@responds(schema=IdStatusResponseSchema, api=api) | ||
def delete(self) -> dict[str, Any]: | ||
log: BoundLogger = LOGGER.new( | ||
request_id=str(uuid.uuid4()), resource="group", request_type="POST" | ||
) # noqa: F841 | ||
|
||
log.info("Request received") | ||
|
||
parsed_obj = request.parsed_obj # type: ignore | ||
group_id = int(parsed_obj["id"]) | ||
return self._group_service.delete(id=group_id) | ||
|
||
|
||
@api.route("/<int:groupId>") | ||
@api.param("groupId", "A string specifying a group's UUID.") | ||
class GroupIdResource(Resource): | ||
"""Shows a single job.""" | ||
|
||
@inject | ||
def __init__(self, *args, _service: GroupService, **kwargs) -> None: | ||
self._group_service = _service | ||
super().__init__(*args, **kwargs) | ||
|
||
@responds(schema=GroupSchema, api=api) | ||
def get(self, groupId: int) -> Group: | ||
"""Gets a group by its unique identifier.""" | ||
log: BoundLogger = LOGGER.new( | ||
request_id=str(uuid.uuid4()), resource="groupId", request_type="GET" | ||
) # noqa: F841 | ||
log.info("Request received", group_id=groupId) | ||
group = self._group_service.get(groupId, error_if_not_found=True, log=log) | ||
|
||
return cast(Group, group) | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# This Software (Dioptra) is being made available as a public service by the | ||
# National Institute of Standards and Technology (NIST), an Agency of the United | ||
# States Department of Commerce. This software was developed in part by employees of | ||
# NIST and in part by NIST contractors. Copyright in portions of this software that | ||
# were developed by NIST contractors has been licensed or assigned to NIST. Pursuant | ||
# to Title 17 United States Code Section 105, works of NIST employees are not | ||
# subject to copyright protection in the United States. However, NIST may hold | ||
# international copyright in software created by its employees and domestic | ||
# copyright (or licensing rights) in portions of software that were assigned or | ||
# licensed to NIST. To the extent that NIST holds copyright in this software, it is | ||
# being made available under the Creative Commons Attribution 4.0 International | ||
# license (CC BY 4.0). The disclaimers of the CC BY 4.0 license apply to all parts | ||
# of the software developed or licensed by NIST. | ||
# | ||
# ACCESS THE FULL CC BY 4.0 LICENSE HERE: | ||
# https://creativecommons.org/licenses/by/4.0/legalcode | ||
"""Binding configurations to shared services using dependency injection.""" | ||
from __future__ import annotations | ||
|
||
from typing import Any, Callable | ||
|
||
from injector import Binder, Module, provider | ||
|
||
from .service import GroupService | ||
|
||
|
||
class GroupServiceModule(Module): | ||
@provider | ||
def provide_queue_name_service_module( | ||
self, | ||
) -> GroupService: | ||
return GroupService() | ||
|
||
|
||
def bind_dependencies(binder: Binder) -> None: | ||
"""Binds interfaces to implementations within the main application. | ||
|
||
Args: | ||
binder: A :py:class:`~injector.Binder` object. | ||
""" | ||
pass | ||
|
||
|
||
def register_providers(modules: list[Callable[..., Any]]) -> None: | ||
"""Registers type providers within the main application. | ||
|
||
Args: | ||
modules: A list of callables used for configuring the dependency injection | ||
environment. | ||
""" | ||
modules.append(GroupServiceModule) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# This Software (Dioptra) is being made available as a public service by the | ||
# National Institute of Standards and Technology (NIST), an Agency of the United | ||
# States Department of Commerce. This software was developed in part by employees of | ||
# NIST and in part by NIST contractors. Copyright in portions of this software that | ||
# were developed by NIST contractors has been licensed or assigned to NIST. Pursuant | ||
# to Title 17 United States Code Section 105, works of NIST employees are not | ||
# subject to copyright protection in the United States. However, NIST may hold | ||
# international copyright in software created by its employees and domestic | ||
# copyright (or licensing rights) in portions of software that were assigned or | ||
# licensed to NIST. To the extent that NIST holds copyright in this software, it is | ||
# being made available under the Creative Commons Attribution 4.0 International | ||
# license (CC BY 4.0). The disclaimers of the CC BY 4.0 license apply to all parts | ||
# of the software developed or licensed by NIST. | ||
# | ||
# ACCESS THE FULL CC BY 4.0 LICENSE HERE: | ||
# https://creativecommons.org/licenses/by/4.0/legalcode | ||
"""Error handlers for the group endpoints.""" | ||
from __future__ import annotations | ||
|
||
from flask_restx import Api | ||
|
||
|
||
class GroupDoesNotExistError(Exception): | ||
"""The requested group does not exist.""" | ||
|
||
|
||
class GroupSubmissionError(Exception): | ||
"""The Group submission form contains invalid parameters.""" | ||
|
||
|
||
def register_error_handlers(api: Api) -> None: | ||
@api.errorhandler(GroupDoesNotExistError) | ||
def handle_job_does_not_exist_error(error): | ||
return {"message": "Not Found - The requested group does not exist"}, 404 | ||
|
||
@api.errorhandler(GroupSubmissionError) | ||
def handle_job_submission_error(error): | ||
return ( | ||
{ | ||
"message": "Bad Request - The group submission form contains " | ||
"invalid parameters. Please verify and resubmit." | ||
}, | ||
400, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# This Software (Dioptra) is being made available as a public service by the | ||
# National Institute of Standards and Technology (NIST), an Agency of the United | ||
# States Department of Commerce. This software was developed in part by employees of | ||
# NIST and in part by NIST contractors. Copyright in portions of this software that | ||
# were developed by NIST contractors has been licensed or assigned to NIST. Pursuant | ||
# to Title 17 United States Code Section 105, works of NIST employees are not | ||
# subject to copyright protection in the United States. However, NIST may hold | ||
# international copyright in software created by its employees and domestic | ||
# copyright (or licensing rights) in portions of software that were assigned or | ||
# licensed to NIST. To the extent that NIST holds copyright in this software, it is | ||
# being made available under the Creative Commons Attribution 4.0 International | ||
# license (CC BY 4.0). The disclaimers of the CC BY 4.0 license apply to all parts | ||
# of the software developed or licensed by NIST. | ||
# | ||
# ACCESS THE FULL CC BY 4.0 LICENSE HERE: | ||
# https://creativecommons.org/licenses/by/4.0/legalcode | ||
"""The data models for the job endpoint objects.""" | ||
from __future__ import annotations | ||
|
||
from dioptra.restapi.app import db | ||
from dioptra.restapi.group_membership.model import GroupMembership | ||
from dioptra.restapi.user.model import User | ||
|
||
|
||
class Group(db.Model): | ||
"""The Groups table. | ||
|
||
Attributes: | ||
group_id: The unique identifier of the group. | ||
name: Human-readable name for the group. | ||
creator_id: The id for the user that created the group. | ||
owner_id: The id for the user that owns the group. | ||
created_on: The time at which the group was created. | ||
deleted: Whether the group has been deleted. | ||
""" | ||
|
||
__tablename__ = "groups" | ||
|
||
group_id = db.Column(db.BigInteger(), primary_key=True) | ||
name = db.Column(db.String(36)) | ||
|
||
creator_id = db.Column(db.BigInteger(), db.ForeignKey("users.user_id"), index=True) | ||
owner_id = db.Column(db.BigInteger(), db.ForeignKey("users.user_id"), index=True) | ||
|
||
created_on = db.Column(db.DateTime()) | ||
deleted = db.Column(db.Boolean) | ||
|
||
creator = db.relationship("User", foreign_keys=[creator_id]) | ||
owner = db.relationship("User", foreign_keys=[owner_id]) | ||
|
||
@classmethod | ||
def next_id(cls) -> int: | ||
"""Generates the next id in the sequence.""" | ||
group: Group | None = cls.query.order_by(cls.group_id.desc()).first() | ||
|
||
if group is None: | ||
return 1 | ||
|
||
return int(group.id) + 1 | ||
|
||
@property | ||
def users(self): | ||
"""The users that are members of the group.""" | ||
return ( | ||
User.query.join(GroupMembership) | ||
.filter(GroupMembership.group_id == self.group_id) | ||
.all() | ||
) | ||
|
||
def check_membership(self, user: User) -> bool: | ||
"""Check if the user has permission to perform the specified action. | ||
|
||
Args: | ||
user: The user to check. | ||
action: The action to check. | ||
|
||
Returns: | ||
True if the user has permission to perform the action, False otherwise. | ||
""" | ||
membership = GroupMembership.query.filter_by( | ||
GroupMembership.user_id == user.user_id, | ||
GroupMembership.group_id == self.group_id, | ||
) | ||
|
||
if membership is None: | ||
return False | ||
else: | ||
return True | ||
|
||
def update(self, changes: dict): | ||
"""Updates the record. | ||
|
||
Args: | ||
changes: A dictionary containing record updates. | ||
""" | ||
for key, val in changes.items(): | ||
setattr(self, key, val) | ||
|
||
return self |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if necessary, service returns Group type already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mypy raises an error as it interprets the return from the query as
Any
.