PyFunceble-dev 4.3.0a25__py3-none-any.whl → 4.4.0__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 (37) hide show
  1. PyFunceble/checker/availability/base.py +11 -3
  2. PyFunceble/checker/availability/domain.py +3 -1
  3. PyFunceble/checker/availability/domain_and_ip.py +2 -0
  4. PyFunceble/checker/base.py +13 -1
  5. PyFunceble/checker/reputation/base.py +10 -2
  6. PyFunceble/checker/syntax/base.py +5 -2
  7. PyFunceble/checker/utils/whois.py +7 -3
  8. PyFunceble/cli/facility.py +2 -0
  9. PyFunceble/cli/factory.py +2 -0
  10. PyFunceble/cli/filesystem/status_file.py +1 -1
  11. PyFunceble/cli/migrators/mariadb/file_and_status.py +8 -4
  12. PyFunceble/cli/processes/base.py +7 -0
  13. PyFunceble/cli/processes/workers/producer.py +9 -3
  14. PyFunceble/cli/processes/workers/tester.py +5 -5
  15. PyFunceble/cli/system/launcher.py +18 -5
  16. PyFunceble/cli/utils/testing.py +9 -3
  17. PyFunceble/database/sqlalchemy/base_schema.py +3 -5
  18. PyFunceble/dataset/autocontinue/csv.py +15 -1
  19. PyFunceble/dataset/base.py +25 -0
  20. PyFunceble/dataset/csv_base.py +4 -1
  21. PyFunceble/dataset/db_base.py +14 -0
  22. PyFunceble/dataset/iana.py +6 -1
  23. PyFunceble/dataset/inactive/csv.py +1 -1
  24. PyFunceble/dataset/ipv4_reputation.py +7 -1
  25. PyFunceble/dataset/public_suffix.py +6 -1
  26. PyFunceble/dataset/user_agent.py +6 -1
  27. PyFunceble/dataset/whois/csv.py +1 -1
  28. PyFunceble/facility.py +2 -0
  29. PyFunceble/factory.py +2 -0
  30. PyFunceble/helpers/command.py +2 -2
  31. PyFunceble/storage.py +1 -1
  32. {pyfunceble_dev-4.3.0a25.dist-info → pyfunceble_dev-4.4.0.dist-info}/METADATA +111 -110
  33. {pyfunceble_dev-4.3.0a25.dist-info → pyfunceble_dev-4.4.0.dist-info}/RECORD +37 -37
  34. {pyfunceble_dev-4.3.0a25.dist-info → pyfunceble_dev-4.4.0.dist-info}/WHEEL +0 -0
  35. {pyfunceble_dev-4.3.0a25.dist-info → pyfunceble_dev-4.4.0.dist-info}/entry_points.txt +0 -0
  36. {pyfunceble_dev-4.3.0a25.dist-info → pyfunceble_dev-4.4.0.dist-info}/licenses/LICENSE +0 -0
  37. {pyfunceble_dev-4.3.0a25.dist-info → pyfunceble_dev-4.4.0.dist-info}/top_level.txt +0 -0
@@ -54,7 +54,7 @@ License:
54
54
 
55
55
  import multiprocessing
56
56
  from datetime import datetime
57
- from typing import Dict, List, Optional
57
+ from typing import Any, Dict, List, Optional
58
58
 
59
59
  from sqlalchemy.orm import Session
60
60
 
@@ -110,8 +110,12 @@ class AvailabilityCheckerBase(CheckerBase):
110
110
  Optional, Activates/Disables the check of the status before the actual
111
111
  status gathering.
112
112
  :param bool use_whois_db:
113
- Optional, Activates/Disable the usage of a local database to store the
113
+ Optional, Activates/Disables the usage of a local database to store the
114
114
  WHOIS datasets.
