PyFunceble-dev 4.2.28a1__py3-none-any.whl → 4.3.0a1__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/cli/entry_points/pyfunceble/cli.py +22 -0
  2. PyFunceble/cli/filesystem/dir_structure/base.py +1 -2
  3. PyFunceble/cli/migrators/base.py +47 -1
  4. PyFunceble/cli/migrators/csv_file/inactive_source_delete.py +1 -1
  5. PyFunceble/cli/migrators/csv_file/whois_registrar_add.py +1 -1
  6. PyFunceble/cli/migrators/file_cleanup/hashes_file.py +1 -1
  7. PyFunceble/cli/migrators/file_cleanup/mining_file.py +1 -1
  8. PyFunceble/cli/migrators/file_cleanup/production_config_file.py +1 -1
  9. PyFunceble/cli/migrators/json2csv/inactive.py +1 -1
  10. PyFunceble/cli/migrators/json2csv/whois.py +1 -1
  11. PyFunceble/cli/scripts/production.py +67 -26
  12. PyFunceble/cli/system/integrator.py +51 -14
  13. PyFunceble/cli/system/launcher.py +17 -6
  14. PyFunceble/config/loader.py +146 -22
  15. PyFunceble/database/credential/base.py +46 -3
  16. PyFunceble/dataset/base.py +3 -3
  17. PyFunceble/dataset/csv_base.py +3 -1
  18. PyFunceble/dataset/db_base.py +44 -0
  19. PyFunceble/dataset/iana.py +3 -6
  20. PyFunceble/dataset/inactive/csv.py +4 -2
  21. PyFunceble/dataset/ipv4_reputation.py +5 -9
  22. PyFunceble/dataset/public_suffix.py +4 -8
  23. PyFunceble/dataset/user_agent.py +4 -7
  24. PyFunceble/dataset/whois/csv.py +1 -1
  25. PyFunceble/downloader/base.py +64 -5
  26. PyFunceble/downloader/iana.py +4 -11
  27. PyFunceble/downloader/ipv4_reputation.py +2 -11
  28. PyFunceble/downloader/public_suffix.py +5 -11
  29. PyFunceble/downloader/user_agents.py +5 -11
  30. PyFunceble/query/whois/query_tool.py +1 -0
  31. PyFunceble/storage.py +2 -19
  32. {PyFunceble_dev-4.2.28a1.dist-info → PyFunceble_dev-4.3.0a1.dist-info}/METADATA +67 -67
  33. {PyFunceble_dev-4.2.28a1.dist-info → PyFunceble_dev-4.3.0a1.dist-info}/RECORD +37 -37
  34. {PyFunceble_dev-4.2.28a1.dist-info → PyFunceble_dev-4.3.0a1.dist-info}/LICENSE +0 -0
  35. {PyFunceble_dev-4.2.28a1.dist-info → PyFunceble_dev-4.3.0a1.dist-info}/WHEEL +0 -0
  36. {PyFunceble_dev-4.2.28a1.dist-info → PyFunceble_dev-4.3.0a1.dist-info}/entry_points.txt +0 -0
  37. {PyFunceble_dev-4.2.28a1.dist-info → PyFunceble_dev-4.3.0a1.dist-info}/top_level.txt +0 -0
@@ -60,6 +60,7 @@ except ImportError: # pragma: no cover ## Retro compatibility
60
60
  import importlib_resources as package_resources
61
61
 
62
62
  from box import Box
63
+ from dotenv import load_dotenv
63
64
  from yaml.error import MarkedYAMLError
64
65
 
65
66
  import PyFunceble.cli.storage
@@ -69,6 +70,7 @@ from PyFunceble.downloader.iana import IANADownloader
69
70
  from PyFunceble.downloader.public_suffix import PublicSuffixDownloader
70
71
  from PyFunceble.downloader.user_agents import UserAgentsDownloader
71
72
  from PyFunceble.helpers.dict import DictHelper
73
+ from PyFunceble.helpers.download import DownloadHelper
72
74
  from PyFunceble.helpers.environment_variable import EnvironmentVariableHelper
73
75
  from PyFunceble.helpers.file import FileHelper
74
76
  from PyFunceble.helpers.merge import Merge
@@ -86,31 +88,42 @@ class ConfigLoader:
86
88
  :code:`PYFUNCEBLE_AUTO_CONFIGURATION` environment variable.
87
89
  """
88
90
 
89
- path_to_config: Optional[str] = None
91
+ _path_to_config: Optional[str] = None
92
+ _remote_config_location: Optional[str] = None
90
93
  path_to_default_config: Optional[str] = None
91
94
  path_to_overwrite_config: Optional[str] = None
92
95
 
93
96
  _custom_config: dict = {}
94
97
  _merge_upstream: bool = False
98
+ _config_dir: Optional[str] = None
95
99
 
96
100
  file_helper: FileHelper = FileHelper()
97
101
  dict_helper: DictHelper = DictHelper()
98
102
 
99
- def __init__(self, merge_upstream: Optional[bool] = None) -> None:
103
+ def __init__(
104
+ self, merge_upstream: Optional[bool] = None, *, config_dir: Optional[str] = None
105
+ ) -> None:
100
106
  with package_resources.path(
101
107
  "PyFunceble.data.infrastructure",
102
108
  PyFunceble.storage.DISTRIBUTED_CONFIGURATION_FILENAME,
103
109
  ) as file_path:
104
110
  self.path_to_default_config = str(file_path)
105
111
 
112
+ if config_dir is not None:
113
+ self.config_dir = config_dir
114
+ else:
115
+ self.config_dir = PyFunceble.storage.CONFIG_DIRECTORY
116
+
106
117
  self.path_to_config = os.path.join(
107
- PyFunceble.storage.CONFIG_DIRECTORY,
118
+ self.config_dir,
108
119
  PyFunceble.storage.CONFIGURATION_FILENAME,
109
120
  )
110
121
 
122
+ self.path_to_remote_config = None
123
+
111
124
  self.path_to_overwrite_config = os.path.join(
112
- PyFunceble.storage.CONFIG_DIRECTORY,
113
- PyFunceble.storage.CONFIGURATION_OVERWRITE_FILENAME,
125
+ self.config_dir,
126
+ ".PyFunceble.overwrite.yaml",
114
127
  )
115
128
 
116
129
  if merge_upstream is not None:
@@ -132,7 +145,7 @@ class ConfigLoader:
132
145
  result = func(self, *args, **kwargs) # pylint: disable=not-callable
133
146
 
134
147
  if self.is_already_loaded():
135
- self.start()
148
+ self.reload(keep_custom=True)
136
149
 
137
150
  return result
138
151
 
@@ -200,6 +213,44 @@ class ConfigLoader:
200
213
 
201
214
  return bool(PyFunceble.storage.CONFIGURATION)
202
215
 
216
+ @property
217
+ def config_dir(self) -> Optional[str]:
218
+ """
219
+ Provides the current state of the :code:`_config_dir` attribute.
220
+ """
221
+
222
+ return self._config_dir
223
+
224
+ @config_dir.setter
225
+ @reload_config
226
+ def config_dir(self, value: str) -> None:
227
+ """
228
+ Sets the configuration directory.
229
+
230
+ :param value:
231
+ The value to set.
232
+
233
+ :raise TypeError:
234
+ When value is not a :py:class:`str`.
235
+ """
236
+
237
+ if not isinstance(value, str):
238
+ raise TypeError(f"<value> should be {str}, {type(value)} given.")
239
+
240
+ self._config_dir = value
241
+
242
+ def set_config_dir(self, value: str) -> "ConfigLoader":
243
+ """
244
+ Sets the configuration directory.
245
+
246
+ :param value:
247
+ The value to set.
248
+ """
249
+
250
+ self.config_dir = value
251
+
252
+ return self
253
+
203
254
  @property
204
255
  def custom_config(self) -> dict:
205
256
  """
@@ -274,23 +325,44 @@ class ConfigLoader:
274
325
 
275
326
  return self
276
327
 
277
- def config_file_exist(
278
- self,
279
- ) -> bool: # pragma: no cover ## Existance checker already tested.
328
+ @property
329
+ def remote_config_location(self) -> Optional[str]:
280
330
  """
281
- Checks if the config file exists.
331
+ Provides the current state of the :code:`_remote_config_location` attribute.
282
332
  """
283
333
 
284
- return FileHelper(self.path_to_config).exists()
334
+ return self._remote_config_location
285
335
 
286
- def default_config_file_exist(
287
- self,
288
- ) -> bool: # pragma: no cover ## Existance checker already tested.
336
+ @remote_config_location.setter
337
+ def remote_config_location(self, value: Optional[str]) -> None:
289
338
  """
290
- Checks if the default configuration file exists.
339
+ Updates the value of :code:`_remote_config_location` attribute.
340
+
341
+ :raise TypeError:
342
+ When :code:`value` is not a :py:class:`str`.
291
343
  """
292
344
 
293
- return self.file_helper.set_path(self.path_to_default_config).exists()
345
+ if value is not None and not isinstance(value, str):
346
+ raise TypeError(f"<value> should be {str}, {type(value)} given.")
347
+
348
+ if not value.startswith("http") and not value.startswith("https"):
349
+ self.path_to_remote_config = os.path.realpath(value)
350
+ else:
351
+ self.path_to_remote_config = os.path.join(
352
+ self.config_dir,
353
+ ".PyFunceble.remote.yaml",
354
+ )
355
+
356
+ self._remote_config_location = value
357
+
358
+ def set_remote_config_location(self, value: Optional[str]) -> "ConfigLoader":
359
+ """
360
+ Updates the value of :code:`_remote_config_location` attribute.
361
+ """
362
+
363
+ self.remote_config_location = value
364
+
365
+ return self
294
366
 
