refactored error codes generate script and error hierarchy

This commit is contained in:
Lex Berezhny 2019-12-06 10:22:21 -05:00
parent cee7e06832
commit 1bbfccd082
3 changed files with 297 additions and 512 deletions

View file

@ -1,99 +1,76 @@
# LBRY Exceptions # Exceptions
Exceptions in LBRY are defined and generated from the Markdown table below. Exceptions in LBRY are defined and generated from the Markdown table at the end of this README.
## Guidelines
When possible, use [built-in Python exceptions](https://docs.python.org/3/library/exceptions.html) or `aiohttp` [general client](https://docs.aiohttp.org/en/latest/client_reference.html#client-exceptions) / [HTTP](https://docs.aiohttp.org/en/latest/web_exceptions.html) exceptions, unless:
1. You want to provide a better error message (extend the closest built-in/`aiohttp` exception in this case).
2. You need to represent a new situation.
When defining your own exceptions, consider:
1. Extending a built-in Python or `aiohttp` exception.
2. Using contextual variables in the error message.
## Table Column Definitions
Column | Meaning Column | Meaning
---|--- ---|---
Code | Codes are used only to define the hierarchy of exceptions and do not end up in the generated output, it is okay to re-number things as necessary to achieve the desired hierarchy. Code | Codes are used only to define the hierarchy of exceptions and do not end up in the generated output, it is okay to re-number things as necessary at anytime to achieve the desired hierarchy.
Log | Log numbers map to the Python Logging Levels and specify at what log level a particular exception should be logged. For example, a Log of "4" would be ERROR log level. Name | Becomes the class name of the exception with "Error" appended to the end. Changing names of existing exceptions makes the API backwards incompatible. When extending other exceptions you must specify the full class name, manually adding "Error" as necessary (if extending another SDK exception).
Name | Generated class name for the error with "Error" appended to the end. Message | User friendly error message explaining the exceptional event. Supports Python formatted strings: any variables used in the string will be generated as arguments in the `__init__` method. Use `--` to provide a doc string after the error message to be added to the class definition.
Message | User friendly error message explaining the error and possible solutions.
## Error Table ## Exceptions Table
Code | Log | Name | Message Code | Name | Message
---:|---:|---|--- ---:|---|---
**1xx** |5| Initialization | **Daemon `start` and other CLI command failures (non-recoverable)** **1xx** | UserInput | User input errors.
**10x** |5| Client | Error codes reported by clients connecting to `lbrynet` daemon. **10x** | Command | Errors preparing to execute commands.
101 |5| RPCConnection | Failed to establish HTTP connection to `lbrynet`. (Is it running?) 101 | CommandDoesNotExist | Command '{command}' does not exist.
102 |5| RPCUnresponsive | HTTP connection established but daemon is not responding to commands. 102 | CommandDeprecated | Command '{command}' is deprecated.
103 |5| WebSocketConnection | WebSocket connection established but daemon is not responding to commands. 103 | CommandInvalidArgument | Invalid argument '{argument}' to command '{command}'.
104 |5| WebSocketUnresponsive | Failed to establish WebSocket connection to `lbrynet`. (Is it running?) 104 | CommandTemporarilyUnavailable | Command '{command}' is temporarily unavailable. -- Such as waiting for required components to start.
**11x** |5| Hardware | Enough of `lbrynet` was able to start to determine external factors causing eventual failure. 105 | CommandPermanentlyUnavailable | Command '{command}' is permanently unavailable. -- such as when required component was intentionally configured not to start.
110 |5| OutOfSpace | Out of disk space. **11x** | InputValue(ValueError) | Invalid argument value provided to command.
111 |5| OutOfRAM | Out of RAM. 111 | GenericInputValue | The value '{value}' for argument '{argument}' is not valid.
**12x** |5| Environment | Internal factors preventing `lbrynet` from bootstrapping itself. **2xx** | Configuration | Configuration errors.
120 |5| IncompatiblePython | Incompatible version of Python. 201 | ConfigWrite | Cannot write configuration file '{path}'. -- When writing the default config fails on startup, such as due to permission issues.
121 |5| IncompatibleDependency | Incompatible version of some library. 202 | ConfigRead | Cannot find provided configuration file '{path}'. -- Can't open the config file user provided via command line args.
**13x** |5| Configuration | Configuration errors. 203 | ConfigParse | Failed to parse the configuration file '{path}'. -- Includes the syntax error / line number to help user fix it.
130 |5| CannotWriteConfiguration | Cannot write configuration file '{path}'. -- When writing the default config fails on startup, such as due to permission issues. 204 | ConfigMissing | Configuration file '{path}' is missing setting that has no default / fallback.
131 |5| CannotOpenConfiguration | Cannot find provided configuration file '{path}'. -- Can't open the config file user provided via command line args. 205 | ConfigInvalid | Configuration file '{path}' has setting with invalid value.
132 |5| CannotParseConfiguration | Failed to parse the configuration file '{path}'. -- Includes the syntax error / line number to help user fix it. **3xx** | Network | **Networking**
133 |5| ConfigurationMissing | Configuration file '{path}' is missing setting that has no default / fallback. 301 | NoInternet | No internet connection.
134 |5| ConfigurationInvalid | Configuration file '{path}' has setting with invalid value. 302 | NoUPnPSupport | Router does not support UPnP.
**14x** |5| Command | Errors preparing to execute commands. **4xx** | Wallet | **Wallet Errors**
140 |5| CommandDoesNotExist | Command '{command}' does not exist. 401 | TransactionRejected | Transaction rejected, unknown reason.
141 |5| CommandDeprecated | Command '{command}' is deprecated. 402 | TransactionFeeTooLow | Fee too low.
142 |5| CommandInvalidArgument | Invalid arguments for command '{command}'. 403 | TransactionInvalidSignature | Invalid signature.
143 |5| CommandTemporarilyUnavailable | Command '{command}' is temporarily unavailable. -- Such as waiting for required components to start. 404 | InsufficientFunds | Insufficient funds. -- determined by wallet prior to attempting to broadcast a tx; this is different for example from a TX being created and sent but then rejected by lbrycrd for unspendable utxos.
144 |5| CommandPermanentlyUnavailable | Command '{command}' is permanently unavailable. -- such as when required component was intentionally configured not to start. 405 | ChannelKeyNotFound | Channel signing key not found.
**2xx** |5| Networking | **Networking** 406 | ChannelKeyInvalid | Channel signing key is out of date. -- For example, channel was updated but you don't have the updated key.
**20x** |5| Connectivity | General connectivity. 407 | DataDownload | Failed to download blob. *generic*
201 |5| NoInternet | No internet connection. 408 | Resolve | Failed to resolve '{url}'.
202 |5| NoUPnPSupport | Router does not support UPnP. 409 | ResolveTimeout | Failed to resolve '{url}' within the timeout.
**21x** |5| WalletConnectivity | Wallet server connectivity. 410 | KeyFeeAboveMaxAllowed | {message}
210 |5| WalletConnection | Failed connecting to a lbryumx server. -- Should normally not need to be handled higher up as `lbrynet` will retry other servers. **5xx** | Blob | **Blobs**
211 |5| WalletConnections | Failed connecting to all known lbryumx servers. -- Will need to bubble up and require user to do something. 500 | BlobNotFound | Blob not found.
212 |5| WalletConnectionDropped | lbryumx droppped our connection. -- Maybe we were being bad? 501 | BlobPermissionDenied | Permission denied to read blob.
**22x** |5| WalletDisconnected | Wallet connection dropped. 502 | BlobTooBig | Blob is too big.
220 |5| WalletServerSuspicious | Disconnected from lbryumx server due to suspicious responses. *generic* 503 | BlobEmpty | Blob is empty.
221 |5| WalletServerValidation | Disconnected from lbryumx server due to SPV validation failure. 510 | BlobFailedDecryption | Failed to decrypt blob.
222 |5| WalletServerHeader | Disconnected from lbryumx server due to incorrect header received. 511 | CorruptBlob | Blobs is corrupted.
228 |5| WalletServerVersion | Disconnected from lbryumx server due to incompatible protocol version. 520 | BlobFailedEncryption | Failed to encrypt blob.
229 |5| WalletServerUnresponsive | Disconnected from lbryumx server due to unresponsiveness. 531 | DownloadCancelled | Download was canceled.
**23x** |5| DataConnectivity | P2P connection errors. 532 | DownloadSDTimeout | Failed to download sd blob {download} within timeout.
**24x** |5| DataNetwork | P2P download errors. 533 | DownloadDataTimeout | Failed to download data blobs for sd hash {download} within timeout.
240 |5| DataDownload | Failed to download blob. *generic* 534 | InvalidStreamDescriptor | {message}
**25x** |5| DataUpload | P2P upload errors. 535 | InvalidData | {message}
**26x** |5| DHTConnectivity | DHT connectivity issues. 536 | InvalidBlobHash | {message}
**27x** |5| DHTProtocol | DHT protocol issues. **6xx** | Component | **Components**
**3xx** |5| Blockchain | **Blockchain** 601 | ComponentStartConditionNotMet | Unresolved dependencies for: {components}
**30x** |5| TransactionRejection | Transaction rejected. 602 | ComponentsNotStarted | {message}
300 |5| TransactionRejected | Transaction rejected, unknown reason. **7xx** | CurrencyExchange | **Currency Exchange**
301 |5| TransactionFeeTooLow | Fee too low. 701 | InvalidExchangeRateResponse | Failed to get exchange rate from {source}: {reason}
302 |5| TransactionInvalidSignature | Invalid signature. 702 | CurrencyConversion | {message}
**31x** |5| Balance | Errors related to your available balance. 703 | InvalidCurrency | Invalid currency: {currency} is not a supported currency.
311 |5| InsufficientFunds | Insufficient funds. -- determined by wallet prior to attempting to broadcast a tx; this is different for example from a TX being created and sent but then rejected by lbrycrd for unspendable utxos.
**32x** |5| ChannelSigning | Channel signing.
320 |5| ChannelKeyNotFound | Channel signing key not found.
321 |5| ChannelKeyInvalid | Channel signing key is out of date. -- For example, channel was updated but you don't have the updated key.
**33x** |5| GeneralResolve | Errors while resolving urls.
331 |5| Resolve | Failed to resolve '{url}'.
332 |5| ResolveTimeout | Failed to resolve '{url}' within the timeout.
**4xx** |5| Blob | **Blobs**
**40x** |5| BlobAvailability | Blob availability.
400 |5| BlobNotFound | Blob not found.
401 |5| BlobPermissionDenied | Permission denied to read blob.
402 |5| BlobTooBig | Blob is too big.
403 |5| BlobEmpty | Blob is empty.
**41x** |5| BlobDecryption | Decryption / Assembly
410 |5| BlobFailedDecryption | Failed to decrypt blob.
411 |5| CorruptBlob | Blobs is corrupted.
**42x** |5| BlobEncryption | Encrypting / Creating
420 |5| BlobFailedEncryption | Failed to encrypt blob.
**43x** |5| BlobRelated | Exceptions carried over from old error system.
431 |5| DownloadCancelled | Download was canceled.
432 |5| DownloadSDTimeout | Failed to download sd blob {download} within timeout.
433 |5| DownloadDataTimeout | Failed to download data blobs for sd hash {download} within timeout.
434 |5| InvalidStreamDescriptor | {message}
435 |5| InvalidData | {message}
436 |5| InvalidBlobHash | {message}
**5xx** |5| Component | **Components**
501 |5| ComponentStartConditionNotMet | Unresolved dependencies for: {components}
502 |5| ComponentsNotStarted | {message}
**6xx** |5| CurrencyExchange | **Currency Exchange**
601 |5| InvalidExchangeRateResponse | Failed to get exchange rate from {source}: {reason}
602 |5| CurrencyConversion | {message}
603 |5| InvalidCurrency | Invalid currency: {currency} is not a supported currency.
**7xx** |5| Purchase | Purchase process errors.
701 |5| KeyFeeAboveMaxAllowed | {message}

View file

@ -1,158 +1,41 @@
from .base import BaseError from .base import BaseError
class InitializationError(BaseError): class UserInputError(BaseError):
""" """
**Daemon `start` and other CLI command failures (non-recoverable)** User input errors.
""" """
log_level = 50
class ClientError(InitializationError): class CommandError(UserInputError):
"""
Error codes reported by clients connecting to `lbrynet` daemon.
"""
log_level = 50
class RPCConnectionError(ClientError):
log_level = 50
def __init__(self):
super().__init__("Failed to establish HTTP connection to `lbrynet`. (Is it running?)")
class RPCUnresponsiveError(ClientError):
log_level = 50
def __init__(self):
super().__init__("HTTP connection established but daemon is not responding to commands.")
class WebSocketConnectionError(ClientError):
log_level = 50
def __init__(self):
super().__init__("WebSocket connection established but daemon is not responding to commands.")
class WebSocketUnresponsiveError(ClientError):
log_level = 50
def __init__(self):
super().__init__("Failed to establish WebSocket connection to `lbrynet`. (Is it running?)")
class HardwareError(InitializationError):
"""
Enough of `lbrynet` was able to start to determine external factors causing eventual failure.
"""
log_level = 50
class OutOfSpaceError(HardwareError):
log_level = 50
def __init__(self):
super().__init__("Out of disk space.")
class OutOfRAMError(HardwareError):
log_level = 50
def __init__(self):
super().__init__("Out of RAM.")
class EnvironmentError(InitializationError):
"""
Internal factors preventing `lbrynet` from bootstrapping itself.
"""
log_level = 50
class IncompatiblePythonError(EnvironmentError):
log_level = 50
def __init__(self):
super().__init__("Incompatible version of Python.")
class IncompatibleDependencyError(EnvironmentError):
log_level = 50
def __init__(self):
super().__init__("Incompatible version of some library.")
class ConfigurationError(InitializationError):
"""
Configuration errors.
"""
log_level = 50
class CannotWriteConfigurationError(ConfigurationError):
"""
When writing the default config fails on startup, such as due to permission issues.
"""
log_level = 50
def __init__(self, path):
super().__init__(f"Cannot write configuration file '{path}'.")
class CannotOpenConfigurationError(ConfigurationError):
"""
Can't open the config file user provided via command line args.
"""
log_level = 50
def __init__(self, path):
super().__init__(f"Cannot find provided configuration file '{path}'.")
class CannotParseConfigurationError(ConfigurationError):
"""
Includes the syntax error / line number to help user fix it.
"""
log_level = 50
def __init__(self, path):
super().__init__(f"Failed to parse the configuration file '{path}'.")
class ConfigurationMissingError(ConfigurationError):
log_level = 50
def __init__(self, path):
super().__init__(f"Configuration file '{path}' is missing setting that has no default / fallback.")
class ConfigurationInvalidError(ConfigurationError):
log_level = 50
def __init__(self, path):
super().__init__(f"Configuration file '{path}' has setting with invalid value.")
class CommandError(InitializationError):
""" """
Errors preparing to execute commands. Errors preparing to execute commands.
""" """
log_level = 50
class CommandDoesNotExistError(CommandError): class CommandDoesNotExistError(CommandError):
log_level = 50
def __init__(self, command): def __init__(self, command):
super().__init__(f"Command '{command}' does not exist.") super().__init__(f"Command '{command}' does not exist.")
class CommandDeprecatedError(CommandError): class CommandDeprecatedError(CommandError):
log_level = 50
def __init__(self, command): def __init__(self, command):
super().__init__(f"Command '{command}' is deprecated.") super().__init__(f"Command '{command}' is deprecated.")
class CommandInvalidArgumentError(CommandError): class CommandInvalidArgumentError(CommandError):
log_level = 50
def __init__(self, command): def __init__(self, argument, command):
super().__init__(f"Invalid arguments for command '{command}'.") super().__init__(f"Invalid argument '{argument}' to command '{command}'.")
class CommandTemporarilyUnavailableError(CommandError): class CommandTemporarilyUnavailableError(CommandError):
""" """
Such as waiting for required components to start. Such as waiting for required components to start.
""" """
log_level = 50
def __init__(self, command): def __init__(self, command):
super().__init__(f"Command '{command}' is temporarily unavailable.") super().__init__(f"Command '{command}' is temporarily unavailable.")
@ -161,348 +44,239 @@ class CommandPermanentlyUnavailableError(CommandError):
""" """
such as when required component was intentionally configured not to start. such as when required component was intentionally configured not to start.
""" """
log_level = 50
def __init__(self, command): def __init__(self, command):
super().__init__(f"Command '{command}' is permanently unavailable.") super().__init__(f"Command '{command}' is permanently unavailable.")
class NetworkingError(BaseError): class InputValueError(UserInputError, ValueError):
"""
Invalid argument value provided to command.
"""
class GenericInputValueError(InputValueError):
def __init__(self, value, argument):
super().__init__(f"The value '{value}' for argument '{argument}' is not valid.")
class ConfigurationError(BaseError):
"""
Configuration errors.
"""
class ConfigWriteError(ConfigurationError):
"""
When writing the default config fails on startup, such as due to permission issues.
"""
def __init__(self, path):
super().__init__(f"Cannot write configuration file '{path}'.")
class ConfigReadError(ConfigurationError):
"""
Can't open the config file user provided via command line args.
"""
def __init__(self, path):
super().__init__(f"Cannot find provided configuration file '{path}'.")
class ConfigParseError(ConfigurationError):
"""
Includes the syntax error / line number to help user fix it.
"""
def __init__(self, path):
super().__init__(f"Failed to parse the configuration file '{path}'.")
class ConfigMissingError(ConfigurationError):
def __init__(self, path):
super().__init__(f"Configuration file '{path}' is missing setting that has no default / fallback.")
class ConfigInvalidError(ConfigurationError):
def __init__(self, path):
super().__init__(f"Configuration file '{path}' has setting with invalid value.")
class NetworkError(BaseError):
""" """
**Networking** **Networking**
""" """
log_level = 50
class ConnectivityError(NetworkingError): class NoInternetError(NetworkError):
"""
General connectivity.
"""
log_level = 50
class NoInternetError(ConnectivityError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("No internet connection.") super().__init__("No internet connection.")
class NoUPnPSupportError(ConnectivityError): class NoUPnPSupportError(NetworkError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Router does not support UPnP.") super().__init__("Router does not support UPnP.")
class WalletConnectivityError(NetworkingError): class WalletError(BaseError):
""" """
Wallet server connectivity. **Wallet Errors**
""" """
log_level = 50
class WalletConnectionError(WalletConnectivityError): class TransactionRejectedError(WalletError):
"""
Should normally not need to be handled higher up as `lbrynet` will retry other servers.
"""
log_level = 50
def __init__(self):
super().__init__("Failed connecting to a lbryumx server.")
class WalletConnectionsError(WalletConnectivityError):
"""
Will need to bubble up and require user to do something.
"""
log_level = 50
def __init__(self):
super().__init__("Failed connecting to all known lbryumx servers.")
class WalletConnectionDroppedError(WalletConnectivityError):
"""
Maybe we were being bad?
"""
log_level = 50
def __init__(self):
super().__init__("lbryumx droppped our connection.")
class WalletDisconnectedError(NetworkingError):
"""
Wallet connection dropped.
"""
log_level = 50
class WalletServerSuspiciousError(WalletDisconnectedError):
log_level = 50
def __init__(self):
super().__init__("Disconnected from lbryumx server due to suspicious responses. *generic*")
class WalletServerValidationError(WalletDisconnectedError):
log_level = 50
def __init__(self):
super().__init__("Disconnected from lbryumx server due to SPV validation failure.")
class WalletServerHeaderError(WalletDisconnectedError):
log_level = 50
def __init__(self):
super().__init__("Disconnected from lbryumx server due to incorrect header received.")
class WalletServerVersionError(WalletDisconnectedError):
log_level = 50
def __init__(self):
super().__init__("Disconnected from lbryumx server due to incompatible protocol version.")
class WalletServerUnresponsiveError(WalletDisconnectedError):
log_level = 50
def __init__(self):
super().__init__("Disconnected from lbryumx server due to unresponsiveness.")
class DataConnectivityError(NetworkingError):
"""
P2P connection errors.
"""
log_level = 50
class DataNetworkError(NetworkingError):
"""
P2P download errors.
"""
log_level = 50
class DataDownloadError(DataNetworkError):
log_level = 50
def __init__(self):
super().__init__("Failed to download blob. *generic*")
class DataUploadError(NetworkingError):
"""
P2P upload errors.
"""
log_level = 50
class DHTConnectivityError(NetworkingError):
"""
DHT connectivity issues.
"""
log_level = 50
class DHTProtocolError(NetworkingError):
"""
DHT protocol issues.
"""
log_level = 50
class BlockchainError(BaseError):
"""
**Blockchain**
"""
log_level = 50
class TransactionRejectionError(BlockchainError):
"""
Transaction rejected.
"""
log_level = 50
class TransactionRejectedError(TransactionRejectionError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Transaction rejected, unknown reason.") super().__init__("Transaction rejected, unknown reason.")
class TransactionFeeTooLowError(TransactionRejectionError): class TransactionFeeTooLowError(WalletError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Fee too low.") super().__init__("Fee too low.")
class TransactionInvalidSignatureError(TransactionRejectionError): class TransactionInvalidSignatureError(WalletError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Invalid signature.") super().__init__("Invalid signature.")
class BalanceError(BlockchainError): class InsufficientFundsError(WalletError):
"""
Errors related to your available balance.
"""
log_level = 50
class InsufficientFundsError(BalanceError):
""" """
determined by wallet prior to attempting to broadcast a tx; this is different for example from a TX determined by wallet prior to attempting to broadcast a tx; this is different for example from a TX
being created and sent but then rejected by lbrycrd for unspendable utxos. being created and sent but then rejected by lbrycrd for unspendable utxos.
""" """
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Insufficient funds.") super().__init__("Insufficient funds.")
class ChannelSigningError(BlockchainError): class ChannelKeyNotFoundError(WalletError):
"""
Channel signing.
"""
log_level = 50
class ChannelKeyNotFoundError(ChannelSigningError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Channel signing key not found.") super().__init__("Channel signing key not found.")
class ChannelKeyInvalidError(ChannelSigningError): class ChannelKeyInvalidError(WalletError):
""" """
For example, channel was updated but you don't have the updated key. For example, channel was updated but you don't have the updated key.
""" """
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Channel signing key is out of date.") super().__init__("Channel signing key is out of date.")
class GeneralResolveError(BlockchainError): class DataDownloadError(WalletError):
"""
Errors while resolving urls. def __init__(self):
""" super().__init__("Failed to download blob. *generic*")
log_level = 50
class ResolveError(GeneralResolveError): class ResolveError(WalletError):
log_level = 50
def __init__(self, url): def __init__(self, url):
super().__init__(f"Failed to resolve '{url}'.") super().__init__(f"Failed to resolve '{url}'.")
class ResolveTimeoutError(GeneralResolveError): class ResolveTimeoutError(WalletError):
log_level = 50
def __init__(self, url): def __init__(self, url):
super().__init__(f"Failed to resolve '{url}' within the timeout.") super().__init__(f"Failed to resolve '{url}' within the timeout.")
class KeyFeeAboveMaxAllowedError(WalletError):
def __init__(self, message):
super().__init__(f"{message}")
class BlobError(BaseError): class BlobError(BaseError):
""" """
**Blobs** **Blobs**
""" """
log_level = 50
class BlobAvailabilityError(BlobError): class BlobNotFoundError(BlobError):
"""
Blob availability.
"""
log_level = 50
class BlobNotFoundError(BlobAvailabilityError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Blob not found.") super().__init__("Blob not found.")
class BlobPermissionDeniedError(BlobAvailabilityError): class BlobPermissionDeniedError(BlobError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Permission denied to read blob.") super().__init__("Permission denied to read blob.")
class BlobTooBigError(BlobAvailabilityError): class BlobTooBigError(BlobError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Blob is too big.") super().__init__("Blob is too big.")
class BlobEmptyError(BlobAvailabilityError): class BlobEmptyError(BlobError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Blob is empty.") super().__init__("Blob is empty.")
class BlobDecryptionError(BlobError): class BlobFailedDecryptionError(BlobError):
"""
Decryption / Assembly
"""
log_level = 50
class BlobFailedDecryptionError(BlobDecryptionError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Failed to decrypt blob.") super().__init__("Failed to decrypt blob.")
class CorruptBlobError(BlobDecryptionError): class CorruptBlobError(BlobError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Blobs is corrupted.") super().__init__("Blobs is corrupted.")
class BlobEncryptionError(BlobError): class BlobFailedEncryptionError(BlobError):
"""
Encrypting / Creating
"""
log_level = 50
class BlobFailedEncryptionError(BlobEncryptionError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Failed to encrypt blob.") super().__init__("Failed to encrypt blob.")
class BlobRelatedError(BlobError): class DownloadCancelledError(BlobError):
"""
Exceptions carried over from old error system.
"""
log_level = 50
class DownloadCancelledError(BlobRelatedError):
log_level = 50
def __init__(self): def __init__(self):
super().__init__("Download was canceled.") super().__init__("Download was canceled.")
class DownloadSDTimeoutError(BlobRelatedError): class DownloadSDTimeoutError(BlobError):
log_level = 50
def __init__(self, download): def __init__(self, download):
super().__init__(f"Failed to download sd blob {download} within timeout.") super().__init__(f"Failed to download sd blob {download} within timeout.")
class DownloadDataTimeoutError(BlobRelatedError): class DownloadDataTimeoutError(BlobError):
log_level = 50
def __init__(self, download): def __init__(self, download):
super().__init__(f"Failed to download data blobs for sd hash {download} within timeout.") super().__init__(f"Failed to download data blobs for sd hash {download} within timeout.")
class InvalidStreamDescriptorError(BlobRelatedError): class InvalidStreamDescriptorError(BlobError):
log_level = 50
def __init__(self, message): def __init__(self, message):
super().__init__(f"{message}") super().__init__(f"{message}")
class InvalidDataError(BlobRelatedError): class InvalidDataError(BlobError):
log_level = 50
def __init__(self, message): def __init__(self, message):
super().__init__(f"{message}") super().__init__(f"{message}")
class InvalidBlobHashError(BlobRelatedError): class InvalidBlobHashError(BlobError):
log_level = 50
def __init__(self, message): def __init__(self, message):
super().__init__(f"{message}") super().__init__(f"{message}")
@ -511,17 +285,16 @@ class ComponentError(BaseError):
""" """
**Components** **Components**
""" """
log_level = 50
class ComponentStartConditionNotMetError(ComponentError): class ComponentStartConditionNotMetError(ComponentError):
log_level = 50
def __init__(self, components): def __init__(self, components):
super().__init__(f"Unresolved dependencies for: {components}") super().__init__(f"Unresolved dependencies for: {components}")
class ComponentsNotStartedError(ComponentError): class ComponentsNotStartedError(ComponentError):
log_level = 50
def __init__(self, message): def __init__(self, message):
super().__init__(f"{message}") super().__init__(f"{message}")
@ -530,36 +303,21 @@ class CurrencyExchangeError(BaseError):
""" """
**Currency Exchange** **Currency Exchange**
""" """
log_level = 50
class InvalidExchangeRateResponseError(CurrencyExchangeError): class InvalidExchangeRateResponseError(CurrencyExchangeError):
log_level = 50
def __init__(self, source, reason): def __init__(self, source, reason):
super().__init__(f"Failed to get exchange rate from {source}: {reason}") super().__init__(f"Failed to get exchange rate from {source}: {reason}")
class CurrencyConversionError(CurrencyExchangeError): class CurrencyConversionError(CurrencyExchangeError):
log_level = 50
def __init__(self, message): def __init__(self, message):
super().__init__(f"{message}") super().__init__(f"{message}")
class InvalidCurrencyError(CurrencyExchangeError): class InvalidCurrencyError(CurrencyExchangeError):
log_level = 50
def __init__(self, currency): def __init__(self, currency):
super().__init__(f"Invalid currency: {currency} is not a supported currency.") super().__init__(f"Invalid currency: {currency} is not a supported currency.")
class PurchaseError(BaseError):
"""
Purchase process errors.
"""
log_level = 50
class KeyFeeAboveMaxAllowedError(PurchaseError):
log_level = 50
def __init__(self, message):
super().__init__(f"{message}")

View file

@ -2,63 +2,113 @@ import re
from textwrap import fill, indent from textwrap import fill, indent
CLASS = """
class {name}Error({parent}Error):{doc}
log_level = {log_level}
"""
INIT = """\
def __init__({args}):
super().__init__({format}"{desc}")
"""
INDENT = ' ' * 4 INDENT = ' ' * 4
CLASS = """
def main(): class {name}({parents}):{doc}
with open('README.md', 'r') as readme: """
lines = readme.readlines()
for line in lines: INIT = """
if line.startswith('## Error Table'): def __init__({args}):
break super().__init__({format}"{message}")
print('from .base import BaseError\n') """
stack = {}
started = False
for line in lines: class ErrorClass:
if not started:
started = line.startswith('---:|') def __init__(self, hierarchy, name, message):
continue self.hierarchy = hierarchy.replace('**', '')
if not line: self.other_parents = []
break if '(' in name:
parent = 'Base' assert ')' in name, f"Missing closing parenthesis in '{name}'."
h, log_level, code, desc = [c.strip() for c in line.split('|')] self.other_parents = name[name.find('(')+1:name.find(')')].split(',')
comment = "" name = name[:name.find('(')]
if '--' in desc: self.name = name
desc, comment = [s.strip() for s in desc.split('--')] self.class_name = name+'Error'
log_level += "0" self.message = message
if h.startswith('**'): self.comment = ""
if h.count('x') == 1: if '--' in message:
parent = stack[h[2:3]][0] self.message, self.comment = message.split('--')
stack[h.replace('**', '').replace('x', '')] = (code, desc) self.message = self.message.strip()
if h.count('x') == 2: self.comment = self.comment.strip()
stack[h.replace('**', '').replace('x', '')+'0'] = (code, desc)
comment = f'\n{INDENT}"""\n{indent(fill(comment or desc, 100), INDENT)}\n{INDENT}"""' @property
print(CLASS.format(name=code, parent=parent, doc=comment, log_level=log_level)) def is_leaf(self):
continue return 'x' not in self.hierarchy
parent = stack[h[:2]][0]
args = ['self'] @property
for arg in re.findall('{([a-z0-1]+)}', desc): def code(self):
args.append(arg) return self.hierarchy.replace('x', '')
fmt = ""
if len(args) > 1: @property
fmt = "f" def parent_codes(self):
if comment: return self.hierarchy[0:2], self.hierarchy[0]
comment = f'\n{INDENT}"""\n{indent(fill(comment, 100), INDENT)}\n{INDENT}"""'
print((CLASS+INIT).format( def get_arguments(self):
name=code, parent=parent, args=', '.join(args), log_level=log_level, args = ['self']
desc=desc, doc=comment, format=fmt for arg in re.findall('{([a-z0-1]+)}', self.message):
args.append(arg)
return args
def get_doc_string(self, doc):
if doc:
return f'\n{INDENT}"""\n{indent(fill(doc, 100), INDENT)}\n{INDENT}"""'
return ""
def render(self, out, parent):
if not parent:
parents = ['BaseError']
else:
parents = [parent.class_name]
parents += self.other_parents
args = self.get_arguments()
if self.is_leaf:
out.write((CLASS + INIT).format(
name=self.class_name, parents=', '.join(parents), args=', '.join(args),
message=self.message, doc=self.get_doc_string(self.comment), format='f' if len(args) > 1 else ''
))
else:
out.write(CLASS.format(
name=self.class_name, parents=', '.join(parents),
doc=self.get_doc_string(self.comment or self.message)
)) ))
def error_rows(lines):
lines = iter(lines)
for line in lines:
if line.startswith('## Exceptions Table'):
break
for line in lines:
if line.startswith('---:|'):
break
for line in lines:
if not line:
break
yield line
def find_parent(stack, child):
for parent_code in child.parent_codes:
parent = stack.get(parent_code)
if parent:
return parent
def main(out):
with open('README.md', 'r') as readme:
lines = readme.readlines()
out.write('from .base import BaseError\n')
stack = {}
for row in error_rows(lines):
error = ErrorClass(*[c.strip() for c in row.split('|')])
error.render(out, find_parent(stack, error))
if not error.is_leaf:
assert error.code not in stack, f"Duplicate code: {error.code}"
stack[error.code] = error
if __name__ == "__main__": if __name__ == "__main__":
main() import sys
main(sys.stdout)