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
---|---
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.
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 | Generated class name for the error with "Error" appended to the end.
Message | User friendly error message explaining the error and possible solutions.
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.
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).
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.
## Error Table
## Exceptions Table
Code | Log | Name | Message
---:|---:|---|---
**1xx** |5| Initialization | **Daemon `start` and other CLI command failures (non-recoverable)**
**10x** |5| Client | Error codes reported by clients connecting to `lbrynet` daemon.
101 |5| RPCConnection | Failed to establish HTTP connection to `lbrynet`. (Is it running?)
102 |5| RPCUnresponsive | HTTP connection established but daemon is not responding to commands.
103 |5| WebSocketConnection | WebSocket connection established but daemon is not responding to commands.
104 |5| WebSocketUnresponsive | Failed to establish WebSocket connection to `lbrynet`. (Is it running?)
**11x** |5| Hardware | Enough of `lbrynet` was able to start to determine external factors causing eventual failure.
110 |5| OutOfSpace | Out of disk space.
111 |5| OutOfRAM | Out of RAM.
**12x** |5| Environment | Internal factors preventing `lbrynet` from bootstrapping itself.
120 |5| IncompatiblePython | Incompatible version of Python.
121 |5| IncompatibleDependency | Incompatible version of some library.
**13x** |5| Configuration | Configuration errors.
130 |5| CannotWriteConfiguration | Cannot write configuration file '{path}'. -- When writing the default config fails on startup, such as due to permission issues.
131 |5| CannotOpenConfiguration | Cannot find provided configuration file '{path}'. -- Can't open the config file user provided via command line args.
132 |5| CannotParseConfiguration | Failed to parse the configuration file '{path}'. -- Includes the syntax error / line number to help user fix it.
133 |5| ConfigurationMissing | Configuration file '{path}' is missing setting that has no default / fallback.
134 |5| ConfigurationInvalid | Configuration file '{path}' has setting with invalid value.
**14x** |5| Command | Errors preparing to execute commands.
140 |5| CommandDoesNotExist | Command '{command}' does not exist.
141 |5| CommandDeprecated | Command '{command}' is deprecated.
142 |5| CommandInvalidArgument | Invalid arguments for command '{command}'.
143 |5| CommandTemporarilyUnavailable | Command '{command}' is temporarily unavailable. -- Such as waiting for required components to start.
144 |5| CommandPermanentlyUnavailable | Command '{command}' is permanently unavailable. -- such as when required component was intentionally configured not to start.
**2xx** |5| Networking | **Networking**
**20x** |5| Connectivity | General connectivity.
201 |5| NoInternet | No internet connection.
202 |5| NoUPnPSupport | Router does not support UPnP.
**21x** |5| WalletConnectivity | Wallet server connectivity.
210 |5| WalletConnection | Failed connecting to a lbryumx server. -- Should normally not need to be handled higher up as `lbrynet` will retry other servers.
211 |5| WalletConnections | Failed connecting to all known lbryumx servers. -- Will need to bubble up and require user to do something.
212 |5| WalletConnectionDropped | lbryumx droppped our connection. -- Maybe we were being bad?
**22x** |5| WalletDisconnected | Wallet connection dropped.
220 |5| WalletServerSuspicious | Disconnected from lbryumx server due to suspicious responses. *generic*
221 |5| WalletServerValidation | Disconnected from lbryumx server due to SPV validation failure.
222 |5| WalletServerHeader | Disconnected from lbryumx server due to incorrect header received.
228 |5| WalletServerVersion | Disconnected from lbryumx server due to incompatible protocol version.
229 |5| WalletServerUnresponsive | Disconnected from lbryumx server due to unresponsiveness.
**23x** |5| DataConnectivity | P2P connection errors.
**24x** |5| DataNetwork | P2P download errors.
240 |5| DataDownload | Failed to download blob. *generic*
**25x** |5| DataUpload | P2P upload errors.
**26x** |5| DHTConnectivity | DHT connectivity issues.
**27x** |5| DHTProtocol | DHT protocol issues.
**3xx** |5| Blockchain | **Blockchain**
**30x** |5| TransactionRejection | Transaction rejected.
300 |5| TransactionRejected | Transaction rejected, unknown reason.
301 |5| TransactionFeeTooLow | Fee too low.
302 |5| TransactionInvalidSignature | Invalid signature.
**31x** |5| Balance | Errors related to your available balance.
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}
Code | Name | Message
---:|---|---
**1xx** | UserInput | User input errors.
**10x** | Command | Errors preparing to execute commands.
101 | CommandDoesNotExist | Command '{command}' does not exist.
102 | CommandDeprecated | Command '{command}' is deprecated.
103 | CommandInvalidArgument | Invalid argument '{argument}' to command '{command}'.
104 | CommandTemporarilyUnavailable | Command '{command}' is temporarily unavailable. -- Such as waiting for required components to start.
105 | CommandPermanentlyUnavailable | Command '{command}' is permanently unavailable. -- such as when required component was intentionally configured not to start.
**11x** | InputValue(ValueError) | Invalid argument value provided to command.
111 | GenericInputValue | The value '{value}' for argument '{argument}' is not valid.
**2xx** | Configuration | Configuration errors.
201 | ConfigWrite | Cannot write configuration file '{path}'. -- When writing the default config fails on startup, such as due to permission issues.
202 | ConfigRead | Cannot find provided configuration file '{path}'. -- Can't open the config file user provided via command line args.
203 | ConfigParse | Failed to parse the configuration file '{path}'. -- Includes the syntax error / line number to help user fix it.
204 | ConfigMissing | Configuration file '{path}' is missing setting that has no default / fallback.
205 | ConfigInvalid | Configuration file '{path}' has setting with invalid value.
**3xx** | Network | **Networking**
301 | NoInternet | No internet connection.
302 | NoUPnPSupport | Router does not support UPnP.
**4xx** | Wallet | **Wallet Errors**
401 | TransactionRejected | Transaction rejected, unknown reason.
402 | TransactionFeeTooLow | Fee too low.
403 | TransactionInvalidSignature | Invalid signature.
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.
405 | ChannelKeyNotFound | Channel signing key not found.
406 | ChannelKeyInvalid | Channel signing key is out of date. -- For example, channel was updated but you don't have the updated key.
407 | DataDownload | Failed to download blob. *generic*
408 | Resolve | Failed to resolve '{url}'.
409 | ResolveTimeout | Failed to resolve '{url}' within the timeout.
410 | KeyFeeAboveMaxAllowed | {message}
**5xx** | Blob | **Blobs**
500 | BlobNotFound | Blob not found.
501 | BlobPermissionDenied | Permission denied to read blob.
502 | BlobTooBig | Blob is too big.
503 | BlobEmpty | Blob is empty.
510 | BlobFailedDecryption | Failed to decrypt blob.
511 | CorruptBlob | Blobs is corrupted.
520 | BlobFailedEncryption | Failed to encrypt blob.
531 | DownloadCancelled | Download was canceled.
532 | DownloadSDTimeout | Failed to download sd blob {download} within timeout.
533 | DownloadDataTimeout | Failed to download data blobs for sd hash {download} within timeout.
534 | InvalidStreamDescriptor | {message}
535 | InvalidData | {message}
536 | InvalidBlobHash | {message}
**6xx** | Component | **Components**
601 | ComponentStartConditionNotMet | Unresolved dependencies for: {components}
602 | ComponentsNotStarted | {message}
**7xx** | CurrencyExchange | **Currency Exchange**
701 | InvalidExchangeRateResponse | Failed to get exchange rate from {source}: {reason}
702 | CurrencyConversion | {message}
703 | InvalidCurrency | Invalid currency: {currency} is not a supported currency.

