ositah 24.4.dev1__py3-none-any.whl → 24.7.dev1__py3-none-any.whl

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 (47) hide show
  1. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/METADATA +1 -1
  2. ositah-24.7.dev1.dist-info/RECORD +6 -0
  3. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/WHEEL +1 -1
  4. ositah/__init__.py +0 -0
  5. ositah/app.py +0 -17
  6. ositah/apps/__init__.py +0 -0
  7. ositah/apps/analysis.py +0 -774
  8. ositah/apps/configuration/__init__.py +0 -0
  9. ositah/apps/configuration/callbacks.py +0 -917
  10. ositah/apps/configuration/main.py +0 -542
  11. ositah/apps/configuration/parameters.py +0 -74
  12. ositah/apps/configuration/tools.py +0 -112
  13. ositah/apps/export.py +0 -1172
  14. ositah/apps/validation/__init__.py +0 -0
  15. ositah/apps/validation/callbacks.py +0 -240
  16. ositah/apps/validation/main.py +0 -89
  17. ositah/apps/validation/parameters.py +0 -25
  18. ositah/apps/validation/tables.py +0 -654
  19. ositah/apps/validation/tools.py +0 -533
  20. ositah/assets/arrow_down_up.svg +0 -4
  21. ositah/assets/ositah.css +0 -54
  22. ositah/assets/sort_ascending.svg +0 -5
  23. ositah/assets/sort_descending.svg +0 -6
  24. ositah/assets/sorttable.js +0 -499
  25. ositah/main.py +0 -449
  26. ositah/ositah.example.cfg +0 -215
  27. ositah/static/style.css +0 -54
  28. ositah/templates/base.html +0 -22
  29. ositah/templates/bootstrap_login.html +0 -38
  30. ositah/templates/login_form.html +0 -27
  31. ositah/utils/__init__.py +0 -0
  32. ositah/utils/agents.py +0 -117
  33. ositah/utils/authentication.py +0 -287
  34. ositah/utils/cache.py +0 -19
  35. ositah/utils/core.py +0 -13
  36. ositah/utils/exceptions.py +0 -64
  37. ositah/utils/hito_db.py +0 -51
  38. ositah/utils/hito_db_model.py +0 -245
  39. ositah/utils/menus.py +0 -339
  40. ositah/utils/period.py +0 -135
  41. ositah/utils/projects.py +0 -1175
  42. ositah/utils/teams.py +0 -42
  43. ositah/utils/utils.py +0 -459
  44. ositah-24.4.dev1.dist-info/RECORD +0 -46
  45. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/LICENSE +0 -0
  46. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/entry_points.txt +0 -0
  47. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/top_level.txt +0 -0
