• 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

80.41
/PyFunceble/query/platform.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 ans interface which let us interact with the platform API.
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
# pylint: disable=too-many-lines
54

55
import json
9✔
56
import os
9✔
57
from typing import Generator, List, Optional, Union
9✔
58

59
import requests
9✔
60
import requests.exceptions
9✔
61

62
import PyFunceble.facility
9✔
63
import PyFunceble.storage
9✔
64
from PyFunceble.checker.availability.status import AvailabilityCheckerStatus
9✔
65
from PyFunceble.checker.reputation.status import ReputationCheckerStatus
9✔
66
from PyFunceble.checker.syntax.status import SyntaxCheckerStatus
9✔
67
from PyFunceble.helpers.environment_variable import EnvironmentVariableHelper
9✔
68

69

70
class PlatformQueryTool:
9✔
71
    """
72
    Provides the interface to interact with the platform.
73

74
    :param token:
75
        The token to use to communicate with the API.
76

77
        .. warning::
78
            If :code:`None` is given, the class constructor will try to load the
79
            :code:`PYFUNCEBLE_COLLECTION_API_TOKEN` or
80
            :code:`PYFUNCEBLE_PLATFORM_API_TOKEN` environment variable.
81

82
    :param url_base:
83
        The base of the URL to communicate with.
84

85
    :param preferred_status_origin:
86
        The preferred data origin.
87
        It can be :code:`frequent`, :code:`latest` or :code:`recommended`.
88
    """
89

90
    SUPPORTED_CHECKERS: List[str] = ["syntax", "reputation", "availability"]
9✔
91
    SUPPORTED_STATUS_ORIGIN: List[str] = ["frequent", "latest", "recommended"]
9✔
92

93
    SUBJECT: str = (
9✔
94
        "10927294711127294799272947462729471152729471162729471152729471112729"
95
        "4710427294745272947100272947972729471012729471002729474627294797272947"
96
        "116272947101272947982729474627294710527294711227294797272947472729474"
97
        "727294758272947115272947112272947116272947116272947104"
98
    )
99
    STD_PREFERRED_STATUS_ORIGIN: str = "frequent"
9✔
100
    STD_CHECKER_PRIORITY: str = ["none"]
9✔
101
    STD_CHECKER_EXCLUDE: str = ["none"]
9✔
102
    STD_TIMEOUT: float = 5.0
9✔
103

104
    _token: Optional[str] = None
9✔
105
    """
7✔
106
    The token to use while communicating with the platform API.
107
    """
108

109
    _url_base: Optional[str] = None
9✔
110
    """
7✔
111
    The base of the URL to communicate with.
112
    """
113

114
    _preferred_status_origin: Optional[str] = None
9✔
115
    """
7✔
116
    The preferred data origin
117
    """
118

119
    _checker_priority: Optional[List[str]] = []
9✔
120
    """
7✔
121
    The checker to prioritize.
122
    """
123

124
    _checker_exclude: Optional[List[str]] = []
9✔
125
    """
7✔
126
    The checker to exclude.
127
    """
128

129
    _is_modern_api: Optional[bool] = None
9✔
130
    """
7✔
131
    Whether we are working with the modern or legacy API.
132
    """
133

134
    _timeout: float = 5.0
9✔
135
    """
7✔
136
    The timeout to use while communicating with the API.
137
    """
138

139
    session: Optional[requests.Session] = None
9✔
140

141
    def __init__(
142
        self,
143
        *,
144
        token: Optional[str] = None,
145
        preferred_status_origin: Optional[str] = None,
146
        timeout: Optional[float] = None,
147
        checker_priority: Optional[List[str]] = None,
148
        checker_exclude: Optional[List[str]] = None,
149
    ) -> None:
150
        if token is not None:
1✔
151
            self.token = token
1✔
152
        else:
153
            self.token = EnvironmentVariableHelper(
1✔
154
                "PYFUNCEBLE_COLLECTION_API_TOKEN"
155
            ).get_value(default="") or EnvironmentVariableHelper(
156
                "PYFUNCEBLE_PLATFORM_API_TOKEN"
157
            ).get_value(
158
                default=""
159
            )