View file

@ -1,158 +1,41 @@
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):
"""
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):
class CommandError(UserInputError):
"""
Errors preparing to execute commands.
"""
log_level = 50
class CommandDoesNotExistError(CommandError):
log_level = 50
def __init__(self, command):
super().__init__(f"Command '{command}' does not exist.")
class CommandDeprecatedError(CommandError):
log_level = 50
def __init__(self, command):
super().__init__(f"Command '{command}' is deprecated.")
class CommandInvalidArgumentError(CommandError):
log_level = 50
def __init__(self, command):
super().__init__(f"Invalid arguments for command '{command}'.")
def __init__(self, argument, command):
super().__init__(f"Invalid argument '{argument}' to command '{command}'.")
class CommandTemporarilyUnavailableError(CommandError):
"""
Such as waiting for required components to start.
"""
log_level = 50
def __init__(self, command):
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.
"""
log_level = 50
def __init__(self, command):
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**
"""
log_level = 50
class ConnectivityError(NetworkingError):
"""
General connectivity.
"""
log_level = 50
class NoInternetError(NetworkError):
class NoInternetError(ConnectivityError):
log_level = 50
def __init__(self):
super().__init__("No internet connection.")
class NoUPnPSupportError(ConnectivityError):
log_level = 50
class NoUPnPSupportError(NetworkError):
def __init__(self):
super().__init__("Router does not support UPnP.")
class WalletConnectivityError(NetworkingError):
class WalletError(BaseError):
"""
Wallet server connectivity.
**Wallet Errors**
"""
log_level = 50
class WalletConnectionError(WalletConnectivityError):
"""
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 TransactionRejectedError(WalletError):
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):
super().__init__("Transaction rejected, unknown reason.")
class TransactionFeeTooLowError(TransactionRejectionError):
log_level = 50
class TransactionFeeTooLowError(WalletError):
def __init__(self):
super().__init__("Fee too low.")
class TransactionInvalidSignatureError(TransactionRejectionError):
log_level = 50
class TransactionInvalidSignatureError(WalletError):
def __init__(self):
super().__init__("Invalid signature.")
class BalanceError(BlockchainError):
"""
Errors related to your available balance.
"""
log_level = 50
class InsufficientFundsError(BalanceError):
class InsufficientFundsError(WalletError):
"""
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.
"""
log_level = 50
def __init__(self):
super().__init__("Insufficient funds.")
class ChannelSigningError(BlockchainError):
"""
Channel signing.
"""
log_level = 50
class ChannelKeyNotFoundError(WalletError):
class ChannelKeyNotFoundError(ChannelSigningError):
log_level = 50
def __init__(self):
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.
"""
log_level = 50
def __init__(self):
super().__init__("Channel signing key is out of date.")
class GeneralResolveError(BlockchainError):
"""
Errors while resolving urls.
"""
log_level = 50
class DataDownloadError(WalletError):
def __init__(self):
super().__init__("Failed to download blob. *generic*")
class ResolveError(GeneralResolveError):
log_level = 50
class ResolveError(WalletError):
def __init__(self, url):
super().__init__(f"Failed to resolve '{url}'.")
class ResolveTimeoutError(GeneralResolveError):
log_level = 50
class ResolveTimeoutError(WalletError):
def __init__(self, url):
super().__init__(f"Failed to resolve '{url}' within the timeout.")
class KeyFeeAboveMaxAllowedError(WalletError):
def __init__(self, message):
super().__init__(f"{message}")
class BlobError(BaseError):
"""
**Blobs**
"""
log_level = 50
class BlobAvailabilityError(BlobError):
"""
Blob availability.
"""
log_level = 50
class BlobNotFoundError(BlobError):
class BlobNotFoundError(BlobAvailabilityError):
log_level = 50
def __init__(self):
super().__init__("Blob not found.")
class BlobPermissionDeniedError(BlobAvailabilityError):
log_level = 50
class BlobPermissionDeniedError(BlobError):
def __init__(self):
super().__init__("Permission denied to read blob.")
class BlobTooBigError(BlobAvailabilityError):
log_level = 50
class BlobTooBigError(BlobError):
def __init__(self):
super().__init__("Blob is too big.")
class BlobEmptyError(BlobAvailabilityError):
log_level = 50
class BlobEmptyError(BlobError):
def __init__(self):
super().__init__("Blob is empty.")
class BlobDecryptionError(BlobError):
"""
Decryption / Assembly
"""
log_level = 50
class BlobFailedDecryptionError(BlobError):
class BlobFailedDecryptionError(BlobDecryptionError):
log_level = 50
def __init__(self):
super().__init__("Failed to decrypt blob.")
class CorruptBlobError(BlobDecryptionError):
log_level = 50
class CorruptBlobError(BlobError):
def __init__(self):
super().__init__("Blobs is corrupted.")
class BlobEncryptionError(BlobError):
"""
Encrypting / Creating
"""
log_level = 50
class BlobFailedEncryptionError(BlobError):
class BlobFailedEncryptionError(BlobEncryptionError):
log_level = 50
def __init__(self):
super().__init__("Failed to encrypt blob.")
class BlobRelatedError(BlobError):
"""
Exceptions carried over from old error system.
"""
log_level = 50
class DownloadCancelledError(BlobError):
class DownloadCancelledError(BlobRelatedError):
log_level = 50
def __init__(self):
super().__init__("Download was canceled.")
class DownloadSDTimeoutError(BlobRelatedError):
log_level = 50
class DownloadSDTimeoutError(BlobError):
def __init__(self, download):
super().__init__(f"Failed to download sd blob {download} within timeout.")
class DownloadDataTimeoutError(BlobRelatedError):
log_level = 50
class DownloadDataTimeoutError(BlobError):
def __init__(self, download):
super().__init__(f"Failed to download data blobs for sd hash {download} within timeout.")
class InvalidStreamDescriptorError(BlobRelatedError):
log_level = 50
class InvalidStreamDescriptorError(BlobError):
def __init__(self, message):
super().__init__(f"{message}")
class InvalidDataError(BlobRelatedError):
log_level = 50
class InvalidDataError(BlobError):
def __init__(self, message):
super().__init__(f"{message}")
class InvalidBlobHashError(BlobRelatedError):
log_level = 50
class InvalidBlobHashError(BlobError):
def __init__(self, message):
super().__init__(f"{message}")
@ -511,17 +285,16 @@ class ComponentError(BaseError):
"""
**Components**
"""
log_level = 50
class ComponentStartConditionNotMetError(ComponentError):
log_level = 50
def __init__(self, components):
super().__init__(f"Unresolved dependencies for: {components}")
class ComponentsNotStartedError(ComponentError):
log_level = 50
def __init__(self, message):
super().__init__(f"{message}")
@ -530,36 +303,21 @@ class CurrencyExchangeError(BaseError):
"""
**Currency Exchange**
"""
log_level = 50
class InvalidExchangeRateResponseError(CurrencyExchangeError):
log_level = 50
def __init__(self, source, reason):
super().__init__(f"Failed to get exchange rate from {source}: {reason}")
class CurrencyConversionError(CurrencyExchangeError):
log_level = 50
def __init__(self, message):
super().__init__(f"{message}")
class InvalidCurrencyError(CurrencyExchangeError):
log_level = 50
def __init__(self, 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
CLASS = """
class {name}Error({parent}Error):{doc}
log_level = {log_level}
"""
INIT = """\
def __init__({args}):
super().__init__({format}"{desc}")
"""
INDENT = ' ' * 4
CLASS = """
def main():
with open('README.md', 'r') as readme:
lines = readme.readlines()
for line in lines:
if line.startswith('## Error Table'):
break
print('from .base import BaseError\n')
stack = {}
started = False
for line in lines:
if not started:
started = line.startswith('---:|')
continue
if not line:
break
parent = 'Base'
h, log_level, code, desc = [c.strip() for c in line.split('|')]
comment = ""
if '--' in desc:
desc, comment = [s.strip() for s in desc.split('--')]
log_level += "0"
if h.startswith('**'):
if h.count('x') == 1:
parent = stack[h[2:3]][0]
stack[h.replace('**', '').replace('x', '')] = (code, desc)
if h.count('x') == 2:
stack[h.replace('**', '').replace('x', '')+'0'] = (code, desc)
comment = f'\n{INDENT}"""\n{indent(fill(comment or desc, 100), INDENT)}\n{INDENT}"""'
print(CLASS.format(name=code, parent=parent, doc=comment, log_level=log_level))
continue
parent = stack[h[:2]][0]
args = ['self']
for arg in re.findall('{([a-z0-1]+)}', desc):
args.append(arg)
fmt = ""
if len(args) > 1:
fmt = "f"
if comment:
comment = f'\n{INDENT}"""\n{indent(fill(comment, 100), INDENT)}\n{INDENT}"""'
print((CLASS+INIT).format(
name=code, parent=parent, args=', '.join(args), log_level=log_level,
desc=desc, doc=comment, format=fmt
class {name}({parents}):{doc}
"""
INIT = """
def __init__({args}):
super().__init__({format}"{message}")
"""
class ErrorClass:
def __init__(self, hierarchy, name, message):
self.hierarchy = hierarchy.replace('**', '')
self.other_parents = []
if '(' in name:
assert ')' in name, f"Missing closing parenthesis in '{name}'."
self.other_parents = name[name.find('(')+1:name.find(')')].split(',')
name = name[:name.find('(')]
self.name = name
self.class_name = name+'Error'
self.message = message
self.comment = ""
if '--' in message:
self.message, self.comment = message.split('--')
self.message = self.message.strip()
self.comment = self.comment.strip()
@property
def is_leaf(self):
return 'x' not in self.hierarchy
@property
def code(self):
return self.hierarchy.replace('x', '')
@property
def parent_codes(self):
return self.hierarchy[0:2], self.hierarchy[0]
def get_arguments(self):
args = ['self']
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__":
main()
import sys
main(sys.stdout)