ositah/static/style.css DELETED
@@ -1,54 +0,0 @@
1
- /* CSS for the validation part of OSITAH */
2
-
3
- div.login_page {
4
- margin-left: 50px;
5
- }
6
-
7
- div.login_form_field {
8
- font-weight: bold;
9
- margin-top: 20px;
10
- }
11
-
12
- input.login_button {
13
- margin-top: 30px;
14
- margin-left: 45px;
15
- }
16
-
17
- ul.flash-message {
18
- display: inline-block;
19
- list-style-type: none;
20
- padding-left: 0;
21
- }
22
-
23
-
24
- /* CSS for Dash components */
25
-
26
- .team_list_dropdown {
27
- margin-top: 15px;
28
- }
29
-
30
- .validated_hito_missing {
31
- background-color: tomato;
32
- }
33
-
34
- table.sortable th::after, th.sorttable_sorted::after, th.sorttable_sorted_reverse::after {
35
- content: " ";
36
- display: inline-block;
37
- width: 24px;
38
- height: 24px;
39
- }
40
-
41
- table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sorttable_nosort):after {
42
- content: url("arrow_down_up.svg");
43
- }
44
-
45
- th.sorttable_sorted::after {
46
- background: url("sort_ascending.svg");
47
- background-size: contain;
48
- }
49
- th.sorttable_sorted_reverse::after {
50
- background: url("sort_descending.svg");
51
- background-size: cover;
52
- }
53
-
54
- #sorttable_sortfwdind, #sorttable_sortrevind { display: none; }
@@ -1,22 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="UTF-8">
5
- <title>OSITAH Login page</title>
6
- <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
7
- <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
8
- </head>
9
- <body>
10
- {% with messages = get_flashed_messages(with_categories=True) %}
11
- {% if messages %}
12
- <ul class="bg-warning flash-message">
13
- {%- for category, message in messages %}
14
- <li>{{ category }}: {{ message }}</li>
15
- {% endfor -%}
16
- </ul>
17
- {% endif %}
18
- {% endwith %}
19
-
20
- {% block content %}{% endblock %}
21
- </body>
22
- </html>
@@ -1,38 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6
- <meta name="description" content="">
7
- <meta name="author" content="">
8
- <link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico">
9
-
10
- <title>Signin Template for Bootstrap</title>
11
-
12
- <link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/sign-in/">
13
-
14
- <!-- Bootstrap core CSS -->
15
- <link href="../../dist/css/bootstrap.min.css" rel="stylesheet">
16
-
17
- <!-- Custom styles for this template -->
18
- <link href="signin.css" rel="stylesheet">
19
- </head>
20
-
21
- <body class="text-center">
22
- <form class="form-signin">
23
- <img class="mb-4" src="https://getbootstrap.com/docs/4.0/assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
24
- <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
25
- <label for="inputEmail" class="sr-only">Email address</label>
26
- <input type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
27
- <label for="inputPassword" class="sr-only">Password</label>
28
- <input type="password" id="inputPassword" class="form-control" placeholder="Password" required>
29
- <div class="checkbox mb-3">
30
- <label>
31
- <input type="checkbox" value="remember-me"> Remember me
32
- </label>
33
- </div>
34
- <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
35
- <p class="mt-5 mb-3 text-muted">&copy; 2017-2018</p>
36
- </form>
37
- </body>
38
- </html>
@@ -1,27 +0,0 @@
1
- <div class="login_page">
2
- {% extends 'base.html' %}
3
- {% block content %}
4
- <p>Log in using <strong>{{ provider.title }}</strong></p>
5
-
6
- <form method=post>
7
- <div class="login_form">
8
- {% for field in form %}
9
- {% if field.widget.__class__.__name__ == 'HiddenInput' %}
10
- {{ field() }}
11
- {% else %}
12
- <div class="login_form_field">
13
- {{ field.label() }}
14
- </div>
15
- <div>
16
- {{ field() }}
17
- {% if field.errors %}
18
- Errors: {{ field.errors|join(' / ') }}
19
- {% endif %}
20
- </div>
21
- {% endif %}
22
- {% endfor %}
23
- <input type="submit" value="Login" class="btn btn-secondary login_button">
24
- </div>
25
- </form>
26
- {% endblock %}
27
- </div>
ositah/utils/__init__.py DELETED
File without changes
ositah/utils/agents.py DELETED
@@ -1,117 +0,0 @@
1
- # Helper functions to interact with the agent table
2
- import pandas as pd
3
- from flask import session
4
-
5
- from ositah.utils.exceptions import SessionDataMissing
6
- from ositah.utils.utils import TEAM_LIST_ALL_AGENTS, GlobalParams, no_session_id_jumbotron
7
-
8
- NSIP_AGENT_COLUMNS = {
9
- "email_reseda": "email_reseda",
10
- "firstname": "firstname",
11
- "lastname": "lastname",
12
- }
13
-
14
-
15
- def get_agent_by_email(connexion_email: str) -> str:
16
- """
17
- Retrieve an agent from Hito using the connexion email. If the connexion email is not
18
- defined for the agent, try to use the contact email.
19
-
20
- :param connexion_email: agent's email
21
- :return: agent entry (row)
22
- """
23
-
24
- from ositah.utils.hito_db_model import Agent
25
-
26
- user = Agent.query.filter_by(email_auth=session["user_email"]).first()
27
- if user is None:
28
- user = Agent.query.filter_by(email=session["user_email"]).first()
29
-
30
- return user
31
-
32
-
33
- def get_agents(period_date: str, team: str = None) -> pd.DataFrame:
34
- """
35
- Read agent table in Hito database and return it as a dataframe. If a cached version exists,
36
- use it.
37
-
38
- :param period_date: a date that must be inside the declaration period
39
- :param team: selected team (and subteams). Ignored if None or TEAM_LIST_ALL_AGENTS.
40
- :return: dataframe
41
- """
42
-
43
- from ositah.utils.hito_db import get_db
44
-
45
- global_params = GlobalParams()
46
- columns = global_params.columns
47
- db = get_db()
48
-
49
- try:
50
- session_data = global_params.session_data
51
- except SessionDataMissing:
52
- return no_session_id_jumbotron()
53
-
54
- # start_date, _ = get_validation_period_dates(period_date)
55
-
56
- agents = session_data.agent_list
57
- if agents is None:
58
- agent_list = pd.read_sql_query(global_params.agent_query, con=db.engine)
59
- # WIP ; attempt to refine the agent list taking into account arrival and departure date
60
- # Difficulty: carriere table has a suboptimal structure making a SQL request difficult
61
- # TODO: query separately agent+team and carriere, then use Pandas to join them taking all
62
- # the criteria into account
63
- # Ignore archived agents that left before the start of the declaration period
64
- # agent_list = agent_list.loc[(agent_list.archive == 0) |
65
- # (agent_list.date_fin >= start_date.date().isoformat())]
66
- if team and team != TEAM_LIST_ALL_AGENTS:
67
- agent_list = agent_list[agent_list["team"].str.match(team, case=False, na=False)]
68
- agent_list[columns["fullname"]] = agent_list[
69
- [columns["lastname"], columns["firstname"]]
70
- ].agg(" ".join, axis=1)
71
- agent_list.columns = agent_list.columns.str.lower()
72
- agent_list["statut"] = agent_list["statut"].str.extract("^statut_(.+)$")
73
- agent_list["optional"] = agent_list["statut"].isin(
74
- global_params.declaration_options["optional_statutes"]
75
- )
76
- agent_list["email_auth"] = agent_list["email_auth"].str.lower()
77
- team_list = pd.DataFrame(agent_list[columns["team"]].unique(), columns=["name"])
78
- optional_teams_list = []
79
- for opt_team in global_params.declaration_options["optional_teams"]:
80
- optional_teams_list.append(
81
- team_list[team_list["name"].str.match(opt_team, case=False, na=False)]["name"]
82
- )
83
- optional_teams = pd.concat(optional_teams_list)
84
- agent_list.loc[~agent_list["optional"], "optional"] = agent_list["team"].isin(
85
- optional_teams
86
- )
87
- session_data.agent_list = agent_list
88
- else:
89
- agent_list = session_data.agent_list
90
-
91
- return agent_list
92
-
93
-
94
- def get_nsip_agents():
95
- """
96
- Retrieve agents from NSIP and return them in a dataframe.
97
-
98
- :return: dataframe or None if NSIP is not configured
99
- """
100
-
101
- global_params = GlobalParams()
102
-
103
- if global_params.nsip:
104
- agents = pd.DataFrame.from_dict(global_params.nsip.get_agent_list())
105
- columns_to_delete = []
106
- for c in agents.columns.tolist():
107
- if c not in NSIP_AGENT_COLUMNS.values():
108
- columns_to_delete.append(c)
109
- agents["fullname"] = agents[
110
- [NSIP_AGENT_COLUMNS["lastname"], NSIP_AGENT_COLUMNS["firstname"]]
111
- ].agg(" ".join, axis=1)
112
- agents.drop(columns=columns_to_delete, inplace=True)
113
-
114
- return agents
115
-
116
- else:
117
- return None
@@ -1,287 +0,0 @@
1
- # Module to handle user login with flask-multipass, a multi-backend authentication module for Flask
2
- # The code is largely based on a simplified version of what Indico is doing and is focused on using
3
- # LDAP (IJCLab ActiveDirectory) as the backend.
4
- #
5
- # There is no attempt to store session data in a database.
6
-
7
- import functools
8
- import json
9
- from urllib.parse import urlparse
10
- from uuid import uuid1
11
-
12
- from flask import flash, redirect, request, session
13
- from flask_multipass import InvalidCredentials, Multipass, NoSuchUser
14
-
15
- from ositah.utils.utils import GlobalParams
16
-
17
- # Redirect URL for login and logout
18
- LOGIN_URL = "/login"
19
- LOGOUT_URL = "/logout"
20
-
21
-
22
- # Session validity duration (in hours), i.e. max time since the last use
23
- SESSION_MAX_DURATION = 4
24
-
25
-
26
- # List of authenticated users
27
- user_list = {}
28
- identity_list = {}
29
-
30
-
31
- class User:
32
- def __init__(self, first_name=None, last_name=None, email=None):
33
- self._id = uuid1()
34
- self._firstname = first_name
35
- self._lastname = last_name
36
- self._email = email
37
- self._identities = []
38
-
39
- def add_identity(self, identity):
40
- self._identities.append(identity)
41
-
42
- @property
43
- def identities(self):
44
- return self._identities
45
-
46
- @property
47
- def email(self):
48
- return self._email
49
-
50
- def get_first_identity(self):
51
- if len(self._identities) >= 1:
52
- return self._identities[0]
53
- else:
54
- return Exception(f"No identity defined for user '{self.email}'")
55
-
56
- @property
57
- def id(self):
58
- return self._id
59
-
60
-
61
- class Identity:
62
- def __init__(self, provider=None, identifier=None):
63
- self._id = identifier
64
- self._provider = provider
65
- self._multipass_data = None
66
-
67
- @property
68
- def id(self):
69
- return self._id
70
-
71
- @property
72
- def multipass_data(self):
73
- return self._multipass_data
74
-
75
- @multipass_data.setter
76
- def multipass_data(self, data):
77
- self._multipass_data = data
78
-
79
- @property
80
- def provider(self):
81
- return self._provider
82
-
83
-
84
- class OSITAHMultipass(Multipass):
85
- def init_app(self, app):
86
- super(OSITAHMultipass, self).init_app(app)
87
- with app.app_context():
88
- self._check_default_provider()
89
-
90
- def _check_default_provider(self):
91
- # Ensure that there is maximum one sync provider
92
- sync_providers = [
93
- p for p in self.identity_providers.values() if p.settings.get("synced_fields")
94
- ]
95
- if len(sync_providers) > 1:
96
- raise ValueError("There can only be one sync provider.")
97
- # Ensure that there is exactly one form-based default auth provider
98
- auth_providers = list(self.auth_providers.values())
99
- external_providers = [p for p in auth_providers if p.is_external]
100
- local_providers = [p for p in auth_providers if not p.is_external]
101
- if any(p.settings.get("default") for p in external_providers):
102
- raise ValueError("The default provider cannot be external")
103
- if all(p.is_external for p in auth_providers):
104
- return
105
- default_providers = [p for p in auth_providers if p.settings.get("default")]
106
- if len(default_providers) > 1:
107
- raise ValueError("There can only be one default auth provider")
108
- elif not default_providers:
109
- if len(local_providers) == 1:
110
- local_providers[0].settings["default"] = True
111
- else:
112
- raise ValueError("There is no default auth provider")
113
-
114
- def handle_auth_error(self, exc, redirect_to_login=False):
115
- if isinstance(exc, (NoSuchUser, InvalidCredentials)):
116
- print("Invalid credentials")
117
- else:
118
- exc_str = str(exc)
119
- print(
120
- "Authentication via %s failed: %s (%r)",
121
- exc.provider.name if exc.provider else None,
122
- exc_str,
123
- exc.details,
124
- )
125
- return super(OSITAHMultipass, self).handle_auth_error(
126
- exc, redirect_to_login=redirect_to_login
127
- )
128
-
129
-
130
- def configure_multipass_ldap(app, provider_title):
131
- """
132
- Configure Flask_multipass from configuration. Inspired from Indico and its configuration file.
133
- Required flask_multipass with PR #42.
134
-
135
- :param app: Flask app
136
- :param provider_title: text associated with the auth/identity provider
137
- :return: none
138
- """
139
-
140
- global_params = GlobalParams()
141
- config = global_params.ldap
142
-
143
- if not config or len(config) == 0:
144
- raise Exception("Missing LDAP configuration")
145
-
146
- ldap_config = {
147
- "uri": config["uri"],
148
- "bind_dn": config["bind_dn"],
149
- "bind_password": config["password"],
150
- "timeout": 30,
151
- "verify_cert": True,
152
- "page_size": 10000,
153
- "uid": "sAMAccountName",
154
- "user_base": config["base_dn"],
155
- "user_filter": "(objectcategory=user)",
156
- }
157
-
158
- auth_provider = {
159
- "ldap": {
160
- "type": "ldap",
161
- "title": provider_title,
162
- "ldap": ldap_config,
163
- },
164
- }
165
-
166
- identity_provider = {
167
- "ldap": {
168
- "type": "ldap",
169
- "title": provider_title,
170
- "ldap": ldap_config,
171
- "identifier_field": "mail",
172
- "accepted_users": "all",
173
- "mapping": {
174
- "first_name": "givenName",
175
- "last_name": "sn",
176
- "email": "mail",
177
- "affiliation": "company",
178
- "phone": "telephoneNumber",
179
- },
180
- "trusted_email": True,
181
- },
182
- }
183
-
184
- app.config["MULTIPASS_AUTH_PROVIDERS"] = auth_provider
185
- app.config["MULTIPASS_IDENTITY_PROVIDERS"] = identity_provider
186
- app.config["MULTIPASS_PROVIDER_MAP"] = {"ldap": "ldap"}
187
- app.config["MULTIPASS_IDENTITY_INFO_KEYS"] = {"first_name", "last_name", "email"}
188
- app.config["MULTIPASS_LOGIN_FORM_TEMPLATE"] = "login_form.html"
189
- app.config["MULTIPASS_SUCCESS_ENDPOINT"] = "/"
190
- app.config["MULTIPASS_FAILURE_MESSAGE"] = "Login failed: {error}"
191
-
192
-
193
- multipass = OSITAHMultipass()
194
-
195
-
196
- @multipass.identity_handler
197
- def identity_handler(identity_info):
198
- if identity_info.identifier in identity_list:
199
- user = identity_list[identity_info.identifier]
200
- identity = user.get_first_identity()
201
- else:
202
- if identity_info.data["email"] in user_list:
203
- user = user_list[identity_info.data["email"]]
204
- else:
205
- user = User(**identity_info.data.to_dict())
206
- user_list[user.email] = user
207
- identity = Identity(
208
- provider=identity_info.provider.name, identifier=identity_info.identifier
209
- )
210
- user.add_identity(identity)
211
- identity_list[identity.id] = user
212
- identity.multipass_data = json.dumps(identity_info.multipass_data)
213
- session["user_id"] = identity.id
214
- flash("Received IdentityInfo: {}".format(identity_info), "success")
215
-
216
-
217
- def login_required(view):
218
- """
219
- A decorator to require login on Flask views
220
-
221
- :param view: a function
222
- :return: decorated function
223
- """
224
-
225
- @functools.wraps(view)
226
- def wrapped_view(**kwargs):
227
- redirect_path = urlparse(request.base_url).path
228
- if len(redirect_path) == 0:
229
- redirect_path = "/"
230
- if "user_id" not in session:
231
- if redirect_path != "/favicon.ico":
232
- return redirect(f"{LOGIN_URL}?next={redirect_path}")
233
- elif redirect_path == LOGOUT_URL:
234
- remove_session()
235
- return multipass.logout("/", clear_session=True)
236
-
237
- return view(**kwargs)
238
-
239
- return wrapped_view
240
-
241
-
242
- def protect_views(app):
243
- for view_func in app.server.view_functions:
244
- if view_func.startswith("/<path:path>"):
245
- app.server.view_functions[view_func] = login_required(
246
- app.server.view_functions[view_func]
247
- )
248
-
249
- return app
250
-
251
-
252
- def remove_session():
253
- """
254
- Remove a session from the database and do additional session cleanup.
255
-
256
- :return: None
257
- """
258
-
259
- from sqlalchemy import delete
260
-
261
- from ositah.utils.hito_db import get_db
262
- from ositah.utils.hito_db_model import OSITAHSession
263
-
264
- global_params = GlobalParams()
265
-
266
- if "uid" in session:
267
- del global_params.session_data
268
-
269
- if session["user_id"] in identity_list:
270
- del user_list[identity_list[session["user_id"]].email]
271
- del identity_list[session["user_id"]]
272
- else:
273
- print(
274
- (
275
- f"WARNING: attempt to delete a non-existing user/identity"
276
- f" {session['uid']} (user={session['user_id']})"
277
- )
278
- )
279
-
280
- if "user_email" in session:
281
- sql_cmd = delete(OSITAHSession).where(
282
- OSITAHSession.id == session["uid"],
283
- OSITAHSession.email == session["user_email"],
284
- )
285
- db = get_db()
286
- db.session.execute(sql_cmd)
287
- db.session.commit()
ositah/utils/cache.py DELETED
@@ -1,19 +0,0 @@
1
- # Helper functions to manage the data cache
2
-
3
- from ositah.utils.exceptions import SessionDataMissing
4
- from ositah.utils.utils import GlobalParams, no_session_id_jumbotron
5
-
6
-
7
- def clear_cached_data():
8
- """
9
- Clear the data cached by the previous requests
10
-
11
- :return: None
12
- """
13
-
14
- global_params = GlobalParams()
15
- try:
16
- session_data = global_params.session_data
17
- session_data.reset_caches()
18
- except SessionDataMissing:
19
- return no_session_id_jumbotron()
ositah/utils/core.py DELETED
@@ -1,13 +0,0 @@
1
- # Module with utility functions
2
-
3
-
4
- # Singleton decorator definition
5
- def singleton(cls):
6
- instances = {}
7
-
8
- def getinstance(*args, **kwargs):
9
- if cls not in instances:
10
- instances[cls] = cls(*args, **kwargs)
11
- return instances[cls]
12
-
13
- return getinstance
@@ -1,64 +0,0 @@
1
- # OSITAH exceptions not specific to one of the sub-application
2
-
3
- from hito_tools.exceptions import EXIT_STATUS_GENERAL_ERROR
4
-
5
-
6
- class InvalidCallbackInput(Exception):
7
- def __init__(self, input_name):
8
- self.msg = f"internal error: invalid input ({input_name}) in callback"
9
- self.status = EXIT_STATUS_GENERAL_ERROR
10
-
11
- def __str__(self):
12
- return repr(self.msg)
13
-
14
-
15
- class InvalidDataSource(Exception):
16
- def __init__(self, source):
17
- self.msg = f"attempt to use and invalid data source ({source})"
18
- self.status = EXIT_STATUS_GENERAL_ERROR
19
-
20
- def __str__(self):
21
- return repr(self.msg)
22
-
23
-
24
- class InvalidHitoProjectName(Exception):
25
- def __init__(self, projects):
26
- self.msg = (
27
- f"The following Hito project names don't match the format 'masterproject / project' :"
28
- f" {', '.join(projects)}"
29
- )
30
- self.status = EXIT_STATUS_GENERAL_ERROR
31
-
32
- def __str__(self):
33
- return repr(self.msg)
34
-
35
-
36
- class SessionDataMissing(Exception):
37
- def __init__(self, session_id=None):
38
- if session_id:
39
- session_txt = f" (session={session_id})"
40
- else:
41
- session_txt = ""
42
- self.msg = f"Attempt to use non existing session data{session_txt}"
43
- self.status = EXIT_STATUS_GENERAL_ERROR
44
-
45
- def __str__(self):
46
- return repr(self.msg)
47
-
48
-
49
- class ValidationPeriodAmbiguous(Exception):
50
- def __init__(self, date):
51
- self.msg = f"Configuration error: several periods matching {date}"
52
- self.status = EXIT_STATUS_GENERAL_ERROR
53
-
54
- def __str__(self):
55
- return repr(self.msg)
56
-
57
-
58
- class ValidationPeriodMissing(Exception):
59
- def __init__(self, date):
60
- self.msg = f"No defined declaration period matching {date}"
61
- self.status = EXIT_STATUS_GENERAL_ERROR
62
-
63
- def __str__(self):
64
- return repr(self.msg)