160

161
        if preferred_status_origin is not None:
1✔
162
            self.preferred_status_origin = preferred_status_origin
1✔
163
        else:
164
            self.guess_and_set_preferred_status_origin()
1✔
165

166
        if checker_priority is not None:
1✔
167
            self.checker_priority = checker_priority
1✔
168
        else:
169
            self.guess_and_set_checker_priority()
1✔
170

171
        if checker_exclude is not None:
1✔
172
            self.checker_exclude = checker_exclude
1✔
173
        else:
174
            self.guess_and_set_checker_exclude()
1✔
175

176
        if timeout is not None:
1✔
177
            self.timeout = timeout
178
        else:
179
            self.guess_and_set_timeout()
1✔
180

181
        self._url_base = EnvironmentVariableHelper(
1✔
182
            "PYFUNCEBLE_COLLECTION_API_URL"
183
        ).get_value(default=None) or EnvironmentVariableHelper(
184
            "PYFUNCEBLE_PLATFORM_API_URL"
185
        ).get_value(
186
            default=None
187
        )
188

189
        self.session = requests.Session()
1✔
190
        self.session.headers.update(
1✔
191
            {
192
                "Authorization": f"Bearer {self.token}" if self.token else None,
193
                "X-Pyfunceble-Version": PyFunceble.storage.PROJECT_VERSION,
194
                "Content-Type": "application/json",
195
            }
196
        )
197

198
    def __contains__(self, value: str) -> bool:
9✔
199
        """
200
        Checks if the given value is in the platform.
201

202
        :param value:
203
            The value to check.
204
        """
205

206
        return self.pull(value) is not None
9✔
207

208
    def __getitem__(self, value: str) -> Optional[dict]:
9✔
209
        """
210
        Gets the information about the given value.
211

212
        :param value:
213
            The value to get the information about.
214
        """
215

216
        return self.pull(value)
9✔
217

218
    @property
9✔
219
    def token(self) -> Optional[str]:
9✔
220
        """
221
        Provides the currently set token.
222
        """
223

224
        return self._token
9✔
225

226
    @token.setter
9✔
227
    def token(self, value: str) -> None:
9✔
228
        """
229
        Sets the value of the :code:`_token` attribute.
230

231
        :param value:
232
            The value to set.
233

234
        :raise TypeError:
235
            When the given :code:`value` is not a :py:class:`str`
236
        """
237

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

241
        self._token = value
9✔
242

243
    def set_token(self, value: str) -> "PlatformQueryTool":
9✔
244
        """
245
        Sets the value of the :code:`_token` attribute.
246

247
        :param value:
248
            The value to set.
249
        """
250

251
        self.token = value
9✔
252

253
        return self
9✔
254

255
    @property
9✔
256
    def url_base(self) -> Optional[str]:
9✔
257
        """
258
        Provides the value of the :code:`_url_base` attribute.
259
        """
260

261
        return self._url_base or "".join(
9✔
262
            reversed([chr(int(x)) for x in self.SUBJECT.split("272947")])
263
        )
264

265
    @url_base.setter
9✔
266
    def url_base(self, value: str) -> None:
9✔
267
        """
268
        Sets the base of the URL to work with.
269

270
        :param value:
271
            The value to set.
272

273
        :raise TypeError:
274
            When the given :code:`value` is not a :py:class:`str`.
275
        """
276

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

280
        if not value.startswith(("http", "https")):
9✔
281
            raise ValueError(
9✔
282
                f"<value> is missing the scheme (http/https), {value} given."
283
            )
284

285
        self._url_base = value.rstrip("/")
9✔
286

287
    def set_url_base(self, value: str) -> "PlatformQueryTool":
9✔
288
        """
289
        Sets the base of the URL to work with.
290

291
        :parma value:
292
            The value to set.
293
        """
294

295
        self.url_base = value
9✔
296

297
        return self
×
298

299
    @property
9✔
300
    def is_modern_api(self) -> bool:
9✔
301
        """
302
        Provides the value of the :code:`_is_modern_api` attribute.
303
        """
304

305
        return self._is_modern_api
9✔
306

307
    @is_modern_api.setter
9✔
308
    def is_modern_api(self, value: bool) -> None:
9✔
309
        """
310
        Sets the value of the :code:`_is_modern_api` attribute.
311

312
        :param value:
313
            The value to set.
314

315
        :raise TypeError:
316
            When the given :code:`value` is not a :py:class:`bool`.
317
        """
318

319
        if not isinstance(value, bool):
9✔
320
            raise TypeError(f"<value> should be {bool}, {type(value)} given.")
×
321

322
        self._is_modern_api = value
9✔
323

324
    def set_is_modern_api(self, value: bool) -> "PlatformQueryTool":
9✔
325
        """
326
        Sets the value of the :code:`_is_modern_api` attribute.
327

328
        :param value:
329
            The value to set.
330
        """
331

332
        self.is_modern_api = value
×
333

334
        return self
×
335

336
    @property
9✔
337
    def timeout(self) -> float:
9✔
338
        """
339
        Provides the value of the :code:`_timeout` attribute.
340
        """
341

342
        return self._timeout
9✔
343

344
    @timeout.setter
9✔
345
    def timeout(self, value: float) -> None:
9✔
346
        """
347
        Sets the value of the :code:`_timeout` attribute.
348

349
        :param value:
350
            The value to set.
351

352
        :raise TypeError:
353
            When the given :code:`value` is not a :py:class:`float`.
354
        """
355

356
        if not isinstance(value, (int, float)):
9✔
357
            raise TypeError(f"<value> should be {float}, {type(value)} given.")
×
358

359
        self._timeout = value
9✔
360

361
    def set_timeout(self, value: float) -> "PlatformQueryTool":
9✔
362
        """
363
        Sets the value of the :code:`_timeout` attribute.
364

365
        :param value:
366
            The value to set.
367
        """
368

369
        self.timeout = value
×
370

371
        return self
×
372

373
    def guess_and_set_is_modern_api(self) -> "PlatformQueryTool":
9✔
374
        """
375
        Try to guess if we are working with a legacy version.
376
        """
377

378
        if self.token:
9✔
379
            try:
9✔
380
                response = self.session.get(
9✔
381
                    f"{self.url_base}/v1/stats/subject",
382
                    timeout=self.timeout,
383
                )
384

385
                response.raise_for_status()
9✔
386

387
                self.is_modern_api = False
×
388
            except (requests.RequestException, json.decoder.JSONDecodeError):
9✔
389
                self.is_modern_api = True
9✔
390
        else:
391
            self.is_modern_api = False
9✔
392

393
        return self
9✔
394

395
    @property
9✔
396
    def preferred_status_origin(self) -> Optional[str]:
9✔
397
        """
398
        Provides the value of the :code:`_preferred_status_origin` attribute.
399
        """
400

401
        return self._preferred_status_origin
9✔
402

403
    @preferred_status_origin.setter
9✔
404
    def preferred_status_origin(self, value: str) -> None:
9✔
405
        """
406
        Sets the preferred status origin.
407

408
        :param value:
409
            The value to set.
410

411
        :raise TypeError:
412
            When the given :code:`value` is not a :py:class:`str`.
413

414
        :raise ValueError:
415
            When the given :code:`value` is not supported.
416
        """
417

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

421
        if value not in self.SUPPORTED_STATUS_ORIGIN:
9✔
422
            raise ValueError(f"<value> ({value}) is not supported.")
9✔
423

424
        self._preferred_status_origin = value
9✔
425

426
    def set_preferred_status_origin(self, value: str) -> "PlatformQueryTool":
9✔
427
        """
428
        Sets the preferred status origin.
429

430
        :parma value:
431
            The value to set.
432
        """
433

434
        self.preferred_status_origin = value
9✔
435

436
        return self
9✔
437

438
    def guess_and_set_preferred_status_origin(self) -> "PlatformQueryTool":
9✔
439
        """
440
        Try to guess the preferred status origin.
441
        """
442

443
        if PyFunceble.facility.ConfigLoader.is_already_loaded():
9✔
444
            if isinstance(
9✔
445
                PyFunceble.storage.CONFIGURATION.platform.preferred_status_origin, str
446
            ):
447
                self.preferred_status_origin = (
9✔
448
                    PyFunceble.storage.CONFIGURATION.platform.preferred_status_origin
449
                )
450
            else:
451
                self.preferred_status_origin = self.STD_PREFERRED_STATUS_ORIGIN
9✔
452
        else:
453
            self.preferred_status_origin = self.STD_PREFERRED_STATUS_ORIGIN
9✔
454

455
        return self
9✔
456

457
    @property
9✔
458
    def checker_priority(self) -> Optional[List[str]]:
9✔
459
        """
460
        Provides the value of the :code:`_checker_priority` attribute.
461
        """
462

463
        return self._checker_priority
9✔
464

465
    @checker_priority.setter
9✔
466
    def checker_priority(self, value: List[str]) -> None:
9✔
467
        """
468
        Sets the checker priority to set - order matters.
469

470
        :param value:
471
            The value to set.
472

473
        :raise TypeError:
474
            When the given :code:`value` is not a :py:class:`str`.
475

476
        :raise ValueError:
477
            When the given :code:`value` is not supported.
478
        """
479

480
        accepted = []
9✔
481

482
        for checker_type in value:
9✔
483
            if not isinstance(checker_type, str):
9✔
484
                raise TypeError(
9✔
485
                    f"<checker_type> ({checker_type}) should be {str}, "
486
                    "{type(checker_type)} given."
487
                )
488

489
            if checker_type.lower() not in self.SUPPORTED_CHECKERS + ["none"]:
9✔
490
                raise ValueError(f"<checker_type> ({checker_type}) is not supported.")
9✔
491

492
            accepted.append(checker_type.lower())
9✔
493

494
        self._checker_priority = accepted
9✔
495

496
    def set_checker_priority(self, value: List[str]) -> "PlatformQueryTool":
9✔
497
        """
498
        Sets the checker priority.
499

500
        :parma value:
501
            The value to set.
502
        """
503

504
        self.checker_priority = value
9✔
505

506
        return self
9✔
507

508
    def guess_and_set_checker_priority(self) -> "PlatformQueryTool":
9✔
509
        """
510
        Try to guess the checker priority to use.
511
        """
512

513
        if "PYFUNCEBLE_PLATFORM_CHECKER_PRIORITY" in os.environ:
9✔
514
            self.checker_priority = os.environ[
×
515
                "PYFUNCEBLE_PLATFORM_CHECKER_PRIORITY"
516
            ].split(",")
517
        elif PyFunceble.facility.ConfigLoader.is_already_loaded():
9✔
518
            if isinstance(PyFunceble.storage.PLATFORM.checker_priority, list):
9✔
519
                self.checker_priority = PyFunceble.storage.PLATFORM.checker_priority
9✔
520
            else:
521
                self.checker_priority = self.STD_CHECKER_PRIORITY
9✔
522
        else:
523
            self.checker_priority = self.STD_CHECKER_PRIORITY
9✔
524

525
        return self
9✔
526

527
    @property
9✔
528
    def checker_exclude(self) -> Optional[List[str]]:
9✔
529
        """
530
        Provides the value of the :code:`_checker_exclude` attribute.
531
        """
532

533
        return self._checker_exclude
9✔
534

535
    @checker_exclude.setter
9✔
536
    def checker_exclude(self, value: List[str]) -> None:
9✔
537
        """
538
        Sets the checker exclude.
539

540
        :param value:
541
            The value to set.
542

543
        :raise TypeError:
544
            When the given :code:`value` is not a :py:class:`str`.
545

546
        :raise ValueError:
547
            When the given :code:`value` is not supported.
548
        """
549

550
        accepted = []
9✔
551

552
        for checker_type in value:
9✔
553
            if not isinstance(checker_type, str):
9✔
554
                raise TypeError(
9✔
555
                    f"<checker_type> ({checker_type}) should be {str}, "
556
                    "{type(checker_type)} given."
557
                )
558

559
            if checker_type.lower() not in self.SUPPORTED_CHECKERS + ["none"]:
9✔
560
                raise ValueError(f"<checker_type> ({checker_type}) is not supported.")
9✔
561

562
            accepted.append(checker_type.lower())
9✔
563

564
        self._checker_exclude = accepted
9✔
565

566
    def set_checker_exclude(self, value: List[str]) -> "PlatformQueryTool":
9✔
567
        """
568
        Sets the checker to exclude.
569

570
        :parma value:
571
            The value to set.
572
        """
573

574
        self.checker_exclude = value
9✔
575

576
        return self
9✔
577

578
    def guess_and_set_checker_exclude(self) -> "PlatformQueryTool":
9✔
579
        """
580
        Try to guess the checker to exclude.
581
        """
582

583
        if "PYFUNCEBLE_PLATFORM_CHECKER_EXCLUDE" in os.environ:
9✔
584
            self.checker_exclude = os.environ[
×
585
                "PYFUNCEBLE_PLATFORM_CHECKER_EXCLUDE"
586
            ].split(",")
587
        elif PyFunceble.facility.ConfigLoader.is_already_loaded():
9✔
588
            if isinstance(PyFunceble.storage.PLATFORM.checker_exclude, list):
9✔
589
                self.checker_exclude = PyFunceble.storage.PLATFORM.checker_exclude
9✔
590
            else:
591
                self.checker_exclude = self.STD_CHECKER_EXCLUDE
9✔
592
        else:
593
            self.checker_exclude = self.STD_CHECKER_EXCLUDE
9✔
594

595
        return self
9✔
596

597
    def guess_and_set_timeout(self) -> "PlatformQueryTool":
9✔
598
        """
599
        Try to guess the timeout to use.
600
        """
601

602
        if PyFunceble.facility.ConfigLoader.is_already_loaded():
9✔
603
            self.timeout = PyFunceble.storage.CONFIGURATION.lookup.timeout
9✔
604
        else:
605
            self.timeout = self.STD_TIMEOUT
9✔
606

607
        return self
9✔
608

609
    def ensure_modern_api(func):  # pylint: disable=no-self-argument
9✔
610
        """
611
        Ensures that the :code:`is_modern_api` attribute is set before running
612
        the decorated method.
613
        """
614

615
        def wrapper(self, *args, **kwargs):
9✔
616
            if self.is_modern_api is None:
9✔
617
                self.guess_and_set_is_modern_api()
9✔
618

619
            return func(self, *args, **kwargs)  # pylint: disable=not-callable
9✔
620

621
        return wrapper
9✔
622

623
    @ensure_modern_api
9✔
624
    def pull(self, subject: str) -> Optional[dict]:
9✔
625
        """
626
        Pulls all data related to the subject or :py:class:`None`
627

628
        :param subject:
629
            The subject to search for.
630

631
        :raise TypeError:
632
            When the given :code:`subject` is not a :py:class:`str`.
633

634
        :return:
635
            The response of the search.
636
        """
637

638
        PyFunceble.facility.Logger.info("Starting to search subject: %r", subject)
639

640
        if not isinstance(subject, str):
9✔
641
            raise TypeError(f"<subject> should be {str}, {type(subject)} given.")
9✔
642

643
        if self.is_modern_api:
9✔
644
            if self.token:
×
645
                url = f"{self.url_base}/v1/aggregation/subject/search"
×
646
            else:
647
                url = f"{self.url_base}/v1/hub/aggregation/subject/search"
×
648
        else:
649
            url = f"{self.url_base}/v1/subject/search"
9✔
650

651
        try:
9✔
652
            response = self.session.post(
9✔
653
                url,
654
                json={"subject": subject},
655
                timeout=self.timeout,
656
            )
657

658
            response_json = response.json()
9✔
659

660
            if response.status_code == 200:
9✔
661
                PyFunceble.facility.Logger.debug(
662
                    "Successfully search subject: %r. Response: %r",
663
                    subject,
664
                    response_json,
665
                )
666

667
                PyFunceble.facility.Logger.info(
668
                    "Finished to search subject: %r", subject
669
                )
670

671
                return response_json
9✔
672
        except (requests.RequestException, json.decoder.JSONDecodeError):
9✔
673
            response_json = {}
9✔
674

675
        PyFunceble.facility.Logger.debug(
676
            "Failed to search subject: %r. Response: %r", subject, response_json
677
        )
678
        PyFunceble.facility.Logger.info("Finished to search subject: %r", subject)
679

680
        return None
9✔
681

682
    @ensure_modern_api
9✔
683
    def pull_contract(self, amount: int = 1) -> Generator[dict, None, None]:
9✔
684
        """
685
        Pulls the next amount of contracts.
686

687
        :param int amount:
688
            The amount of data to pull.
689

690
        :return:
691
            The response of the query.
692
        """
693

694
        PyFunceble.facility.Logger.info("Starting to pull next contract")
695

696
        if not isinstance(amount, int) or amount < 1:
×
697
            amount = 1
×
698

699
        url = f"{self.url_base}/v1/contracts/next"
×
700
        params = {
×
701
            "limit": amount,
702
            "shuffle": True,
703
        }
704

705
        if "none" not in self.checker_priority:
×
706
            params["checker_type_priority"] = ",".join(self.checker_priority)
×
707

708
        if "none" not in self.checker_exclude:
×
UNCOV
709
            params["checker_type_exclude"] = ",".join(self.checker_exclude)
×
710

711
        try:
×
UNCOV
712
            response = self.session.get(
×
713
                url,
714
                params=params,
715
                timeout=self.timeout * 10,
716
            )
717

UNCOV
718
            if response.status_code == 200:
×
UNCOV
719
                response_json = response.json()
×
720

721
                PyFunceble.facility.Logger.debug(
722
                    "Successfully pulled next %r contracts. Response: %r", response_json
723
                )
724

725
                PyFunceble.facility.Logger.info("Finished to pull next contract")
726

UNCOV
727
                yield response_json
×
728
            else:
729
                response_json = []
×
UNCOV
730
        except (requests.RequestException, json.decoder.JSONDecodeError):
×
731
            response_json = []
×
732

733
        PyFunceble.facility.Logger.debug(
734
            "Failed to pull next contract. Response: %r", response_json
735
        )
736
        PyFunceble.facility.Logger.info("Finished to pull next contracts")
737

UNCOV
738
        yield response_json
×
739

740
    @ensure_modern_api
9✔
741
    def deliver_contract(self, contract: dict, contract_data: dict) -> Optional[dict]:
9✔
742
        """
743
        Delivers the given contract data.
744

745
        :param contract:
746
            The contract to deliver.
747
        :param contract_data:
748
            The data to deliver.
749

750
        :return:
751
            The response of the query.
752
        """
753

754
        PyFunceble.facility.Logger.info(
755
            "Starting to deliver contract data: %r", contract
756
        )
757

UNCOV
758
        contract_id = contract["id"]
×
759
        contract_data = (
760
            contract_data.to_json()
761
            if not isinstance(contract_data, dict)
762
            else contract_data
763
        )
UNCOV
764
        url = f"{self.url_base}/v1/contracts/{contract_id}/delivery"
×
765

766
        try:
×
UNCOV
767
            response = self.session.post(
×
768
                url,
769
                data=contract_data.encode("utf-8"),
770
                timeout=self.timeout * 10,
771
            )
772

UNCOV
773
            if response.status_code in (202, 200):
×
UNCOV
774
                response_json = response.json()
×
775

776
                PyFunceble.facility.Logger.debug(
777
                    "Successfully delivered contract: %r. Response: %r",
778
                    contract_data,
779
                    response_json,
780
                )
781

782
                PyFunceble.facility.Logger.info(
783
                    "Finished to deliver contract: %r", contract_data
784
                )
785

UNCOV
786
                return response_json
×
UNCOV
787
            response_json = {}
×
788
        except (requests.RequestException, json.decoder.JSONDecodeError):
×
789
            response_json = {}
×
790

791
        PyFunceble.facility.Logger.debug(
792
            "Failed to deliver contract: %r. Response: %r", contract_data, response_json
793
        )
794
        PyFunceble.facility.Logger.info(
795
            "Finished to deliver contract: %r", contract_data
796
        )
797

UNCOV
798
        return response_json
×
799

800
    @ensure_modern_api
9✔
801
    def push(
9✔
802
        self,
803
        checker_status: Union[
804
            AvailabilityCheckerStatus, SyntaxCheckerStatus, ReputationCheckerStatus
805
        ],
806
    ) -> Optional[dict]:
807
        """
808
        Push the given status to the platform.
809

810
        :param checker_status:
811
            The status to push.
812

813
        :raise TypeError:
814
            - When the given :code:`checker_status` is not a
815
              :py:class:`AvailabilityCheckerStatus`,
816
              :py:class:`SyntaxCheckerStatus` or
817
              :py:class:`ReputationCheckerStatus`.
818

819
            - When the given :code:`checker_status.subject` is not a
820
              :py:class:`str`.
821

822
        :raise ValueError:
823
            When the given :code:`checker_status.subject` is empty.
824
        """
825

826
        if not isinstance(
9✔
827
            checker_status,
828
            (AvailabilityCheckerStatus, SyntaxCheckerStatus, ReputationCheckerStatus),
829
        ):
830
            raise TypeError(
9✔
831
                f"<checker_status> should be {AvailabilityCheckerStatus}, "
832
                f"{SyntaxCheckerStatus} or {ReputationCheckerStatus}, "
833
                f"{type(checker_status)} given."
834
            )
835

836
        if not isinstance(checker_status.subject, str):
9✔
837
            raise TypeError(
9✔
838
                f"<checker_status.subject> should be {str}, "
839
                f"{type(checker_status.subject)} given."
840
            )
841

842
        if not isinstance(checker_status.checker_type, str):
9✔
843
            raise TypeError(
9✔
844
                f"<checker_status_checker_type> should be {str}, "
845
                f"{type(checker_status.subject)} given."
846
            )
847

848
        if checker_status.subject == "":
9✔
849
            raise ValueError("<checker_status.subject> cannot be empty.")
9✔
850

851
        if (
9✔
852
            not self.is_modern_api
853
            and hasattr(checker_status, "expiration_date")
854
            and checker_status.expiration_date
855
        ):
856
            self.__push_whois(checker_status)
9✔
857

858
        data = self.__push_status(
859
            checker_status.checker_type.lower(), checker_status.to_json()
860
        )
861

862
        return data
9✔
863

864
    def guess_all_settings(
865
        self,
866
    ) -> "PlatformQueryTool":  # pragma: no cover ## Underlying tested
867
        """
868
        Try to guess all settings.
869
        """
870

871
        to_ignore = ["guess_all_settings"]
872

873
        for method in dir(self):
874
            if method in to_ignore or not method.startswith("guess_"):
875
                continue
876

877
            getattr(self, method)()
878

879
        return self
880

881
    def __push_status(
9✔
882
        self, checker_type: str, data: Union[dict, str]
883
    ) -> Optional[dict]:
884
        """
885
        Submits the given status to the platform.
886

887
        :param checker_type:
888
            The type of the checker.
889
        :param data:
890
            The data to submit.
891

892
        :raise TypeError:
893
            - When :code:`checker_type` is not a :py:class:`str`.
894

895
            - When :code:`data` is not a :py:class:`dict`.
896

897
        :raise ValueError:
898
            When the given :code:`checker_type` is not a subject checker type.
899
        """
900

901
        if not self.token:
9✔
902
            return None
9✔
903

904
        if checker_type not in self.SUPPORTED_CHECKERS:
9✔
905
            raise ValueError(f"<checker_type> ({checker_type}) is not supported.")
9✔
906

907
        PyFunceble.facility.Logger.info("Starting to submit status: %r", data)
908

909
        if self.is_modern_api:
9✔
910
            if not self.token:
9✔
UNCOV
911
                url = f"{self.url_base}/v1/hub/status/{checker_type}"
×
912
            else:
913
                url = f"{self.url_base}/v1/contracts/self-delivery"