115
+ :param bool use_platform:
116
+ Optional, Activates/Disables the usage of the platform features.
117
+ :param multiprocessing.Lock shared_lock:
118
+ Optional, The shared lock to use to access shared resources.
115
119
  """
116
120
 
117
121
  # pylint: disable=too-many-public-methods, too-many-instance-attributes
@@ -160,6 +164,7 @@ class AvailabilityCheckerBase(CheckerBase):
160
164
  db_session: Optional[Session] = None,
161
165
  use_whois_db: Optional[bool] = None,
162
166
  use_platform: Optional[bool] = None,
167
+ shared_lock: Optional[Any] = None,
163
168
  ) -> None:
164
169
  self.dns_query_tool = DNSQueryTool()
165
170
  self.whois_query_tool = WhoisQueryTool()
@@ -226,6 +231,7 @@ class AvailabilityCheckerBase(CheckerBase):
226
231
  do_syntax_check_first=do_syntax_check_first,
227
232
  db_session=db_session,
228
233
  use_platform=use_platform,
234
+ shared_lock=shared_lock,
229
235
  )
230
236
 
231
237
  @property
@@ -761,7 +767,9 @@ class AvailabilityCheckerBase(CheckerBase):
761
767
  if (
762
768
  PyFunceble.facility.ConfigLoader.is_already_loaded() and self.use_whois_db
763
769
  ): # pragma: no cover ## Not interesting enough to spend time on it.
764
- whois_object = get_whois_dataset_object(db_session=self.db_session)
770
+ whois_object = get_whois_dataset_object(
771
+ db_session=self.db_session, shared_lock=self.shared_lock
772
+ )
765
773
  known_record = whois_object[self.subject]
766
774
 
767
775
  if known_record and not isinstance(known_record, dict):
@@ -115,7 +115,9 @@ class DomainAvailabilityChecker(AvailabilityCheckerBase):
115
115
  self.status.idna_subject,
116
116
  )
117
117
 
118
- lookup_result = DomainReputationChecker(self.status.idna_subject).get_status()
118
+ lookup_result = DomainReputationChecker(
119
+ self.status.idna_subject, shared_lock=self.shared_lock
120
+ ).get_status()
119
121
 
120
122
  # pylint: disable=no-member
121
123
  if lookup_result and lookup_result.is_malicious():
@@ -112,6 +112,7 @@ class DomainAndIPAvailabilityChecker(AvailabilityCheckerBase):
112
112
  db_session=self.db_session,
113
113
  use_whois_db=self.use_whois_db,
114
114
  use_platform=self.use_platform,
115
+ shared_lock=self.shared_lock,
115
116
  )
116
117
  else:
117
118
  query_object = DomainAvailabilityChecker(
@@ -126,6 +127,7 @@ class DomainAndIPAvailabilityChecker(AvailabilityCheckerBase):
126
127
  db_session=self.db_session,
127
128
  use_whois_db=self.use_whois_db,
128
129
  use_platform=self.use_platform,
130
+ shared_lock=self.shared_lock,
129
131
  )
130
132
 
131
133
  query_object.dns_query_tool = self.dns_query_tool
@@ -52,7 +52,7 @@ License:
52
52
 
53
53
  import datetime
54
54
  import functools
55
- from typing import Optional
55
+ from typing import Any, Optional
56
56
 
57
57
  import domain2idna
58
58
  from sqlalchemy.orm import Session
@@ -77,6 +77,12 @@ class CheckerBase:
77
77
 
78
78
  .. warning::
79
79
  This does not apply to the syntax checker - itself.
80
+ :param Session db_session:
81
+ Optional, The database session to use.
82
+ :param bool use_platform:
83
+ Optional, Authorizes the usage of the platform.
84
+ :param Any shared_lock:
85
+ Optional, The shared lock to use to access shared resources.
80
86
  """
81
87
 
82
88
  STD_DO_SYNTAX_CHECK_FIRST: bool = False
@@ -97,6 +103,8 @@ class CheckerBase:
97
103
  status: Optional[CheckerStatusBase] = None
98
104
  params: Optional[CheckerParamsBase] = None
99
105
 
106
+ shared_lock: Optional[Any] = None
107
+
100
108
  def __init__(
101
109
  self,
102
110
  subject: Optional[str] = None,
@@ -104,6 +112,7 @@ class CheckerBase:
104
112
  do_syntax_check_first: Optional[bool] = None,
105
113
  db_session: Optional[Session] = None,
106
114
  use_platform: Optional[bool] = None,
115
+ shared_lock: Optional[Any] = None,
107
116
  ) -> None:
