Exceptions¶
Every exception is importable from pgpcbot and derives from PGPCbotError, so
except PGPCbotError catches anything the library raises on purpose. The guide is
Errors.
No exception ever contains the token, a passphrase or private key material.
Server error codes¶
Each error code of the Bot API has its own class. A code this version does not know is mapped by
its HTTP status (400 to BadRequestError, 404 to NotFoundError, 5xx to
ServerError, and so on).
Code |
Status |
Exception |
Meaning |
|---|---|---|---|
|
400 |
the body or the query is wrong; |
|
|
400 |
|
|
|
400 |
the buttons break a rule |
|
|
400 |
the webhook URL is not acceptable |
|
|
400 |
keys asked for a readable chat |
|
|
413 |
the request body is over 256 KiB |
|
|
401 |
missing, revoked, replaced or invalid token |
|
|
403 |
the owner paused the bot |
|
|
403 |
no right to do this here |
|
|
403 |
the person blocked the bot |
|
|
403 |
the person closed their account |
|
|
404 |
no such path: usually a wrong |
|
|
404 |
no such chat, or the bot is not in it |
|
|
404 |
no such message |
|
|
404 |
not a member, or hidden from the bot |
|
|
404 |
no such person, or no chat in common |
|
|
404 |
nobody and no chat answers to that handle |
|
|
404 |
the person never published a key |
|
|
404 |
no such button press |
|
|
409 |
the |
|
|
409 |
polling while a webhook is set |
|
|
409 |
the press was answered already |
|
|
410 |
more than 60 seconds since the press |
|
|
429 |
too many API calls |
|
|
429 |
too many messages into one chat, or overall; also too many handle lookups |
|
|
429 |
the group’s slow mode |
|
|
500 |
the server failed |
The base classes¶
- exception PGPCbotError¶
Bases:
ExceptionBase class of every error pgpcbot raises on purpose.
except PGPCbotErrorcatches what can go wrong at run time: the setup, the server, the network, OpenPGP, the storage. It is never raised itself. A mistake in how the library is called, such as an argument of the wrong type, can raise a plainTypeErrororValueErrorinstead.
- exception ConfigurationError¶
Bases:
PGPCbotError,ValueErrorThe bot, a client or a key is set up wrong.
Raised before anything is sent: no token, or one that is not shaped like a token (the error never repeats it); an
api_urlthat is not usable, or plainhttpto a host that is not local withoutallow_insecure_http=True; an option out of range; a key file that cannot be read; a handler of the wrong kind (a plaindefonBot, anasync defonSyncBot); a webhook app without a secret. Retrying never helps: fix the setup. It is also aValueError.
- exception ValidationError(message, *, field=None)¶
Bases:
PGPCbotError,ValueErrorA request refused before it left, because the server would refuse it too.
Every method that calls the API first checks its input against the server’s rules: text length, keyboards, IDs, URLs, the webhook secret, and so on. When a check fails, nothing is sent and this error says why, so retrying the same call fails the same way. The only check made after a request went out is the size limit of
AsyncClient.download_media(max_bytes). It is also aValueError.- field¶
The parameter at fault, as the method names it (
"text","markup","send_id"…), orNonewhen no single parameter is to blame.- Type:
str | None
Example
A text over 4096 characters is refused before it is sent, with
fieldset to"text":try: await message.reply(report) except ValidationError as exc: log.warning("not sent: %s (%s)", exc, exc.field)
Creates the error.
Answers from the server¶
- exception APIError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
PGPCbotErrorThe server answered with an error.
Every error answer of the Bot API carries a stable
code, an HTTP status and a message. The client raises the subclass that belongs to the code, so you can catch the one case you can do something about (BlockedByUserError,ChatNotFoundError…) or every refusal at once withexcept APIError. A code this version does not know yet still gets the class of its status (NotFoundErrorfor any404,ServerErrorfor any5xx…).Branch on the class or on
code: both are stable.messageis for a person reading a log and may change between server versions.str(error)readscode (status): message [METHOD path].Whether a retry helps depends on the subclass: after
retry_afterseconds for aRateLimitError, later for aServerError. The others fail the same way until something changes.- code¶
The server’s error code, such as
"blocked_by_user"."http_<status>"when the answer had none.- Type:
- message¶
The server’s explanation, for people; the code when there was none. Text shaped like a bot token is masked.
- Type:
- expected¶
"text"or"armored": the body the chat takes, onwrong_body_kind(and on abad_requestfor a send with no body).Noneotherwise.- Type:
str | None
Examples
Catch the cases you can do something about, and log the rest:
try: await bot.send_message(user_id, "Your order has shipped.") except (BlockedByUserError, UserGoneError): subscribers.discard(user_id) except APIError as exc: log.warning("not sent to %s: %s", user_id, exc)
A test can build one by hand:
>>> from pgpcbot import BlockedByUserError >>> error = BlockedByUserError( ... "blocked", status=403, code="blocked_by_user", method="POST", path="/messages" ... ) >>> str(error) 'blocked_by_user (403): blocked [POST /messages]'
Creates the error. The client does this for you; tests can do it by hand.
- Parameters:
message (str) – The explanation. Text shaped like a bot token is masked.
status (int) – The HTTP status.
code (str) – The error code.
retry_after (float | None) – Seconds to wait, for a
429.expected (str | None) –
"text"or"armored", forwrong_body_kind.method (str | None) – The HTTP method of the request.
path (str | None) – The path of the request.
payload (dict[str, Any] | None) – The
errorobject as it came. Its top-level strings shaped like a bot token are masked too.
- exception BadRequestError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
APIError400: the body or the query of the request is wrong.
With the code
bad_request,messagesays which field. The client checks the rules it knows before sending (aValidationError); this comes when the server refuses anyway. Its subclasses cover the other400codes. Retrying the same request fails the same way.
- exception WrongBodyKindError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
BadRequestError400
wrong_body_kind: the body does not fit the chat’s mode.The request sent
textinto an end-to-end encrypted chat, orarmoredinto a readable one, usually because the chat changed mode since the bot last looked.expectedsays which body the chat takes now.Botdeals with it in sends and edits. When a readable chat became encrypted, it seals the text and sends it again, a send with the samesendId(the words did reach the server in the refused request, so a warning is logged). When an encrypted chat became readable, it raises this error instead of sending in clear, and the next send uses the new mode. WithAsyncClientandClient, send the other kind of body.
- exception BadMarkupError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
BadRequestError400
bad_markup: the inline keyboard breaks one of the server’s rules.The client checks keyboards before sending (a
ValidationErrorwithfield"markup"); this comes when the server refuses one anyway. Retrying the same keyboard fails the same way.
- exception BadWebhookError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
BadRequestError400
bad_webhook: the server refused the webhook URL.For example it is not
https, or it resolves to a private address. The client checks the shape of the URL before sending (aValidationError); the rest only the server can judge. Use another URL.
- exception ChatIsOpenError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
BadRequestError400
chat_is_open: keys were asked for a readable chat.A readable chat has no keys: send
textthere.Botgets it when a chat stopped being encrypted while it was sealing a message; it raises instead of sending in clear, and the next send uses the new mode.
- exception PayloadTooLargeError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
APIError413
too_large: the request body was over 256 KiB.Retrying the same body fails the same way.
- exception AuthenticationError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
APIError401
unauthorized: the token is missing, revoked, replaced or invalid.Any call can raise it, and a bot stops with it. Retrying never helps: the owner has to make a new token in the console. A token that is not even shaped like one fails earlier, with a
ConfigurationError, before anything is sent.
- exception ForbiddenError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
APIError403: the bot has no right to do that here.
With the code
forbidden: for example a send where the bot may not write, or an edit or delete of a message that is not the bot’s own. Its subclasses cover the other403codes. Retrying fails the same way until the bot’s rights change.
- exception BotPausedError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
ForbiddenError403
bot_paused: the owner paused the bot from the console.Nothing goes in or out until the owner resumes it. Meanwhile only
get_me,get_webhook,delete_webhookandrevoke_tokenwork. A polling bot does not stop: it logs it once and checks again every 30 seconds. Elsewhere, retrying helps only once the bot is resumed.
- exception BlockedByUserError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
ForbiddenError403
blocked_by_user: the person blocked the bot.Raised by calls into the private chat with them, such as sends and edits. Stop writing to them: retrying fails until they unblock the bot.
- exception UserGoneError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
ForbiddenError403
user_gone: the person closed their account.Raised by calls into the private chat with them. Retrying never helps: remove them from your lists.
- exception NotFoundError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
APIError404: something named in the request does not exist, or is not the bot’s to see.
Its subclasses say what was not found. A
404with a code this version does not know is a plainNotFoundError, and so is a media file that is gone (codeis"media_not_found", fromAsyncClient.download_media). Retrying fails the same way.
- exception EndpointNotFoundError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
NotFoundError404
not_found: the server has no such path.Usually
api_url(orPGPCHAT_API) points to the wrong server: use the API address the console shows. Retrying does not help.
- exception ChatNotFoundError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
NotFoundError404
chat_not_found: no such chat, or the bot is not in it.The server gives the same answer for both, so a bot that was removed from a group gets this too.
- exception MessageNotFoundError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
NotFoundError404
message_not_found: no such message in that chat.On a delete it means the message is already gone. When a delete timed out and the retry gets this answer, the first try did delete it, and the client returns
Trueinstead of raising.
- exception MemberNotFoundError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
NotFoundError404
member_not_found: not a member of the chat, or hidden from the bot.Where the member list is hidden, a bot that is not an admin sees only the admins, and gets this error for everybody else.
- exception UserNotFoundError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
NotFoundError404
user_not_found: no such person, or nobody the bot shares a chat with.The bot can only look up people it shares a chat with. For anybody else the server answers this, whether or not they exist.
- exception UsernameNotFoundError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
NotFoundError404
username_not_found: nobody and no chat answers to that handle.From
AsyncClient.resolve. It means the handle is free, held by a chat with no public name, or held by an account that has been closed - the answer does not say which.
- exception NoUserKeyError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
NotFoundError404
no_user_key: the person never published a public key.From
AsyncClient.get_user_key, and fromAsyncClient.get_chat_keysfor a private chat whose person has no key. Nothing can be sealed to them until they publish one.
- exception CallbackNotFoundError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
NotFoundError404
callback_not_found: no such button press for this bot.
- exception ConflictError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
APIError409: the request clashes with the state the server is in.
Its subclasses say what the clash is. A
409with a code this version does not know is a plainConflictError.
- exception SendIdConflictError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
ConflictError409
conflict: thissend_idwas already used for a message in another chat.The same
send_idagain in the same chat is not an error: the server returns the message it already wrote. Leavesend_idout and the library makes a new one for each message.
- exception WebhookActiveError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
ConflictError409
webhook_active: polling while a webhook is set.The server refuses
get_updateswhile the bot has a webhook. Delete the webhook first (bot.run(drop_webhook=True)does it at start), or take the updates on the webhook. A polling bot turns this into aPollingError.
- exception CallbackAnsweredError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
ConflictError409
callback_answered: that press was answered already.Each press can be answered once, so this is safe to ignore. When an answer timed out and the retry gets this, the first try went through, and the client returns
Trueinstead of raising.
- exception CallbackExpiredError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
APIError410
callback_expired: more than 60 seconds went by since the press.A press can only be answered within 60 seconds: this one is too late, and retrying fails the same way. A
410with a code this version does not know also gets this class.
- exception RateLimitError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
APIError429: too many requests, after the client waited as much as its
RetryPolicyallows.The client waits out a
429on its own, as long as the server asks, up torate_limit_retriestimes per call. It raises at once when the server asks for more thanmax_rate_limit_waitseconds. Waitretry_afterseconds before trying again: the server’s counter does not move while you wait, so waiting exactly that long is enough.With the code
rate_limitedit means too many API calls of any kind (600 a minute by default). The subclasses are about messages:FloodWaitErrorandSlowModeError. A429with a code this version does not know is a plainRateLimitError.
- exception FloodWaitError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
RateLimitError429
flood_wait: too many messages, into one chat or overall.By default the server takes one message a second into a private chat, twenty a minute into a group or channel, and thirty a second in all; edits are counted apart, with the same numbers. The client paces itself the same way (
RateLimiter), so this comes mostly when another process uses the same token. Waitretry_afterseconds.
- exception SlowModeError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
RateLimitError429
slow_mode: the group has slow mode on.Its admins set a time everybody, bots included, must wait between two messages.
retry_aftersays how long until the bot may write again. The client’s own pacing does not know about slow mode.
- exception ServerError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
APIError5xx: the server failed.
It has the code
internal, or comes from any other5xxanswer, with or without the API’s envelope. The client already tried again for the calls that are safe to repeat (RetryPolicy.attemptstries in all), and a polling bot keeps trying on its own. Worth retrying later.
- exception UnexpectedResponseError(
- message,
- *,
- status,
- code,
- retry_after=None,
- expected=None,
- method=None,
- path=None,
- payload=None,
Bases:
APIErrorThe answer was not the API’s JSON envelope.
For example an HTML page from a proxy, a body cut short, plain text or nothing at all, with a status below 500 (with a
5xxstatus it is aServerError).codeis"http_<status>", andmessagequotes the start of the body. The client does not retry it. Check thatapi_urlpoints to the Bot API and that no proxy in between answers in its place.
- exception PayloadError¶
Bases:
PGPCbotError,ValueErrorA JSON object from the server is missing something this version cannot do without.
Raised while an answer or an update is read into a model, for example a message without a date. Retrying gets the same answer: update PGPCbot. It is also a
ValueError.
- error_from_envelope(
- error,
- *,
- http_status,
- method=None,
- path=None,
- retry_after_header=None,
Builds the right
APIErrorsubclass from theerrorobject of an answer.The clients call it for every error answer. It is public for code that reads the Bot API’s answers some other way, such as a test double. The class comes from
code; a code this version does not know falls back on the status:ServerErrorfor any5xx, the class of the status for400,401,403,404,409,410,413and429, and a plainAPIErrorfor anything else.Missing or odd values never make it fail: no status means
http_status, no code means"http_<status>", no message means the code. Text shaped like a bot token is masked.- Parameters:
error (Mapping[str, Any]) – The
errorobject of the answer, as decoded from JSON.http_status (int) – The status on the HTTP line, used when
errorhas none.method (str | None) – The HTTP method of the request, kept in the error.
path (str | None) – The path of the request, kept in the error.
retry_after_header (float | None) – The
Retry-Afterheader in seconds, if the answer had one. It is used when the body has noretryAfter, and always forrate_limited, where the header has the exact time left.
- Returns:
The error, ready to raise. Nothing is raised here.
- Return type:
Examples
>>> from pgpcbot.errors import error_from_envelope >>> error = error_from_envelope( ... {"code": "flood_wait", "message": "too many messages", "retryAfter": 3}, ... http_status=429, ... method="POST", ... path="/messages", ... ) >>> type(error).__name__, error.retry_after ('FloodWaitError', 3.0) >>> str(error) 'flood_wait (429): too many messages [POST /messages]'
A code this version does not know gets the class of its status:
>>> error_from_envelope({"code": "brand_new", "message": "?"}, http_status=404) NotFoundError(status=404, code='brand_new', message='?')
The connection¶
- exception NetworkError¶
Bases:
PGPCbotErrorThe request got no answer: DNS, a refused or reset connection, TLS, a timeout.
The client already tried again, up to
RetryPolicy.attemptstries in all, for the calls that are safe to repeat: every operation exceptget_updatesandsend_typing. Media downloads are not retried. A polling bot keeps trying on its own. An answer larger than 8 MiB is aNetworkErrortoo, and is not retried: asking again would only download it again. The message names the request and the cause, never the token.
- exception RequestTimeoutError¶
Bases:
NetworkErrorNo answer came in time.
The limit is the
timeoutof the bot or the client: 30 seconds by default, 10 to connect; long polls get extra time. Like anyNetworkErrorit was already retried when the call is safe to repeat. A send that timed out may still have been written. To be able to try it again yourself, give it asend_idand pass the same one again: the server then returns the message it already wrote instead of writing it twice.
OpenPGP¶
- exception CryptoError¶
Bases:
PGPCbotErrorSomething in the OpenPGP layer went wrong.
The base class of the OpenPGP errors: catch it to handle all of them at once. It is raised as itself only in a rare case: a private key loaded from text that is not UTF-8, which then cannot be armored again.
- exception InvalidKeyError¶
Bases:
CryptoErrorA key that cannot be read, or cannot do what it is asked to.
Raised when a key is loaded or used: text that is not an OpenPGP key, a public key where the bot needs its private key (or the other way round), a private key the bot cannot use (it needs a signing key and an encryption subkey, neither expired nor revoked), a key whose fingerprint is not the one the server lists for it (
PublicKey.from_user_key), or a v6 key to publish, which phones on PGPainless 1.7 cannot use. Retrying with the same key fails the same way.
- exception PassphraseError¶
Bases:
CryptoErrorThe private key is locked, and the passphrase is missing or wrong.
Raised when a key is loaded (
BotKey.from_fileand the other loaders), so a bot with a wrong passphrase fails when it is created, not at the first message. Checkpgp_passphraseorPGPCHAT_BOT_KEY_PASSPHRASE; retrying with the same passphrase fails the same way.
- exception EncryptionError¶
Bases:
CryptoErrorA message could not be encrypted. It was not sent, in clear or otherwise.
BotandSyncBotraise it when they have to seal a message for an encrypted chat and cannot: the bot has no private key loaded, nobody in the chat has a key to seal to, the key of the person in a private chat cannot be used (revoked, expired, v6, or with no encryption subkey), or the sealing failed.BotKey.sealraises it for a recipient key that cannot be used, andBotKey.export_secretandBotKey.savewhen they cannot seal the key with a passphrase. Retrying fails the same way until the cause is fixed.
- exception EncryptionRequiredError¶
Bases:
EncryptionErrorThe chat is readable, and this message had to be encrypted. It was not sent.
Raised by the sends of
BotandSyncBotwhen the chat is not end-to-end encrypted, and either the bot was made withrequire_encryption=Trueor the call passedencrypt=True. It is the bot keeping its promise, not a fault: retrying sends nothing until the chat becomes encrypted.
- exception DecryptionError¶
Bases:
CryptoErrorAn encrypted message or attachment could not be opened.
For example: the bot has no private key loaded, the message was altered on the way (the integrity check failed), it is not a well-formed OpenPGP message, it opens to more than the size allowed (1 MiB for a message,
max_bytesfor an attachment), or it is a compressed attachment, which phones never send. Loading a key file sealed with a passphrase raises it too when the file cannot be opened; a wrong passphrase is aPassphraseError.In an encrypted chat, an incoming message that fails this way does not reach your handlers: the error goes to the error handlers, and the update to the failed list if none of them takes it. Retrying opens the same bytes the same way.
- exception NotARecipientError¶
Bases:
DecryptionErrorThe message was not sealed to any key the bot has loaded.
Usually it was written to an old key after a rotation (keep the old key in
pgp_previous_keysfor 72 hours), the bot runs with a key that is not the published one (pgpcbot whoamishows the published fingerprint), or it is an attachment in an encrypted group: phones seal those only to the group’s key list, which leaves out bots in privacy mode. Retrying does not help: load the right key.
- exception SignatureError¶
Bases:
CryptoErrorA message without a valid signature, refused because of
verify_signatures="require".The signature is missing, wrong, or made by a key that is not the sender’s. The message does not reach your handlers: the error goes to the error handlers, and the update to the failed list if none of them takes it. With the default,
"auto", this is never raised: the result is inmessage.verificationinstead. Retrying gives the same result.
- exception KeyChangedError(message, *, user_id, pinned, current)¶
Bases:
CryptoErrorA person’s key changed while key pinning is on (
pin_keys=True).With pinning on, the bot remembers the first key fingerprint it sees for each person. When the server later gives a different key for them, the bot refuses it both ways: sealing a message for a chat they are in raises this error, and their incoming messages go to the error handlers with it instead of reaching your handlers (they wait in the failed list).
Encryption alone cannot tell a key the person changed from a key someone swapped on the server. Check the new fingerprint with the person over another channel, then call
Bot.trust_key, andBot.retry_failedfor the messages that waited. Until then, retrying fails the same way.Example
Log it and keep the update in the failed list until the key is checked:
@bot.on_error(KeyChangedError) async def key_changed(error, update): log.warning( "user %s has a new key %s, was %s", error.user_id, error.current, error.pinned ) raise error
Creates the error.
Webhooks, polling and storage¶
- exception WebhookError(message, *, status=400)¶
Bases:
PGPCbotErrorA delivery that reached the webhook could not be read as an update.
Raised while the body of a delivery is read: it is not JSON, not a JSON object, or has no
updateIdthat is a non-negative integer.WebhookAppcatches it itself: a delivery with the right secret comes from the server, so it is logged and answered200, because any other answer would make the server send it again for days and hold back every later update. It does not reach your code. Retrying the same body fails the same way.Creates the error.
- exception PollingError¶
Bases:
PGPCbotErrorPolling cannot start or go on, because a webhook is set.
The server refuses to be polled while the bot has a webhook.
Bot.run,Bot.start_pollingandSyncBot.runcheck it at start, and raise this error too when a webhook is set while they poll (the server then answers409 webhook_active). Retrying does not help: start withrun(drop_webhook=True), delete the webhook (pgpcbot webhook delete), or take the updates on the webhook instead.
- exception StorageError¶
Bases:
PGPCbotErrorThe bot’s storage failed: queued updates, failed updates or pinned keys.
SQLiteStorageraises it when its file cannot be opened as a database, when SQLite reports an error, or when it is used before the bot opened it; both storages for an update without a validupdateId. A polling bot does not confirm a batch it could not store: it waits, and the server sends the batch again.
- exception PollingConflictError¶
Bases:
StorageErrorAnother bot is already polling with the same storage.
Raised when polling starts. With
SQLiteStoragethe bot holds an operating-system lock on the file, so a second process with the same file fails here, and the message gives the process ID of the first one when it can. WithMemoryStorageonly a second bot in the same process, with the same storage object, is caught. Two pollers would take each other’s updates: stop one. To handle more work, raiseconcurrency, or use a webhook.