lib-x17-fintech 2.1.3__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.
- lib_x17_fintech-2.1.3.dist-info/METADATA +633 -0
- lib_x17_fintech-2.1.3.dist-info/RECORD +282 -0
- lib_x17_fintech-2.1.3.dist-info/WHEEL +5 -0
- lib_x17_fintech-2.1.3.dist-info/licenses/LICENSE +1 -0
- lib_x17_fintech-2.1.3.dist-info/top_level.txt +1 -0
- xfintech/__init__.py +0 -0
- xfintech/connect/__init__.py +18 -0
- xfintech/connect/artifact/__init__.py +5 -0
- xfintech/connect/artifact/artifact.py +168 -0
- xfintech/connect/artifact/tests/__init__.py +3 -0
- xfintech/connect/artifact/tests/test_class_artifact_all.py +564 -0
- xfintech/connect/common/__init__.py +12 -0
- xfintech/connect/common/connect.py +49 -0
- xfintech/connect/common/connectref.py +119 -0
- xfintech/connect/common/error.py +62 -0
- xfintech/connect/common/tests/__init__.py +1 -0
- xfintech/connect/common/tests/test_class_connectlike_all.py +544 -0
- xfintech/connect/common/tests/test_class_connectref_all.py +586 -0
- xfintech/connect/common/tests/test_class_errors_all.py +524 -0
- xfintech/connect/instance/__init__.py +7 -0
- xfintech/connect/instance/macos.py +121 -0
- xfintech/connect/instance/s3.py +176 -0
- xfintech/connect/instance/tests/__init__.py +1 -0
- xfintech/connect/instance/tests/test_class_macosconnect_all.py +692 -0
- xfintech/connect/instance/tests/test_class_s3connect_all.py +603 -0
- xfintech/data/__init__.py +20 -0
- xfintech/data/common/__init__.py +15 -0
- xfintech/data/common/cache.py +186 -0
- xfintech/data/common/coolant.py +171 -0
- xfintech/data/common/metric.py +138 -0
- xfintech/data/common/paginate.py +132 -0
- xfintech/data/common/params.py +162 -0
- xfintech/data/common/retry.py +201 -0
- xfintech/data/common/tests/__init__.py +1 -0
- xfintech/data/common/tests/test_class_cache_all.py +681 -0
- xfintech/data/common/tests/test_class_coolant_all.py +534 -0
- xfintech/data/common/tests/test_class_metric_all.py +705 -0
- xfintech/data/common/tests/test_class_paginate_all.py +508 -0
- xfintech/data/common/tests/test_class_params_all.py +891 -0
- xfintech/data/common/tests/test_class_retry_all.py +714 -0
- xfintech/data/job/__init__.py +17 -0
- xfintech/data/job/errors.py +112 -0
- xfintech/data/job/house.py +156 -0
- xfintech/data/job/job.py +247 -0
- xfintech/data/job/joblike.py +47 -0
- xfintech/data/job/tests/__init__.py +1 -0
- xfintech/data/job/tests/test_class_errors_all.py +275 -0
- xfintech/data/job/tests/test_class_house_all.py +801 -0
- xfintech/data/job/tests/test_class_job_all.py +684 -0
- xfintech/data/job/tests/test_class_joblike_all.py +482 -0
- xfintech/data/relay/__init__.py +7 -0
- xfintech/data/relay/client.py +114 -0
- xfintech/data/relay/clientlike.py +45 -0
- xfintech/data/relay/tests/test_class_relayclient_all.py +484 -0
- xfintech/data/relay/tests/test_class_relayclientlike_all.py +500 -0
- xfintech/data/source/__init__.py +7 -0
- xfintech/data/source/baostock/__init__.py +21 -0
- xfintech/data/source/baostock/job/__init__.py +5 -0
- xfintech/data/source/baostock/job/job.py +217 -0
- xfintech/data/source/baostock/job/tests/__init__.py +0 -0
- xfintech/data/source/baostock/job/tests/test_class_baostockjob_all.py +547 -0
- xfintech/data/source/baostock/session/__init__.py +8 -0
- xfintech/data/source/baostock/session/relay.py +223 -0
- xfintech/data/source/baostock/session/session.py +241 -0
- xfintech/data/source/baostock/session/tests/__init__.py +0 -0
- xfintech/data/source/baostock/session/tests/test_class_relay_all.py +694 -0
- xfintech/data/source/baostock/session/tests/test_class_session_all.py +505 -0
- xfintech/data/source/baostock/stock/__init__.py +0 -0
- xfintech/data/source/baostock/stock/hs300stock/__init__.py +3 -0
- xfintech/data/source/baostock/stock/hs300stock/constant.py +49 -0
- xfintech/data/source/baostock/stock/hs300stock/hs300stock.py +133 -0
- xfintech/data/source/baostock/stock/hs300stock/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/hs300stock/tests/test_class_hs300index_all.py +413 -0
- xfintech/data/source/baostock/stock/minuteline/__init__.py +19 -0
- xfintech/data/source/baostock/stock/minuteline/constant.py +89 -0
- xfintech/data/source/baostock/stock/minuteline/minuteline.py +163 -0
- xfintech/data/source/baostock/stock/minuteline/tests/__init__.py +0 -0
- xfintech/data/source/baostock/stock/minuteline/tests/test_class_minuteline_all.py +582 -0
- xfintech/data/source/baostock/stock/stock/__init__.py +19 -0
- xfintech/data/source/baostock/stock/stock/constant.py +55 -0
- xfintech/data/source/baostock/stock/stock/stock.py +149 -0
- xfintech/data/source/baostock/stock/stock/tests/__init__.py +0 -0
- xfintech/data/source/baostock/stock/stock/tests/test_class_stock_all.py +508 -0
- xfintech/data/source/baostock/stock/stockinfo/__init__.py +5 -0
- xfintech/data/source/baostock/stock/stockinfo/constant.py +66 -0
- xfintech/data/source/baostock/stock/stockinfo/stockinfo.py +176 -0
- xfintech/data/source/baostock/stock/stockinfo/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/stockinfo/tests/test_class_stockinfo_all.py +617 -0
- xfintech/data/source/baostock/stock/sz50stock/__init__.py +3 -0
- xfintech/data/source/baostock/stock/sz50stock/constant.py +49 -0
- xfintech/data/source/baostock/stock/sz50stock/sz50stock.py +133 -0
- xfintech/data/source/baostock/stock/sz50stock/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/sz50stock/tests/test_class_sz50stock_all.py +397 -0
- xfintech/data/source/baostock/stock/tradedate/__init__.py +19 -0
- xfintech/data/source/baostock/stock/tradedate/constant.py +72 -0
- xfintech/data/source/baostock/stock/tradedate/tests/__init__.py +0 -0
- xfintech/data/source/baostock/stock/tradedate/tests/test_class_tradedate_all.py +695 -0
- xfintech/data/source/baostock/stock/tradedate/tradedate.py +208 -0
- xfintech/data/source/baostock/stock/zz500stock/__init__.py +3 -0
- xfintech/data/source/baostock/stock/zz500stock/constant.py +55 -0
- xfintech/data/source/baostock/stock/zz500stock/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/zz500stock/tests/test_class_zz500stock_all.py +421 -0
- xfintech/data/source/baostock/stock/zz500stock/zz500stock.py +133 -0
- xfintech/data/source/tushare/__init__.py +61 -0
- xfintech/data/source/tushare/job/__init__.py +5 -0
- xfintech/data/source/tushare/job/job.py +257 -0
- xfintech/data/source/tushare/job/tests/test_class_tusharejob_all.py +589 -0
- xfintech/data/source/tushare/session/__init__.py +5 -0
- xfintech/data/source/tushare/session/relay.py +231 -0
- xfintech/data/source/tushare/session/session.py +239 -0
- xfintech/data/source/tushare/session/tests/test_class_relay_all.py +719 -0
- xfintech/data/source/tushare/session/tests/test_class_session_all.py +705 -0
- xfintech/data/source/tushare/stock/__init__.py +55 -0
- xfintech/data/source/tushare/stock/adjfactor/__init__.py +19 -0
- xfintech/data/source/tushare/stock/adjfactor/adjfactor.py +150 -0
- xfintech/data/source/tushare/stock/adjfactor/constant.py +71 -0
- xfintech/data/source/tushare/stock/adjfactor/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/adjfactor/tests/test_class_adjfactor_all.py +372 -0
- xfintech/data/source/tushare/stock/capflow/__init__.py +19 -0
- xfintech/data/source/tushare/stock/capflow/capflow.py +171 -0
- xfintech/data/source/tushare/stock/capflow/constant.py +105 -0
- xfintech/data/source/tushare/stock/capflow/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/capflow/tests/test_class_capflow_all.py +589 -0
- xfintech/data/source/tushare/stock/capflowdc/__init__.py +19 -0
- xfintech/data/source/tushare/stock/capflowdc/capflowdc.py +167 -0
- xfintech/data/source/tushare/stock/capflowdc/constant.py +95 -0
- xfintech/data/source/tushare/stock/capflowdc/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/capflowdc/tests/test_class_capflowdc_all.py +814 -0
- xfintech/data/source/tushare/stock/capflowths/__init__.py +19 -0
- xfintech/data/source/tushare/stock/capflowths/capflowths.py +173 -0
- xfintech/data/source/tushare/stock/capflowths/constant.py +92 -0
- xfintech/data/source/tushare/stock/capflowths/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/capflowths/tests/test_class_capflowths_all.py +551 -0
- xfintech/data/source/tushare/stock/company/__init__.py +19 -0
- xfintech/data/source/tushare/stock/company/company.py +188 -0
- xfintech/data/source/tushare/stock/company/constant.py +92 -0
- xfintech/data/source/tushare/stock/company/tests/__init__.py +1 -0
- xfintech/data/source/tushare/stock/company/tests/test_class_company_all.py +829 -0
- xfintech/data/source/tushare/stock/companybusiness/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companybusiness/companybusiness.py +183 -0
- xfintech/data/source/tushare/stock/companybusiness/constant.py +91 -0
- xfintech/data/source/tushare/stock/companybusiness/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companybusiness/tests/test_class_companybusiness_all.py +633 -0
- xfintech/data/source/tushare/stock/companycashflow/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companycashflow/companycashflow.py +277 -0
- xfintech/data/source/tushare/stock/companycashflow/constant.py +293 -0
- xfintech/data/source/tushare/stock/companycashflow/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companycashflow/tests/test_class_companycashflow_all.py +619 -0
- xfintech/data/source/tushare/stock/companydebt/__init__.py +19 -0
- xfintech/data/source/tushare/stock/companydebt/companydebt.py +339 -0
- xfintech/data/source/tushare/stock/companydebt/constant.py +403 -0
- xfintech/data/source/tushare/stock/companydebt/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companydebt/tests/test_class_companydebt_all.py +655 -0
- xfintech/data/source/tushare/stock/companyoverview/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companyoverview/companyoverview.py +214 -0
- xfintech/data/source/tushare/stock/companyoverview/constant.py +152 -0
- xfintech/data/source/tushare/stock/companyoverview/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companyoverview/tests/test_class_companyoverview_all.py +647 -0
- xfintech/data/source/tushare/stock/companyprofit/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companyprofit/companyprofit.py +272 -0
- xfintech/data/source/tushare/stock/companyprofit/constant.py +259 -0
- xfintech/data/source/tushare/stock/companyprofit/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companyprofit/tests/test_class_companyprofit_all.py +635 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/__init__.py +21 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/conceptcapflowdc.py +175 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/constant.py +106 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/tests/test_class_conceptcapflowdc_all.py +568 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/__init__.py +21 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/conceptcapflowths.py +188 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/constant.py +89 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/tests/test_class_conceptcapflowths_all.py +516 -0
- xfintech/data/source/tushare/stock/dayline/__init__.py +19 -0
- xfintech/data/source/tushare/stock/dayline/constant.py +87 -0
- xfintech/data/source/tushare/stock/dayline/dayline.py +177 -0
- xfintech/data/source/tushare/stock/dayline/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/dayline/tests/test_class_dayline_all.py +585 -0
- xfintech/data/source/tushare/stock/industrycapflowths/__init__.py +21 -0
- xfintech/data/source/tushare/stock/industrycapflowths/constant.py +89 -0
- xfintech/data/source/tushare/stock/industrycapflowths/industrycapflowths.py +192 -0
- xfintech/data/source/tushare/stock/industrycapflowths/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/industrycapflowths/tests/test_class_industrycapflowths_all.py +683 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/__init__.py +21 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/constant.py +90 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/marketindexcapflowdc.py +173 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/test_class_marketindexcapflowdc_all.py +793 -0
- xfintech/data/source/tushare/stock/monthline/__init__.py +19 -0
- xfintech/data/source/tushare/stock/monthline/constant.py +87 -0
- xfintech/data/source/tushare/stock/monthline/monthline.py +180 -0
- xfintech/data/source/tushare/stock/monthline/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/monthline/tests/test_class_monthline_all.py +574 -0
- xfintech/data/source/tushare/stock/stock/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stock/constant.py +105 -0
- xfintech/data/source/tushare/stock/stock/stock.py +193 -0
- xfintech/data/source/tushare/stock/stock/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stock/tests/test_class_stock_all.py +788 -0
- xfintech/data/source/tushare/stock/stockdividend/__init__.py +21 -0
- xfintech/data/source/tushare/stock/stockdividend/constant.py +111 -0
- xfintech/data/source/tushare/stock/stockdividend/stockdividend.py +180 -0
- xfintech/data/source/tushare/stock/stockdividend/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockdividend/tests/test_class_stockdividend_all.py +725 -0
- xfintech/data/source/tushare/stock/stockinfo/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockinfo/constant.py +104 -0
- xfintech/data/source/tushare/stock/stockinfo/stockinfo.py +208 -0
- xfintech/data/source/tushare/stock/stockinfo/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockinfo/tests/test_class_stockinfo_all.py +881 -0
- xfintech/data/source/tushare/stock/stockipo/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockipo/constant.py +90 -0
- xfintech/data/source/tushare/stock/stockipo/stockipo.py +234 -0
- xfintech/data/source/tushare/stock/stockipo/tests/__init__.py +1 -0
- xfintech/data/source/tushare/stock/stockipo/tests/test_class_stockipo_all.py +750 -0
- xfintech/data/source/tushare/stock/stockpledge/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockpledge/constant.py +72 -0
- xfintech/data/source/tushare/stock/stockpledge/stockpledge.py +158 -0
- xfintech/data/source/tushare/stock/stockpledge/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockpledge/tests/test_class_stockpledge_all.py +664 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/__init__.py +21 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/constant.py +85 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/stockpledgedetail.py +171 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/tests/test_class_stockpledgedetail_all.py +112 -0
- xfintech/data/source/tushare/stock/stockst/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockst/constant.py +80 -0
- xfintech/data/source/tushare/stock/stockst/stockst.py +189 -0
- xfintech/data/source/tushare/stock/stockst/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockst/tests/test_class_stockst_all.py +693 -0
- xfintech/data/source/tushare/stock/stocksuspend/__init__.py +21 -0
- xfintech/data/source/tushare/stock/stocksuspend/constant.py +75 -0
- xfintech/data/source/tushare/stock/stocksuspend/stocksuspend.py +151 -0
- xfintech/data/source/tushare/stock/stocksuspend/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stocksuspend/tests/test_class_stocksuspend_all.py +626 -0
- xfintech/data/source/tushare/stock/techindex/__init__.py +19 -0
- xfintech/data/source/tushare/stock/techindex/constant.py +600 -0
- xfintech/data/source/tushare/stock/techindex/techindex.py +314 -0
- xfintech/data/source/tushare/stock/techindex/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/techindex/tests/test_class_techindex_all.py +576 -0
- xfintech/data/source/tushare/stock/tradedate/__init__.py +19 -0
- xfintech/data/source/tushare/stock/tradedate/constant.py +93 -0
- xfintech/data/source/tushare/stock/tradedate/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/tradedate/tests/test_class_tradedate_all.py +947 -0
- xfintech/data/source/tushare/stock/tradedate/tradedate.py +234 -0
- xfintech/data/source/tushare/stock/weekline/__init__.py +19 -0
- xfintech/data/source/tushare/stock/weekline/constant.py +87 -0
- xfintech/data/source/tushare/stock/weekline/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/weekline/tests/test_class_weekline_all.py +575 -0
- xfintech/data/source/tushare/stock/weekline/weekline.py +182 -0
- xfintech/fabric/__init__.py +18 -0
- xfintech/fabric/column/__init__.py +7 -0
- xfintech/fabric/column/info.py +202 -0
- xfintech/fabric/column/kind.py +102 -0
- xfintech/fabric/column/tests/__init__.py +0 -0
- xfintech/fabric/column/tests/test_class_info_all.py +207 -0
- xfintech/fabric/column/tests/test_class_kind_all.py +80 -0
- xfintech/fabric/table/__init__.py +5 -0
- xfintech/fabric/table/info.py +263 -0
- xfintech/fabric/table/tests/__init__.py +0 -0
- xfintech/fabric/table/tests/test_class_info_all.py +547 -0
- xfintech/serde/__init__.py +35 -0
- xfintech/serde/common/__init__.py +9 -0
- xfintech/serde/common/dataformat.py +78 -0
- xfintech/serde/common/deserialiserlike.py +38 -0
- xfintech/serde/common/error.py +182 -0
- xfintech/serde/common/serialiserlike.py +38 -0
- xfintech/serde/common/tests/__init__.py +1 -0
- xfintech/serde/common/tests/test_class_dataformat_all.py +694 -0
- xfintech/serde/common/tests/test_class_deserialiserlike_all.py +500 -0
- xfintech/serde/common/tests/test_class_errors_all.py +518 -0
- xfintech/serde/common/tests/test_class_serialiserlike_all.py +401 -0
- xfintech/serde/deserialiser/__init__.py +7 -0
- xfintech/serde/deserialiser/pandas.py +113 -0
- xfintech/serde/deserialiser/python.py +68 -0
- xfintech/serde/deserialiser/tests/__init__.py +1 -0
- xfintech/serde/deserialiser/tests/test_class_pandasdeserialiser_all.py +503 -0
- xfintech/serde/deserialiser/tests/test_class_pythondeserialiser_all.py +570 -0
- xfintech/serde/serialiser/__init__.py +7 -0
- xfintech/serde/serialiser/pandas.py +116 -0
- xfintech/serde/serialiser/python.py +71 -0
- xfintech/serde/serialiser/tests/__init__.py +1 -0
- xfintech/serde/serialiser/tests/test_class_pandasserialiser_all.py +474 -0
- xfintech/serde/serialiser/tests/test_class_pythonserialiser_all.py +508 -0
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from xfintech.serde.common.dataformat import DataFormat
|
|
6
|
+
from xfintech.serde.common.error import (
|
|
7
|
+
DeserialiserFailedError,
|
|
8
|
+
DeserialiserInputTypeError,
|
|
9
|
+
DeserialiserNotSupportedError,
|
|
10
|
+
)
|
|
11
|
+
from xfintech.serde.deserialiser.python import PythonDeserialiser
|
|
12
|
+
from xfintech.serde.serialiser.python import PythonSerialiser
|
|
13
|
+
|
|
14
|
+
# ==================== Class Structure Tests ====================
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_pythondeserialiser_has_deserialise_method():
|
|
18
|
+
"""Test PythonDeserialiser has deserialise method"""
|
|
19
|
+
assert hasattr(PythonDeserialiser, "deserialise")
|
|
20
|
+
assert callable(PythonDeserialiser.deserialise)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_pythondeserialiser_has_from_json_method():
|
|
24
|
+
"""Test PythonDeserialiser has from_json method"""
|
|
25
|
+
assert hasattr(PythonDeserialiser, "from_json")
|
|
26
|
+
assert callable(PythonDeserialiser.from_json)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_pythondeserialiser_methods_are_static():
|
|
30
|
+
"""Test all methods are static methods"""
|
|
31
|
+
assert isinstance(PythonDeserialiser.__dict__["deserialise"], staticmethod)
|
|
32
|
+
assert isinstance(PythonDeserialiser.__dict__["from_json"], staticmethod)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ==================== deserialise() Method Tests ====================
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_deserialise_simple_dict():
|
|
39
|
+
"""Test deserialise with simple dictionary"""
|
|
40
|
+
data = {"key": "value", "number": 42}
|
|
41
|
+
bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
|
|
42
|
+
result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
|
|
43
|
+
assert isinstance(result, dict)
|
|
44
|
+
assert result == data
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_deserialise_with_json_format_enum():
|
|
48
|
+
"""Test deserialise with JSON DataFormat enum"""
|
|
49
|
+
data = {"a": 1, "b": 2}
|
|
50
|
+
bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
|
|
51
|
+
result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
|
|
52
|
+
assert result == data
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_deserialise_with_json_format_string_lowercase():
|
|
56
|
+
"""Test deserialise with 'json' string"""
|
|
57
|
+
data = {"a": 1}
|
|
58
|
+
bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
|
|
59
|
+
result = PythonDeserialiser.deserialise(bytes_data, "json")
|
|
60
|
+
assert result == data
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_deserialise_with_json_format_string_uppercase():
|
|
64
|
+
"""Test deserialise with 'JSON' string"""
|
|
65
|
+
data = {"a": 1}
|
|
66
|
+
bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
|
|
67
|
+
result = PythonDeserialiser.deserialise(bytes_data, "JSON")
|
|
68
|
+
assert result == data
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_deserialise_with_json_format_string_mixed_case():
|
|
72
|
+
"""Test deserialise with 'Json' string"""
|
|
73
|
+
data = {"a": 1}
|
|
74
|
+
bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
|
|
75
|
+
result = PythonDeserialiser.deserialise(bytes_data, "Json")
|
|
76
|
+
assert result == data
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_deserialise_returns_dict():
|
|
80
|
+
"""Test deserialise returns dict type"""
|
|
81
|
+
data = {"test": "data"}
|
|
82
|
+
bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
|
|
83
|
+
result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
|
|
84
|
+
assert isinstance(result, dict)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_deserialise_nested_dict():
|
|
88
|
+
"""Test deserialise with nested dictionary"""
|
|
89
|
+
data = {"outer": {"inner": {"deep": "value"}}}
|
|
90
|
+
bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
|
|
91
|
+
result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
|
|
92
|
+
assert result["outer"]["inner"]["deep"] == "value"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_deserialise_dict_with_list():
|
|
96
|
+
"""Test deserialise with dictionary containing list"""
|
|
97
|
+
data = {"items": [1, 2, 3, 4, 5]}
|
|
98
|
+
bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
|
|
99
|
+
result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
|
|
100
|
+
assert result["items"] == [1, 2, 3, 4, 5]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_deserialise_dict_with_various_types():
|
|
104
|
+
"""Test deserialise with various data types"""
|
|
105
|
+
data = {
|
|
106
|
+
"string": "text",
|
|
107
|
+
"integer": 42,
|
|
108
|
+
"float": 3.14,
|
|
109
|
+
"boolean": True,
|
|
110
|
+
"null": None,
|
|
111
|
+
"list": [1, 2, 3],
|
|
112
|
+
"nested": {"key": "value"},
|
|
113
|
+
}
|
|
114
|
+
bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
|
|
115
|
+
result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
|
|
116
|
+
assert result == data
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_deserialise_empty_dict():
|
|
120
|
+
"""Test deserialise with empty dictionary"""
|
|
121
|
+
data = {}
|
|
122
|
+
bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
|
|
123
|
+
result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
|
|
124
|
+
assert result == {}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_deserialise_large_dict():
|
|
128
|
+
"""Test deserialise with large dictionary"""
|
|
129
|
+
data = {f"key_{i}": i for i in range(1000)}
|
|
130
|
+
bytes_data = PythonSerialiser.serialise(data, DataFormat.JSON)
|
|
131
|
+
result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
|
|
132
|
+
assert len(result) == 1000
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_deserialise_raises_on_non_bytes_list():
|
|
136
|
+
"""Test deserialise raises error for list input"""
|
|
137
|
+
with pytest.raises(DeserialiserInputTypeError) as exc_info:
|
|
138
|
+
PythonDeserialiser.deserialise([1, 2, 3], DataFormat.JSON)
|
|
139
|
+
assert "expects bytes" in str(exc_info.value)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_deserialise_raises_on_non_bytes_string():
|
|
143
|
+
"""Test deserialise raises error for string input"""
|
|
144
|
+
with pytest.raises(DeserialiserInputTypeError):
|
|
145
|
+
PythonDeserialiser.deserialise("not bytes", DataFormat.JSON)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_deserialise_raises_on_non_bytes_dict():
|
|
149
|
+
"""Test deserialise raises error for dict input"""
|
|
150
|
+
with pytest.raises(DeserialiserInputTypeError):
|
|
151
|
+
PythonDeserialiser.deserialise({"a": 1}, DataFormat.JSON)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_deserialise_raises_on_non_bytes_int():
|
|
155
|
+
"""Test deserialise raises error for integer input"""
|
|
156
|
+
with pytest.raises(DeserialiserInputTypeError):
|
|
157
|
+
PythonDeserialiser.deserialise(123, DataFormat.JSON)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_deserialise_raises_on_none():
|
|
161
|
+
"""Test deserialise raises error for None input"""
|
|
162
|
+
with pytest.raises(DeserialiserInputTypeError):
|
|
163
|
+
PythonDeserialiser.deserialise(None, DataFormat.JSON)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_deserialise_raises_on_unsupported_format_parquet():
|
|
167
|
+
"""Test deserialise raises error for PARQUET format"""
|
|
168
|
+
bytes_data = b'{"a": 1}'
|
|
169
|
+
with pytest.raises(DeserialiserNotSupportedError) as exc_info:
|
|
170
|
+
PythonDeserialiser.deserialise(bytes_data, DataFormat.PARQUET)
|
|
171
|
+
assert "Unsupported DataFormat" in str(exc_info.value)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_deserialise_raises_on_unsupported_format_csv():
|
|
175
|
+
"""Test deserialise raises error for CSV format"""
|
|
176
|
+
bytes_data = b'{"a": 1}'
|
|
177
|
+
with pytest.raises(DeserialiserNotSupportedError):
|
|
178
|
+
PythonDeserialiser.deserialise(bytes_data, DataFormat.CSV)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_deserialise_raises_on_invalid_format_string():
|
|
182
|
+
"""Test deserialise raises error for invalid format string"""
|
|
183
|
+
bytes_data = b'{"a": 1}'
|
|
184
|
+
with pytest.raises(ValueError) as exc_info:
|
|
185
|
+
PythonDeserialiser.deserialise(bytes_data, "xml")
|
|
186
|
+
assert "Unknown DataFormat" in str(exc_info.value)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_deserialise_raises_on_invalid_json():
|
|
190
|
+
"""Test deserialise raises error for invalid JSON"""
|
|
191
|
+
with pytest.raises(DeserialiserFailedError):
|
|
192
|
+
PythonDeserialiser.deserialise(b"not valid json", DataFormat.JSON)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# ==================== from_json() Method Tests ====================
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def test_from_json_basic():
|
|
199
|
+
"""Test from_json with basic dictionary"""
|
|
200
|
+
data = {"key": "value"}
|
|
201
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
202
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
203
|
+
assert isinstance(result, dict)
|
|
204
|
+
assert result == data
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_from_json_returns_dict():
|
|
208
|
+
"""Test from_json returns dict type"""
|
|
209
|
+
data = {"test": "data"}
|
|
210
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
211
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
212
|
+
assert isinstance(result, dict)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def test_from_json_with_utf8_encoding():
|
|
216
|
+
"""Test from_json with UTF-8 encoding (default)"""
|
|
217
|
+
data = {"text": "Hello 世界"}
|
|
218
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
219
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
220
|
+
assert result["text"] == "Hello 世界"
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_from_json_with_custom_encoding():
|
|
224
|
+
"""Test from_json with custom encoding"""
|
|
225
|
+
data = {"text": "test"}
|
|
226
|
+
bytes_data = PythonSerialiser.to_json(data, encoding="utf-8")
|
|
227
|
+
result = PythonDeserialiser.from_json(bytes_data, encoding="utf-8")
|
|
228
|
+
assert result == data
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def test_from_json_with_unicode_characters():
|
|
232
|
+
"""Test from_json preserves Unicode"""
|
|
233
|
+
data = {"chinese": "中文", "russian": "Русский", "arabic": "عربي"}
|
|
234
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
235
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
236
|
+
assert result == data
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def test_from_json_nested_structure():
|
|
240
|
+
"""Test from_json with nested structure"""
|
|
241
|
+
data = {"level1": {"level2": {"level3": "deep value"}}}
|
|
242
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
243
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
244
|
+
assert result["level1"]["level2"]["level3"] == "deep value"
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_from_json_with_list_values():
|
|
248
|
+
"""Test from_json with list values"""
|
|
249
|
+
data = {"numbers": [1, 2, 3, 4, 5], "strings": ["a", "b", "c"]}
|
|
250
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
251
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
252
|
+
assert result["numbers"] == [1, 2, 3, 4, 5]
|
|
253
|
+
assert result["strings"] == ["a", "b", "c"]
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def test_from_json_with_null_value():
|
|
257
|
+
"""Test from_json with None/null value"""
|
|
258
|
+
data = {"key": None}
|
|
259
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
260
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
261
|
+
assert result["key"] is None
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def test_from_json_with_boolean_values():
|
|
265
|
+
"""Test from_json with boolean values"""
|
|
266
|
+
data = {"true_val": True, "false_val": False}
|
|
267
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
268
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
269
|
+
assert result["true_val"] is True
|
|
270
|
+
assert result["false_val"] is False
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def test_from_json_with_numeric_values():
|
|
274
|
+
"""Test from_json with various numeric types"""
|
|
275
|
+
data = {"int": 42, "float": 3.14159, "negative": -100, "zero": 0}
|
|
276
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
277
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
278
|
+
assert result["int"] == 42
|
|
279
|
+
assert abs(result["float"] - 3.14159) < 0.00001
|
|
280
|
+
assert result["negative"] == -100
|
|
281
|
+
assert result["zero"] == 0
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def test_from_json_empty_dict():
|
|
285
|
+
"""Test from_json with empty dictionary"""
|
|
286
|
+
data = {}
|
|
287
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
288
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
289
|
+
assert result == {}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def test_from_json_single_key():
|
|
293
|
+
"""Test from_json with single key dictionary"""
|
|
294
|
+
data = {"only": "one"}
|
|
295
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
296
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
297
|
+
assert result == data
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_from_json_special_characters_in_values():
|
|
301
|
+
"""Test from_json with special characters"""
|
|
302
|
+
data = {
|
|
303
|
+
"quotes": 'He said "hello"',
|
|
304
|
+
"newlines": "line1\nline2",
|
|
305
|
+
"tabs": "col1\tcol2",
|
|
306
|
+
"backslash": "path\\to\\file",
|
|
307
|
+
}
|
|
308
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
309
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
310
|
+
assert result == data
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def test_from_json_special_characters_in_keys():
|
|
314
|
+
"""Test from_json with special characters in keys"""
|
|
315
|
+
data = {"key with spaces": "value1", "key-with-dashes": "value2", "key.with.dots": "value3"}
|
|
316
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
317
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
318
|
+
assert result == data
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def test_from_json_large_dict():
|
|
322
|
+
"""Test from_json with large dictionary"""
|
|
323
|
+
data = {f"key_{i}": f"value_{i}" for i in range(1000)}
|
|
324
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
325
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
326
|
+
assert len(result) == 1000
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def test_from_json_deeply_nested():
|
|
330
|
+
"""Test from_json with deeply nested structure"""
|
|
331
|
+
data = {"a": {"b": {"c": {"d": {"e": {"f": "deep"}}}}}}
|
|
332
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
333
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
334
|
+
assert result["a"]["b"]["c"]["d"]["e"]["f"] == "deep"
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def test_from_json_mixed_nesting():
|
|
338
|
+
"""Test from_json with mixed nested structures"""
|
|
339
|
+
data = {
|
|
340
|
+
"list_of_dicts": [{"id": 1, "name": "first"}, {"id": 2, "name": "second"}],
|
|
341
|
+
"dict_of_lists": {"group1": [1, 2, 3], "group2": [4, 5, 6]},
|
|
342
|
+
}
|
|
343
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
344
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
345
|
+
assert result == data
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def test_from_json_raises_on_invalid_json():
|
|
349
|
+
"""Test from_json raises error for invalid JSON"""
|
|
350
|
+
with pytest.raises(DeserialiserFailedError):
|
|
351
|
+
PythonDeserialiser.from_json(b"not valid json")
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def test_from_json_raises_on_malformed_json():
|
|
355
|
+
"""Test from_json raises error for malformed JSON"""
|
|
356
|
+
with pytest.raises(DeserialiserFailedError):
|
|
357
|
+
PythonDeserialiser.from_json(b'{"key": }')
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def test_from_json_can_return_list():
|
|
361
|
+
"""Test from_json can deserialise to list (not just dict)"""
|
|
362
|
+
bytes_data = b"[1, 2, 3]"
|
|
363
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
364
|
+
assert result == [1, 2, 3]
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def test_from_json_can_return_string():
|
|
368
|
+
"""Test from_json can deserialise to string"""
|
|
369
|
+
bytes_data = b'"hello"'
|
|
370
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
371
|
+
assert result == "hello"
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def test_from_json_can_return_number():
|
|
375
|
+
"""Test from_json can deserialise to number"""
|
|
376
|
+
bytes_data = b"42"
|
|
377
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
378
|
+
assert result == 42
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def test_from_json_can_return_boolean():
|
|
382
|
+
"""Test from_json can deserialise to boolean"""
|
|
383
|
+
bytes_data = b"true"
|
|
384
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
385
|
+
assert result is True
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def test_from_json_can_return_null():
|
|
389
|
+
"""Test from_json can deserialise to None"""
|
|
390
|
+
bytes_data = b"null"
|
|
391
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
392
|
+
assert result is None
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
# ==================== Roundtrip Tests ====================
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def test_roundtrip_serialise_deserialise():
|
|
399
|
+
"""Test data can be serialised and deserialised back"""
|
|
400
|
+
original = {"name": "test", "value": 123, "nested": {"key": "value"}, "list": [1, 2, 3]}
|
|
401
|
+
|
|
402
|
+
# Serialise
|
|
403
|
+
bytes_data = PythonSerialiser.serialise(original, DataFormat.JSON)
|
|
404
|
+
|
|
405
|
+
# Deserialise
|
|
406
|
+
result = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
|
|
407
|
+
|
|
408
|
+
assert result == original
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def test_roundtrip_to_json_from_json():
|
|
412
|
+
"""Test to_json and from_json are inverses"""
|
|
413
|
+
original = {"key1": "value1", "key2": 123, "key3": [1, 2, 3]}
|
|
414
|
+
|
|
415
|
+
bytes_data = PythonSerialiser.to_json(original)
|
|
416
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
417
|
+
|
|
418
|
+
assert result == original
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def test_roundtrip_preserves_types():
|
|
422
|
+
"""Test roundtrip preserves data types"""
|
|
423
|
+
original = {
|
|
424
|
+
"string": "text",
|
|
425
|
+
"int": 42,
|
|
426
|
+
"float": 3.14,
|
|
427
|
+
"bool": True,
|
|
428
|
+
"null": None,
|
|
429
|
+
"list": [1, 2, 3],
|
|
430
|
+
"dict": {"nested": "value"},
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
bytes_data = PythonSerialiser.to_json(original)
|
|
434
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
435
|
+
|
|
436
|
+
assert isinstance(result["string"], str)
|
|
437
|
+
assert isinstance(result["int"], int)
|
|
438
|
+
assert isinstance(result["float"], float)
|
|
439
|
+
assert isinstance(result["bool"], bool)
|
|
440
|
+
assert result["null"] is None
|
|
441
|
+
assert isinstance(result["list"], list)
|
|
442
|
+
assert isinstance(result["dict"], dict)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def test_roundtrip_multiple_times():
|
|
446
|
+
"""Test multiple roundtrips produce consistent results"""
|
|
447
|
+
original = {"data": "test", "value": 42}
|
|
448
|
+
|
|
449
|
+
# First roundtrip
|
|
450
|
+
bytes1 = PythonSerialiser.to_json(original)
|
|
451
|
+
result1 = PythonDeserialiser.from_json(bytes1)
|
|
452
|
+
|
|
453
|
+
# Second roundtrip
|
|
454
|
+
bytes2 = PythonSerialiser.to_json(result1)
|
|
455
|
+
result2 = PythonDeserialiser.from_json(bytes2)
|
|
456
|
+
|
|
457
|
+
assert result1 == original
|
|
458
|
+
assert result2 == original
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def test_roundtrip_with_unicode():
|
|
462
|
+
"""Test roundtrip with Unicode data"""
|
|
463
|
+
original = {
|
|
464
|
+
"latin": "Hello",
|
|
465
|
+
"cyrillic": "Привет",
|
|
466
|
+
"arabic": "مرحبا",
|
|
467
|
+
"chinese": "你好",
|
|
468
|
+
"japanese": "こんにちは",
|
|
469
|
+
"korean": "안녕하세요",
|
|
470
|
+
"emoji": "👋🌍",
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
bytes_data = PythonSerialiser.to_json(original)
|
|
474
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
475
|
+
|
|
476
|
+
assert result == original
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def test_roundtrip_empty_dict():
|
|
480
|
+
"""Test roundtrip with empty dictionary"""
|
|
481
|
+
original = {}
|
|
482
|
+
bytes_data = PythonSerialiser.to_json(original)
|
|
483
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
484
|
+
assert result == {}
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def test_roundtrip_with_empty_strings():
|
|
488
|
+
"""Test roundtrip with empty string values"""
|
|
489
|
+
original = {"empty": "", "not_empty": "value"}
|
|
490
|
+
bytes_data = PythonSerialiser.to_json(original)
|
|
491
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
492
|
+
assert result == original
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def test_roundtrip_with_empty_lists():
|
|
496
|
+
"""Test roundtrip with empty list values"""
|
|
497
|
+
original = {"empty_list": [], "list": [1, 2]}
|
|
498
|
+
bytes_data = PythonSerialiser.to_json(original)
|
|
499
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
500
|
+
assert result == original
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def test_roundtrip_with_empty_nested_dicts():
|
|
504
|
+
"""Test roundtrip with empty nested dictionaries"""
|
|
505
|
+
original = {"outer": {}, "another": {"inner": {}}}
|
|
506
|
+
bytes_data = PythonSerialiser.to_json(original)
|
|
507
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
508
|
+
assert result == original
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
# ==================== Integration and Edge Case Tests ====================
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def test_deserialise_and_from_json_produce_same_result():
|
|
515
|
+
"""Test deserialise and from_json produce same result"""
|
|
516
|
+
bytes_data = b'{"a": 1, "b": 2}'
|
|
517
|
+
result1 = PythonDeserialiser.deserialise(bytes_data, DataFormat.JSON)
|
|
518
|
+
result2 = PythonDeserialiser.from_json(bytes_data)
|
|
519
|
+
assert result1 == result2
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def test_from_json_with_very_long_strings():
|
|
523
|
+
"""Test from_json with very long string values"""
|
|
524
|
+
long_string = "x" * 100000
|
|
525
|
+
data = {"long": long_string}
|
|
526
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
527
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
528
|
+
assert result["long"] == long_string
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def test_from_json_with_numeric_string_keys():
|
|
532
|
+
"""Test from_json with numeric string keys"""
|
|
533
|
+
data = {"1": "one", "2": "two", "10": "ten"}
|
|
534
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
535
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
536
|
+
assert result["1"] == "one"
|
|
537
|
+
assert result["2"] == "two"
|
|
538
|
+
assert result["10"] == "ten"
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def test_from_json_preserves_order():
|
|
542
|
+
"""Test from_json preserves key order (in Python 3.7+)"""
|
|
543
|
+
data = {"z": 1, "a": 2, "m": 3}
|
|
544
|
+
bytes_data = json.dumps(data).encode("utf-8")
|
|
545
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
546
|
+
# Note: Order preservation depends on json.dumps maintaining order
|
|
547
|
+
assert list(result.keys()) == ["z", "a", "m"]
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def test_output_decoding_consistency():
|
|
551
|
+
"""Test output is consistently decoded"""
|
|
552
|
+
data = {"text": "test"}
|
|
553
|
+
bytes_data = PythonSerialiser.to_json(data)
|
|
554
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
555
|
+
assert isinstance(result, dict)
|
|
556
|
+
assert isinstance(result["text"], str)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def test_from_json_with_escaped_characters():
|
|
560
|
+
"""Test from_json handles escaped characters"""
|
|
561
|
+
bytes_data = b'{"text": "line1\\nline2"}'
|
|
562
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
563
|
+
assert result["text"] == "line1\nline2"
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def test_from_json_with_unicode_escape_sequences():
|
|
567
|
+
"""Test from_json handles Unicode escape sequences"""
|
|
568
|
+
bytes_data = b'{"text": "\\u4e2d\\u6587"}'
|
|
569
|
+
result = PythonDeserialiser.from_json(bytes_data)
|
|
570
|
+
assert result["text"] == "中文"
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from xfintech.serde.common.dataformat import DataFormat
|
|
9
|
+
from xfintech.serde.common.error import (
|
|
10
|
+
SerialiserFailedError,
|
|
11
|
+
SerialiserImportError,
|
|
12
|
+
SerialiserInputTypeError,
|
|
13
|
+
SerialiserNotSupportedError,
|
|
14
|
+
)
|
|
15
|
+
from xfintech.serde.common.serialiserlike import SerialiserLike
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PandasSerialiser(SerialiserLike):
|
|
19
|
+
"""
|
|
20
|
+
描述:
|
|
21
|
+
- PandasSerialiser序列化模块。
|
|
22
|
+
- DataFrame -> BYTES 的转换,支持多种常见数据格式(Parquet、CSV、JSON)。
|
|
23
|
+
|
|
24
|
+
方法:
|
|
25
|
+
- serialise(data, format, **kwargs): 通用序列化方法,支持多种格式。
|
|
26
|
+
- to_parquet(data, **kwargs): 将 DataFrame 序列化为 Parquet 格式的字节流。
|
|
27
|
+
- to_csv(data, **kwargs): 将 DataFrame 序列化为 CSV 格式的字节流。
|
|
28
|
+
- to_json(data, **kwargs): 将 DataFrame 序列化为 JSON 格式的字节流。
|
|
29
|
+
|
|
30
|
+
例子:
|
|
31
|
+
```python
|
|
32
|
+
from xfintech.serde.serialiser.pandas import PandasSerialiser
|
|
33
|
+
from xfintech.serde.common.dataformat import DataFormat
|
|
34
|
+
import pandas as pd
|
|
35
|
+
|
|
36
|
+
# 使用通用序列化方法(推荐)
|
|
37
|
+
bytes = PandasSerialiser.serialise(
|
|
38
|
+
df,
|
|
39
|
+
format=DataFormat.PARQUET,
|
|
40
|
+
)
|
|
41
|
+
```
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def serialise(
|
|
46
|
+
data: pd.DataFrame,
|
|
47
|
+
format: DataFormat | str,
|
|
48
|
+
**kwargs: Any,
|
|
49
|
+
) -> bytes:
|
|
50
|
+
if not isinstance(data, pd.DataFrame):
|
|
51
|
+
msg = f"PandasSerialiser.serialise expects pd.DataFrame, got {type(data)}"
|
|
52
|
+
raise SerialiserInputTypeError(msg)
|
|
53
|
+
|
|
54
|
+
format = DataFormat.from_any(format)
|
|
55
|
+
if format == DataFormat.PARQUET:
|
|
56
|
+
return PandasSerialiser.to_parquet(data, **kwargs)
|
|
57
|
+
elif format == DataFormat.CSV:
|
|
58
|
+
return PandasSerialiser.to_csv(data, **kwargs)
|
|
59
|
+
elif format == DataFormat.JSON:
|
|
60
|
+
return PandasSerialiser.to_json(data, **kwargs)
|
|
61
|
+
else:
|
|
62
|
+
msg = f"Unsupported DataFormat for serialization: {format}"
|
|
63
|
+
raise SerialiserNotSupportedError(msg)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def to_parquet(
|
|
67
|
+
data: pd.DataFrame,
|
|
68
|
+
**kwargs: Any,
|
|
69
|
+
) -> bytes:
|
|
70
|
+
kwargs.setdefault("index", False)
|
|
71
|
+
kwargs.setdefault("engine", "pyarrow")
|
|
72
|
+
kwargs.setdefault("coerce_timestamps", "us")
|
|
73
|
+
kwargs.setdefault("allow_truncated_timestamps", True)
|
|
74
|
+
buffer = BytesIO()
|
|
75
|
+
try:
|
|
76
|
+
data.to_parquet(buffer, **kwargs)
|
|
77
|
+
return buffer.getvalue()
|
|
78
|
+
except ImportError as e:
|
|
79
|
+
msg = "pyarrow is required for parquet serialization. install via 'pip install pyarrow'."
|
|
80
|
+
raise SerialiserImportError(msg) from e
|
|
81
|
+
except Exception as e:
|
|
82
|
+
msg = f"Failed to serialize data: {e}"
|
|
83
|
+
raise SerialiserFailedError(msg) from e
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def to_csv(
|
|
87
|
+
data: pd.DataFrame,
|
|
88
|
+
**kwargs: Any,
|
|
89
|
+
) -> bytes:
|
|
90
|
+
kwargs.setdefault("index", False)
|
|
91
|
+
encoding = kwargs.pop("encoding", "utf-8")
|
|
92
|
+
try:
|
|
93
|
+
text = data.to_csv(**kwargs)
|
|
94
|
+
return text.encode(encoding)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
msg = f"Failed to serialize data: {e}"
|
|
97
|
+
raise SerialiserFailedError(msg) from e
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def to_json(
|
|
101
|
+
data: pd.DataFrame,
|
|
102
|
+
**kwargs: Any,
|
|
103
|
+
) -> bytes:
|
|
104
|
+
encoding = kwargs.pop("encoding", "utf-8")
|
|
105
|
+
orient = kwargs.pop("orient", "records")
|
|
106
|
+
lines = kwargs.pop("lines", False)
|
|
107
|
+
try:
|
|
108
|
+
text = data.to_json(
|
|
109
|
+
orient=orient,
|
|
110
|
+
lines=lines,
|
|
111
|
+
**kwargs,
|
|
112
|
+
)
|
|
113
|
+
return text.encode(encoding)
|
|
114
|
+
except Exception as e:
|
|
115
|
+
msg = f"Failed to serialize data: {e}"
|
|
116
|
+
raise SerialiserFailedError(msg) from e
|