npis-api-utils 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. npis_api_utils-1.0.0/MANIFEST.in +1 -0
  2. npis_api_utils-1.0.0/PKG-INFO +20 -0
  3. npis_api_utils-1.0.0/README.md +12 -0
  4. npis_api_utils-1.0.0/requirements.txt +19 -0
  5. npis_api_utils-1.0.0/setup.cfg +4 -0
  6. npis_api_utils-1.0.0/setup.py +42 -0
  7. npis_api_utils-1.0.0/src/npis_api_utils/__init__.py +9 -0
  8. npis_api_utils-1.0.0/src/npis_api_utils/exceptions/__init__.py +16 -0
  9. npis_api_utils-1.0.0/src/npis_api_utils/schemas/__init__.py +2 -0
  10. npis_api_utils-1.0.0/src/npis_api_utils/schemas/formio_roles.py +14 -0
  11. npis_api_utils-1.0.0/src/npis_api_utils/services/__init__.py +7 -0
  12. npis_api_utils-1.0.0/src/npis_api_utils/services/external/__init__.py +3 -0
  13. npis_api_utils-1.0.0/src/npis_api_utils/services/external/formio.py +140 -0
  14. npis_api_utils-1.0.0/src/npis_api_utils/utils/__init__.py +35 -0
  15. npis_api_utils-1.0.0/src/npis_api_utils/utils/auth.py +83 -0
  16. npis_api_utils-1.0.0/src/npis_api_utils/utils/caching.py +5 -0
  17. npis_api_utils-1.0.0/src/npis_api_utils/utils/constants.py +46 -0
  18. npis_api_utils-1.0.0/src/npis_api_utils/utils/enums.py +70 -0
  19. npis_api_utils-1.0.0/src/npis_api_utils/utils/format.py +31 -0
  20. npis_api_utils-1.0.0/src/npis_api_utils/utils/logging.py +38 -0
  21. npis_api_utils-1.0.0/src/npis_api_utils/utils/pdf.py +101 -0
  22. npis_api_utils-1.0.0/src/npis_api_utils/utils/profiler.py +24 -0
  23. npis_api_utils-1.0.0/src/npis_api_utils/utils/roles.py +9 -0
  24. npis_api_utils-1.0.0/src/npis_api_utils/utils/startup.py +68 -0
  25. npis_api_utils-1.0.0/src/npis_api_utils/utils/translations/__init__.py +3 -0
  26. npis_api_utils-1.0.0/src/npis_api_utils/utils/translations/translations.py +130 -0
  27. npis_api_utils-1.0.0/src/npis_api_utils/utils/user_context.py +87 -0
  28. npis_api_utils-1.0.0/src/npis_api_utils/utils/util.py +125 -0
  29. npis_api_utils-1.0.0/src/npis_api_utils.egg-info/PKG-INFO +20 -0
  30. npis_api_utils-1.0.0/src/npis_api_utils.egg-info/SOURCES.txt +32 -0
  31. npis_api_utils-1.0.0/src/npis_api_utils.egg-info/dependency_links.txt +1 -0
  32. npis_api_utils-1.0.0/src/npis_api_utils.egg-info/not-zip-safe +1 -0
  33. npis_api_utils-1.0.0/src/npis_api_utils.egg-info/requires.txt +19 -0
  34. npis_api_utils-1.0.0/src/npis_api_utils.egg-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ include README.md requirements.txt
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.1
2
+ Name: npis_api_utils
3
+ Version: 1.0.0
4
+ Summary: NPIS api related libraries.
5
+ Home-page: https://github.com/katxeus/npis_api_utils
6
+ Author: JK
7
+ Description-Content-Type: text/markdown
8
+
9
+ # NPIS UTILS
10
+
11
+ ![Python](https://img.shields.io/badge/python-3.9-blue) ![Flask](https://img.shields.io/badge/Flask-2.3.2-blue) ![postgres](https://img.shields.io/badge/postgres-11.0-blue)
12
+ [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)[![linting: pylint](https://img.shields.io/badge/linting-pylint-yellowgreen)](https://github.com/PyCQA/pylint)
13
+
14
+
15
+ This Python package include all the libraries and utils needed for NPIS related services.
16
+
17
+ ## Usage
18
+
19
+ Install using pip
20
+
@@ -0,0 +1,12 @@
1
+ # NPIS UTILS
2
+
3
+ ![Python](https://img.shields.io/badge/python-3.9-blue) ![Flask](https://img.shields.io/badge/Flask-2.3.2-blue) ![postgres](https://img.shields.io/badge/postgres-11.0-blue)
4
+ [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)[![linting: pylint](https://img.shields.io/badge/linting-pylint-yellowgreen)](https://github.com/PyCQA/pylint)
5
+
6
+
7
+ This Python package include all the libraries and utils needed for NPIS related services.
8
+
9
+ ## Usage
10
+
11
+ Install using pip
12
+
@@ -0,0 +1,19 @@
1
+ gunicorn
2
+ Flask
3
+ Flask-Caching
4
+ Flask-Migrate
5
+ Flask-Moment
6
+ Flask-SQLAlchemy
7
+ flask-restx
8
+ flask-marshmallow
9
+ flask-jwt-oidc
10
+ python-dotenv
11
+ psycopg2-binary
12
+ marshmallow-sqlalchemy
13
+ requests
14
+ Werkzeug
15
+ sqlalchemy_utils
16
+ markupsafe
17
+ PyJWT
18
+ selenium
19
+ selenium-wire
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,42 @@
1
+ import setuptools
2
+ from glob import glob
3
+ from os.path import basename, splitext
4
+
5
+ def read(filepath):
6
+ """Read the contents from a file.
7
+ :param str filepath: path to the file to be read
8
+ :return: file contents
9
+ :rtype: str
10
+ """
11
+ with open(filepath, "r") as file_handle:
12
+ content = file_handle.read()
13
+ return content
14
+
15
+
16
+ def read_requirements(filename):
17
+ """Get application requirements from the requirements.txt file.
18
+ :return: Python requirements
19
+ :rtype: list
20
+ """
21
+ with open(filename, "r") as req:
22
+ requirements = req.readlines()
23
+ install_requires = [r.strip() for r in requirements if r.find("git+") != 0]
24
+ return install_requires
25
+
26
+ REQUIREMENTS = read_requirements("requirements.txt")
27
+
28
+ setuptools.setup(
29
+ name='npis_api_utils',
30
+ version='1.0.0',
31
+ author='JK',
32
+ description='NPIS api related libraries.',
33
+ long_description=read("README.md"),
34
+ long_description_content_type="text/markdown",
35
+ url='https://github.com/katxeus/npis_api_utils',
36
+ packages=setuptools.find_packages("src"),
37
+ package_dir={"": "src"},
38
+ py_modules=[splitext(basename(path))[0] for path in glob("src/*.py")],
39
+ include_package_data=True,
40
+ zip_safe=False,
41
+ install_requires=REQUIREMENTS,
42
+ )
@@ -0,0 +1,9 @@
1
+ """The forms flow API service."""
2
+
3
+ from npis_api_utils.utils.startup import (
4
+ collect_role_ids,
5
+ setup_jwt_manager,
6
+ collect_user_resource_ids,
7
+ )
8
+
9
+ __version__ = "5.0.1"
@@ -0,0 +1,16 @@
1
+ """Application Specific Exceptions, to manage the business errors.
2
+
3
+ BusinessException - error, status_code - Business rules error
4
+ error - a description of the error {code / description: classname / full text}
5
+ status_code - where possible use HTTP Error Codes
6
+ """
7
+
8
+
9
+ class BusinessException(Exception):
10
+ """Exception that adds error code and error."""
11
+
12
+ def __init__(self, error, status_code, *args, **kwargs):
13
+ """Return a valid BusinessException."""
14
+ super().__init__(*args, **kwargs)
15
+ self.error = error
16
+ self.status_code = status_code
@@ -0,0 +1,2 @@
1
+ """This exports all of the schemas used by the application."""
2
+ from npis_api_utils.schemas.formio_roles import FormioRoleSchema
@@ -0,0 +1,14 @@
1
+ """This is for marshmallowing Formio role ids."""
2
+ from marshmallow import EXCLUDE, Schema, fields
3
+
4
+
5
+ class FormioRoleSchema(Schema):
6
+ """This class manages the Formio role id request response schema."""
7
+
8
+ class Meta: # pylint: disable=too-few-public-methods
9
+ """Exclude unknown fields in the deserialized output."""
10
+
11
+ unknown = EXCLUDE
12
+
13
+ roleId = fields.Str(data_key="_id", required=True)
14
+ type = fields.Str(data_key="machineName", required=True)
@@ -0,0 +1,7 @@
1
+ """This exports all of the services used by the application."""
2
+
3
+ from npis_api_utils.services.external.formio import FormioService
4
+
5
+ __all__ = [
6
+ "FormioService",
7
+ ]
@@ -0,0 +1,3 @@
1
+ """This exports all of the external communication services used by the application."""
2
+
3
+ from .formio import FormioService
@@ -0,0 +1,140 @@
1
+ """This exposes the Formio APIs."""
2
+ import json
3
+ from http import HTTPStatus
4
+
5
+ import jwt
6
+ import requests
7
+ from flask import current_app
8
+
9
+ from npis_api_utils.exceptions import BusinessException
10
+ from npis_api_utils.utils import cache
11
+
12
+
13
+ class FormioService:
14
+ """This class manages formio API calls."""
15
+
16
+ def __init__(self):
17
+ """Initializing the service."""
18
+ self.base_url = current_app.config.get("FORMIO_URL")
19
+
20
+ @classmethod
21
+ def decode_timeout(cls, token):
22
+ """Method to decode token and get timeout."""
23
+ token = jwt.decode(token, options={"verify_signature": False})
24
+ timeout = token["exp"] - token["iat"]
25
+ return timeout
26
+
27
+ def get_formio_access_token(self):
28
+ """Method to get formio access token."""
29
+ formio_token = cache.get("formio_token")
30
+ if formio_token is None:
31
+ formio_token = self.generate_formio_token()
32
+ cache.set(
33
+ "formio_token",
34
+ formio_token,
35
+ timeout=self.decode_timeout(formio_token),
36
+ )
37
+ return formio_token
38
+
39
+ def generate_formio_token(self):
40
+ """Method to generate formio token using formio login API."""
41
+ headers = {"Content-Type": "application/json"}
42
+ url = f"{self.base_url}/user/login"
43
+ payload = {
44
+ "data": {
45
+ "email": current_app.config.get("FORMIO_USERNAME"),
46
+ "password": current_app.config.get("FORMIO_PASSWORD"),
47
+ }
48
+ }
49
+ current_app.logger.info("Generate formio token using formio login API.")
50
+ response = requests.post(url, headers=headers, data=json.dumps(payload))
51
+ if response.ok:
52
+ form_io_token = response.headers["x-jwt-token"]
53
+ return form_io_token
54
+ raise BusinessException(
55
+ "Unable to get access token from formio server",
56
+ HTTPStatus.SERVICE_UNAVAILABLE,
57
+ )
58
+
59
+ def create_form(self, data, formio_token):
60
+ """Post request to formio API to create form."""
61
+ headers = {"Content-Type": "application/json", "x-jwt-token": formio_token}
62
+ url = f"{self.base_url}/form"
63
+ response = requests.post(url, headers=headers, data=json.dumps(data))
64
+ if response.ok:
65
+ return response.json()
66
+ raise BusinessException(response.json(), HTTPStatus.BAD_REQUEST)
67
+
68
+ def update_form(self, form_id, data, formio_token):
69
+ """Put request to formio API to update form."""
70
+ headers = {"Content-Type": "application/json", "x-jwt-token": formio_token}
71
+ url = f"{self.base_url}/form/{form_id}"
72
+ response = requests.put(url, headers=headers, data=json.dumps(data))
73
+ if response.ok:
74
+ return response.json()
75
+ raise BusinessException(response.json(), HTTPStatus.BAD_REQUEST)
76
+
77
+ def get_role_ids(self):
78
+ """Get request to Formio API to retrieve role ids."""
79
+ url = f"{self.base_url}/role"
80
+ headers = {"x-jwt-token": self.get_formio_access_token()}
81
+ current_app.logger.info("Role id fetching started...")
82
+
83
+ response = requests.get(url, headers=headers)
84
+ if response.ok:
85
+ current_app.logger.info("Role ids collected successfully...")
86
+ return response.json()
87
+ current_app.logger.error("Failed to fetch role ids !!!")
88
+ raise BusinessException(response.json(), HTTPStatus.SERVICE_UNAVAILABLE)
89
+
90
+ def get_user_resource_ids(self):
91
+ """Get request to Formio API to retrieve user resource ids."""
92
+ url = f"{self.base_url}/user"
93
+ current_app.logger.info("Fetching user resource ids...")
94
+ response = requests.get(url)
95
+ if response.ok:
96
+ current_app.logger.info("User resource ids collected successfully.")
97
+ return response.json()
98
+ current_app.logger.error("Failed to fetch user resource ids!")
99
+ raise BusinessException(response.json(), HTTPStatus.SERVICE_UNAVAILABLE)
100
+
101
+ def get_form(self, data, formio_token):
102
+ """Get request to formio API to get form details."""
103
+ headers = {"Content-Type": "application/json", "x-jwt-token": formio_token}
104
+ url = f"{self.base_url}/form/" + data["form_id"]
105
+ response = requests.get(url, headers=headers)
106
+ if response.ok:
107
+ return response.json()
108
+ raise BusinessException(response.json(), HTTPStatus.BAD_REQUEST)
109
+
110
+ def get_submission(self, data, formio_token):
111
+ """Get request to formio API to get submission details."""
112
+ headers = {"Content-Type": "application/json", "x-jwt-token": formio_token}
113
+ url = (
114
+ f"{self.base_url}/form/" + data["form_id"] + "/submission/" + data["sub_id"]
115
+ )
116
+ response = requests.get(url, headers=headers, data=json.dumps(data))
117
+ if response.ok:
118
+ return response.json()
119
+ raise BusinessException(response.json(), HTTPStatus.BAD_REQUEST)
120
+
121
+ def post_submission(self, data, formio_token):
122
+ """Post request to formio API to create submission details."""
123
+ headers = {"Content-Type": "application/json", "x-jwt-token": formio_token}
124
+ url = (
125
+ f"{self.base_url}/form/{data['formId']}/submission"
126
+ )
127
+ response = requests.post(url, headers=headers, data=json.dumps(data))
128
+ if response.ok:
129
+ return response.json()
130
+ raise BusinessException(response.json(), HTTPStatus.BAD_REQUEST)
131
+
132
+ def get_form_by_path(self, path_name: str, formio_token: str) -> dict:
133
+ """Get request to formio API to get form details from path."""
134
+ headers = {"Content-Type": "application/json", "x-jwt-token": formio_token}
135
+ url = f"{self.base_url}/{path_name}"
136
+ response = requests.get(url, headers=headers)
137
+ if response.ok:
138
+ return response.json()
139
+ raise BusinessException(response.json(), HTTPStatus.BAD_REQUEST)
140
+
@@ -0,0 +1,35 @@
1
+ """This module holds general utility functions and helpers for the main package."""
2
+
3
+ from .auth import auth, jwt
4
+ from .caching import cache
5
+ from .constants import (
6
+ ALLOW_ALL_APPLICATIONS,
7
+ ALLOW_ALL_ORIGINS,
8
+ ANONYMOUS_USER,
9
+ ADMIN_GROUP,
10
+ CLIENT_GROUP,
11
+ CORS_ORIGINS,
12
+ DEFAULT_PROCESS_KEY,
13
+ DEFAULT_PROCESS_NAME,
14
+ DESIGNER_GROUP,
15
+ DRAFT_APPLICATION_STATUS,
16
+ FILTER_MAPS,
17
+ NPIS_API_CORS_ORIGINS,
18
+ NPIS_ROLES,
19
+ KEYCLOAK_DASHBOARD_BASE_GROUP,
20
+ NEW_APPLICATION_STATUS,
21
+ REVIEWER_GROUP,
22
+ HTTP_TIMEOUT
23
+ )
24
+ from .enums import ApplicationSortingParameters
25
+ from .format import CustomFormatter
26
+ from .logging import setup_logging, log_bpm_error
27
+ from .profiler import profiletime
28
+ from .user_context import UserContext, user_context
29
+ from .util import (
30
+ cors_preflight,
31
+ get_form_and_submission_id_from_form_url,
32
+ get_role_ids_from_user_groups,
33
+ translate,
34
+ validate_sort_order_and_order_by,
35
+ )
@@ -0,0 +1,83 @@
1
+ """Bring in the common JWT Manager and helper functions."""
2
+
3
+ from functools import wraps
4
+ from http import HTTPStatus
5
+
6
+ from flask import g, request, current_app
7
+ from flask_jwt_oidc import JwtManager
8
+
9
+ from jose import jwt as json_web_token
10
+ from jose.exceptions import JWTError
11
+
12
+ from ..exceptions import BusinessException
13
+
14
+ jwt = JwtManager() # pylint: disable=invalid-name
15
+
16
+
17
+ class Auth:
18
+ """Extending JwtManager to include additional functionalities."""
19
+
20
+ @classmethod
21
+ def require(cls, f):
22
+ """Validate the Bearer Token."""
23
+
24
+ @jwt.requires_auth
25
+ @wraps(f)
26
+ def decorated(*args, **kwargs):
27
+ g.authorization_header = request.headers.get("Authorization", None)
28
+ g.token_info = g.jwt_oidc_token_info
29
+
30
+ return f(*args, **kwargs)
31
+
32
+ return decorated
33
+
34
+ @classmethod
35
+ def has_one_of_roles(cls, roles):
36
+ """Check that at least one of the realm roles are in the token.
37
+
38
+ Args:
39
+ roles [str,]: Comma separated list of valid roles
40
+ """
41
+
42
+ def decorated(f):
43
+ @Auth.require
44
+ @wraps(f)
45
+ def wrapper(*args, **kwargs):
46
+ if jwt.contains_role(roles):
47
+ return f(*args, **kwargs)
48
+
49
+ raise BusinessException("Access Denied", HTTPStatus.UNAUTHORIZED)
50
+
51
+ return wrapper
52
+
53
+ return decorated
54
+
55
+ @classmethod
56
+ def has_role(cls, role):
57
+ """Method to validate the role."""
58
+ return jwt.validate_roles(role)
59
+
60
+ @classmethod
61
+ def require_custom(cls, f):
62
+ """Validate custom form embed token."""
63
+ @wraps(f)
64
+ def decorated(*args, **kwargs):
65
+ token = jwt.get_token_auth_header()
66
+ try:
67
+ data = json_web_token.decode(
68
+ token,
69
+ algorithms="HS256",
70
+ key=current_app.config.get('FORM_EMBED_JWT_SECRET'),
71
+ )
72
+ g.authorization_header = token
73
+ g.token_info = g.jwt_oidc_token_info = data
74
+ except JWTError as err:
75
+ raise BusinessException("Invalid token", HTTPStatus.UNAUTHORIZED)
76
+ except Exception as err:
77
+ raise err
78
+ return f(*args, **kwargs)
79
+ return decorated
80
+
81
+ auth = (
82
+ Auth()
83
+ ) # pylint: disable=invalid-name; lower case name as used by convention in most Flask apps
@@ -0,0 +1,5 @@
1
+ """Cache configurations."""
2
+
3
+ from flask_caching import Cache
4
+
5
+ cache = Cache(config={"CACHE_TYPE": "SimpleCache"})
@@ -0,0 +1,46 @@
1
+ """All constants for project."""
2
+ import os
3
+
4
+ from dotenv import find_dotenv, load_dotenv
5
+
6
+ # this will load all the envars from a .env file located in the project root (api)
7
+ load_dotenv(find_dotenv())
8
+
9
+ NPIS_API_CORS_ORIGINS = os.getenv("NPIS_API_CORS_ORIGINS", "*")
10
+ ALLOW_ALL_ORIGINS = "*"
11
+ CORS_ORIGINS = []
12
+ if NPIS_API_CORS_ORIGINS != "*":
13
+ CORS_ORIGINS = NPIS_API_CORS_ORIGINS.split(",")
14
+ ADMIN_GROUP = "npis-admin"
15
+ DESIGNER_GROUP = "npis-designer"
16
+ REVIEWER_GROUP = "npis-reviewer"
17
+ CLIENT_GROUP = "npis-client"
18
+ NPIS_ROLES = [DESIGNER_GROUP, REVIEWER_GROUP, CLIENT_GROUP]
19
+ ALLOW_ALL_APPLICATIONS = "/npis/npis-reviewer/access-allow-applications"
20
+
21
+ NEW_APPLICATION_STATUS = "New"
22
+ DRAFT_APPLICATION_STATUS = "Draft"
23
+ KEYCLOAK_DASHBOARD_BASE_GROUP = "npis-analytics"
24
+ ANONYMOUS_USER = "Anonymous-user"
25
+
26
+ FILTER_MAPS = {
27
+ "application_id": {"field": "id", "operator": "eq"},
28
+ "application_name": {"field": "form_name", "operator": "ilike"},
29
+ "application_status": {"field": "application_status", "operator": "eq"},
30
+ "created_by": {"field": "created_by", "operator": "eq"},
31
+ "modified_from": {"field": "modified", "operator": "ge"},
32
+ "modified_to": {"field": "modified", "operator": "le"},
33
+ "created_from": {"field": "created", "operator": "ge"},
34
+ "created_to": {"field": "created", "operator": "le"},
35
+ "form_name": {"field": "form_name", "operator": "ilike"},
36
+ "id": {"field": "id", "operator": "eq"},
37
+ "form_type": {"field": "form_type", "operator": "eq"},
38
+ "can_bundle": {"field": "can_bundle", "operator": "eq"},
39
+ "is_bundle": {"field": "is_bundle", "operator": "eq"},
40
+ "title":{"field": "title", "operator": "ilike"},
41
+ "category":{"field": "category", "operator": "ilike"},
42
+ }
43
+
44
+ DEFAULT_PROCESS_KEY = "Defaultflow"
45
+ DEFAULT_PROCESS_NAME = "Default Flow"
46
+ HTTP_TIMEOUT = 30
@@ -0,0 +1,70 @@
1
+ """Enum User Definition."""
2
+ from enum import Enum, unique
3
+
4
+
5
+ class FormProcessMapperStatus(Enum):
6
+ """This enum provides the list of FormProcessMapper Status."""
7
+
8
+ ACTIVE = "active"
9
+ INACTIVE = "inactive"
10
+
11
+
12
+ class MetricsState(Enum):
13
+ """This enum provides the list of states of Metrics."""
14
+
15
+ CREATED = "created"
16
+ MODIFIED = "modified"
17
+
18
+
19
+ class ApplicationSortingParameters: # pylint: disable=too-few-public-methods
20
+ """This enum provides the list of Sorting Parameters."""
21
+
22
+ Id = "id"
23
+ Created = "created"
24
+ Name = "applicationName"
25
+ Status = "applicationStatus"
26
+ Modified = "modified"
27
+ FormName = "formName"
28
+
29
+
30
+ @unique
31
+ class FormioRoles(Enum):
32
+ """Roles and corresponding machine names."""
33
+
34
+ CLIENT = "npisClient"
35
+ REVIEWER = "npisReviewer"
36
+ DESIGNER = "administrator"
37
+ ANONYMOUS = "anonymous"
38
+ RESOURCE_ID = "RESOURCE_ID"
39
+
40
+ @classmethod
41
+ def contains(cls, item: str) -> bool:
42
+ """Checks if the parameter exists in the enum."""
43
+ return item in [entry.value for entry in cls]
44
+
45
+
46
+ @unique
47
+ class DraftStatus(Enum):
48
+ """Draft status and corresponding values."""
49
+
50
+ ACTIVE = 1
51
+ INACTIVE = 0
52
+
53
+
54
+ class DraftSortingParameters: # pylint: disable=too-few-public-methods
55
+ """This enum provides the list of Sorting Parameters."""
56
+
57
+ id = "id"
58
+ Created = "created"
59
+ Name = "DraftName"
60
+ Status = "applicationStatus"
61
+ Modified = "modified"
62
+ FormName = "formName"
63
+
64
+
65
+ @unique
66
+ class FilterStatus(Enum):
67
+ """This enum provides the filter status."""
68
+
69
+ ACTIVE = "active"
70
+ INACTIVE = "inactive"
@@ -0,0 +1,31 @@
1
+ """Module handle the console display formatting."""
2
+ import logging
3
+
4
+
5
+ class CustomFormatter(logging.Formatter):
6
+ """Class extends the logging Formatter class to support custom colour messages."""
7
+
8
+ blue = "\x1b[34;21m"
9
+ grey = "\x1b[38;21m"
10
+ green = "\x1b[32;1m"
11
+ yellow = "\x1b[33;21m"
12
+ red = "\x1b[31;21m"
13
+ bold_red = "\x1b[31;1m"
14
+ reset = "\x1b[0m"
15
+ __format = (
16
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
17
+ )
18
+
19
+ FORMATS = {
20
+ logging.DEBUG: blue + __format + reset,
21
+ logging.INFO: green + __format + reset,
22
+ logging.WARNING: yellow + __format + reset,
23
+ logging.ERROR: red + __format + reset,
24
+ logging.CRITICAL: bold_red + __format + reset,
25
+ }
26
+
27
+ def format(self, record):
28
+ """Returns the formatted information."""
29
+ log_fmt = self.FORMATS.get(record.levelno)
30
+ formatter = logging.Formatter(log_fmt)
31
+ return formatter.format(record)
@@ -0,0 +1,38 @@
1
+ """Centralized setup of logging for the service."""
2
+
3
+ import logging.config
4
+ import sys
5
+ from os import path
6
+
7
+
8
+ def setup_logging(conf):
9
+ """Create the services logger."""
10
+ if conf and path.isfile(conf):
11
+ logging.config.fileConfig(conf)
12
+ print(f"Configure logging, from conf:{conf}", file=sys.stdout)
13
+ else:
14
+ print(
15
+ f"Unable to configure logging, attempted conf:{conf}",
16
+ file=sys.stderr,
17
+ )
18
+ return logging.getLogger(__name__)
19
+
20
+
21
+ def log_error(msg):
22
+ """Log error."""
23
+ logging.error(msg)
24
+
25
+
26
+ def log_bpm_error(msg):
27
+ """Log error."""
28
+ logging.error(msg)
29
+ logging.error(
30
+ """The connection with Python and Camunda API is not proper.
31
+ Ensure you have passed env variables properly and
32
+ have set listener in Keycloak(camunda-rest-api)"""
33
+ )
34
+
35
+
36
+ def log_info(msg):
37
+ """Log info."""
38
+ logging.info(msg)
@@ -0,0 +1,101 @@
1
+ """Utility functions to manage pdf generation using selenium chrome."""
2
+
3
+ import base64
4
+ import json
5
+
6
+ from flask import current_app, make_response
7
+ from selenium.common.exceptions import TimeoutException
8
+ from selenium.webdriver.chrome.options import Options
9
+ from selenium.webdriver.chrome.service import Service
10
+ from selenium.webdriver.common.by import By
11
+ from selenium.webdriver.support import expected_conditions as EC
12
+ from selenium.webdriver.support.ui import WebDriverWait
13
+ from seleniumwire import webdriver
14
+
15
+
16
+ def send_devtools(driver, cmd, params=None):
17
+ """Chrome dev tools execution function."""
18
+ resource = "/session/" + driver.session_id + "/chromium/send_command_and_get_result"
19
+ # pylint: disable=protected-access
20
+ url = driver.command_executor._url + resource
21
+ body = json.dumps({"cmd": cmd, "params": params})
22
+ # pylint: disable=protected-access
23
+ response = driver.command_executor._request("POST", url, body)
24
+ if response.get("status"):
25
+ raise Exception(response.get("value"))
26
+ return response.get("value")
27
+
28
+
29
+ # pylint: disable=R1710
30
+
31
+
32
+ def get_pdf_from_html(path, chromedriver=None, p_options=None, args=None):
33
+ """Load url in chrome web driver and print as pdf."""
34
+
35
+ def interceptor(request):
36
+ request.headers["Authorization"] = args["auth_token"]
37
+
38
+ if args is None:
39
+ args = {}
40
+
41
+ options = Options()
42
+ options.add_argument("--headless")
43
+ options.add_argument("--disable-gpu")
44
+ options.add_argument("--no-sandbox")
45
+ options.add_argument("--disable-dev-shm-usage")
46
+ options.add_argument("--run-all-compositor-stages-before-draw")
47
+ options.add_argument("--disable-logging")
48
+ options.add_argument("--log-level=3")
49
+ sel_options = {"request_storage_base_dir": "/tmp"}
50
+
51
+ service = Service(executable_path=chromedriver)
52
+ # pylint: disable=E1123
53
+ driver = webdriver.Chrome(
54
+ service=service, options=options, seleniumwire_options=sel_options
55
+ )
56
+ driver.set_window_size(1920, 1080)
57
+
58
+ if "auth_token" in args:
59
+ driver.request_interceptor = interceptor
60
+
61
+ if "timezone" in args and args["timezone"] is not None:
62
+ tz_params = {"timezoneId": args["timezone"]}
63
+ driver.execute_cdp_cmd("Emulation.setTimezoneOverride", tz_params)
64
+
65
+ driver.get(path)
66
+
67
+ try:
68
+ if "wait" in args:
69
+ delay = 30 # seconds
70
+ elem_loc = EC.presence_of_element_located((By.CLASS_NAME, args["wait"]))
71
+ WebDriverWait(driver, delay).until(elem_loc)
72
+ calculated_print_options = {
73
+ "landscape": False,
74
+ "displayHeaderFooter": False,
75
+ "printBackground": True,
76
+ "preferCSSPageSize": True,
77
+ }
78
+ if p_options is not None:
79
+ calculated_print_options.update(p_options)
80
+ result = send_devtools(driver, "Page.printToPDF", calculated_print_options)
81
+ driver.quit()
82
+ return base64.b64decode(result["data"])
83
+
84
+ except TimeoutException as err:
85
+ driver.quit()
86
+ current_app.logger.warning(err)
87
+ return False
88
+
89
+
90
+ def pdf_response(result, file_name="Pdf.pdf"):
91
+ """Render pdf response from html content."""
92
+ response = make_response(result)
93
+ response.headers["Content-Type"] = "application/pdf"
94
+ response.headers["Content-Disposition"] = "inline; filename=" + file_name
95
+ return response
96
+
97
+
98
+ def save_pdf_local(result, file_name="Pdf.pdf"):
99
+ """Save html content as pdf response."""
100
+ with open(file_name, "wb") as file:
101
+ file.write(result)
@@ -0,0 +1,24 @@
1
+ """Utility function for profiling functions."""
2
+ import time
3
+ from functools import wraps
4
+
5
+ from flask import current_app
6
+
7
+
8
+ def profiletime(profile_fn):
9
+ """Function to profile time."""
10
+
11
+ @wraps(profile_fn)
12
+ def measure_time(*args, **kwargs):
13
+ """Measure the API response time using time module."""
14
+ start_time = time.time()
15
+ result = profile_fn(*args, **kwargs)
16
+ end_time = time.time()
17
+ diff = end_time - start_time
18
+
19
+ current_app.logger.info(
20
+ f"API endpoint: {profile_fn.__qualname__} took {diff} seconds"
21
+ )
22
+ return result
23
+
24
+ return measure_time
@@ -0,0 +1,9 @@
1
+ """Role definitions."""
2
+
3
+
4
+ # class Role: # pylint: disable=too-few-public-methods
5
+ # """User Role."""
6
+
7
+ # rpas_designer = "rpas-designer"
8
+ # rpas_reviewer = "rpas-reviewer"
9
+ # rpas_client = "rpas-client"
@@ -0,0 +1,68 @@
1
+ """
2
+ App initialization.
3
+ Functions to initialize app and startup
4
+
5
+ """
6
+ from typing import Dict
7
+ from npis_api_utils.exceptions import BusinessException
8
+ from npis_api_utils.schemas import FormioRoleSchema
9
+ from npis_api_utils.services.external import FormioService
10
+ from npis_api_utils.utils import cache
11
+ from npis_api_utils.utils.enums import FormioRoles
12
+
13
+
14
+ def setup_jwt_manager(app, jwt_manager):
15
+ """Use flask app to configure the JWTManager to work for a particular Realm."""
16
+
17
+ def get_roles(a_dict):
18
+ resource = a_dict["resource_access"].get(app.config["JWT_OIDC_AUDIENCE"])
19
+ return resource["roles"] if resource else a_dict["roles"]
20
+
21
+ app.config["JWT_ROLE_CALLBACK"] = get_roles
22
+ jwt_manager.init_app(app)
23
+
24
+
25
+ def collect_role_ids(app):
26
+ """Collect role ids from Form.io."""
27
+ try:
28
+ service = FormioService()
29
+ app.logger.info("Establishing new connection to formio...")
30
+ role_ids = FormioRoleSchema().load(service.get_role_ids(), many=True)
31
+ role_ids_filtered = list(filter(None, map(standardization_fn, role_ids)))
32
+ # Cache will be having infinite expiry
33
+ if role_ids:
34
+ cache.set(
35
+ "formio_role_ids",
36
+ role_ids_filtered,
37
+ timeout=0,
38
+ )
39
+ app.logger.info("Role ids saved to cache successfully.")
40
+ except BusinessException as err:
41
+ app.logger.error(err.error)
42
+ except Exception as err: # pylint: disable=broad-except
43
+ app.logger.error(err)
44
+
45
+
46
+ def collect_user_resource_ids(app):
47
+ """Collects user resource ids from Form.io."""
48
+ try:
49
+ service = FormioService()
50
+ user_resource = service.get_user_resource_ids()
51
+ user_resource_id = user_resource["_id"]
52
+ if user_resource:
53
+ cache.set(
54
+ "user_resource_id",
55
+ user_resource_id,
56
+ timeout=0,
57
+ )
58
+ app.logger.info("User resource ids saved to cache successfully.")
59
+ except Exception as err: # pylint: disable=broad-except
60
+ app.logger.error(err)
61
+
62
+
63
+ def standardization_fn(item: Dict) -> Dict or None:
64
+ """Updates the type value to enum key for standardization."""
65
+ if FormioRoles.contains(item["type"]):
66
+ item["type"] = FormioRoles(item["type"]).name
67
+ return item
68
+ return None
@@ -0,0 +1,3 @@
1
+ """This module holds translations related files."""
2
+
3
+ from .translations import translations
@@ -0,0 +1,130 @@
1
+ """Translations dictionary."""
2
+ # pylint: skip-file
3
+
4
+ translations = {
5
+ "fr": {
6
+ "errors": "les erreurs",
7
+ "type": "taper",
8
+ "message": "message",
9
+ "Bad request error": "Mauvaise erreur de demande",
10
+ "Invalid request data object": "Objet de données de requête non valide",
11
+ "Invalid Token Error": "Erreur de jeton non valide",
12
+ "Invalid Request Object": "Objet de demande non valide",
13
+ "Required fields are not passed": "Les champs obligatoires ne sont pas passés",
14
+ "Access to NPIS API Denied. Check if the bearer token is passed for Authorization or has expired.": "Accès à l'API NPIS refusé. Vérifiez si le jeton du porteur est passé pour l'autorisation ou a expiré.",
15
+ "Authorization error": "Erreur d'autorisation",
16
+ "Permission denied": "Permission refusée",
17
+ "Invalid application request passed": "Demande de candidature invalide transmise",
18
+ "Invalid Request Object Passed": "Objet de demande non valide transmis",
19
+ "Welcome to NPIS API": "Bienvenue dans l'API NPIS",
20
+ "Dashboards not available": "Tableaux de bord non disponibles",
21
+ "Error": "Erreur",
22
+ "Dashboard not found": "Tableau de bord introuvable",
23
+ "Invalid request object passed for FormProcessmapper POST API": "Objet de demande non valide transmis pour l'API POST FormProcessmapper",
24
+ "Invalid response data": "Données de réponse non valides",
25
+ "Invalid request passed": "Demande invalide transmise",
26
+ "Invalid Request Object format": "Format d'objet de demande non valide",
27
+ "Missing from_date or to_date. Invalid request object for application metrics endpoint": "From_date ou to_date manquant. Objet de requête non valide pour le point de terminaison des métriques d'application",
28
+ "Error while getting application metrics": "Erreur lors de l'obtention des métriques d'application",
29
+ },
30
+ "zh-CN": {
31
+ "errors": "错误",
32
+ "type": "类型",
33
+ "message": "信息",
34
+ "Bad request error": "错误的请求错误",
35
+ "Invalid request data object": "请求数据对象无效",
36
+ "Invalid Token Error": "无效令牌错误",
37
+ "Invalid Request Object": "无效的请求对象",
38
+ "Required fields are not passed": "必填字段未通过",
39
+ "Access to NPIS API Denied. Check if the bearer token is passed for Authorization or has expired.": "拒绝访问 N API。检查不记名令牌是否已通过授权或已过期。",
40
+ "Authorization error": "授权错误",
41
+ "Permission denied": "“没有权限",
42
+ "Invalid application request passed": "通过了无效的应用程序请求",
43
+ "Invalid Request Object Passed": "传递的请求对象无效",
44
+ "Welcome to NPIS API": "欢迎使用 NPIS API",
45
+ "Dashboards not available": "仪表板不可用",
46
+ "Error": "错误",
47
+ "Dashboard not found": "未找到仪表板",
48
+ "Invalid request object passed for FormProcessmapper POST API": "为 FormProcessmapper POST API 传递的请求对象无效",
49
+ "Invalid response data": "无效的响应数据",
50
+ "Invalid request passed": "无效请求已通过",
51
+ "Invalid Request Object format": "请求对象格式无效",
52
+ "Missing from_date or to_date. Invalid request object for application metrics endpoint": "缺少 from_date 或 to_date。应用程序指标端点的请求对象无效",
53
+ "Error while getting application metrics": "获取应用程序指标时出错",
54
+ },
55
+ "bg": {
56
+ "errors": "errors",
57
+ "type": "Тип",
58
+ "message": "съобщение",
59
+ "Bad request error": "Грешка в лоша заявка",
60
+ "Invalid request data object": "Невалиден обект с данни за заявка",
61
+ "Invalid Token Error": "Грешка с невалиден токен",
62
+ "Invalid Request Object": "Невалиден обект на заявка",
63
+ "Required fields are not passed": "Задължителните полета не се предават",
64
+ "Access to NPIS API Denied. Check if the bearer token is passed for Authorization or has expired.": "Достъпът до API на NPIS е отказан. Проверете дали токенът на носител е предаден за оторизация или е изтекъл.",
65
+ "Authorization error": "Грешка в упълномощаването",
66
+ "Permission denied": "Permission denied",
67
+ "Invalid application request passed": "Невалидна заявка за приложение е преминала",
68
+ "Invalid Request Object Passed": "Предаден е невалиден обект на заявка",
69
+ "Welcome to NPIS API": "Добре дошли в API на NPIS",
70
+ "Dashboards not available": "Таблата за управление не са налични",
71
+ "Error": "Грешка",
72
+ "Dashboard not found": "Таблото за управление не е намерено",
73
+ "Invalid request object passed for FormProcessmapper POST API": "Предаден е невалиден обект на заявка за FormProcessmapper POST API",
74
+ "Invalid response data": "Невалидни данни за отговор",
75
+ "Invalid request passed": "Невалидна заявка е преминала",
76
+ "Invalid Request Object format": "Невалиден формат на обекта на заявка",
77
+ "Missing from_date or to_date. Invalid request object for application metrics endpoint": "Липсва от_дата или до_дата. Невалиден обект на заявка за крайна точка на показателите на приложението",
78
+ "Error while getting application metrics": "Грешка при получаване на показатели за приложението",
79
+ },
80
+ "pt": {
81
+ "errors": "erros",
82
+ "type": "modelo",
83
+ "message": "mensagem",
84
+ "Bad request error": "Erro de solicitação incorreta",
85
+ "Invalid request data object": "Objeto de dados de solicitação inválido",
86
+ "Invalid Token Error": "Erro de token inválido",
87
+ "Invalid Request Object": "Objeto de solicitação inválido",
88
+ "Required fields are not passed": "Campos obrigatórios não são passados",
89
+ "Access to NPIS API Denied. Check if the bearer token is passed for Authorization or has expired.": "Acesso negado à API do NPIS. Verifique se o token do portador foi aprovado para autorização ou expirou.",
90
+ "Authorization error": "Erro de autorização",
91
+ "Permission denied": "Permissão negada",
92
+ "Invalid application request passed": "Solicitação de inscrição inválida aprovada",
93
+ "Invalid Request Object Passed": "Objeto de solicitação inválido passado",
94
+ "Welcome to NPIS API": "Bem-vindo à API do NPIS",
95
+ "Dashboards not available": "Painéis não disponíveis",
96
+ "Error": "Erro",
97
+ "Dashboard not found": "Painel não encontrado",
98
+ "Invalid request object passed for FormProcessmapper POST API": "Objeto de solicitação inválido passado para API POST FormProcessmapper",
99
+ "Invalid response data": "Dados de resposta inválidos",
100
+ "Invalid request passed": "Solicitação inválida aprovada",
101
+ "Invalid Request Object format": "Formato de objeto de solicitação inválido",
102
+ "Missing from_date or to_date. Invalid request object for application metrics endpoint": "Está faltando from_date ou to_date. Objeto de solicitação inválido para o endpoint de métricas do aplicativo",
103
+ "Error while getting application metrics": "Erro ao obter as métricas do aplicativo",
104
+ },
105
+ "de": {
106
+ "errors": "Fehler",
107
+ "type": "Typ",
108
+ "message": "Botschaft",
109
+ "Bad request error": "Ungültiger Anforderungsfehler",
110
+ "Invalid request data object": "Ungültiges Anforderungsdatenobjekt",
111
+ "Invalid Token Error": "Ungültiger Token-Fehler",
112
+ "Invalid Request Object": "Ungültiges Anforderungsobjekt",
113
+ "Required fields are not passed": "Erforderliche Felder werden nicht übergeben",
114
+ "Access to NPIS API Denied. Check if the bearer token is passed for Authorization or has expired.": "Zugriff auf die Formsflow.ai-API verweigert. Überprüfen Sie, ob das Bearer-Token für die Autorisierung übergeben wurde oder abgelaufen ist.",
115
+ "Authorization error": "Autorisierungsfehlero",
116
+ "Permission denied": "Zugang verweigert",
117
+ "Invalid application request passed": "Ungültige Anwendungsanforderung bestanden",
118
+ "Invalid Request Object Passed": "Ungültiges Anforderungsobjekt übergeben",
119
+ "Welcome to NPIS API": "Willkommen bei der NPIS-API",
120
+ "Dashboards not available": "Dashboards nicht verfügbar",
121
+ "Error": "Fehler",
122
+ "Dashboard not found": "Dashboard nicht gefunden",
123
+ "Invalid request object passed for FormProcessmapper POST API": "Ungültiges Anforderungsobjekt für FormProcessmapper POST API übergeben",
124
+ "Invalid response data": "Ungültige Antwortdaten",
125
+ "Invalid request passed": "Ungültige Anfrage bestanden",
126
+ "Invalid Request Object format": "Ungültiges Anforderungsobjektformat",
127
+ "Missing from_date or to_date. Invalid request object for application metrics endpoint": "From_date oder to_date fehlen. Ungültiges Anforderungsobjekt für Endpunkt der Anwendungsmetriken",
128
+ "Error while getting application metrics": "Fehler beim Abrufen von Anwendungsmetriken",
129
+ },
130
+ }
@@ -0,0 +1,87 @@
1
+ """User Context to hold request scoped variables."""
2
+
3
+ import functools
4
+ from typing import Dict, List
5
+
6
+ from flask import current_app, g, request
7
+
8
+
9
+ def _get_context():
10
+ """Return User context."""
11
+ return UserContext()
12
+
13
+
14
+ class UserContext: # pylint: disable=too-many-instance-attributes
15
+ """Object to hold request scoped user context."""
16
+
17
+ def __init__(self):
18
+ """Return a User Context object."""
19
+ token_info: Dict = _get_token_info()
20
+ self._tenant_key = token_info.get("tenantKey", None)
21
+ self._user_name = token_info.get("preferred_username", None)
22
+ self._bearer_token: str = _get_token()
23
+ self.token_info = token_info
24
+ self._email = token_info.get("email")
25
+ self._roles: list = token_info.get("roles", None) or token_info.get(
26
+ "role", None
27
+ )
28
+ self._groups: list = token_info.get("groups", None)
29
+
30
+ @property
31
+ def tenant_key(self) -> str:
32
+ """Return the tenant key."""
33
+ return self._tenant_key
34
+
35
+ @property
36
+ def user_name(self) -> str:
37
+ """Return the user name."""
38
+ return self._user_name
39
+
40
+ @property
41
+ def bearer_token(self) -> str:
42
+ """Return the bearer_token."""
43
+ return self._bearer_token
44
+
45
+ @property
46
+ def email(self) -> str:
47
+ """Return the email."""
48
+ return self._email
49
+
50
+ @property
51
+ def roles(self) -> List[str]:
52
+ """Return the roles."""
53
+ return self._roles
54
+
55
+ @property
56
+ def group_or_roles(self) -> List[str]:
57
+ """Return groups is env is using groups, else roles."""
58
+ return (
59
+ self._roles
60
+ if current_app.config.get("KEYCLOAK_ENABLE_CLIENT_AUTH")
61
+ else self._groups
62
+ )
63
+
64
+
65
+ def user_context(function):
66
+ """Add user context object as an argument to function."""
67
+
68
+ @functools.wraps(function)
69
+ def wrapper(*func_args, **func_kwargs):
70
+ context = _get_context()
71
+ func_kwargs["user"] = context
72
+ return function(*func_args, **func_kwargs)
73
+
74
+ return wrapper
75
+
76
+
77
+ def _get_token_info() -> Dict:
78
+ return g.jwt_oidc_token_info if g and "jwt_oidc_token_info" in g else {}
79
+
80
+
81
+ def _get_token() -> str:
82
+ token: str = (
83
+ request.headers["Authorization"]
84
+ if request and "Authorization" in request.headers
85
+ else None
86
+ )
87
+ return token
@@ -0,0 +1,125 @@
1
+ """Common utils.
2
+
3
+ CORS pre-flight decorator. A simple decorator to add the options
4
+ method to a Request Class.
5
+ camel_to_snake - Converts camel case to snake case.
6
+ validate_sort_order_and_order_by - Utility function to validate
7
+ if sort order and sort order by is correct.
8
+ translate - Translate the response to provided language
9
+ """
10
+ import re
11
+ from typing import Tuple
12
+
13
+ from .constants import (
14
+ ALLOW_ALL_ORIGINS,
15
+ CLIENT_GROUP,
16
+ DESIGNER_GROUP,
17
+ REVIEWER_GROUP,
18
+ )
19
+ from .enums import (
20
+ ApplicationSortingParameters,
21
+ DraftSortingParameters,
22
+ FormioRoles,
23
+ )
24
+ from .translations.translations import translations
25
+
26
+
27
+ def cors_preflight(methods: str = "GET"):
28
+ """Render an option method on the class."""
29
+
30
+ def wrapper(f): # pylint: disable=invalid-name
31
+ def options(self, *args, **kwargs): # pylint: disable=unused-argument
32
+ return (
33
+ {"Allow": "GET"},
34
+ 200,
35
+ {
36
+ "Access-Control-Allow-Origin": ALLOW_ALL_ORIGINS,
37
+ "Access-Control-Allow-Methods": methods,
38
+ "Access-Control-Allow-Headers": "Authorization, Content-Type",
39
+ },
40
+ )
41
+
42
+ setattr(f, "options", options)
43
+ return f
44
+
45
+ return wrapper
46
+
47
+
48
+ def camel_to_snake(name: str) -> str:
49
+ """Convert camel case to snake case."""
50
+ s_1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
51
+ return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s_1).lower()
52
+
53
+
54
+ def validate_sort_order_and_order_by(order_by: str, sort_order: str) -> bool:
55
+ """Validate sort order and order by."""
56
+ if order_by not in [
57
+ ApplicationSortingParameters.Id,
58
+ ApplicationSortingParameters.Name,
59
+ ApplicationSortingParameters.Status,
60
+ ApplicationSortingParameters.Modified,
61
+ ApplicationSortingParameters.FormName,
62
+ DraftSortingParameters.Name,
63
+ ]:
64
+ order_by = None
65
+ else:
66
+ if order_by in [ApplicationSortingParameters.Name, DraftSortingParameters.Name]:
67
+ order_by = ApplicationSortingParameters.FormName
68
+ order_by = camel_to_snake(order_by)
69
+ if sort_order not in ["asc", "desc"]:
70
+ sort_order = None
71
+ return order_by, sort_order
72
+
73
+
74
+ def translate(to_lang: str, data: dict) -> dict:
75
+ """Translate the response to provided language.
76
+
77
+ will return the translated object if there is match
78
+ else return the original object
79
+ """
80
+ try:
81
+ translated_data = {}
82
+ if to_lang not in translations:
83
+ raise KeyError
84
+ for key, value in data.items():
85
+ # if matching translation is present for either key / value,
86
+ # then translated string is used
87
+ # original string otherwise
88
+ translated_data[
89
+ translations[to_lang][key] if key in translations[to_lang] else key
90
+ ] = (
91
+ translations[to_lang][value]
92
+ if value in translations[to_lang]
93
+ else value
94
+ )
95
+ return translated_data
96
+ except KeyError as err:
97
+ raise err
98
+ except Exception as err:
99
+ raise err
100
+
101
+
102
+ def get_role_ids_from_user_groups(role_ids, user_role):
103
+ """Filters out formio role ids specific to user groups."""
104
+ if role_ids is None or user_role is None:
105
+ return None
106
+
107
+ if DESIGNER_GROUP in user_role:
108
+ return role_ids
109
+ if REVIEWER_GROUP in user_role:
110
+ return filter_list_by_user_role(FormioRoles.REVIEWER.name, role_ids)
111
+ if CLIENT_GROUP in user_role:
112
+ return filter_list_by_user_role(FormioRoles.CLIENT.name, role_ids)
113
+ return None
114
+
115
+
116
+ def filter_list_by_user_role(formio_role, role_ids):
117
+ """Iterate over role_ids and return entries with matching formio role."""
118
+ return list(filter(lambda item: item["type"] == formio_role, role_ids))
119
+
120
+
121
+ def get_form_and_submission_id_from_form_url(form_url: str) -> Tuple:
122
+ """Retrieves the formid and submission id from the url parameters."""
123
+ form_id = form_url[form_url.find("/form/") + 6 : form_url.find("/submission/")]
124
+ submission_id = form_url[form_url.find("/submission/") + 12 : len(form_url)]
125
+ return (form_id, submission_id)
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.1
2
+ Name: npis-api-utils
3
+ Version: 1.0.0
4
+ Summary: NPIS api related libraries.
5
+ Home-page: https://github.com/katxeus/npis_api_utils
6
+ Author: JK
7
+ Description-Content-Type: text/markdown
8
+
9
+ # NPIS UTILS
10
+
11
+ ![Python](https://img.shields.io/badge/python-3.9-blue) ![Flask](https://img.shields.io/badge/Flask-2.3.2-blue) ![postgres](https://img.shields.io/badge/postgres-11.0-blue)
12
+ [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)[![linting: pylint](https://img.shields.io/badge/linting-pylint-yellowgreen)](https://github.com/PyCQA/pylint)
13
+
14
+
15
+ This Python package include all the libraries and utils needed for NPIS related services.
16
+
17
+ ## Usage
18
+
19
+ Install using pip
20
+
@@ -0,0 +1,32 @@
1
+ MANIFEST.in
2
+ README.md
3
+ requirements.txt
4
+ setup.py
5
+ src/npis_api_utils/__init__.py
6
+ src/npis_api_utils.egg-info/PKG-INFO
7
+ src/npis_api_utils.egg-info/SOURCES.txt
8
+ src/npis_api_utils.egg-info/dependency_links.txt
9
+ src/npis_api_utils.egg-info/not-zip-safe
10
+ src/npis_api_utils.egg-info/requires.txt
11
+ src/npis_api_utils.egg-info/top_level.txt
12
+ src/npis_api_utils/exceptions/__init__.py
13
+ src/npis_api_utils/schemas/__init__.py
14
+ src/npis_api_utils/schemas/formio_roles.py
15
+ src/npis_api_utils/services/__init__.py
16
+ src/npis_api_utils/services/external/__init__.py
17
+ src/npis_api_utils/services/external/formio.py
18
+ src/npis_api_utils/utils/__init__.py
19
+ src/npis_api_utils/utils/auth.py
20
+ src/npis_api_utils/utils/caching.py
21
+ src/npis_api_utils/utils/constants.py
22
+ src/npis_api_utils/utils/enums.py
23
+ src/npis_api_utils/utils/format.py
24
+ src/npis_api_utils/utils/logging.py
25
+ src/npis_api_utils/utils/pdf.py
26
+ src/npis_api_utils/utils/profiler.py
27
+ src/npis_api_utils/utils/roles.py
28
+ src/npis_api_utils/utils/startup.py
29
+ src/npis_api_utils/utils/user_context.py
30
+ src/npis_api_utils/utils/util.py
31
+ src/npis_api_utils/utils/translations/__init__.py
32
+ src/npis_api_utils/utils/translations/translations.py
@@ -0,0 +1,19 @@
1
+ Flask
2
+ Flask-Caching
3
+ Flask-Migrate
4
+ Flask-Moment
5
+ Flask-SQLAlchemy
6
+ PyJWT
7
+ Werkzeug
8
+ flask-jwt-oidc
9
+ flask-marshmallow
10
+ flask-restx
11
+ gunicorn
12
+ markupsafe
13
+ marshmallow-sqlalchemy
14
+ psycopg2-binary
15
+ python-dotenv
16
+ requests
17
+ selenium
18
+ selenium-wire
19
+ sqlalchemy_utils
@@ -0,0 +1 @@
1
+ npis_api_utils