108
117
  self.platform_query_tool = PlatformQueryTool()
109
118
  self.url2netloc = Url2Netloc()
@@ -115,6 +124,9 @@ class CheckerBase:
115
124
  if self.status is None:
116
125
  self.status = CheckerStatusBase()
117
126
 
127
+ if shared_lock is not None:
128
+ self.shared_lock = shared_lock
129
+
118
130
  if subject is not None:
119
131
  self.subject = subject
120
132
 
@@ -50,7 +50,7 @@ License:
50
50
  limitations under the License.
51
51
  """
52
52
 
53
- from typing import List, Optional
53
+ from typing import Any, List, Optional
54
54
 
55
55
  from sqlalchemy.orm import Session
56
56
 
@@ -75,6 +75,12 @@ class ReputationCheckerBase(CheckerBase):
75
75
  :param bool do_syntax_check_first:
76
76
  Optional, Activates/Disables the check of the status before the actual
77
77
  status gathering.
78
+ :param Session db_session:
79
+ Optional, The database session to use.
80
+ :param bool use_platform:
81
+ Optional, Authorizes the usage of the platform.
82
+ :param Any shared_lock:
83
+ Optional, The shared lock to use to access shared resources.
78
84
  """
79
85
 
80
86
  dns_query_tool: Optional[DNSQueryTool] = None
@@ -92,9 +98,10 @@ class ReputationCheckerBase(CheckerBase):
92
98
  do_syntax_check_first: Optional[bool] = None,
93
99
  db_session: Optional[Session] = None,
94
100
  use_platform: Optional[bool] = None,
101
+ shared_lock: Optional[Any] = None,
95
102
  ) -> None:
96
103
  self.dns_query_tool = DNSQueryTool()
97
- self.ipv4_reputation_query_tool = IPV4ReputationDataset()
104
+ self.ipv4_reputation_query_tool = IPV4ReputationDataset(shared_lock=shared_lock)
98
105
  self.domain_syntax_checker = DomainSyntaxChecker()
99
106
  self.ip_syntax_checker = IPSyntaxChecker()
100
107
  self.url_syntax_checker = URLSyntaxChecker()
@@ -110,6 +117,7 @@ class ReputationCheckerBase(CheckerBase):
110
117
  do_syntax_check_first=do_syntax_check_first,
111
118
  db_session=db_session,
112
119
  use_platform=use_platform,
120
+ shared_lock=shared_lock,
113
121
  )
114
122
 
115
123
  @staticmethod
@@ -50,7 +50,7 @@ License:
50
50
  limitations under the License.
51
51
  """
52
52
 
53
- from typing import Optional
53
+ from typing import Any, Optional
54
54
 
55
55
  from sqlalchemy.orm import Session
56
56
 
@@ -78,13 +78,16 @@ class SyntaxCheckerBase(CheckerBase):
78
78
  self,
79
79
  subject: Optional[str] = None,
80
80
  db_session: Optional[Session] = None,
81
+ shared_lock: Optional[Any] = None,
81
82
  ) -> None:
82
83
  self.params = SyntaxCheckerParams()
83
84
 
84
85
  self.status = SyntaxCheckerStatus()
85
86
  self.status.params = self.params
86
87
 
87
- super().__init__(subject=subject, db_session=db_session)
88
+ super().__init__(
89
+ subject=subject, db_session=db_session, shared_lock=shared_lock
90
+ )
88
91
 
89
92
  def subject_propagator(self) -> "CheckerBase":
90
93
  """
@@ -50,7 +50,7 @@ License:
50
50
  limitations under the License.
51
51
  """
52
52
 
53
- from typing import Optional, Union
53
+ from typing import Any, Optional, Union
54
54
 
55
55
  from sqlalchemy.orm import Session
56
56
 
@@ -64,13 +64,17 @@ from PyFunceble.dataset.whois.sql import SQLDBWhoisDataset
64
64
 
65
65
 
66
66
  def get_whois_dataset_object(
67
- *, db_session: Optional[Session] = None
67
+ *,
68
+ db_session: Optional[Session] = None,
69
+ shared_lock: Optional[Any] = None,
68
70
  ) -> Union[DatasetBase, CSVDatasetBase, DBDatasetBase]:
69
71
  """
