HTTP transport¶
The transport every online client shares: pacing, retries, the circuit breaker and the exception hierarchy. See Network behaviour.
provesid.http
¶
One rate-limited, retrying HTTP transport shared by every PROVESID web client.
Before this module each web-API client carried its own copy of "pause, request,
decide what the status code meant, maybe give up" --- four copies that had
drifted apart, none of which honoured Retry-After and only one of which
retried at all. HTTPClient is the single place
that decides when to ask again; the clients keep everything that encodes
their upstream's contract: the URLs they build, the bodies they parse and the
exceptions they raise.
The one thing services genuinely disagree about is what a response means.
PubChem answers a momentary overload with PUGVIEW.ServerBusy behind a 404,
so its status code alone is a lie; the NCI resolver returns plain text and a
bare 404 for absence. That disagreement is the classify callback --- a
function from a response to an Outcome --- and it is
the only part of the policy a caller is expected to supply.
Two things belong to the host rather than to any one client, and live on its
shared RateLimiter: the pacing clock, and the
circuit breaker. The breaker is the time a Retry-After named, and no client
sharing the host asks before it.
Examples:
>>> client = HTTPClient(min_interval=0.2, timeout=30)
>>> client.get_text("https://example.org/thing")
'an answer'
Attributes¶
RETRYABLE_STATUS
module-attribute
¶
Status codes that mean "the service is momentarily unwilling", whatever else the body says. 429 is explicit throttling; 5xx is the service failing on its own side.
Classes¶
ServiceError
¶
Bases: Exception
Base for every failure this package reports from a web service.
Each client raises its own subclass ---
PubChemViewError,
NCIResolverError and so on --- so a
caller can catch one service or, through this base, all of them. A raw
requests exception never escapes
HTTPClient.
The message is the whole of what most callers want, so it stays the single
positional argument and str(exc) is unchanged. The response detail is
keyword-only and defaults to None, which is what lets a client raise one of
these by hand --- raise PubChemError("bad request") --- exactly as
before. It exists because two services distinguish their failures by
status: CAS Common Chemistry reports a rejected key as 401 and an unknown
CAS number as 404, and both have to become different entries in the dict it
returns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
The human-readable description. |
''
|
status_code
|
Optional[int]
|
The HTTP status that caused the failure, when there was a response at all. None for a timeout or a connection error. |
None
|
url
|
Optional[str]
|
The URL that was requested. |
None
|
response
|
Optional[Response]
|
The raw response, for a caller that needs to read the body. None when no response arrived. |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
status_code |
As above. |
|
url |
As above. |
|
response |
As above. |
|
held_until |
Optional[float]
|
The Unix time an earlier |
Examples:
>>> issubclass(NotFoundError, ServiceError)
True
>>> exc = ServiceError("nope", status_code=404)
>>> str(exc), exc.status_code
('nope', 404)
>>> ServiceError("by hand").status_code is None
True
Source code in src/provesid/http.py
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | |
NotFoundError
¶
Bases: ServiceError
The service answered, and its answer was that the record does not exist.
This is a statement about the data, not about the request. It is never retried, because asking again cannot change it.
Examples:
>>> issubclass(NotFoundError, ServiceError)
True
Source code in src/provesid/http.py
101 102 103 104 105 106 107 108 109 110 111 112 | |
RateLimitError
¶
Bases: ServiceError
The service kept throttling the request until the retry budget ran out.
Raised only when every attempt was refused with a rate-limit response; a 429 that clears on a later attempt is invisible to the caller.
Examples:
>>> issubclass(RateLimitError, ServiceError)
True
Source code in src/provesid/http.py
115 116 117 118 119 120 121 122 123 124 125 126 | |
ServiceTimeoutError
¶
Bases: ServiceError
Every attempt timed out or the connection could not be made.
Examples:
>>> issubclass(ServiceTimeoutError, ServiceError)
True
Source code in src/provesid/http.py
129 130 131 132 133 134 135 136 137 | |
Outcome
¶
Bases: Enum
What a response means, once a service-specific classifier has read it.
Attributes:
| Name | Type | Description |
|---|---|---|
OK |
Use the response. |
|
ABSENT |
The record does not exist --- raise the client's not-found exception without retrying. |
|
RETRY |
A transient condition; ask again after backing off. |
|
FATAL |
A permanent error that is not absence --- a malformed request, a rejected key. Retrying cannot help. |
Examples:
>>> Outcome.RETRY.name
'RETRY'
Source code in src/provesid/http.py
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | |
RateLimiter
¶
The clock a host's requests are paced against, and the time before which the host has asked not to be asked at all.
A client's min_interval is its own promise about how fast it will ask.
The clock it measures that promise against belongs to the host, because
the limit being respected does too: PubChem publishes five requests per
second per IP, not per Python object. Two clients aimed at PubChem in
one process --- a PubChemAPI and a
PubChemView, which is the ordinary way to
use this package --- each kept their own clock before this class existed,
so each could believe it was pacing correctly while together they asked
twice as fast as PubChem allows.
Sharing the clock means a request waits min_interval after the last
request anyone made to that host. The lock is held across the sleep, so
threads queue rather than all waking at once.
A Retry-After is information about the host in the same way, so it is
kept here too, as not_before: the circuit
breaker. Before, each call rediscovered a throttle by being refused it; a
caller resolving a thousand names against a PubChem that had blocked this
IP paid a request per name to learn the same thing a thousand times. Now
the first refusal is remembered, and every client aimed at that host either
waits it out, when that fits its own retry patience, or fails at once
without asking --- see
HTTPClient.request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host
|
Optional[str]
|
The host this clock belongs to, for messages. None for a client's private clock. |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
host |
As above. |
|
last_request_time |
When any client last asked this host, as a Unix timestamp; 0.0 before the first request. |
|
not_before |
The Unix time before which the host has asked not to be
asked; 0.0 when it has asked for nothing. Only ever moves later,
until |
Examples:
>>> limiter = RateLimiter()
>>> limiter.last_request_time
0.0
>>> _ = limiter.wait(0.0) # pacing off: returns at once
>>> limiter.last_request_time > 0
True
>>> limiter.hold(30)
>>> 29 < limiter.held_for() <= 30
True
>>> limiter.release()
>>> limiter.held_for()
0.0
Source code in src/provesid/http.py
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 | |
Methods:¶
wait(min_interval)
¶
Sleep until min_interval has passed since this host was last asked.
Pacing only: this does not wait out a
hold, because whether a hold is
worth waiting for is the caller's decision, not the clock's.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
min_interval
|
float
|
Seconds the caller promises to leave between requests. 0 or less disables the wait but still records the request, because the request is happening either way and the next caller needs to know when. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The time at which the caller may proceed, as a Unix timestamp. |
Examples:
>>> limiter = RateLimiter()
>>> first = limiter.wait(0.05)
>>> limiter.wait(0.05) - first >= 0.045
True
Source code in src/provesid/http.py
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 | |
hold(seconds)
¶
Record that the host asked not to be asked again for seconds.
The hold is the host's full request, not a client's capped wait: a client unwilling to wait that long gives up rather than asking early. A shorter hold never shortens a longer one already in place, because the host's latest word does not retract what it said to another request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seconds
|
float
|
How long, from now. 0 or less records nothing. |
required |
Examples:
>>> limiter = RateLimiter()
>>> limiter.hold(60); limiter.hold(5)
>>> limiter.held_for() > 50
True
Source code in src/provesid/http.py
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 | |
held_for()
¶
Return how many seconds remain before the host may be asked again.
Returns:
| Type | Description |
|---|---|
float
|
Seconds until |
Examples:
>>> RateLimiter().held_for()
0.0
Source code in src/provesid/http.py
372 373 374 375 376 377 378 379 380 381 382 383 384 | |
release()
¶
Forget any hold, so the next request is sent without waiting.
For a caller who knows better than the header --- a network change, a new API key --- and for tests, which must not leave a hold on a shared host behind them.
Examples:
>>> limiter = RateLimiter()
>>> limiter.hold(600); limiter.release()
>>> limiter.held_for()
0.0
Source code in src/provesid/http.py
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 | |
HTTPClient
¶
Rate-limited HTTP client with retry and back-off, shared by every PROVESID web-API module.
One instance belongs to one client object and paces that client's own requests; it holds no state beyond the time of its last request and the policy it was given.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
min_interval
|
float
|
Minimum seconds between two requests from this client. 0 disables pacing. |
0.0
|
timeout
|
float
|
Default per-request timeout in seconds. |
30
|
max_retries
|
int
|
Retries after the first attempt, so a request is made
at most |
3
|
backoff
|
float
|
Base for the exponential wait, |
1.0
|
max_backoff
|
float
|
Ceiling on any single wait. A |
60.0
|
max_elapsed
|
Optional[float]
|
Ceiling on the total time spent waiting between
attempts. Retrying stops once the next wait would take the sum past
it, whatever This exists because a service that says |
None
|
headers
|
Optional[Dict[str, str]]
|
Headers sent with every request. Omitted entirely when None,
so a stub that accepts only |
None
|
classify
|
Callable[[Response], Outcome]
|
Maps a response to an |
default_classify
|
error_cls
|
Type[Exception]
|
Raised for a fatal response --- a malformed request, a
rejected key --- and, unless |
ServiceError
|
not_found_cls
|
Type[Exception]
|
Raised for |
NotFoundError
|
timeout_cls
|
Optional[Type[Exception]]
|
Raised when every attempt timed out or could not connect.
Defaults to |
None
|
rate_limit_cls
|
Optional[Type[Exception]]
|
Raised when every attempt was throttled. Defaults to
|
None
|
retry_exhausted_cls
|
Optional[Type[Exception]]
|
Raised when a transient condition that was neither
a throttle nor a timeout outlived the retry budget --- a service
that stayed busy. Defaults to |
None
|
session
|
Optional[Session]
|
A |
None
|
pace_host
|
Optional[str]
|
The service this client shares its pacing clock with, as a
host or as the base URL it already holds. Given, the client waits
|
None
|
logger
|
Optional[Logger]
|
Logger for the DEBUG line per request and the WARNING per retry. Defaults to this module's logger. |
None
|
Examples:
>>> client = HTTPClient(min_interval=0.2, timeout=10, max_retries=2)
>>> client.get_json("https://example.org/data.json")
{'ok': True}
Source code in src/provesid/http.py
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 | |
Attributes¶
limiter
instance-attribute
¶
The RateLimiter that paces and holds
this client's requests: the host's shared one from
host_limiter when pace_host is
given, so every client aimed at that host waits on one clock and one
Retry-After, and a private one otherwise.
Methods:¶
rate_limit()
¶
Sleep, if needed, so requests to this service stay min_interval
apart.
Called by request before every
attempt --- including retries, which is the point: a service that is
shedding load should not be asked again faster than a service that is
not.
The interval is this client's own; the clock it is measured against
belongs to limiter, which is shared with every other client
aimed at the same host when pace_host was given. So the wait is
min_interval since anybody last asked that service, not since
this object did.
Examples:
>>> client = HTTPClient(min_interval=0.0)
>>> client.rate_limit() # returns at once when pacing is off
Source code in src/provesid/http.py
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 | |
request(method, url, **kwargs)
¶
Make a request, retrying transient failures, and return the response.
A Retry-After on any refused attempt is recorded on the host's
RateLimiter, so it binds every client
sharing that host, not only this call. A call that finds the host held
waits the hold out when it is no longer than max_backoff (and
max_elapsed), and otherwise raises rate_limit_cls at once,
without a request. That is what makes a throttled host fail in
microseconds for the thousandth name rather than cost a refused request
per name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
str
|
|
required |
url
|
str
|
The full URL. |
required |
**kwargs
|
Any
|
|
{}
|
Returns:
| Type | Description |
|---|---|
Response
|
The response, already classified as |
Raises:
| Type | Description |
|---|---|
not_found_cls
|
The service reported the record as absent. |
error_cls
|
A permanent error. |
timeout_cls
|
Every attempt timed out or failed to connect. |
rate_limit_cls
|
Every attempt was throttled, or the host is held
by an earlier |
retry_exhausted_cls
|
A transient condition outlived the retry
budget, either in attempts, in |
Every one of those carries the status, the URL and the response on the
exception when there was a response --- see
ServiceError --- so a client can tell a
rejected key from an unknown record without re-reading the wire.
Examples:
>>> client = HTTPClient()
>>> client.request("GET", "https://example.org/x").text
'ok'
Source code in src/provesid/http.py
729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 | |
get(url, **kwargs)
¶
GET a URL and return the raw response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The full URL. |
required |
**kwargs
|
Any
|
See |
{}
|
Returns:
| Type | Description |
|---|---|
Response
|
The response. |
Examples:
>>> HTTPClient().get("https://example.org/img.png").content
b'\x89PNG...'
Source code in src/provesid/http.py
938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 | |
post(url, **kwargs)
¶
POST to a URL and return the raw response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The full URL. |
required |
**kwargs
|
Any
|
See |
{}
|
Returns:
| Type | Description |
|---|---|
Response
|
The response. |
Examples:
>>> HTTPClient().post("https://example.org/q", data={"cid": 2244})
<Response [200]>
Source code in src/provesid/http.py
955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 | |
get_text(url, **kwargs)
¶
GET a URL and return its body as stripped text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The full URL. |
required |
**kwargs
|
Any
|
See |
{}
|
Returns:
| Type | Description |
|---|---|
str
|
The response body, with surrounding whitespace removed. |
Examples:
>>> HTTPClient().get_text("https://example.org/smiles")
'CCO'
Source code in src/provesid/http.py
972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 | |
get_json(url, **kwargs)
¶
GET a URL and return its parsed JSON body.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The full URL. |
required |
**kwargs
|
Any
|
See |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
The decoded JSON. |
Raises:
| Type | Description |
|---|---|
error_cls
|
The body is not valid JSON. A service that answers 200 with something unparseable has failed as surely as one that answers 500, and the caller should hear about it in the same way. |
Examples:
>>> HTTPClient().get_json("https://example.org/data.json")
{'ok': True}
Source code in src/provesid/http.py
989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 | |
post_json(url, **kwargs)
¶
POST to a URL and return its parsed JSON body.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The full URL. |
required |
**kwargs
|
Any
|
See |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
The decoded JSON. |
Raises:
| Type | Description |
|---|---|
error_cls
|
The body is not valid JSON. |
Examples:
>>> HTTPClient().post_json("https://example.org/q", json={"n": 1})
{'ok': True}
Source code in src/provesid/http.py
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 | |
decode_json(response, url=None)
¶
Parse a response body as JSON, reporting failure as a service error.
Public because a client that decides for itself whether a body is JSON
--- ChEBI reads the Content-Type, because it serves molfiles and SVG
from the same API as its records --- needs the same failure reported the
same way.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
response
|
Response
|
The response to decode. |
required |
url
|
Optional[str]
|
The URL, for the error message. Falls back to the response's own when it has one. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
The decoded JSON. |
Raises:
| Type | Description |
|---|---|
error_cls
|
The body is not valid JSON. |
Examples:
>>> class R:
... text = 'not json'
... def json(self): raise ValueError("nope")
>>> HTTPClient().decode_json(R(), "https://example.org/x")
Traceback (most recent call last):
...
provesid.http.ServiceError: Response from https://example.org/x is not JSON: not json
Source code in src/provesid/http.py
1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 | |
Functions:¶
default_classify(response)
¶
Classify a response by its HTTP status alone.
The right reading for any service that uses status codes honestly: 2xx is an answer, 404 is absence, 429 and 5xx are worth retrying, and any other 4xx is a permanent error in the request itself. Services that describe errors in the body --- PubChem does --- supply their own classifier instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
response
|
Response
|
The response to classify. |
required |
Returns:
| Type | Description |
|---|---|
Outcome
|
The |
Examples:
>>> class R: status_code = 404
>>> default_classify(R()).name
'ABSENT'
>>> class R: status_code = 503
>>> default_classify(R()).name
'RETRY'
Source code in src/provesid/http.py
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | |
retry_after_seconds(response)
¶
Read a Retry-After header, in either form the standard allows.
RFC 9110 permits a number of seconds or an HTTP date. A service that
troubles itself to say when to come back knows better than any backoff
curve, so HTTPClient prefers this over its
own schedule.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
response
|
Response
|
The response whose headers to read. |
required |
Returns:
| Type | Description |
|---|---|
Optional[float]
|
The wait in seconds, or None when the header is absent, unparseable or in the past. |
Examples:
>>> class R: headers = {"Retry-After": "12"}
>>> retry_after_seconds(R())
12.0
>>> class R: headers = {}
>>> retry_after_seconds(R()) is None
True
Source code in src/provesid/http.py
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | |
host_limiter(url_or_host)
¶
Return the process-wide RateLimiter for one
host, creating it once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url_or_host
|
str
|
A full URL, whose host is used, or a bare host. Accepting either is deliberate: a client already holds its service's base URL, so it can pass that and needs no second piece of configuration to get its pacing shared correctly. |
required |
Returns:
| Type | Description |
|---|---|
RateLimiter
|
The limiter for that host. Two calls naming the same host return the same object. |
Examples:
>>> a = host_limiter("https://pubchem.ncbi.nlm.nih.gov/rest/pug")
>>> b = host_limiter("https://pubchem.ncbi.nlm.nih.gov/rest/pug_view")
>>> a is b
True
>>> a is host_limiter("https://www.ebi.ac.uk/chebi")
False
Source code in src/provesid/http.py
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 | |
release_holds()
¶
Forget every host's Retry-After hold in this process.
The process-wide form of
RateLimiter.release: after it, every
client asks its host again on its next call. Pacing clocks are untouched.
Examples:
>>> host_limiter("https://held.example.test").hold(600)
>>> release_holds()
>>> host_limiter("https://held.example.test").held_for()
0.0
Source code in src/provesid/http.py
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 | |