commit
98388440de
8535 changed files with 1708240 additions and 0 deletions
-
11.idea/CnOCRService.iml
-
4.idea/encodings.xml
-
10.idea/misc.xml
-
8.idea/modules.xml
-
6.idea/vcs.xml
-
536.idea/workspace.xml
-
19CnOcr.py
-
1Lib/site-packages/Flask-2.2.5.dist-info/INSTALLER
-
28Lib/site-packages/Flask-2.2.5.dist-info/LICENSE.rst
-
123Lib/site-packages/Flask-2.2.5.dist-info/METADATA
-
53Lib/site-packages/Flask-2.2.5.dist-info/RECORD
-
5Lib/site-packages/Flask-2.2.5.dist-info/WHEEL
-
2Lib/site-packages/Flask-2.2.5.dist-info/entry_points.txt
-
1Lib/site-packages/Flask-2.2.5.dist-info/top_level.txt
-
1Lib/site-packages/Flask_Cors-5.0.0.dist-info/INSTALLER
-
7Lib/site-packages/Flask_Cors-5.0.0.dist-info/LICENSE
-
148Lib/site-packages/Flask_Cors-5.0.0.dist-info/METADATA
-
16Lib/site-packages/Flask_Cors-5.0.0.dist-info/RECORD
-
6Lib/site-packages/Flask_Cors-5.0.0.dist-info/WHEEL
-
1Lib/site-packages/Flask_Cors-5.0.0.dist-info/top_level.txt
-
132Lib/site-packages/Flask_RESTful-0.3.10.dist-info/AUTHORS.md
-
1Lib/site-packages/Flask_RESTful-0.3.10.dist-info/INSTALLER
-
25Lib/site-packages/Flask_RESTful-0.3.10.dist-info/LICENSE
-
29Lib/site-packages/Flask_RESTful-0.3.10.dist-info/METADATA
-
27Lib/site-packages/Flask_RESTful-0.3.10.dist-info/RECORD
-
6Lib/site-packages/Flask_RESTful-0.3.10.dist-info/WHEEL
-
1Lib/site-packages/Flask_RESTful-0.3.10.dist-info/top_level.txt
-
1Lib/site-packages/MarkupSafe-2.1.5.dist-info/INSTALLER
-
28Lib/site-packages/MarkupSafe-2.1.5.dist-info/LICENSE.rst
-
93Lib/site-packages/MarkupSafe-2.1.5.dist-info/METADATA
-
14Lib/site-packages/MarkupSafe-2.1.5.dist-info/RECORD
-
5Lib/site-packages/MarkupSafe-2.1.5.dist-info/WHEEL
-
1Lib/site-packages/MarkupSafe-2.1.5.dist-info/top_level.txt
-
1Lib/site-packages/PyYAML-6.0.1.dist-info/INSTALLER
-
20Lib/site-packages/PyYAML-6.0.1.dist-info/LICENSE
-
46Lib/site-packages/PyYAML-6.0.1.dist-info/METADATA
-
43Lib/site-packages/PyYAML-6.0.1.dist-info/RECORD
-
5Lib/site-packages/PyYAML-6.0.1.dist-info/WHEEL
-
2Lib/site-packages/PyYAML-6.0.1.dist-info/top_level.txt
-
1Lib/site-packages/Werkzeug-2.2.3.dist-info/INSTALLER
-
28Lib/site-packages/Werkzeug-2.2.3.dist-info/LICENSE.rst
-
126Lib/site-packages/Werkzeug-2.2.3.dist-info/METADATA
-
98Lib/site-packages/Werkzeug-2.2.3.dist-info/RECORD
-
5Lib/site-packages/Werkzeug-2.2.3.dist-info/WHEEL
-
1Lib/site-packages/Werkzeug-2.2.3.dist-info/top_level.txt
-
BINLib/site-packages/__pycache__/isympy.cpython-37.pyc
-
BINLib/site-packages/__pycache__/readline.cpython-37.pyc
-
BINLib/site-packages/__pycache__/six.cpython-37.pyc
-
BINLib/site-packages/__pycache__/typing_extensions.cpython-37.pyc
-
33Lib/site-packages/_yaml/__init__.py
-
BINLib/site-packages/_yaml/__pycache__/__init__.cpython-37.pyc
-
1Lib/site-packages/aniso8601-9.0.1.dist-info/INSTALLER
-
27Lib/site-packages/aniso8601-9.0.1.dist-info/LICENSE
-
513Lib/site-packages/aniso8601-9.0.1.dist-info/METADATA
-
60Lib/site-packages/aniso8601-9.0.1.dist-info/RECORD
-
6Lib/site-packages/aniso8601-9.0.1.dist-info/WHEEL
-
1Lib/site-packages/aniso8601-9.0.1.dist-info/top_level.txt
-
26Lib/site-packages/aniso8601/__init__.py
-
BINLib/site-packages/aniso8601/__pycache__/__init__.cpython-37.pyc
-
BINLib/site-packages/aniso8601/__pycache__/compat.cpython-37.pyc
-
BINLib/site-packages/aniso8601/__pycache__/date.cpython-37.pyc
-
BINLib/site-packages/aniso8601/__pycache__/decimalfraction.cpython-37.pyc
-
BINLib/site-packages/aniso8601/__pycache__/duration.cpython-37.pyc
-
BINLib/site-packages/aniso8601/__pycache__/exceptions.cpython-37.pyc
-
BINLib/site-packages/aniso8601/__pycache__/interval.cpython-37.pyc
-
BINLib/site-packages/aniso8601/__pycache__/resolution.cpython-37.pyc
-
BINLib/site-packages/aniso8601/__pycache__/time.cpython-37.pyc
-
BINLib/site-packages/aniso8601/__pycache__/timezone.cpython-37.pyc
-
BINLib/site-packages/aniso8601/__pycache__/utcoffset.cpython-37.pyc
-
614Lib/site-packages/aniso8601/builders/__init__.py
-
BINLib/site-packages/aniso8601/builders/__pycache__/__init__.cpython-37.pyc
-
BINLib/site-packages/aniso8601/builders/__pycache__/python.cpython-37.pyc
-
705Lib/site-packages/aniso8601/builders/python.py
-
7Lib/site-packages/aniso8601/builders/tests/__init__.py
-
BINLib/site-packages/aniso8601/builders/tests/__pycache__/__init__.cpython-37.pyc
-
BINLib/site-packages/aniso8601/builders/tests/__pycache__/test_init.cpython-37.pyc
-
BINLib/site-packages/aniso8601/builders/tests/__pycache__/test_python.cpython-37.pyc
-
838Lib/site-packages/aniso8601/builders/tests/test_init.py
-
1710Lib/site-packages/aniso8601/builders/tests/test_python.py
-
24Lib/site-packages/aniso8601/compat.py
-
161Lib/site-packages/aniso8601/date.py
-
12Lib/site-packages/aniso8601/decimalfraction.py
-
291Lib/site-packages/aniso8601/duration.py
-
51Lib/site-packages/aniso8601/exceptions.py
-
350Lib/site-packages/aniso8601/interval.py
-
27Lib/site-packages/aniso8601/resolution.py
-
7Lib/site-packages/aniso8601/tests/__init__.py
-
BINLib/site-packages/aniso8601/tests/__pycache__/__init__.cpython-37.pyc
-
BINLib/site-packages/aniso8601/tests/__pycache__/compat.cpython-37.pyc
-
BINLib/site-packages/aniso8601/tests/__pycache__/test_compat.cpython-37.pyc
-
BINLib/site-packages/aniso8601/tests/__pycache__/test_date.cpython-37.pyc
-
BINLib/site-packages/aniso8601/tests/__pycache__/test_decimalfraction.cpython-37.pyc
-
BINLib/site-packages/aniso8601/tests/__pycache__/test_duration.cpython-37.pyc
-
BINLib/site-packages/aniso8601/tests/__pycache__/test_init.cpython-37.pyc
-
BINLib/site-packages/aniso8601/tests/__pycache__/test_interval.cpython-37.pyc
-
BINLib/site-packages/aniso8601/tests/__pycache__/test_time.cpython-37.pyc
-
BINLib/site-packages/aniso8601/tests/__pycache__/test_timezone.cpython-37.pyc
-
BINLib/site-packages/aniso8601/tests/__pycache__/test_utcoffset.cpython-37.pyc
-
16Lib/site-packages/aniso8601/tests/compat.py
-
27Lib/site-packages/aniso8601/tests/test_compat.py
@ -0,0 +1,11 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<module type="PYTHON_MODULE" version="4"> |
|||
<component name="NewModuleRootManager"> |
|||
<content url="file://$MODULE_DIR$" /> |
|||
<orderEntry type="jdk" jdkName="Python 3.7" jdkType="Python SDK" /> |
|||
<orderEntry type="sourceFolder" forTests="false" /> |
|||
</component> |
|||
<component name="TestRunnerService"> |
|||
<option name="PROJECT_TEST_RUNNER" value="Unittests" /> |
|||
</component> |
|||
</module> |
@ -0,0 +1,4 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="Encoding" addBOMForNewFiles="with NO BOM" /> |
|||
</project> |
@ -0,0 +1,10 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="JavaScriptSettings"> |
|||
<option name="languageLevel" value="ES6" /> |
|||
</component> |
|||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7" project-jdk-type="Python SDK" /> |
|||
<component name="PyCharmProfessionalAdvertiser"> |
|||
<option name="shown" value="true" /> |
|||
</component> |
|||
</project> |
@ -0,0 +1,8 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="ProjectModuleManager"> |
|||
<modules> |
|||
<module fileurl="file://$PROJECT_DIR$/.idea/CnOCRService.iml" filepath="$PROJECT_DIR$/.idea/CnOCRService.iml" /> |
|||
</modules> |
|||
</component> |
|||
</project> |
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="VcsDirectoryMappings"> |
|||
<mapping directory="$PROJECT_DIR$" vcs="Git" /> |
|||
</component> |
|||
</project> |
@ -0,0 +1,536 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="ChangeListManager"> |
|||
<list default="true" id="62fb0e8b-1fdf-4b91-9565-798bb9e19152" name="Default Changelist" comment=""> |
|||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> |
|||
</list> |
|||
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" /> |
|||
<option name="SHOW_DIALOG" value="false" /> |
|||
<option name="HIGHLIGHT_CONFLICTS" value="true" /> |
|||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> |
|||
<option name="LAST_RESOLUTION" value="IGNORE" /> |
|||
</component> |
|||
<component name="CoverageDataManager"> |
|||
<SUITE FILE_PATH="coverage/CnOCRService$zk_util.coverage" NAME="zk_util Coverage Results" MODIFIED="1728975622411" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> |
|||
<SUITE FILE_PATH="coverage/CnOCRService$load_scenes.coverage" NAME="load_scenes Coverage Results" MODIFIED="1728902059389" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> |
|||
<SUITE FILE_PATH="coverage/CnOCRService$zk_listener.coverage" NAME="zk_listener Coverage Results" MODIFIED="1728898778709" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> |
|||
<SUITE FILE_PATH="coverage/CnOCRService$dataUtil.coverage" NAME="dataUtil Coverage Results" MODIFIED="1726123342941" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> |
|||
<SUITE FILE_PATH="coverage/CnOCRService$app.coverage" NAME="app Coverage Results" MODIFIED="1726050413312" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> |
|||
</component> |
|||
<component name="FileEditorManager"> |
|||
<leaf> |
|||
<file pinned="false" current-in-tab="false"> |
|||
<entry file="file://$PROJECT_DIR$/CnOcr.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="342"> |
|||
<caret line="18" column="5" lean-forward="true" selection-start-line="18" selection-start-column="5" selection-end-line="18" selection-end-column="5" /> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
</file> |
|||
<file pinned="false" current-in-tab="true"> |
|||
<entry file="file://$PROJECT_DIR$/ocr_handler.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="57"> |
|||
<caret line="3" column="10" selection-start-line="3" selection-start-column="10" selection-end-line="3" selection-end-column="10" /> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
</file> |
|||
</leaf> |
|||
</component> |
|||
<component name="FileTemplateManagerImpl"> |
|||
<option name="RECENT_TEMPLATES"> |
|||
<list> |
|||
<option value="Python Script" /> |
|||
</list> |
|||
</option> |
|||
</component> |
|||
<component name="FindInProjectRecents"> |
|||
<findStrings> |
|||
<find>prod</find> |
|||
<find>DataWatch</find> |
|||
<find>1931</find> |
|||
<find>Successfully wrote queue</find> |
|||
<find>Received OCR task</find> |
|||
<find>stop_event</find> |
|||
<find>Error writing queue item to file</find> |
|||
<find>cloce</find> |
|||
<find>open</find> |
|||
<find>kafka</find> |
|||
<find>VOCAB_FP</find> |
|||
</findStrings> |
|||
</component> |
|||
<component name="Git.Settings"> |
|||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> |
|||
</component> |
|||
<component name="IdeDocumentHistory"> |
|||
<option name="CHANGED_PATHS"> |
|||
<list> |
|||
<option value="$PROJECT_DIR$/dataUtil.py" /> |
|||
<option value="$PROJECT_DIR$/config_loader.py" /> |
|||
<option value="$PROJECT_DIR$/config.yml" /> |
|||
<option value="$PROJECT_DIR$/zk_listener.py" /> |
|||
<option value="$PROJECT_DIR$/load_scenes.py" /> |
|||
<option value="$PROJECT_DIR$/global_dict.py" /> |
|||
<option value="$PROJECT_DIR$/zk_util.py" /> |
|||
<option value="$PROJECT_DIR$/app.py" /> |
|||
<option value="$PROJECT_DIR$/queue_manager.py" /> |
|||
<option value="$PROJECT_DIR$/CnOcr.py" /> |
|||
<option value="$PROJECT_DIR$/ocr_handler.py" /> |
|||
</list> |
|||
</option> |
|||
</component> |
|||
<component name="ProjectFrameBounds" extendedState="6"> |
|||
<option name="x" value="-12" /> |
|||
<option name="y" value="-12" /> |
|||
<option name="width" value="1944" /> |
|||
<option name="height" value="1044" /> |
|||
</component> |
|||
<component name="ProjectLevelVcsManager" settingsEditedManually="true" /> |
|||
<component name="ProjectView"> |
|||
<navigator proportions="" version="1"> |
|||
<foldersAlwaysOnTop value="true" /> |
|||
</navigator> |
|||
<panes> |
|||
<pane id="ProjectPane"> |
|||
<subPane> |
|||
<expand> |
|||
<path> |
|||
<item name="CnOCRService" type="b2602c69:ProjectViewProjectNode" /> |
|||
<item name="CnOCRService" type="462c0819:PsiDirectoryNode" /> |
|||
</path> |
|||
</expand> |
|||
<select /> |
|||
</subPane> |
|||
</pane> |
|||
<pane id="Scope" /> |
|||
</panes> |
|||
</component> |
|||
<component name="PropertiesComponent"> |
|||
<property name="WebServerToolWindowFactoryState" value="false" /> |
|||
<property name="last_opened_file_path" value="$PROJECT_DIR$" /> |
|||
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" /> |
|||
<property name="nodejs_npm_path_reset_for_default_project" value="true" /> |
|||
<property name="settings.editor.selected.configurable" value="com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable" /> |
|||
</component> |
|||
<component name="RecentsManager"> |
|||
<key name="CopyFile.RECENT_KEYS"> |
|||
<recent name="C:\Users\毛健\PycharmProjects\CnOCRService" /> |
|||
</key> |
|||
</component> |
|||
<component name="RunDashboard"> |
|||
<option name="ruleStates"> |
|||
<list> |
|||
<RuleState> |
|||
<option name="name" value="ConfigurationTypeDashboardGroupingRule" /> |
|||
</RuleState> |
|||
<RuleState> |
|||
<option name="name" value="StatusDashboardGroupingRule" /> |
|||
</RuleState> |
|||
</list> |
|||
</option> |
|||
</component> |
|||
<component name="RunManager" selected="Python.app"> |
|||
<configuration name="app" type="PythonConfigurationType" factoryName="Python" temporary="true"> |
|||
<module name="CnOCRService" /> |
|||
<option name="INTERPRETER_OPTIONS" value="" /> |
|||
<option name="PARENT_ENVS" value="true" /> |
|||
<envs> |
|||
<env name="PYTHONUNBUFFERED" value="1" /> |
|||
</envs> |
|||
<option name="SDK_HOME" value="" /> |
|||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> |
|||
<option name="IS_MODULE_SDK" value="true" /> |
|||
<option name="ADD_CONTENT_ROOTS" value="true" /> |
|||
<option name="ADD_SOURCE_ROOTS" value="true" /> |
|||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> |
|||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/app.py" /> |
|||
<option name="PARAMETERS" value="" /> |
|||
<option name="SHOW_COMMAND_LINE" value="false" /> |
|||
<option name="EMULATE_TERMINAL" value="false" /> |
|||
<option name="MODULE_MODE" value="false" /> |
|||
<option name="REDIRECT_INPUT" value="false" /> |
|||
<option name="INPUT_FILE" value="" /> |
|||
<method v="2" /> |
|||
</configuration> |
|||
<configuration name="dataUtil" type="PythonConfigurationType" factoryName="Python" temporary="true"> |
|||
<module name="CnOCRService" /> |
|||
<option name="INTERPRETER_OPTIONS" value="" /> |
|||
<option name="PARENT_ENVS" value="true" /> |
|||
<envs> |
|||
<env name="PYTHONUNBUFFERED" value="1" /> |
|||
</envs> |
|||
<option name="SDK_HOME" value="" /> |
|||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> |
|||
<option name="IS_MODULE_SDK" value="true" /> |
|||
<option name="ADD_CONTENT_ROOTS" value="true" /> |
|||
<option name="ADD_SOURCE_ROOTS" value="true" /> |
|||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> |
|||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/dataUtil.py" /> |
|||
<option name="PARAMETERS" value="" /> |
|||
<option name="SHOW_COMMAND_LINE" value="false" /> |
|||
<option name="EMULATE_TERMINAL" value="false" /> |
|||
<option name="MODULE_MODE" value="false" /> |
|||
<option name="REDIRECT_INPUT" value="false" /> |
|||
<option name="INPUT_FILE" value="" /> |
|||
<method v="2" /> |
|||
</configuration> |
|||
<configuration name="load_scenes" type="PythonConfigurationType" factoryName="Python" temporary="true"> |
|||
<module name="CnOCRService" /> |
|||
<option name="INTERPRETER_OPTIONS" value="" /> |
|||
<option name="PARENT_ENVS" value="true" /> |
|||
<envs> |
|||
<env name="PYTHONUNBUFFERED" value="1" /> |
|||
</envs> |
|||
<option name="SDK_HOME" value="" /> |
|||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> |
|||
<option name="IS_MODULE_SDK" value="true" /> |
|||
<option name="ADD_CONTENT_ROOTS" value="true" /> |
|||
<option name="ADD_SOURCE_ROOTS" value="true" /> |
|||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> |
|||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/load_scenes.py" /> |
|||
<option name="PARAMETERS" value="" /> |
|||
<option name="SHOW_COMMAND_LINE" value="false" /> |
|||
<option name="EMULATE_TERMINAL" value="false" /> |
|||
<option name="MODULE_MODE" value="false" /> |
|||
<option name="REDIRECT_INPUT" value="false" /> |
|||
<option name="INPUT_FILE" value="" /> |
|||
<method v="2" /> |
|||
</configuration> |
|||
<configuration name="zk_listener" type="PythonConfigurationType" factoryName="Python" temporary="true"> |
|||
<module name="CnOCRService" /> |
|||
<option name="INTERPRETER_OPTIONS" value="" /> |
|||
<option name="PARENT_ENVS" value="true" /> |
|||
<envs> |
|||
<env name="PYTHONUNBUFFERED" value="1" /> |
|||
</envs> |
|||
<option name="SDK_HOME" value="" /> |
|||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> |
|||
<option name="IS_MODULE_SDK" value="true" /> |
|||
<option name="ADD_CONTENT_ROOTS" value="true" /> |
|||
<option name="ADD_SOURCE_ROOTS" value="true" /> |
|||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> |
|||
<option name="SCRIPT_NAME" value="C:\Users\毛健\PycharmProjects\CnOCRService\zk_util.py" /> |
|||
<option name="PARAMETERS" value="" /> |
|||
<option name="SHOW_COMMAND_LINE" value="false" /> |
|||
<option name="EMULATE_TERMINAL" value="false" /> |
|||
<option name="MODULE_MODE" value="false" /> |
|||
<option name="REDIRECT_INPUT" value="false" /> |
|||
<option name="INPUT_FILE" value="" /> |
|||
<method v="2" /> |
|||
</configuration> |
|||
<configuration name="zk_util" type="PythonConfigurationType" factoryName="Python" temporary="true"> |
|||
<module name="CnOCRService" /> |
|||
<option name="INTERPRETER_OPTIONS" value="" /> |
|||
<option name="PARENT_ENVS" value="true" /> |
|||
<envs> |
|||
<env name="PYTHONUNBUFFERED" value="1" /> |
|||
</envs> |
|||
<option name="SDK_HOME" value="" /> |
|||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> |
|||
<option name="IS_MODULE_SDK" value="true" /> |
|||
<option name="ADD_CONTENT_ROOTS" value="true" /> |
|||
<option name="ADD_SOURCE_ROOTS" value="true" /> |
|||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> |
|||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/zk_util.py" /> |
|||
<option name="PARAMETERS" value="" /> |
|||
<option name="SHOW_COMMAND_LINE" value="false" /> |
|||
<option name="EMULATE_TERMINAL" value="false" /> |
|||
<option name="MODULE_MODE" value="false" /> |
|||
<option name="REDIRECT_INPUT" value="false" /> |
|||
<option name="INPUT_FILE" value="" /> |
|||
<method v="2" /> |
|||
</configuration> |
|||
<recent_temporary> |
|||
<list> |
|||
<item itemvalue="Python.app" /> |
|||
<item itemvalue="Python.zk_util" /> |
|||
<item itemvalue="Python.load_scenes" /> |
|||
<item itemvalue="Python.zk_listener" /> |
|||
<item itemvalue="Python.dataUtil" /> |
|||
</list> |
|||
</recent_temporary> |
|||
</component> |
|||
<component name="SvnConfiguration"> |
|||
<configuration /> |
|||
</component> |
|||
<component name="TaskManager"> |
|||
<task active="true" id="Default" summary="Default task"> |
|||
<changelist id="62fb0e8b-1fdf-4b91-9565-798bb9e19152" name="Default Changelist" comment="" /> |
|||
<created>1726036962320</created> |
|||
<option name="number" value="Default" /> |
|||
<option name="presentableId" value="Default" /> |
|||
<updated>1726036962320</updated> |
|||
<workItem from="1726036966579" duration="22732000" /> |
|||
<workItem from="1726738964285" duration="662000" /> |
|||
<workItem from="1728701735578" duration="6034000" /> |
|||
<workItem from="1728872678803" duration="31923000" /> |
|||
<workItem from="1730278275853" duration="234000" /> |
|||
<workItem from="1730690391160" duration="45000" /> |
|||
<workItem from="1730692474286" duration="627000" /> |
|||
<workItem from="1730946197214" duration="2750000" /> |
|||
<workItem from="1731373999568" duration="2486000" /> |
|||
<workItem from="1731575462691" duration="6511000" /> |
|||
<workItem from="1731667531083" duration="18000" /> |
|||
<workItem from="1732499598045" duration="2391000" /> |
|||
<workItem from="1733278436428" duration="43000" /> |
|||
<workItem from="1735283750296" duration="646000" /> |
|||
</task> |
|||
<task id="LOCAL-00001" summary="新增zk监控,版本控制"> |
|||
<created>1728958677000</created> |
|||
<option name="number" value="00001" /> |
|||
<option name="presentableId" value="LOCAL-00001" /> |
|||
<option name="project" value="LOCAL" /> |
|||
<updated>1728958677000</updated> |
|||
</task> |
|||
<task id="LOCAL-00002" summary="新增zk监控,版本控制"> |
|||
<created>1728977076914</created> |
|||
<option name="number" value="00002" /> |
|||
<option name="presentableId" value="LOCAL-00002" /> |
|||
<option name="project" value="LOCAL" /> |
|||
<updated>1728977076914</updated> |
|||
</task> |
|||
<task id="LOCAL-00003" summary="新增zk监控,版本控制"> |
|||
<created>1728977089049</created> |
|||
<option name="number" value="00003" /> |
|||
<option name="presentableId" value="LOCAL-00003" /> |
|||
<option name="project" value="LOCAL" /> |
|||
<updated>1728977089049</updated> |
|||
</task> |
|||
<task id="LOCAL-00004" summary="新增zk监控,版本控制"> |
|||
<created>1728978278870</created> |
|||
<option name="number" value="00004" /> |
|||
<option name="presentableId" value="LOCAL-00004" /> |
|||
<option name="project" value="LOCAL" /> |
|||
<updated>1728978278870</updated> |
|||
</task> |
|||
<task id="LOCAL-00005" summary="新增zk监控,版本控制"> |
|||
<created>1728982994791</created> |
|||
<option name="number" value="00005" /> |
|||
<option name="presentableId" value="LOCAL-00005" /> |
|||
<option name="project" value="LOCAL" /> |
|||
<updated>1728982994791</updated> |
|||
</task> |
|||
<task id="LOCAL-00006" summary="新增zk监控,版本控制"> |
|||
<created>1728984597368</created> |
|||
<option name="number" value="00006" /> |
|||
<option name="presentableId" value="LOCAL-00006" /> |
|||
<option name="project" value="LOCAL" /> |
|||
<updated>1728984597368</updated> |
|||
</task> |
|||
<task id="LOCAL-00007" summary="新增调式模型逻辑"> |
|||
<created>1731579772217</created> |
|||
<option name="number" value="00007" /> |
|||
<option name="presentableId" value="LOCAL-00007" /> |
|||
<option name="project" value="LOCAL" /> |
|||
<updated>1731579772217</updated> |
|||
</task> |
|||
<task id="LOCAL-00008" summary="新增调式模型逻辑"> |
|||
<created>1731580317498</created> |
|||
<option name="number" value="00008" /> |
|||
<option name="presentableId" value="LOCAL-00008" /> |
|||
<option name="project" value="LOCAL" /> |
|||
<updated>1731580317498</updated> |
|||
</task> |
|||
<option name="localTasksCounter" value="9" /> |
|||
<servers /> |
|||
</component> |
|||
<component name="TimeTrackingManager"> |
|||
<option name="totallyTimeSpent" value="77102000" /> |
|||
</component> |
|||
<component name="TodoView" selected-index="4"> |
|||
<todo-panel id="selected-file"> |
|||
<is-autoscroll-to-source value="true" /> |
|||
</todo-panel> |
|||
<todo-panel id="all"> |
|||
<are-packages-shown value="true" /> |
|||
<is-autoscroll-to-source value="true" /> |
|||
</todo-panel> |
|||
</component> |
|||
<component name="ToolWindowManager"> |
|||
<frame x="-6" y="-6" width="1453" height="865" extended-state="6" /> |
|||
<layout> |
|||
<window_info id="Favorites" order="0" side_tool="true" /> |
|||
<window_info content_ui="combo" id="Project" order="1" visible="true" weight="0.14467184" /> |
|||
<window_info id="Structure" order="2" side_tool="true" weight="0.25" /> |
|||
<window_info anchor="bottom" id="Database Changes" order="0" /> |
|||
<window_info active="true" anchor="bottom" id="Terminal" order="1" sideWeight="0.4982357" visible="true" weight="0.32833788" /> |
|||
<window_info anchor="bottom" id="Run" order="2" sideWeight="0.5017643" side_tool="true" weight="0.32833788" /> |
|||
<window_info anchor="bottom" id="Event Log" order="3" sideWeight="0.50039744" side_tool="true" weight="0.3299532" /> |
|||
<window_info anchor="bottom" id="Version Control" order="4" weight="0.3299532" /> |
|||
<window_info anchor="bottom" id="Python Console" order="5" weight="0.32917318" /> |
|||
<window_info anchor="bottom" id="Docker" order="6" show_stripe_button="false" /> |
|||
<window_info anchor="bottom" id="Message" order="7" /> |
|||
<window_info anchor="bottom" id="Find" order="8" /> |
|||
<window_info anchor="bottom" id="Debug" order="9" sideWeight="0.49960256" weight="0.39937598" /> |
|||
<window_info anchor="bottom" id="Cvs" order="10" weight="0.25" /> |
|||
<window_info anchor="bottom" id="Inspection" order="11" weight="0.4" /> |
|||
<window_info anchor="bottom" id="TODO" order="12" sideWeight="0.49960256" weight="0.3299532" /> |
|||
<window_info anchor="right" id="Database" order="0" /> |
|||
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="1" type="SLIDING" weight="0.4" /> |
|||
<window_info anchor="right" id="SciView" order="2" /> |
|||
<window_info anchor="right" id="Ant Build" order="3" weight="0.25" /> |
|||
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="4" weight="0.25" /> |
|||
</layout> |
|||
<layout-to-restore> |
|||
<window_info id="Favorites" order="0" side_tool="true" /> |
|||
<window_info content_ui="combo" id="Project" order="1" visible="true" weight="0.13434023" /> |
|||
<window_info id="Structure" order="2" side_tool="true" weight="0.25" /> |
|||
<window_info anchor="bottom" id="Database Changes" order="0" /> |
|||
<window_info anchor="bottom" id="Terminal" order="1" sideWeight="0.49960256" weight="0.3299532" /> |
|||
<window_info active="true" anchor="bottom" id="Run" order="2" sideWeight="0.50039744" side_tool="true" visible="true" weight="0.39937598" /> |
|||
<window_info anchor="bottom" id="Event Log" order="3" side_tool="true" weight="0.3299532" /> |
|||
<window_info anchor="bottom" id="Version Control" order="4" weight="0.3299532" /> |
|||
<window_info anchor="bottom" id="Python Console" order="5" weight="0.32917318" /> |
|||
<window_info anchor="bottom" id="Docker" order="6" show_stripe_button="false" /> |
|||
<window_info anchor="bottom" id="Message" order="7" /> |
|||
<window_info anchor="bottom" id="Find" order="8" /> |
|||
<window_info anchor="bottom" id="Debug" order="9" sideWeight="0.49960256" weight="0.39937598" /> |
|||
<window_info anchor="bottom" id="Cvs" order="10" weight="0.25" /> |
|||
<window_info anchor="bottom" id="Inspection" order="11" weight="0.4" /> |
|||
<window_info anchor="bottom" id="TODO" order="12" weight="0.3299532" /> |
|||
<window_info anchor="right" id="Database" order="0" /> |
|||
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="1" type="SLIDING" weight="0.4" /> |
|||
<window_info anchor="right" id="SciView" order="2" /> |
|||
<window_info anchor="right" id="Ant Build" order="3" weight="0.25" /> |
|||
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="4" weight="0.25" /> |
|||
</layout-to-restore> |
|||
</component> |
|||
<component name="TypeScriptGeneratedFilesManager"> |
|||
<option name="version" value="1" /> |
|||
</component> |
|||
<component name="Vcs.Log.History.Properties"> |
|||
<option name="COLUMN_ORDER"> |
|||
<list> |
|||
<option value="0" /> |
|||
<option value="2" /> |
|||
<option value="3" /> |
|||
<option value="1" /> |
|||
</list> |
|||
</option> |
|||
</component> |
|||
<component name="VcsManagerConfiguration"> |
|||
<MESSAGE value="新增zk监控,版本控制" /> |
|||
<MESSAGE value="新增调式模型逻辑" /> |
|||
<option name="LAST_COMMIT_MESSAGE" value="新增调式模型逻辑" /> |
|||
</component> |
|||
<component name="editorHistoryManager"> |
|||
<entry file="file://$PROJECT_DIR$/Lib/site-packages/kafka/producer/kafka.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="688"> |
|||
<caret line="547" column="52" lean-forward="true" selection-start-line="542" selection-start-column="30" selection-end-line="547" selection-end-column="52" /> |
|||
<folding> |
|||
<element signature="e#0#38#0" expanded="true" /> |
|||
</folding> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
<entry file="file://$USER_HOME$/AppData/Local/Programs/Python/Python37/Lib/logging/__init__.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="277"> |
|||
<caret line="1929" column="4" selection-start-line="1929" selection-start-column="4" selection-end-line="1929" selection-end-column="4" /> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
<entry file="file://$USER_HOME$/AppData/Local/Programs/Python/Python37/Lib/queue.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="444"> |
|||
<caret line="91" column="68" lean-forward="true" selection-start-line="91" selection-start-column="11" selection-end-line="91" selection-end-column="68" /> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
<entry file="file://$PROJECT_DIR$/config.yml"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="270"> |
|||
<caret line="10" selection-end-line="10" /> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
<entry file="file://$PROJECT_DIR$/zk_util.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="675"> |
|||
<caret line="31" column="38" selection-start-line="31" selection-start-column="38" selection-end-line="31" selection-end-column="38" /> |
|||
<folding> |
|||
<element signature="e#13#49#0" expanded="true" /> |
|||
</folding> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
<entry file="file://$PROJECT_DIR$/load_scenes.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="189"> |
|||
<caret line="9" column="19" selection-start-line="9" selection-start-column="19" selection-end-line="9" selection-end-column="19" /> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
<entry file="file://$PROJECT_DIR$/dataUtil.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="189"> |
|||
<caret line="7" column="4" selection-start-line="7" selection-start-column="4" selection-end-line="7" selection-end-column="4" /> |
|||
<folding> |
|||
<element signature="e#13#24#0" expanded="true" /> |
|||
</folding> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
<entry file="file://$PROJECT_DIR$/Lib/site-packages/flask_restful/__init__.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="417"> |
|||
<caret line="378" column="8" selection-start-line="378" selection-start-column="8" selection-end-line="378" selection-end-column="8" /> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
<entry file="file://$PROJECT_DIR$/queue_manager.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="171"> |
|||
<caret line="9" column="12" selection-start-line="9" selection-start-column="12" selection-end-line="9" selection-end-column="12" /> |
|||
<folding> |
|||
<element signature="e#13#42#0" expanded="true" /> |
|||
</folding> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
<entry file="file://$PROJECT_DIR$/global_dict.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="19"> |
|||
<caret line="1" selection-start-line="1" selection-end-line="1" /> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
<entry file="file://$PROJECT_DIR$/app.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="266"> |
|||
<caret line="101" column="76" selection-start-line="101" selection-start-column="76" selection-end-line="101" selection-end-column="76" /> |
|||
<folding> |
|||
<element signature="e#14#27#0" expanded="true" /> |
|||
</folding> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
<entry file="file://$PROJECT_DIR$/config_loader.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="114"> |
|||
<caret line="6" column="48" selection-start-line="6" selection-start-column="48" selection-end-line="6" selection-end-column="48" /> |
|||
<folding> |
|||
<element signature="e#13#24#0" expanded="true" /> |
|||
</folding> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
<entry file="file://$PROJECT_DIR$/CnOcr.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="342"> |
|||
<caret line="18" column="5" lean-forward="true" selection-start-line="18" selection-start-column="5" selection-end-line="18" selection-end-column="5" /> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
<entry file="file://$PROJECT_DIR$/ocr_handler.py"> |
|||
<provider selected="true" editor-type-id="text-editor"> |
|||
<state relative-caret-position="57"> |
|||
<caret line="3" column="10" selection-start-line="3" selection-start-column="10" selection-end-line="3" selection-end-column="10" /> |
|||
</state> |
|||
</provider> |
|||
</entry> |
|||
</component> |
|||
</project> |
@ -0,0 +1,19 @@ |
|||
class CnOcr(object): |
|||
def __init__( |
|||
self, |
|||
rec_model_name: str = 'densenet_lite_136-fc', |
|||
*, |
|||
det_model_name: str = 'ch_PP-OCRv3_det', |
|||
cand_alphabet: Optional[Union[Collection, str]] = None, |
|||
context: str = 'cpu', # ['cpu', 'gpu', 'cuda'] |
|||
rec_model_fp: Optional[str] = None, |
|||
rec_model_backend: str = 'onnx', # ['pytorch', 'onnx'] |
|||
rec_vocab_fp: Union[str, Path] = VOCAB_FP, |
|||
rec_more_configs: Optional[Dict[str, Any]] = None, |
|||
rec_root: Union[str, Path] = data_dir(), |
|||
det_model_fp: Optional[str] = None, |
|||
det_model_backend: str = 'onnx', # ['pytorch', 'onnx'] |
|||
det_more_configs: Optional[Dict[str, Any]] = None, |
|||
det_root: Union[str, Path] = det_data_dir(), |
|||
**kwargs, |
|||
) |
@ -0,0 +1 @@ |
|||
pip |
@ -0,0 +1,28 @@ |
|||
Copyright 2010 Pallets |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are |
|||
met: |
|||
|
|||
1. Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
|
|||
2. Redistributions in binary form must reproduce the above copyright |
|||
notice, this list of conditions and the following disclaimer in the |
|||
documentation and/or other materials provided with the distribution. |
|||
|
|||
3. Neither the name of the copyright holder nor the names of its |
|||
contributors may be used to endorse or promote products derived from |
|||
this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
|||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
|||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,123 @@ |
|||
Metadata-Version: 2.1 |
|||
Name: Flask |
|||
Version: 2.2.5 |
|||
Summary: A simple framework for building complex web applications. |
|||
Home-page: https://palletsprojects.com/p/flask |
|||
Author: Armin Ronacher |
|||
Author-email: armin.ronacher@active-4.com |
|||
Maintainer: Pallets |
|||
Maintainer-email: contact@palletsprojects.com |
|||
License: BSD-3-Clause |
|||
Project-URL: Donate, https://palletsprojects.com/donate |
|||
Project-URL: Documentation, https://flask.palletsprojects.com/ |
|||
Project-URL: Changes, https://flask.palletsprojects.com/changes/ |
|||
Project-URL: Source Code, https://github.com/pallets/flask/ |
|||
Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/ |
|||
Project-URL: Twitter, https://twitter.com/PalletsTeam |
|||
Project-URL: Chat, https://discord.gg/pallets |
|||
Classifier: Development Status :: 5 - Production/Stable |
|||
Classifier: Environment :: Web Environment |
|||
Classifier: Framework :: Flask |
|||
Classifier: Intended Audience :: Developers |
|||
Classifier: License :: OSI Approved :: BSD License |
|||
Classifier: Operating System :: OS Independent |
|||
Classifier: Programming Language :: Python |
|||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content |
|||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI |
|||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application |
|||
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks |
|||
Requires-Python: >=3.7 |
|||
Description-Content-Type: text/x-rst |
|||
License-File: LICENSE.rst |
|||
Requires-Dist: Werkzeug (>=2.2.2) |
|||
Requires-Dist: Jinja2 (>=3.0) |
|||
Requires-Dist: itsdangerous (>=2.0) |
|||
Requires-Dist: click (>=8.0) |
|||
Requires-Dist: importlib-metadata (>=3.6.0) ; python_version < "3.10" |
|||
Provides-Extra: async |
|||
Requires-Dist: asgiref (>=3.2) ; extra == 'async' |
|||
Provides-Extra: dotenv |
|||
Requires-Dist: python-dotenv ; extra == 'dotenv' |
|||
|
|||
Flask |
|||
===== |
|||
|
|||
Flask is a lightweight `WSGI`_ web application framework. It is designed |
|||
to make getting started quick and easy, with the ability to scale up to |
|||
complex applications. It began as a simple wrapper around `Werkzeug`_ |
|||
and `Jinja`_ and has become one of the most popular Python web |
|||
application frameworks. |
|||
|
|||
Flask offers suggestions, but doesn't enforce any dependencies or |
|||
project layout. It is up to the developer to choose the tools and |
|||
libraries they want to use. There are many extensions provided by the |
|||
community that make adding new functionality easy. |
|||
|
|||
.. _WSGI: https://wsgi.readthedocs.io/ |
|||
.. _Werkzeug: https://werkzeug.palletsprojects.com/ |
|||
.. _Jinja: https://jinja.palletsprojects.com/ |
|||
|
|||
|
|||
Installing |
|||
---------- |
|||
|
|||
Install and update using `pip`_: |
|||
|
|||
.. code-block:: text |
|||
|
|||
$ pip install -U Flask |
|||
|
|||
.. _pip: https://pip.pypa.io/en/stable/getting-started/ |
|||
|
|||
|
|||
A Simple Example |
|||
---------------- |
|||
|
|||
.. code-block:: python |
|||
|
|||
# save this as app.py |
|||
from flask import Flask |
|||
|
|||
app = Flask(__name__) |
|||
|
|||
@app.route("/") |
|||
def hello(): |
|||
return "Hello, World!" |
|||
|
|||
.. code-block:: text |
|||
|
|||
$ flask run |
|||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) |
|||
|
|||
|
|||
Contributing |
|||
------------ |
|||
|
|||
For guidance on setting up a development environment and how to make a |
|||
contribution to Flask, see the `contributing guidelines`_. |
|||
|
|||
.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst |
|||
|
|||
|
|||
Donate |
|||
------ |
|||
|
|||
The Pallets organization develops and supports Flask and the libraries |
|||
it uses. In order to grow the community of contributors and users, and |
|||
allow the maintainers to devote more time to the projects, `please |
|||
donate today`_. |
|||
|
|||
.. _please donate today: https://palletsprojects.com/donate |
|||
|
|||
|
|||
Links |
|||
----- |
|||
|
|||
- Documentation: https://flask.palletsprojects.com/ |
|||
- Changes: https://flask.palletsprojects.com/changes/ |
|||
- PyPI Releases: https://pypi.org/project/Flask/ |
|||
- Source Code: https://github.com/pallets/flask/ |
|||
- Issue Tracker: https://github.com/pallets/flask/issues/ |
|||
- Website: https://palletsprojects.com/p/flask/ |
|||
- Twitter: https://twitter.com/PalletsTeam |
|||
- Chat: https://discord.gg/pallets |
@ -0,0 +1,53 @@ |
|||
flask/__init__.py,sha256=GJgAILDWhW_DQljuoJ4pk9zBUy70zPPu-VZ6kLyiVI4,2890 |
|||
flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30 |
|||
flask/app.py,sha256=ue4tEeDnr3m-eSEwz7OJ1_wafSYl3fl6eo-NLFgNNJQ,99141 |
|||
flask/blueprints.py,sha256=fenhKP_Sh5eU6qtWeHacg1GVeun4pQzK2vq8sNDd1hY,27266 |
|||
flask/cli.py,sha256=pLmnWObe_G4_ZAFQdh7kgwqPMxRXm4oUhaUSBpJMeq4,33532 |
|||
flask/config.py,sha256=Ubo_juzSYsAKqD2vD3vm6mjsPo3EOJDdSEzYq8lKTJI,12585 |
|||
flask/ctx.py,sha256=bGEQQuF2_cHqZ3ZNMeMeEG8HOLJkDlL88u2BBxCrRao,14829 |
|||
flask/debughelpers.py,sha256=_RvAL3TW5lqMJeCVWtTU6rSDJC7jnRaBL6OEkVmooyU,5511 |
|||
flask/globals.py,sha256=EX0XdX73BTWdVF0UHDSNet2ER3kI6sKveo3_o5IOs98,3187 |
|||
flask/helpers.py,sha256=XTHRgLlyxeEzR988q63-4OY8RswTscR-5exFxK10CLU,25280 |
|||
flask/logging.py,sha256=WYng0bLTRS_CJrocGcCLJpibHf1lygHE_pg-KoUIQ4w,2293 |
|||
flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 |
|||
flask/scaffold.py,sha256=EKx-Tr5BXLzeKKvq3ZAi_2oUQVZuC4OJSJTocyDXsSo,35958 |
|||
flask/sessions.py,sha256=adWCRnJYETJcjjhlcvUgZR5S0DMqKQctS0nzkY9g9Us,15927 |
|||
flask/signals.py,sha256=H7QwDciK-dtBxinjKpexpglP0E6k0MJILiFWTItfmqU,2136 |
|||
flask/templating.py,sha256=1P4OzvSnA2fsJTYgQT3G4owVKsuOz8XddCiR6jMHGJ0,7419 |
|||
flask/testing.py,sha256=JtHRQY7mIH39SM4S51svAr8e7Xk87dqMb30Z6Dyv9TA,10706 |
|||
flask/typing.py,sha256=KgxegTF9v9WvuongeF8LooIvpZPauzGrq9ZXf3gBlYc,2969 |
|||
flask/views.py,sha256=LulttWL4owVFlgwrJi8GCNM4inC3xbs2IBlY31bdCS4,6765 |
|||
flask/wrappers.py,sha256=el3tn1LgSUV0eNGgYMjKICT5I7qGJgbpIhvci4nrwQ8,5702 |
|||
flask/json/__init__.py,sha256=TOwldHT3_kFaXHlORKi9yCWt7dbPNB0ovdHHQWlSRzY,11175 |
|||
flask/json/provider.py,sha256=jXCNypf11PN4ngQjEt6LnSdCWQ1yHIAkNLHlXQlCB-A,10674 |
|||
flask/json/tag.py,sha256=fys3HBLssWHuMAIJuTcf2K0bCtosePBKXIWASZEEjnU,8857 |
|||
Flask-2.2.5.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 |
|||
Flask-2.2.5.dist-info/METADATA,sha256=rZTjr5v4M7HB-zC-w2Y0ZU96OYSGBb-Hm15jlLJhs3g,3889 |
|||
Flask-2.2.5.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92 |
|||
Flask-2.2.5.dist-info/entry_points.txt,sha256=s3MqQpduU25y4dq3ftBYD6bMVdVnbMpZP-sUNw0zw0k,41 |
|||
Flask-2.2.5.dist-info/top_level.txt,sha256=dvi65F6AeGWVU0TBpYiC04yM60-FX1gJFkK31IKQr5c,6 |
|||
Flask-2.2.5.dist-info/RECORD,, |
|||
../../Scripts/flask.exe,sha256=CT8LhI-frVdxvcOzhTdJW6AhmXXe_S6MOnhXlUfMyR8,102780 |
|||
Flask-2.2.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 |
|||
flask/json/__pycache__/provider.cpython-37.pyc,, |
|||
flask/json/__pycache__/tag.cpython-37.pyc,, |
|||
flask/json/__pycache__/__init__.cpython-37.pyc,, |
|||
flask/__pycache__/app.cpython-37.pyc,, |
|||
flask/__pycache__/blueprints.cpython-37.pyc,, |
|||
flask/__pycache__/cli.cpython-37.pyc,, |
|||
flask/__pycache__/config.cpython-37.pyc,, |
|||
flask/__pycache__/ctx.cpython-37.pyc,, |
|||
flask/__pycache__/debughelpers.cpython-37.pyc,, |
|||
flask/__pycache__/globals.cpython-37.pyc,, |
|||
flask/__pycache__/helpers.cpython-37.pyc,, |
|||
flask/__pycache__/logging.cpython-37.pyc,, |
|||
flask/__pycache__/scaffold.cpython-37.pyc,, |
|||
flask/__pycache__/sessions.cpython-37.pyc,, |
|||
flask/__pycache__/signals.cpython-37.pyc,, |
|||
flask/__pycache__/templating.cpython-37.pyc,, |
|||
flask/__pycache__/testing.cpython-37.pyc,, |
|||
flask/__pycache__/typing.cpython-37.pyc,, |
|||
flask/__pycache__/views.cpython-37.pyc,, |
|||
flask/__pycache__/wrappers.cpython-37.pyc,, |
|||
flask/__pycache__/__init__.cpython-37.pyc,, |
|||
flask/__pycache__/__main__.cpython-37.pyc,, |
@ -0,0 +1,5 @@ |
|||
Wheel-Version: 1.0 |
|||
Generator: bdist_wheel (0.40.0) |
|||
Root-Is-Purelib: true |
|||
Tag: py3-none-any |
|||
|
@ -0,0 +1,2 @@ |
|||
[console_scripts] |
|||
flask = flask.cli:main |
@ -0,0 +1 @@ |
|||
flask |
@ -0,0 +1 @@ |
|||
pip |
@ -0,0 +1,7 @@ |
|||
Copyright (C) 2016 Cory Dolphin, Olin College |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,148 @@ |
|||
Metadata-Version: 2.1 |
|||
Name: Flask-Cors |
|||
Version: 5.0.0 |
|||
Summary: A Flask extension adding a decorator for CORS support |
|||
Home-page: https://github.com/corydolphin/flask-cors |
|||
Author: Cory Dolphin |
|||
Author-email: corydolphin@gmail.com |
|||
License: MIT |
|||
Platform: any |
|||
Classifier: Environment :: Web Environment |
|||
Classifier: Intended Audience :: Developers |
|||
Classifier: License :: OSI Approved :: MIT License |
|||
Classifier: Operating System :: OS Independent |
|||
Classifier: Programming Language :: Python |
|||
Classifier: Programming Language :: Python :: 3.8 |
|||
Classifier: Programming Language :: Python :: 3.9 |
|||
Classifier: Programming Language :: Python :: 3.10 |
|||
Classifier: Programming Language :: Python :: 3.11 |
|||
Classifier: Programming Language :: Python :: 3.12 |
|||
Classifier: Programming Language :: Python :: Implementation :: CPython |
|||
Classifier: Programming Language :: Python :: Implementation :: PyPy |
|||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content |
|||
Classifier: Topic :: Software Development :: Libraries :: Python Modules |
|||
License-File: LICENSE |
|||
Requires-Dist: Flask >=0.9 |
|||
|
|||
Flask-CORS |
|||
========== |
|||
|
|||
|Build Status| |Latest Version| |Supported Python versions| |
|||
|License| |
|||
|
|||
A Flask extension for handling Cross Origin Resource Sharing (CORS), making cross-origin AJAX possible. |
|||
|
|||
This package has a simple philosophy: when you want to enable CORS, you wish to enable it for all use cases on a domain. |
|||
This means no mucking around with different allowed headers, methods, etc. |
|||
|
|||
By default, submission of cookies across domains is disabled due to the security implications. |
|||
Please see the documentation for how to enable credential'ed requests, and please make sure you add some sort of `CSRF <http://en.wikipedia.org/wiki/Cross-site_request_forgery>`__ protection before doing so! |
|||
|
|||
Installation |
|||
------------ |
|||
|
|||
Install the extension with using pip, or easy\_install. |
|||
|
|||
.. code:: bash |
|||
|
|||
$ pip install -U flask-cors |
|||
|
|||
Usage |
|||
----- |
|||
|
|||
This package exposes a Flask extension which by default enables CORS support on all routes, for all origins and methods. |
|||
It allows parameterization of all CORS headers on a per-resource level. |
|||
The package also contains a decorator, for those who prefer this approach. |
|||
|
|||
Simple Usage |
|||
~~~~~~~~~~~~ |
|||
|
|||
In the simplest case, initialize the Flask-Cors extension with default arguments in order to allow CORS for all domains on all routes. |
|||
See the full list of options in the `documentation <https://flask-cors.corydolphin.com/en/latest/api.html#extension>`__. |
|||
|
|||
.. code:: python |
|||
|
|||
|
|||
from flask import Flask |
|||
from flask_cors import CORS |
|||
|
|||
app = Flask(__name__) |
|||
CORS(app) |
|||
|
|||
@app.route("/") |
|||
def helloWorld(): |
|||
return "Hello, cross-origin-world!" |
|||
|
|||
Resource specific CORS |
|||
^^^^^^^^^^^^^^^^^^^^^^ |
|||
|
|||
Alternatively, you can specify CORS options on a resource and origin level of granularity by passing a dictionary as the `resources` option, mapping paths to a set of options. |
|||
See the full list of options in the `documentation <https://flask-cors.corydolphin.com/en/latest/api.html#extension>`__. |
|||
|
|||
.. code:: python |
|||
|
|||
app = Flask(__name__) |
|||
cors = CORS(app, resources={r"/api/*": {"origins": "*"}}) |
|||
|
|||
@app.route("/api/v1/users") |
|||
def list_users(): |
|||
return "user example" |
|||
|
|||
Route specific CORS via decorator |
|||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|||
|
|||
This extension also exposes a simple decorator to decorate flask routes with. |
|||
Simply add ``@cross_origin()`` below a call to Flask's ``@app.route(..)`` to allow CORS on a given route. |
|||
See the full list of options in the `decorator documentation <https://flask-cors.corydolphin.com/en/latest/api.html#decorator>`__. |
|||
|
|||
.. code:: python |
|||
|
|||
@app.route("/") |
|||
@cross_origin() |
|||
def helloWorld(): |
|||
return "Hello, cross-origin-world!" |
|||
|
|||
Documentation |
|||
------------- |
|||
|
|||
For a full list of options, please see the full `documentation <https://flask-cors.corydolphin.com/en/latest/api.html>`__ |
|||
|
|||
Troubleshooting |
|||
--------------- |
|||
|
|||
If things aren't working as you expect, enable logging to help understand what is going on under the hood, and why. |
|||
|
|||
.. code:: python |
|||
|
|||
logging.getLogger('flask_cors').level = logging.DEBUG |
|||
|
|||
|
|||
Tests |
|||
----- |
|||
|
|||
A simple set of tests is included in ``test/``. |
|||
To run, install nose, and simply invoke ``nosetests`` or ``python setup.py test`` to exercise the tests. |
|||
|
|||
If nosetests does not work for you, due to it no longer working with newer python versions. |
|||
You can use pytest to run the tests instead. |
|||
|
|||
Contributing |
|||
------------ |
|||
|
|||
Questions, comments or improvements? |
|||
Please create an issue on `Github <https://github.com/corydolphin/flask-cors>`__, tweet at `@corydolphin <https://twitter.com/corydolphin>`__ or send me an email. |
|||
I do my best to include every contribution proposed in any way that I can. |
|||
|
|||
Credits |
|||
------- |
|||
|
|||
This Flask extension is based upon the `Decorator for the HTTP Access Control <https://web.archive.org/web/20190128010149/http://flask.pocoo.org/snippets/56/>`__ written by Armin Ronacher. |
|||
|
|||
.. |Build Status| image:: https://github.com/corydolphin/flask-cors/actions/workflows/unittests.yaml/badge.svg |
|||
:target: https://travis-ci.org/corydolphin/flask-cors |
|||
.. |Latest Version| image:: https://img.shields.io/pypi/v/Flask-Cors.svg |
|||
:target: https://pypi.python.org/pypi/Flask-Cors/ |
|||
.. |Supported Python versions| image:: https://img.shields.io/pypi/pyversions/Flask-Cors.svg |
|||
:target: https://img.shields.io/pypi/pyversions/Flask-Cors.svg |
|||
.. |License| image:: http://img.shields.io/:license-mit-blue.svg |
|||
:target: https://pypi.python.org/pypi/Flask-Cors/ |
@ -0,0 +1,16 @@ |
|||
flask_cors/__init__.py,sha256=wZDCvPTHspA2g1VV7KyKN7R-uCdBnirTlsCzgPDcQtI,792 |
|||
flask_cors/core.py,sha256=y76xxLasWTdV_3ka19IxpdJPOgROBZQZ5L8t20IjqRA,14252 |
|||
flask_cors/decorator.py,sha256=BeJsyX1wYhVKWN04FAhb6z8YqffiRr7wKqwzHPap4bw,5009 |
|||
flask_cors/extension.py,sha256=gzv6zWUwSDYlGHBWzMuTI_hoQ7gQmp9DlcAcrKTVHdw,8602 |
|||
flask_cors/version.py,sha256=JzYPYpvaglqIJRGCDrh5-hYmXI0ISrDDed0V1QQZAGU,22 |
|||
Flask_Cors-5.0.0.dist-info/LICENSE,sha256=bhob3FSDTB4HQMvOXV9vLK4chG_Sp_SCsRZJWU-vvV0,1069 |
|||
Flask_Cors-5.0.0.dist-info/METADATA,sha256=V2L_s849dFlZXsOhcgXVqv5Slj_JKSVuiiuRgDOft5s,5474 |
|||
Flask_Cors-5.0.0.dist-info/WHEEL,sha256=WDDPHYzpiOIm6GP1C2_8y8W6q16ICddAgOHlhTje9Qc,109 |
|||
Flask_Cors-5.0.0.dist-info/top_level.txt,sha256=aWye_0QNZPp_QtPF4ZluLHqnyVLT9CPJsfiGhwqkWuo,11 |
|||
Flask_Cors-5.0.0.dist-info/RECORD,, |
|||
Flask_Cors-5.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 |
|||
flask_cors/__pycache__/core.cpython-37.pyc,, |
|||
flask_cors/__pycache__/decorator.cpython-37.pyc,, |
|||
flask_cors/__pycache__/extension.cpython-37.pyc,, |
|||
flask_cors/__pycache__/version.cpython-37.pyc,, |
|||
flask_cors/__pycache__/__init__.cpython-37.pyc,, |
@ -0,0 +1,6 @@ |
|||
Wheel-Version: 1.0 |
|||
Generator: setuptools (74.0.0) |
|||
Root-Is-Purelib: true |
|||
Tag: py2-none-any |
|||
Tag: py3-none-any |
|||
|
@ -0,0 +1 @@ |
|||
flask_cors |
@ -0,0 +1,132 @@ |
|||
Authors |
|||
======= |
|||
|
|||
A huge thanks to all of our contributors: |
|||
|
|||
- Adam Chainz |
|||
- Alec Nikolas Reiter |
|||
- Alex Gaynor |
|||
- Alex M |
|||
- Alex Morken |
|||
- Andrew Dunham |
|||
- Andriy Yurchuk |
|||
- Anil Kulkarni |
|||
- Antonio Dourado |
|||
- Antonio Herraiz |
|||
- Ares Ou |
|||
- Artur Rodrigues |
|||
- Axel Haustant |
|||
- Belousow Makc |
|||
- Benjamin Dopplinger |
|||
- Bennett, Bryan |
|||
- Bohan Zhang |
|||
- Bryan Bennett |
|||
- Bulat Bochkariov |
|||
- Cameron Brandon White |
|||
- Catherine Devlin |
|||
- Dan Quirk |
|||
- Daniele Esposti |
|||
- Dario Bertini |
|||
- David Arnold |
|||
- David Baumgold |
|||
- David Boucha |
|||
- David Crawford |
|||
- Dimitris Theodorou |
|||
- Doug Black |
|||
- Evan Dale Aromin |
|||
- Eyal Levin |
|||
- Francesco Della Vedova |
|||
- Frank Stratton |
|||
- Garret Raziel |
|||
- Gary Belvin |
|||
- Gilles Dartiguelongue |
|||
- Giorgio Salluzzo |
|||
- Guillaume BINET |
|||
- Heston Liebowitz |
|||
- Hu WQ |
|||
- Jacob Magnusson |
|||
- James Booth |
|||
- James Ogura |
|||
- James Turk |
|||
- Jeff Widman |
|||
- Joakim Ekberg |
|||
- Johannes |
|||
- Jordan Yelloz |
|||
- Josh Friend |
|||
- Joshua C. Randall |
|||
- Joshua Randall |
|||
- José Fernández Ramos |
|||
- Juan Rossi |
|||
- JuneHyeon Bae |
|||
- Kamil Gałuszka |
|||
- Kevin Burke |
|||
- Kevin Deldycke |
|||
- Kevin Funk |
|||
- Kyle Conroy |
|||
- Lance Ingle |
|||
- Lars Holm Nielsen |
|||
- Luiz Armesto |
|||
- Malthe Borch |
|||
- Marek Hlobil |
|||
- Matt Wright |
|||
- Max Mautner |
|||
- Max Peterson |
|||
- Maxim |
|||
- Michael Hwang |
|||
- Michael Newman |
|||
- Miguel Grinberg |
|||
- Mihai Tomescu |
|||
- Neil Halelamien |
|||
- Nicolas Harraudeau |
|||
- Pavel Tyslyatsky |
|||
- Petrus J.v.Rensburg |
|||
- Philippe Ndiaye |
|||
- Piotr Husiatyński |
|||
- Prasanna Swaminathan |
|||
- Robert Warner |
|||
- Rod Cloutier |
|||
- Ryan Horn |
|||
- Rémi Alvergnat |
|||
- Sam Kimbrel |
|||
- Samarth Shah |
|||
- Sami Jaktholm |
|||
- Sander Sink |
|||
- Sasha Baranov |
|||
- Saul Diez-Guerra |
|||
- Sergey Romanov |
|||
- Shreyans Sheth |
|||
- Steven Leggett |
|||
- Sven-Hendrik Haase |
|||
- Usman Ehtesham Gul |
|||
- Victor Neo |
|||
- Vlad Frolov |
|||
- Vladimir Pal |
|||
- WooParadog |
|||
- Yaniv Aknin |
|||
- akash |
|||
- bret barker |
|||
- hachichaud |
|||
- jbouzekri |
|||
- jobou |
|||
- johnrichter |
|||
- justanr |
|||
- k-funk |
|||
- kelvinhammond |
|||
- kenjones |
|||
- kieran gorman |
|||
- kumy |
|||
- lyschoening |
|||
- mailto1587 |
|||
- mniebla |
|||
- mozillazg |
|||
- muchosalsa |
|||
- nachinius |
|||
- nixdata |
|||
- papaeye |
|||
- pingz |
|||
- saml |
|||
- siavashg |
|||
- silasray |
|||
- soasme |
|||
- ueg1990 |
|||
- y-p |
@ -0,0 +1 @@ |
|||
pip |
@ -0,0 +1,25 @@ |
|||
Copyright (c) 2013, Twilio, Inc. |
|||
All rights reserved. |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are met: |
|||
|
|||
- Redistributions of source code must retain the above copyright notice, this |
|||
list of conditions and the following disclaimer. |
|||
- Redistributions in binary form must reproduce the above copyright notice, |
|||
this list of conditions and the following disclaimer in the documentation |
|||
and/or other materials provided with the distribution. |
|||
- Neither the name of the Twilio, Inc. nor the names of its contributors may be |
|||
used to endorse or promote products derived from this software without |
|||
specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
|||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
|||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
|||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,29 @@ |
|||
Metadata-Version: 2.1 |
|||
Name: Flask-RESTful |
|||
Version: 0.3.10 |
|||
Summary: Simple framework for creating REST APIs |
|||
Home-page: https://www.github.com/flask-restful/flask-restful/ |
|||
Author: Twilio API Team |
|||
Author-email: help@twilio.com |
|||
License: BSD |
|||
Project-URL: Source, https://github.com/flask-restful/flask-restful |
|||
Platform: any |
|||
Classifier: Framework :: Flask |
|||
Classifier: Programming Language :: Python :: 2 |
|||
Classifier: Programming Language :: Python :: 2.7 |
|||
Classifier: Programming Language :: Python :: 3 |
|||
Classifier: Programming Language :: Python :: 3.4 |
|||
Classifier: Programming Language :: Python :: 3.5 |
|||
Classifier: Programming Language :: Python :: 3.6 |
|||
Classifier: Programming Language :: Python :: 3.7 |
|||
Classifier: Programming Language :: Python :: 3.8 |
|||
Classifier: License :: OSI Approved :: BSD License |
|||
License-File: LICENSE |
|||
License-File: AUTHORS.md |
|||
Requires-Dist: aniso8601 (>=0.82) |
|||
Requires-Dist: Flask (>=0.8) |
|||
Requires-Dist: six (>=1.3.0) |
|||
Requires-Dist: pytz |
|||
Provides-Extra: docs |
|||
Requires-Dist: sphinx ; extra == 'docs' |
|||
|
@ -0,0 +1,27 @@ |
|||
flask_restful/__init__.py,sha256=KDyCbekXcfGMyV6E7neY6ZJ8b8GdM6eLtJbtRmn_nL8,28624 |
|||
flask_restful/__version__.py,sha256=JbZfv76t9J7HHmoA2wdjKemYHpQE0jhBfMJIil6HEsg,46 |
|||
flask_restful/fields.py,sha256=43GbFejZ3kiOb20A1QuzLXjevfsxMZSbmpOpGtW56vo,13018 |
|||
flask_restful/inputs.py,sha256=561w8fjLqBq4I_7yXPHJM567ijWhpuf8d8uZnKzTehA,9118 |
|||
flask_restful/reqparse.py,sha256=-xZmkyrvDFfGvFFokTtXe4J-2PWnNX4EfKolhkT995E,14681 |
|||
flask_restful/representations/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 |
|||
flask_restful/representations/json.py,sha256=swKwnbt7v2ioHfHkqhqbzIu_yrcP0ComlSl49IGFJOo,873 |
|||
flask_restful/utils/__init__.py,sha256=jgedvOLGeTk4Sqox4WHE_vAFLP0T_PrLHO4PXaqFqxw,723 |
|||
flask_restful/utils/cors.py,sha256=cZiqaHhIn0w66spRoSIdC-jIn4X_b6OlVms5eGF4Ess,2084 |
|||
flask_restful/utils/crypto.py,sha256=q3PBvAYMJYybbqqQlKNF_Pqeyo9h3x5jFJuVqtEA5bA,996 |
|||
Flask_RESTful-0.3.10.dist-info/AUTHORS.md,sha256=HBq00z_VgMI2xfwfUobrU16_qamdouMkpNxbR0BzaVg,1992 |
|||
Flask_RESTful-0.3.10.dist-info/LICENSE,sha256=PFjoO0Jk5okmshAgMix5-RZTC0sFT_EJWpC_CtQcCyM,1485 |
|||
Flask_RESTful-0.3.10.dist-info/METADATA,sha256=eTeg3NLzPPlJxKSMhedGPPQvRaQm-9lMafpxwIddLT8,1018 |
|||
Flask_RESTful-0.3.10.dist-info/WHEEL,sha256=a-zpFRIJzOq5QfuhBzbhiA1eHTzNCJn8OdRvhdNX0Rk,110 |
|||
Flask_RESTful-0.3.10.dist-info/top_level.txt,sha256=lNpWPlejgBAtMhCUwz_FTyJH12ul1mBZ-Uv3ZK1HiGg,14 |
|||
Flask_RESTful-0.3.10.dist-info/RECORD,, |
|||
Flask_RESTful-0.3.10.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 |
|||
flask_restful/representations/__pycache__/json.cpython-37.pyc,, |
|||
flask_restful/representations/__pycache__/__init__.cpython-37.pyc,, |
|||
flask_restful/utils/__pycache__/cors.cpython-37.pyc,, |
|||
flask_restful/utils/__pycache__/crypto.cpython-37.pyc,, |
|||
flask_restful/utils/__pycache__/__init__.cpython-37.pyc,, |
|||
flask_restful/__pycache__/fields.cpython-37.pyc,, |
|||
flask_restful/__pycache__/inputs.cpython-37.pyc,, |
|||
flask_restful/__pycache__/reqparse.cpython-37.pyc,, |
|||
flask_restful/__pycache__/__init__.cpython-37.pyc,, |
|||
flask_restful/__pycache__/__version__.cpython-37.pyc,, |
@ -0,0 +1,6 @@ |
|||
Wheel-Version: 1.0 |
|||
Generator: bdist_wheel (0.40.0) |
|||
Root-Is-Purelib: true |
|||
Tag: py2-none-any |
|||
Tag: py3-none-any |
|||
|
@ -0,0 +1 @@ |
|||
flask_restful |
@ -0,0 +1 @@ |
|||
pip |
@ -0,0 +1,28 @@ |
|||
Copyright 2010 Pallets |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are |
|||
met: |
|||
|
|||
1. Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
|
|||
2. Redistributions in binary form must reproduce the above copyright |
|||
notice, this list of conditions and the following disclaimer in the |
|||
documentation and/or other materials provided with the distribution. |
|||
|
|||
3. Neither the name of the copyright holder nor the names of its |
|||
contributors may be used to endorse or promote products derived from |
|||
this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
|||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
|||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,93 @@ |
|||
Metadata-Version: 2.1 |
|||
Name: MarkupSafe |
|||
Version: 2.1.5 |
|||
Summary: Safely add untrusted strings to HTML/XML markup. |
|||
Home-page: https://palletsprojects.com/p/markupsafe/ |
|||
Maintainer: Pallets |
|||
Maintainer-email: contact@palletsprojects.com |
|||
License: BSD-3-Clause |
|||
Project-URL: Donate, https://palletsprojects.com/donate |
|||
Project-URL: Documentation, https://markupsafe.palletsprojects.com/ |
|||
Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/ |
|||
Project-URL: Source Code, https://github.com/pallets/markupsafe/ |
|||
Project-URL: Issue Tracker, https://github.com/pallets/markupsafe/issues/ |
|||
Project-URL: Chat, https://discord.gg/pallets |
|||
Classifier: Development Status :: 5 - Production/Stable |
|||
Classifier: Environment :: Web Environment |
|||
Classifier: Intended Audience :: Developers |
|||
Classifier: License :: OSI Approved :: BSD License |
|||
Classifier: Operating System :: OS Independent |
|||
Classifier: Programming Language :: Python |
|||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content |
|||
Classifier: Topic :: Text Processing :: Markup :: HTML |
|||
Requires-Python: >=3.7 |
|||
Description-Content-Type: text/x-rst |
|||
License-File: LICENSE.rst |
|||
|
|||
MarkupSafe |
|||
========== |
|||
|
|||
MarkupSafe implements a text object that escapes characters so it is |
|||
safe to use in HTML and XML. Characters that have special meanings are |
|||
replaced so that they display as the actual characters. This mitigates |
|||
injection attacks, meaning untrusted user input can safely be displayed |
|||
on a page. |
|||
|
|||
|
|||
Installing |
|||
---------- |
|||
|
|||
Install and update using `pip`_: |
|||
|
|||
.. code-block:: text |
|||
|
|||
pip install -U MarkupSafe |
|||
|
|||
.. _pip: https://pip.pypa.io/en/stable/getting-started/ |
|||
|
|||
|
|||
Examples |
|||
-------- |
|||
|
|||
.. code-block:: pycon |
|||
|
|||
>>> from markupsafe import Markup, escape |
|||
|
|||
>>> # escape replaces special characters and wraps in Markup |
|||
>>> escape("<script>alert(document.cookie);</script>") |
|||
Markup('<script>alert(document.cookie);</script>') |
|||
|
|||
>>> # wrap in Markup to mark text "safe" and prevent escaping |
|||
>>> Markup("<strong>Hello</strong>") |
|||
Markup('<strong>hello</strong>') |
|||
|
|||
>>> escape(Markup("<strong>Hello</strong>")) |
|||
Markup('<strong>hello</strong>') |
|||
|
|||
>>> # Markup is a str subclass |
|||
>>> # methods and operators escape their arguments |
|||
>>> template = Markup("Hello <em>{name}</em>") |
|||
>>> template.format(name='"World"') |
|||
Markup('Hello <em>"World"</em>') |
|||
|
|||
|
|||
Donate |
|||
------ |
|||
|
|||
The Pallets organization develops and supports MarkupSafe and other |
|||
popular packages. In order to grow the community of contributors and |
|||
users, and allow the maintainers to devote more time to the projects, |
|||
`please donate today`_. |
|||
|
|||
.. _please donate today: https://palletsprojects.com/donate |
|||
|
|||
|
|||
Links |
|||
----- |
|||
|
|||
- Documentation: https://markupsafe.palletsprojects.com/ |
|||
- Changes: https://markupsafe.palletsprojects.com/changes/ |
|||
- PyPI Releases: https://pypi.org/project/MarkupSafe/ |
|||
- Source Code: https://github.com/pallets/markupsafe/ |
|||
- Issue Tracker: https://github.com/pallets/markupsafe/issues/ |
|||
- Chat: https://discord.gg/pallets |
@ -0,0 +1,14 @@ |
|||
markupsafe/__init__.py,sha256=m1ysNeqf55zbEoJtaovca40ivrkEFolPlw5bGoC5Gi4,11290 |
|||
markupsafe/_native.py,sha256=_Q7UsXCOvgdonCgqG3l5asANI6eo50EKnDM-mlwEC5M,1776 |
|||
markupsafe/_speedups.c,sha256=n3jzzaJwXcoN8nTFyA53f3vSqsWK2vujI-v6QYifjhQ,7403 |
|||
markupsafe/_speedups.cp37-win_amd64.pyd,sha256=k3EXotF4ZpaL3vGiqsU1KBjvNWTXbO3VEEaPcLEhlN0,15872 |
|||
markupsafe/_speedups.pyi,sha256=f5QtwIOP0eLrxh2v5p6SmaYmlcHIGIfmz0DovaqL0OU,238 |
|||
markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 |
|||
MarkupSafe-2.1.5.dist-info/LICENSE.rst,sha256=RjHsDbX9kKVH4zaBcmTGeYIUM4FG-KyUtKV_lu6MnsQ,1503 |
|||
MarkupSafe-2.1.5.dist-info/METADATA,sha256=icNlaniV7YIQZ1BScCVqNaRtm7MAgfw8d3OBmoSVyAY,3096 |
|||
MarkupSafe-2.1.5.dist-info/WHEEL,sha256=slqBGdqRnxanDn00BSYHhryEsWH_8CUurgRUvoMtK_Y,101 |
|||
MarkupSafe-2.1.5.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11 |
|||
MarkupSafe-2.1.5.dist-info/RECORD,, |
|||
MarkupSafe-2.1.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 |
|||
markupsafe/__pycache__/_native.cpython-37.pyc,, |
|||
markupsafe/__pycache__/__init__.cpython-37.pyc,, |
@ -0,0 +1,5 @@ |
|||
Wheel-Version: 1.0 |
|||
Generator: bdist_wheel (0.42.0) |
|||
Root-Is-Purelib: false |
|||
Tag: cp37-cp37m-win_amd64 |
|||
|
@ -0,0 +1 @@ |
|||
markupsafe |
@ -0,0 +1 @@ |
|||
pip |
@ -0,0 +1,20 @@ |
|||
Copyright (c) 2017-2021 Ingy döt Net |
|||
Copyright (c) 2006-2016 Kirill Simonov |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy of |
|||
this software and associated documentation files (the "Software"), to deal in |
|||
the Software without restriction, including without limitation the rights to |
|||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
|||
of the Software, and to permit persons to whom the Software is furnished to do |
|||
so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
@ -0,0 +1,46 @@ |
|||
Metadata-Version: 2.1 |
|||
Name: PyYAML |
|||
Version: 6.0.1 |
|||
Summary: YAML parser and emitter for Python |
|||
Home-page: https://pyyaml.org/ |
|||
Author: Kirill Simonov |
|||
Author-email: xi@resolvent.net |
|||
License: MIT |
|||
Download-URL: https://pypi.org/project/PyYAML/ |
|||
Project-URL: Bug Tracker, https://github.com/yaml/pyyaml/issues |
|||
Project-URL: CI, https://github.com/yaml/pyyaml/actions |
|||
Project-URL: Documentation, https://pyyaml.org/wiki/PyYAMLDocumentation |
|||
Project-URL: Mailing lists, http://lists.sourceforge.net/lists/listinfo/yaml-core |
|||
Project-URL: Source Code, https://github.com/yaml/pyyaml |
|||
Platform: Any |
|||
Classifier: Development Status :: 5 - Production/Stable |
|||
Classifier: Intended Audience :: Developers |
|||
Classifier: License :: OSI Approved :: MIT License |
|||
Classifier: Operating System :: OS Independent |
|||
Classifier: Programming Language :: Cython |
|||
Classifier: Programming Language :: Python |
|||
Classifier: Programming Language :: Python :: 3 |
|||
Classifier: Programming Language :: Python :: 3.6 |
|||
Classifier: Programming Language :: Python :: 3.7 |
|||
Classifier: Programming Language :: Python :: 3.8 |
|||
Classifier: Programming Language :: Python :: 3.9 |
|||
Classifier: Programming Language :: Python :: 3.10 |
|||
Classifier: Programming Language :: Python :: 3.11 |
|||
Classifier: Programming Language :: Python :: Implementation :: CPython |
|||
Classifier: Programming Language :: Python :: Implementation :: PyPy |
|||
Classifier: Topic :: Software Development :: Libraries :: Python Modules |
|||
Classifier: Topic :: Text Processing :: Markup |
|||
Requires-Python: >=3.6 |
|||
|
|||
YAML is a data serialization format designed for human readability |
|||
and interaction with scripting languages. PyYAML is a YAML parser |
|||
and emitter for Python. |
|||
|
|||
PyYAML features a complete YAML 1.1 parser, Unicode support, pickle |
|||
support, capable extension API, and sensible error messages. PyYAML |
|||
supports standard YAML tags and provides Python-specific tags that |
|||
allow to represent an arbitrary Python object. |
|||
|
|||
PyYAML is applicable for a broad range of tasks from complex |
|||
configuration files to object serialization and persistence. |
|||
|
@ -0,0 +1,43 @@ |
|||
_yaml/__init__.py,sha256=04Ae_5osxahpJHa3XBZUAf4wi6XX32gR8D6X6p64GEA,1402 |
|||
yaml/__init__.py,sha256=bhl05qSeO-1ZxlSRjGrvl2m9nrXb1n9-GQatTN0Mrqc,12311 |
|||
yaml/_yaml.cp37-win_amd64.pyd,sha256=fDBGa8nWRKTESNlw3QVb1j56CchXC9eqZexW6hFMgl8,261632 |
|||
yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883 |
|||
yaml/constructor.py,sha256=kNgkfaeLUkwQYY_Q6Ff1Tz2XVw_pG1xVE9Ak7z-viLA,28639 |
|||
yaml/cyaml.py,sha256=6ZrAG9fAYvdVe2FK_w0hmXoG7ZYsoYUwapG8CiC72H0,3851 |
|||
yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837 |
|||
yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006 |
|||
yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533 |
|||
yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445 |
|||
yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061 |
|||
yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440 |
|||
yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495 |
|||
yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794 |
|||
yaml/representer.py,sha256=IuWP-cAW9sHKEnS0gCqSa894k1Bg4cgTxaDwIcbRQ-Y,14190 |
|||
yaml/resolver.py,sha256=9L-VYfm4mWHxUD1Vg4X7rjDRK_7VZd6b92wzq7Y2IKY,9004 |
|||
yaml/scanner.py,sha256=YEM3iLZSaQwXcQRg2l2R4MdT0zGP2F9eHkKGKnHyWQY,51279 |
|||
yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165 |
|||
yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573 |
|||
PyYAML-6.0.1.dist-info/LICENSE,sha256=jTko-dxEkP1jVwfLiOsmvXZBAqcoKVQwfT5RZ6V36KQ,1101 |
|||
PyYAML-6.0.1.dist-info/METADATA,sha256=fOy4MbPYqGeWtmYxczrxRS9fIXo9aBDkKzsWbM23X1M,2083 |
|||
PyYAML-6.0.1.dist-info/WHEEL,sha256=bipuClDp75Tl92wOCbNQvfy8uWiHgUDTfxkDCRx_slY,101 |
|||
PyYAML-6.0.1.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11 |
|||
PyYAML-6.0.1.dist-info/RECORD,, |
|||
PyYAML-6.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 |
|||
yaml/__pycache__/composer.cpython-37.pyc,, |
|||
yaml/__pycache__/constructor.cpython-37.pyc,, |
|||
yaml/__pycache__/cyaml.cpython-37.pyc,, |
|||
yaml/__pycache__/dumper.cpython-37.pyc,, |
|||
yaml/__pycache__/emitter.cpython-37.pyc,, |
|||
yaml/__pycache__/error.cpython-37.pyc,, |
|||
yaml/__pycache__/events.cpython-37.pyc,, |
|||
yaml/__pycache__/loader.cpython-37.pyc,, |
|||
yaml/__pycache__/nodes.cpython-37.pyc,, |
|||
yaml/__pycache__/parser.cpython-37.pyc,, |
|||
yaml/__pycache__/reader.cpython-37.pyc,, |
|||
yaml/__pycache__/representer.cpython-37.pyc,, |
|||
yaml/__pycache__/resolver.cpython-37.pyc,, |
|||
yaml/__pycache__/scanner.cpython-37.pyc,, |
|||
yaml/__pycache__/serializer.cpython-37.pyc,, |
|||
yaml/__pycache__/tokens.cpython-37.pyc,, |
|||
yaml/__pycache__/__init__.cpython-37.pyc,, |
|||
_yaml/__pycache__/__init__.cpython-37.pyc,, |
@ -0,0 +1,5 @@ |
|||
Wheel-Version: 1.0 |
|||
Generator: bdist_wheel (0.40.0) |
|||
Root-Is-Purelib: false |
|||
Tag: cp37-cp37m-win_amd64 |
|||
|
@ -0,0 +1,2 @@ |
|||
_yaml |
|||
yaml |
@ -0,0 +1 @@ |
|||
pip |
@ -0,0 +1,28 @@ |
|||
Copyright 2007 Pallets |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are |
|||
met: |
|||
|
|||
1. Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
|
|||
2. Redistributions in binary form must reproduce the above copyright |
|||
notice, this list of conditions and the following disclaimer in the |
|||
documentation and/or other materials provided with the distribution. |
|||
|
|||
3. Neither the name of the copyright holder nor the names of its |
|||
contributors may be used to endorse or promote products derived from |
|||
this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
|||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
|||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,126 @@ |
|||
Metadata-Version: 2.1 |
|||
Name: Werkzeug |
|||
Version: 2.2.3 |
|||
Summary: The comprehensive WSGI web application library. |
|||
Home-page: https://palletsprojects.com/p/werkzeug/ |
|||
Author: Armin Ronacher |
|||
Author-email: armin.ronacher@active-4.com |
|||
Maintainer: Pallets |
|||
Maintainer-email: contact@palletsprojects.com |
|||
License: BSD-3-Clause |
|||
Project-URL: Donate, https://palletsprojects.com/donate |
|||
Project-URL: Documentation, https://werkzeug.palletsprojects.com/ |
|||
Project-URL: Changes, https://werkzeug.palletsprojects.com/changes/ |
|||
Project-URL: Source Code, https://github.com/pallets/werkzeug/ |
|||
Project-URL: Issue Tracker, https://github.com/pallets/werkzeug/issues/ |
|||
Project-URL: Twitter, https://twitter.com/PalletsTeam |
|||
Project-URL: Chat, https://discord.gg/pallets |
|||
Classifier: Development Status :: 5 - Production/Stable |
|||
Classifier: Environment :: Web Environment |
|||
Classifier: Intended Audience :: Developers |
|||
Classifier: License :: OSI Approved :: BSD License |
|||
Classifier: Operating System :: OS Independent |
|||
Classifier: Programming Language :: Python |
|||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content |
|||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI |
|||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application |
|||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware |
|||
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks |
|||
Requires-Python: >=3.7 |
|||
Description-Content-Type: text/x-rst |
|||
License-File: LICENSE.rst |
|||
Requires-Dist: MarkupSafe (>=2.1.1) |
|||
Provides-Extra: watchdog |
|||
Requires-Dist: watchdog ; extra == 'watchdog' |
|||
|
|||
Werkzeug |
|||
======== |
|||
|
|||
*werkzeug* German noun: "tool". Etymology: *werk* ("work"), *zeug* ("stuff") |
|||
|
|||
Werkzeug is a comprehensive `WSGI`_ web application library. It began as |
|||
a simple collection of various utilities for WSGI applications and has |
|||
become one of the most advanced WSGI utility libraries. |
|||
|
|||
It includes: |
|||
|
|||
- An interactive debugger that allows inspecting stack traces and |
|||
source code in the browser with an interactive interpreter for any |
|||
frame in the stack. |
|||
- A full-featured request object with objects to interact with |
|||
headers, query args, form data, files, and cookies. |
|||
- A response object that can wrap other WSGI applications and handle |
|||
streaming data. |
|||
- A routing system for matching URLs to endpoints and generating URLs |
|||
for endpoints, with an extensible system for capturing variables |
|||
from URLs. |
|||
- HTTP utilities to handle entity tags, cache control, dates, user |
|||
agents, cookies, files, and more. |
|||
- A threaded WSGI server for use while developing applications |
|||
locally. |
|||
- A test client for simulating HTTP requests during testing without |
|||
requiring running a server. |
|||
|
|||
Werkzeug doesn't enforce any dependencies. It is up to the developer to |
|||
choose a template engine, database adapter, and even how to handle |
|||
requests. It can be used to build all sorts of end user applications |
|||
such as blogs, wikis, or bulletin boards. |
|||
|
|||
`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while |
|||
providing more structure and patterns for defining powerful |
|||
applications. |
|||
|
|||
.. _WSGI: https://wsgi.readthedocs.io/en/latest/ |
|||
.. _Flask: https://www.palletsprojects.com/p/flask/ |
|||
|
|||
|
|||
Installing |
|||
---------- |
|||
|
|||
Install and update using `pip`_: |
|||
|
|||
.. code-block:: text |
|||
|
|||
pip install -U Werkzeug |
|||
|
|||
.. _pip: https://pip.pypa.io/en/stable/getting-started/ |
|||
|
|||
|
|||
A Simple Example |
|||
---------------- |
|||
|
|||
.. code-block:: python |
|||
|
|||
from werkzeug.wrappers import Request, Response |
|||
|
|||
@Request.application |
|||
def application(request): |
|||
return Response('Hello, World!') |
|||
|
|||
if __name__ == '__main__': |
|||
from werkzeug.serving import run_simple |
|||
run_simple('localhost', 4000, application) |
|||
|
|||
|
|||
Donate |
|||
------ |
|||
|
|||
The Pallets organization develops and supports Werkzeug and other |
|||
popular packages. In order to grow the community of contributors and |
|||
users, and allow the maintainers to devote more time to the projects, |
|||
`please donate today`_. |
|||
|
|||
.. _please donate today: https://palletsprojects.com/donate |
|||
|
|||
|
|||
Links |
|||
----- |
|||
|
|||
- Documentation: https://werkzeug.palletsprojects.com/ |
|||
- Changes: https://werkzeug.palletsprojects.com/changes/ |
|||
- PyPI Releases: https://pypi.org/project/Werkzeug/ |
|||
- Source Code: https://github.com/pallets/werkzeug/ |
|||
- Issue Tracker: https://github.com/pallets/werkzeug/issues/ |
|||
- Website: https://palletsprojects.com/p/werkzeug/ |
|||
- Twitter: https://twitter.com/PalletsTeam |
|||
- Chat: https://discord.gg/pallets |
@ -0,0 +1,98 @@ |
|||
werkzeug/__init__.py,sha256=Hr0lQweC21HXPVBemSpBJUIzrbq2mn8h70J1h30QcqY,188 |
|||
werkzeug/_internal.py,sha256=4lwshe63pFlCo0h2IMcmvhbugA50QXQvfLD5VoY5c4Q,16271 |
|||
werkzeug/_reloader.py,sha256=hiP0z4bi6p_8UIJOtq7K0BV2dqCik5yztWLsDXeI_WE,14285 |
|||
werkzeug/datastructures.py,sha256=v2WYfs1rb1OuQgXyLripHQFwgodrfTNCd5P5f8n3ueA,97081 |
|||
werkzeug/datastructures.pyi,sha256=HRzDLc7A6qnwluhNqn6AT76CsLZIkAbVVqxn0AbfV-s,34506 |
|||
werkzeug/exceptions.py,sha256=8-KOXguQkOLoBUdN-7x_WyHT92TcAmjTWNwG4t8QYIg,26527 |
|||
werkzeug/formparser.py,sha256=DBRbbAnzspYUBzgfxPaZC7MjGAK_m5QTvdWoyvrhw4o,16516 |
|||
werkzeug/http.py,sha256=NqJjYCt8tKn2XOEKPApq4L3q8zb8YFq3GFOe5gsonI4,42776 |
|||
werkzeug/local.py,sha256=v-HEqr4bLpLHl4upCj97MOfUyCjW10Tp6mcNaFRFyew,22288 |
|||
werkzeug/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 |
|||
werkzeug/security.py,sha256=7TVI0L62emBHAh-1RHB_KlwGYcE08pPCyU674Ho4aNE,4653 |
|||
werkzeug/serving.py,sha256=XCiHFbMCFCgecKycgajhF4rFsGoemeN0xW1eTQqNt-g,37558 |
|||
werkzeug/test.py,sha256=uMahfM81RqEN3d3Sp4SkN36Pi8oZpV6dTgFY0cW1_2c,48126 |
|||
werkzeug/testapp.py,sha256=RJhT_2JweNiMKe304N3bF1zaIeMpRx-CIMERdeydfTY,9404 |
|||
werkzeug/urls.py,sha256=Q9Si-eVh7yxk3rwkzrwGRm146FXVXgg9lBP3k0HUfVM,36600 |
|||
werkzeug/user_agent.py,sha256=WclZhpvgLurMF45hsioSbS75H1Zb4iMQGKN3_yZ2oKo,1420 |
|||
werkzeug/utils.py,sha256=BDX5_7OCMVgl-ib84bCEdBG5MVvrxaSlfdg7Cxh4ND0,25174 |
|||
werkzeug/wsgi.py,sha256=-VKI2iwCgLb-VToIZeBpdutkTETxy9HkIwgcFC5orkU,36060 |
|||
werkzeug/debug/__init__.py,sha256=wfJ2OmljsO5C0e0sXJpTUiG6bwGU6uHtFDDDMfJfQJk,18877 |
|||
werkzeug/debug/console.py,sha256=dechqiCtHfs0AQZWZofUC1S97tCuvwDgT0gdha5KwWM,6208 |
|||
werkzeug/debug/repr.py,sha256=vF3TLnYBohYr8V6Gz13PTJspQs42uv3gUJSzSbmHJBo,9472 |
|||
werkzeug/debug/tbtools.py,sha256=6iohJovtBSFRAcgX7_aRY4r3e19PLj3FavYB3RM4CmA,13263 |
|||
werkzeug/debug/shared/ICON_LICENSE.md,sha256=DhA6Y1gUl5Jwfg0NFN9Rj4VWITt8tUx0IvdGf0ux9-s,222 |
|||
werkzeug/debug/shared/console.png,sha256=bxax6RXXlvOij_KeqvSNX0ojJf83YbnZ7my-3Gx9w2A,507 |
|||
werkzeug/debug/shared/debugger.js,sha256=tg42SZs1SVmYWZ-_Fj5ELK5-FLHnGNQrei0K2By8Bw8,10521 |
|||
werkzeug/debug/shared/less.png,sha256=-4-kNRaXJSONVLahrQKUxMwXGm9R4OnZ9SxDGpHlIR4,191 |
|||
werkzeug/debug/shared/more.png,sha256=GngN7CioHQoV58rH6ojnkYi8c_qED2Aka5FO5UXrReY,200 |
|||
werkzeug/debug/shared/style.css,sha256=-xSxzUEZGw_IqlDR5iZxitNl8LQUjBM-_Y4UAvXVH8g,6078 |
|||
werkzeug/middleware/__init__.py,sha256=qfqgdT5npwG9ses3-FXQJf3aB95JYP1zchetH_T3PUw,500 |
|||
werkzeug/middleware/dispatcher.py,sha256=Fh_w-KyWnTSYF-Lfv5dimQ7THSS7afPAZMmvc4zF1gg,2580 |
|||
werkzeug/middleware/http_proxy.py,sha256=HE8VyhS7CR-E1O6_9b68huv8FLgGGR1DLYqkS3Xcp3Q,7558 |
|||
werkzeug/middleware/lint.py,sha256=1w_UVKkAwq5wjjtCcDCDZwhAhWzPSZ0aDyUmbjAEeXw,13952 |
|||
werkzeug/middleware/profiler.py,sha256=7pWYDYPC774S0-HYLkG3Uge58PGUMX7tWp_Cor3etvo,4883 |
|||
werkzeug/middleware/proxy_fix.py,sha256=l7LC_LDu0Yd4SvUxS5SFigAJMzcIOGm6LNKl9IXJBSU,6974 |
|||
werkzeug/middleware/shared_data.py,sha256=fXjrEkuqxUVLG1DLrOdQLc96QQdjftCBZ1oM5oK89h4,9528 |
|||
werkzeug/routing/__init__.py,sha256=HpvahY7WwkLdV4Cq3Bsc3GrqNon4u6t8-vhbb9E5o00,4819 |
|||
werkzeug/routing/converters.py,sha256=05bkekg64vLC6mqqK4ddBh589WH9yBsjtW8IJhdUBvw,6968 |
|||
werkzeug/routing/exceptions.py,sha256=RklUDL9ajOv2fTcRNj4pb18Bs4Y-GKk4rIeTSfsqkks,4737 |
|||
werkzeug/routing/map.py,sha256=XN4ZjzEF1SfMxtdov89SDE-1_U78KVnnobTfnHzqbmE,36757 |
|||
werkzeug/routing/matcher.py,sha256=6VvQYCCOjyj1JKUZKuAiVA_U1nXtvvJ70pSbBUdL_1k,7509 |
|||
werkzeug/routing/rules.py,sha256=3YsPpI9ZGcNmFiV2Go2Td1DvZ9ZdaMMnvGP1o17aMfc,31836 |
|||
werkzeug/sansio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 |
|||
werkzeug/sansio/http.py,sha256=k3nREBfU-r8fXCfSTKQenys25q9bzUOvdY-OVGrqztA,5107 |
|||
werkzeug/sansio/multipart.py,sha256=vMZ85cvLD55clUTcTin2DtBv2GQRGh0_fExklnXKHoI,10055 |
|||
werkzeug/sansio/request.py,sha256=SiGcx2cz-l81TlCCrKrT2fePqC64hN8fSg5Ig6J6vRs,20175 |
|||
werkzeug/sansio/response.py,sha256=UTl-teQDDjovrZMkjj3ZQsHw-JtiFak5JfKEk1_vBYU,26026 |
|||
werkzeug/sansio/utils.py,sha256=EjbqdHdT-JZWgjUQaaWSgBUIRprXUkrsMQQqJlJHpVU,4847 |
|||
werkzeug/wrappers/__init__.py,sha256=kGyK7rOud3qCxll_jFyW15YarJhj1xtdf3ocx9ZheB8,120 |
|||
werkzeug/wrappers/request.py,sha256=XmpTThXytTdznbPJnIsfdoIAvdi-THzTJQ9DsvARhn4,24026 |
|||
werkzeug/wrappers/response.py,sha256=ii1IaN2eUfoB-tBqbn_46fCB_SVVL8Fu4qd6cu0AlEY,34963 |
|||
Werkzeug-2.2.3.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475 |
|||
Werkzeug-2.2.3.dist-info/METADATA,sha256=TIyameVEp5p52N9E1mTWWabY6g1sB0Dm25vznZQeXPQ,4416 |
|||
Werkzeug-2.2.3.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92 |
|||
Werkzeug-2.2.3.dist-info/top_level.txt,sha256=QRyj2VjwJoQkrwjwFIOlB8Xg3r9un0NtqVHQF-15xaw,9 |
|||
Werkzeug-2.2.3.dist-info/RECORD,, |
|||
Werkzeug-2.2.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 |
|||
werkzeug/debug/__pycache__/console.cpython-37.pyc,, |
|||
werkzeug/debug/__pycache__/repr.cpython-37.pyc,, |
|||
werkzeug/debug/__pycache__/tbtools.cpython-37.pyc,, |
|||
werkzeug/debug/__pycache__/__init__.cpython-37.pyc,, |
|||
werkzeug/middleware/__pycache__/dispatcher.cpython-37.pyc,, |
|||
werkzeug/middleware/__pycache__/http_proxy.cpython-37.pyc,, |
|||
werkzeug/middleware/__pycache__/lint.cpython-37.pyc,, |
|||
werkzeug/middleware/__pycache__/profiler.cpython-37.pyc,, |
|||
werkzeug/middleware/__pycache__/proxy_fix.cpython-37.pyc,, |
|||
werkzeug/middleware/__pycache__/shared_data.cpython-37.pyc,, |
|||
werkzeug/middleware/__pycache__/__init__.cpython-37.pyc,, |
|||
werkzeug/routing/__pycache__/converters.cpython-37.pyc,, |
|||
werkzeug/routing/__pycache__/exceptions.cpython-37.pyc,, |
|||
werkzeug/routing/__pycache__/map.cpython-37.pyc,, |
|||
werkzeug/routing/__pycache__/matcher.cpython-37.pyc,, |
|||
werkzeug/routing/__pycache__/rules.cpython-37.pyc,, |
|||
werkzeug/routing/__pycache__/__init__.cpython-37.pyc,, |
|||
werkzeug/sansio/__pycache__/http.cpython-37.pyc,, |
|||
werkzeug/sansio/__pycache__/multipart.cpython-37.pyc,, |
|||
werkzeug/sansio/__pycache__/request.cpython-37.pyc,, |
|||
werkzeug/sansio/__pycache__/response.cpython-37.pyc,, |
|||
werkzeug/sansio/__pycache__/utils.cpython-37.pyc,, |
|||
werkzeug/sansio/__pycache__/__init__.cpython-37.pyc,, |
|||
werkzeug/wrappers/__pycache__/request.cpython-37.pyc,, |
|||
werkzeug/wrappers/__pycache__/response.cpython-37.pyc,, |
|||
werkzeug/wrappers/__pycache__/__init__.cpython-37.pyc,, |
|||
werkzeug/__pycache__/datastructures.cpython-37.pyc,, |
|||
werkzeug/__pycache__/exceptions.cpython-37.pyc,, |
|||
werkzeug/__pycache__/formparser.cpython-37.pyc,, |
|||
werkzeug/__pycache__/http.cpython-37.pyc,, |
|||
werkzeug/__pycache__/local.cpython-37.pyc,, |
|||
werkzeug/__pycache__/security.cpython-37.pyc,, |
|||
werkzeug/__pycache__/serving.cpython-37.pyc,, |
|||
werkzeug/__pycache__/test.cpython-37.pyc,, |
|||
werkzeug/__pycache__/testapp.cpython-37.pyc,, |
|||
werkzeug/__pycache__/urls.cpython-37.pyc,, |
|||
werkzeug/__pycache__/user_agent.cpython-37.pyc,, |
|||
werkzeug/__pycache__/utils.cpython-37.pyc,, |
|||
werkzeug/__pycache__/wsgi.cpython-37.pyc,, |
|||
werkzeug/__pycache__/_internal.cpython-37.pyc,, |
|||
werkzeug/__pycache__/_reloader.cpython-37.pyc,, |
|||
werkzeug/__pycache__/__init__.cpython-37.pyc,, |
@ -0,0 +1,5 @@ |
|||
Wheel-Version: 1.0 |
|||
Generator: bdist_wheel (0.38.4) |
|||
Root-Is-Purelib: true |
|||
Tag: py3-none-any |
|||
|
@ -0,0 +1 @@ |
|||
werkzeug |
@ -0,0 +1,33 @@ |
|||
# This is a stub package designed to roughly emulate the _yaml |
|||
# extension module, which previously existed as a standalone module |
|||
# and has been moved into the `yaml` package namespace. |
|||
# It does not perfectly mimic its old counterpart, but should get |
|||
# close enough for anyone who's relying on it even when they shouldn't. |
|||
import yaml |
|||
|
|||
# in some circumstances, the yaml module we imoprted may be from a different version, so we need |
|||
# to tread carefully when poking at it here (it may not have the attributes we expect) |
|||
if not getattr(yaml, '__with_libyaml__', False): |
|||
from sys import version_info |
|||
|
|||
exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError |
|||
raise exc("No module named '_yaml'") |
|||
else: |
|||
from yaml._yaml import * |
|||
import warnings |
|||
warnings.warn( |
|||
'The _yaml extension module is now located at yaml._yaml' |
|||
' and its location is subject to change. To use the' |
|||
' LibYAML-based parser and emitter, import from `yaml`:' |
|||
' `from yaml import CLoader as Loader, CDumper as Dumper`.', |
|||
DeprecationWarning |
|||
) |
|||
del warnings |
|||
# Don't `del yaml` here because yaml is actually an existing |
|||
# namespace member of _yaml. |
|||
|
|||
__name__ = '_yaml' |
|||
# If the module is top-level (i.e. not a part of any specific package) |
|||
# then the attribute should be set to ''. |
|||
# https://docs.python.org/3.8/library/types.html |
|||
__package__ = '' |
@ -0,0 +1 @@ |
|||
pip |
@ -0,0 +1,27 @@ |
|||
Copyright (c) 2021, Brandon Nielsen |
|||
All rights reserved. |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are met: |
|||
|
|||
1. Redistributions of source code must retain the above copyright notice, this |
|||
list of conditions and the following disclaimer. |
|||
|
|||
2. Redistributions in binary form must reproduce the above copyright notice, |
|||
this list of conditions and the following disclaimer in the documentation |
|||
and/or other materials provided with the distribution. |
|||
|
|||
3. Neither the name of the copyright holder nor the names of its contributors |
|||
may be used to endorse or promote products derived from this software |
|||
without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
|||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
|||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
|||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,513 @@ |
|||
Metadata-Version: 2.1 |
|||
Name: aniso8601 |
|||
Version: 9.0.1 |
|||
Summary: A library for parsing ISO 8601 strings. |
|||
Home-page: https://bitbucket.org/nielsenb/aniso8601 |
|||
Author: Brandon Nielsen |
|||
Author-email: nielsenb@jetfuse.net |
|||
License: UNKNOWN |
|||
Project-URL: Documentation, https://aniso8601.readthedocs.io/ |
|||
Project-URL: Source, https://bitbucket.org/nielsenb/aniso8601 |
|||
Project-URL: Tracker, https://bitbucket.org/nielsenb/aniso8601/issues |
|||
Keywords: iso8601 parser |
|||
Platform: UNKNOWN |
|||
Classifier: Development Status :: 5 - Production/Stable |
|||
Classifier: Intended Audience :: Developers |
|||
Classifier: License :: OSI Approved :: BSD License |
|||
Classifier: Operating System :: OS Independent |
|||
Classifier: Programming Language :: Python |
|||
Classifier: Programming Language :: Python :: 2 |
|||
Classifier: Programming Language :: Python :: 2.7 |
|||
Classifier: Programming Language :: Python :: 3 |
|||
Classifier: Programming Language :: Python :: 3.4 |
|||
Classifier: Programming Language :: Python :: 3.5 |
|||
Classifier: Programming Language :: Python :: 3.6 |
|||
Classifier: Programming Language :: Python :: 3.7 |
|||
Classifier: Programming Language :: Python :: 3.8 |
|||
Classifier: Programming Language :: Python :: 3.9 |
|||
Classifier: Topic :: Software Development :: Libraries :: Python Modules |
|||
Description-Content-Type: text/x-rst |
|||
Provides-Extra: dev |
|||
Requires-Dist: black ; extra == 'dev' |
|||
Requires-Dist: coverage ; extra == 'dev' |
|||
Requires-Dist: isort ; extra == 'dev' |
|||
Requires-Dist: pre-commit ; extra == 'dev' |
|||
Requires-Dist: pyenchant ; extra == 'dev' |
|||
Requires-Dist: pylint ; extra == 'dev' |
|||
|
|||
aniso8601 |
|||
========= |
|||
|
|||
Another ISO 8601 parser for Python |
|||
---------------------------------- |
|||
|
|||
Features |
|||
======== |
|||
* Pure Python implementation |
|||
* Logical behavior |
|||
|
|||
- Parse a time, get a `datetime.time <http://docs.python.org/3/library/datetime.html#datetime.time>`_ |
|||
- Parse a date, get a `datetime.date <http://docs.python.org/3/library/datetime.html#datetime.date>`_ |
|||
- Parse a datetime, get a `datetime.datetime <http://docs.python.org/3/library/datetime.html#datetime.datetime>`_ |
|||
- Parse a duration, get a `datetime.timedelta <http://docs.python.org/3/library/datetime.html#datetime.timedelta>`_ |
|||
- Parse an interval, get a tuple of dates or datetimes |
|||
- Parse a repeating interval, get a date or datetime `generator <https://wiki.python.org/moin/Generators>`_ |
|||
|
|||
* UTC offset represented as fixed-offset tzinfo |
|||
* Parser separate from representation, allowing parsing to different datetime representations (see `Builders`_) |
|||
* No regular expressions |
|||
|
|||
Installation |
|||
============ |
|||
|
|||
The recommended installation method is to use pip:: |
|||
|
|||
$ pip install aniso8601 |
|||
|
|||
Alternatively, you can download the source (git repository hosted at `Bitbucket <https://bitbucket.org/nielsenb/aniso8601>`_) and install directly:: |
|||
|
|||
$ python setup.py install |
|||
|
|||
Use |
|||
=== |
|||
|
|||
Parsing datetimes |
|||
----------------- |
|||
|
|||
*Consider* `datetime.datetime.fromisoformat <https://docs.python.org/3/library/datetime.html#datetime.datetime.fromisoformat>`_ *for basic ISO 8601 datetime parsing* |
|||
|
|||
To parse a typical ISO 8601 datetime string:: |
|||
|
|||
>>> import aniso8601 |
|||
>>> aniso8601.parse_datetime('1977-06-10T12:00:00Z') |
|||
datetime.datetime(1977, 6, 10, 12, 0, tzinfo=+0:00:00 UTC) |
|||
|
|||
Alternative delimiters can be specified, for example, a space:: |
|||
|
|||
>>> aniso8601.parse_datetime('1977-06-10 12:00:00Z', delimiter=' ') |
|||
datetime.datetime(1977, 6, 10, 12, 0, tzinfo=+0:00:00 UTC) |
|||
|
|||
UTC offsets are supported:: |
|||
|
|||
>>> aniso8601.parse_datetime('1979-06-05T08:00:00-08:00') |
|||
datetime.datetime(1979, 6, 5, 8, 0, tzinfo=-8:00:00 UTC) |
|||
|
|||
If a UTC offset is not specified, the returned datetime will be naive:: |
|||
|
|||
>>> aniso8601.parse_datetime('1983-01-22T08:00:00') |
|||
datetime.datetime(1983, 1, 22, 8, 0) |
|||
|
|||
Leap seconds are currently not supported and attempting to parse one raises a :code:`LeapSecondError`:: |
|||
|
|||
>>> aniso8601.parse_datetime('2018-03-06T23:59:60') |
|||
Traceback (most recent call last): |
|||
File "<stdin>", line 1, in <module> |
|||
File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/time.py", line 196, in parse_datetime |
|||
return builder.build_datetime(datepart, timepart) |
|||
File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/python.py", line 237, in build_datetime |
|||
cls._build_object(time)) |
|||
File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/__init__.py", line 336, in _build_object |
|||
return cls.build_time(hh=parsetuple.hh, mm=parsetuple.mm, |
|||
File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/python.py", line 191, in build_time |
|||
hh, mm, ss, tz = cls.range_check_time(hh, mm, ss, tz) |
|||
File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/__init__.py", line 266, in range_check_time |
|||
raise LeapSecondError('Leap seconds are not supported.') |
|||
aniso8601.exceptions.LeapSecondError: Leap seconds are not supported. |
|||
|
|||
To get the resolution of an ISO 8601 datetime string:: |
|||
|
|||
>>> aniso8601.get_datetime_resolution('1977-06-10T12:00:00Z') == aniso8601.resolution.TimeResolution.Seconds |
|||
True |
|||
>>> aniso8601.get_datetime_resolution('1977-06-10T12:00') == aniso8601.resolution.TimeResolution.Minutes |
|||
True |
|||
>>> aniso8601.get_datetime_resolution('1977-06-10T12') == aniso8601.resolution.TimeResolution.Hours |
|||
True |
|||
|
|||
Note that datetime resolutions map to :code:`TimeResolution` as a valid datetime must have at least one time member so the resolution mapping is equivalent. |
|||
|
|||
Parsing dates |
|||
------------- |
|||
|
|||
*Consider* `datetime.date.fromisoformat <https://docs.python.org/3/library/datetime.html#datetime.date.fromisoformat>`_ *for basic ISO 8601 date parsing* |
|||
|
|||
To parse a date represented in an ISO 8601 string:: |
|||
|
|||
>>> import aniso8601 |
|||
>>> aniso8601.parse_date('1984-04-23') |
|||
datetime.date(1984, 4, 23) |
|||
|
|||
Basic format is supported as well:: |
|||
|
|||
>>> aniso8601.parse_date('19840423') |
|||
datetime.date(1984, 4, 23) |
|||
|
|||
To parse a date using the ISO 8601 week date format:: |
|||
|
|||
>>> aniso8601.parse_date('1986-W38-1') |
|||
datetime.date(1986, 9, 15) |
|||
|
|||
To parse an ISO 8601 ordinal date:: |
|||
|
|||
>>> aniso8601.parse_date('1988-132') |
|||
datetime.date(1988, 5, 11) |
|||
|
|||
To get the resolution of an ISO 8601 date string:: |
|||
|
|||
>>> aniso8601.get_date_resolution('1981-04-05') == aniso8601.resolution.DateResolution.Day |
|||
True |
|||
>>> aniso8601.get_date_resolution('1981-04') == aniso8601.resolution.DateResolution.Month |
|||
True |
|||
>>> aniso8601.get_date_resolution('1981') == aniso8601.resolution.DateResolution.Year |
|||
True |
|||
|
|||
Parsing times |
|||
------------- |
|||
|
|||
*Consider* `datetime.time.fromisoformat <https://docs.python.org/3/library/datetime.html#datetime.time.fromisoformat>`_ *for basic ISO 8601 time parsing* |
|||
|
|||
To parse a time formatted as an ISO 8601 string:: |
|||
|
|||
>>> import aniso8601 |
|||
>>> aniso8601.parse_time('11:31:14') |
|||
datetime.time(11, 31, 14) |
|||
|
|||
As with all of the above, basic format is supported:: |
|||
|
|||
>>> aniso8601.parse_time('113114') |
|||
datetime.time(11, 31, 14) |
|||
|
|||
A UTC offset can be specified for times:: |
|||
|
|||
>>> aniso8601.parse_time('17:18:19-02:30') |
|||
datetime.time(17, 18, 19, tzinfo=-2:30:00 UTC) |
|||
>>> aniso8601.parse_time('171819Z') |
|||
datetime.time(17, 18, 19, tzinfo=+0:00:00 UTC) |
|||
|
|||
Reduced accuracy is supported:: |
|||
|
|||
>>> aniso8601.parse_time('21:42') |
|||
datetime.time(21, 42) |
|||
>>> aniso8601.parse_time('22') |
|||
datetime.time(22, 0) |
|||
|
|||
A decimal fraction is always allowed on the lowest order element of an ISO 8601 formatted time:: |
|||
|
|||
>>> aniso8601.parse_time('22:33.5') |
|||
datetime.time(22, 33, 30) |
|||
>>> aniso8601.parse_time('23.75') |
|||
datetime.time(23, 45) |
|||
|
|||
The decimal fraction can be specified with a comma instead of a full-stop:: |
|||
|
|||
>>> aniso8601.parse_time('22:33,5') |
|||
datetime.time(22, 33, 30) |
|||
>>> aniso8601.parse_time('23,75') |
|||
datetime.time(23, 45) |
|||
|
|||
Leap seconds are currently not supported and attempting to parse one raises a :code:`LeapSecondError`:: |
|||
|
|||
>>> aniso8601.parse_time('23:59:60') |
|||
Traceback (most recent call last): |
|||
File "<stdin>", line 1, in <module> |
|||
File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/time.py", line 174, in parse_time |
|||
return builder.build_time(hh=hourstr, mm=minutestr, ss=secondstr, tz=tz) |
|||
File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/python.py", line 191, in build_time |
|||
hh, mm, ss, tz = cls.range_check_time(hh, mm, ss, tz) |
|||
File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/__init__.py", line 266, in range_check_time |
|||
raise LeapSecondError('Leap seconds are not supported.') |
|||
aniso8601.exceptions.LeapSecondError: Leap seconds are not supported. |
|||
|
|||
To get the resolution of an ISO 8601 time string:: |
|||
|
|||
>>> aniso8601.get_time_resolution('11:31:14') == aniso8601.resolution.TimeResolution.Seconds |
|||
True |
|||
>>> aniso8601.get_time_resolution('11:31') == aniso8601.resolution.TimeResolution.Minutes |
|||
True |
|||
>>> aniso8601.get_time_resolution('11') == aniso8601.resolution.TimeResolution.Hours |
|||
True |
|||
|
|||
Parsing durations |
|||
----------------- |
|||
|
|||
To parse a duration formatted as an ISO 8601 string:: |
|||
|
|||
>>> import aniso8601 |
|||
>>> aniso8601.parse_duration('P1Y2M3DT4H54M6S') |
|||
datetime.timedelta(428, 17646) |
|||
|
|||
Reduced accuracy is supported:: |
|||
|
|||
>>> aniso8601.parse_duration('P1Y') |
|||
datetime.timedelta(365) |
|||
|
|||
A decimal fraction is allowed on the lowest order element:: |
|||
|
|||
>>> aniso8601.parse_duration('P1YT3.5M') |
|||
datetime.timedelta(365, 210) |
|||
|
|||
The decimal fraction can be specified with a comma instead of a full-stop:: |
|||
|
|||
>>> aniso8601.parse_duration('P1YT3,5M') |
|||
datetime.timedelta(365, 210) |
|||
|
|||
Parsing a duration from a combined date and time is supported as well:: |
|||
|
|||
>>> aniso8601.parse_duration('P0001-01-02T01:30:05') |
|||
datetime.timedelta(397, 5405) |
|||
|
|||
To get the resolution of an ISO 8601 duration string:: |
|||
|
|||
>>> aniso8601.get_duration_resolution('P1Y2M3DT4H54M6S') == aniso8601.resolution.DurationResolution.Seconds |
|||
True |
|||
>>> aniso8601.get_duration_resolution('P1Y2M3DT4H54M') == aniso8601.resolution.DurationResolution.Minutes |
|||
True |
|||
>>> aniso8601.get_duration_resolution('P1Y2M3DT4H') == aniso8601.resolution.DurationResolution.Hours |
|||
True |
|||
>>> aniso8601.get_duration_resolution('P1Y2M3D') == aniso8601.resolution.DurationResolution.Days |
|||
True |
|||
>>> aniso8601.get_duration_resolution('P1Y2M') == aniso8601.resolution.DurationResolution.Months |
|||
True |
|||
>>> aniso8601.get_duration_resolution('P1Y') == aniso8601.resolution.DurationResolution.Years |
|||
True |
|||
|
|||
The default :code:`PythonTimeBuilder` assumes years are 365 days, and months are 30 days. Where calendar level accuracy is required, a `RelativeTimeBuilder <https://bitbucket.org/nielsenb/relativetimebuilder>`_ can be used, see also `Builders`_. |
|||
|
|||
Parsing intervals |
|||
----------------- |
|||
|
|||
To parse an interval specified by a start and end:: |
|||
|
|||
>>> import aniso8601 |
|||
>>> aniso8601.parse_interval('2007-03-01T13:00:00/2008-05-11T15:30:00') |
|||
(datetime.datetime(2007, 3, 1, 13, 0), datetime.datetime(2008, 5, 11, 15, 30)) |
|||
|
|||
Intervals specified by a start time and a duration are supported:: |
|||
|
|||
>>> aniso8601.parse_interval('2007-03-01T13:00:00Z/P1Y2M10DT2H30M') |
|||
(datetime.datetime(2007, 3, 1, 13, 0, tzinfo=+0:00:00 UTC), datetime.datetime(2008, 5, 9, 15, 30, tzinfo=+0:00:00 UTC)) |
|||
|
|||
A duration can also be specified by a duration and end time:: |
|||
|
|||
>>> aniso8601.parse_interval('P1M/1981-04-05') |
|||
(datetime.date(1981, 4, 5), datetime.date(1981, 3, 6)) |
|||
|
|||
Notice that the result of the above parse is not in order from earliest to latest. If sorted intervals are required, simply use the :code:`sorted` keyword as shown below:: |
|||
|
|||
>>> sorted(aniso8601.parse_interval('P1M/1981-04-05')) |
|||
[datetime.date(1981, 3, 6), datetime.date(1981, 4, 5)] |
|||
|
|||
The end of an interval is returned as a datetime when required to maintain the resolution specified by a duration, even if the duration start is given as a date:: |
|||
|
|||
>>> aniso8601.parse_interval('2014-11-12/PT4H54M6.5S') |
|||
(datetime.date(2014, 11, 12), datetime.datetime(2014, 11, 12, 4, 54, 6, 500000)) |
|||
>>> aniso8601.parse_interval('2007-03-01/P1.5D') |
|||
(datetime.date(2007, 3, 1), datetime.datetime(2007, 3, 2, 12, 0)) |
|||
|
|||
Concise representations are supported:: |
|||
|
|||
>>> aniso8601.parse_interval('2020-01-01/02') |
|||
(datetime.date(2020, 1, 1), datetime.date(2020, 1, 2)) |
|||
>>> aniso8601.parse_interval('2007-12-14T13:30/15:30') |
|||
(datetime.datetime(2007, 12, 14, 13, 30), datetime.datetime(2007, 12, 14, 15, 30)) |
|||
>>> aniso8601.parse_interval('2008-02-15/03-14') |
|||
(datetime.date(2008, 2, 15), datetime.date(2008, 3, 14)) |
|||
>>> aniso8601.parse_interval('2007-11-13T09:00/15T17:00') |
|||
(datetime.datetime(2007, 11, 13, 9, 0), datetime.datetime(2007, 11, 15, 17, 0)) |
|||
|
|||
Repeating intervals are supported as well, and return a `generator <https://wiki.python.org/moin/Generators>`_:: |
|||
|
|||
>>> aniso8601.parse_repeating_interval('R3/1981-04-05/P1D') |
|||
<generator object _date_generator at 0x7fd800d3b320> |
|||
>>> list(aniso8601.parse_repeating_interval('R3/1981-04-05/P1D')) |
|||
[datetime.date(1981, 4, 5), datetime.date(1981, 4, 6), datetime.date(1981, 4, 7)] |
|||
|
|||
Repeating intervals are allowed to go in the reverse direction:: |
|||
|
|||
>>> list(aniso8601.parse_repeating_interval('R2/PT1H2M/1980-03-05T01:01:00')) |
|||
[datetime.datetime(1980, 3, 5, 1, 1), datetime.datetime(1980, 3, 4, 23, 59)] |
|||
|
|||
Unbounded intervals are also allowed (Python 2):: |
|||
|
|||
>>> result = aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00') |
|||
>>> result.next() |
|||
datetime.datetime(1980, 3, 5, 1, 1) |
|||
>>> result.next() |
|||
datetime.datetime(1980, 3, 4, 23, 59) |
|||
|
|||
or for Python 3:: |
|||
|
|||
>>> result = aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00') |
|||
>>> next(result) |
|||
datetime.datetime(1980, 3, 5, 1, 1) |
|||
>>> next(result) |
|||
datetime.datetime(1980, 3, 4, 23, 59) |
|||
|
|||
Note that you should never try to convert a generator produced by an unbounded interval to a list:: |
|||
|
|||
>>> list(aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00')) |
|||
Traceback (most recent call last): |
|||
File "<stdin>", line 1, in <module> |
|||
File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/python.py", line 560, in _date_generator_unbounded |
|||
currentdate += timedelta |
|||
OverflowError: date value out of range |
|||
|
|||
To get the resolution of an ISO 8601 interval string:: |
|||
|
|||
>>> aniso8601.get_interval_resolution('2007-03-01T13:00:00/2008-05-11T15:30:00') == aniso8601.resolution.IntervalResolution.Seconds |
|||
True |
|||
>>> aniso8601.get_interval_resolution('2007-03-01T13:00/2008-05-11T15:30') == aniso8601.resolution.IntervalResolution.Minutes |
|||
True |
|||
>>> aniso8601.get_interval_resolution('2007-03-01T13/2008-05-11T15') == aniso8601.resolution.IntervalResolution.Hours |
|||
True |
|||
>>> aniso8601.get_interval_resolution('2007-03-01/2008-05-11') == aniso8601.resolution.IntervalResolution.Day |
|||
True |
|||
>>> aniso8601.get_interval_resolution('2007-03/P1Y') == aniso8601.resolution.IntervalResolution.Month |
|||
True |
|||
>>> aniso8601.get_interval_resolution('2007/P1Y') == aniso8601.resolution.IntervalResolution.Year |
|||
True |
|||
|
|||
And for repeating ISO 8601 interval strings:: |
|||
|
|||
>>> aniso8601.get_repeating_interval_resolution('R3/1981-04-05/P1D') == aniso8601.resolution.IntervalResolution.Day |
|||
True |
|||
>>> aniso8601.get_repeating_interval_resolution('R/PT1H2M/1980-03-05T01:01:00') == aniso8601.resolution.IntervalResolution.Seconds |
|||
True |
|||
|
|||
Builders |
|||
======== |
|||
|
|||
Builders can be used to change the output format of a parse operation. All parse functions have a :code:`builder` keyword argument which accepts a builder class. |
|||
|
|||
Two builders are included. The :code:`PythonTimeBuilder` (the default) in the :code:`aniso8601.builders.python` module, and the :code:`TupleBuilder` which returns the parse result as a corresponding named tuple and is located in the :code:`aniso8601.builders` module. |
|||
|
|||
Information on writing a builder can be found in `BUILDERS </BUILDERS.rst>`_. |
|||
|
|||
The following builders are available as separate projects: |
|||
|
|||
* `RelativeTimeBuilder <https://bitbucket.org/nielsenb/relativetimebuilder>`_ supports parsing to `datetutil relativedelta types <https://dateutil.readthedocs.io/en/stable/relativedelta.html>`_ for calendar level accuracy |
|||
* `AttoTimeBuilder <https://bitbucket.org/nielsenb/attotimebuilder>`_ supports parsing directly to `attotime attodatetime and attotimedelta types <https://bitbucket.org/nielsenb/attotime>`_ which support sub-nanosecond precision |
|||
* `NumPyTimeBuilder <https://bitbucket.org/nielsenb/numpytimebuilder>`_ supports parsing directly to `NumPy datetime64 and timedelta64 types <https://docs.scipy.org/doc/numpy/reference/arrays.datetime.html>`_ |
|||
|
|||
TupleBuilder |
|||
------------ |
|||
|
|||
The :code:`TupleBuilder` returns parse results as `named tuples <https://docs.python.org/3/library/collections.html#collections.namedtuple>`_. It is located in the :code:`aniso8601.builders` module. |
|||
|
|||
Datetimes |
|||
^^^^^^^^^ |
|||
|
|||
Parsing a datetime returns a :code:`DatetimeTuple` containing :code:`Date` and :code:`Time` tuples . The date tuple contains the following parse components: :code:`YYYY`, :code:`MM`, :code:`DD`, :code:`Www`, :code:`D`, :code:`DDD`. The time tuple contains the following parse components :code:`hh`, :code:`mm`, :code:`ss`, :code:`tz`, where :code:`tz` itself is a tuple with the following components :code:`negative`, :code:`Z`, :code:`hh`, :code:`mm`, :code:`name` with :code:`negative` and :code:`Z` being booleans:: |
|||
|
|||
>>> import aniso8601 |
|||
>>> from aniso8601.builders import TupleBuilder |
|||
>>> aniso8601.parse_datetime('1977-06-10T12:00:00', builder=TupleBuilder) |
|||
Datetime(date=Date(YYYY='1977', MM='06', DD='10', Www=None, D=None, DDD=None), time=Time(hh='12', mm='00', ss='00', tz=None)) |
|||
>>> aniso8601.parse_datetime('1979-06-05T08:00:00-08:00', builder=TupleBuilder) |
|||
Datetime(date=Date(YYYY='1979', MM='06', DD='05', Www=None, D=None, DDD=None), time=Time(hh='08', mm='00', ss='00', tz=Timezone(negative=True, Z=None, hh='08', mm='00', name='-08:00'))) |
|||
|
|||
Dates |
|||
^^^^^ |
|||
|
|||
Parsing a date returns a :code:`DateTuple` containing the following parse components: :code:`YYYY`, :code:`MM`, :code:`DD`, :code:`Www`, :code:`D`, :code:`DDD`:: |
|||
|
|||
>>> import aniso8601 |
|||
>>> from aniso8601.builders import TupleBuilder |
|||
>>> aniso8601.parse_date('1984-04-23', builder=TupleBuilder) |
|||
Date(YYYY='1984', MM='04', DD='23', Www=None, D=None, DDD=None) |
|||
>>> aniso8601.parse_date('1986-W38-1', builder=TupleBuilder) |
|||
Date(YYYY='1986', MM=None, DD=None, Www='38', D='1', DDD=None) |
|||
>>> aniso8601.parse_date('1988-132', builder=TupleBuilder) |
|||
Date(YYYY='1988', MM=None, DD=None, Www=None, D=None, DDD='132') |
|||
|
|||
Times |
|||
^^^^^ |
|||
|
|||
Parsing a time returns a :code:`TimeTuple` containing following parse components: :code:`hh`, :code:`mm`, :code:`ss`, :code:`tz`, where :code:`tz` is a :code:`TimezoneTuple` with the following components :code:`negative`, :code:`Z`, :code:`hh`, :code:`mm`, :code:`name`, with :code:`negative` and :code:`Z` being booleans:: |
|||
|
|||
>>> import aniso8601 |
|||
>>> from aniso8601.builders import TupleBuilder |
|||
>>> aniso8601.parse_time('11:31:14', builder=TupleBuilder) |
|||
Time(hh='11', mm='31', ss='14', tz=None) |
|||
>>> aniso8601.parse_time('171819Z', builder=TupleBuilder) |
|||
Time(hh='17', mm='18', ss='19', tz=Timezone(negative=False, Z=True, hh=None, mm=None, name='Z')) |
|||
>>> aniso8601.parse_time('17:18:19-02:30', builder=TupleBuilder) |
|||
Time(hh='17', mm='18', ss='19', tz=Timezone(negative=True, Z=None, hh='02', mm='30', name='-02:30')) |
|||
|
|||
Durations |
|||
^^^^^^^^^ |
|||
|
|||
Parsing a duration returns a :code:`DurationTuple` containing the following parse components: :code:`PnY`, :code:`PnM`, :code:`PnW`, :code:`PnD`, :code:`TnH`, :code:`TnM`, :code:`TnS`:: |
|||
|
|||
>>> import aniso8601 |
|||
>>> from aniso8601.builders import TupleBuilder |
|||
>>> aniso8601.parse_duration('P1Y2M3DT4H54M6S', builder=TupleBuilder) |
|||
Duration(PnY='1', PnM='2', PnW=None, PnD='3', TnH='4', TnM='54', TnS='6') |
|||
>>> aniso8601.parse_duration('P7W', builder=TupleBuilder) |
|||
Duration(PnY=None, PnM=None, PnW='7', PnD=None, TnH=None, TnM=None, TnS=None) |
|||
|
|||
Intervals |
|||
^^^^^^^^^ |
|||
|
|||
Parsing an interval returns an :code:`IntervalTuple` containing the following parse components: :code:`start`, :code:`end`, :code:`duration`, :code:`start` and :code:`end` may both be datetime or date tuples, :code:`duration` is a duration tuple:: |
|||
|
|||
>>> import aniso8601 |
|||
>>> from aniso8601.builders import TupleBuilder |
|||
>>> aniso8601.parse_interval('2007-03-01T13:00:00/2008-05-11T15:30:00', builder=TupleBuilder) |
|||
Interval(start=Datetime(date=Date(YYYY='2007', MM='03', DD='01', Www=None, D=None, DDD=None), time=Time(hh='13', mm='00', ss='00', tz=None)), end=Datetime(date=Date(YYYY='2008', MM='05', DD='11', Www=None, D=None, DDD=None), time=Time(hh='15', mm='30', ss='00', tz=None)), duration=None) |
|||
>>> aniso8601.parse_interval('2007-03-01T13:00:00Z/P1Y2M10DT2H30M', builder=TupleBuilder) |
|||
Interval(start=Datetime(date=Date(YYYY='2007', MM='03', DD='01', Www=None, D=None, DDD=None), time=Time(hh='13', mm='00', ss='00', tz=Timezone(negative=False, Z=True, hh=None, mm=None, name='Z'))), end=None, duration=Duration(PnY='1', PnM='2', PnW=None, PnD='10', TnH='2', TnM='30', TnS=None)) |
|||
>>> aniso8601.parse_interval('P1M/1981-04-05', builder=TupleBuilder) |
|||
Interval(start=None, end=Date(YYYY='1981', MM='04', DD='05', Www=None, D=None, DDD=None), duration=Duration(PnY=None, PnM='1', PnW=None, PnD=None, TnH=None, TnM=None, TnS=None)) |
|||
|
|||
A repeating interval returns a :code:`RepeatingIntervalTuple` containing the following parse components: :code:`R`, :code:`Rnn`, :code:`interval`, where :code:`R` is a boolean, :code:`True` for an unbounded interval, :code:`False` otherwise.:: |
|||
|
|||
>>> aniso8601.parse_repeating_interval('R3/1981-04-05/P1D', builder=TupleBuilder) |
|||
RepeatingInterval(R=False, Rnn='3', interval=Interval(start=Date(YYYY='1981', MM='04', DD='05', Www=None, D=None, DDD=None), end=None, duration=Duration(PnY=None, PnM=None, PnW=None, PnD='1', TnH=None, TnM=None, TnS=None))) |
|||
>>> aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00', builder=TupleBuilder) |
|||
RepeatingInterval(R=True, Rnn=None, interval=Interval(start=None, end=Datetime(date=Date(YYYY='1980', MM='03', DD='05', Www=None, D=None, DDD=None), time=Time(hh='01', mm='01', ss='00', tz=None)), duration=Duration(PnY=None, PnM=None, PnW=None, PnD=None, TnH='1', TnM='2', TnS=None))) |
|||
|
|||
Development |
|||
=========== |
|||
|
|||
Setup |
|||
----- |
|||
|
|||
It is recommended to develop using a `virtualenv <https://virtualenv.pypa.io/en/stable/>`_. |
|||
|
|||
Inside a virtualenv, development dependencies can be installed automatically:: |
|||
|
|||
$ pip install -e .[dev] |
|||
|
|||
`pre-commit <https://pre-commit.com/>`_ is used for managing pre-commit hooks:: |
|||
|
|||
$ pre-commit install |
|||
|
|||
To run the pre-commit hooks manually:: |
|||
|
|||
$ pre-commit run --all-files |
|||
|
|||
Tests |
|||
----- |
|||
|
|||
Tests can be run using the `unittest testing framework <https://docs.python.org/3/library/unittest.html>`_:: |
|||
|
|||
$ python -m unittest discover aniso8601 |
|||
|
|||
Contributing |
|||
============ |
|||
|
|||
aniso8601 is an open source project hosted on `Bitbucket <https://bitbucket.org/nielsenb/aniso8601>`_. |
|||
|
|||
Any and all bugs are welcome on our `issue tracker <https://bitbucket.org/nielsenb/aniso8601/issues>`_. |
|||
Of particular interest are valid ISO 8601 strings that don't parse, or invalid ones that do. At a minimum, |
|||
bug reports should include an example of the misbehaving string, as well as the expected result. Of course |
|||
patches containing unit tests (or fixed bugs) are welcome! |
|||
|
|||
References |
|||
========== |
|||
|
|||
* `ISO 8601:2004(E) <http://dotat.at/tmp/ISO_8601-2004_E.pdf>`_ (Caution, PDF link) |
|||
* `Wikipedia article on ISO 8601 <http://en.wikipedia.org/wiki/Iso8601>`_ |
|||
* `Discussion on alternative ISO 8601 parsers for Python <https://groups.google.com/forum/#!topic/comp.lang.python/Q2w4R89Nq1w>`_ |
|||
|
|||
|
@ -0,0 +1,60 @@ |
|||
aniso8601/__init__.py,sha256=tHN7Nq-3I79PLzKkBuWun_UKgolnDrn7ISO8s1HlMdo,704 |
|||
aniso8601/compat.py,sha256=2exJsHW2DAxt_D2_mGj5mv0HCSMFeAAkPyFAM-ZFrA0,571 |
|||
aniso8601/date.py,sha256=IDn_kqeZshllwr4pICUNZhjbqSVVlYTyHmBOgp2MlNE,4475 |
|||
aniso8601/decimalfraction.py,sha256=EtwqSZJTtsQlu05m2guolhii5N1yN4dVv0v1zCZhiyk,333 |
|||
aniso8601/duration.py,sha256=6AAl9A-WM2Io898peIz9xbwOvxcLc6WYGUdkYuQlTU8,9583 |
|||
aniso8601/exceptions.py,sha256=-zrdcKocZhzhl71HhgVKXWF481XDWO3UhinbcycCzPU,1313 |
|||
aniso8601/interval.py,sha256=7e5wICHdF2gTeFluPxBrzaA4-_5b78QzXC62DSnNzlM,10763 |
|||
aniso8601/resolution.py,sha256=ee7GxL865D0dJL70TsXScz4Kzo_dwMORNvfuyCXdsgI,684 |
|||
aniso8601/time.py,sha256=9IRsCERfEl_SnBBUIOR8E43XFD7Y2EqhowjiCcfinb0,5688 |
|||
aniso8601/timezone.py,sha256=5_LRd_pYd08i2hmXsn_1tTUxKOI4caSvxci-VByHCWU,2134 |
|||
aniso8601/utcoffset.py,sha256=8Gh8WNk_q9ELLEFZLMPbMESH-yqcoNFjul7VcpHq_1Q,2423 |
|||
aniso8601/builders/__init__.py,sha256=sJanTP5Lo0lRxpLa5VKVBS9u6ZP8R1VRgozx5uSUMUU,17958 |
|||
aniso8601/builders/python.py,sha256=I0RhPY2syncaMwYRVJxM6ct-O_5MHnNFY3dcF6wvy0Y,22122 |
|||
aniso8601/builders/tests/__init__.py,sha256=XWM00Wzg9EZkSKyy3IW18Z8TiXfCbJS-XJNFVuylvuU,209 |
|||
aniso8601/builders/tests/test_init.py,sha256=wnDhjyb5iBt9l_zTXT96uqXus-igSqn5Kn_rqX_NSHA,29997 |
|||
aniso8601/builders/tests/test_python.py,sha256=pNr3lwfBKVSUQKc5BPmwCiCTpP_063WpOM-canDz4J8,61593 |
|||
aniso8601/tests/__init__.py,sha256=XWM00Wzg9EZkSKyy3IW18Z8TiXfCbJS-XJNFVuylvuU,209 |
|||
aniso8601/tests/compat.py,sha256=9HJqKvl0PIFBjePUgT-1eMGkA9tlESX0wNDkPvV7GOk,346 |
|||
aniso8601/tests/test_compat.py,sha256=2oFOFLKTfOJIMbLjkeVhrkxSDMjE0wM-NB86SJ6st5g,763 |
|||
aniso8601/tests/test_date.py,sha256=3AWmIHTS2sxm9_ZUYcI2w9ALJOYnHkkYEwlD1VW90iQ,8960 |
|||
aniso8601/tests/test_decimalfraction.py,sha256=T4R_SY24DW30YuQkyofxvAmngTuXtsmwd77pF25QAlc,578 |
|||
aniso8601/tests/test_duration.py,sha256=ZqUxodLrDBZ1GZWutFXjktAFHYS1hidxLclIGZP7aSA,44952 |
|||
aniso8601/tests/test_init.py,sha256=GazCeGTv-OFocCx9Cck04b-c1cWiiRnqhGwoGgm4Y1Q,1689 |
|||
aniso8601/tests/test_interval.py,sha256=lTg-E1vW1xmgwiWfHHwJDJ25AogSR-1p-0L4O2gQKQw,60457 |
|||
aniso8601/tests/test_time.py,sha256=HLutGVdg2_HHU51U2eEEZ9UNwljrQBPU_PtX8JrdVV0,19147 |
|||
aniso8601/tests/test_timezone.py,sha256=Shw7-fcUJZAbH7diCx37iXZ4VZEH45lqIgMJvoQQhtQ,4649 |
|||
aniso8601/tests/test_utcoffset.py,sha256=fRNuiz3WPMrHtrdMGK3HOuZRYd68hR-VNldbwVG-cDA,1926 |
|||
aniso8601-9.0.1.dist-info/LICENSE,sha256=Z_-MGC_A4Nc1cViNi8B5tOSmJKknTE4mqSPeIxDTvSk,1501 |
|||
aniso8601-9.0.1.dist-info/METADATA,sha256=8x7vpReMZppobPRH8Q564bwHL9XFsgCFR3TKrHhfkjE,23431 |
|||
aniso8601-9.0.1.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110 |
|||
aniso8601-9.0.1.dist-info/top_level.txt,sha256=MVQomyeED8nGIH7PUQdMzxgLppIB48oYHtcmL17ETB0,10 |
|||
aniso8601-9.0.1.dist-info/RECORD,, |
|||
aniso8601-9.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 |
|||
aniso8601/builders/tests/__pycache__/test_init.cpython-37.pyc,, |
|||
aniso8601/builders/tests/__pycache__/test_python.cpython-37.pyc,, |
|||
aniso8601/builders/tests/__pycache__/__init__.cpython-37.pyc,, |
|||
aniso8601/builders/__pycache__/python.cpython-37.pyc,, |
|||
aniso8601/builders/__pycache__/__init__.cpython-37.pyc,, |
|||
aniso8601/tests/__pycache__/compat.cpython-37.pyc,, |
|||
aniso8601/tests/__pycache__/test_compat.cpython-37.pyc,, |
|||
aniso8601/tests/__pycache__/test_date.cpython-37.pyc,, |
|||
aniso8601/tests/__pycache__/test_decimalfraction.cpython-37.pyc,, |
|||
aniso8601/tests/__pycache__/test_duration.cpython-37.pyc,, |
|||
aniso8601/tests/__pycache__/test_init.cpython-37.pyc,, |
|||
aniso8601/tests/__pycache__/test_interval.cpython-37.pyc,, |
|||
aniso8601/tests/__pycache__/test_time.cpython-37.pyc,, |
|||
aniso8601/tests/__pycache__/test_timezone.cpython-37.pyc,, |
|||
aniso8601/tests/__pycache__/test_utcoffset.cpython-37.pyc,, |
|||
aniso8601/tests/__pycache__/__init__.cpython-37.pyc,, |
|||
aniso8601/__pycache__/compat.cpython-37.pyc,, |
|||
aniso8601/__pycache__/date.cpython-37.pyc,, |
|||
aniso8601/__pycache__/decimalfraction.cpython-37.pyc,, |
|||
aniso8601/__pycache__/duration.cpython-37.pyc,, |
|||
aniso8601/__pycache__/exceptions.cpython-37.pyc,, |
|||
aniso8601/__pycache__/interval.cpython-37.pyc,, |
|||
aniso8601/__pycache__/resolution.cpython-37.pyc,, |
|||
aniso8601/__pycache__/time.cpython-37.pyc,, |
|||
aniso8601/__pycache__/timezone.cpython-37.pyc,, |
|||
aniso8601/__pycache__/utcoffset.cpython-37.pyc,, |
|||
aniso8601/__pycache__/__init__.cpython-37.pyc,, |
@ -0,0 +1,6 @@ |
|||
Wheel-Version: 1.0 |
|||
Generator: bdist_wheel (0.36.2) |
|||
Root-Is-Purelib: true |
|||
Tag: py2-none-any |
|||
Tag: py3-none-any |
|||
|
@ -0,0 +1 @@ |
|||
aniso8601 |
@ -0,0 +1,26 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
|||
|
|||
from aniso8601.date import get_date_resolution, parse_date |
|||
from aniso8601.duration import get_duration_resolution, parse_duration |
|||
from aniso8601.interval import ( |
|||
get_interval_resolution, |
|||
get_repeating_interval_resolution, |
|||
parse_interval, |
|||
parse_repeating_interval, |
|||
) |
|||
|
|||
# Import the main parsing functions so they are readily available |
|||
from aniso8601.time import ( |
|||
get_datetime_resolution, |
|||
get_time_resolution, |
|||
parse_datetime, |
|||
parse_time, |
|||
) |
|||
|
|||
__version__ = "9.0.1" |
@ -0,0 +1,614 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
|||
|
|||
import calendar |
|||
from collections import namedtuple |
|||
|
|||
from aniso8601.exceptions import ( |
|||
DayOutOfBoundsError, |
|||
HoursOutOfBoundsError, |
|||
ISOFormatError, |
|||
LeapSecondError, |
|||
MidnightBoundsError, |
|||
MinutesOutOfBoundsError, |
|||
MonthOutOfBoundsError, |
|||
SecondsOutOfBoundsError, |
|||
WeekOutOfBoundsError, |
|||
YearOutOfBoundsError, |
|||
) |
|||
|
|||
DateTuple = namedtuple("Date", ["YYYY", "MM", "DD", "Www", "D", "DDD"]) |
|||
TimeTuple = namedtuple("Time", ["hh", "mm", "ss", "tz"]) |
|||
DatetimeTuple = namedtuple("Datetime", ["date", "time"]) |
|||
DurationTuple = namedtuple( |
|||
"Duration", ["PnY", "PnM", "PnW", "PnD", "TnH", "TnM", "TnS"] |
|||
) |
|||
IntervalTuple = namedtuple("Interval", ["start", "end", "duration"]) |
|||
RepeatingIntervalTuple = namedtuple("RepeatingInterval", ["R", "Rnn", "interval"]) |
|||
TimezoneTuple = namedtuple("Timezone", ["negative", "Z", "hh", "mm", "name"]) |
|||
|
|||
Limit = namedtuple( |
|||
"Limit", |
|||
[ |
|||
"casterrorstring", |
|||
"min", |
|||
"max", |
|||
"rangeexception", |
|||
"rangeerrorstring", |
|||
"rangefunc", |
|||
], |
|||
) |
|||
|
|||
|
|||
def cast( |
|||
value, |
|||
castfunction, |
|||
caughtexceptions=(ValueError,), |
|||
thrownexception=ISOFormatError, |
|||
thrownmessage=None, |
|||
): |
|||
try: |
|||
result = castfunction(value) |
|||
except caughtexceptions: |
|||
raise thrownexception(thrownmessage) |
|||
|
|||
return result |
|||
|
|||
|
|||
def range_check(valuestr, limit): |
|||
# Returns cast value if in range, raises defined exceptions on failure |
|||
if valuestr is None: |
|||
return None |
|||
|
|||
if "." in valuestr: |
|||
castfunc = float |
|||
else: |
|||
castfunc = int |
|||
|
|||
value = cast(valuestr, castfunc, thrownmessage=limit.casterrorstring) |
|||
|
|||
if limit.min is not None and value < limit.min: |
|||
raise limit.rangeexception(limit.rangeerrorstring) |
|||
|
|||
if limit.max is not None and value > limit.max: |
|||
raise limit.rangeexception(limit.rangeerrorstring) |
|||
|
|||
return value |
|||
|
|||
|
|||
class BaseTimeBuilder(object): |
|||
# Limit tuple format cast function, cast error string, |
|||
# lower limit, upper limit, limit error string |
|||
DATE_YYYY_LIMIT = Limit( |
|||
"Invalid year string.", |
|||
0000, |
|||
9999, |
|||
YearOutOfBoundsError, |
|||
"Year must be between 1..9999.", |
|||
range_check, |
|||
) |
|||
DATE_MM_LIMIT = Limit( |
|||
"Invalid month string.", |
|||
1, |
|||
12, |
|||
MonthOutOfBoundsError, |
|||
"Month must be between 1..12.", |
|||
range_check, |
|||
) |
|||
DATE_DD_LIMIT = Limit( |
|||
"Invalid day string.", |
|||
1, |
|||
31, |
|||
DayOutOfBoundsError, |
|||
"Day must be between 1..31.", |
|||
range_check, |
|||
) |
|||
DATE_WWW_LIMIT = Limit( |
|||
"Invalid week string.", |
|||
1, |
|||
53, |
|||
WeekOutOfBoundsError, |
|||
"Week number must be between 1..53.", |
|||
range_check, |
|||
) |
|||
DATE_D_LIMIT = Limit( |
|||
"Invalid weekday string.", |
|||
1, |
|||
7, |
|||
DayOutOfBoundsError, |
|||
"Weekday number must be between 1..7.", |
|||
range_check, |
|||
) |
|||
DATE_DDD_LIMIT = Limit( |
|||
"Invalid ordinal day string.", |
|||
1, |
|||
366, |
|||
DayOutOfBoundsError, |
|||
"Ordinal day must be between 1..366.", |
|||
range_check, |
|||
) |
|||
TIME_HH_LIMIT = Limit( |
|||
"Invalid hour string.", |
|||
0, |
|||
24, |
|||
HoursOutOfBoundsError, |
|||
"Hour must be between 0..24 with " "24 representing midnight.", |
|||
range_check, |
|||
) |
|||
TIME_MM_LIMIT = Limit( |
|||
"Invalid minute string.", |
|||
0, |
|||
59, |
|||
MinutesOutOfBoundsError, |
|||
"Minute must be between 0..59.", |
|||
range_check, |
|||
) |
|||
TIME_SS_LIMIT = Limit( |
|||
"Invalid second string.", |
|||
0, |
|||
60, |
|||
SecondsOutOfBoundsError, |
|||
"Second must be between 0..60 with " "60 representing a leap second.", |
|||
range_check, |
|||
) |
|||
TZ_HH_LIMIT = Limit( |
|||
"Invalid timezone hour string.", |
|||
0, |
|||
23, |
|||
HoursOutOfBoundsError, |
|||
"Hour must be between 0..23.", |
|||
range_check, |
|||
) |
|||
TZ_MM_LIMIT = Limit( |
|||
"Invalid timezone minute string.", |
|||
0, |
|||
59, |
|||
MinutesOutOfBoundsError, |
|||
"Minute must be between 0..59.", |
|||
range_check, |
|||
) |
|||
DURATION_PNY_LIMIT = Limit( |
|||
"Invalid year duration string.", |
|||
0, |
|||
None, |
|||
ISOFormatError, |
|||
"Duration years component must be positive.", |
|||
range_check, |
|||
) |
|||
DURATION_PNM_LIMIT = Limit( |
|||
"Invalid month duration string.", |
|||
0, |
|||
None, |
|||
ISOFormatError, |
|||
"Duration months component must be positive.", |
|||
range_check, |
|||
) |
|||
DURATION_PNW_LIMIT = Limit( |
|||
"Invalid week duration string.", |
|||
0, |
|||
None, |
|||
ISOFormatError, |
|||
"Duration weeks component must be positive.", |
|||
range_check, |
|||
) |
|||
DURATION_PND_LIMIT = Limit( |
|||
"Invalid day duration string.", |
|||
0, |
|||
None, |
|||
ISOFormatError, |
|||
"Duration days component must be positive.", |
|||
range_check, |
|||
) |
|||
DURATION_TNH_LIMIT = Limit( |
|||
"Invalid hour duration string.", |
|||
0, |
|||
None, |
|||
ISOFormatError, |
|||
"Duration hours component must be positive.", |
|||
range_check, |
|||
) |
|||
DURATION_TNM_LIMIT = Limit( |
|||
"Invalid minute duration string.", |
|||
0, |
|||
None, |
|||
ISOFormatError, |
|||
"Duration minutes component must be positive.", |
|||
range_check, |
|||
) |
|||
DURATION_TNS_LIMIT = Limit( |
|||
"Invalid second duration string.", |
|||
0, |
|||
None, |
|||
ISOFormatError, |
|||
"Duration seconds component must be positive.", |
|||
range_check, |
|||
) |
|||
INTERVAL_RNN_LIMIT = Limit( |
|||
"Invalid duration repetition string.", |
|||
0, |
|||
None, |
|||
ISOFormatError, |
|||
"Duration repetition count must be positive.", |
|||
range_check, |
|||
) |
|||
|
|||
DATE_RANGE_DICT = { |
|||
"YYYY": DATE_YYYY_LIMIT, |
|||
"MM": DATE_MM_LIMIT, |
|||
"DD": DATE_DD_LIMIT, |
|||
"Www": DATE_WWW_LIMIT, |
|||
"D": DATE_D_LIMIT, |
|||
"DDD": DATE_DDD_LIMIT, |
|||
} |
|||
|
|||
TIME_RANGE_DICT = {"hh": TIME_HH_LIMIT, "mm": TIME_MM_LIMIT, "ss": TIME_SS_LIMIT} |
|||
|
|||
DURATION_RANGE_DICT = { |
|||
"PnY": DURATION_PNY_LIMIT, |
|||
"PnM": DURATION_PNM_LIMIT, |
|||
"PnW": DURATION_PNW_LIMIT, |
|||
"PnD": DURATION_PND_LIMIT, |
|||
"TnH": DURATION_TNH_LIMIT, |
|||
"TnM": DURATION_TNM_LIMIT, |
|||
"TnS": DURATION_TNS_LIMIT, |
|||
} |
|||
|
|||
REPEATING_INTERVAL_RANGE_DICT = {"Rnn": INTERVAL_RNN_LIMIT} |
|||
|
|||
TIMEZONE_RANGE_DICT = {"hh": TZ_HH_LIMIT, "mm": TZ_MM_LIMIT} |
|||
|
|||
LEAP_SECONDS_SUPPORTED = False |
|||
|
|||
@classmethod |
|||
def build_date(cls, YYYY=None, MM=None, DD=None, Www=None, D=None, DDD=None): |
|||
raise NotImplementedError |
|||
|
|||
@classmethod |
|||
def build_time(cls, hh=None, mm=None, ss=None, tz=None): |
|||
raise NotImplementedError |
|||
|
|||
@classmethod |
|||
def build_datetime(cls, date, time): |
|||
raise NotImplementedError |
|||
|
|||
@classmethod |
|||
def build_duration( |
|||
cls, PnY=None, PnM=None, PnW=None, PnD=None, TnH=None, TnM=None, TnS=None |
|||
): |
|||
raise NotImplementedError |
|||
|
|||
@classmethod |
|||
def build_interval(cls, start=None, end=None, duration=None): |
|||
# start, end, and duration are all tuples |
|||
raise NotImplementedError |
|||
|
|||
@classmethod |
|||
def build_repeating_interval(cls, R=None, Rnn=None, interval=None): |
|||
# interval is a tuple |
|||
raise NotImplementedError |
|||
|
|||
@classmethod |
|||
def build_timezone(cls, negative=None, Z=None, hh=None, mm=None, name=""): |
|||
raise NotImplementedError |
|||
|
|||
@classmethod |
|||
def range_check_date( |
|||
cls, YYYY=None, MM=None, DD=None, Www=None, D=None, DDD=None, rangedict=None |
|||
): |
|||
if rangedict is None: |
|||
rangedict = cls.DATE_RANGE_DICT |
|||
|
|||
if "YYYY" in rangedict: |
|||
YYYY = rangedict["YYYY"].rangefunc(YYYY, rangedict["YYYY"]) |
|||
|
|||
if "MM" in rangedict: |
|||
MM = rangedict["MM"].rangefunc(MM, rangedict["MM"]) |
|||
|
|||
if "DD" in rangedict: |
|||
DD = rangedict["DD"].rangefunc(DD, rangedict["DD"]) |
|||
|
|||
if "Www" in rangedict: |
|||
Www = rangedict["Www"].rangefunc(Www, rangedict["Www"]) |
|||
|
|||
if "D" in rangedict: |
|||
D = rangedict["D"].rangefunc(D, rangedict["D"]) |
|||
|
|||
if "DDD" in rangedict: |
|||
DDD = rangedict["DDD"].rangefunc(DDD, rangedict["DDD"]) |
|||
|
|||
if DD is not None: |
|||
# Check calendar |
|||
if DD > calendar.monthrange(YYYY, MM)[1]: |
|||
raise DayOutOfBoundsError( |
|||
"{0} is out of range for {1}-{2}".format(DD, YYYY, MM) |
|||
) |
|||
|
|||
if DDD is not None: |
|||
if calendar.isleap(YYYY) is False and DDD == 366: |
|||
raise DayOutOfBoundsError( |
|||
"{0} is only valid for leap year.".format(DDD) |
|||
) |
|||
|
|||
return (YYYY, MM, DD, Www, D, DDD) |
|||
|
|||
@classmethod |
|||
def range_check_time(cls, hh=None, mm=None, ss=None, tz=None, rangedict=None): |
|||
# Used for midnight and leap second handling |
|||
midnight = False # Handle hh = '24' specially |
|||
|
|||
if rangedict is None: |
|||
rangedict = cls.TIME_RANGE_DICT |
|||
|
|||
if "hh" in rangedict: |
|||
try: |
|||
hh = rangedict["hh"].rangefunc(hh, rangedict["hh"]) |
|||
except HoursOutOfBoundsError as e: |
|||
if float(hh) > 24 and float(hh) < 25: |
|||
raise MidnightBoundsError("Hour 24 may only represent midnight.") |
|||
|
|||
raise e |
|||
|
|||
if "mm" in rangedict: |
|||
mm = rangedict["mm"].rangefunc(mm, rangedict["mm"]) |
|||
|
|||
if "ss" in rangedict: |
|||
ss = rangedict["ss"].rangefunc(ss, rangedict["ss"]) |
|||
|
|||
if hh is not None and hh == 24: |
|||
midnight = True |
|||
|
|||
# Handle midnight range |
|||
if midnight is True and ( |
|||
(mm is not None and mm != 0) or (ss is not None and ss != 0) |
|||
): |
|||
raise MidnightBoundsError("Hour 24 may only represent midnight.") |
|||
|
|||
if cls.LEAP_SECONDS_SUPPORTED is True: |
|||
if hh != 23 and mm != 59 and ss == 60: |
|||
raise cls.TIME_SS_LIMIT.rangeexception( |
|||
cls.TIME_SS_LIMIT.rangeerrorstring |
|||
) |
|||
else: |
|||
if hh == 23 and mm == 59 and ss == 60: |
|||
# https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is |
|||
raise LeapSecondError("Leap seconds are not supported.") |
|||
|
|||
if ss == 60: |
|||
raise cls.TIME_SS_LIMIT.rangeexception( |
|||
cls.TIME_SS_LIMIT.rangeerrorstring |
|||
) |
|||
|
|||
return (hh, mm, ss, tz) |
|||
|
|||
@classmethod |
|||
def range_check_duration( |
|||
cls, |
|||
PnY=None, |
|||
PnM=None, |
|||
PnW=None, |
|||
PnD=None, |
|||
TnH=None, |
|||
TnM=None, |
|||
TnS=None, |
|||
rangedict=None, |
|||
): |
|||
if rangedict is None: |
|||
rangedict = cls.DURATION_RANGE_DICT |
|||
|
|||
if "PnY" in rangedict: |
|||
PnY = rangedict["PnY"].rangefunc(PnY, rangedict["PnY"]) |
|||
|
|||
if "PnM" in rangedict: |
|||
PnM = rangedict["PnM"].rangefunc(PnM, rangedict["PnM"]) |
|||
|
|||
if "PnW" in rangedict: |
|||
PnW = rangedict["PnW"].rangefunc(PnW, rangedict["PnW"]) |
|||
|
|||
if "PnD" in rangedict: |
|||
PnD = rangedict["PnD"].rangefunc(PnD, rangedict["PnD"]) |
|||
|
|||
if "TnH" in rangedict: |
|||
TnH = rangedict["TnH"].rangefunc(TnH, rangedict["TnH"]) |
|||
|
|||
if "TnM" in rangedict: |
|||
TnM = rangedict["TnM"].rangefunc(TnM, rangedict["TnM"]) |
|||
|
|||
if "TnS" in rangedict: |
|||
TnS = rangedict["TnS"].rangefunc(TnS, rangedict["TnS"]) |
|||
|
|||
return (PnY, PnM, PnW, PnD, TnH, TnM, TnS) |
|||
|
|||
@classmethod |
|||
def range_check_repeating_interval( |
|||
cls, R=None, Rnn=None, interval=None, rangedict=None |
|||
): |
|||
if rangedict is None: |
|||
rangedict = cls.REPEATING_INTERVAL_RANGE_DICT |
|||
|
|||
if "Rnn" in rangedict: |
|||
Rnn = rangedict["Rnn"].rangefunc(Rnn, rangedict["Rnn"]) |
|||
|
|||
return (R, Rnn, interval) |
|||
|
|||
@classmethod |
|||
def range_check_timezone( |
|||
cls, negative=None, Z=None, hh=None, mm=None, name="", rangedict=None |
|||
): |
|||
if rangedict is None: |
|||
rangedict = cls.TIMEZONE_RANGE_DICT |
|||
|
|||
if "hh" in rangedict: |
|||
hh = rangedict["hh"].rangefunc(hh, rangedict["hh"]) |
|||
|
|||
if "mm" in rangedict: |
|||
mm = rangedict["mm"].rangefunc(mm, rangedict["mm"]) |
|||
|
|||
return (negative, Z, hh, mm, name) |
|||
|
|||
@classmethod |
|||
def _build_object(cls, parsetuple): |
|||
# Given a TupleBuilder tuple, build the correct object |
|||
if type(parsetuple) is DateTuple: |
|||
return cls.build_date( |
|||
YYYY=parsetuple.YYYY, |
|||
MM=parsetuple.MM, |
|||
DD=parsetuple.DD, |
|||
Www=parsetuple.Www, |
|||
D=parsetuple.D, |
|||
DDD=parsetuple.DDD, |
|||
) |
|||
|
|||
if type(parsetuple) is TimeTuple: |
|||
return cls.build_time( |
|||
hh=parsetuple.hh, mm=parsetuple.mm, ss=parsetuple.ss, tz=parsetuple.tz |
|||
) |
|||
|
|||
if type(parsetuple) is DatetimeTuple: |
|||
return cls.build_datetime(parsetuple.date, parsetuple.time) |
|||
|
|||
if type(parsetuple) is DurationTuple: |
|||
return cls.build_duration( |
|||
PnY=parsetuple.PnY, |
|||
PnM=parsetuple.PnM, |
|||
PnW=parsetuple.PnW, |
|||
PnD=parsetuple.PnD, |
|||
TnH=parsetuple.TnH, |
|||
TnM=parsetuple.TnM, |
|||
TnS=parsetuple.TnS, |
|||
) |
|||
|
|||
if type(parsetuple) is IntervalTuple: |
|||
return cls.build_interval( |
|||
start=parsetuple.start, end=parsetuple.end, duration=parsetuple.duration |
|||
) |
|||
|
|||
if type(parsetuple) is RepeatingIntervalTuple: |
|||
return cls.build_repeating_interval( |
|||
R=parsetuple.R, Rnn=parsetuple.Rnn, interval=parsetuple.interval |
|||
) |
|||
|
|||
return cls.build_timezone( |
|||
negative=parsetuple.negative, |
|||
Z=parsetuple.Z, |
|||
hh=parsetuple.hh, |
|||
mm=parsetuple.mm, |
|||
name=parsetuple.name, |
|||
) |
|||
|
|||
@classmethod |
|||
def _is_interval_end_concise(cls, endtuple): |
|||
if type(endtuple) is TimeTuple: |
|||
return True |
|||
|
|||
if type(endtuple) is DatetimeTuple: |
|||
enddatetuple = endtuple.date |
|||
else: |
|||
enddatetuple = endtuple |
|||
|
|||
if enddatetuple.YYYY is None: |
|||
return True |
|||
|
|||
return False |
|||
|
|||
@classmethod |
|||
def _combine_concise_interval_tuples(cls, starttuple, conciseendtuple): |
|||
starttimetuple = None |
|||
startdatetuple = None |
|||
|
|||
endtimetuple = None |
|||
enddatetuple = None |
|||
|
|||
if type(starttuple) is DateTuple: |
|||
startdatetuple = starttuple |
|||
else: |
|||
# Start is a datetime |
|||
starttimetuple = starttuple.time |
|||
startdatetuple = starttuple.date |
|||
|
|||
if type(conciseendtuple) is DateTuple: |
|||
enddatetuple = conciseendtuple |
|||
elif type(conciseendtuple) is DatetimeTuple: |
|||
enddatetuple = conciseendtuple.date |
|||
endtimetuple = conciseendtuple.time |
|||
else: |
|||
# Time |
|||
endtimetuple = conciseendtuple |
|||
|
|||
if enddatetuple is not None: |
|||
if enddatetuple.YYYY is None and enddatetuple.MM is None: |
|||
newenddatetuple = DateTuple( |
|||
YYYY=startdatetuple.YYYY, |
|||
MM=startdatetuple.MM, |
|||
DD=enddatetuple.DD, |
|||
Www=enddatetuple.Www, |
|||
D=enddatetuple.D, |
|||
DDD=enddatetuple.DDD, |
|||
) |
|||
else: |
|||
newenddatetuple = DateTuple( |
|||
YYYY=startdatetuple.YYYY, |
|||
MM=enddatetuple.MM, |
|||
DD=enddatetuple.DD, |
|||
Www=enddatetuple.Www, |
|||
D=enddatetuple.D, |
|||
DDD=enddatetuple.DDD, |
|||
) |
|||
|
|||
if (starttimetuple is not None and starttimetuple.tz is not None) and ( |
|||
endtimetuple is not None and endtimetuple.tz != starttimetuple.tz |
|||
): |
|||
# Copy the timezone across |
|||
endtimetuple = TimeTuple( |
|||
hh=endtimetuple.hh, |
|||
mm=endtimetuple.mm, |
|||
ss=endtimetuple.ss, |
|||
tz=starttimetuple.tz, |
|||
) |
|||
|
|||
if enddatetuple is not None and endtimetuple is None: |
|||
return newenddatetuple |
|||
|
|||
if enddatetuple is not None and endtimetuple is not None: |
|||
return TupleBuilder.build_datetime(newenddatetuple, endtimetuple) |
|||
|
|||
return TupleBuilder.build_datetime(startdatetuple, endtimetuple) |
|||
|
|||
|
|||
class TupleBuilder(BaseTimeBuilder): |
|||
# Builder used to return the arguments as a tuple, cleans up some parse methods |
|||
@classmethod |
|||
def build_date(cls, YYYY=None, MM=None, DD=None, Www=None, D=None, DDD=None): |
|||
|
|||
return DateTuple(YYYY, MM, DD, Www, D, DDD) |
|||
|
|||
@classmethod |
|||
def build_time(cls, hh=None, mm=None, ss=None, tz=None): |
|||
return TimeTuple(hh, mm, ss, tz) |
|||
|
|||
@classmethod |
|||
def build_datetime(cls, date, time): |
|||
return DatetimeTuple(date, time) |
|||
|
|||
@classmethod |
|||
def build_duration( |
|||
cls, PnY=None, PnM=None, PnW=None, PnD=None, TnH=None, TnM=None, TnS=None |
|||
): |
|||
|
|||
return DurationTuple(PnY, PnM, PnW, PnD, TnH, TnM, TnS) |
|||
|
|||
@classmethod |
|||
def build_interval(cls, start=None, end=None, duration=None): |
|||
return IntervalTuple(start, end, duration) |
|||
|
|||
@classmethod |
|||
def build_repeating_interval(cls, R=None, Rnn=None, interval=None): |
|||
return RepeatingIntervalTuple(R, Rnn, interval) |
|||
|
|||
@classmethod |
|||
def build_timezone(cls, negative=None, Z=None, hh=None, mm=None, name=""): |
|||
return TimezoneTuple(negative, Z, hh, mm, name) |
@ -0,0 +1,705 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
|||
|
|||
import datetime |
|||
from collections import namedtuple |
|||
from functools import partial |
|||
|
|||
from aniso8601.builders import ( |
|||
BaseTimeBuilder, |
|||
DatetimeTuple, |
|||
DateTuple, |
|||
Limit, |
|||
TimeTuple, |
|||
TupleBuilder, |
|||
cast, |
|||
range_check, |
|||
) |
|||
from aniso8601.exceptions import ( |
|||
DayOutOfBoundsError, |
|||
HoursOutOfBoundsError, |
|||
ISOFormatError, |
|||
LeapSecondError, |
|||
MidnightBoundsError, |
|||
MinutesOutOfBoundsError, |
|||
MonthOutOfBoundsError, |
|||
SecondsOutOfBoundsError, |
|||
WeekOutOfBoundsError, |
|||
YearOutOfBoundsError, |
|||
) |
|||
from aniso8601.utcoffset import UTCOffset |
|||
|
|||
DAYS_PER_YEAR = 365 |
|||
DAYS_PER_MONTH = 30 |
|||
DAYS_PER_WEEK = 7 |
|||
|
|||
HOURS_PER_DAY = 24 |
|||
|
|||
MINUTES_PER_HOUR = 60 |
|||
MINUTES_PER_DAY = MINUTES_PER_HOUR * HOURS_PER_DAY |
|||
|
|||
SECONDS_PER_MINUTE = 60 |
|||
SECONDS_PER_DAY = MINUTES_PER_DAY * SECONDS_PER_MINUTE |
|||
|
|||
MICROSECONDS_PER_SECOND = int(1e6) |
|||
|
|||
MICROSECONDS_PER_MINUTE = 60 * MICROSECONDS_PER_SECOND |
|||
MICROSECONDS_PER_HOUR = 60 * MICROSECONDS_PER_MINUTE |
|||
MICROSECONDS_PER_DAY = 24 * MICROSECONDS_PER_HOUR |
|||
MICROSECONDS_PER_WEEK = 7 * MICROSECONDS_PER_DAY |
|||
MICROSECONDS_PER_MONTH = DAYS_PER_MONTH * MICROSECONDS_PER_DAY |
|||
MICROSECONDS_PER_YEAR = DAYS_PER_YEAR * MICROSECONDS_PER_DAY |
|||
|
|||
TIMEDELTA_MAX_DAYS = datetime.timedelta.max.days |
|||
|
|||
FractionalComponent = namedtuple( |
|||
"FractionalComponent", ["principal", "microsecondremainder"] |
|||
) |
|||
|
|||
|
|||
def year_range_check(valuestr, limit): |
|||
YYYYstr = valuestr |
|||
|
|||
# Truncated dates, like '19', refer to 1900-1999 inclusive, |
|||
# we simply parse to 1900 |
|||
if len(valuestr) < 4: |
|||
# Shift 0s in from the left to form complete year |
|||
YYYYstr = valuestr.ljust(4, "0") |
|||
|
|||
return range_check(YYYYstr, limit) |
|||
|
|||
|
|||
def fractional_range_check(conversion, valuestr, limit): |
|||
if valuestr is None: |
|||
return None |
|||
|
|||
if "." in valuestr: |
|||
castfunc = partial(_cast_to_fractional_component, conversion) |
|||
else: |
|||
castfunc = int |
|||
|
|||
value = cast(valuestr, castfunc, thrownmessage=limit.casterrorstring) |
|||
|
|||
if type(value) is FractionalComponent: |
|||
tocheck = float(valuestr) |
|||
else: |
|||
tocheck = int(valuestr) |
|||
|
|||
if limit.min is not None and tocheck < limit.min: |
|||
raise limit.rangeexception(limit.rangeerrorstring) |
|||
|
|||
if limit.max is not None and tocheck > limit.max: |
|||
raise limit.rangeexception(limit.rangeerrorstring) |
|||
|
|||
return value |
|||
|
|||
|
|||
def _cast_to_fractional_component(conversion, floatstr): |
|||
# Splits a string with a decimal point into an int, and |
|||
# int representing the floating point remainder as a number |
|||
# of microseconds, determined by multiplying by conversion |
|||
intpart, floatpart = floatstr.split(".") |
|||
|
|||
intvalue = int(intpart) |
|||
preconvertedvalue = int(floatpart) |
|||
|
|||
convertedvalue = (preconvertedvalue * conversion) // (10 ** len(floatpart)) |
|||
|
|||
return FractionalComponent(intvalue, convertedvalue) |
|||
|
|||
|
|||
class PythonTimeBuilder(BaseTimeBuilder): |
|||
# 0000 (1 BC) is not representable as a Python date |
|||
DATE_YYYY_LIMIT = Limit( |
|||
"Invalid year string.", |
|||
datetime.MINYEAR, |
|||
datetime.MAXYEAR, |
|||
YearOutOfBoundsError, |
|||
"Year must be between {0}..{1}.".format(datetime.MINYEAR, datetime.MAXYEAR), |
|||
year_range_check, |
|||
) |
|||
TIME_HH_LIMIT = Limit( |
|||
"Invalid hour string.", |
|||
0, |
|||
24, |
|||
HoursOutOfBoundsError, |
|||
"Hour must be between 0..24 with " "24 representing midnight.", |
|||
partial(fractional_range_check, MICROSECONDS_PER_HOUR), |
|||
) |
|||
TIME_MM_LIMIT = Limit( |
|||
"Invalid minute string.", |
|||
0, |
|||
59, |
|||
MinutesOutOfBoundsError, |
|||
"Minute must be between 0..59.", |
|||
partial(fractional_range_check, MICROSECONDS_PER_MINUTE), |
|||
) |
|||
TIME_SS_LIMIT = Limit( |
|||
"Invalid second string.", |
|||
0, |
|||
60, |
|||
SecondsOutOfBoundsError, |
|||
"Second must be between 0..60 with " "60 representing a leap second.", |
|||
partial(fractional_range_check, MICROSECONDS_PER_SECOND), |
|||
) |
|||
DURATION_PNY_LIMIT = Limit( |
|||
"Invalid year duration string.", |
|||
None, |
|||
None, |
|||
YearOutOfBoundsError, |
|||
None, |
|||
partial(fractional_range_check, MICROSECONDS_PER_YEAR), |
|||
) |
|||
DURATION_PNM_LIMIT = Limit( |
|||
"Invalid month duration string.", |
|||
None, |
|||
None, |
|||
MonthOutOfBoundsError, |
|||
None, |
|||
partial(fractional_range_check, MICROSECONDS_PER_MONTH), |
|||
) |
|||
DURATION_PNW_LIMIT = Limit( |
|||
"Invalid week duration string.", |
|||
None, |
|||
None, |
|||
WeekOutOfBoundsError, |
|||
None, |
|||
partial(fractional_range_check, MICROSECONDS_PER_WEEK), |
|||
) |
|||
DURATION_PND_LIMIT = Limit( |
|||
"Invalid day duration string.", |
|||
None, |
|||
None, |
|||
DayOutOfBoundsError, |
|||
None, |
|||
partial(fractional_range_check, MICROSECONDS_PER_DAY), |
|||
) |
|||
DURATION_TNH_LIMIT = Limit( |
|||
"Invalid hour duration string.", |
|||
None, |
|||
None, |
|||
HoursOutOfBoundsError, |
|||
None, |
|||
partial(fractional_range_check, MICROSECONDS_PER_HOUR), |
|||
) |
|||
DURATION_TNM_LIMIT = Limit( |
|||
"Invalid minute duration string.", |
|||
None, |
|||
None, |
|||
MinutesOutOfBoundsError, |
|||
None, |
|||
partial(fractional_range_check, MICROSECONDS_PER_MINUTE), |
|||
) |
|||
DURATION_TNS_LIMIT = Limit( |
|||
"Invalid second duration string.", |
|||
None, |
|||
None, |
|||
SecondsOutOfBoundsError, |
|||
None, |
|||
partial(fractional_range_check, MICROSECONDS_PER_SECOND), |
|||
) |
|||
|
|||
DATE_RANGE_DICT = BaseTimeBuilder.DATE_RANGE_DICT |
|||
DATE_RANGE_DICT["YYYY"] = DATE_YYYY_LIMIT |
|||
|
|||
TIME_RANGE_DICT = {"hh": TIME_HH_LIMIT, "mm": TIME_MM_LIMIT, "ss": TIME_SS_LIMIT} |
|||
|
|||
DURATION_RANGE_DICT = { |
|||
"PnY": DURATION_PNY_LIMIT, |
|||
"PnM": DURATION_PNM_LIMIT, |
|||
"PnW": DURATION_PNW_LIMIT, |
|||
"PnD": DURATION_PND_LIMIT, |
|||
"TnH": DURATION_TNH_LIMIT, |
|||
"TnM": DURATION_TNM_LIMIT, |
|||
"TnS": DURATION_TNS_LIMIT, |
|||
} |
|||
|
|||
@classmethod |
|||
def build_date(cls, YYYY=None, MM=None, DD=None, Www=None, D=None, DDD=None): |
|||
YYYY, MM, DD, Www, D, DDD = cls.range_check_date(YYYY, MM, DD, Www, D, DDD) |
|||
|
|||
if MM is None: |
|||
MM = 1 |
|||
|
|||
if DD is None: |
|||
DD = 1 |
|||
|
|||
if DDD is not None: |
|||
return PythonTimeBuilder._build_ordinal_date(YYYY, DDD) |
|||
|
|||
if Www is not None: |
|||
return PythonTimeBuilder._build_week_date(YYYY, Www, isoday=D) |
|||
|
|||
return datetime.date(YYYY, MM, DD) |
|||
|
|||
@classmethod |
|||
def build_time(cls, hh=None, mm=None, ss=None, tz=None): |
|||
# Builds a time from the given parts, handling fractional arguments |
|||
# where necessary |
|||
hours = 0 |
|||
minutes = 0 |
|||
seconds = 0 |
|||
microseconds = 0 |
|||
|
|||
hh, mm, ss, tz = cls.range_check_time(hh, mm, ss, tz) |
|||
|
|||
if type(hh) is FractionalComponent: |
|||
hours = hh.principal |
|||
microseconds = hh.microsecondremainder |
|||
elif hh is not None: |
|||
hours = hh |
|||
|
|||
if type(mm) is FractionalComponent: |
|||
minutes = mm.principal |
|||
microseconds = mm.microsecondremainder |
|||
elif mm is not None: |
|||
minutes = mm |
|||
|
|||
if type(ss) is FractionalComponent: |
|||
seconds = ss.principal |
|||
microseconds = ss.microsecondremainder |
|||
elif ss is not None: |
|||
seconds = ss |
|||
|
|||
( |
|||
hours, |
|||
minutes, |
|||
seconds, |
|||
microseconds, |
|||
) = PythonTimeBuilder._distribute_microseconds( |
|||
microseconds, |
|||
(hours, minutes, seconds), |
|||
(MICROSECONDS_PER_HOUR, MICROSECONDS_PER_MINUTE, MICROSECONDS_PER_SECOND), |
|||
) |
|||
|
|||
# Move midnight into range |
|||
if hours == 24: |
|||
hours = 0 |
|||
|
|||
# Datetimes don't handle fractional components, so we use a timedelta |
|||
if tz is not None: |
|||
return ( |
|||
datetime.datetime( |
|||
1, 1, 1, hour=hours, minute=minutes, tzinfo=cls._build_object(tz) |
|||
) |
|||
+ datetime.timedelta(seconds=seconds, microseconds=microseconds) |
|||
).timetz() |
|||
|
|||
return ( |
|||
datetime.datetime(1, 1, 1, hour=hours, minute=minutes) |
|||
+ datetime.timedelta(seconds=seconds, microseconds=microseconds) |
|||
).time() |
|||
|
|||
@classmethod |
|||
def build_datetime(cls, date, time): |
|||
return datetime.datetime.combine( |
|||
cls._build_object(date), cls._build_object(time) |
|||
) |
|||
|
|||
@classmethod |
|||
def build_duration( |
|||
cls, PnY=None, PnM=None, PnW=None, PnD=None, TnH=None, TnM=None, TnS=None |
|||
): |
|||
# PnY and PnM will be distributed to PnD, microsecond remainder to TnS |
|||
PnY, PnM, PnW, PnD, TnH, TnM, TnS = cls.range_check_duration( |
|||
PnY, PnM, PnW, PnD, TnH, TnM, TnS |
|||
) |
|||
|
|||
seconds = TnS.principal |
|||
microseconds = TnS.microsecondremainder |
|||
|
|||
return datetime.timedelta( |
|||
days=PnD, |
|||
seconds=seconds, |
|||
microseconds=microseconds, |
|||
minutes=TnM, |
|||
hours=TnH, |
|||
weeks=PnW, |
|||
) |
|||
|
|||
@classmethod |
|||
def build_interval(cls, start=None, end=None, duration=None): |
|||
start, end, duration = cls.range_check_interval(start, end, duration) |
|||
|
|||
if start is not None and end is not None: |
|||
# <start>/<end> |
|||
startobject = cls._build_object(start) |
|||
endobject = cls._build_object(end) |
|||
|
|||
return (startobject, endobject) |
|||
|
|||
durationobject = cls._build_object(duration) |
|||
|
|||
# Determine if datetime promotion is required |
|||
datetimerequired = ( |
|||
duration.TnH is not None |
|||
or duration.TnM is not None |
|||
or duration.TnS is not None |
|||
or durationobject.seconds != 0 |
|||
or durationobject.microseconds != 0 |
|||
) |
|||
|
|||
if end is not None: |
|||
# <duration>/<end> |
|||
endobject = cls._build_object(end) |
|||
|
|||
# Range check |
|||
if type(end) is DateTuple and datetimerequired is True: |
|||
# <end> is a date, and <duration> requires datetime resolution |
|||
return ( |
|||
endobject, |
|||
cls.build_datetime(end, TupleBuilder.build_time()) - durationobject, |
|||
) |
|||
|
|||
return (endobject, endobject - durationobject) |
|||
|
|||
# <start>/<duration> |
|||
startobject = cls._build_object(start) |
|||
|
|||
# Range check |
|||
if type(start) is DateTuple and datetimerequired is True: |
|||
# <start> is a date, and <duration> requires datetime resolution |
|||
return ( |
|||
startobject, |
|||
cls.build_datetime(start, TupleBuilder.build_time()) + durationobject, |
|||
) |
|||
|
|||
return (startobject, startobject + durationobject) |
|||
|
|||
@classmethod |
|||
def build_repeating_interval(cls, R=None, Rnn=None, interval=None): |
|||
startobject = None |
|||
endobject = None |
|||
|
|||
R, Rnn, interval = cls.range_check_repeating_interval(R, Rnn, interval) |
|||
|
|||
if interval.start is not None: |
|||
startobject = cls._build_object(interval.start) |
|||
|
|||
if interval.end is not None: |
|||
endobject = cls._build_object(interval.end) |
|||
|
|||
if interval.duration is not None: |
|||
durationobject = cls._build_object(interval.duration) |
|||
else: |
|||
durationobject = endobject - startobject |
|||
|
|||
if R is True: |
|||
if startobject is not None: |
|||
return cls._date_generator_unbounded(startobject, durationobject) |
|||
|
|||
return cls._date_generator_unbounded(endobject, -durationobject) |
|||
|
|||
iterations = int(Rnn) |
|||
|
|||
if startobject is not None: |
|||
return cls._date_generator(startobject, durationobject, iterations) |
|||
|
|||
return cls._date_generator(endobject, -durationobject, iterations) |
|||
|
|||
@classmethod |
|||
def build_timezone(cls, negative=None, Z=None, hh=None, mm=None, name=""): |
|||
negative, Z, hh, mm, name = cls.range_check_timezone(negative, Z, hh, mm, name) |
|||
|
|||
if Z is True: |
|||
# Z -> UTC |
|||
return UTCOffset(name="UTC", minutes=0) |
|||
|
|||
tzhour = int(hh) |
|||
|
|||
if mm is not None: |
|||
tzminute = int(mm) |
|||
else: |
|||
tzminute = 0 |
|||
|
|||
if negative is True: |
|||
return UTCOffset(name=name, minutes=-(tzhour * 60 + tzminute)) |
|||
|
|||
return UTCOffset(name=name, minutes=tzhour * 60 + tzminute) |
|||
|
|||
@classmethod |
|||
def range_check_duration( |
|||
cls, |
|||
PnY=None, |
|||
PnM=None, |
|||
PnW=None, |
|||
PnD=None, |
|||
TnH=None, |
|||
TnM=None, |
|||
TnS=None, |
|||
rangedict=None, |
|||
): |
|||
years = 0 |
|||
months = 0 |
|||
days = 0 |
|||
weeks = 0 |
|||
hours = 0 |
|||
minutes = 0 |
|||
seconds = 0 |
|||
microseconds = 0 |
|||
|
|||
PnY, PnM, PnW, PnD, TnH, TnM, TnS = BaseTimeBuilder.range_check_duration( |
|||
PnY, PnM, PnW, PnD, TnH, TnM, TnS, rangedict=cls.DURATION_RANGE_DICT |
|||
) |
|||
|
|||
if PnY is not None: |
|||
if type(PnY) is FractionalComponent: |
|||
years = PnY.principal |
|||
microseconds = PnY.microsecondremainder |
|||
else: |
|||
years = PnY |
|||
|
|||
if years * DAYS_PER_YEAR > TIMEDELTA_MAX_DAYS: |
|||
raise YearOutOfBoundsError("Duration exceeds maximum timedelta size.") |
|||
|
|||
if PnM is not None: |
|||
if type(PnM) is FractionalComponent: |
|||
months = PnM.principal |
|||
microseconds = PnM.microsecondremainder |
|||
else: |
|||
months = PnM |
|||
|
|||
if months * DAYS_PER_MONTH > TIMEDELTA_MAX_DAYS: |
|||
raise MonthOutOfBoundsError("Duration exceeds maximum timedelta size.") |
|||
|
|||
if PnW is not None: |
|||
if type(PnW) is FractionalComponent: |
|||
weeks = PnW.principal |
|||
microseconds = PnW.microsecondremainder |
|||
else: |
|||
weeks = PnW |
|||
|
|||
if weeks * DAYS_PER_WEEK > TIMEDELTA_MAX_DAYS: |
|||
raise WeekOutOfBoundsError("Duration exceeds maximum timedelta size.") |
|||
|
|||
if PnD is not None: |
|||
if type(PnD) is FractionalComponent: |
|||
days = PnD.principal |
|||
microseconds = PnD.microsecondremainder |
|||
else: |
|||
days = PnD |
|||
|
|||
if days > TIMEDELTA_MAX_DAYS: |
|||
raise DayOutOfBoundsError("Duration exceeds maximum timedelta size.") |
|||
|
|||
if TnH is not None: |
|||
if type(TnH) is FractionalComponent: |
|||
hours = TnH.principal |
|||
microseconds = TnH.microsecondremainder |
|||
else: |
|||
hours = TnH |
|||
|
|||
if hours // HOURS_PER_DAY > TIMEDELTA_MAX_DAYS: |
|||
raise HoursOutOfBoundsError("Duration exceeds maximum timedelta size.") |
|||
|
|||
if TnM is not None: |
|||
if type(TnM) is FractionalComponent: |
|||
minutes = TnM.principal |
|||
microseconds = TnM.microsecondremainder |
|||
else: |
|||
minutes = TnM |
|||
|
|||
if minutes // MINUTES_PER_DAY > TIMEDELTA_MAX_DAYS: |
|||
raise MinutesOutOfBoundsError( |
|||
"Duration exceeds maximum timedelta size." |
|||
) |
|||
|
|||
if TnS is not None: |
|||
if type(TnS) is FractionalComponent: |
|||
seconds = TnS.principal |
|||
microseconds = TnS.microsecondremainder |
|||
else: |
|||
seconds = TnS |
|||
|
|||
if seconds // SECONDS_PER_DAY > TIMEDELTA_MAX_DAYS: |
|||
raise SecondsOutOfBoundsError( |
|||
"Duration exceeds maximum timedelta size." |
|||
) |
|||
|
|||
( |
|||
years, |
|||
months, |
|||
weeks, |
|||
days, |
|||
hours, |
|||
minutes, |
|||
seconds, |
|||
microseconds, |
|||
) = PythonTimeBuilder._distribute_microseconds( |
|||
microseconds, |
|||
(years, months, weeks, days, hours, minutes, seconds), |
|||
( |
|||
MICROSECONDS_PER_YEAR, |
|||
MICROSECONDS_PER_MONTH, |
|||
MICROSECONDS_PER_WEEK, |
|||
MICROSECONDS_PER_DAY, |
|||
MICROSECONDS_PER_HOUR, |
|||
MICROSECONDS_PER_MINUTE, |
|||
MICROSECONDS_PER_SECOND, |
|||
), |
|||
) |
|||
|
|||
# Note that weeks can be handled without conversion to days |
|||
totaldays = years * DAYS_PER_YEAR + months * DAYS_PER_MONTH + days |
|||
|
|||
# Check against timedelta limits |
|||
if ( |
|||
totaldays |
|||
+ weeks * DAYS_PER_WEEK |
|||
+ hours // HOURS_PER_DAY |
|||
+ minutes // MINUTES_PER_DAY |
|||
+ seconds // SECONDS_PER_DAY |
|||
> TIMEDELTA_MAX_DAYS |
|||
): |
|||
raise DayOutOfBoundsError("Duration exceeds maximum timedelta size.") |
|||
|
|||
return ( |
|||
None, |
|||
None, |
|||
weeks, |
|||
totaldays, |
|||
hours, |
|||
minutes, |
|||
FractionalComponent(seconds, microseconds), |
|||
) |
|||
|
|||
@classmethod |
|||
def range_check_interval(cls, start=None, end=None, duration=None): |
|||
# Handles concise format, range checks any potential durations |
|||
if start is not None and end is not None: |
|||
# <start>/<end> |
|||
# Handle concise format |
|||
if cls._is_interval_end_concise(end) is True: |
|||
end = cls._combine_concise_interval_tuples(start, end) |
|||
|
|||
return (start, end, duration) |
|||
|
|||
durationobject = cls._build_object(duration) |
|||
|
|||
if end is not None: |
|||
# <duration>/<end> |
|||
endobject = cls._build_object(end) |
|||
|
|||
# Range check |
|||
if type(end) is DateTuple: |
|||
enddatetime = cls.build_datetime(end, TupleBuilder.build_time()) |
|||
|
|||
if enddatetime - datetime.datetime.min < durationobject: |
|||
raise YearOutOfBoundsError("Interval end less than minimium date.") |
|||
else: |
|||
mindatetime = datetime.datetime.min |
|||
|
|||
if end.time.tz is not None: |
|||
mindatetime = mindatetime.replace(tzinfo=endobject.tzinfo) |
|||
|
|||
if endobject - mindatetime < durationobject: |
|||
raise YearOutOfBoundsError("Interval end less than minimium date.") |
|||
else: |
|||
# <start>/<duration> |
|||
startobject = cls._build_object(start) |
|||
|
|||
# Range check |
|||
if type(start) is DateTuple: |
|||
startdatetime = cls.build_datetime(start, TupleBuilder.build_time()) |
|||
|
|||
if datetime.datetime.max - startdatetime < durationobject: |
|||
raise YearOutOfBoundsError( |
|||
"Interval end greater than maximum date." |
|||
) |
|||
else: |
|||
maxdatetime = datetime.datetime.max |
|||
|
|||
if start.time.tz is not None: |
|||
maxdatetime = maxdatetime.replace(tzinfo=startobject.tzinfo) |
|||
|
|||
if maxdatetime - startobject < durationobject: |
|||
raise YearOutOfBoundsError( |
|||
"Interval end greater than maximum date." |
|||
) |
|||
|
|||
return (start, end, duration) |
|||
|
|||
@staticmethod |
|||
def _build_week_date(isoyear, isoweek, isoday=None): |
|||
if isoday is None: |
|||
return PythonTimeBuilder._iso_year_start(isoyear) + datetime.timedelta( |
|||
weeks=isoweek - 1 |
|||
) |
|||
|
|||
return PythonTimeBuilder._iso_year_start(isoyear) + datetime.timedelta( |
|||
weeks=isoweek - 1, days=isoday - 1 |
|||
) |
|||
|
|||
@staticmethod |
|||
def _build_ordinal_date(isoyear, isoday): |
|||
# Day of year to a date |
|||
# https://stackoverflow.com/questions/2427555/python-question-year-and-day-of-year-to-date |
|||
builtdate = datetime.date(isoyear, 1, 1) + datetime.timedelta(days=isoday - 1) |
|||
|
|||
return builtdate |
|||
|
|||
@staticmethod |
|||
def _iso_year_start(isoyear): |
|||
# Given an ISO year, returns the equivalent of the start of the year |
|||
# on the Gregorian calendar (which is used by Python) |
|||
# Stolen from: |
|||
# http://stackoverflow.com/questions/304256/whats-the-best-way-to-find-the-inverse-of-datetime-isocalendar |
|||
|
|||
# Determine the location of the 4th of January, the first week of |
|||
# the ISO year is the week containing the 4th of January |
|||
# http://en.wikipedia.org/wiki/ISO_week_date |
|||
fourth_jan = datetime.date(isoyear, 1, 4) |
|||
|
|||
# Note the conversion from ISO day (1 - 7) and Python day (0 - 6) |
|||
delta = datetime.timedelta(days=fourth_jan.isoweekday() - 1) |
|||
|
|||
# Return the start of the year |
|||
return fourth_jan - delta |
|||
|
|||
@staticmethod |
|||
def _date_generator(startdate, timedelta, iterations): |
|||
currentdate = startdate |
|||
currentiteration = 0 |
|||
|
|||
while currentiteration < iterations: |
|||
yield currentdate |
|||
|
|||
# Update the values |
|||
currentdate += timedelta |
|||
currentiteration += 1 |
|||
|
|||
@staticmethod |
|||
def _date_generator_unbounded(startdate, timedelta): |
|||
currentdate = startdate |
|||
|
|||
while True: |
|||
yield currentdate |
|||
|
|||
# Update the value |
|||
currentdate += timedelta |
|||
|
|||
@staticmethod |
|||
def _distribute_microseconds(todistribute, recipients, reductions): |
|||
# Given a number of microseconds as int, a tuple of ints length n |
|||
# to distribute to, and a tuple of ints length n to divide todistribute |
|||
# by (from largest to smallest), returns a tuple of length n + 1, with |
|||
# todistribute divided across recipients using the reductions, with |
|||
# the final remainder returned as the final tuple member |
|||
results = [] |
|||
|
|||
remainder = todistribute |
|||
|
|||
for index, reduction in enumerate(reductions): |
|||
additional, remainder = divmod(remainder, reduction) |
|||
|
|||
results.append(recipients[index] + additional) |
|||
|
|||
# Always return the remaining microseconds |
|||
results.append(remainder) |
|||
|
|||
return tuple(results) |
@ -0,0 +1,7 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
@ -0,0 +1,838 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
|||
|
|||
import unittest |
|||
|
|||
import aniso8601 |
|||
from aniso8601.builders import ( |
|||
BaseTimeBuilder, |
|||
DatetimeTuple, |
|||
DateTuple, |
|||
DurationTuple, |
|||
IntervalTuple, |
|||
RepeatingIntervalTuple, |
|||
TimeTuple, |
|||
TimezoneTuple, |
|||
TupleBuilder, |
|||
cast, |
|||
) |
|||
from aniso8601.exceptions import ( |
|||
DayOutOfBoundsError, |
|||
HoursOutOfBoundsError, |
|||
ISOFormatError, |
|||
LeapSecondError, |
|||
MidnightBoundsError, |
|||
MinutesOutOfBoundsError, |
|||
MonthOutOfBoundsError, |
|||
SecondsOutOfBoundsError, |
|||
WeekOutOfBoundsError, |
|||
) |
|||
from aniso8601.tests.compat import mock |
|||
|
|||
|
|||
class LeapSecondSupportingTestBuilder(BaseTimeBuilder): |
|||
LEAP_SECONDS_SUPPORTED = True |
|||
|
|||
|
|||
class TestBuilderFunctions(unittest.TestCase): |
|||
def test_cast(self): |
|||
self.assertEqual(cast("1", int), 1) |
|||
self.assertEqual(cast("-2", int), -2) |
|||
self.assertEqual(cast("3", float), float(3)) |
|||
self.assertEqual(cast("-4", float), float(-4)) |
|||
self.assertEqual(cast("5.6", float), 5.6) |
|||
self.assertEqual(cast("-7.8", float), -7.8) |
|||
|
|||
def test_cast_exception(self): |
|||
with self.assertRaises(ISOFormatError): |
|||
cast("asdf", int) |
|||
|
|||
with self.assertRaises(ISOFormatError): |
|||
cast("asdf", float) |
|||
|
|||
def test_cast_caughtexception(self): |
|||
def tester(value): |
|||
raise RuntimeError |
|||
|
|||
with self.assertRaises(ISOFormatError): |
|||
cast("asdf", tester, caughtexceptions=(RuntimeError,)) |
|||
|
|||
def test_cast_thrownexception(self): |
|||
with self.assertRaises(RuntimeError): |
|||
cast("asdf", int, thrownexception=RuntimeError) |
|||
|
|||
|
|||
class TestBaseTimeBuilder(unittest.TestCase): |
|||
def test_build_date(self): |
|||
with self.assertRaises(NotImplementedError): |
|||
BaseTimeBuilder.build_date() |
|||
|
|||
def test_build_time(self): |
|||
with self.assertRaises(NotImplementedError): |
|||
BaseTimeBuilder.build_time() |
|||
|
|||
def test_build_datetime(self): |
|||
with self.assertRaises(NotImplementedError): |
|||
BaseTimeBuilder.build_datetime(None, None) |
|||
|
|||
def test_build_duration(self): |
|||
with self.assertRaises(NotImplementedError): |
|||
BaseTimeBuilder.build_duration() |
|||
|
|||
def test_build_interval(self): |
|||
with self.assertRaises(NotImplementedError): |
|||
BaseTimeBuilder.build_interval() |
|||
|
|||
def test_build_repeating_interval(self): |
|||
with self.assertRaises(NotImplementedError): |
|||
BaseTimeBuilder.build_repeating_interval() |
|||
|
|||
def test_build_timezone(self): |
|||
with self.assertRaises(NotImplementedError): |
|||
BaseTimeBuilder.build_timezone() |
|||
|
|||
def test_range_check_date(self): |
|||
# Check the calendar for day ranges |
|||
with self.assertRaises(DayOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_date(YYYY="0007", MM="02", DD="30") |
|||
|
|||
with self.assertRaises(DayOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_date(YYYY="0007", DDD="366") |
|||
|
|||
with self.assertRaises(MonthOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_date(YYYY="4333", MM="30", DD="30") |
|||
|
|||
# 0 isn't a valid week number |
|||
with self.assertRaises(WeekOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_date(YYYY="2003", Www="00") |
|||
|
|||
# Week must not be larger than 53 |
|||
with self.assertRaises(WeekOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_date(YYYY="2004", Www="54") |
|||
|
|||
# 0 isn't a valid day number |
|||
with self.assertRaises(DayOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_date(YYYY="2001", Www="02", D="0") |
|||
|
|||
# Day must not be larger than 7 |
|||
with self.assertRaises(DayOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_date(YYYY="2001", Www="02", D="8") |
|||
|
|||
with self.assertRaises(DayOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_date(YYYY="1981", DDD="000") |
|||
|
|||
# Day must be 365, or 366, not larger |
|||
with self.assertRaises(DayOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_date(YYYY="1234", DDD="000") |
|||
|
|||
with self.assertRaises(DayOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_date(YYYY="1234", DDD="367") |
|||
|
|||
# https://bitbucket.org/nielsenb/aniso8601/issues/14/parsing-ordinal-dates-should-only-allow |
|||
with self.assertRaises(DayOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_date(YYYY="1981", DDD="366") |
|||
|
|||
# Make sure Nones pass through unmodified |
|||
self.assertEqual( |
|||
BaseTimeBuilder.range_check_date(rangedict={}), |
|||
(None, None, None, None, None, None), |
|||
) |
|||
|
|||
def test_range_check_time(self): |
|||
# Leap seconds not supported |
|||
# https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is |
|||
# https://bitbucket.org/nielsenb/aniso8601/issues/13/parsing-of-leap-second-gives-wildly |
|||
with self.assertRaises(LeapSecondError): |
|||
BaseTimeBuilder.range_check_time(hh="23", mm="59", ss="60") |
|||
|
|||
with self.assertRaises(SecondsOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_time(hh="00", mm="00", ss="60") |
|||
|
|||
with self.assertRaises(SecondsOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_time(hh="00", mm="00", ss="61") |
|||
|
|||
with self.assertRaises(MinutesOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_time(hh="00", mm="61") |
|||
|
|||
with self.assertRaises(MinutesOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_time(hh="00", mm="60") |
|||
|
|||
with self.assertRaises(MinutesOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_time(hh="00", mm="60.1") |
|||
|
|||
with self.assertRaises(HoursOutOfBoundsError): |
|||
BaseTimeBuilder.range_check_time(hh="25") |
|||
|
|||
# Hour 24 can only represent midnight |
|||
with self.assertRaises(MidnightBoundsError): |
|||
BaseTimeBuilder.range_check_time(hh="24", mm="00", ss="01") |
|||
|
|||
with self.assertRaises(MidnightBoundsError): |
|||
BaseTimeBuilder.range_check_time(hh="24", mm="00.1") |
|||
|
|||
with self.assertRaises(MidnightBoundsError): |
|||
BaseTimeBuilder.range_check_time(hh="24", mm="01") |
|||
|
|||
with self.assertRaises(MidnightBoundsError): |
|||
BaseTimeBuilder.range_check_time(hh="24.1") |
|||
|
|||
# Leap seconds not supported |
|||
# https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is |
|||
# https://bitbucket.org/nielsenb/aniso8601/issues/13/parsing-of-leap-second-gives-wildly |
|||
with self.assertRaises(LeapSecondError): |
|||
BaseTimeBuilder.range_check_time(hh="23", mm="59", ss="60") |
|||
|
|||
# Make sure Nones pass through unmodified |
|||
self.assertEqual( |
|||
BaseTimeBuilder.range_check_time(rangedict={}), (None, None, None, None) |
|||
) |
|||
|
|||
def test_range_check_time_leap_seconds_supported(self): |
|||
self.assertEqual( |
|||
LeapSecondSupportingTestBuilder.range_check_time(hh="23", mm="59", ss="60"), |
|||
(23, 59, 60, None), |
|||
) |
|||
|
|||
with self.assertRaises(SecondsOutOfBoundsError): |
|||
LeapSecondSupportingTestBuilder.range_check_time(hh="01", mm="02", ss="60") |
|||
|
|||
def test_range_check_duration(self): |
|||
self.assertEqual( |
|||
BaseTimeBuilder.range_check_duration(), |
|||
(None, None, None, None, None, None, None), |
|||
) |
|||
|
|||
self.assertEqual( |
|||
BaseTimeBuilder.range_check_duration(rangedict={}), |
|||
(None, None, None, None, None, None, None), |
|||
) |
|||
|
|||
def test_range_check_repeating_interval(self): |
|||
self.assertEqual( |
|||
BaseTimeBuilder.range_check_repeating_interval(), (None, None, None) |
|||
) |
|||
|
|||
self.assertEqual( |
|||
BaseTimeBuilder.range_check_repeating_interval(rangedict={}), |
|||
(None, None, None), |
|||
) |
|||
|
|||
def test_range_check_timezone(self): |
|||
self.assertEqual( |
|||
BaseTimeBuilder.range_check_timezone(), (None, None, None, None, "") |
|||
) |
|||
|
|||
self.assertEqual( |
|||
BaseTimeBuilder.range_check_timezone(rangedict={}), |
|||
(None, None, None, None, ""), |
|||
) |
|||
|
|||
def test_build_object(self): |
|||
datetest = ( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
{"YYYY": "1", "MM": "2", "DD": "3", "Www": "4", "D": "5", "DDD": "6"}, |
|||
) |
|||
|
|||
timetest = ( |
|||
TimeTuple("1", "2", "3", TimezoneTuple(False, False, "4", "5", "tz name")), |
|||
{ |
|||
"hh": "1", |
|||
"mm": "2", |
|||
"ss": "3", |
|||
"tz": TimezoneTuple(False, False, "4", "5", "tz name"), |
|||
}, |
|||
) |
|||
|
|||
datetimetest = ( |
|||
DatetimeTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
TimeTuple( |
|||
"7", "8", "9", TimezoneTuple(True, False, "10", "11", "tz name") |
|||
), |
|||
), |
|||
( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
TimeTuple( |
|||
"7", "8", "9", TimezoneTuple(True, False, "10", "11", "tz name") |
|||
), |
|||
), |
|||
) |
|||
|
|||
durationtest = ( |
|||
DurationTuple("1", "2", "3", "4", "5", "6", "7"), |
|||
{ |
|||
"PnY": "1", |
|||
"PnM": "2", |
|||
"PnW": "3", |
|||
"PnD": "4", |
|||
"TnH": "5", |
|||
"TnM": "6", |
|||
"TnS": "7", |
|||
}, |
|||
) |
|||
|
|||
intervaltests = ( |
|||
( |
|||
IntervalTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
DateTuple("7", "8", "9", "10", "11", "12"), |
|||
None, |
|||
), |
|||
{ |
|||
"start": DateTuple("1", "2", "3", "4", "5", "6"), |
|||
"end": DateTuple("7", "8", "9", "10", "11", "12"), |
|||
"duration": None, |
|||
}, |
|||
), |
|||
( |
|||
IntervalTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
None, |
|||
DurationTuple("7", "8", "9", "10", "11", "12", "13"), |
|||
), |
|||
{ |
|||
"start": DateTuple("1", "2", "3", "4", "5", "6"), |
|||
"end": None, |
|||
"duration": DurationTuple("7", "8", "9", "10", "11", "12", "13"), |
|||
}, |
|||
), |
|||
( |
|||
IntervalTuple( |
|||
None, |
|||
TimeTuple( |
|||
"1", "2", "3", TimezoneTuple(True, False, "4", "5", "tz name") |
|||
), |
|||
DurationTuple("6", "7", "8", "9", "10", "11", "12"), |
|||
), |
|||
{ |
|||
"start": None, |
|||
"end": TimeTuple( |
|||
"1", "2", "3", TimezoneTuple(True, False, "4", "5", "tz name") |
|||
), |
|||
"duration": DurationTuple("6", "7", "8", "9", "10", "11", "12"), |
|||
}, |
|||
), |
|||
) |
|||
|
|||
repeatingintervaltests = ( |
|||
( |
|||
RepeatingIntervalTuple( |
|||
True, |
|||
None, |
|||
IntervalTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
DateTuple("7", "8", "9", "10", "11", "12"), |
|||
None, |
|||
), |
|||
), |
|||
{ |
|||
"R": True, |
|||
"Rnn": None, |
|||
"interval": IntervalTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
DateTuple("7", "8", "9", "10", "11", "12"), |
|||
None, |
|||
), |
|||
}, |
|||
), |
|||
( |
|||
RepeatingIntervalTuple( |
|||
False, |
|||
"1", |
|||
IntervalTuple( |
|||
DatetimeTuple( |
|||
DateTuple("2", "3", "4", "5", "6", "7"), |
|||
TimeTuple("8", "9", "10", None), |
|||
), |
|||
DatetimeTuple( |
|||
DateTuple("11", "12", "13", "14", "15", "16"), |
|||
TimeTuple("17", "18", "19", None), |
|||
), |
|||
None, |
|||
), |
|||
), |
|||
{ |
|||
"R": False, |
|||
"Rnn": "1", |
|||
"interval": IntervalTuple( |
|||
DatetimeTuple( |
|||
DateTuple("2", "3", "4", "5", "6", "7"), |
|||
TimeTuple("8", "9", "10", None), |
|||
), |
|||
DatetimeTuple( |
|||
DateTuple("11", "12", "13", "14", "15", "16"), |
|||
TimeTuple("17", "18", "19", None), |
|||
), |
|||
None, |
|||
), |
|||
}, |
|||
), |
|||
) |
|||
|
|||
timezonetest = ( |
|||
TimezoneTuple(False, False, "1", "2", "+01:02"), |
|||
{"negative": False, "Z": False, "hh": "1", "mm": "2", "name": "+01:02"}, |
|||
) |
|||
|
|||
with mock.patch.object( |
|||
aniso8601.builders.BaseTimeBuilder, "build_date" |
|||
) as mock_build: |
|||
mock_build.return_value = datetest[0] |
|||
|
|||
result = BaseTimeBuilder._build_object(datetest[0]) |
|||
|
|||
self.assertEqual(result, datetest[0]) |
|||
mock_build.assert_called_once_with(**datetest[1]) |
|||
|
|||
with mock.patch.object( |
|||
aniso8601.builders.BaseTimeBuilder, "build_time" |
|||
) as mock_build: |
|||
mock_build.return_value = timetest[0] |
|||
|
|||
result = BaseTimeBuilder._build_object(timetest[0]) |
|||
|
|||
self.assertEqual(result, timetest[0]) |
|||
mock_build.assert_called_once_with(**timetest[1]) |
|||
|
|||
with mock.patch.object( |
|||
aniso8601.builders.BaseTimeBuilder, "build_datetime" |
|||
) as mock_build: |
|||
mock_build.return_value = datetimetest[0] |
|||
|
|||
result = BaseTimeBuilder._build_object(datetimetest[0]) |
|||
|
|||
self.assertEqual(result, datetimetest[0]) |
|||
mock_build.assert_called_once_with(*datetimetest[1]) |
|||
|
|||
with mock.patch.object( |
|||
aniso8601.builders.BaseTimeBuilder, "build_duration" |
|||
) as mock_build: |
|||
mock_build.return_value = durationtest[0] |
|||
|
|||
result = BaseTimeBuilder._build_object(durationtest[0]) |
|||
|
|||
self.assertEqual(result, durationtest[0]) |
|||
mock_build.assert_called_once_with(**durationtest[1]) |
|||
|
|||
for intervaltest in intervaltests: |
|||
with mock.patch.object( |
|||
aniso8601.builders.BaseTimeBuilder, "build_interval" |
|||
) as mock_build: |
|||
mock_build.return_value = intervaltest[0] |
|||
|
|||
result = BaseTimeBuilder._build_object(intervaltest[0]) |
|||
|
|||
self.assertEqual(result, intervaltest[0]) |
|||
mock_build.assert_called_once_with(**intervaltest[1]) |
|||
|
|||
for repeatingintervaltest in repeatingintervaltests: |
|||
with mock.patch.object( |
|||
aniso8601.builders.BaseTimeBuilder, "build_repeating_interval" |
|||
) as mock_build: |
|||
mock_build.return_value = repeatingintervaltest[0] |
|||
|
|||
result = BaseTimeBuilder._build_object(repeatingintervaltest[0]) |
|||
|
|||
self.assertEqual(result, repeatingintervaltest[0]) |
|||
mock_build.assert_called_once_with(**repeatingintervaltest[1]) |
|||
|
|||
with mock.patch.object( |
|||
aniso8601.builders.BaseTimeBuilder, "build_timezone" |
|||
) as mock_build: |
|||
mock_build.return_value = timezonetest[0] |
|||
|
|||
result = BaseTimeBuilder._build_object(timezonetest[0]) |
|||
|
|||
self.assertEqual(result, timezonetest[0]) |
|||
mock_build.assert_called_once_with(**timezonetest[1]) |
|||
|
|||
def test_is_interval_end_concise(self): |
|||
self.assertTrue( |
|||
BaseTimeBuilder._is_interval_end_concise(TimeTuple("1", "2", "3", None)) |
|||
) |
|||
self.assertTrue( |
|||
BaseTimeBuilder._is_interval_end_concise( |
|||
DateTuple(None, "2", "3", "4", "5", "6") |
|||
) |
|||
) |
|||
self.assertTrue( |
|||
BaseTimeBuilder._is_interval_end_concise( |
|||
DatetimeTuple( |
|||
DateTuple(None, "2", "3", "4", "5", "6"), |
|||
TimeTuple("7", "8", "9", None), |
|||
) |
|||
) |
|||
) |
|||
|
|||
self.assertFalse( |
|||
BaseTimeBuilder._is_interval_end_concise( |
|||
DateTuple("1", "2", "3", "4", "5", "6") |
|||
) |
|||
) |
|||
self.assertFalse( |
|||
BaseTimeBuilder._is_interval_end_concise( |
|||
DatetimeTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
TimeTuple("7", "8", "9", None), |
|||
) |
|||
) |
|||
) |
|||
|
|||
def test_combine_concise_interval_tuples(self): |
|||
testtuples = ( |
|||
( |
|||
DateTuple("2020", "01", "01", None, None, None), |
|||
DateTuple(None, None, "02", None, None, None), |
|||
DateTuple("2020", "01", "02", None, None, None), |
|||
), |
|||
( |
|||
DateTuple("2008", "02", "15", None, None, None), |
|||
DateTuple(None, "03", "14", None, None, None), |
|||
DateTuple("2008", "03", "14", None, None, None), |
|||
), |
|||
( |
|||
DatetimeTuple( |
|||
DateTuple("2007", "12", "14", None, None, None), |
|||
TimeTuple("13", "30", None, None), |
|||
), |
|||
TimeTuple("15", "30", None, None), |
|||
DatetimeTuple( |
|||
DateTuple("2007", "12", "14", None, None, None), |
|||
TimeTuple("15", "30", None, None), |
|||
), |
|||
), |
|||
( |
|||
DatetimeTuple( |
|||
DateTuple("2007", "11", "13", None, None, None), |
|||
TimeTuple("09", "00", None, None), |
|||
), |
|||
DatetimeTuple( |
|||
DateTuple(None, None, "15", None, None, None), |
|||
TimeTuple("17", "00", None, None), |
|||
), |
|||
DatetimeTuple( |
|||
DateTuple("2007", "11", "15", None, None, None), |
|||
TimeTuple("17", "00", None, None), |
|||
), |
|||
), |
|||
( |
|||
DatetimeTuple( |
|||
DateTuple("2007", "11", "13", None, None, None), |
|||
TimeTuple("00", "00", None, None), |
|||
), |
|||
DatetimeTuple( |
|||
DateTuple(None, None, "16", None, None, None), |
|||
TimeTuple("00", "00", None, None), |
|||
), |
|||
DatetimeTuple( |
|||
DateTuple("2007", "11", "16", None, None, None), |
|||
TimeTuple("00", "00", None, None), |
|||
), |
|||
), |
|||
( |
|||
DatetimeTuple( |
|||
DateTuple("2007", "11", "13", None, None, None), |
|||
TimeTuple( |
|||
"09", "00", None, TimezoneTuple(False, True, None, None, "Z") |
|||
), |
|||
), |
|||
DatetimeTuple( |
|||
DateTuple(None, None, "15", None, None, None), |
|||
TimeTuple("17", "00", None, None), |
|||
), |
|||
DatetimeTuple( |
|||
DateTuple("2007", "11", "15", None, None, None), |
|||
TimeTuple( |
|||
"17", "00", None, TimezoneTuple(False, True, None, None, "Z") |
|||
), |
|||
), |
|||
), |
|||
) |
|||
|
|||
for testtuple in testtuples: |
|||
result = BaseTimeBuilder._combine_concise_interval_tuples( |
|||
testtuple[0], testtuple[1] |
|||
) |
|||
self.assertEqual(result, testtuple[2]) |
|||
|
|||
|
|||
class TestTupleBuilder(unittest.TestCase): |
|||
def test_build_date(self): |
|||
datetuple = TupleBuilder.build_date() |
|||
|
|||
self.assertEqual(datetuple, DateTuple(None, None, None, None, None, None)) |
|||
|
|||
datetuple = TupleBuilder.build_date( |
|||
YYYY="1", MM="2", DD="3", Www="4", D="5", DDD="6" |
|||
) |
|||
|
|||
self.assertEqual(datetuple, DateTuple("1", "2", "3", "4", "5", "6")) |
|||
|
|||
def test_build_time(self): |
|||
testtuples = ( |
|||
({}, TimeTuple(None, None, None, None)), |
|||
( |
|||
{"hh": "1", "mm": "2", "ss": "3", "tz": None}, |
|||
TimeTuple("1", "2", "3", None), |
|||
), |
|||
( |
|||
{ |
|||
"hh": "1", |
|||
"mm": "2", |
|||
"ss": "3", |
|||
"tz": TimezoneTuple(False, False, "4", "5", "tz name"), |
|||
}, |
|||
TimeTuple( |
|||
"1", "2", "3", TimezoneTuple(False, False, "4", "5", "tz name") |
|||
), |
|||
), |
|||
) |
|||
|
|||
for testtuple in testtuples: |
|||
self.assertEqual(TupleBuilder.build_time(**testtuple[0]), testtuple[1]) |
|||
|
|||
def test_build_datetime(self): |
|||
testtuples = ( |
|||
( |
|||
{ |
|||
"date": DateTuple("1", "2", "3", "4", "5", "6"), |
|||
"time": TimeTuple("7", "8", "9", None), |
|||
}, |
|||
DatetimeTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
TimeTuple("7", "8", "9", None), |
|||
), |
|||
), |
|||
( |
|||
{ |
|||
"date": DateTuple("1", "2", "3", "4", "5", "6"), |
|||
"time": TimeTuple( |
|||
"7", "8", "9", TimezoneTuple(True, False, "10", "11", "tz name") |
|||
), |
|||
}, |
|||
DatetimeTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
TimeTuple( |
|||
"7", "8", "9", TimezoneTuple(True, False, "10", "11", "tz name") |
|||
), |
|||
), |
|||
), |
|||
) |
|||
|
|||
for testtuple in testtuples: |
|||
self.assertEqual(TupleBuilder.build_datetime(**testtuple[0]), testtuple[1]) |
|||
|
|||
def test_build_duration(self): |
|||
testtuples = ( |
|||
({}, DurationTuple(None, None, None, None, None, None, None)), |
|||
( |
|||
{ |
|||
"PnY": "1", |
|||
"PnM": "2", |
|||
"PnW": "3", |
|||
"PnD": "4", |
|||
"TnH": "5", |
|||
"TnM": "6", |
|||
"TnS": "7", |
|||
}, |
|||
DurationTuple("1", "2", "3", "4", "5", "6", "7"), |
|||
), |
|||
) |
|||
|
|||
for testtuple in testtuples: |
|||
self.assertEqual(TupleBuilder.build_duration(**testtuple[0]), testtuple[1]) |
|||
|
|||
def test_build_interval(self): |
|||
testtuples = ( |
|||
({}, IntervalTuple(None, None, None)), |
|||
( |
|||
{ |
|||
"start": DateTuple("1", "2", "3", "4", "5", "6"), |
|||
"end": DateTuple("7", "8", "9", "10", "11", "12"), |
|||
}, |
|||
IntervalTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
DateTuple("7", "8", "9", "10", "11", "12"), |
|||
None, |
|||
), |
|||
), |
|||
( |
|||
{ |
|||
"start": TimeTuple( |
|||
"1", "2", "3", TimezoneTuple(True, False, "7", "8", "tz name") |
|||
), |
|||
"end": TimeTuple( |
|||
"4", "5", "6", TimezoneTuple(False, False, "9", "10", "tz name") |
|||
), |
|||
}, |
|||
IntervalTuple( |
|||
TimeTuple( |
|||
"1", "2", "3", TimezoneTuple(True, False, "7", "8", "tz name") |
|||
), |
|||
TimeTuple( |
|||
"4", "5", "6", TimezoneTuple(False, False, "9", "10", "tz name") |
|||
), |
|||
None, |
|||
), |
|||
), |
|||
( |
|||
{ |
|||
"start": DatetimeTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
TimeTuple( |
|||
"7", |
|||
"8", |
|||
"9", |
|||
TimezoneTuple(True, False, "10", "11", "tz name"), |
|||
), |
|||
), |
|||
"end": DatetimeTuple( |
|||
DateTuple("12", "13", "14", "15", "16", "17"), |
|||
TimeTuple( |
|||
"18", |
|||
"19", |
|||
"20", |
|||
TimezoneTuple(False, False, "21", "22", "tz name"), |
|||
), |
|||
), |
|||
}, |
|||
IntervalTuple( |
|||
DatetimeTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
TimeTuple( |
|||
"7", |
|||
"8", |
|||
"9", |
|||
TimezoneTuple(True, False, "10", "11", "tz name"), |
|||
), |
|||
), |
|||
DatetimeTuple( |
|||
DateTuple("12", "13", "14", "15", "16", "17"), |
|||
TimeTuple( |
|||
"18", |
|||
"19", |
|||
"20", |
|||
TimezoneTuple(False, False, "21", "22", "tz name"), |
|||
), |
|||
), |
|||
None, |
|||
), |
|||
), |
|||
( |
|||
{ |
|||
"start": DateTuple("1", "2", "3", "4", "5", "6"), |
|||
"end": None, |
|||
"duration": DurationTuple("7", "8", "9", "10", "11", "12", "13"), |
|||
}, |
|||
IntervalTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
None, |
|||
DurationTuple("7", "8", "9", "10", "11", "12", "13"), |
|||
), |
|||
), |
|||
( |
|||
{ |
|||
"start": None, |
|||
"end": TimeTuple( |
|||
"1", "2", "3", TimezoneTuple(True, False, "4", "5", "tz name") |
|||
), |
|||
"duration": DurationTuple("6", "7", "8", "9", "10", "11", "12"), |
|||
}, |
|||
IntervalTuple( |
|||
None, |
|||
TimeTuple( |
|||
"1", "2", "3", TimezoneTuple(True, False, "4", "5", "tz name") |
|||
), |
|||
DurationTuple("6", "7", "8", "9", "10", "11", "12"), |
|||
), |
|||
), |
|||
) |
|||
|
|||
for testtuple in testtuples: |
|||
self.assertEqual(TupleBuilder.build_interval(**testtuple[0]), testtuple[1]) |
|||
|
|||
def test_build_repeating_interval(self): |
|||
testtuples = ( |
|||
({}, RepeatingIntervalTuple(None, None, None)), |
|||
( |
|||
{ |
|||
"R": True, |
|||
"interval": IntervalTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
DateTuple("7", "8", "9", "10", "11", "12"), |
|||
None, |
|||
), |
|||
}, |
|||
RepeatingIntervalTuple( |
|||
True, |
|||
None, |
|||
IntervalTuple( |
|||
DateTuple("1", "2", "3", "4", "5", "6"), |
|||
DateTuple("7", "8", "9", "10", "11", "12"), |
|||
None, |
|||
), |
|||
), |
|||
), |
|||
( |
|||
{ |
|||
"R": False, |
|||
"Rnn": "1", |
|||
"interval": IntervalTuple( |
|||
DatetimeTuple( |
|||
DateTuple("2", "3", "4", "5", "6", "7"), |
|||
TimeTuple("8", "9", "10", None), |
|||
), |
|||
DatetimeTuple( |
|||
DateTuple("11", "12", "13", "14", "15", "16"), |
|||
TimeTuple("17", "18", "19", None), |
|||
), |
|||
None, |
|||
), |
|||
}, |
|||
RepeatingIntervalTuple( |
|||
False, |
|||
"1", |
|||
IntervalTuple( |
|||
DatetimeTuple( |
|||
DateTuple("2", "3", "4", "5", "6", "7"), |
|||
TimeTuple("8", "9", "10", None), |
|||
), |
|||
DatetimeTuple( |
|||
DateTuple("11", "12", "13", "14", "15", "16"), |
|||
TimeTuple("17", "18", "19", None), |
|||
), |
|||
None, |
|||
), |
|||
), |
|||
), |
|||
) |
|||
|
|||
for testtuple in testtuples: |
|||
result = TupleBuilder.build_repeating_interval(**testtuple[0]) |
|||
self.assertEqual(result, testtuple[1]) |
|||
|
|||
def test_build_timezone(self): |
|||
testtuples = ( |
|||
({}, TimezoneTuple(None, None, None, None, "")), |
|||
( |
|||
{"negative": False, "Z": True, "name": "UTC"}, |
|||
TimezoneTuple(False, True, None, None, "UTC"), |
|||
), |
|||
( |
|||
{"negative": False, "Z": False, "hh": "1", "mm": "2", "name": "+01:02"}, |
|||
TimezoneTuple(False, False, "1", "2", "+01:02"), |
|||
), |
|||
( |
|||
{"negative": True, "Z": False, "hh": "1", "mm": "2", "name": "-01:02"}, |
|||
TimezoneTuple(True, False, "1", "2", "-01:02"), |
|||
), |
|||
) |
|||
|
|||
for testtuple in testtuples: |
|||
result = TupleBuilder.build_timezone(**testtuple[0]) |
|||
self.assertEqual(result, testtuple[1]) |
1710
Lib/site-packages/aniso8601/builders/tests/test_python.py
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,24 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
|||
|
|||
import sys |
|||
|
|||
PY2 = sys.version_info[0] == 2 |
|||
|
|||
if PY2: # pragma: no cover |
|||
range = xrange # pylint: disable=undefined-variable |
|||
else: |
|||
range = range |
|||
|
|||
|
|||
def is_string(tocheck): |
|||
# pylint: disable=undefined-variable |
|||
if PY2: # pragma: no cover |
|||
return isinstance(tocheck, str) or isinstance(tocheck, unicode) |
|||
|
|||
return isinstance(tocheck, str) |
@ -0,0 +1,161 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
|||
|
|||
from aniso8601.builders import TupleBuilder |
|||
from aniso8601.builders.python import PythonTimeBuilder |
|||
from aniso8601.compat import is_string |
|||
from aniso8601.exceptions import ISOFormatError |
|||
from aniso8601.resolution import DateResolution |
|||
|
|||
|
|||
def get_date_resolution(isodatestr): |
|||
# Valid string formats are: |
|||
# |
|||
# Y[YYY] |
|||
# YYYY-MM-DD |
|||
# YYYYMMDD |
|||
# YYYY-MM |
|||
# YYYY-Www |
|||
# YYYYWww |
|||
# YYYY-Www-D |
|||
# YYYYWwwD |
|||
# YYYY-DDD |
|||
# YYYYDDD |
|||
isodatetuple = parse_date(isodatestr, builder=TupleBuilder) |
|||
|
|||
if isodatetuple.DDD is not None: |
|||
# YYYY-DDD |
|||
# YYYYDDD |
|||
return DateResolution.Ordinal |
|||
|
|||
if isodatetuple.D is not None: |
|||
# YYYY-Www-D |
|||
# YYYYWwwD |
|||
return DateResolution.Weekday |
|||
|
|||
if isodatetuple.Www is not None: |
|||
# YYYY-Www |
|||
# YYYYWww |
|||
return DateResolution.Week |
|||
|
|||
if isodatetuple.DD is not None: |
|||
# YYYY-MM-DD |
|||
# YYYYMMDD |
|||
return DateResolution.Day |
|||
|
|||
if isodatetuple.MM is not None: |
|||
# YYYY-MM |
|||
return DateResolution.Month |
|||
|
|||
# Y[YYY] |
|||
return DateResolution.Year |
|||
|
|||
|
|||
def parse_date(isodatestr, builder=PythonTimeBuilder): |
|||
# Given a string in any ISO 8601 date format, return a datetime.date |
|||
# object that corresponds to the given date. Valid string formats are: |
|||
# |
|||
# Y[YYY] |
|||
# YYYY-MM-DD |
|||
# YYYYMMDD |
|||
# YYYY-MM |
|||
# YYYY-Www |
|||
# YYYYWww |
|||
# YYYY-Www-D |
|||
# YYYYWwwD |
|||
# YYYY-DDD |
|||
# YYYYDDD |
|||
if is_string(isodatestr) is False: |
|||
raise ValueError("Date must be string.") |
|||
|
|||
if isodatestr.startswith("+") or isodatestr.startswith("-"): |
|||
raise NotImplementedError( |
|||
"ISO 8601 extended year representation " "not supported." |
|||
) |
|||
|
|||
if len(isodatestr) == 0 or isodatestr.count("-") > 2: |
|||
raise ISOFormatError('"{0}" is not a valid ISO 8601 date.'.format(isodatestr)) |
|||
yearstr = None |
|||
monthstr = None |
|||
daystr = None |
|||
weekstr = None |
|||
weekdaystr = None |
|||
ordinaldaystr = None |
|||
|
|||
if len(isodatestr) <= 4: |
|||
# Y[YYY] |
|||
yearstr = isodatestr |
|||
elif "W" in isodatestr: |
|||
if len(isodatestr) == 10: |
|||
# YYYY-Www-D |
|||
yearstr = isodatestr[0:4] |
|||
weekstr = isodatestr[6:8] |
|||
weekdaystr = isodatestr[9] |
|||
elif len(isodatestr) == 8: |
|||
if "-" in isodatestr: |
|||
# YYYY-Www |
|||
yearstr = isodatestr[0:4] |
|||
weekstr = isodatestr[6:] |
|||
else: |
|||
# YYYYWwwD |
|||
yearstr = isodatestr[0:4] |
|||
weekstr = isodatestr[5:7] |
|||
weekdaystr = isodatestr[7] |
|||
elif len(isodatestr) == 7: |
|||
# YYYYWww |
|||
yearstr = isodatestr[0:4] |
|||
weekstr = isodatestr[5:] |
|||
elif len(isodatestr) == 7: |
|||
if "-" in isodatestr: |
|||
# YYYY-MM |
|||
yearstr = isodatestr[0:4] |
|||
monthstr = isodatestr[5:] |
|||
else: |
|||
# YYYYDDD |
|||
yearstr = isodatestr[0:4] |
|||
ordinaldaystr = isodatestr[4:] |
|||
elif len(isodatestr) == 8: |
|||
if "-" in isodatestr: |
|||
# YYYY-DDD |
|||
yearstr = isodatestr[0:4] |
|||
ordinaldaystr = isodatestr[5:] |
|||
else: |
|||
# YYYYMMDD |
|||
yearstr = isodatestr[0:4] |
|||
monthstr = isodatestr[4:6] |
|||
daystr = isodatestr[6:] |
|||
elif len(isodatestr) == 10: |
|||
# YYYY-MM-DD |
|||
yearstr = isodatestr[0:4] |
|||
monthstr = isodatestr[5:7] |
|||
daystr = isodatestr[8:] |
|||
else: |
|||
raise ISOFormatError('"{0}" is not a valid ISO 8601 date.'.format(isodatestr)) |
|||
|
|||
hascomponent = False |
|||
|
|||
for componentstr in [yearstr, monthstr, daystr, weekstr, weekdaystr, ordinaldaystr]: |
|||
if componentstr is not None: |
|||
hascomponent = True |
|||
|
|||
if componentstr.isdigit() is False: |
|||
raise ISOFormatError( |
|||
'"{0}" is not a valid ISO 8601 date.'.format(isodatestr) |
|||
) |
|||
|
|||
if hascomponent is False: |
|||
raise ISOFormatError('"{0}" is not a valid ISO 8601 date.'.format(isodatestr)) |
|||
|
|||
return builder.build_date( |
|||
YYYY=yearstr, |
|||
MM=monthstr, |
|||
DD=daystr, |
|||
Www=weekstr, |
|||
D=weekdaystr, |
|||
DDD=ordinaldaystr, |
|||
) |
@ -0,0 +1,12 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
|||
|
|||
|
|||
def normalize(value): |
|||
"""Returns the string with decimal separators normalized.""" |
|||
return value.replace(",", ".") |
@ -0,0 +1,291 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
|||
|
|||
from aniso8601 import compat |
|||
from aniso8601.builders import TupleBuilder |
|||
from aniso8601.builders.python import PythonTimeBuilder |
|||
from aniso8601.date import parse_date |
|||
from aniso8601.decimalfraction import normalize |
|||
from aniso8601.exceptions import ISOFormatError |
|||
from aniso8601.resolution import DurationResolution |
|||
from aniso8601.time import parse_time |
|||
|
|||
|
|||
def get_duration_resolution(isodurationstr): |
|||
# Valid string formats are: |
|||
# |
|||
# PnYnMnDTnHnMnS (or any reduced precision equivalent) |
|||
# PnW |
|||
# P<date>T<time> |
|||
isodurationtuple = parse_duration(isodurationstr, builder=TupleBuilder) |
|||
|
|||
if isodurationtuple.TnS is not None: |
|||
return DurationResolution.Seconds |
|||
|
|||
if isodurationtuple.TnM is not None: |
|||
return DurationResolution.Minutes |
|||
|
|||
if isodurationtuple.TnH is not None: |
|||
return DurationResolution.Hours |
|||
|
|||
if isodurationtuple.PnD is not None: |
|||
return DurationResolution.Days |
|||
|
|||
if isodurationtuple.PnW is not None: |
|||
return DurationResolution.Weeks |
|||
|
|||
if isodurationtuple.PnM is not None: |
|||
return DurationResolution.Months |
|||
|
|||
return DurationResolution.Years |
|||
|
|||
|
|||
def parse_duration(isodurationstr, builder=PythonTimeBuilder): |
|||
# Given a string representing an ISO 8601 duration, return a |
|||
# a duration built by the given builder. Valid formats are: |
|||
# |
|||
# PnYnMnDTnHnMnS (or any reduced precision equivalent) |
|||
# PnW |
|||
# P<date>T<time> |
|||
|
|||
if compat.is_string(isodurationstr) is False: |
|||
raise ValueError("Duration must be string.") |
|||
|
|||
if len(isodurationstr) == 0: |
|||
raise ISOFormatError( |
|||
'"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr) |
|||
) |
|||
|
|||
if isodurationstr[0] != "P": |
|||
raise ISOFormatError("ISO 8601 duration must start with a P.") |
|||
|
|||
# If Y, M, D, H, S, or W are in the string, |
|||
# assume it is a specified duration |
|||
if _has_any_component(isodurationstr, ["Y", "M", "D", "H", "S", "W"]) is True: |
|||
parseresult = _parse_duration_prescribed(isodurationstr) |
|||
return builder.build_duration(**parseresult) |
|||
|
|||
if isodurationstr.find("T") != -1: |
|||
parseresult = _parse_duration_combined(isodurationstr) |
|||
return builder.build_duration(**parseresult) |
|||
|
|||
raise ISOFormatError( |
|||
'"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr) |
|||
) |
|||
|
|||
|
|||
def _parse_duration_prescribed(isodurationstr): |
|||
# durationstr can be of the form PnYnMnDTnHnMnS or PnW |
|||
|
|||
# Make sure the end character is valid |
|||
# https://bitbucket.org/nielsenb/aniso8601/issues/9/durations-with-trailing-garbage-are-parsed |
|||
if isodurationstr[-1] not in ["Y", "M", "D", "H", "S", "W"]: |
|||
raise ISOFormatError("ISO 8601 duration must end with a valid " "character.") |
|||
|
|||
# Make sure only the lowest order element has decimal precision |
|||
durationstr = normalize(isodurationstr) |
|||
|
|||
if durationstr.count(".") > 1: |
|||
raise ISOFormatError( |
|||
"ISO 8601 allows only lowest order element to " "have a decimal fraction." |
|||
) |
|||
|
|||
seperatoridx = durationstr.find(".") |
|||
|
|||
if seperatoridx != -1: |
|||
remaining = durationstr[seperatoridx + 1 : -1] |
|||
|
|||
# There should only ever be 1 letter after a decimal if there is more |
|||
# then one, the string is invalid |
|||
if remaining.isdigit() is False: |
|||
raise ISOFormatError( |
|||
"ISO 8601 duration must end with " "a single valid character." |
|||
) |
|||
|
|||
# Do not allow W in combination with other designators |
|||
# https://bitbucket.org/nielsenb/aniso8601/issues/2/week-designators-should-not-be-combinable |
|||
if ( |
|||
durationstr.find("W") != -1 |
|||
and _has_any_component(durationstr, ["Y", "M", "D", "H", "S"]) is True |
|||
): |
|||
raise ISOFormatError( |
|||
"ISO 8601 week designators may not be combined " |
|||
"with other time designators." |
|||
) |
|||
|
|||
# Parse the elements of the duration |
|||
if durationstr.find("T") == -1: |
|||
return _parse_duration_prescribed_notime(durationstr) |
|||
|
|||
return _parse_duration_prescribed_time(durationstr) |
|||
|
|||
|
|||
def _parse_duration_prescribed_notime(isodurationstr): |
|||
# durationstr can be of the form PnYnMnD or PnW |
|||
|
|||
durationstr = normalize(isodurationstr) |
|||
|
|||
yearstr = None |
|||
monthstr = None |
|||
daystr = None |
|||
weekstr = None |
|||
|
|||
weekidx = durationstr.find("W") |
|||
yearidx = durationstr.find("Y") |
|||
monthidx = durationstr.find("M") |
|||
dayidx = durationstr.find("D") |
|||
|
|||
if weekidx != -1: |
|||
weekstr = durationstr[1:-1] |
|||
elif yearidx != -1 and monthidx != -1 and dayidx != -1: |
|||
yearstr = durationstr[1:yearidx] |
|||
monthstr = durationstr[yearidx + 1 : monthidx] |
|||
daystr = durationstr[monthidx + 1 : -1] |
|||
elif yearidx != -1 and monthidx != -1: |
|||
yearstr = durationstr[1:yearidx] |
|||
monthstr = durationstr[yearidx + 1 : monthidx] |
|||
elif yearidx != -1 and dayidx != -1: |
|||
yearstr = durationstr[1:yearidx] |
|||
daystr = durationstr[yearidx + 1 : dayidx] |
|||
elif monthidx != -1 and dayidx != -1: |
|||
monthstr = durationstr[1:monthidx] |
|||
daystr = durationstr[monthidx + 1 : -1] |
|||
elif yearidx != -1: |
|||
yearstr = durationstr[1:-1] |
|||
elif monthidx != -1: |
|||
monthstr = durationstr[1:-1] |
|||
elif dayidx != -1: |
|||
daystr = durationstr[1:-1] |
|||
else: |
|||
raise ISOFormatError( |
|||
'"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr) |
|||
) |
|||
|
|||
for componentstr in [yearstr, monthstr, daystr, weekstr]: |
|||
if componentstr is not None: |
|||
if "." in componentstr: |
|||
intstr, fractionalstr = componentstr.split(".", 1) |
|||
|
|||
if intstr.isdigit() is False: |
|||
raise ISOFormatError( |
|||
'"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr) |
|||
) |
|||
else: |
|||
if componentstr.isdigit() is False: |
|||
raise ISOFormatError( |
|||
'"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr) |
|||
) |
|||
|
|||
return {"PnY": yearstr, "PnM": monthstr, "PnW": weekstr, "PnD": daystr} |
|||
|
|||
|
|||
def _parse_duration_prescribed_time(isodurationstr): |
|||
# durationstr can be of the form PnYnMnDTnHnMnS |
|||
|
|||
timeidx = isodurationstr.find("T") |
|||
|
|||
datestr = isodurationstr[:timeidx] |
|||
timestr = normalize(isodurationstr[timeidx + 1 :]) |
|||
|
|||
hourstr = None |
|||
minutestr = None |
|||
secondstr = None |
|||
|
|||
houridx = timestr.find("H") |
|||
minuteidx = timestr.find("M") |
|||
secondidx = timestr.find("S") |
|||
|
|||
if houridx != -1 and minuteidx != -1 and secondidx != -1: |
|||
hourstr = timestr[0:houridx] |
|||
minutestr = timestr[houridx + 1 : minuteidx] |
|||
secondstr = timestr[minuteidx + 1 : -1] |
|||
elif houridx != -1 and minuteidx != -1: |
|||
hourstr = timestr[0:houridx] |
|||
minutestr = timestr[houridx + 1 : minuteidx] |
|||
elif houridx != -1 and secondidx != -1: |
|||
hourstr = timestr[0:houridx] |
|||
secondstr = timestr[houridx + 1 : -1] |
|||
elif minuteidx != -1 and secondidx != -1: |
|||
minutestr = timestr[0:minuteidx] |
|||
secondstr = timestr[minuteidx + 1 : -1] |
|||
elif houridx != -1: |
|||
hourstr = timestr[0:-1] |
|||
elif minuteidx != -1: |
|||
minutestr = timestr[0:-1] |
|||
elif secondidx != -1: |
|||
secondstr = timestr[0:-1] |
|||
else: |
|||
raise ISOFormatError( |
|||
'"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr) |
|||
) |
|||
|
|||
for componentstr in [hourstr, minutestr, secondstr]: |
|||
if componentstr is not None: |
|||
if "." in componentstr: |
|||
intstr, fractionalstr = componentstr.split(".", 1) |
|||
|
|||
if intstr.isdigit() is False: |
|||
raise ISOFormatError( |
|||
'"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr) |
|||
) |
|||
else: |
|||
if componentstr.isdigit() is False: |
|||
raise ISOFormatError( |
|||
'"{0}" is not a valid ISO 8601 duration.'.format(isodurationstr) |
|||
) |
|||
|
|||
# Parse any date components |
|||
durationdict = {"PnY": None, "PnM": None, "PnW": None, "PnD": None} |
|||
|
|||
if len(datestr) > 1: |
|||
durationdict = _parse_duration_prescribed_notime(datestr) |
|||
|
|||
durationdict.update({"TnH": hourstr, "TnM": minutestr, "TnS": secondstr}) |
|||
|
|||
return durationdict |
|||
|
|||
|
|||
def _parse_duration_combined(durationstr): |
|||
# Period of the form P<date>T<time> |
|||
|
|||
# Split the string in to its component parts |
|||
datepart, timepart = durationstr[1:].split("T", 1) # We skip the 'P' |
|||
|
|||
datevalue = parse_date(datepart, builder=TupleBuilder) |
|||
timevalue = parse_time(timepart, builder=TupleBuilder) |
|||
|
|||
return { |
|||
"PnY": datevalue.YYYY, |
|||
"PnM": datevalue.MM, |
|||
"PnD": datevalue.DD, |
|||
"TnH": timevalue.hh, |
|||
"TnM": timevalue.mm, |
|||
"TnS": timevalue.ss, |
|||
} |
|||
|
|||
|
|||
def _has_any_component(durationstr, components): |
|||
# Given a duration string, and a list of components, returns True |
|||
# if any of the listed components are present, False otherwise. |
|||
# |
|||
# For instance: |
|||
# durationstr = 'P1Y' |
|||
# components = ['Y', 'M'] |
|||
# |
|||
# returns True |
|||
# |
|||
# durationstr = 'P1Y' |
|||
# components = ['M', 'D'] |
|||
# |
|||
# returns False |
|||
|
|||
for component in components: |
|||
if durationstr.find(component) != -1: |
|||
return True |
|||
|
|||
return False |
@ -0,0 +1,51 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
|||
|
|||
|
|||
class ISOFormatError(ValueError): |
|||
"""Raised when ISO 8601 string fails a format check.""" |
|||
|
|||
|
|||
class RangeCheckError(ValueError): |
|||
"""Parent type of range check errors.""" |
|||
|
|||
|
|||
class YearOutOfBoundsError(RangeCheckError): |
|||
"""Raised when year exceeds limits.""" |
|||
|
|||
|
|||
class MonthOutOfBoundsError(RangeCheckError): |
|||
"""Raised when month is outside of 1..12.""" |
|||
|
|||
|
|||
class WeekOutOfBoundsError(RangeCheckError): |
|||
"""Raised when week exceeds a year.""" |
|||
|
|||
|
|||
class DayOutOfBoundsError(RangeCheckError): |
|||
"""Raised when day is outside of 1..365, 1..366 for leap year.""" |
|||
|
|||
|
|||
class HoursOutOfBoundsError(RangeCheckError): |
|||
"""Raise when parsed hours are greater than 24.""" |
|||
|
|||
|
|||
class MinutesOutOfBoundsError(RangeCheckError): |
|||
"""Raise when parsed seconds are greater than 60.""" |
|||
|
|||
|
|||
class SecondsOutOfBoundsError(RangeCheckError): |
|||
"""Raise when parsed seconds are greater than 60.""" |
|||
|
|||
|
|||
class MidnightBoundsError(RangeCheckError): |
|||
"""Raise when parsed time has an hour of 24 but is not midnight.""" |
|||
|
|||
|
|||
class LeapSecondError(RangeCheckError): |
|||
"""Raised when attempting to parse a leap second""" |
@ -0,0 +1,350 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
|||
|
|||
from aniso8601.builders import DatetimeTuple, DateTuple, TupleBuilder |
|||
from aniso8601.builders.python import PythonTimeBuilder |
|||
from aniso8601.compat import is_string |
|||
from aniso8601.date import parse_date |
|||
from aniso8601.duration import parse_duration |
|||
from aniso8601.exceptions import ISOFormatError |
|||
from aniso8601.resolution import IntervalResolution |
|||
from aniso8601.time import parse_datetime, parse_time |
|||
|
|||
|
|||
def get_interval_resolution( |
|||
isointervalstr, intervaldelimiter="/", datetimedelimiter="T" |
|||
): |
|||
isointervaltuple = parse_interval( |
|||
isointervalstr, |
|||
intervaldelimiter=intervaldelimiter, |
|||
datetimedelimiter=datetimedelimiter, |
|||
builder=TupleBuilder, |
|||
) |
|||
|
|||
return _get_interval_resolution(isointervaltuple) |
|||
|
|||
|
|||
def get_repeating_interval_resolution( |
|||
isointervalstr, intervaldelimiter="/", datetimedelimiter="T" |
|||
): |
|||
repeatingintervaltuple = parse_repeating_interval( |
|||
isointervalstr, |
|||
intervaldelimiter=intervaldelimiter, |
|||
datetimedelimiter=datetimedelimiter, |
|||
builder=TupleBuilder, |
|||
) |
|||
|
|||
return _get_interval_resolution(repeatingintervaltuple.interval) |
|||
|
|||
|
|||
def _get_interval_resolution(intervaltuple): |
|||
if intervaltuple.start is not None and intervaltuple.end is not None: |
|||
return max( |
|||
_get_interval_component_resolution(intervaltuple.start), |
|||
_get_interval_component_resolution(intervaltuple.end), |
|||
) |
|||
|
|||
if intervaltuple.start is not None and intervaltuple.duration is not None: |
|||
return max( |
|||
_get_interval_component_resolution(intervaltuple.start), |
|||
_get_interval_component_resolution(intervaltuple.duration), |
|||
) |
|||
|
|||
return max( |
|||
_get_interval_component_resolution(intervaltuple.end), |
|||
_get_interval_component_resolution(intervaltuple.duration), |
|||
) |
|||
|
|||
|
|||
def _get_interval_component_resolution(componenttuple): |
|||
if type(componenttuple) is DateTuple: |
|||
if componenttuple.DDD is not None: |
|||
# YYYY-DDD |
|||
# YYYYDDD |
|||
return IntervalResolution.Ordinal |
|||
|
|||
if componenttuple.D is not None: |
|||
# YYYY-Www-D |
|||
# YYYYWwwD |
|||
return IntervalResolution.Weekday |
|||
|
|||
if componenttuple.Www is not None: |
|||
# YYYY-Www |
|||
# YYYYWww |
|||
return IntervalResolution.Week |
|||
|
|||
if componenttuple.DD is not None: |
|||
# YYYY-MM-DD |
|||
# YYYYMMDD |
|||
return IntervalResolution.Day |
|||
|
|||
if componenttuple.MM is not None: |
|||
# YYYY-MM |
|||
return IntervalResolution.Month |
|||
|
|||
# Y[YYY] |
|||
return IntervalResolution.Year |
|||
elif type(componenttuple) is DatetimeTuple: |
|||
# Datetime |
|||
if componenttuple.time.ss is not None: |
|||
return IntervalResolution.Seconds |
|||
|
|||
if componenttuple.time.mm is not None: |
|||
return IntervalResolution.Minutes |
|||
|
|||
return IntervalResolution.Hours |
|||
|
|||
# Duration |
|||
if componenttuple.TnS is not None: |
|||
return IntervalResolution.Seconds |
|||
|
|||
if componenttuple.TnM is not None: |
|||
return IntervalResolution.Minutes |
|||
|
|||
if componenttuple.TnH is not None: |
|||
return IntervalResolution.Hours |
|||
|
|||
if componenttuple.PnD is not None: |
|||
return IntervalResolution.Day |
|||
|
|||
if componenttuple.PnW is not None: |
|||
return IntervalResolution.Week |
|||
|
|||
if componenttuple.PnM is not None: |
|||
return IntervalResolution.Month |
|||
|
|||
return IntervalResolution.Year |
|||
|
|||
|
|||
def parse_interval( |
|||
isointervalstr, |
|||
intervaldelimiter="/", |
|||
datetimedelimiter="T", |
|||
builder=PythonTimeBuilder, |
|||
): |
|||
# Given a string representing an ISO 8601 interval, return an |
|||
# interval built by the given builder. Valid formats are: |
|||
# |
|||
# <start>/<end> |
|||
# <start>/<duration> |
|||
# <duration>/<end> |
|||
# |
|||
# The <start> and <end> values can represent dates, or datetimes, |
|||
# not times. |
|||
# |
|||
# The format: |
|||
# |
|||
# <duration> |
|||
# |
|||
# Is expressly not supported as there is no way to provide the additional |
|||
# required context. |
|||
|
|||
if is_string(isointervalstr) is False: |
|||
raise ValueError("Interval must be string.") |
|||
|
|||
if len(isointervalstr) == 0: |
|||
raise ISOFormatError("Interval string is empty.") |
|||
|
|||
if isointervalstr[0] == "R": |
|||
raise ISOFormatError( |
|||
"ISO 8601 repeating intervals must be parsed " |
|||
"with parse_repeating_interval." |
|||
) |
|||
|
|||
intervaldelimitercount = isointervalstr.count(intervaldelimiter) |
|||
|
|||
if intervaldelimitercount == 0: |
|||
raise ISOFormatError( |
|||
'Interval delimiter "{0}" is not in interval ' |
|||
'string "{1}".'.format(intervaldelimiter, isointervalstr) |
|||
) |
|||
|
|||
if intervaldelimitercount > 1: |
|||
raise ISOFormatError( |
|||
"{0} is not a valid ISO 8601 interval".format(isointervalstr) |
|||
) |
|||
|
|||
return _parse_interval( |
|||
isointervalstr, builder, intervaldelimiter, datetimedelimiter |
|||
) |
|||
|
|||
|
|||
def parse_repeating_interval( |
|||
isointervalstr, |
|||
intervaldelimiter="/", |
|||
datetimedelimiter="T", |
|||
builder=PythonTimeBuilder, |
|||
): |
|||
# Given a string representing an ISO 8601 interval repeating, return an |
|||
# interval built by the given builder. Valid formats are: |
|||
# |
|||
# Rnn/<interval> |
|||
# R/<interval> |
|||
|
|||
if not isinstance(isointervalstr, str): |
|||
raise ValueError("Interval must be string.") |
|||
|
|||
if len(isointervalstr) == 0: |
|||
raise ISOFormatError("Repeating interval string is empty.") |
|||
|
|||
if isointervalstr[0] != "R": |
|||
raise ISOFormatError("ISO 8601 repeating interval must start " "with an R.") |
|||
|
|||
if intervaldelimiter not in isointervalstr: |
|||
raise ISOFormatError( |
|||
'Interval delimiter "{0}" is not in interval ' |
|||
'string "{1}".'.format(intervaldelimiter, isointervalstr) |
|||
) |
|||
|
|||
# Parse the number of iterations |
|||
iterationpart, intervalpart = isointervalstr.split(intervaldelimiter, 1) |
|||
|
|||
if len(iterationpart) > 1: |
|||
R = False |
|||
Rnn = iterationpart[1:] |
|||
else: |
|||
R = True |
|||
Rnn = None |
|||
|
|||
interval = _parse_interval( |
|||
intervalpart, TupleBuilder, intervaldelimiter, datetimedelimiter |
|||
) |
|||
|
|||
return builder.build_repeating_interval(R=R, Rnn=Rnn, interval=interval) |
|||
|
|||
|
|||
def _parse_interval( |
|||
isointervalstr, builder, intervaldelimiter="/", datetimedelimiter="T" |
|||
): |
|||
# Returns a tuple containing the start of the interval, the end of the |
|||
# interval, and or the interval duration |
|||
|
|||
firstpart, secondpart = isointervalstr.split(intervaldelimiter) |
|||
|
|||
if len(firstpart) == 0 or len(secondpart) == 0: |
|||
raise ISOFormatError( |
|||
"{0} is not a valid ISO 8601 interval".format(isointervalstr) |
|||
) |
|||
|
|||
if firstpart[0] == "P": |
|||
# <duration>/<end> |
|||
# Notice that these are not returned 'in order' (earlier to later), this |
|||
# is to maintain consistency with parsing <start>/<end> durations, as |
|||
# well as making repeating interval code cleaner. Users who desire |
|||
# durations to be in order can use the 'sorted' operator. |
|||
duration = parse_duration(firstpart, builder=TupleBuilder) |
|||
|
|||
# We need to figure out if <end> is a date, or a datetime |
|||
if secondpart.find(datetimedelimiter) != -1: |
|||
# <end> is a datetime |
|||
endtuple = parse_datetime( |
|||
secondpart, delimiter=datetimedelimiter, builder=TupleBuilder |
|||
) |
|||
else: |
|||
endtuple = parse_date(secondpart, builder=TupleBuilder) |
|||
|
|||
return builder.build_interval(end=endtuple, duration=duration) |
|||
elif secondpart[0] == "P": |
|||
# <start>/<duration> |
|||
# We need to figure out if <start> is a date, or a datetime |
|||
duration = parse_duration(secondpart, builder=TupleBuilder) |
|||
|
|||
if firstpart.find(datetimedelimiter) != -1: |
|||
# <start> is a datetime |
|||
starttuple = parse_datetime( |
|||
firstpart, delimiter=datetimedelimiter, builder=TupleBuilder |
|||
) |
|||
else: |
|||
# <start> must just be a date |
|||
starttuple = parse_date(firstpart, builder=TupleBuilder) |
|||
|
|||
return builder.build_interval(start=starttuple, duration=duration) |
|||
|
|||
# <start>/<end> |
|||
if firstpart.find(datetimedelimiter) != -1: |
|||
# Both parts are datetimes |
|||
starttuple = parse_datetime( |
|||
firstpart, delimiter=datetimedelimiter, builder=TupleBuilder |
|||
) |
|||
else: |
|||
starttuple = parse_date(firstpart, builder=TupleBuilder) |
|||
|
|||
endtuple = _parse_interval_end(secondpart, starttuple, datetimedelimiter) |
|||
|
|||
return builder.build_interval(start=starttuple, end=endtuple) |
|||
|
|||
|
|||
def _parse_interval_end(endstr, starttuple, datetimedelimiter): |
|||
datestr = None |
|||
timestr = None |
|||
|
|||
monthstr = None |
|||
daystr = None |
|||
|
|||
concise = False |
|||
|
|||
if type(starttuple) is DateTuple: |
|||
startdatetuple = starttuple |
|||
else: |
|||
# Start is a datetime |
|||
startdatetuple = starttuple.date |
|||
|
|||
if datetimedelimiter in endstr: |
|||
datestr, timestr = endstr.split(datetimedelimiter, 1) |
|||
elif ":" in endstr: |
|||
timestr = endstr |
|||
else: |
|||
datestr = endstr |
|||
|
|||
if timestr is not None: |
|||
endtimetuple = parse_time(timestr, builder=TupleBuilder) |
|||
|
|||
# End is just a time |
|||
if datestr is None: |
|||
return endtimetuple |
|||
|
|||
# Handle backwards concise representation |
|||
if datestr.count("-") == 1: |
|||
monthstr, daystr = datestr.split("-") |
|||
concise = True |
|||
elif len(datestr) <= 2: |
|||
daystr = datestr |
|||
concise = True |
|||
elif len(datestr) <= 4: |
|||
monthstr = datestr[0:2] |
|||
daystr = datestr[2:] |
|||
concise = True |
|||
|
|||
if concise is True: |
|||
concisedatestr = startdatetuple.YYYY |
|||
|
|||
# Separators required because concise elements may be missing digits |
|||
if monthstr is not None: |
|||
concisedatestr += "-" + monthstr |
|||
elif startdatetuple.MM is not None: |
|||
concisedatestr += "-" + startdatetuple.MM |
|||
|
|||
concisedatestr += "-" + daystr |
|||
|
|||
enddatetuple = parse_date(concisedatestr, builder=TupleBuilder) |
|||
|
|||
# Clear unsupplied components |
|||
if monthstr is None: |
|||
enddatetuple = TupleBuilder.build_date(DD=enddatetuple.DD) |
|||
else: |
|||
# Year not provided |
|||
enddatetuple = TupleBuilder.build_date( |
|||
MM=enddatetuple.MM, DD=enddatetuple.DD |
|||
) |
|||
else: |
|||
enddatetuple = parse_date(datestr, builder=TupleBuilder) |
|||
|
|||
if timestr is None: |
|||
return enddatetuple |
|||
|
|||
return TupleBuilder.build_datetime(enddatetuple, endtimetuple) |
@ -0,0 +1,27 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
|||
|
|||
from aniso8601 import compat |
|||
|
|||
|
|||
class DateResolution(object): |
|||
Year, Month, Week, Weekday, Day, Ordinal = list(compat.range(6)) |
|||
|
|||
|
|||
class DurationResolution(object): |
|||
Years, Months, Weeks, Days, Hours, Minutes, Seconds = list(compat.range(7)) |
|||
|
|||
|
|||
class IntervalResolution(object): |
|||
Year, Month, Week, Weekday, Day, Ordinal, Hours, Minutes, Seconds = list( |
|||
compat.range(9) |
|||
) |
|||
|
|||
|
|||
class TimeResolution(object): |
|||
Seconds, Minutes, Hours = list(compat.range(3)) |
@ -0,0 +1,7 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
@ -0,0 +1,16 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
|||
|
|||
import sys |
|||
|
|||
PY2 = sys.version_info[0] == 2 |
|||
|
|||
if PY2: |
|||
import mock # pylint: disable=import-error |
|||
else: |
|||
from unittest import mock |
@ -0,0 +1,27 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2021, Brandon Nielsen |
|||
# All rights reserved. |
|||
# |
|||
# This software may be modified and distributed under the terms |
|||
# of the BSD license. See the LICENSE file for details. |
|||
|
|||
import unittest |
|||
|
|||
from aniso8601.compat import PY2, is_string |
|||
|
|||
|
|||
class TestCompatFunctions(unittest.TestCase): |
|||
def test_is_string(self): |
|||
self.assertTrue(is_string("asdf")) |
|||
self.assertTrue(is_string("")) |
|||
|
|||
# pylint: disable=undefined-variable |
|||
if PY2 is True: |
|||
self.assertTrue(is_string(unicode("asdf"))) |
|||
|
|||
self.assertFalse(is_string(None)) |
|||
self.assertFalse(is_string(123)) |
|||
self.assertFalse(is_string(4.56)) |
|||
self.assertFalse(is_string([])) |
|||
self.assertFalse(is_string({})) |
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue