• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

funilrys / PyFunceble / 17046250819

18 Aug 2025 04:12PM UTC coverage: 96.648% (+1.9%) from 94.721%
17046250819

push

github

funilrys
Bump verstion to v4.3.0

1 of 1 new or added line in 1 file covered. (100.0%)

154 existing lines in 14 files now uncovered.

11967 of 12382 relevant lines covered (96.65%)

8.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.3
/PyFunceble/checker/base.py
1
"""
2
The tool to check the availability or syntax of domain, IP or URL.
3

4
::
5

6

7
    ██████╗ ██╗   ██╗███████╗██╗   ██╗███╗   ██╗ ██████╗███████╗██████╗ ██╗     ███████╗
8
    ██╔══██╗╚██╗ ██╔╝██╔════╝██║   ██║████╗  ██║██╔════╝██╔════╝██╔══██╗██║     ██╔════╝
9
    ██████╔╝ ╚████╔╝ █████╗  ██║   ██║██╔██╗ ██║██║     █████╗  ██████╔╝██║     █████╗
10
    ██╔═══╝   ╚██╔╝  ██╔══╝  ██║   ██║██║╚██╗██║██║     ██╔══╝  ██╔══██╗██║     ██╔══╝
11
    ██║        ██║   ██║     ╚██████╔╝██║ ╚████║╚██████╗███████╗██████╔╝███████╗███████╗
12
    ╚═╝        ╚═╝   ╚═╝      ╚═════╝ ╚═╝  ╚═══╝ ╚═════╝╚══════╝╚═════╝ ╚══════╝╚══════╝
13

14
Provides the base of all checker.
15

16
Author:
17
    Nissar Chababy, @funilrys, contactTATAfunilrysTODTODcom
18

19
Special thanks:
20
    https://pyfunceble.github.io/#/special-thanks
21

22
Contributors:
23
    https://pyfunceble.github.io/#/contributors
24

25
Project link:
26
    https://github.com/funilrys/PyFunceble
27

28
Project documentation:
29
    https://docs.pyfunceble.com
30

31
Project homepage:
32
    https://pyfunceble.github.io/
33

34
License:
35
::
36

37

38
    Copyright 2017, 2018, 2019, 2020, 2022, 2023, 2024, 2025 Nissar Chababy
39

40
    Licensed under the Apache License, Version 2.0 (the "License");
41
    you may not use this file except in compliance with the License.
42
    You may obtain a copy of the License at
43

44
        https://www.apache.org/licenses/LICENSE-2.0
45

46
    Unless required by applicable law or agreed to in writing, software
47
    distributed under the License is distributed on an "AS IS" BASIS,
48
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
49
    See the License for the specific language governing permissions and
50
    limitations under the License.
51
"""
52

53
import datetime
9✔
54
import functools
9✔
55
from typing import Optional
9✔
56

57
import domain2idna
9✔
58
from sqlalchemy.orm import Session
9✔
59

60
import PyFunceble.facility
9✔
61
import PyFunceble.storage
9✔
62
from PyFunceble.checker.params_base import CheckerParamsBase
9✔
63
from PyFunceble.checker.status_base import CheckerStatusBase
9✔
64
from PyFunceble.converter.url2netloc import Url2Netloc
9✔
65
from PyFunceble.helpers.regex import RegexHelper
9✔
66
from PyFunceble.query.platform import PlatformQueryTool
9✔
67

68

69
class CheckerBase:
9✔
70
    """
71
    Provides the base of all checker.
72

73
    :param str subject:
74
        Optional, The subject to work with.
75
    :param bool do_syntax_check_first:
76
        Optional, Forces the checker to first perform a syntax check,
77

78
        .. warning::
79
            This does not apply to the syntax checker - itself.
80
    """
81

82
    STD_DO_SYNTAX_CHECK_FIRST: bool = False
9✔
83
    STD_USE_PLATFORM: bool = False
9✔
84

85
    _do_syntax_check_first: bool = False
9✔
86
    _use_platform: bool = False
9✔
87

88
    _subject: Optional[str] = None
9✔
89
    _idna_subject: Optional[str] = None
9✔
90

91
    url2netloc: Optional[Url2Netloc] = None
9✔
92
    regex_helper: Optional[RegexHelper] = None
9✔
93

94
    db_session: Optional[Session] = None
9✔
95
    platform_query_tool: Optional[PlatformQueryTool] = None
9✔
96

97
    status: Optional[CheckerStatusBase] = None
9✔
98
    params: Optional[CheckerParamsBase] = None
9✔
99

100
    def __init__(
101
        self,
102
        subject: Optional[str] = None,
103
        *,
104
        do_syntax_check_first: Optional[bool] = None,
105
        db_session: Optional[Session] = None,
106
        use_platform: Optional[bool] = None,
107
    ) -> None:
108
        self.platform_query_tool = PlatformQueryTool()
1✔
109
        self.url2netloc = Url2Netloc()
1✔
110
        self.regex_helper = RegexHelper()
1✔
111

112
        if self.params is None:
1✔
113
            self.params = CheckerParamsBase()
1✔
114

115
        if self.status is None:
1✔
116
            self.status = CheckerStatusBase()
1✔
117

118
        if subject is not None:
1✔
119
            self.subject = subject
1✔
120

121
        if do_syntax_check_first is not None:
1✔
122
            self.do_syntax_check_first = do_syntax_check_first
1✔
123
        else:
124
            self.do_syntax_check_first = self.STD_DO_SYNTAX_CHECK_FIRST
1✔
125

126
        if use_platform is not None:
1✔
127
            self.use_platform = use_platform
1✔
128
        else:
129
            self.guess_and_set_use_platform()
1✔
130

131
        self.db_session = db_session
1✔
132

133
    def propagate_subject(func):  # pylint: disable=no-self-argument
9✔
134
        """
135
        Propagates the subject to the object that need it after launching
136
        the decorated method.
137
        """
138

139
        @functools.wraps(func)
9✔
140
        def wrapper(self, *args, **kwargs):
9✔
141
            result = func(self, *args, **kwargs)  # pylint: disable=not-callable
9✔
142

143
            self.subject_propagator()
9✔
144

145
            return result
9✔
146

147
        return wrapper
9✔
148

149
    def ensure_subject_is_given(func):  # pylint: disable=no-self-argument
9✔
150
        """
151
        Ensures that the subject is given before running the decorated method.
152

153
        :raise TypeError:
154
            If the subject is not a string.
155
        """
156

157
        @functools.wraps(func)
1✔
158
        def wrapper(self, *args, **kwargs):  # pragma: no cover ## Safety!
159
            if not isinstance(self.subject, str):
160
                raise TypeError(
161
                    f"<self.subject> should be {str}, {type(self.subject)} given."
162
                )
163

164
            return func(self, *args, **kwargs)  # pylint: disable=not-callable
165

166
        return wrapper
9✔
167

168
    def query_status_if_missing(func):  # pylint: disable=no-self-argument
9✔
169
        """
170
        Queries the status if it's missing.
171
        """
172

173
        @functools.wraps(func)
1✔
174
        def wrapper(self, *args, **kwargs):  # pragma: no cover ## Safety!
175
            if not self.status.status or self.status.status is None:
176
                self.query_status()
177

178
            return func(self, *args, **kwargs)  # pylint: disable=not-callable
179

180
        return wrapper
9✔
181

182
    def update_status_date_after_query(func):  # pylint: disable=no-self-argument
9✔
183
        """
184
        Updates the status dates after running the decorated method.
185
        """
186

187
        @functools.wraps(func)
1✔
188
        def wrapper(self, *args, **kwargs):  # pragma: no cover ## Safety!
189
            result = func(self, *args, **kwargs)  # pylint: disable=not-callable
190

191
            self.status.tested_at = datetime.datetime.now(datetime.timezone.utc)
192

193
            return result
194

195
        return wrapper
9✔
196

197
    @property
9✔
198
    def subject(self) -> Optional[str]:
9✔
199
        """
200
        Provides the current state of the :code:`_subject` attribute.
201
        """
202

203
        return self._subject
9✔
204

205
    @subject.setter
9✔
206
    @propagate_subject
9✔
207
    def subject(self, value: str) -> None:
9✔
208
        """
209
        Sets the subject to work with.
210

211
        :param value:
212
            The subject to set.
213

214
        :raise TypeError:
215
            When the given :code:`value` is not a :py:class:`str`.
216
        :raise ValueError:
217
            When the given :code:`value` is empty.
218
        """
219

220
        if not isinstance(value, str):
9✔
221
            raise TypeError(f"<value> should be {str}, {type(value)} given.")
9✔
222

223
        if not value:
9✔
224
            raise ValueError("<value> should not be empty.")
9✔
225

226
        self._subject = value
9✔
227

228
        try:
9✔
229
            self.idna_subject = domain2idna.domain2idna(value)
9✔
230
        except ValueError:
9✔
231
            self.idna_subject = value
9✔
232

233
    def set_subject(self, value: str) -> "CheckerBase":
9✔
234
        """
235
        Sets the subject to work with.
236

237
        :param value:
238
            The subject to set.
239
        """
240

241
        self.subject = value
9✔
242

243
        return self
9✔
244

245
    @property
9✔
246
    def idna_subject(self) -> Optional[str]:
9✔
247
        """
248
        Provides the current state of the :code:`_idna_subject` attribute.
249
        """
250

251
        return self._idna_subject
9✔
252

253
    @idna_subject.setter
9✔
254
    def idna_subject(self, value: str) -> None:
9✔
255
        """
256
        Sets the subject to work with.
257

258
        :param value:
259
            The subject to set.
260

261
        :raise TypeError:
262
            When the given :code:`value` is not a :py:class:`str`.
263
        :raise ValueError:
264
            When the given :code:`value` is empty.
265
        """
266

267
        if not isinstance(value, str):
9✔
268
            raise TypeError(f"<value> should be {str}, {type(value)} given.")
9✔
269

270
        if not value:
9✔
271
            raise ValueError("<value> should not be empty.")
9✔
272

273
        self._idna_subject = value
9✔
274

275
    def set_idna_subject(self, value: str) -> "CheckerBase":
9✔
276
        """
277
        Sets the subject to work with.
278

279
        :param value:
280
            The subject to set.
281
        """
282

283
        self.idna_subject = value
9✔
284

285
        return self
9✔
286

287
    @property
9✔
288
    def do_syntax_check_first(self) -> None:
9✔
289
        """
290
        Provides the current state of the :code:`do_syntax_check_first`
291
        attribute.
292
        """
293

294
        return self._do_syntax_check_first
9✔
295

296
    @do_syntax_check_first.setter
9✔
297
    def do_syntax_check_first(self, value: bool) -> None:
9✔
298
        """
299
        Sets the value which allow us to do a syntax check first.
300

301
        :param value:
302
            The subject to set.
303

304
        :raise TypeError:
305
            When the given :code:`value` is not a :py:class:`bool`.
306
        """
307

308
        if not isinstance(value, bool):
9✔
309
            raise TypeError(f"<value> should be {str}, {type(value)} given.")
9✔
310

311
        self._do_syntax_check_first = self.params.do_syntax_check_first = value
9✔
312

313
    def set_do_syntax_check_first(self, value: bool) -> "CheckerBase":
9✔
314
        """
315
        Sets the value which allow us to do a syntax check first.
316

317
        :param value:
318
            The subject to set.
319
        """
320

321
        self.do_syntax_check_first = value
9✔
322

323
        return self
9✔
324

325
    @property
9✔
326
    def use_platform(self) -> bool:
9✔
327
        """
328
        Provides the current value of the :code:`_use_platform` attribute.
329
        """
330

331
        return self._use_platform
9✔
332

333
    @use_platform.setter
9✔
334
    def use_platform(self, value: bool) -> None:
9✔
335
        """
336
        Sets the value which authorizes the usage of the platform.
337

338
        :param value:
339
            The value to set.
340

341
        :param TypeError:
342
            When the given :code:`value` is not a :py:class:`bool`.
343
        """
344

345
        if not isinstance(value, bool):
9✔
346
            raise TypeError(f"<value> should be {bool}, {type(value)} given.")
9✔
347

348
        self._use_platform = self.params.use_platform = value
9✔
349

350
    def set_use_platform(self, value: bool) -> "CheckerBase":
9✔
351
        """
352
        Sets the value which authorizes the usage of the platform.
353

354
        :param value:
355
            The value to set.
356
        """
357

358
        self.use_platform = value
9✔
359

360
        return self
9✔
361

362
    def guess_and_set_use_platform(self) -> "CheckerBase":
9✔
363
        """
364
        Try to guess and set the value of the :code:`use_platform` attribute.
365
        """
366

367
        if PyFunceble.facility.ConfigLoader.is_already_loaded():
9✔
368
            if isinstance(PyFunceble.storage.CONFIGURATION.lookup.platform, bool):
9✔
369
                self.use_platform = PyFunceble.storage.CONFIGURATION.lookup.platform
9✔
370
            else:
371
                self.use_platform = self.STD_USE_PLATFORM
9✔
372
        else:
373
            self.use_platform = self.STD_USE_PLATFORM
9✔
374

375
    def subject_propagator(self) -> "CheckerBase":
9✔
376
        """
377
        Propagate the currently set subject.
378

379
        .. warning::
380
            Be sure to use setup your status first.
381
        """
382

383
        self.status.subject = self.subject
9✔
384
        self.status.idna_subject = self.idna_subject
9✔
385
        self.status.netloc = self.url2netloc.set_data_to_convert(
9✔
386
            self.idna_subject
387
        ).get_converted()
388
        self.status.status = None
9✔
389
        self.status.status_source = None
9✔
390

391
        return self.query_common_checker()
9✔
392

393
    def query_common_checker(self) -> "CheckerBase":
9✔
394
        """
395
        Queries the common checkers.
396

397
        .. warning::
398
            Be sure to use setup your status first.
399
        """
400

401
        if not self.status.subject_kind:
9✔
402
            cls_name = self.__class__.__name__.lower()
9✔
403
            if (
9✔
404
                hasattr(self.status, "ip_syntax") and self.status.ip_syntax
405
            ) or "ip" in cls_name:
406
                self.status.subject_kind = "ip"
9✔
407
            elif (
9✔
408
                hasattr(self.status, "url_syntax") and self.status.url_syntax
409
            ) or "url" in cls_name:
UNCOV
410
                self.status.subject_kind = "url"
×
411
            elif (
9✔
412
                hasattr(self.status, "domain_syntax") and self.status.domain_syntax
413
            ) or "domain" in cls_name:
414
                self.status.subject_kind = "domain"
9✔
415
            else:
416
                self.status.subject_kind = "unknown"
9✔
417

418
        return self
9✔
419

420
    @ensure_subject_is_given
9✔
421
    def is_valid(self) -> bool:
9✔
422
        """
423
        Provides the result of the validation.
424
        """
425

426
        raise NotImplementedError()
427

428
    @ensure_subject_is_given
9✔
429
    @update_status_date_after_query
9✔
430
    def query_status(self) -> "CheckerBase":
9✔
431
        """
432
        Queries the status.
433
        """
434

435
        raise NotImplementedError()
436

437
    @query_status_if_missing
9✔
438
    def get_status(self) -> Optional[CheckerStatusBase]:
9✔
439
        """
440
        Provides the current state of the status.
441

442
        .. note::
443
            This method will automatically query status using the
444
            :meth:`PyFunceble.checker.base.CheckerBase.query_status` if
445
            the
446
            :attr:`PyFunceble.checker.status_base.CheckerStatusBase.status`
447
            attribute is not set.
448
        """
449

450
        return self.status
9✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc