ccxt 4.2.77__py2.py3-none-any.whl → 4.4.48__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ccxt/__init__.py +36 -14
- ccxt/abstract/alpaca.py +4 -0
- ccxt/abstract/bigone.py +1 -1
- ccxt/abstract/binance.py +112 -48
- ccxt/abstract/binancecoinm.py +112 -48
- ccxt/abstract/binanceus.py +147 -83
- ccxt/abstract/binanceusdm.py +112 -48
- ccxt/abstract/bingx.py +133 -78
- ccxt/abstract/bitbank.py +5 -0
- ccxt/abstract/bitfinex.py +136 -65
- ccxt/abstract/bitfinex1.py +69 -0
- ccxt/abstract/bitflyer.py +1 -0
- ccxt/abstract/bitget.py +8 -1
- ccxt/abstract/bitmart.py +13 -1
- ccxt/abstract/bitopro.py +1 -0
- ccxt/abstract/bitpanda.py +0 -12
- ccxt/abstract/bitrue.py +3 -3
- ccxt/abstract/bitstamp.py +26 -3
- ccxt/abstract/blofin.py +24 -0
- ccxt/abstract/btcbox.py +1 -0
- ccxt/abstract/bybit.py +29 -14
- ccxt/abstract/cex.py +28 -29
- ccxt/abstract/coinbase.py +6 -0
- ccxt/abstract/coinbaseadvanced.py +94 -0
- ccxt/abstract/{coinbasepro.py → coinbaseexchange.py} +1 -0
- ccxt/abstract/coinbaseinternational.py +1 -1
- ccxt/abstract/coincatch.py +94 -0
- ccxt/abstract/coinex.py +233 -123
- ccxt/abstract/coinmetro.py +1 -0
- ccxt/abstract/cryptocom.py +14 -0
- ccxt/abstract/defx.py +69 -0
- ccxt/abstract/deribit.py +1 -0
- ccxt/abstract/digifinex.py +1 -0
- ccxt/abstract/ellipx.py +25 -0
- ccxt/abstract/gate.py +20 -0
- ccxt/abstract/gateio.py +20 -0
- ccxt/abstract/gemini.py +1 -0
- ccxt/abstract/hashkey.py +67 -0
- ccxt/abstract/hyperliquid.py +1 -1
- ccxt/abstract/independentreserve.py +6 -0
- ccxt/abstract/kraken.py +4 -3
- ccxt/abstract/krakenfutures.py +4 -0
- ccxt/abstract/kucoin.py +24 -0
- ccxt/abstract/kucoinfutures.py +34 -0
- ccxt/abstract/luno.py +2 -0
- ccxt/abstract/mexc.py +4 -0
- ccxt/abstract/myokx.py +340 -0
- ccxt/abstract/oceanex.py +5 -0
- ccxt/abstract/okx.py +30 -0
- ccxt/abstract/onetrading.py +0 -12
- ccxt/abstract/oxfun.py +34 -0
- ccxt/abstract/paradex.py +40 -0
- ccxt/abstract/phemex.py +1 -0
- ccxt/abstract/upbit.py +4 -0
- ccxt/abstract/vertex.py +19 -0
- ccxt/abstract/whitebit.py +31 -1
- ccxt/abstract/woo.py +6 -2
- ccxt/abstract/woofipro.py +119 -0
- ccxt/abstract/xt.py +153 -0
- ccxt/abstract/zonda.py +6 -0
- ccxt/ace.py +164 -60
- ccxt/alpaca.py +727 -63
- ccxt/ascendex.py +395 -249
- ccxt/async_support/__init__.py +36 -14
- ccxt/async_support/ace.py +164 -60
- ccxt/async_support/alpaca.py +727 -63
- ccxt/async_support/ascendex.py +396 -249
- ccxt/async_support/base/exchange.py +531 -155
- ccxt/async_support/base/ws/aiohttp_client.py +28 -5
- ccxt/async_support/base/ws/cache.py +3 -2
- ccxt/async_support/base/ws/client.py +26 -5
- ccxt/async_support/base/ws/fast_client.py +4 -3
- ccxt/async_support/base/ws/functions.py +1 -1
- ccxt/async_support/base/ws/future.py +40 -31
- ccxt/async_support/base/ws/order_book_side.py +3 -0
- ccxt/async_support/bequant.py +1 -1
- ccxt/async_support/bigone.py +329 -202
- ccxt/async_support/binance.py +3030 -1087
- ccxt/async_support/binancecoinm.py +2 -1
- ccxt/async_support/binanceus.py +12 -1
- ccxt/async_support/binanceusdm.py +3 -1
- ccxt/async_support/bingx.py +3104 -880
- ccxt/async_support/bit2c.py +119 -38
- ccxt/async_support/bitbank.py +215 -76
- ccxt/async_support/bitbns.py +124 -53
- ccxt/async_support/bitfinex.py +3236 -1078
- ccxt/async_support/bitfinex1.py +1711 -0
- ccxt/async_support/bitflyer.py +238 -49
- ccxt/async_support/bitget.py +1513 -563
- ccxt/async_support/bithumb.py +199 -65
- ccxt/async_support/bitmart.py +1320 -435
- ccxt/async_support/bitmex.py +308 -111
- ccxt/async_support/bitopro.py +256 -96
- ccxt/async_support/bitrue.py +365 -233
- ccxt/async_support/bitso.py +201 -89
- ccxt/async_support/bitstamp.py +438 -269
- ccxt/async_support/bitteam.py +179 -73
- ccxt/async_support/bitvavo.py +180 -70
- ccxt/async_support/bl3p.py +92 -25
- ccxt/async_support/blockchaincom.py +193 -79
- ccxt/async_support/blofin.py +392 -148
- ccxt/async_support/btcalpha.py +161 -55
- ccxt/async_support/btcbox.py +250 -34
- ccxt/async_support/btcmarkets.py +232 -85
- ccxt/async_support/btcturk.py +159 -60
- ccxt/async_support/bybit.py +2231 -1193
- ccxt/async_support/cex.py +1409 -1329
- ccxt/async_support/coinbase.py +1454 -287
- ccxt/async_support/coinbaseadvanced.py +17 -0
- ccxt/async_support/{coinbasepro.py → coinbaseexchange.py} +233 -99
- ccxt/async_support/coinbaseinternational.py +428 -88
- ccxt/async_support/coincatch.py +5152 -0
- ccxt/async_support/coincheck.py +121 -38
- ccxt/async_support/coinex.py +4020 -3339
- ccxt/async_support/coinlist.py +273 -116
- ccxt/async_support/coinmate.py +204 -97
- ccxt/async_support/coinmetro.py +203 -110
- ccxt/async_support/coinone.py +142 -68
- ccxt/async_support/coinsph.py +206 -89
- ccxt/async_support/coinspot.py +137 -62
- ccxt/async_support/cryptocom.py +515 -185
- ccxt/async_support/currencycom.py +203 -85
- ccxt/async_support/defx.py +2066 -0
- ccxt/async_support/delta.py +404 -109
- ccxt/async_support/deribit.py +557 -323
- ccxt/async_support/digifinex.py +340 -223
- ccxt/async_support/ellipx.py +1826 -0
- ccxt/async_support/exmo.py +259 -128
- ccxt/async_support/gate.py +1472 -463
- ccxt/async_support/gemini.py +206 -84
- ccxt/async_support/hashkey.py +4164 -0
- ccxt/async_support/hitbtc.py +334 -178
- ccxt/async_support/hollaex.py +134 -83
- ccxt/async_support/htx.py +1095 -563
- ccxt/async_support/huobijp.py +105 -56
- ccxt/async_support/hyperliquid.py +1633 -268
- ccxt/async_support/idex.py +148 -95
- ccxt/async_support/independentreserve.py +236 -31
- ccxt/async_support/indodax.py +165 -62
- ccxt/async_support/kraken.py +871 -354
- ccxt/async_support/krakenfutures.py +324 -100
- ccxt/async_support/kucoin.py +917 -357
- ccxt/async_support/kucoinfutures.py +1004 -149
- ccxt/async_support/kuna.py +138 -106
- ccxt/async_support/latoken.py +135 -79
- ccxt/async_support/lbank.py +290 -113
- ccxt/async_support/luno.py +112 -62
- ccxt/async_support/lykke.py +104 -55
- ccxt/async_support/mercado.py +36 -29
- ccxt/async_support/mexc.py +995 -429
- ccxt/async_support/myokx.py +43 -0
- ccxt/async_support/ndax.py +163 -82
- ccxt/async_support/novadax.py +121 -75
- ccxt/async_support/oceanex.py +175 -59
- ccxt/async_support/okcoin.py +222 -163
- ccxt/async_support/okx.py +1776 -454
- ccxt/async_support/onetrading.py +132 -414
- ccxt/async_support/oxfun.py +2832 -0
- ccxt/async_support/p2b.py +79 -51
- ccxt/async_support/paradex.py +2017 -0
- ccxt/async_support/paymium.py +56 -32
- ccxt/async_support/phemex.py +572 -196
- ccxt/async_support/poloniex.py +218 -95
- ccxt/async_support/poloniexfutures.py +260 -92
- ccxt/async_support/probit.py +143 -110
- ccxt/async_support/timex.py +123 -70
- ccxt/async_support/tokocrypto.py +129 -93
- ccxt/async_support/tradeogre.py +39 -25
- ccxt/async_support/upbit.py +322 -113
- ccxt/async_support/vertex.py +2983 -0
- ccxt/async_support/wavesexchange.py +227 -173
- ccxt/async_support/wazirx.py +145 -65
- ccxt/async_support/whitebit.py +533 -138
- ccxt/async_support/woo.py +1137 -296
- ccxt/async_support/woofipro.py +2716 -0
- ccxt/async_support/xt.py +4628 -0
- ccxt/async_support/yobit.py +160 -92
- ccxt/async_support/zaif.py +80 -33
- ccxt/async_support/zonda.py +140 -69
- ccxt/base/errors.py +51 -20
- ccxt/base/exchange.py +1722 -480
- ccxt/base/precise.py +10 -0
- ccxt/base/types.py +223 -4
- ccxt/bequant.py +1 -1
- ccxt/bigone.py +329 -202
- ccxt/binance.py +3030 -1087
- ccxt/binancecoinm.py +2 -1
- ccxt/binanceus.py +12 -1
- ccxt/binanceusdm.py +3 -1
- ccxt/bingx.py +3104 -880
- ccxt/bit2c.py +119 -38
- ccxt/bitbank.py +215 -76
- ccxt/bitbns.py +124 -53
- ccxt/bitfinex.py +3235 -1078
- ccxt/bitfinex1.py +1710 -0
- ccxt/bitflyer.py +238 -49
- ccxt/bitget.py +1513 -563
- ccxt/bithumb.py +198 -65
- ccxt/bitmart.py +1320 -435
- ccxt/bitmex.py +308 -111
- ccxt/bitopro.py +256 -96
- ccxt/bitrue.py +365 -233
- ccxt/bitso.py +201 -89
- ccxt/bitstamp.py +438 -269
- ccxt/bitteam.py +179 -73
- ccxt/bitvavo.py +180 -70
- ccxt/bl3p.py +92 -25
- ccxt/blockchaincom.py +193 -79
- ccxt/blofin.py +392 -148
- ccxt/btcalpha.py +161 -55
- ccxt/btcbox.py +250 -34
- ccxt/btcmarkets.py +232 -85
- ccxt/btcturk.py +159 -60
- ccxt/bybit.py +2231 -1193
- ccxt/cex.py +1408 -1329
- ccxt/coinbase.py +1454 -287
- ccxt/coinbaseadvanced.py +17 -0
- ccxt/{coinbasepro.py → coinbaseexchange.py} +233 -99
- ccxt/coinbaseinternational.py +428 -88
- ccxt/coincatch.py +5152 -0
- ccxt/coincheck.py +121 -38
- ccxt/coinex.py +4020 -3339
- ccxt/coinlist.py +273 -116
- ccxt/coinmate.py +204 -97
- ccxt/coinmetro.py +203 -110
- ccxt/coinone.py +142 -68
- ccxt/coinsph.py +206 -89
- ccxt/coinspot.py +137 -62
- ccxt/cryptocom.py +515 -185
- ccxt/currencycom.py +203 -85
- ccxt/defx.py +2065 -0
- ccxt/delta.py +404 -109
- ccxt/deribit.py +557 -323
- ccxt/digifinex.py +340 -223
- ccxt/ellipx.py +1826 -0
- ccxt/exmo.py +259 -128
- ccxt/gate.py +1472 -463
- ccxt/gemini.py +206 -84
- ccxt/hashkey.py +4164 -0
- ccxt/hitbtc.py +334 -178
- ccxt/hollaex.py +134 -83
- ccxt/htx.py +1095 -563
- ccxt/huobijp.py +105 -56
- ccxt/hyperliquid.py +1632 -268
- ccxt/idex.py +148 -95
- ccxt/independentreserve.py +235 -31
- ccxt/indodax.py +165 -62
- ccxt/kraken.py +871 -354
- ccxt/krakenfutures.py +324 -100
- ccxt/kucoin.py +917 -357
- ccxt/kucoinfutures.py +1004 -149
- ccxt/kuna.py +138 -106
- ccxt/latoken.py +135 -79
- ccxt/lbank.py +290 -113
- ccxt/luno.py +112 -62
- ccxt/lykke.py +104 -55
- ccxt/mercado.py +36 -29
- ccxt/mexc.py +994 -429
- ccxt/myokx.py +43 -0
- ccxt/ndax.py +163 -82
- ccxt/novadax.py +121 -75
- ccxt/oceanex.py +175 -59
- ccxt/okcoin.py +222 -163
- ccxt/okx.py +1776 -454
- ccxt/onetrading.py +132 -414
- ccxt/oxfun.py +2831 -0
- ccxt/p2b.py +79 -51
- ccxt/paradex.py +2017 -0
- ccxt/paymium.py +56 -32
- ccxt/phemex.py +572 -196
- ccxt/poloniex.py +218 -95
- ccxt/poloniexfutures.py +260 -92
- ccxt/pro/__init__.py +29 -5
- ccxt/pro/alpaca.py +32 -17
- ccxt/pro/ascendex.py +62 -14
- ccxt/pro/bequant.py +4 -0
- ccxt/pro/binance.py +1596 -329
- ccxt/pro/binancecoinm.py +1 -0
- ccxt/pro/binanceus.py +2 -9
- ccxt/pro/binanceusdm.py +2 -0
- ccxt/pro/bingx.py +527 -134
- ccxt/pro/bitcoincom.py +4 -1
- ccxt/pro/bitfinex.py +731 -266
- ccxt/pro/bitfinex1.py +635 -0
- ccxt/pro/bitget.py +726 -357
- ccxt/pro/bithumb.py +380 -0
- ccxt/pro/bitmart.py +138 -39
- ccxt/pro/bitmex.py +199 -40
- ccxt/pro/bitopro.py +25 -13
- ccxt/pro/bitrue.py +31 -32
- ccxt/pro/bitstamp.py +7 -6
- ccxt/pro/bitvavo.py +203 -81
- ccxt/pro/blockchaincom.py +30 -17
- ccxt/pro/blofin.py +692 -0
- ccxt/pro/bybit.py +791 -82
- ccxt/pro/cex.py +99 -51
- ccxt/pro/coinbase.py +220 -30
- ccxt/{async_support/hitbtc3.py → pro/coinbaseadvanced.py} +5 -5
- ccxt/pro/{coinbasepro.py → coinbaseexchange.py} +19 -19
- ccxt/pro/coinbaseinternational.py +193 -30
- ccxt/pro/coincatch.py +1464 -0
- ccxt/pro/coincheck.py +11 -6
- ccxt/pro/coinex.py +965 -665
- ccxt/pro/coinone.py +17 -10
- ccxt/pro/cryptocom.py +446 -66
- ccxt/pro/currencycom.py +11 -10
- ccxt/pro/defx.py +832 -0
- ccxt/pro/deribit.py +167 -31
- ccxt/pro/exmo.py +252 -20
- ccxt/pro/gate.py +729 -64
- ccxt/pro/gemini.py +44 -26
- ccxt/pro/hashkey.py +802 -0
- ccxt/pro/hitbtc.py +208 -103
- ccxt/pro/hollaex.py +25 -9
- ccxt/pro/htx.py +83 -39
- ccxt/pro/huobijp.py +17 -16
- ccxt/pro/hyperliquid.py +502 -31
- ccxt/pro/idex.py +28 -13
- ccxt/pro/independentreserve.py +21 -16
- ccxt/pro/kraken.py +298 -51
- ccxt/pro/krakenfutures.py +166 -75
- ccxt/pro/kucoin.py +395 -77
- ccxt/pro/kucoinfutures.py +400 -99
- ccxt/pro/lbank.py +52 -31
- ccxt/pro/luno.py +12 -10
- ccxt/pro/mexc.py +400 -50
- ccxt/pro/myokx.py +28 -0
- ccxt/pro/ndax.py +25 -12
- ccxt/pro/okcoin.py +28 -9
- ccxt/pro/okx.py +935 -124
- ccxt/pro/onetrading.py +41 -24
- ccxt/pro/oxfun.py +1054 -0
- ccxt/pro/p2b.py +100 -24
- ccxt/pro/paradex.py +352 -0
- ccxt/pro/phemex.py +92 -33
- ccxt/pro/poloniex.py +128 -49
- ccxt/pro/poloniexfutures.py +53 -32
- ccxt/pro/probit.py +92 -85
- ccxt/pro/upbit.py +401 -8
- ccxt/pro/vertex.py +943 -0
- ccxt/pro/wazirx.py +46 -28
- ccxt/pro/whitebit.py +65 -12
- ccxt/pro/woo.py +437 -65
- ccxt/pro/woofipro.py +1271 -0
- ccxt/pro/xt.py +1067 -0
- ccxt/probit.py +143 -110
- ccxt/static_dependencies/__init__.py +1 -1
- ccxt/static_dependencies/lark/__init__.py +38 -0
- ccxt/static_dependencies/lark/__pyinstaller/__init__.py +6 -0
- ccxt/static_dependencies/lark/__pyinstaller/hook-lark.py +14 -0
- ccxt/static_dependencies/lark/ast_utils.py +59 -0
- ccxt/static_dependencies/lark/common.py +86 -0
- ccxt/static_dependencies/lark/exceptions.py +292 -0
- ccxt/static_dependencies/lark/grammar.py +130 -0
- ccxt/static_dependencies/lark/grammars/__init__.py +0 -0
- ccxt/static_dependencies/lark/indenter.py +143 -0
- ccxt/static_dependencies/lark/lark.py +658 -0
- ccxt/static_dependencies/lark/lexer.py +678 -0
- ccxt/static_dependencies/lark/load_grammar.py +1428 -0
- ccxt/static_dependencies/lark/parse_tree_builder.py +391 -0
- ccxt/static_dependencies/lark/parser_frontends.py +257 -0
- ccxt/static_dependencies/lark/parsers/__init__.py +0 -0
- ccxt/static_dependencies/lark/parsers/cyk.py +340 -0
- ccxt/static_dependencies/lark/parsers/earley.py +314 -0
- ccxt/static_dependencies/lark/parsers/earley_common.py +42 -0
- ccxt/static_dependencies/lark/parsers/earley_forest.py +801 -0
- ccxt/static_dependencies/lark/parsers/grammar_analysis.py +203 -0
- ccxt/static_dependencies/lark/parsers/lalr_analysis.py +332 -0
- ccxt/static_dependencies/lark/parsers/lalr_interactive_parser.py +158 -0
- ccxt/static_dependencies/lark/parsers/lalr_parser.py +122 -0
- ccxt/static_dependencies/lark/parsers/lalr_parser_state.py +110 -0
- ccxt/static_dependencies/lark/parsers/xearley.py +165 -0
- ccxt/static_dependencies/lark/py.typed +0 -0
- ccxt/static_dependencies/lark/reconstruct.py +107 -0
- ccxt/static_dependencies/lark/tools/__init__.py +70 -0
- ccxt/static_dependencies/lark/tools/nearley.py +202 -0
- ccxt/static_dependencies/lark/tools/serialize.py +32 -0
- ccxt/static_dependencies/lark/tools/standalone.py +196 -0
- ccxt/static_dependencies/lark/tree.py +267 -0
- ccxt/static_dependencies/lark/tree_matcher.py +186 -0
- ccxt/static_dependencies/lark/tree_templates.py +180 -0
- ccxt/static_dependencies/lark/utils.py +343 -0
- ccxt/static_dependencies/lark/visitors.py +596 -0
- ccxt/static_dependencies/marshmallow/__init__.py +81 -0
- ccxt/static_dependencies/marshmallow/base.py +65 -0
- ccxt/static_dependencies/marshmallow/class_registry.py +94 -0
- ccxt/static_dependencies/marshmallow/decorators.py +231 -0
- ccxt/static_dependencies/marshmallow/error_store.py +60 -0
- ccxt/static_dependencies/marshmallow/exceptions.py +71 -0
- ccxt/static_dependencies/marshmallow/fields.py +2114 -0
- ccxt/static_dependencies/marshmallow/orderedset.py +89 -0
- ccxt/static_dependencies/marshmallow/py.typed +0 -0
- ccxt/static_dependencies/marshmallow/schema.py +1228 -0
- ccxt/static_dependencies/marshmallow/types.py +12 -0
- ccxt/static_dependencies/marshmallow/utils.py +378 -0
- ccxt/static_dependencies/marshmallow/validate.py +678 -0
- ccxt/static_dependencies/marshmallow/warnings.py +2 -0
- ccxt/static_dependencies/marshmallow_dataclass/__init__.py +1047 -0
- ccxt/static_dependencies/marshmallow_dataclass/collection_field.py +51 -0
- ccxt/static_dependencies/marshmallow_dataclass/lazy_class_attribute.py +45 -0
- ccxt/static_dependencies/marshmallow_dataclass/mypy.py +71 -0
- ccxt/static_dependencies/marshmallow_dataclass/py.typed +0 -0
- ccxt/static_dependencies/marshmallow_dataclass/typing.py +14 -0
- ccxt/static_dependencies/marshmallow_dataclass/union_field.py +82 -0
- ccxt/static_dependencies/marshmallow_oneofschema/__init__.py +1 -0
- ccxt/static_dependencies/marshmallow_oneofschema/one_of_schema.py +193 -0
- ccxt/static_dependencies/marshmallow_oneofschema/py.typed +0 -0
- ccxt/static_dependencies/starknet/__init__.py +0 -0
- ccxt/static_dependencies/starknet/cairo/__init__.py +0 -0
- ccxt/static_dependencies/starknet/cairo/data_types.py +123 -0
- ccxt/static_dependencies/starknet/cairo/deprecated_parse/__init__.py +0 -0
- ccxt/static_dependencies/starknet/cairo/deprecated_parse/cairo_types.py +77 -0
- ccxt/static_dependencies/starknet/cairo/deprecated_parse/parser.py +46 -0
- ccxt/static_dependencies/starknet/cairo/deprecated_parse/parser_transformer.py +138 -0
- ccxt/static_dependencies/starknet/cairo/felt.py +64 -0
- ccxt/static_dependencies/starknet/cairo/type_parser.py +121 -0
- ccxt/static_dependencies/starknet/cairo/v1/__init__.py +0 -0
- ccxt/static_dependencies/starknet/cairo/v1/type_parser.py +59 -0
- ccxt/static_dependencies/starknet/cairo/v2/__init__.py +0 -0
- ccxt/static_dependencies/starknet/cairo/v2/type_parser.py +77 -0
- ccxt/static_dependencies/starknet/ccxt_utils.py +7 -0
- ccxt/static_dependencies/starknet/common.py +15 -0
- ccxt/static_dependencies/starknet/constants.py +39 -0
- ccxt/static_dependencies/starknet/hash/__init__.py +0 -0
- ccxt/static_dependencies/starknet/hash/address.py +79 -0
- ccxt/static_dependencies/starknet/hash/compiled_class_hash_objects.py +111 -0
- ccxt/static_dependencies/starknet/hash/selector.py +16 -0
- ccxt/static_dependencies/starknet/hash/storage.py +12 -0
- ccxt/static_dependencies/starknet/hash/utils.py +78 -0
- ccxt/static_dependencies/starknet/models/__init__.py +0 -0
- ccxt/static_dependencies/starknet/models/typed_data.py +45 -0
- ccxt/static_dependencies/starknet/serialization/__init__.py +24 -0
- ccxt/static_dependencies/starknet/serialization/_calldata_reader.py +40 -0
- ccxt/static_dependencies/starknet/serialization/_context.py +142 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/__init__.py +10 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/_common.py +82 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/array_serializer.py +43 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/bool_serializer.py +37 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/byte_array_serializer.py +66 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/cairo_data_serializer.py +71 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/enum_serializer.py +71 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/felt_serializer.py +50 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/named_tuple_serializer.py +58 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/option_serializer.py +43 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/output_serializer.py +40 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/payload_serializer.py +72 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/struct_serializer.py +36 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/tuple_serializer.py +36 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/uint256_serializer.py +76 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/uint_serializer.py +100 -0
- ccxt/static_dependencies/starknet/serialization/data_serializers/unit_serializer.py +32 -0
- ccxt/static_dependencies/starknet/serialization/errors.py +10 -0
- ccxt/static_dependencies/starknet/serialization/factory.py +229 -0
- ccxt/static_dependencies/starknet/serialization/function_serialization_adapter.py +110 -0
- ccxt/static_dependencies/starknet/serialization/tuple_dataclass.py +59 -0
- ccxt/static_dependencies/starknet/utils/__init__.py +0 -0
- ccxt/static_dependencies/starknet/utils/constructor_args_translator.py +86 -0
- ccxt/static_dependencies/starknet/utils/iterable.py +13 -0
- ccxt/static_dependencies/starknet/utils/schema.py +13 -0
- ccxt/static_dependencies/starknet/utils/typed_data.py +182 -0
- ccxt/static_dependencies/starkware/__init__.py +0 -0
- ccxt/static_dependencies/starkware/crypto/__init__.py +0 -0
- ccxt/static_dependencies/starkware/crypto/fast_pedersen_hash.py +50 -0
- ccxt/static_dependencies/starkware/crypto/math_utils.py +78 -0
- ccxt/static_dependencies/starkware/crypto/signature.py +2344 -0
- ccxt/static_dependencies/starkware/crypto/utils.py +63 -0
- ccxt/static_dependencies/sympy/__init__.py +0 -0
- ccxt/static_dependencies/sympy/core/__init__.py +0 -0
- ccxt/static_dependencies/sympy/core/intfunc.py +35 -0
- ccxt/static_dependencies/sympy/external/__init__.py +0 -0
- ccxt/static_dependencies/sympy/external/gmpy.py +345 -0
- ccxt/static_dependencies/sympy/external/importtools.py +187 -0
- ccxt/static_dependencies/sympy/external/ntheory.py +637 -0
- ccxt/static_dependencies/sympy/external/pythonmpq.py +341 -0
- ccxt/static_dependencies/typing_inspect/__init__.py +0 -0
- ccxt/static_dependencies/typing_inspect/typing_inspect.py +851 -0
- ccxt/test/{test_async.py → tests_async.py} +456 -391
- ccxt/test/tests_helpers.py +285 -0
- ccxt/test/tests_init.py +39 -0
- ccxt/test/{test_sync.py → tests_sync.py} +456 -393
- ccxt/timex.py +123 -70
- ccxt/tokocrypto.py +129 -93
- ccxt/tradeogre.py +39 -25
- ccxt/upbit.py +322 -113
- ccxt/vertex.py +2983 -0
- ccxt/wavesexchange.py +227 -173
- ccxt/wazirx.py +145 -65
- ccxt/whitebit.py +533 -138
- ccxt/woo.py +1137 -296
- ccxt/woofipro.py +2716 -0
- ccxt/xt.py +4627 -0
- ccxt/yobit.py +159 -92
- ccxt/zaif.py +80 -33
- ccxt/zonda.py +140 -69
- ccxt-4.4.48.dist-info/LICENSE.txt +21 -0
- ccxt-4.4.48.dist-info/METADATA +646 -0
- ccxt-4.4.48.dist-info/RECORD +669 -0
- {ccxt-4.2.77.dist-info → ccxt-4.4.48.dist-info}/WHEEL +1 -1
- ccxt/abstract/bitbay.py +0 -47
- ccxt/abstract/bitfinex2.py +0 -139
- ccxt/abstract/hitbtc3.py +0 -115
- ccxt/async_support/bitbay.py +0 -17
- ccxt/async_support/bitfinex2.py +0 -3496
- ccxt/async_support/flowbtc.py +0 -34
- ccxt/bitbay.py +0 -17
- ccxt/bitfinex2.py +0 -3496
- ccxt/flowbtc.py +0 -34
- ccxt/hitbtc3.py +0 -16
- ccxt/pro/bitfinex2.py +0 -1081
- ccxt/test/base/__init__.py +0 -28
- ccxt/test/base/test_account.py +0 -26
- ccxt/test/base/test_balance.py +0 -56
- ccxt/test/base/test_borrow_interest.py +0 -35
- ccxt/test/base/test_borrow_rate.py +0 -32
- ccxt/test/base/test_calculate_fee.py +0 -51
- ccxt/test/base/test_crypto.py +0 -127
- ccxt/test/base/test_currency.py +0 -76
- ccxt/test/base/test_datetime.py +0 -103
- ccxt/test/base/test_decimal_to_precision.py +0 -392
- ccxt/test/base/test_deep_extend.py +0 -68
- ccxt/test/base/test_deposit_withdrawal.py +0 -50
- ccxt/test/base/test_exchange_datetime_functions.py +0 -76
- ccxt/test/base/test_funding_rate_history.py +0 -29
- ccxt/test/base/test_last_price.py +0 -32
- ccxt/test/base/test_ledger_entry.py +0 -45
- ccxt/test/base/test_ledger_item.py +0 -48
- ccxt/test/base/test_leverage_tier.py +0 -33
- ccxt/test/base/test_margin_mode.py +0 -24
- ccxt/test/base/test_margin_modification.py +0 -35
- ccxt/test/base/test_market.py +0 -190
- ccxt/test/base/test_number.py +0 -411
- ccxt/test/base/test_ohlcv.py +0 -32
- ccxt/test/base/test_open_interest.py +0 -32
- ccxt/test/base/test_order.py +0 -64
- ccxt/test/base/test_order_book.py +0 -63
- ccxt/test/base/test_position.py +0 -60
- ccxt/test/base/test_shared_methods.py +0 -345
- ccxt/test/base/test_status.py +0 -24
- ccxt/test/base/test_throttle.py +0 -126
- ccxt/test/base/test_ticker.py +0 -86
- ccxt/test/base/test_trade.py +0 -47
- ccxt/test/base/test_trading_fee.py +0 -26
- ccxt/test/base/test_transaction.py +0 -39
- ccxt-4.2.77.dist-info/METADATA +0 -626
- ccxt-4.2.77.dist-info/RECORD +0 -534
- {ccxt-4.2.77.dist-info → ccxt-4.4.48.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,678 @@
|
|
1
|
+
"""Validation classes for various types of data."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import re
|
6
|
+
import typing
|
7
|
+
from abc import ABC, abstractmethod
|
8
|
+
from itertools import zip_longest
|
9
|
+
from operator import attrgetter
|
10
|
+
|
11
|
+
from . import types
|
12
|
+
from .exceptions import ValidationError
|
13
|
+
|
14
|
+
_T = typing.TypeVar("_T")
|
15
|
+
|
16
|
+
|
17
|
+
class Validator(ABC):
|
18
|
+
"""Abstract base class for validators.
|
19
|
+
|
20
|
+
.. note::
|
21
|
+
This class does not provide any validation behavior. It is only used to
|
22
|
+
add a useful `__repr__` implementation for validators.
|
23
|
+
"""
|
24
|
+
|
25
|
+
error = None # type: str | None
|
26
|
+
|
27
|
+
def __repr__(self) -> str:
|
28
|
+
args = self._repr_args()
|
29
|
+
args = f"{args}, " if args else ""
|
30
|
+
|
31
|
+
return f"<{self.__class__.__name__}({args}error={self.error!r})>"
|
32
|
+
|
33
|
+
def _repr_args(self) -> str:
|
34
|
+
"""A string representation of the args passed to this validator. Used by
|
35
|
+
`__repr__`.
|
36
|
+
"""
|
37
|
+
return ""
|
38
|
+
|
39
|
+
@abstractmethod
|
40
|
+
def __call__(self, value: typing.Any) -> typing.Any: ...
|
41
|
+
|
42
|
+
|
43
|
+
class And(Validator):
|
44
|
+
"""Compose multiple validators and combine their error messages.
|
45
|
+
|
46
|
+
Example: ::
|
47
|
+
|
48
|
+
from . import validate, ValidationError
|
49
|
+
|
50
|
+
|
51
|
+
def is_even(value):
|
52
|
+
if value % 2 != 0:
|
53
|
+
raise ValidationError("Not an even value.")
|
54
|
+
|
55
|
+
|
56
|
+
validator = validate.And(validate.Range(min=0), is_even)
|
57
|
+
validator(-1)
|
58
|
+
# ValidationError: ['Must be greater than or equal to 0.', 'Not an even value.']
|
59
|
+
|
60
|
+
:param validators: Validators to combine.
|
61
|
+
:param error: Error message to use when a validator returns ``False``.
|
62
|
+
"""
|
63
|
+
|
64
|
+
default_error_message = "Invalid value."
|
65
|
+
|
66
|
+
def __init__(self, *validators: types.Validator, error: str | None = None):
|
67
|
+
self.validators = tuple(validators)
|
68
|
+
self.error = error or self.default_error_message # type: str
|
69
|
+
|
70
|
+
def _repr_args(self) -> str:
|
71
|
+
return f"validators={self.validators!r}"
|
72
|
+
|
73
|
+
def __call__(self, value: typing.Any) -> typing.Any:
|
74
|
+
errors = []
|
75
|
+
kwargs = {}
|
76
|
+
for validator in self.validators:
|
77
|
+
try:
|
78
|
+
r = validator(value)
|
79
|
+
if not isinstance(validator, Validator) and r is False:
|
80
|
+
raise ValidationError(self.error)
|
81
|
+
except ValidationError as err:
|
82
|
+
kwargs.update(err.kwargs)
|
83
|
+
if isinstance(err.messages, dict):
|
84
|
+
errors.append(err.messages)
|
85
|
+
else:
|
86
|
+
# FIXME : Get rid of cast
|
87
|
+
errors.extend(typing.cast(list, err.messages))
|
88
|
+
if errors:
|
89
|
+
raise ValidationError(errors, **kwargs)
|
90
|
+
return value
|
91
|
+
|
92
|
+
|
93
|
+
class URL(Validator):
|
94
|
+
"""Validate a URL.
|
95
|
+
|
96
|
+
:param relative: Whether to allow relative URLs.
|
97
|
+
:param absolute: Whether to allow absolute URLs.
|
98
|
+
:param error: Error message to raise in case of a validation error.
|
99
|
+
Can be interpolated with `{input}`.
|
100
|
+
:param schemes: Valid schemes. By default, ``http``, ``https``,
|
101
|
+
``ftp``, and ``ftps`` are allowed.
|
102
|
+
:param require_tld: Whether to reject non-FQDN hostnames.
|
103
|
+
"""
|
104
|
+
|
105
|
+
class RegexMemoizer:
|
106
|
+
def __init__(self):
|
107
|
+
self._memoized = {}
|
108
|
+
|
109
|
+
def _regex_generator(
|
110
|
+
self, relative: bool, absolute: bool, require_tld: bool
|
111
|
+
) -> typing.Pattern:
|
112
|
+
hostname_variants = [
|
113
|
+
# a normal domain name, expressed in [A-Z0-9] chars with hyphens allowed only in the middle
|
114
|
+
# note that the regex will be compiled with IGNORECASE, so these are upper and lowercase chars
|
115
|
+
(
|
116
|
+
r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+"
|
117
|
+
r"(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)"
|
118
|
+
),
|
119
|
+
# or the special string 'localhost'
|
120
|
+
r"localhost",
|
121
|
+
# or IPv4
|
122
|
+
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}",
|
123
|
+
# or IPv6
|
124
|
+
r"\[[A-F0-9]*:[A-F0-9:]+\]",
|
125
|
+
]
|
126
|
+
if not require_tld:
|
127
|
+
# allow dotless hostnames
|
128
|
+
hostname_variants.append(r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.?)")
|
129
|
+
|
130
|
+
absolute_part = "".join(
|
131
|
+
(
|
132
|
+
# scheme (e.g. 'https://', 'ftp://', etc)
|
133
|
+
# this is validated separately against allowed schemes, so in the regex
|
134
|
+
# we simply want to capture its existence
|
135
|
+
r"(?:[a-z0-9\.\-\+]*)://",
|
136
|
+
# userinfo, for URLs encoding authentication
|
137
|
+
# e.g. 'ftp://foo:bar@ftp.example.org/'
|
138
|
+
r"(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?",
|
139
|
+
# netloc, the hostname/domain part of the URL plus the optional port
|
140
|
+
r"(?:",
|
141
|
+
"|".join(hostname_variants),
|
142
|
+
r")",
|
143
|
+
r"(?::\d+)?",
|
144
|
+
)
|
145
|
+
)
|
146
|
+
relative_part = r"(?:/?|[/?]\S+)\Z"
|
147
|
+
|
148
|
+
if relative:
|
149
|
+
if absolute:
|
150
|
+
parts: tuple[str, ...] = (
|
151
|
+
r"^(",
|
152
|
+
absolute_part,
|
153
|
+
r")?",
|
154
|
+
relative_part,
|
155
|
+
)
|
156
|
+
else:
|
157
|
+
parts = (r"^", relative_part)
|
158
|
+
else:
|
159
|
+
parts = (r"^", absolute_part, relative_part)
|
160
|
+
|
161
|
+
return re.compile("".join(parts), re.IGNORECASE)
|
162
|
+
|
163
|
+
def __call__(
|
164
|
+
self, relative: bool, absolute: bool, require_tld: bool
|
165
|
+
) -> typing.Pattern:
|
166
|
+
key = (relative, absolute, require_tld)
|
167
|
+
if key not in self._memoized:
|
168
|
+
self._memoized[key] = self._regex_generator(
|
169
|
+
relative, absolute, require_tld
|
170
|
+
)
|
171
|
+
|
172
|
+
return self._memoized[key]
|
173
|
+
|
174
|
+
_regex = RegexMemoizer()
|
175
|
+
|
176
|
+
default_message = "Not a valid URL."
|
177
|
+
default_schemes = {"http", "https", "ftp", "ftps"}
|
178
|
+
|
179
|
+
def __init__(
|
180
|
+
self,
|
181
|
+
*,
|
182
|
+
relative: bool = False,
|
183
|
+
absolute: bool = True,
|
184
|
+
schemes: types.StrSequenceOrSet | None = None,
|
185
|
+
require_tld: bool = True,
|
186
|
+
error: str | None = None,
|
187
|
+
):
|
188
|
+
if not relative and not absolute:
|
189
|
+
raise ValueError(
|
190
|
+
"URL validation cannot set both relative and absolute to False."
|
191
|
+
)
|
192
|
+
self.relative = relative
|
193
|
+
self.absolute = absolute
|
194
|
+
self.error = error or self.default_message # type: str
|
195
|
+
self.schemes = schemes or self.default_schemes
|
196
|
+
self.require_tld = require_tld
|
197
|
+
|
198
|
+
def _repr_args(self) -> str:
|
199
|
+
return f"relative={self.relative!r}, absolute={self.absolute!r}"
|
200
|
+
|
201
|
+
def _format_error(self, value) -> str:
|
202
|
+
return self.error.format(input=value)
|
203
|
+
|
204
|
+
def __call__(self, value: str) -> str:
|
205
|
+
message = self._format_error(value)
|
206
|
+
if not value:
|
207
|
+
raise ValidationError(message)
|
208
|
+
|
209
|
+
# Check first if the scheme is valid
|
210
|
+
if "://" in value:
|
211
|
+
scheme = value.split("://")[0].lower()
|
212
|
+
if scheme not in self.schemes:
|
213
|
+
raise ValidationError(message)
|
214
|
+
|
215
|
+
regex = self._regex(self.relative, self.absolute, self.require_tld)
|
216
|
+
|
217
|
+
if not regex.search(value):
|
218
|
+
raise ValidationError(message)
|
219
|
+
|
220
|
+
return value
|
221
|
+
|
222
|
+
|
223
|
+
class Email(Validator):
|
224
|
+
"""Validate an email address.
|
225
|
+
|
226
|
+
:param error: Error message to raise in case of a validation error. Can be
|
227
|
+
interpolated with `{input}`.
|
228
|
+
"""
|
229
|
+
|
230
|
+
USER_REGEX = re.compile(
|
231
|
+
r"(^[-!#$%&'*+/=?^`{}|~\w]+(\.[-!#$%&'*+/=?^`{}|~\w]+)*\Z" # dot-atom
|
232
|
+
# quoted-string
|
233
|
+
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]'
|
234
|
+
r'|\\[\001-\011\013\014\016-\177])*"\Z)',
|
235
|
+
re.IGNORECASE | re.UNICODE,
|
236
|
+
)
|
237
|
+
|
238
|
+
DOMAIN_REGEX = re.compile(
|
239
|
+
# domain
|
240
|
+
r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+"
|
241
|
+
r"(?:[A-Z]{2,6}|[A-Z0-9-]{2,})\Z"
|
242
|
+
# literal form, ipv4 address (SMTP 4.1.3)
|
243
|
+
r"|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)"
|
244
|
+
r"(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]\Z",
|
245
|
+
re.IGNORECASE | re.UNICODE,
|
246
|
+
)
|
247
|
+
|
248
|
+
DOMAIN_WHITELIST = ("localhost",)
|
249
|
+
|
250
|
+
default_message = "Not a valid email address."
|
251
|
+
|
252
|
+
def __init__(self, *, error: str | None = None):
|
253
|
+
self.error = error or self.default_message # type: str
|
254
|
+
|
255
|
+
def _format_error(self, value: str) -> str:
|
256
|
+
return self.error.format(input=value)
|
257
|
+
|
258
|
+
def __call__(self, value: str) -> str:
|
259
|
+
message = self._format_error(value)
|
260
|
+
|
261
|
+
if not value or "@" not in value:
|
262
|
+
raise ValidationError(message)
|
263
|
+
|
264
|
+
user_part, domain_part = value.rsplit("@", 1)
|
265
|
+
|
266
|
+
if not self.USER_REGEX.match(user_part):
|
267
|
+
raise ValidationError(message)
|
268
|
+
|
269
|
+
if domain_part not in self.DOMAIN_WHITELIST:
|
270
|
+
if not self.DOMAIN_REGEX.match(domain_part):
|
271
|
+
try:
|
272
|
+
domain_part = domain_part.encode("idna").decode("ascii")
|
273
|
+
except UnicodeError:
|
274
|
+
pass
|
275
|
+
else:
|
276
|
+
if self.DOMAIN_REGEX.match(domain_part):
|
277
|
+
return value
|
278
|
+
raise ValidationError(message)
|
279
|
+
|
280
|
+
return value
|
281
|
+
|
282
|
+
|
283
|
+
class Range(Validator):
|
284
|
+
"""Validator which succeeds if the value passed to it is within the specified
|
285
|
+
range. If ``min`` is not specified, or is specified as `None`,
|
286
|
+
no lower bound exists. If ``max`` is not specified, or is specified as `None`,
|
287
|
+
no upper bound exists. The inclusivity of the bounds (if they exist) is configurable.
|
288
|
+
If ``min_inclusive`` is not specified, or is specified as `True`, then
|
289
|
+
the ``min`` bound is included in the range. If ``max_inclusive`` is not specified,
|
290
|
+
or is specified as `True`, then the ``max`` bound is included in the range.
|
291
|
+
|
292
|
+
:param min: The minimum value (lower bound). If not provided, minimum
|
293
|
+
value will not be checked.
|
294
|
+
:param max: The maximum value (upper bound). If not provided, maximum
|
295
|
+
value will not be checked.
|
296
|
+
:param min_inclusive: Whether the `min` bound is included in the range.
|
297
|
+
:param max_inclusive: Whether the `max` bound is included in the range.
|
298
|
+
:param error: Error message to raise in case of a validation error.
|
299
|
+
Can be interpolated with `{input}`, `{min}` and `{max}`.
|
300
|
+
"""
|
301
|
+
|
302
|
+
message_min = "Must be {min_op} {{min}}."
|
303
|
+
message_max = "Must be {max_op} {{max}}."
|
304
|
+
message_all = "Must be {min_op} {{min}} and {max_op} {{max}}."
|
305
|
+
|
306
|
+
message_gte = "greater than or equal to"
|
307
|
+
message_gt = "greater than"
|
308
|
+
message_lte = "less than or equal to"
|
309
|
+
message_lt = "less than"
|
310
|
+
|
311
|
+
def __init__(
|
312
|
+
self,
|
313
|
+
min=None,
|
314
|
+
max=None,
|
315
|
+
*,
|
316
|
+
min_inclusive: bool = True,
|
317
|
+
max_inclusive: bool = True,
|
318
|
+
error: str | None = None,
|
319
|
+
):
|
320
|
+
self.min = min
|
321
|
+
self.max = max
|
322
|
+
self.error = error
|
323
|
+
self.min_inclusive = min_inclusive
|
324
|
+
self.max_inclusive = max_inclusive
|
325
|
+
|
326
|
+
# interpolate messages based on bound inclusivity
|
327
|
+
self.message_min = self.message_min.format(
|
328
|
+
min_op=self.message_gte if self.min_inclusive else self.message_gt
|
329
|
+
)
|
330
|
+
self.message_max = self.message_max.format(
|
331
|
+
max_op=self.message_lte if self.max_inclusive else self.message_lt
|
332
|
+
)
|
333
|
+
self.message_all = self.message_all.format(
|
334
|
+
min_op=self.message_gte if self.min_inclusive else self.message_gt,
|
335
|
+
max_op=self.message_lte if self.max_inclusive else self.message_lt,
|
336
|
+
)
|
337
|
+
|
338
|
+
def _repr_args(self) -> str:
|
339
|
+
return f"min={self.min!r}, max={self.max!r}, min_inclusive={self.min_inclusive!r}, max_inclusive={self.max_inclusive!r}"
|
340
|
+
|
341
|
+
def _format_error(self, value: _T, message: str) -> str:
|
342
|
+
return (self.error or message).format(input=value, min=self.min, max=self.max)
|
343
|
+
|
344
|
+
def __call__(self, value: _T) -> _T:
|
345
|
+
if self.min is not None and (
|
346
|
+
value < self.min if self.min_inclusive else value <= self.min
|
347
|
+
):
|
348
|
+
message = self.message_min if self.max is None else self.message_all
|
349
|
+
raise ValidationError(self._format_error(value, message))
|
350
|
+
|
351
|
+
if self.max is not None and (
|
352
|
+
value > self.max if self.max_inclusive else value >= self.max
|
353
|
+
):
|
354
|
+
message = self.message_max if self.min is None else self.message_all
|
355
|
+
raise ValidationError(self._format_error(value, message))
|
356
|
+
|
357
|
+
return value
|
358
|
+
|
359
|
+
|
360
|
+
class Length(Validator):
|
361
|
+
"""Validator which succeeds if the value passed to it has a
|
362
|
+
length between a minimum and maximum. Uses len(), so it
|
363
|
+
can work for strings, lists, or anything with length.
|
364
|
+
|
365
|
+
:param min: The minimum length. If not provided, minimum length
|
366
|
+
will not be checked.
|
367
|
+
:param max: The maximum length. If not provided, maximum length
|
368
|
+
will not be checked.
|
369
|
+
:param equal: The exact length. If provided, maximum and minimum
|
370
|
+
length will not be checked.
|
371
|
+
:param error: Error message to raise in case of a validation error.
|
372
|
+
Can be interpolated with `{input}`, `{min}` and `{max}`.
|
373
|
+
"""
|
374
|
+
|
375
|
+
message_min = "Shorter than minimum length {min}."
|
376
|
+
message_max = "Longer than maximum length {max}."
|
377
|
+
message_all = "Length must be between {min} and {max}."
|
378
|
+
message_equal = "Length must be {equal}."
|
379
|
+
|
380
|
+
def __init__(
|
381
|
+
self,
|
382
|
+
min: int | None = None,
|
383
|
+
max: int | None = None,
|
384
|
+
*,
|
385
|
+
equal: int | None = None,
|
386
|
+
error: str | None = None,
|
387
|
+
):
|
388
|
+
if equal is not None and any([min, max]):
|
389
|
+
raise ValueError(
|
390
|
+
"The `equal` parameter was provided, maximum or "
|
391
|
+
"minimum parameter must not be provided."
|
392
|
+
)
|
393
|
+
|
394
|
+
self.min = min
|
395
|
+
self.max = max
|
396
|
+
self.error = error
|
397
|
+
self.equal = equal
|
398
|
+
|
399
|
+
def _repr_args(self) -> str:
|
400
|
+
return f"min={self.min!r}, max={self.max!r}, equal={self.equal!r}"
|
401
|
+
|
402
|
+
def _format_error(self, value: typing.Sized, message: str) -> str:
|
403
|
+
return (self.error or message).format(
|
404
|
+
input=value, min=self.min, max=self.max, equal=self.equal
|
405
|
+
)
|
406
|
+
|
407
|
+
def __call__(self, value: typing.Sized) -> typing.Sized:
|
408
|
+
length = len(value)
|
409
|
+
|
410
|
+
if self.equal is not None:
|
411
|
+
if length != self.equal:
|
412
|
+
raise ValidationError(self._format_error(value, self.message_equal))
|
413
|
+
return value
|
414
|
+
|
415
|
+
if self.min is not None and length < self.min:
|
416
|
+
message = self.message_min if self.max is None else self.message_all
|
417
|
+
raise ValidationError(self._format_error(value, message))
|
418
|
+
|
419
|
+
if self.max is not None and length > self.max:
|
420
|
+
message = self.message_max if self.min is None else self.message_all
|
421
|
+
raise ValidationError(self._format_error(value, message))
|
422
|
+
|
423
|
+
return value
|
424
|
+
|
425
|
+
|
426
|
+
class Equal(Validator):
|
427
|
+
"""Validator which succeeds if the ``value`` passed to it is
|
428
|
+
equal to ``comparable``.
|
429
|
+
|
430
|
+
:param comparable: The object to compare to.
|
431
|
+
:param error: Error message to raise in case of a validation error.
|
432
|
+
Can be interpolated with `{input}` and `{other}`.
|
433
|
+
"""
|
434
|
+
|
435
|
+
default_message = "Must be equal to {other}."
|
436
|
+
|
437
|
+
def __init__(self, comparable, *, error: str | None = None):
|
438
|
+
self.comparable = comparable
|
439
|
+
self.error = error or self.default_message # type: str
|
440
|
+
|
441
|
+
def _repr_args(self) -> str:
|
442
|
+
return f"comparable={self.comparable!r}"
|
443
|
+
|
444
|
+
def _format_error(self, value: _T) -> str:
|
445
|
+
return self.error.format(input=value, other=self.comparable)
|
446
|
+
|
447
|
+
def __call__(self, value: _T) -> _T:
|
448
|
+
if value != self.comparable:
|
449
|
+
raise ValidationError(self._format_error(value))
|
450
|
+
return value
|
451
|
+
|
452
|
+
|
453
|
+
class Regexp(Validator):
|
454
|
+
"""Validator which succeeds if the ``value`` matches ``regex``.
|
455
|
+
|
456
|
+
.. note::
|
457
|
+
|
458
|
+
Uses `re.match`, which searches for a match at the beginning of a string.
|
459
|
+
|
460
|
+
:param regex: The regular expression string to use. Can also be a compiled
|
461
|
+
regular expression pattern.
|
462
|
+
:param flags: The regexp flags to use, for example re.IGNORECASE. Ignored
|
463
|
+
if ``regex`` is not a string.
|
464
|
+
:param error: Error message to raise in case of a validation error.
|
465
|
+
Can be interpolated with `{input}` and `{regex}`.
|
466
|
+
"""
|
467
|
+
|
468
|
+
default_message = "String does not match expected pattern."
|
469
|
+
|
470
|
+
def __init__(
|
471
|
+
self,
|
472
|
+
regex: str | bytes | typing.Pattern,
|
473
|
+
flags: int = 0,
|
474
|
+
*,
|
475
|
+
error: str | None = None,
|
476
|
+
):
|
477
|
+
self.regex = (
|
478
|
+
re.compile(regex, flags) if isinstance(regex, (str, bytes)) else regex
|
479
|
+
)
|
480
|
+
self.error = error or self.default_message # type: str
|
481
|
+
|
482
|
+
def _repr_args(self) -> str:
|
483
|
+
return f"regex={self.regex!r}"
|
484
|
+
|
485
|
+
def _format_error(self, value: str | bytes) -> str:
|
486
|
+
return self.error.format(input=value, regex=self.regex.pattern)
|
487
|
+
|
488
|
+
@typing.overload
|
489
|
+
def __call__(self, value: str) -> str: ...
|
490
|
+
|
491
|
+
@typing.overload
|
492
|
+
def __call__(self, value: bytes) -> bytes: ...
|
493
|
+
|
494
|
+
def __call__(self, value):
|
495
|
+
if self.regex.match(value) is None:
|
496
|
+
raise ValidationError(self._format_error(value))
|
497
|
+
|
498
|
+
return value
|
499
|
+
|
500
|
+
|
501
|
+
class Predicate(Validator):
|
502
|
+
"""Call the specified ``method`` of the ``value`` object. The
|
503
|
+
validator succeeds if the invoked method returns an object that
|
504
|
+
evaluates to True in a Boolean context. Any additional keyword
|
505
|
+
argument will be passed to the method.
|
506
|
+
|
507
|
+
:param method: The name of the method to invoke.
|
508
|
+
:param error: Error message to raise in case of a validation error.
|
509
|
+
Can be interpolated with `{input}` and `{method}`.
|
510
|
+
:param kwargs: Additional keyword arguments to pass to the method.
|
511
|
+
"""
|
512
|
+
|
513
|
+
default_message = "Invalid input."
|
514
|
+
|
515
|
+
def __init__(self, method: str, *, error: str | None = None, **kwargs):
|
516
|
+
self.method = method
|
517
|
+
self.error = error or self.default_message # type: str
|
518
|
+
self.kwargs = kwargs
|
519
|
+
|
520
|
+
def _repr_args(self) -> str:
|
521
|
+
return f"method={self.method!r}, kwargs={self.kwargs!r}"
|
522
|
+
|
523
|
+
def _format_error(self, value: typing.Any) -> str:
|
524
|
+
return self.error.format(input=value, method=self.method)
|
525
|
+
|
526
|
+
def __call__(self, value: typing.Any) -> typing.Any:
|
527
|
+
method = getattr(value, self.method)
|
528
|
+
|
529
|
+
if not method(**self.kwargs):
|
530
|
+
raise ValidationError(self._format_error(value))
|
531
|
+
|
532
|
+
return value
|
533
|
+
|
534
|
+
|
535
|
+
class NoneOf(Validator):
|
536
|
+
"""Validator which fails if ``value`` is a member of ``iterable``.
|
537
|
+
|
538
|
+
:param iterable: A sequence of invalid values.
|
539
|
+
:param error: Error message to raise in case of a validation error. Can be
|
540
|
+
interpolated using `{input}` and `{values}`.
|
541
|
+
"""
|
542
|
+
|
543
|
+
default_message = "Invalid input."
|
544
|
+
|
545
|
+
def __init__(self, iterable: typing.Iterable, *, error: str | None = None):
|
546
|
+
self.iterable = iterable
|
547
|
+
self.values_text = ", ".join(str(each) for each in self.iterable)
|
548
|
+
self.error = error or self.default_message # type: str
|
549
|
+
|
550
|
+
def _repr_args(self) -> str:
|
551
|
+
return f"iterable={self.iterable!r}"
|
552
|
+
|
553
|
+
def _format_error(self, value) -> str:
|
554
|
+
return self.error.format(input=value, values=self.values_text)
|
555
|
+
|
556
|
+
def __call__(self, value: typing.Any) -> typing.Any:
|
557
|
+
try:
|
558
|
+
if value in self.iterable:
|
559
|
+
raise ValidationError(self._format_error(value))
|
560
|
+
except TypeError:
|
561
|
+
pass
|
562
|
+
|
563
|
+
return value
|
564
|
+
|
565
|
+
|
566
|
+
class OneOf(Validator):
|
567
|
+
"""Validator which succeeds if ``value`` is a member of ``choices``.
|
568
|
+
|
569
|
+
:param choices: A sequence of valid values.
|
570
|
+
:param labels: Optional sequence of labels to pair with the choices.
|
571
|
+
:param error: Error message to raise in case of a validation error. Can be
|
572
|
+
interpolated with `{input}`, `{choices}` and `{labels}`.
|
573
|
+
"""
|
574
|
+
|
575
|
+
default_message = "Must be one of: {choices}."
|
576
|
+
|
577
|
+
def __init__(
|
578
|
+
self,
|
579
|
+
choices: typing.Iterable,
|
580
|
+
labels: typing.Iterable[str] | None = None,
|
581
|
+
*,
|
582
|
+
error: str | None = None,
|
583
|
+
):
|
584
|
+
self.choices = choices
|
585
|
+
self.choices_text = ", ".join(str(choice) for choice in self.choices)
|
586
|
+
self.labels = labels if labels is not None else []
|
587
|
+
self.labels_text = ", ".join(str(label) for label in self.labels)
|
588
|
+
self.error = error or self.default_message # type: str
|
589
|
+
|
590
|
+
def _repr_args(self) -> str:
|
591
|
+
return f"choices={self.choices!r}, labels={self.labels!r}"
|
592
|
+
|
593
|
+
def _format_error(self, value) -> str:
|
594
|
+
return self.error.format(
|
595
|
+
input=value, choices=self.choices_text, labels=self.labels_text
|
596
|
+
)
|
597
|
+
|
598
|
+
def __call__(self, value: typing.Any) -> typing.Any:
|
599
|
+
try:
|
600
|
+
if value not in self.choices:
|
601
|
+
raise ValidationError(self._format_error(value))
|
602
|
+
except TypeError as error:
|
603
|
+
raise ValidationError(self._format_error(value)) from error
|
604
|
+
|
605
|
+
return value
|
606
|
+
|
607
|
+
def options(
|
608
|
+
self,
|
609
|
+
valuegetter: str | typing.Callable[[typing.Any], typing.Any] = str,
|
610
|
+
) -> typing.Iterable[tuple[typing.Any, str]]:
|
611
|
+
"""Return a generator over the (value, label) pairs, where value
|
612
|
+
is a string associated with each choice. This convenience method
|
613
|
+
is useful to populate, for instance, a form select field.
|
614
|
+
|
615
|
+
:param valuegetter: Can be a callable or a string. In the former case, it must
|
616
|
+
be a one-argument callable which returns the value of a
|
617
|
+
choice. In the latter case, the string specifies the name
|
618
|
+
of an attribute of the choice objects. Defaults to `str()`
|
619
|
+
or `str()`.
|
620
|
+
"""
|
621
|
+
valuegetter = valuegetter if callable(valuegetter) else attrgetter(valuegetter)
|
622
|
+
pairs = zip_longest(self.choices, self.labels, fillvalue="")
|
623
|
+
|
624
|
+
return ((valuegetter(choice), label) for choice, label in pairs)
|
625
|
+
|
626
|
+
|
627
|
+
class ContainsOnly(OneOf):
|
628
|
+
"""Validator which succeeds if ``value`` is a sequence and each element
|
629
|
+
in the sequence is also in the sequence passed as ``choices``. Empty input
|
630
|
+
is considered valid.
|
631
|
+
|
632
|
+
:param iterable choices: Same as :class:`OneOf`.
|
633
|
+
:param iterable labels: Same as :class:`OneOf`.
|
634
|
+
:param str error: Same as :class:`OneOf`.
|
635
|
+
|
636
|
+
.. versionchanged:: 3.0.0b2
|
637
|
+
Duplicate values are considered valid.
|
638
|
+
.. versionchanged:: 3.0.0b2
|
639
|
+
Empty input is considered valid. Use `validate.Length(min=1) <marshmallow.validate.Length>`
|
640
|
+
to validate against empty inputs.
|
641
|
+
"""
|
642
|
+
|
643
|
+
default_message = "One or more of the choices you made was not in: {choices}."
|
644
|
+
|
645
|
+
def _format_error(self, value) -> str:
|
646
|
+
value_text = ", ".join(str(val) for val in value)
|
647
|
+
return super()._format_error(value_text)
|
648
|
+
|
649
|
+
def __call__(self, value: typing.Sequence[_T]) -> typing.Sequence[_T]:
|
650
|
+
# We can't use set.issubset because does not handle unhashable types
|
651
|
+
for val in value:
|
652
|
+
if val not in self.choices:
|
653
|
+
raise ValidationError(self._format_error(value))
|
654
|
+
return value
|
655
|
+
|
656
|
+
|
657
|
+
class ContainsNoneOf(NoneOf):
|
658
|
+
"""Validator which fails if ``value`` is a sequence and any element
|
659
|
+
in the sequence is a member of the sequence passed as ``iterable``. Empty input
|
660
|
+
is considered valid.
|
661
|
+
|
662
|
+
:param iterable iterable: Same as :class:`NoneOf`.
|
663
|
+
:param str error: Same as :class:`NoneOf`.
|
664
|
+
|
665
|
+
.. versionadded:: 3.6.0
|
666
|
+
"""
|
667
|
+
|
668
|
+
default_message = "One or more of the choices you made was in: {values}."
|
669
|
+
|
670
|
+
def _format_error(self, value) -> str:
|
671
|
+
value_text = ", ".join(str(val) for val in value)
|
672
|
+
return super()._format_error(value_text)
|
673
|
+
|
674
|
+
def __call__(self, value: typing.Sequence[_T]) -> typing.Sequence[_T]:
|
675
|
+
for val in value:
|
676
|
+
if val in self.iterable:
|
677
|
+
raise ValidationError(self._format_error(value))
|
678
|
+
return value
|