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,1047 @@
|
|
1
|
+
"""
|
2
|
+
This library allows the conversion of python 3.7's :mod:`dataclasses`
|
3
|
+
to :mod:`marshmallow` schemas.
|
4
|
+
|
5
|
+
It takes a python class, and generates a marshmallow schema for it.
|
6
|
+
|
7
|
+
Simple example::
|
8
|
+
|
9
|
+
from marshmallow import Schema
|
10
|
+
from marshmallow_dataclass import dataclass
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class Point:
|
14
|
+
x:float
|
15
|
+
y:float
|
16
|
+
|
17
|
+
point = Point(x=0, y=0)
|
18
|
+
point_json = Point.Schema().dumps(point)
|
19
|
+
|
20
|
+
Full example::
|
21
|
+
|
22
|
+
from marshmallow import Schema
|
23
|
+
from dataclasses import field
|
24
|
+
from marshmallow_dataclass import dataclass
|
25
|
+
import datetime
|
26
|
+
|
27
|
+
@dataclass
|
28
|
+
class User:
|
29
|
+
birth: datetime.date = field(metadata= {
|
30
|
+
"required": True # A parameter to pass to marshmallow's field
|
31
|
+
})
|
32
|
+
website:str = field(metadata = {
|
33
|
+
"marshmallow_field": marshmallow.fields.Url() # Custom marshmallow field
|
34
|
+
})
|
35
|
+
Schema: ClassVar[Type[Schema]] = Schema # For the type checker
|
36
|
+
"""
|
37
|
+
|
38
|
+
import collections.abc
|
39
|
+
import dataclasses
|
40
|
+
import inspect
|
41
|
+
import sys
|
42
|
+
import threading
|
43
|
+
import types
|
44
|
+
import warnings
|
45
|
+
from enum import Enum
|
46
|
+
from functools import lru_cache, partial
|
47
|
+
from typing import (
|
48
|
+
Any,
|
49
|
+
Callable,
|
50
|
+
Dict,
|
51
|
+
FrozenSet,
|
52
|
+
Generic,
|
53
|
+
List,
|
54
|
+
Mapping,
|
55
|
+
NewType as typing_NewType,
|
56
|
+
Optional,
|
57
|
+
Sequence,
|
58
|
+
Set,
|
59
|
+
Tuple,
|
60
|
+
Type,
|
61
|
+
TypeVar,
|
62
|
+
Union,
|
63
|
+
cast,
|
64
|
+
get_type_hints,
|
65
|
+
overload,
|
66
|
+
)
|
67
|
+
|
68
|
+
from .. import marshmallow
|
69
|
+
from ..typing_extensions import *
|
70
|
+
from ..typing_inspect import *
|
71
|
+
|
72
|
+
from .lazy_class_attribute import lazy_class_attribute
|
73
|
+
|
74
|
+
if sys.version_info >= (3, 9):
|
75
|
+
from typing import Annotated
|
76
|
+
else:
|
77
|
+
from ..typing_extensions import Annotated
|
78
|
+
|
79
|
+
if sys.version_info >= (3, 11):
|
80
|
+
from typing import dataclass_transform
|
81
|
+
else:
|
82
|
+
from ..typing_extensions import dataclass_transform
|
83
|
+
|
84
|
+
|
85
|
+
__all__ = ["dataclass", "add_schema", "class_schema", "field_for_schema", "NewType"]
|
86
|
+
|
87
|
+
NoneType = type(None)
|
88
|
+
_U = TypeVar("_U")
|
89
|
+
|
90
|
+
# Whitelist of dataclass members that will be copied to generated schema.
|
91
|
+
MEMBERS_WHITELIST: Set[str] = {"Meta"}
|
92
|
+
|
93
|
+
# Max number of generated schemas that class_schema keeps of generated schemas. Removes duplicates.
|
94
|
+
MAX_CLASS_SCHEMA_CACHE_SIZE = 1024
|
95
|
+
|
96
|
+
|
97
|
+
def _maybe_get_callers_frame(
|
98
|
+
cls: type, stacklevel: int = 1
|
99
|
+
) -> Optional[types.FrameType]:
|
100
|
+
"""Return the caller's frame, but only if it will help resolve forward type references.
|
101
|
+
|
102
|
+
We sometimes need the caller's frame to get access to the caller's
|
103
|
+
local namespace in order to be able to resolve forward type
|
104
|
+
references in dataclasses.
|
105
|
+
|
106
|
+
Notes
|
107
|
+
-----
|
108
|
+
|
109
|
+
If the caller's locals are the same as the dataclass' module
|
110
|
+
globals — this is the case for the common case of dataclasses
|
111
|
+
defined at the module top-level — we don't need the locals.
|
112
|
+
(Typing.get_type_hints() knows how to check the class module
|
113
|
+
globals on its own.)
|
114
|
+
|
115
|
+
In that case, we don't need the caller's frame. Not holding a
|
116
|
+
reference to the frame in our our lazy ``.Scheme`` class attribute
|
117
|
+
is a significant win, memory-wise.
|
118
|
+
|
119
|
+
"""
|
120
|
+
try:
|
121
|
+
frame = inspect.currentframe()
|
122
|
+
for _ in range(stacklevel + 1):
|
123
|
+
if frame is None:
|
124
|
+
return None
|
125
|
+
frame = frame.f_back
|
126
|
+
|
127
|
+
if frame is None:
|
128
|
+
return None
|
129
|
+
|
130
|
+
globalns = getattr(sys.modules.get(cls.__module__), "__dict__", None)
|
131
|
+
if frame.f_locals is globalns:
|
132
|
+
# Locals are the globals
|
133
|
+
return None
|
134
|
+
|
135
|
+
return frame
|
136
|
+
|
137
|
+
finally:
|
138
|
+
# Paranoia, per https://docs.python.org/3/library/inspect.html#the-interpreter-stack
|
139
|
+
del frame
|
140
|
+
|
141
|
+
|
142
|
+
@overload
|
143
|
+
def dataclass(
|
144
|
+
_cls: Type[_U],
|
145
|
+
*,
|
146
|
+
repr: bool = True,
|
147
|
+
eq: bool = True,
|
148
|
+
order: bool = False,
|
149
|
+
unsafe_hash: bool = False,
|
150
|
+
frozen: bool = False,
|
151
|
+
base_schema: Optional[Type[marshmallow.Schema]] = None,
|
152
|
+
cls_frame: Optional[types.FrameType] = None,
|
153
|
+
) -> Type[_U]:
|
154
|
+
...
|
155
|
+
|
156
|
+
|
157
|
+
@overload
|
158
|
+
def dataclass(
|
159
|
+
*,
|
160
|
+
repr: bool = True,
|
161
|
+
eq: bool = True,
|
162
|
+
order: bool = False,
|
163
|
+
unsafe_hash: bool = False,
|
164
|
+
frozen: bool = False,
|
165
|
+
base_schema: Optional[Type[marshmallow.Schema]] = None,
|
166
|
+
cls_frame: Optional[types.FrameType] = None,
|
167
|
+
) -> Callable[[Type[_U]], Type[_U]]:
|
168
|
+
...
|
169
|
+
|
170
|
+
|
171
|
+
# _cls should never be specified by keyword, so start it with an
|
172
|
+
# underscore. The presence of _cls is used to detect if this
|
173
|
+
# decorator is being called with parameters or not.
|
174
|
+
@dataclass_transform(field_specifiers=(dataclasses.Field, dataclasses.field))
|
175
|
+
def dataclass(
|
176
|
+
_cls: Optional[Type[_U]] = None,
|
177
|
+
*,
|
178
|
+
repr: bool = True,
|
179
|
+
eq: bool = True,
|
180
|
+
order: bool = False,
|
181
|
+
unsafe_hash: bool = False,
|
182
|
+
frozen: bool = False,
|
183
|
+
base_schema: Optional[Type[marshmallow.Schema]] = None,
|
184
|
+
cls_frame: Optional[types.FrameType] = None,
|
185
|
+
stacklevel: int = 1,
|
186
|
+
) -> Union[Type[_U], Callable[[Type[_U]], Type[_U]]]:
|
187
|
+
"""
|
188
|
+
This decorator does the same as dataclasses.dataclass, but also applies :func:`add_schema`.
|
189
|
+
It adds a `.Schema` attribute to the class object
|
190
|
+
|
191
|
+
:param base_schema: marshmallow schema used as a base class when deriving dataclass schema
|
192
|
+
:param cls_frame: frame of cls definition, used to obtain locals with other classes definitions.
|
193
|
+
If None is passed the caller frame will be treated as cls_frame
|
194
|
+
|
195
|
+
>>> @dataclass
|
196
|
+
... class Artist:
|
197
|
+
... name: str
|
198
|
+
>>> Artist.Schema
|
199
|
+
<class 'marshmallow.schema.Artist'>
|
200
|
+
|
201
|
+
>>> from typing import ClassVar
|
202
|
+
>>> from marshmallow import Schema
|
203
|
+
>>> @dataclass(order=True) # preserve field order
|
204
|
+
... class Point:
|
205
|
+
... x:float
|
206
|
+
... y:float
|
207
|
+
... Schema: ClassVar[Type[Schema]] = Schema # For the type checker
|
208
|
+
...
|
209
|
+
>>> Point.Schema().load({'x':0, 'y':0}) # This line can be statically type checked
|
210
|
+
Point(x=0.0, y=0.0)
|
211
|
+
"""
|
212
|
+
dc = dataclasses.dataclass(
|
213
|
+
repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen
|
214
|
+
)
|
215
|
+
|
216
|
+
def decorator(cls: Type[_U], stacklevel: int = 1) -> Type[_U]:
|
217
|
+
return add_schema(
|
218
|
+
dc(cls), base_schema, cls_frame=cls_frame, stacklevel=stacklevel + 1
|
219
|
+
)
|
220
|
+
|
221
|
+
if _cls is None:
|
222
|
+
return decorator
|
223
|
+
return decorator(_cls, stacklevel=stacklevel + 1)
|
224
|
+
|
225
|
+
|
226
|
+
@overload
|
227
|
+
def add_schema(_cls: Type[_U]) -> Type[_U]:
|
228
|
+
...
|
229
|
+
|
230
|
+
|
231
|
+
@overload
|
232
|
+
def add_schema(
|
233
|
+
base_schema: Optional[Type[marshmallow.Schema]] = None,
|
234
|
+
) -> Callable[[Type[_U]], Type[_U]]:
|
235
|
+
...
|
236
|
+
|
237
|
+
|
238
|
+
@overload
|
239
|
+
def add_schema(
|
240
|
+
_cls: Type[_U],
|
241
|
+
base_schema: Optional[Type[marshmallow.Schema]] = None,
|
242
|
+
cls_frame: Optional[types.FrameType] = None,
|
243
|
+
stacklevel: int = 1,
|
244
|
+
) -> Type[_U]:
|
245
|
+
...
|
246
|
+
|
247
|
+
|
248
|
+
def add_schema(_cls=None, base_schema=None, cls_frame=None, stacklevel=1):
|
249
|
+
"""
|
250
|
+
This decorator adds a marshmallow schema as the 'Schema' attribute in a dataclass.
|
251
|
+
It uses :func:`class_schema` internally.
|
252
|
+
|
253
|
+
:param type _cls: The dataclass to which a Schema should be added
|
254
|
+
:param base_schema: marshmallow schema used as a base class when deriving dataclass schema
|
255
|
+
:param cls_frame: frame of cls definition
|
256
|
+
|
257
|
+
>>> class BaseSchema(marshmallow.Schema):
|
258
|
+
... def on_bind_field(self, field_name, field_obj):
|
259
|
+
... field_obj.data_key = (field_obj.data_key or field_name).upper()
|
260
|
+
|
261
|
+
>>> @add_schema(base_schema=BaseSchema)
|
262
|
+
... @dataclasses.dataclass
|
263
|
+
... class Artist:
|
264
|
+
... names: Tuple[str, str]
|
265
|
+
>>> artist = Artist.Schema().loads('{"NAMES": ["Martin", "Ramirez"]}')
|
266
|
+
>>> artist
|
267
|
+
Artist(names=('Martin', 'Ramirez'))
|
268
|
+
"""
|
269
|
+
|
270
|
+
def decorator(clazz: Type[_U], stacklevel: int = stacklevel) -> Type[_U]:
|
271
|
+
if cls_frame is not None:
|
272
|
+
frame = cls_frame
|
273
|
+
else:
|
274
|
+
frame = _maybe_get_callers_frame(clazz, stacklevel=stacklevel)
|
275
|
+
|
276
|
+
# noinspection PyTypeHints
|
277
|
+
clazz.Schema = lazy_class_attribute( # type: ignore
|
278
|
+
partial(class_schema, clazz, base_schema, frame),
|
279
|
+
"Schema",
|
280
|
+
clazz.__name__,
|
281
|
+
)
|
282
|
+
return clazz
|
283
|
+
|
284
|
+
if _cls is None:
|
285
|
+
return decorator
|
286
|
+
return decorator(_cls, stacklevel=stacklevel + 1)
|
287
|
+
|
288
|
+
|
289
|
+
@overload
|
290
|
+
def class_schema(
|
291
|
+
clazz: type,
|
292
|
+
base_schema: Optional[Type[marshmallow.Schema]] = None,
|
293
|
+
*,
|
294
|
+
globalns: Optional[Dict[str, Any]] = None,
|
295
|
+
localns: Optional[Dict[str, Any]] = None,
|
296
|
+
) -> Type[marshmallow.Schema]:
|
297
|
+
...
|
298
|
+
|
299
|
+
|
300
|
+
@overload
|
301
|
+
def class_schema(
|
302
|
+
clazz: type,
|
303
|
+
base_schema: Optional[Type[marshmallow.Schema]] = None,
|
304
|
+
clazz_frame: Optional[types.FrameType] = None,
|
305
|
+
*,
|
306
|
+
globalns: Optional[Dict[str, Any]] = None,
|
307
|
+
) -> Type[marshmallow.Schema]:
|
308
|
+
...
|
309
|
+
|
310
|
+
|
311
|
+
def class_schema(
|
312
|
+
clazz: type,
|
313
|
+
base_schema: Optional[Type[marshmallow.Schema]] = None,
|
314
|
+
# FIXME: delete clazz_frame from API?
|
315
|
+
clazz_frame: Optional[types.FrameType] = None,
|
316
|
+
*,
|
317
|
+
globalns: Optional[Dict[str, Any]] = None,
|
318
|
+
localns: Optional[Dict[str, Any]] = None,
|
319
|
+
) -> Type[marshmallow.Schema]:
|
320
|
+
"""
|
321
|
+
Convert a class to a marshmallow schema
|
322
|
+
|
323
|
+
:param clazz: A python class (may be a dataclass)
|
324
|
+
:param base_schema: marshmallow schema used as a base class when deriving dataclass schema
|
325
|
+
:param clazz_frame: frame of cls definition
|
326
|
+
:return: A marshmallow Schema corresponding to the dataclass
|
327
|
+
|
328
|
+
.. note::
|
329
|
+
All the arguments supported by marshmallow field classes can
|
330
|
+
be passed in the `metadata` dictionary of a field.
|
331
|
+
|
332
|
+
|
333
|
+
If you want to use a custom marshmallow field
|
334
|
+
(one that has no equivalent python type), you can pass it as the
|
335
|
+
``marshmallow_field`` key in the metadata dictionary.
|
336
|
+
|
337
|
+
>>> import typing
|
338
|
+
>>> Meters = typing.NewType('Meters', float)
|
339
|
+
>>> @dataclasses.dataclass()
|
340
|
+
... class Building:
|
341
|
+
... height: Optional[Meters]
|
342
|
+
... name: str = dataclasses.field(default="anonymous")
|
343
|
+
... class Meta:
|
344
|
+
... ordered = True
|
345
|
+
...
|
346
|
+
>>> class_schema(Building) # Returns a marshmallow schema class (not an instance)
|
347
|
+
<class 'marshmallow.schema.Building'>
|
348
|
+
>>> @dataclasses.dataclass()
|
349
|
+
... class City:
|
350
|
+
... name: str = dataclasses.field(metadata={'required':True})
|
351
|
+
... best_building: Building # Reference to another dataclass. A schema will be created for it too.
|
352
|
+
... other_buildings: List[Building] = dataclasses.field(default_factory=lambda: [])
|
353
|
+
...
|
354
|
+
>>> citySchema = class_schema(City)()
|
355
|
+
>>> city = citySchema.load({"name":"Paris", "best_building": {"name": "Eiffel Tower"}})
|
356
|
+
>>> city
|
357
|
+
City(name='Paris', best_building=Building(height=None, name='Eiffel Tower'), other_buildings=[])
|
358
|
+
|
359
|
+
>>> citySchema.load({"name":"Paris"})
|
360
|
+
Traceback (most recent call last):
|
361
|
+
...
|
362
|
+
marshmallow.exceptions.ValidationError: {'best_building': ['Missing data for required field.']}
|
363
|
+
|
364
|
+
>>> city_json = citySchema.dump(city)
|
365
|
+
>>> city_json['best_building'] # We get an OrderedDict because we specified order = True in the Meta class
|
366
|
+
OrderedDict([('height', None), ('name', 'Eiffel Tower')])
|
367
|
+
|
368
|
+
>>> @dataclasses.dataclass()
|
369
|
+
... class Person:
|
370
|
+
... name: str = dataclasses.field(default="Anonymous")
|
371
|
+
... friends: List['Person'] = dataclasses.field(default_factory=lambda:[]) # Recursive field
|
372
|
+
...
|
373
|
+
>>> person = class_schema(Person)().load({
|
374
|
+
... "friends": [{"name": "Roger Boucher"}]
|
375
|
+
... })
|
376
|
+
>>> person
|
377
|
+
Person(name='Anonymous', friends=[Person(name='Roger Boucher', friends=[])])
|
378
|
+
|
379
|
+
Marking dataclass fields as non-initialized (``init=False``), by default, will result in those
|
380
|
+
fields from being exluded in the schema. To override this behaviour, set the ``Meta`` option
|
381
|
+
``include_non_init=True``.
|
382
|
+
|
383
|
+
>>> @dataclasses.dataclass()
|
384
|
+
... class C:
|
385
|
+
... important: int = dataclasses.field(init=True, default=0)
|
386
|
+
... # Only fields that are in the __init__ method will be added:
|
387
|
+
... unimportant: int = dataclasses.field(init=False, default=0)
|
388
|
+
...
|
389
|
+
>>> c = class_schema(C)().load({
|
390
|
+
... "important": 9, # This field will be imported
|
391
|
+
... "unimportant": 9 # This field will NOT be imported
|
392
|
+
... }, unknown=marshmallow.EXCLUDE)
|
393
|
+
>>> c
|
394
|
+
C(important=9, unimportant=0)
|
395
|
+
|
396
|
+
>>> @dataclasses.dataclass()
|
397
|
+
... class C:
|
398
|
+
... class Meta:
|
399
|
+
... include_non_init = True
|
400
|
+
... important: int = dataclasses.field(init=True, default=0)
|
401
|
+
... unimportant: int = dataclasses.field(init=False, default=0)
|
402
|
+
...
|
403
|
+
>>> c = class_schema(C)().load({
|
404
|
+
... "important": 9, # This field will be imported
|
405
|
+
... "unimportant": 9 # This field will be imported
|
406
|
+
... }, unknown=marshmallow.EXCLUDE)
|
407
|
+
>>> c
|
408
|
+
C(important=9, unimportant=9)
|
409
|
+
|
410
|
+
>>> @dataclasses.dataclass
|
411
|
+
... class Website:
|
412
|
+
... url:str = dataclasses.field(metadata = {
|
413
|
+
... "marshmallow_field": marshmallow.fields.Url() # Custom marshmallow field
|
414
|
+
... })
|
415
|
+
...
|
416
|
+
>>> class_schema(Website)().load({"url": "I am not a good URL !"})
|
417
|
+
Traceback (most recent call last):
|
418
|
+
...
|
419
|
+
marshmallow.exceptions.ValidationError: {'url': ['Not a valid URL.']}
|
420
|
+
|
421
|
+
>>> @dataclasses.dataclass
|
422
|
+
... class NeverValid:
|
423
|
+
... @marshmallow.validates_schema
|
424
|
+
... def validate(self, data, **_):
|
425
|
+
... raise marshmallow.ValidationError('never valid')
|
426
|
+
...
|
427
|
+
>>> class_schema(NeverValid)().load({})
|
428
|
+
Traceback (most recent call last):
|
429
|
+
...
|
430
|
+
marshmallow.exceptions.ValidationError: {'_schema': ['never valid']}
|
431
|
+
|
432
|
+
>>> @dataclasses.dataclass
|
433
|
+
... class Anything:
|
434
|
+
... name: str
|
435
|
+
... @marshmallow.validates('name')
|
436
|
+
... def validates(self, value):
|
437
|
+
... if len(value) > 5: raise marshmallow.ValidationError("Name too long")
|
438
|
+
>>> class_schema(Anything)().load({"name": "aaaaaargh"})
|
439
|
+
Traceback (most recent call last):
|
440
|
+
...
|
441
|
+
marshmallow.exceptions.ValidationError: {'name': ['Name too long']}
|
442
|
+
|
443
|
+
You can use the ``metadata`` argument to override default field behaviour, e.g. the fact that
|
444
|
+
``Optional`` fields allow ``None`` values:
|
445
|
+
|
446
|
+
>>> @dataclasses.dataclass
|
447
|
+
... class Custom:
|
448
|
+
... name: Optional[str] = dataclasses.field(metadata={"allow_none": False})
|
449
|
+
>>> class_schema(Custom)().load({"name": None})
|
450
|
+
Traceback (most recent call last):
|
451
|
+
...
|
452
|
+
marshmallow.exceptions.ValidationError: {'name': ['Field may not be null.']}
|
453
|
+
>>> class_schema(Custom)().load({})
|
454
|
+
Custom(name=None)
|
455
|
+
"""
|
456
|
+
if not dataclasses.is_dataclass(clazz):
|
457
|
+
clazz = dataclasses.dataclass(clazz)
|
458
|
+
if localns is None:
|
459
|
+
if clazz_frame is None:
|
460
|
+
clazz_frame = _maybe_get_callers_frame(clazz)
|
461
|
+
if clazz_frame is not None:
|
462
|
+
localns = clazz_frame.f_locals
|
463
|
+
with _SchemaContext(globalns, localns):
|
464
|
+
return _internal_class_schema(clazz, base_schema)
|
465
|
+
|
466
|
+
|
467
|
+
class _SchemaContext:
|
468
|
+
"""Global context for an invocation of class_schema."""
|
469
|
+
|
470
|
+
def __init__(
|
471
|
+
self,
|
472
|
+
globalns: Optional[Dict[str, Any]] = None,
|
473
|
+
localns: Optional[Dict[str, Any]] = None,
|
474
|
+
):
|
475
|
+
self.seen_classes: Dict[type, str] = {}
|
476
|
+
self.globalns = globalns
|
477
|
+
self.localns = localns
|
478
|
+
|
479
|
+
def __enter__(self) -> "_SchemaContext":
|
480
|
+
_schema_ctx_stack.push(self)
|
481
|
+
return self
|
482
|
+
|
483
|
+
def __exit__(
|
484
|
+
self,
|
485
|
+
_typ: Optional[Type[BaseException]],
|
486
|
+
_value: Optional[BaseException],
|
487
|
+
_tb: Optional[types.TracebackType],
|
488
|
+
) -> None:
|
489
|
+
_schema_ctx_stack.pop()
|
490
|
+
|
491
|
+
|
492
|
+
class _LocalStack(threading.local, Generic[_U]):
|
493
|
+
def __init__(self) -> None:
|
494
|
+
self.stack: List[_U] = []
|
495
|
+
|
496
|
+
def push(self, value: _U) -> None:
|
497
|
+
self.stack.append(value)
|
498
|
+
|
499
|
+
def pop(self) -> None:
|
500
|
+
self.stack.pop()
|
501
|
+
|
502
|
+
@property
|
503
|
+
def top(self) -> _U:
|
504
|
+
return self.stack[-1]
|
505
|
+
|
506
|
+
|
507
|
+
_schema_ctx_stack = _LocalStack[_SchemaContext]()
|
508
|
+
|
509
|
+
|
510
|
+
@lru_cache(maxsize=MAX_CLASS_SCHEMA_CACHE_SIZE)
|
511
|
+
def _internal_class_schema(
|
512
|
+
clazz: type,
|
513
|
+
base_schema: Optional[Type[marshmallow.Schema]] = None,
|
514
|
+
) -> Type[marshmallow.Schema]:
|
515
|
+
schema_ctx = _schema_ctx_stack.top
|
516
|
+
|
517
|
+
if typing_extensions.get_origin(clazz) is Annotated and sys.version_info < (3, 10):
|
518
|
+
# https://github.com/python/cpython/blob/3.10/Lib/typing.py#L977
|
519
|
+
class_name = clazz._name or clazz.__origin__.__name__ # type: ignore[attr-defined]
|
520
|
+
else:
|
521
|
+
class_name = clazz.__name__
|
522
|
+
|
523
|
+
schema_ctx.seen_classes[clazz] = class_name
|
524
|
+
|
525
|
+
try:
|
526
|
+
# noinspection PyDataclass
|
527
|
+
fields: Tuple[dataclasses.Field, ...] = dataclasses.fields(clazz)
|
528
|
+
except TypeError: # Not a dataclass
|
529
|
+
try:
|
530
|
+
warnings.warn(
|
531
|
+
"****** WARNING ****** "
|
532
|
+
f"marshmallow_dataclass was called on the class {clazz}, which is not a dataclass. "
|
533
|
+
"It is going to try and convert the class into a dataclass, which may have "
|
534
|
+
"undesirable side effects. To avoid this message, make sure all your classes and "
|
535
|
+
"all the classes of their fields are either explicitly supported by "
|
536
|
+
"marshmallow_dataclass, or define the schema explicitly using "
|
537
|
+
"field(metadata=dict(marshmallow_field=...)). For more information, see "
|
538
|
+
"https://github.com/lovasoa/marshmallow_dataclass/issues/51 "
|
539
|
+
"****** WARNING ******"
|
540
|
+
)
|
541
|
+
created_dataclass: type = dataclasses.dataclass(clazz)
|
542
|
+
return _internal_class_schema(created_dataclass, base_schema)
|
543
|
+
except Exception as exc:
|
544
|
+
raise TypeError(
|
545
|
+
f"{getattr(clazz, '__name__', repr(clazz))} is not a dataclass and cannot be turned into one."
|
546
|
+
) from exc
|
547
|
+
|
548
|
+
# Copy all marshmallow hooks and whitelisted members of the dataclass to the schema.
|
549
|
+
attributes = {
|
550
|
+
k: v
|
551
|
+
for k, v in inspect.getmembers(clazz)
|
552
|
+
if hasattr(v, "__marshmallow_hook__") or k in MEMBERS_WHITELIST
|
553
|
+
}
|
554
|
+
|
555
|
+
# Determine whether we should include non-init fields
|
556
|
+
include_non_init = getattr(getattr(clazz, "Meta", None), "include_non_init", False)
|
557
|
+
|
558
|
+
# Update the schema members to contain marshmallow fields instead of dataclass fields
|
559
|
+
|
560
|
+
if sys.version_info >= (3, 9):
|
561
|
+
type_hints = get_type_hints(
|
562
|
+
clazz,
|
563
|
+
globalns=schema_ctx.globalns,
|
564
|
+
localns=schema_ctx.localns,
|
565
|
+
include_extras=True,
|
566
|
+
)
|
567
|
+
else:
|
568
|
+
type_hints = get_type_hints(
|
569
|
+
clazz, globalns=schema_ctx.globalns, localns=schema_ctx.localns
|
570
|
+
)
|
571
|
+
attributes.update(
|
572
|
+
(
|
573
|
+
field.name,
|
574
|
+
_field_for_schema(
|
575
|
+
type_hints[field.name],
|
576
|
+
_get_field_default(field),
|
577
|
+
field.metadata,
|
578
|
+
base_schema,
|
579
|
+
),
|
580
|
+
)
|
581
|
+
for field in fields
|
582
|
+
if field.init or include_non_init
|
583
|
+
)
|
584
|
+
|
585
|
+
schema_class = type(clazz.__name__, (_base_schema(clazz, base_schema),), attributes)
|
586
|
+
return cast(Type[marshmallow.Schema], schema_class)
|
587
|
+
|
588
|
+
|
589
|
+
def _field_by_type(
|
590
|
+
typ: Union[type, Any], base_schema: Optional[Type[marshmallow.Schema]]
|
591
|
+
) -> Optional[Type[marshmallow.fields.Field]]:
|
592
|
+
return (
|
593
|
+
base_schema and base_schema.TYPE_MAPPING.get(typ)
|
594
|
+
) or marshmallow.Schema.TYPE_MAPPING.get(typ)
|
595
|
+
|
596
|
+
|
597
|
+
def _field_by_supertype(
|
598
|
+
typ: Type,
|
599
|
+
default: Any,
|
600
|
+
newtype_supertype: Type,
|
601
|
+
metadata: dict,
|
602
|
+
base_schema: Optional[Type[marshmallow.Schema]],
|
603
|
+
) -> marshmallow.fields.Field:
|
604
|
+
"""
|
605
|
+
Return a new field for fields based on a super field. (Usually spawned from NewType)
|
606
|
+
"""
|
607
|
+
# Add the information coming our custom NewType implementation
|
608
|
+
|
609
|
+
typ_args = getattr(typ, "_marshmallow_args", {})
|
610
|
+
|
611
|
+
# Handle multiple validators from both `typ` and `metadata`.
|
612
|
+
# See https://github.com/lovasoa/marshmallow_dataclass/issues/91
|
613
|
+
new_validators: List[Callable] = []
|
614
|
+
for meta_dict in (typ_args, metadata):
|
615
|
+
if "validate" in meta_dict:
|
616
|
+
if marshmallow.utils.is_iterable_but_not_string(meta_dict["validate"]):
|
617
|
+
new_validators.extend(meta_dict["validate"])
|
618
|
+
elif callable(meta_dict["validate"]):
|
619
|
+
new_validators.append(meta_dict["validate"])
|
620
|
+
metadata["validate"] = new_validators if new_validators else None
|
621
|
+
|
622
|
+
metadata = {**typ_args, **metadata}
|
623
|
+
metadata.setdefault("metadata", {}).setdefault("description", typ.__name__)
|
624
|
+
field = getattr(typ, "_marshmallow_field", None)
|
625
|
+
if field:
|
626
|
+
return field(**metadata)
|
627
|
+
else:
|
628
|
+
return _field_for_schema(
|
629
|
+
newtype_supertype,
|
630
|
+
metadata=metadata,
|
631
|
+
default=default,
|
632
|
+
base_schema=base_schema,
|
633
|
+
)
|
634
|
+
|
635
|
+
|
636
|
+
def _generic_type_add_any(typ: type) -> type:
|
637
|
+
"""if typ is generic type without arguments, replace them by Any."""
|
638
|
+
if typ is list or typ is List:
|
639
|
+
typ = List[Any]
|
640
|
+
elif typ is dict or typ is Dict:
|
641
|
+
typ = Dict[Any, Any]
|
642
|
+
elif typ is Mapping:
|
643
|
+
typ = Mapping[Any, Any]
|
644
|
+
elif typ is Sequence:
|
645
|
+
typ = Sequence[Any]
|
646
|
+
elif typ is set or typ is Set:
|
647
|
+
typ = Set[Any]
|
648
|
+
elif typ is frozenset or typ is FrozenSet:
|
649
|
+
typ = FrozenSet[Any]
|
650
|
+
return typ
|
651
|
+
|
652
|
+
|
653
|
+
def _field_for_generic_type(
|
654
|
+
typ: type,
|
655
|
+
base_schema: Optional[Type[marshmallow.Schema]],
|
656
|
+
**metadata: Any,
|
657
|
+
) -> Optional[marshmallow.fields.Field]:
|
658
|
+
"""
|
659
|
+
If the type is a generic interface, resolve the arguments and construct the appropriate Field.
|
660
|
+
"""
|
661
|
+
origin = typing_extensions.get_origin(typ)
|
662
|
+
arguments = typing_extensions.get_args(typ)
|
663
|
+
if origin:
|
664
|
+
# Override base_schema.TYPE_MAPPING to change the class used for generic types below
|
665
|
+
type_mapping = base_schema.TYPE_MAPPING if base_schema else {}
|
666
|
+
|
667
|
+
if origin in (list, List):
|
668
|
+
child_type = _field_for_schema(arguments[0], base_schema=base_schema)
|
669
|
+
list_type = cast(
|
670
|
+
Type[marshmallow.fields.List],
|
671
|
+
type_mapping.get(List, marshmallow.fields.List),
|
672
|
+
)
|
673
|
+
return list_type(child_type, **metadata)
|
674
|
+
if origin in (collections.abc.Sequence, Sequence) or (
|
675
|
+
origin in (tuple, Tuple)
|
676
|
+
and len(arguments) == 2
|
677
|
+
and arguments[1] is Ellipsis
|
678
|
+
):
|
679
|
+
from . import collection_field
|
680
|
+
|
681
|
+
child_type = _field_for_schema(arguments[0], base_schema=base_schema)
|
682
|
+
return collection_field.Sequence(cls_or_instance=child_type, **metadata)
|
683
|
+
if origin in (set, Set):
|
684
|
+
from . import collection_field
|
685
|
+
|
686
|
+
child_type = _field_for_schema(arguments[0], base_schema=base_schema)
|
687
|
+
return collection_field.Set(
|
688
|
+
cls_or_instance=child_type, frozen=False, **metadata
|
689
|
+
)
|
690
|
+
if origin in (frozenset, FrozenSet):
|
691
|
+
from . import collection_field
|
692
|
+
|
693
|
+
child_type = _field_for_schema(arguments[0], base_schema=base_schema)
|
694
|
+
return collection_field.Set(
|
695
|
+
cls_or_instance=child_type, frozen=True, **metadata
|
696
|
+
)
|
697
|
+
if origin in (tuple, Tuple):
|
698
|
+
children = tuple(
|
699
|
+
_field_for_schema(arg, base_schema=base_schema) for arg in arguments
|
700
|
+
)
|
701
|
+
tuple_type = cast(
|
702
|
+
Type[marshmallow.fields.Tuple],
|
703
|
+
type_mapping.get( # type:ignore[call-overload]
|
704
|
+
Tuple, marshmallow.fields.Tuple
|
705
|
+
),
|
706
|
+
)
|
707
|
+
return tuple_type(children, **metadata)
|
708
|
+
elif origin in (dict, Dict, collections.abc.Mapping, Mapping):
|
709
|
+
dict_type = type_mapping.get(Dict, marshmallow.fields.Dict)
|
710
|
+
return dict_type(
|
711
|
+
keys=_field_for_schema(arguments[0], base_schema=base_schema),
|
712
|
+
values=_field_for_schema(arguments[1], base_schema=base_schema),
|
713
|
+
**metadata,
|
714
|
+
)
|
715
|
+
|
716
|
+
return None
|
717
|
+
|
718
|
+
|
719
|
+
def _field_for_annotated_type(
|
720
|
+
typ: type,
|
721
|
+
**metadata: Any,
|
722
|
+
) -> Optional[marshmallow.fields.Field]:
|
723
|
+
"""
|
724
|
+
If the type is an Annotated interface, resolve the arguments and construct the appropriate Field.
|
725
|
+
"""
|
726
|
+
origin = typing_extensions.get_origin(typ)
|
727
|
+
arguments = typing_extensions.get_args(typ)
|
728
|
+
if origin and origin is Annotated:
|
729
|
+
marshmallow_annotations = [
|
730
|
+
arg
|
731
|
+
for arg in arguments[1:]
|
732
|
+
if (inspect.isclass(arg) and issubclass(arg, marshmallow.fields.Field))
|
733
|
+
or isinstance(arg, marshmallow.fields.Field)
|
734
|
+
]
|
735
|
+
if marshmallow_annotations:
|
736
|
+
if len(marshmallow_annotations) > 1:
|
737
|
+
warnings.warn(
|
738
|
+
"Multiple marshmallow Field annotations found. Using the last one."
|
739
|
+
)
|
740
|
+
|
741
|
+
field = marshmallow_annotations[-1]
|
742
|
+
# Got a field instance, return as is. User must know what they're doing
|
743
|
+
if isinstance(field, marshmallow.fields.Field):
|
744
|
+
return field
|
745
|
+
|
746
|
+
return field(**metadata)
|
747
|
+
return None
|
748
|
+
|
749
|
+
|
750
|
+
def _field_for_union_type(
|
751
|
+
typ: type,
|
752
|
+
base_schema: Optional[Type[marshmallow.Schema]],
|
753
|
+
**metadata: Any,
|
754
|
+
) -> Optional[marshmallow.fields.Field]:
|
755
|
+
arguments = typing_extensions.get_args(typ)
|
756
|
+
if typing_inspect.is_union_type(typ):
|
757
|
+
if typing_inspect.is_optional_type(typ):
|
758
|
+
metadata["allow_none"] = metadata.get("allow_none", True)
|
759
|
+
metadata["dump_default"] = metadata.get("dump_default", None)
|
760
|
+
if not metadata.get("required"):
|
761
|
+
metadata["load_default"] = metadata.get("load_default", None)
|
762
|
+
metadata.setdefault("required", False)
|
763
|
+
subtypes = [t for t in arguments if t is not NoneType] # type: ignore
|
764
|
+
if len(subtypes) == 1:
|
765
|
+
return _field_for_schema(
|
766
|
+
subtypes[0],
|
767
|
+
metadata=metadata,
|
768
|
+
base_schema=base_schema,
|
769
|
+
)
|
770
|
+
from . import union_field
|
771
|
+
|
772
|
+
return union_field.Union(
|
773
|
+
[
|
774
|
+
(
|
775
|
+
subtyp,
|
776
|
+
_field_for_schema(
|
777
|
+
subtyp,
|
778
|
+
metadata={"required": True},
|
779
|
+
base_schema=base_schema,
|
780
|
+
),
|
781
|
+
)
|
782
|
+
for subtyp in subtypes
|
783
|
+
],
|
784
|
+
**metadata,
|
785
|
+
)
|
786
|
+
return None
|
787
|
+
|
788
|
+
|
789
|
+
def field_for_schema(
|
790
|
+
typ: type,
|
791
|
+
default: Any = marshmallow.missing,
|
792
|
+
metadata: Optional[Mapping[str, Any]] = None,
|
793
|
+
base_schema: Optional[Type[marshmallow.Schema]] = None,
|
794
|
+
# FIXME: delete typ_frame from API?
|
795
|
+
typ_frame: Optional[types.FrameType] = None,
|
796
|
+
) -> marshmallow.fields.Field:
|
797
|
+
"""
|
798
|
+
Get a marshmallow Field corresponding to the given python type.
|
799
|
+
The metadata of the dataclass field is used as arguments to the marshmallow Field.
|
800
|
+
|
801
|
+
:param typ: The type for which a field should be generated
|
802
|
+
:param default: value to use for (de)serialization when the field is missing
|
803
|
+
:param metadata: Additional parameters to pass to the marshmallow field constructor
|
804
|
+
:param base_schema: marshmallow schema used as a base class when deriving dataclass schema
|
805
|
+
:param typ_frame: frame of type definition
|
806
|
+
|
807
|
+
>>> int_field = field_for_schema(int, default=9, metadata=dict(required=True))
|
808
|
+
>>> int_field.__class__
|
809
|
+
<class 'marshmallow.fields.Integer'>
|
810
|
+
|
811
|
+
>>> int_field.dump_default
|
812
|
+
9
|
813
|
+
|
814
|
+
>>> field_for_schema(str, metadata={"marshmallow_field": marshmallow.fields.Url()}).__class__
|
815
|
+
<class 'marshmallow.fields.Url'>
|
816
|
+
"""
|
817
|
+
with _SchemaContext(localns=typ_frame.f_locals if typ_frame is not None else None):
|
818
|
+
return _field_for_schema(typ, default, metadata, base_schema)
|
819
|
+
|
820
|
+
|
821
|
+
def _field_for_schema(
|
822
|
+
typ: type,
|
823
|
+
default: Any = marshmallow.missing,
|
824
|
+
metadata: Optional[Mapping[str, Any]] = None,
|
825
|
+
base_schema: Optional[Type[marshmallow.Schema]] = None,
|
826
|
+
) -> marshmallow.fields.Field:
|
827
|
+
"""
|
828
|
+
Get a marshmallow Field corresponding to the given python type.
|
829
|
+
The metadata of the dataclass field is used as arguments to the marshmallow Field.
|
830
|
+
|
831
|
+
This is an internal version of field_for_schema. It assumes a _SchemaContext
|
832
|
+
has been pushed onto the local stack.
|
833
|
+
|
834
|
+
:param typ: The type for which a field should be generated
|
835
|
+
:param default: value to use for (de)serialization when the field is missing
|
836
|
+
:param metadata: Additional parameters to pass to the marshmallow field constructor
|
837
|
+
:param base_schema: marshmallow schema used as a base class when deriving dataclass schema
|
838
|
+
|
839
|
+
"""
|
840
|
+
|
841
|
+
metadata = {} if metadata is None else dict(metadata)
|
842
|
+
|
843
|
+
if default is not marshmallow.missing:
|
844
|
+
metadata.setdefault("dump_default", default)
|
845
|
+
# 'missing' must not be set for required fields.
|
846
|
+
if not metadata.get("required"):
|
847
|
+
metadata.setdefault("load_default", default)
|
848
|
+
else:
|
849
|
+
metadata.setdefault("required", not typing_inspect.is_optional_type(typ))
|
850
|
+
|
851
|
+
# If the field was already defined by the user
|
852
|
+
predefined_field = metadata.get("marshmallow_field")
|
853
|
+
if predefined_field:
|
854
|
+
return predefined_field
|
855
|
+
|
856
|
+
# Generic types specified without type arguments
|
857
|
+
typ = _generic_type_add_any(typ)
|
858
|
+
|
859
|
+
# Base types
|
860
|
+
field = _field_by_type(typ, base_schema)
|
861
|
+
if field:
|
862
|
+
return field(**metadata)
|
863
|
+
|
864
|
+
if typ is Any:
|
865
|
+
metadata.setdefault("allow_none", True)
|
866
|
+
return marshmallow.fields.Raw(**metadata)
|
867
|
+
|
868
|
+
# i.e.: Literal['abc']
|
869
|
+
if typing_inspect.is_literal_type(typ):
|
870
|
+
arguments = typing_inspect.get_args(typ)
|
871
|
+
return marshmallow.fields.Raw(
|
872
|
+
validate=(
|
873
|
+
marshmallow.validate.Equal(arguments[0])
|
874
|
+
if len(arguments) == 1
|
875
|
+
else marshmallow.validate.OneOf(arguments)
|
876
|
+
),
|
877
|
+
**metadata,
|
878
|
+
)
|
879
|
+
|
880
|
+
# i.e.: Final[str] = 'abc'
|
881
|
+
if typing_inspect.is_final_type(typ):
|
882
|
+
arguments = typing_inspect.get_args(typ)
|
883
|
+
if arguments:
|
884
|
+
subtyp = arguments[0]
|
885
|
+
elif default is not marshmallow.missing:
|
886
|
+
if callable(default):
|
887
|
+
subtyp = Any
|
888
|
+
warnings.warn(
|
889
|
+
"****** WARNING ****** "
|
890
|
+
"marshmallow_dataclass was called on a dataclass with an "
|
891
|
+
'attribute that is type-annotated with "Final" and uses '
|
892
|
+
"dataclasses.field for specifying a default value using a "
|
893
|
+
"factory. The Marshmallow field type cannot be inferred from the "
|
894
|
+
"factory and will fall back to a raw field which is equivalent to "
|
895
|
+
'the type annotation "Any" and will result in no validation. '
|
896
|
+
"Provide a type to Final[...] to ensure accurate validation. "
|
897
|
+
"****** WARNING ******"
|
898
|
+
)
|
899
|
+
else:
|
900
|
+
subtyp = type(default)
|
901
|
+
warnings.warn(
|
902
|
+
"****** WARNING ****** "
|
903
|
+
"marshmallow_dataclass was called on a dataclass with an "
|
904
|
+
'attribute that is type-annotated with "Final" with a default '
|
905
|
+
"value from which the Marshmallow field type is inferred. "
|
906
|
+
"Support for type inference from a default value is limited and "
|
907
|
+
"may result in inaccurate validation. Provide a type to "
|
908
|
+
"Final[...] to ensure accurate validation. "
|
909
|
+
"****** WARNING ******"
|
910
|
+
)
|
911
|
+
else:
|
912
|
+
subtyp = Any
|
913
|
+
return _field_for_schema(subtyp, default, metadata, base_schema)
|
914
|
+
|
915
|
+
annotated_field = _field_for_annotated_type(typ, **metadata)
|
916
|
+
if annotated_field:
|
917
|
+
return annotated_field
|
918
|
+
|
919
|
+
union_field = _field_for_union_type(typ, base_schema, **metadata)
|
920
|
+
if union_field:
|
921
|
+
return union_field
|
922
|
+
|
923
|
+
# Generic types
|
924
|
+
generic_field = _field_for_generic_type(typ, base_schema, **metadata)
|
925
|
+
if generic_field:
|
926
|
+
return generic_field
|
927
|
+
|
928
|
+
# typing.NewType returns a function (in python <= 3.9) or a class (python >= 3.10) with a
|
929
|
+
# __supertype__ attribute
|
930
|
+
newtype_supertype = getattr(typ, "__supertype__", None)
|
931
|
+
if typing_inspect.is_new_type(typ) and newtype_supertype is not None:
|
932
|
+
return _field_by_supertype(
|
933
|
+
typ=typ,
|
934
|
+
default=default,
|
935
|
+
newtype_supertype=newtype_supertype,
|
936
|
+
metadata=metadata,
|
937
|
+
base_schema=base_schema,
|
938
|
+
)
|
939
|
+
|
940
|
+
# enumerations
|
941
|
+
if inspect.isclass(typ) and issubclass(typ, Enum):
|
942
|
+
return marshmallow.fields.Enum(typ, **metadata)
|
943
|
+
|
944
|
+
# Nested marshmallow dataclass
|
945
|
+
# it would be just a class name instead of actual schema util the schema is not ready yet
|
946
|
+
nested_schema = getattr(typ, "Schema", None)
|
947
|
+
|
948
|
+
# Nested dataclasses
|
949
|
+
forward_reference = getattr(typ, "__forward_arg__", None)
|
950
|
+
|
951
|
+
nested = (
|
952
|
+
nested_schema
|
953
|
+
or forward_reference
|
954
|
+
or _schema_ctx_stack.top.seen_classes.get(typ)
|
955
|
+
or _internal_class_schema(typ, base_schema) # type: ignore[arg-type] # FIXME
|
956
|
+
)
|
957
|
+
|
958
|
+
return marshmallow.fields.Nested(nested, **metadata)
|
959
|
+
|
960
|
+
|
961
|
+
def _base_schema(
|
962
|
+
clazz: type, base_schema: Optional[Type[marshmallow.Schema]] = None
|
963
|
+
) -> Type[marshmallow.Schema]:
|
964
|
+
"""
|
965
|
+
Base schema factory that creates a schema for `clazz` derived either from `base_schema`
|
966
|
+
or `BaseSchema`
|
967
|
+
"""
|
968
|
+
|
969
|
+
# Remove `type: ignore` when mypy handles dynamic base classes
|
970
|
+
# https://github.com/python/mypy/issues/2813
|
971
|
+
class BaseSchema(base_schema or marshmallow.Schema): # type: ignore
|
972
|
+
def load(self, data: Mapping, *, many: Optional[bool] = None, **kwargs):
|
973
|
+
all_loaded = super().load(data, many=many, **kwargs)
|
974
|
+
many = self.many if many is None else bool(many)
|
975
|
+
if many:
|
976
|
+
return [clazz(**loaded) for loaded in all_loaded]
|
977
|
+
else:
|
978
|
+
return clazz(**all_loaded)
|
979
|
+
|
980
|
+
return BaseSchema
|
981
|
+
|
982
|
+
|
983
|
+
def _get_field_default(field: dataclasses.Field):
|
984
|
+
"""
|
985
|
+
Return a marshmallow default value given a dataclass default value
|
986
|
+
|
987
|
+
>>> _get_field_default(dataclasses.field())
|
988
|
+
<marshmallow.missing>
|
989
|
+
"""
|
990
|
+
# Remove `type: ignore` when https://github.com/python/mypy/issues/6910 is fixed
|
991
|
+
default_factory = field.default_factory # type: ignore
|
992
|
+
if default_factory is not dataclasses.MISSING:
|
993
|
+
return default_factory
|
994
|
+
elif field.default is dataclasses.MISSING:
|
995
|
+
return marshmallow.missing
|
996
|
+
return field.default
|
997
|
+
|
998
|
+
|
999
|
+
def NewType(
|
1000
|
+
name: str,
|
1001
|
+
typ: Type[_U],
|
1002
|
+
field: Optional[Type[marshmallow.fields.Field]] = None,
|
1003
|
+
**kwargs,
|
1004
|
+
) -> Callable[[_U], _U]:
|
1005
|
+
"""DEPRECATED: Use typing.Annotated instead.
|
1006
|
+
NewType creates simple unique types
|
1007
|
+
to which you can attach custom marshmallow attributes.
|
1008
|
+
All the keyword arguments passed to this function will be transmitted
|
1009
|
+
to the marshmallow field constructor.
|
1010
|
+
|
1011
|
+
>>> import marshmallow.validate
|
1012
|
+
>>> IPv4 = NewType('IPv4', str, validate=marshmallow.validate.Regexp(r'^([0-9]{1,3}\\.){3}[0-9]{1,3}$'))
|
1013
|
+
>>> @dataclass
|
1014
|
+
... class MyIps:
|
1015
|
+
... ips: List[IPv4]
|
1016
|
+
>>> MyIps.Schema().load({"ips": ["0.0.0.0", "grumble grumble"]})
|
1017
|
+
Traceback (most recent call last):
|
1018
|
+
...
|
1019
|
+
marshmallow.exceptions.ValidationError: {'ips': {1: ['String does not match expected pattern.']}}
|
1020
|
+
>>> MyIps.Schema().load({"ips": ["127.0.0.1"]})
|
1021
|
+
MyIps(ips=['127.0.0.1'])
|
1022
|
+
|
1023
|
+
>>> Email = NewType('Email', str, field=marshmallow.fields.Email)
|
1024
|
+
>>> @dataclass
|
1025
|
+
... class ContactInfo:
|
1026
|
+
... mail: Email = dataclasses.field(default="anonymous@example.org")
|
1027
|
+
>>> ContactInfo.Schema().load({})
|
1028
|
+
ContactInfo(mail='anonymous@example.org')
|
1029
|
+
>>> ContactInfo.Schema().load({"mail": "grumble grumble"})
|
1030
|
+
Traceback (most recent call last):
|
1031
|
+
...
|
1032
|
+
marshmallow.exceptions.ValidationError: {'mail': ['Not a valid email address.']}
|
1033
|
+
"""
|
1034
|
+
|
1035
|
+
# noinspection PyTypeHints
|
1036
|
+
new_type = typing_NewType(name, typ) # type: ignore
|
1037
|
+
# noinspection PyTypeHints
|
1038
|
+
new_type._marshmallow_field = field # type: ignore
|
1039
|
+
# noinspection PyTypeHints
|
1040
|
+
new_type._marshmallow_args = kwargs # type: ignore
|
1041
|
+
return new_type
|
1042
|
+
|
1043
|
+
|
1044
|
+
if __name__ == "__main__":
|
1045
|
+
import doctest
|
1046
|
+
|
1047
|
+
doctest.testmod(verbose=True)
|