I might be inclined to do something like this:
class Capabilities:
def __init__(self) -> None:
self.canEditCompany = True
self.canDeleteCompany = True
self.canShareCompany = True
self.canAddFiscal = True
self.canAddCategory = True
self.canEditCategory = True
self.canGetCategory = True
self.canAddAccount = True
self.canGetAccount = True
self.canEditAccount = True
self.canAddTransaction = True
self.canAddScan = True
self.canAddTax = True
actions_dict = {
"canEditCompany": lambda company: (company, 'PUT'),
"canDeleteCompany": lambda company: (company, 'Delete'),
"canShareCompany": lambda company: (f'{company}/sharing', 'Post'),
"canAddFiscal": lambda company: (f'{company}/fiscal_year', 'POST'),
"canAddCategory": lambda company: (f'{company}/fiscals/*/categories', 'Post'),
"canEditCategory": lambda company: (f'{company}/fiscals/*/categories/*', 'PUT'),
"canGetCategory": lambda company: (f'{company}/fiscals/*/categories', 'GET'),
"canAddAccount": lambda company: (f'{company}/fiscals/*/accounts', 'POST'),
"canGetAccount": lambda company: (f'{company}/fiscals/*/accounts', 'GET'),
"canEditAccount": lambda company: (f'{company}/fiscals/*/accounts/*', 'PUT'),
"canAddAccount": lambda company: (f'{company}/fiscals/*/account', 'POST'),
"canAddTax": lambda company: (f'{company}/tax', 'POST'),
"canAddScan": lambda company: (f'{company}/scan', 'POST'),
"canAddTransaction": lambda company: (f'{company}/accounts/*', 'POST')
}
def permission_control(user_email, company):
capabilities = Capabilities()
return [(user_email, *action(company)) for capability, action in actions_dict.items() if getattr(capabilities, capability)]
I agree that your code was in some ways less readable, but I think you were heading in the right direction by taking out that awful if-tree, which definitely counts as an anti-pattern in my opinion. I think keeping the input and the result together in a dictionary is much more readable than keeping them in two separate lists, as you had them — and by using lambda
functions, we can construct the dict in the global namespace so it doesn't have to be made afresh every time the function is called, which is what your code was doing with your two lists.
EDIT: Following a suggestion in the comments, an even better solution would be to use format-strings
rather than lambdas
:
class Capabilities:
def __init__(self) -> None:
self.canEditCompany = True
self.canDeleteCompany = True
self.canShareCompany = True
self.canAddFiscal = True
self.canAddCategory = True
self.canEditCategory = True
self.canGetCategory = True
self.canAddAccount = True
self.canGetAccount = True
self.canEditAccount = True
self.canAddTransaction = True
self.canAddScan = True
self.canAddTax = True
actions_dict = {
"canEditCompany": ('{}', 'PUT'),
"canDeleteCompany": ('{}', 'Delete'),
"canShareCompany": ('{}/sharing', 'Post'),
"canAddFiscal": ('{}/fiscal_year', 'POST'),
"canAddCategory": ('{}/fiscals/*/categories', 'Post'),
"canEditCategory": ('{}/fiscals/*/categories/*', 'PUT'),
"canGetCategory": ('{}/fiscals/*/categories', 'GET'),
"canAddAccount": ('{}/fiscals/*/accounts', 'POST'),
"canGetAccount": ('{}/fiscals/*/accounts', 'GET'),
"canEditAccount": ('{}/fiscals/*/accounts/*', 'PUT'),
"canAddAccount": ('{}/fiscals/*/account', 'POST'),
"canAddTax": ('{}/tax', 'POST'),
"canAddScan": ('{}/scan', 'POST'),
"canAddTransaction": ('{}/accounts/*', 'POST')
}
def permission_control(user_email, company):
capabilities = Capabilities()
return [
(user_email, url.format(company), method)
for capability, (url, method) in actions_dict.items()
if getattr(capabilities, capability)
]
SECOND EDIT: As was pointed out in the comments, the answer with format strings could be improved even more by pre-binding the format
method of the format strings. This is slightly more performant, and constrains the strings to a single purpose.
I've also changed the data structure from a str: tuple[str, str]
dictionary to a list of NamedTuples
. A dictionary no longer made sense at this stage. NamedTuples aren't strictly necessary as opposed to tuples, but I think using them makes the intention of the code clearer and improves readability.
from typing import NamedTuple, Callable, Literal
class Capabilities:
def __init__(self) -> None:
self.canEditCompany = True
self.canDeleteCompany = True
self.canShareCompany = True
self.canAddFiscal = True
self.canAddCategory = True
self.canEditCategory = True
self.canGetCategory = True
self.canAddAccount = True
self.canGetAccount = True
self.canEditAccount = True
self.canAddTransaction = True
self.canAddScan = True
self.canAddTax = True
class CapabilityTuple(NamedTuple):
capability: str
url_factory: Callable[[str], str]
# Any reason why some of these aren't all-uppercase in your question?
# HTTP methods are usually all-uppercase
method: Literal['PUT', 'Delete', 'POST', 'GET', 'Post']
actions_list = [
CapabilityTuple(capability, url.format, method) for capability, url, method in (
("canEditCompany", '{}', 'PUT'),
("canDeleteCompany", '{}', 'Delete'),
("canShareCompany", '{}/sharing', 'Post'),
("canAddFiscal", '{}/fiscal_year', 'POST'),
("canAddCategory", '{}/fiscals/*/categories', 'Post'),
("canEditCategory", '{}/fiscals/*/categories/*', 'PUT'),
("canGetCategory", '{}/fiscals/*/categories', 'GET'),
("canAddAccount", '{}/fiscals/*/accounts', 'POST'),
("canGetAccount", '{}/fiscals/*/accounts', 'GET'),
("canEditAccount", '{}/fiscals/*/accounts/*', 'PUT'),
("canAddAccount", '{}/fiscals/*/account', 'POST'),
("canAddTax", '{}/tax', 'POST'),
("canAddScan", '{}/scan', 'POST'),
("canAddTransaction", '{}/accounts/*', 'POST')
)
]
def permission_control(user_email, company):
capabilities = Capabilities()
return [
(user_email, url_factory(company), method)
for capability, url_factory, method in actions_list
if getattr(capabilities, capability)
]