70
72
  Provides the whois dataset object to work with.
71
73
 
72
74
  :param db_session:
73
75
  A database session to use.
76
+ :param shared_lock:
77
+ Optional, The shared lock to use to access shared resources.
74
78
 
75
79
  :raise ValueError:
76
80
  When the given database type is unknown.
@@ -80,7 +84,7 @@ def get_whois_dataset_object(
80
84
 
81
85
  if PyFunceble.facility.ConfigLoader.is_already_loaded():
82
86
  if PyFunceble.storage.CONFIGURATION.cli_testing.db_type == "csv":
83
- return CSVWhoisDataset().set_authorized(
87
+ return CSVWhoisDataset(shared_lock=shared_lock).set_authorized(
84
88
  bool(PyFunceble.storage.CONFIGURATION.cli_testing.whois_db)
85
89
  )
86
90
 
@@ -52,4 +52,6 @@ License:
52
52
 
53
53
  from PyFunceble.cli.credential_loader import CredentialLoader as credential_loader
54
54
 
55
+ # pylint: disable=invalid-name
56
+
55
57
  CredentialLoader = credential_loader()
PyFunceble/cli/factory.py CHANGED
@@ -52,4 +52,6 @@ License:
52
52
 
53
53
  from PyFunceble.database.session import DBSession as db_session
54
54
 
55
+ # pylint: disable=invalid-name
56
+
55
57
  DBSession = db_session()
@@ -77,7 +77,7 @@ class StatusFileGenerator(FilesystemDirBase):
77
77
 
78
78
  # pylint: disable=too-many-public-methods
79
79
 
80
- STD_HOSTS_IP: str = "0.0.0.0"
80
+ STD_HOSTS_IP: str = "0.0.0.0" # nosec: B104 # Default to non-routable IP
81
81
  STD_ALLOW_HOSTS_FILES: bool = True
82
82
  STD_ALLOW_PLAIN_FILES: bool = True
83
83
  STD_ALLOW_ANALYTIC_FILES: bool = True
@@ -106,8 +106,8 @@ class FileAndStatusMigrator(MariaDBMigratorBase):
106
106
 
107
107
  destination = get_destination_from_origin(file_info["path"])
108
108
 
109
- for status in self.get_rows(
110
- f"SELECT * from pyfunceble_status WHERE file_id = {file_info['id']}"
109
+ for status in self.get_rows( # nosec: B608 # Self-Controlled Input
110
+ "SELECT * from pyfunceble_status WHERE file_id = {file_info['id']}"
111
111
  ):
112
112
  if (
113
113
  self.continuous_integration
@@ -144,7 +144,9 @@ class FileAndStatusMigrator(MariaDBMigratorBase):
144
144
 
145
145
  # pylint: disable=line-too-long
146
146
  self.db_session.execute(
147
- text(f"DELETE from pyfunceble_status WHERE id = {status['id']}")
147
+ text(
148
+ f"DELETE from pyfunceble_status WHERE id = {status['id']}" # nosec: B608 # Self-Controlled Input
149
+ )
148
150
  )
149
151
  self.db_session.commit()
150
152
 
@@ -155,7 +157,9 @@ class FileAndStatusMigrator(MariaDBMigratorBase):
155
157
  if drop_table:
156
158
  # pylint: disable=line-too-long
157
159
  self.db_session.execute(
158
- text(f"DELETE from pyfunceble_file WHERE id = {file_info['id']}")
160
+ text(
161
+ f"DELETE from pyfunceble_file WHERE id = {file_info['id']}" # nosec: B608 # Self-Controlled Input
162
+ )
159
163
  )
160
164
  self.db_session.commit()
161
165
 
@@ -60,3 +60,10 @@ class ProcessesManagerBase(PyFunceble.ext.process_manager.ProcessManagerCore):
60
60
  """
61
61
  Provides the base of all classes.
62
62
  """
63
+
64
+ def __init__(self, *args, **kwargs) -> None:
65
+ # Better handling of shared locks.
66
+ # This help be be retro-compatible. Allowing existing users to
67
+ # not get exceptions.
68
+ shared_lock = kwargs.pop("shared_lock", None)
69
+ super().__init__(*args, **kwargs, shared_lock=shared_lock)
@@ -125,9 +125,15 @@ class ProducerWorker(WorkerBase):
125
125
  self.file_printer = FilePrinter(
126
126
  skip_column=skip_columns, extra_formatters=extra_formatters
127
127
  )
128
- self.whois_dataset = get_whois_dataset_object(db_session=self.db_session)
129
- self.inactive_dataset = get_inactive_dataset_object(db_session=self.db_session)
130
- self.continue_dataset = get_continue_dataset_object(db_session=self.db_session)
128
+ self.whois_dataset = get_whois_dataset_object(
129
+ db_session=self.db_session, shared_lock=self.shared_lock
130
+ )
131
+ self.inactive_dataset = get_inactive_dataset_object(
132
+ db_session=self.db_session, shared_lock=self.shared_lock
133
+ )
134
+ self.continue_dataset = get_continue_dataset_object(
135
+ db_session=self.db_session, shared_lock=self.shared_lock
136
+ )
131
137
  self.status_file_generator = StatusFileGenerator().guess_all_settings()
132
138
  self.counter = FilesystemCounter()
133
139
  self.registrar_counter = RegistrarCounter()
@@ -90,12 +90,12 @@ class TesterWorker(WorkerBase):
90
90
 
91
91
  self.continue_dataset = (
92
92
  PyFunceble.cli.utils.testing.get_continue_dataset_object(
93
- db_session=self.db_session
93
+ db_session=self.db_session, shared_lock=self.shared_lock
94
94
  )
95
95
  )
96
96
  self.inactive_dataset = (
97
97
  PyFunceble.cli.utils.testing.get_inactive_dataset_object(
98
- db_session=self.db_session
98
+ db_session=self.db_session, shared_lock=self.shared_lock
99
99
  )
100
100
  )
101
101
 
@@ -191,7 +191,7 @@ class TesterWorker(WorkerBase):
191
191
  self.initiated_testing_objects[checker_type][
192
192
  subject_type
193
193
  ] = self.known_testing_objects[checker_type][subject_type](
194
- db_session=self.db_session
194
+ db_session=self.db_session, shared_lock=self.shared_lock
195
195
  ).set_do_syntax_check_first(
196
196
  # We want to always check the syntax first (ONLY UNDER THE CLI)
197
197
  not bool(PyFunceble.storage.CONFIGURATION.cli_testing.local_network)
@@ -278,12 +278,12 @@ class TesterWorker(WorkerBase):
278
278
  test_dataset["idna_subject"],
279
279
  )
280
280
 
281
- self._init_testing_object(
281
+ testing_object = self._init_testing_object(
282
282
  test_dataset["subject_type"], test_dataset["checker_type"]
283
283
  )
284
284
 
285
285
  result = (
286
- self.testing_object.set_subject(test_dataset["idna_subject"])
286
+ testing_object.set_subject(test_dataset["idna_subject"])
287
287
  .query_status()
288
288
  .get_status()
289
289
  )
@@ -156,6 +156,7 @@ class SystemLauncher(SystemBase):
156
156
  file_preloader: Optional[FilePreloader] = None
157
157
 
158
158
  manager: Optional[multiprocessing.Manager]
159
+ shared_lock: Optional[multiprocessing.Lock] = None
159
160
  tester_process_manager: Optional[
160
161
  Union[TesterProcessesManager, ChancyTesterProcessesManager]
161
162
  ] = None
@@ -185,11 +186,20 @@ class SystemLauncher(SystemBase):
185
186
  except TypeError:
186
187
  self.db_session = None
187
188
 
189
+ self.manager = multiprocessing.Manager()
190
+ self.shared_lock = multiprocessing.Lock()
191
+
188
192
  self.execution_time_holder = ExecutionTime().set_start_time()
189
193
  self.checker_type = get_testing_mode()
190
- self.continue_dataset = get_continue_dataset_object(db_session=self.db_session)
191
- self.inactive_dataset = get_inactive_dataset_object(db_session=self.db_session)
192
- self.whois_dataset = get_whois_dataset_object(db_session=self.db_session)
194
+ self.continue_dataset = get_continue_dataset_object(
195
+ db_session=self.db_session, shared_lock=self.shared_lock
196
+ )
197
+ self.inactive_dataset = get_inactive_dataset_object(
198
+ db_session=self.db_session, shared_lock=self.shared_lock
199
+ )
200
+ self.whois_dataset = get_whois_dataset_object(
201
+ db_session=self.db_session, shared_lock=self.shared_lock
202
+ )
193
203
  self.continuous_integration = ci_object()
194
204
 
195
205
  if self.continuous_integration.authorized:
@@ -197,8 +207,6 @@ class SystemLauncher(SystemBase):
197
207
 
198
208
  self.stdout_printer.guess_allow_coloration().guess_allow_background_coloration()
199
209
 
200
- self.manager = multiprocessing.Manager()
201
-
202
210
  if not PyFunceble.storage.CONFIGURATION.cli_testing.chancy_tester:
203
211
  self.tester_process_manager = TesterProcessesManager(
204
212
  max_workers=PyFunceble.storage.CONFIGURATION.cli_testing.max_workers,
@@ -215,6 +223,7 @@ class SystemLauncher(SystemBase):
215
223
  delay_shutdown=PyFunceble.storage.CONFIGURATION.cli_testing.mining,
216
224
  generate_configuration_queue=False,
217
225
  continuous_integration=self.continuous_integration,
226
+ shared_lock=self.shared_lock,
218
227
  )
219
228
 
220
229
  self.producer_process_manager = ProducerProcessesManager(
@@ -227,6 +236,7 @@ class SystemLauncher(SystemBase):
227
236
  delay_shutdown=PyFunceble.storage.CONFIGURATION.cli_testing.mining,
228
237
  generate_configuration_queue=False,
229
238
  continuous_integration=self.continuous_integration,
239
+ shared_lock=self.shared_lock,
230
240
  )
231
241
  else:
232
242
  self.tester_process_manager = ChancyTesterProcessesManager(
@@ -266,6 +276,7 @@ class SystemLauncher(SystemBase):
266
276
  daemon=True,
267
277
  generate_output_queue=False,
268
278
  generate_configuration_queue=False,
279
+ shared_lock=self.shared_lock,
269
280
  )
270
281
  self.migrator_process_manager = MigratorProcessesManager(
271
282
  manager=self.manager,
@@ -273,6 +284,7 @@ class SystemLauncher(SystemBase):
273
284
  daemon=True,
274
285
  generate_input_queue=False,
275
286
  generate_output_queue=False,
287
+ shared_lock=self.shared_lock,
276
288
  )
277
289
 
278
290
  if PyFunceble.storage.CONFIGURATION.cli_testing.mining:
@@ -287,6 +299,7 @@ class SystemLauncher(SystemBase):
287
299
  spread_stop_signal=False,
288
300
  spread_wait_signal=True,
289
301
  delay_shutdown=PyFunceble.storage.CONFIGURATION.cli_testing.mining,
302
+ shared_lock=self.shared_lock,
290
303
  )
291
304
 
292
305
  self.tester_process_manager.add_dependent_manager(
@@ -51,7 +51,7 @@ License:
51
51
  """
52
52
 
53
53
  import os
54
- from typing import List, Optional, Union
54
+ from typing import Any, List, Optional, Union
55
55
 
56
56
  from sqlalchemy.orm import Session
57
57
 
@@ -92,12 +92,15 @@ def get_testing_mode() -> str:
92
92
 
93
93
  def get_continue_dataset_object(
94
94
  db_session: Optional[Session] = None,
95
+ shared_lock: Optional[Any] = None,
95
96
  ) -> Union[DatasetBase, CSVDatasetBase, DBDatasetBase]:
96
97
  """
97
98
  Provides the continue object to work with.
98
99
 
99
100
  :param db_session:
100
101
  A database session to use.
102
+ :param shared_lock:
103
+ Optional, The shared lock to use to access shared resources.
101
104
 
102
105
  :raise ValueError:
103
106
  When the given database type is unkown.
@@ -106,7 +109,7 @@ def get_continue_dataset_object(
106
109
  result = None
107
110
 
108
111
  if PyFunceble.storage.CONFIGURATION.cli_testing.db_type in "csv":
109
- result = CSVContinueDataset()
112
+ result = CSVContinueDataset(shared_lock=shared_lock)
110
113
  elif PyFunceble.storage.CONFIGURATION.cli_testing.db_type in (
111
114
  "mariadb",
112
115
  "mysql",
@@ -129,12 +132,15 @@ def get_continue_dataset_object(
129
132
 
130
133
  def get_inactive_dataset_object(
131
134
  db_session: Optional[Session] = None,
135
+ shared_lock: Optional[Any] = None,
132
136
  ) -> Union[DatasetBase, CSVDatasetBase, DBDatasetBase]:
133
137
  """
134
138
  Provides the inactive object to work with.
135
139
 
136
140
  :param db_session:
137
141
  A database session to use.
142
+ :param shared_lock:
143
+ An optional shared lock to use.
138
144
 
139
145
  :raise ValueError:
140
146
  When the given database type is unkown.
@@ -143,7 +149,7 @@ def get_inactive_dataset_object(
143
149
  result = None
144
150
 
145
151
  if PyFunceble.storage.CONFIGURATION.cli_testing.db_type == "csv":
146
- result = CSVInactiveDataset()
152
+ result = CSVInactiveDataset(shared_lock=shared_lock)
147
153
  elif PyFunceble.storage.CONFIGURATION.cli_testing.db_type in (
148
154
  "mariadb",
149
155
  "mysql",
@@ -54,12 +54,13 @@ from datetime import datetime, timezone
54
54
 
55
55
  import inflection
56
56
  from sqlalchemy import BigInteger, Column, DateTime
57
- from sqlalchemy.ext.declarative import declarative_base, declared_attr
57
+ from sqlalchemy.ext.declarative import declared_attr
58
+ from sqlalchemy.orm import DeclarativeBase
58
59
 
59
60
  import PyFunceble.storage
60
61
 
61
62
 
62
- class OurSchemaBase:
63
+ class SchemaBase(DeclarativeBase):
63
64
  """
64
65
  Provides the base of all our schema.
65
66
  """
@@ -92,6 +93,3 @@ class OurSchemaBase:
92
93
  """
93
94
 
94
95
  return {x: y for x, y in self.__dict__.items() if not x.startswith("_sa")}
95
-
96
-
97
- SchemaBase = declarative_base(cls=OurSchemaBase)
@@ -66,6 +66,17 @@ class CSVContinueDataset(CSVDatasetBase, ContinueDatasetBase):
66
66
  """
67
67
  Provides the interface for the management of the continue
68
68
  CSV file.
69
+
70
+ :param bool authorized:
71
+ Optional, Authorizes/Disables the usage of this dataset.
72
+ If not set, :code:`STD_AUTHORIZED` is used.
73
+ :param bool remove_unneeded_fields:
74
+ Optional, Activates/Deactivates the removal of unneeded fields.
75
+ If not set, :code:`STD_REMOVE_UNNEEDED_FIELDS` is used.
76
+ :param str base_directory:
77
+ Optional, The base directory to use.
78
+ :param Any shared_lock:
79
+ Optional, The shared lock to use to access shared resources.
69
80
  """
70
81
 
71
82
  source_file: Optional[str] = None
@@ -78,12 +89,15 @@ class CSVContinueDataset(CSVDatasetBase, ContinueDatasetBase):
78
89
  authorized: Optional[bool] = None,
79
90
  remove_unneeded_fields: Optional[bool] = None,
80
91
  base_directory: Optional[str] = None,
92
+ shared_lock: Optional[Any] = None,
81
93
  ) -> None:
82
94
  if base_directory is not None:
83
95
  self.set_base_directory(base_directory)
84
96
 
85
97
  super().__init__(
86
- authorized=authorized, remove_unneeded_fields=remove_unneeded_fields
98
+ authorized=authorized,
99
+ remove_unneeded_fields=remove_unneeded_fields,
100
+ shared_lock=shared_lock,
87
101
  )
88
102
 
89
103
  def __contains__(self, value: str) -> bool:
@@ -57,11 +57,15 @@ import PyFunceble.storage
57
57
  from PyFunceble.downloader.base import DownloaderBase
58
58
  from PyFunceble.helpers.dict import DictHelper
59
59
  from PyFunceble.helpers.file import FileHelper
60
+ from PyFunceble.utils.platform import PlatformUtility
60
61
 
61
62
 
62
63
  class DatasetBase:
63
64
  """
64
65
  Provides the base of all dataset.
66
+
67
+ :param Any shared_lock:
68
+ Optional, The shared lock to use to access shared resources.
65
69
  """
66
70
 
67
71
  STORAGE_INDEX: Optional[str] = None
@@ -69,6 +73,12 @@ class DatasetBase:
69
73
 
70
74
  source_file: Optional[str] = None
71
75
 
76
+ shared_lock: Optional[Any] = None
77
+
78
+ def __init__(self, *, shared_lock: Optional[Any] = None) -> None:
79
+ if shared_lock is not None:
80
+ self.shared_lock = shared_lock
81
+
72
82
  def __contains__(self, value: Any): # pragma: no cover
73
83
  raise NotImplementedError()
74
84
 
@@ -110,7 +120,22 @@ class DatasetBase:
110
120
 
111
121
  return wrapper
112
122
 
123
+ def autolock(func): # pylint: disable=no-self-argument
124
+ """
125
+ A decorator to automatically lock and unlock the shared lock.
126
+ """
127
+
128
+ @functools.wraps(func)
129
+ def wrapper(self, *args, **kwargs):
130
+ if PlatformUtility.is_windows() and self.shared_lock is not None:
131
+ with self.shared_lock:
132
+ return func(self, *args, **kwargs) # pylint: disable=not-callable
133
+ return func(self, *args, **kwargs) # pylint: disable=not-callable
134
+
135
+ return wrapper
136
+
113
137
  @ensure_source_file_exists
138
+ @autolock
114
139
  def get_content(self) -> Optional[dict]:
115
140
  """
116
141
  Provides the cached or the real contend of the dataset (after caching)
@@ -118,6 +118,7 @@ class CSVDatasetBase(DBDatasetBase):
118
118
 
119
119
  @DBDatasetBase.ensure_source_file_exists
120
120
  @DBDatasetBase.execute_if_authorized(None)
121
+ @DBDatasetBase.autolock
121
122
  def add(self, row: dict) -> "CSVDatasetBase":
122
123
  """
123
124
  Adds the given dataset into the CSV file.
@@ -151,6 +152,7 @@ class CSVDatasetBase(DBDatasetBase):
151
152
 
152
153
  @DBDatasetBase.ensure_source_file_exists
153
154
  @DBDatasetBase.execute_if_authorized(None)
155
+ @DBDatasetBase.autolock
154
156
  def remove(self, row: dict) -> "CSVDatasetBase":
155
157
  """
156
158
  Removes the given dataset from the CSV file.
@@ -194,6 +196,7 @@ class CSVDatasetBase(DBDatasetBase):
194
196
 
195
197
  @DBDatasetBase.ensure_source_file_exists
196
198
  @DBDatasetBase.execute_if_authorized(None)
199
+ @DBDatasetBase.autolock
197
200
  def get_content(self) -> Generator[Optional[dict], None, None]:
198
201
  """
199
202
  Provides a generator which provides the next line to read.
@@ -211,7 +214,7 @@ class CSVDatasetBase(DBDatasetBase):
211
214
  row["tested_at"] = datetime.fromisoformat(
212
215
  row["tested_at"]
213
216
  ).astimezone(timezone.utc)
214
- except (TypeError, ValueError):
217
+ except (TypeError, ValueError, KeyError):
215
218
  row["tested_at"] = datetime.now(timezone.utc) - timedelta(
216
219
  days=365
217
220
  )