295
367
  def install_missing_infrastructure_files(
296
368
  self,
@@ -347,9 +419,34 @@ class ConfigLoader:
347
419
 
348
420
  return config and "days_between_inactive_db_clean" in config
349
421
 
422
+ def download_remote_config(src: str, dest: str = None) -> None:
423
+ """
424
+ Downloads the remote configuration.
425
+
426
+ :param src:
427
+ The source to download from.
428
+ :param dest:
429
+ The destination to download
430
+ """
431
+
432
+ if src and (src.startswith("http") or src.startswith("https")):
433
+ if dest is None:
434
+ destination = os.path.join(
435
+ self.config_dir,
436
+ os.path.basename(dest),
437
+ )
438
+ else:
439
+ destination = dest
440
+
441
+ DownloadHelper(src).download_text(destination=destination)
442
+
350
443
  if not self.is_already_loaded():
351
444
  self.install_missing_infrastructure_files()
352
445
  self.download_dynamic_infrastructure_files()
446
+ download_remote_config(
447
+ self.remote_config_location, self.path_to_remote_config
448
+ )
449
+ download_remote_config(self.path_to_config)
353
450
 
354
451
  try:
355
452
  config = self.dict_helper.from_yaml_file(self.path_to_config)
@@ -377,15 +474,22 @@ class ConfigLoader:
377
474
 
378
475
  self.dict_helper.set_subject(config).to_yaml_file(self.path_to_config)
379
476
 
477
+ if (
478
+ self.path_to_remote_config
479
+ and self.file_helper.set_path(self.path_to_remote_config).exists()
480
+ ):
481
+ remote_data = self.dict_helper.from_yaml_file(self.path_to_remote_config)
482
+
483
+ if isinstance(remote_data, dict):
484
+ config = Merge(remote_data).into(config)
485
+
380
486
  if self.file_helper.set_path(self.path_to_overwrite_config).exists():
381
487
  overwrite_data = self.dict_helper.from_yaml_file(
382
488
  self.path_to_overwrite_config
383
489
  )
384
490
 
385
491
  if isinstance(overwrite_data, dict):
386
- config = Merge(
387
- self.dict_helper.from_yaml_file(self.path_to_overwrite_config)
388
- ).into(config)
492
+ config = Merge(overwrite_data).into(config)
389
493
  else: # pragma: no cover ## Just make it visible to end-user.
390
494
  self.file_helper.write("")
391
495
 
@@ -415,11 +519,26 @@ class ConfigLoader:
415
519
 
416
520
  return PyFunceble.storage.FLATTEN_CONFIGURATION[entry]
417
521
 
522
+ def reload(self, keep_custom: bool = False) -> "ConfigLoader":
523
+ """
524
+ Reloads the configuration.
525
+
526
+ :param bool keep_custom:
527
+ If set to :code:`True`, we keep the custom configuration, otherwise
528
+ we delete it.
529
+ """
530
+
531
+ self.destroy(keep_custom=keep_custom)
532
+ self.start()
533
+
418
534
  def start(self) -> "ConfigLoader":
419
535
  """
420
536
  Starts the loading processIs.
421
537
  """
422
538
 
539
+ load_dotenv(os.path.join(self.config_dir, ".env"))
540
+ load_dotenv(os.path.join(self.config_dir, PyFunceble.storage.ENV_FILENAME))
541
+
423
542
  config = self.get_config_file_content()
424
543
 
425
544
  if self.custom_config:
@@ -445,9 +564,13 @@ class ConfigLoader:
445
564
 
446
565
  return self
447
566
 
448
- def destroy(self) -> "ConfigLoader":
567
+ def destroy(self, keep_custom: bool = False) -> "ConfigLoader":
449
568
  """
450
569
  Destroys everything loaded.
570
+
571
+ :param bool keep_custom:
572
+ If set to :code:`True`, we keep the custom configuration, otherwise
573
+ we delete it.
451
574
  """
452
575
 
453
576
  try:
@@ -462,7 +585,8 @@ class ConfigLoader:
462
585
  except (AttributeError, TypeError): # pragma: no cover ## Safety.
463
586
  pass
464
587
 
465
- # This is not a mistake.
466
- self._custom_config = {}
588
+ if not keep_custom:
589
+ # This is not a mistake.
590
+ self._custom_config = {}
467
591
 
468
592
  return self
@@ -100,6 +100,8 @@ class CredentialBase:
100
100
  _password: Optional[str] = None
101
101
  _charset: Optional[str] = None
102
102
 
103
+ _config_dir: Optional[str] = None
104
+
103
105
  def __init__(
104
106
  self,
105
107
  *,
@@ -109,6 +111,7 @@ class CredentialBase:
109
111
  username: Optional[str] = None,
110
112
  password: Optional[str] = None,
111
113
  charset: Optional[str] = None,
114
+ config_dir: Optional[str] = None,
112
115
  ) -> None:
113
116
  if host is not None:
114
117
  self.host = host
@@ -140,11 +143,14 @@ class CredentialBase:
140
143
  else:
141
144
  self.charset = self.STD_CHARSET
142
145
 
146
+ if config_dir is not None:
147
+ self.config_dir = config_dir
148
+ else:
149
+ self.config_dir = PyFunceble.storage.CONFIG_DIRECTORY
150
+
143
151
  self.dotenv_locations = [
144
152
  os.path.realpath(PyFunceble.storage.ENV_FILENAME),
145
- os.path.join(
146
- PyFunceble.storage.CONFIG_DIRECTORY, PyFunceble.storage.ENV_FILENAME
147
- ),
153
+ os.path.join(self.config_dir, PyFunceble.storage.ENV_FILENAME),
148
154
  ]
149
155
 
150
156
  def ensure_protocol_is_given(func): # pylint: disable=no-self-argument
@@ -165,6 +171,43 @@ class CredentialBase:
165
171
 
166
172
  return wrapper
167
173
 
174
+ @property
175
+ def config_dir(self) -> Optional[str]:
176
+ """
177
+ Provides the current state of the :code:`_config_dir` attribute.
178
+ """
179
+
180
+ return self._config_dir
181
+
182
+ @config_dir.setter
183
+ def config_dir(self, value: str) -> None:
184
+ """
185
+ Sets the configuration directory.
186
+
187
+ :param value:
188
+ The value to set.
189
+
190
+ :raise TypeError:
191
+ When value is not a :py:class:`str`.
192
+ """
193
+
194
+ if not isinstance(value, str):
195
+ raise TypeError(f"<value> should be {str}, {type(value)} given.")
196
+
197
+ self._config_dir = value
198
+
199
+ def set_config_dir(self, value: str) -> "CredentialBase":
200
+ """
201
+ Sets the configuration directory.
202
+
203
+ :param value:
204
+ The value to set.
205
+ """
206
+
207
+ self.config_dir = value
208
+
209
+ return self
210
+
168
211
  @property
169
212
  def host(self) -> Optional[str]:
170
213
  """
@@ -65,7 +65,7 @@ class DatasetBase:
65
65
  """
66
66
 
67
67
  STORAGE_INDEX: Optional[str] = None
68
- DOWNLOADER: Optional[DownloaderBase] = None
68
+ downloader: Optional[DownloaderBase] = None
69
69
 
70
70
  source_file: Optional[str] = None
71
71
 
@@ -129,9 +129,9 @@ class DatasetBase:
129
129
  file_helper = FileHelper(self.source_file)
130
130
 
131
131
  if not file_helper.exists() and bool(
132
- self.DOWNLOADER
132
+ self.downloader
133
133
  ): # pragma: no cover ## This is just a safety endpoint.
134
- self.DOWNLOADER.start()
134
+ self.downloader.start()
135
135
 
136
136
  if not file_helper.exists():
137
137
  raise FileNotFoundError(file_helper.path)
@@ -208,7 +208,9 @@ class CSVDatasetBase(DBDatasetBase):
208
208
  for row in reader:
209
209
  if "tested_at" in row:
210
210
  try:
211
- row["tested_at"] = datetime.fromisoformat(row["tested_at"])
211
+ row["tested_at"] = datetime.fromisoformat(
212
+ row["tested_at"]
213
+ ).astimezone(timezone.utc)
212
214
  except (TypeError, ValueError):
213
215
  row["tested_at"] = datetime.now(timezone.utc) - timedelta(
214
216
  days=365
@@ -53,6 +53,7 @@ License:
53
53
  import functools
54
54
  from typing import Any, Generator, List, Optional
55
55
 
56
+ import PyFunceble.storage
56
57
  from PyFunceble.dataset.base import DatasetBase
57
58
 
58
59
 
@@ -77,7 +78,13 @@ class DBDatasetBase(DatasetBase):
77
78
  *,
78
79
  authorized: Optional[bool] = None,
79
80
  remove_unneeded_fields: Optional[bool] = None,
81
+ config_dir: Optional[str] = None,
80
82
  ) -> None:
83
+ if config_dir is not None:
84
+ self.config_dir = config_dir
85
+ else:
86
+ self.config_dir = PyFunceble.storage.CONFIG_DIRECTORY
87
+
81
88
  if authorized is not None:
82
89
  self.set_authorized(authorized)
83
90
 
@@ -108,6 +115,43 @@ class DBDatasetBase(DatasetBase):
108
115
 
109
116
  return inner_metdhod
110
117
 
118
+ @property
119
+ def config_dir(self) -> Optional[str]:
120
+ """
121
+ Provides the current state of the :code:`_config_dir` attribute.
122
+ """
123
+
124
+ return self._config_dir
125
+
126
+ @config_dir.setter
127
+ def config_dir(self, value: str) -> None:
128
+ """
129
+ Sets the configuration directory.
130
+
131
+ :param value:
132
+ The value to set.
133
+
134
+ :raise TypeError:
135
+ When value is not a :py:class:`str`.
136
+ """
137
+
138
+ if not isinstance(value, str):
139
+ raise TypeError(f"<value> should be {str}, {type(value)} given.")
140
+
141
+ self._config_dir = value
142
+
143
+ def set_config_dir(self, value: str) -> "DBDatasetBase":
144
+ """
145
+ Sets the configuration directory.
146
+
147
+ :param value:
148
+ The value to set.
149
+ """
150
+
151
+ self.config_dir = value
152
+
153
+ return self
154
+
111
155
  @property
112
156
  def authorized(self) -> Optional[bool]:
113
157
  """
@@ -50,10 +50,8 @@ License:
50
50
  limitations under the License.
51
51
  """
52
52
 
53
- import os
54
53
  from typing import Any, Optional
55
54
 
56
- import PyFunceble.storage
57
55
  from PyFunceble.dataset.base import DatasetBase
58
56
  from PyFunceble.downloader.iana import IANADownloader
59
57
 
@@ -64,12 +62,11 @@ class IanaDataset(DatasetBase):
64
62
  """
65
63
 
66
64
  STORAGE_INDEX: str = "IANA"
67
- DOWNLOADER: IANADownloader = IANADownloader()
65
+ downloader: Optional[IANADownloader] = None
68
66
 
69
67
  def __init__(self) -> None:
70
- self.source_file = os.path.join(
71
- PyFunceble.storage.CONFIG_DIRECTORY, PyFunceble.storage.IANA_DUMP_FILENAME
72
- )
68
+ self.downloader = IANADownloader()
69
+ self.source_file = self.downloader.destination
73
70
 
74
71
  def __contains__(self, value: Any) -> bool:
75
72
  if value.startswith("."):
@@ -68,7 +68,7 @@ class CSVInactiveDataset(CSVDatasetBase, InactiveDatasetBase):
68
68
 
69
69
  def __post_init__(self) -> None:
70
70
  self.source_file = os.path.join(
71
- PyFunceble.storage.CONFIG_DIRECTORY, PyFunceble.cli.storage.INACTIVE_DB_FILE
71
+ self.config_dir, PyFunceble.cli.storage.INACTIVE_DB_FILE
72
72
  )
73
73
 
74
74
  return super().__post_init__()
@@ -84,7 +84,9 @@ class CSVInactiveDataset(CSVDatasetBase, InactiveDatasetBase):
84
84
  ):
85
85
  if not isinstance(dataset["tested_at"], datetime):
86
86
  try:
87
- date_of_inclusion = datetime.fromisoformat(dataset["tested_at"])
87
+ date_of_inclusion = datetime.fromisoformat(
88
+ dataset["tested_at"]
89
+ ).astimezone(timezone.utc)
88
90
  except (TypeError, ValueError):
89
91
  date_of_inclusion = datetime.now(timezone.utc) - timedelta(days=365)
90
92
  else:
@@ -50,10 +50,8 @@ License:
50
50
  limitations under the License.
51
51
  """
52
52
 
53
- import os
54
53
  from typing import Any, Optional
55
54
 
56
- import PyFunceble.storage
57
55
  from PyFunceble.dataset.base import DatasetBase
58
56
  from PyFunceble.downloader.ipv4_reputation import IPV4ReputationDownloader
59
57
  from PyFunceble.helpers.file import FileHelper
@@ -65,13 +63,11 @@ class IPV4ReputationDataset(DatasetBase):
65
63
  """
66
64
 
67
65
  STORAGE_INDEX: Optional[str] = None
68
- DOWNLOADER: IPV4ReputationDownloader = IPV4ReputationDownloader()
66
+ downloader: Optional[IPV4ReputationDownloader] = None
69
67
 
70
68
  def __init__(self) -> None:
71
- self.source_file = os.path.join(
72
- PyFunceble.storage.CONFIG_DIRECTORY,
73
- PyFunceble.storage.IPV4_REPUTATION_FILENAME,
74
- )
69
+ self.downloader = IPV4ReputationDownloader()
70
+ self.source_file = self.downloader.destination
75
71
 
76
72
  def __contains__(self, value: Any) -> bool:
77
73
  with self.get_content() as file_stream:
@@ -95,9 +91,9 @@ class IPV4ReputationDataset(DatasetBase):
95
91
 
96
92
  file_helper = FileHelper(self.source_file)
97
93
 
98
- if not file_helper.exists() and bool(self.DOWNLOADER): # pragma: no cover
94
+ if not file_helper.exists() and bool(self.downloader): # pragma: no cover
99
95
  ## pragma reason: Safety.
100
- self.DOWNLOADER.start()
96
+ self.downloader.start()
101
97
 
102
98
  if not file_helper.exists():
103
99
  raise FileNotFoundError(file_helper.path)
@@ -50,10 +50,8 @@ License:
50
50
  limitations under the License.
51
51
  """
52
52
 
53
- import os
54
- from typing import Any, List
53
+ from typing import Any, List, Optional
55
54
 
56
- import PyFunceble.storage
57
55
  from PyFunceble.dataset.base import DatasetBase
58
56
  from PyFunceble.downloader.public_suffix import PublicSuffixDownloader
59
57
 
@@ -64,13 +62,11 @@ class PublicSuffixDataset(DatasetBase):
64
62
  """
65
63
 
66
64
  STORAGE_INDEX: str = "PUBLIC_SUFFIX"
67
- DOWNLOADER: PublicSuffixDownloader = PublicSuffixDownloader()
65
+ downloader: Optional[PublicSuffixDownloader] = None
68
66
 
69
67
  def __init__(self) -> None:
70
- self.source_file = os.path.join(
71
- PyFunceble.storage.CONFIG_DIRECTORY,
72
- PyFunceble.storage.PUBLIC_SUFFIX_DUMP_FILENAME,
73
- )
68
+ self.downloader = PublicSuffixDownloader()
69
+ self.source_file = self.downloader.destination
74
70
 
75
71
  def __contains__(self, value: Any) -> bool:
76
72
  if value.startswith("."):
@@ -50,9 +50,8 @@ License:
50
50
  limitations under the License.
51
51
  """
52
52
 
53
- import os
54
53
  import secrets
55
- from typing import Any
54
+ from typing import Any, Optional
56
55
  from warnings import warn
57
56
 
58
57
  import PyFunceble.storage
@@ -66,16 +65,14 @@ class UserAgentDataset(DatasetBase):
66
65
  """
67
66
 
68
67
  STORAGE_INDEX: str = "USER_AGENTS"
69
- DOWNLOADER: UserAgentsDownloader = UserAgentsDownloader()
68
+ downloader: Optional[UserAgentsDownloader] = None
70
69
 
71
70
  preferred_browser: str = "chrome"
72
71
  preferred_platform: str = "linux"
73
72
 
74
73
  def __init__(self) -> None:
75
- self.source_file = os.path.join(
76
- PyFunceble.storage.CONFIG_DIRECTORY,
77
- PyFunceble.storage.USER_AGENT_FILENAME,
78
- )
74
+ self.downloader = UserAgentsDownloader()
75
+ self.source_file = self.downloader.destination
79
76
 
80
77
  def __contains__(self, value: Any) -> bool:
81
78
  content = self.get_content()
@@ -67,7 +67,7 @@ class CSVWhoisDataset(CSVDatasetBase, WhoisDatasetBase):
67
67
 
68
68
  def __post_init__(self) -> None:
69
69
  self.source_file = os.path.join(
70
- PyFunceble.storage.CONFIG_DIRECTORY, PyFunceble.cli.storage.WHOIS_DB_FILE
70
+ self.config_dir, PyFunceble.cli.storage.WHOIS_DB_FILE
71
71
  )
72
72
 
73
73
  return super().__post_init__()