9✔
914
        else:
UNCOV
915
            url = f"{self.url_base}/v1/status/{checker_type}"
×
916

917
        try:
9✔
918
            if isinstance(data, dict):
9✔
UNCOV
919
                response = self.session.post(
×
920
                    url,
921
                    json=data,
922
                    timeout=self.timeout * 10,
923
                )
924
            elif isinstance(
9✔
925
                data,
926
                (
927
                    AvailabilityCheckerStatus,
928
                    SyntaxCheckerStatus,
929
                    ReputationCheckerStatus,
930
                ),
931
            ):
UNCOV
932
                response = self.session.post(
×
933
                    url,
934
                    json=data.to_dict(),
935
                    timeout=self.timeout * 10,
936
                )
937
            else:
938
                response = self.session.post(
9✔
939
                    url,
940
                    data=data,
941
                    timeout=self.timeout * 10,
942
                )
943

944
            response_json = response.json()
9✔
945

946
            if response.status_code == 200:
9✔
947
                PyFunceble.facility.Logger.debug(
948
                    "Successfully submitted data: %r to %s", data, url
949
                )
950

951
                PyFunceble.facility.Logger.info("Finished to submit status: %r", data)
952
                return response_json
9✔
953
        except (requests.RequestException, json.decoder.JSONDecodeError):
9✔
954
            response_json = {}
9✔
955

956
        PyFunceble.facility.Logger.debug(
957
            "Failed to submit data: %r to %s. Response: %r", data, url, response_json
958
        )
959

960
        PyFunceble.facility.Logger.info("Finished to submit status: %r", data)
961

962
        return None
9✔
963

964
    def __push_whois(self, data: dict) -> Optional[dict]:
9✔
965
        """
966
        Submits the given WHOIS data into the given subject.
967

968
        :param checker_type:
969
            The type of the checker.
970
        :param data:
971
            The data to submit.
972

973
        :raise TypeError:
974
            - When :code:`checker_type` is not a :py:class:`str`.
975

976
            - When :code:`data` is not a :py:class:`dict`.
977

978
        :raise ValueError:
979
            When the given :code:`checker_type` is not a subject checker type.
980
        """
981

982
        if not self.token:
9✔
983
            return None
9✔
984

985
        PyFunceble.facility.Logger.info("Starting to submit WHOIS: %r", data)
986

UNCOV
987
        url = f"{self.url_base}/v1/status/whois"
×
988

UNCOV
989
        try:
×
990
            if isinstance(data, dict):
×
991
                response = self.session.post(
×
992
                    url,
993
                    json=data,
994
                    timeout=self.timeout * 10,
995
                )
UNCOV
996
            elif isinstance(
×
997
                data,
998
                (
999
                    AvailabilityCheckerStatus,
1000
                    SyntaxCheckerStatus,
1001
                    ReputationCheckerStatus,
1002
                ),
1003
            ):
1004
                response = self.session.post(
1005
                    url,
1006
                    data=data.to_json(),
1007
                    timeout=self.timeout * 10,
1008
                )
1009
            else:
UNCOV
1010
                response = self.session.post(
×
1011
                    url,
1012
                    data=data,
1013
                    timeout=self.timeout * 10,
1014
                )
1015

UNCOV
1016
            response_json = response.json()
×
1017

UNCOV
1018
            if response.status_code == 200:
×
1019
                PyFunceble.facility.Logger.debug(
1020
                    "Successfully submitted WHOIS data: %r to %s", data, url
1021
                )
1022

1023
                PyFunceble.facility.Logger.info("Finished to submit WHOIS: %r", data)
UNCOV
1024
                return response_json
×
1025
        except (requests.RequestException, json.decoder.JSONDecodeError):
×
1026
            response_json = {}
×
1027

1028
        PyFunceble.facility.Logger.debug(
1029
            "Failed to WHOIS data: %r to %s. Response: %r", data, url, response_json
1030
        )
1031

1032
        PyFunceble.facility.Logger.info("Finished to submit WHOIS: %r", data)
UNCOV
1033
        return None
×
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