From 2b5ad6c5820ff7a93c91ed352fb74ebd5e326fec Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Sun, 11 Feb 2024 18:59:24 +0300 Subject: [PATCH 01/10] =?UTF-8?q?chore:=20=F0=9F=A7=B9=20python.gitignore?= =?UTF-8?q?=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- .gitignore | 185 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 162 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index f7f7b71..f52a6b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,6 @@ # Project-specific ignores. -venv/ tmp/ -# Parts below taken from GitHub's gitignores repository: https://github.com/github/gitignore -*.py[cod] - -# C extensions -*.so - -# Packages -*.egg -*.egg-info -MANIFEST -dist -/build -eggs -parts -bin -var -sdist -develop-eggs -.installed.cfg -lib -lib64 - # Installer logs pip-log.txt @@ -59,3 +36,165 @@ $RECYCLE.BIN/ # OS X ignores .DS_Store + + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file From f443af5f755de90c74c27a1f32a720dbd47707c6 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Sun, 11 Feb 2024 19:00:36 +0300 Subject: [PATCH 02/10] =?UTF-8?q?docs:=20=F0=9F=A7=B9=20removal=20of=20old?= =?UTF-8?q?=20documents,packages,requirements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- .gitmodules | 4 ---- docs/build/.gitignore | 1 - docs/build/html | 1 - docs_requirements.txt | 19 ------------------- 4 files changed, 25 deletions(-) delete mode 100644 .gitmodules delete mode 100644 docs/build/.gitignore delete mode 160000 docs/build/html delete mode 100644 docs_requirements.txt diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 3b9cefa..0000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "docs/build/html"] - path = docs/build/html - url = https://github.com/andrew-d/python-multipart.git - branch = gh-pages diff --git a/docs/build/.gitignore b/docs/build/.gitignore deleted file mode 100644 index fad74e7..0000000 --- a/docs/build/.gitignore +++ /dev/null @@ -1 +0,0 @@ -doctrees diff --git a/docs/build/html b/docs/build/html deleted file mode 160000 index 79f2967..0000000 --- a/docs/build/html +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 79f29674b090ff0af0b62ec63c24477f2b66ca1d diff --git a/docs_requirements.txt b/docs_requirements.txt deleted file mode 100644 index 8c3eda5..0000000 --- a/docs_requirements.txt +++ /dev/null @@ -1,19 +0,0 @@ -Jinja2==3.1.3 -PyYAML==6.0.1 -Pygments==2.17.2 -Sphinx==7.2.6 -cov-core==1.15.0 -coverage==7.4.1 -distribute==0.7.3 -docutils==0.20.1 -invoke==2.2.0 -pexpect-u==2.5.1 -py==1.11.0 -pytest==8.0.0 -pytest-capturelog==0.7 -pytest-cov==4.1.0 -pytest-timeout==2.2.0 -sphinx-bootstrap-theme==0.8.1 -tox==4.12.1 -virtualenv==20.25.0 -wsgiref==0.1.2 From 343d75c6cd6b21f92aa38bd7c8610771a22cc90a Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Sun, 11 Feb 2024 19:01:36 +0300 Subject: [PATCH 03/10] =?UTF-8?q?docs:=20=E2=9C=A8=20mkdocs-material=20pac?= =?UTF-8?q?kages=20added=20as=20doc-dep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- pyproject.toml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index ed22c97..e53399c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,17 @@ dev = [ "ruff==0.2.1", "hatch", ] +doc = [ + "mkdocs==1.5.3", + "mkdocs-material==9.5.9", + "mkdocs-git-authors-plugin==0.7.2", + "mkdocs-git-revision-date-localized-plugin==1.2.4", + "mkdocs-material-extensions==1.3.1", + "pymdown-extensions==10.7", + "mkdocstrings==0.24.0", + "mkdocstrings-python==1.8.0", + "mkdocs-autorefs=0.5.0" +] [project.urls] Homepage = "https://github.com/Kludex/python-multipart" From 748046b0f80b7c49aeaed04e75007a4e6febc472 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Sun, 11 Feb 2024 19:47:49 +0300 Subject: [PATCH 04/10] =?UTF-8?q?docs:=20=E2=9C=A8=20initial=20mkdocs=20pa?= =?UTF-8?q?ges=20and=20documentation=20added,=20old=20docs=20removed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- docs/Makefile | 177 -------- docs/changelog.md | 1 + docs/decoders.md | 0 docs/exceptions.md | 0 docs/helper-functions.md | 4 + docs/index.md | 0 docs/license.md | 5 + docs/main-class.md | 0 docs/make.bat | 242 ----------- docs/parsers.md | 0 docs/source/_themes/.gitignore | 3 - docs/source/_themes/LICENSE | 37 -- docs/source/_themes/README | 31 -- docs/source/_themes/flask/layout.html | 25 -- docs/source/_themes/flask/relations.html | 19 - docs/source/_themes/flask/static/flasky.css_t | 395 ------------------ .../_themes/flask/static/small_flask.css | 70 ---- docs/source/_themes/flask/theme.conf | 9 - docs/source/_themes/flask_small/layout.html | 22 - .../_themes/flask_small/static/flasky.css_t | 287 ------------- docs/source/_themes/flask_small/theme.conf | 10 - docs/source/_themes/flask_theme_support.py | 86 ---- docs/source/api.rst | 74 ---- docs/source/conf.py | 256 ------------ docs/source/contents.rst.inc | 20 - docs/source/index.rst | 10 - docs/source/quickstart.rst | 195 --------- docs/support-classess.md | 0 mkdocs.yml | 107 +++++ multipart/multipart.py | 103 ++--- pyproject.toml | 3 +- 31 files changed, 174 insertions(+), 2017 deletions(-) delete mode 100644 docs/Makefile create mode 100644 docs/changelog.md create mode 100644 docs/decoders.md create mode 100644 docs/exceptions.md create mode 100644 docs/helper-functions.md create mode 100644 docs/index.md create mode 100644 docs/license.md create mode 100644 docs/main-class.md delete mode 100644 docs/make.bat create mode 100644 docs/parsers.md delete mode 100644 docs/source/_themes/.gitignore delete mode 100644 docs/source/_themes/LICENSE delete mode 100644 docs/source/_themes/README delete mode 100644 docs/source/_themes/flask/layout.html delete mode 100644 docs/source/_themes/flask/relations.html delete mode 100644 docs/source/_themes/flask/static/flasky.css_t delete mode 100644 docs/source/_themes/flask/static/small_flask.css delete mode 100644 docs/source/_themes/flask/theme.conf delete mode 100644 docs/source/_themes/flask_small/layout.html delete mode 100644 docs/source/_themes/flask_small/static/flasky.css_t delete mode 100644 docs/source/_themes/flask_small/theme.conf delete mode 100644 docs/source/_themes/flask_theme_support.py delete mode 100644 docs/source/api.rst delete mode 100644 docs/source/conf.py delete mode 100644 docs/source/contents.rst.inc delete mode 100644 docs/source/index.rst delete mode 100644 docs/source/quickstart.rst create mode 100644 docs/support-classess.md create mode 100644 mkdocs.yml diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index c045a56..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,177 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from https://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-multipart.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-multipart.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/python-multipart" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-multipart" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..786b75d --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1 @@ +--8<-- "CHANGELOG.md" diff --git a/docs/decoders.md b/docs/decoders.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/exceptions.md b/docs/exceptions.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/helper-functions.md b/docs/helper-functions.md new file mode 100644 index 0000000..460fa3b --- /dev/null +++ b/docs/helper-functions.md @@ -0,0 +1,4 @@ + +# Helper Functions + +:::multipart.multipart.parse_form diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/license.md b/docs/license.md new file mode 100644 index 0000000..03843e9 --- /dev/null +++ b/docs/license.md @@ -0,0 +1,5 @@ +# License + +``` +--8<-- "LICENSE.txt" +``` diff --git a/docs/main-class.md b/docs/main-class.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 10fdc25..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,242 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source -set I18NSPHINXOPTS=%SPHINXOPTS% source -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-multipart.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-multipart.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/docs/parsers.md b/docs/parsers.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/source/_themes/.gitignore b/docs/source/_themes/.gitignore deleted file mode 100644 index 66b6e4c..0000000 --- a/docs/source/_themes/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.pyc -*.pyo -.DS_Store diff --git a/docs/source/_themes/LICENSE b/docs/source/_themes/LICENSE deleted file mode 100644 index 8daab7e..0000000 --- a/docs/source/_themes/LICENSE +++ /dev/null @@ -1,37 +0,0 @@ -Copyright (c) 2010 by Armin Ronacher. - -Some rights reserved. - -Redistribution and use in source and binary forms of the theme, 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. - -* The names of the contributors may not be used to endorse or - promote products derived from this software without specific - prior written permission. - -We kindly ask you to only use these themes in an unmodified manner just -for Flask and Flask-related products, not for unrelated projects. If you -like the visual style and want to use it for your own projects, please -consider making some larger changes to the themes (such as changing -font faces, sizes, colors or margins). - -THIS THEME 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 OWNER 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 THEME, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/source/_themes/README b/docs/source/_themes/README deleted file mode 100644 index b3292bd..0000000 --- a/docs/source/_themes/README +++ /dev/null @@ -1,31 +0,0 @@ -Flask Sphinx Styles -=================== - -This repository contains sphinx styles for Flask and Flask related -projects. To use this style in your Sphinx documentation, follow -this guide: - -1. put this folder as _themes into your docs folder. Alternatively - you can also use git submodules to check out the contents there. -2. add this to your conf.py: - - sys.path.append(os.path.abspath('_themes')) - html_theme_path = ['_themes'] - html_theme = 'flask' - -The following themes exist: - -- 'flask' - the standard flask documentation theme for large - projects -- 'flask_small' - small one-page theme. Intended to be used by - very small addon libraries for flask. - -The following options exist for the flask_small theme: - - [options] - index_logo = '' filename of a picture in _static - to be used as replacement for the - h1 in the index.rst file. - index_logo_height = 120px height of the index logo - github_fork = '' repository name on github for the - "fork me" badge diff --git a/docs/source/_themes/flask/layout.html b/docs/source/_themes/flask/layout.html deleted file mode 100644 index 6a80763..0000000 --- a/docs/source/_themes/flask/layout.html +++ /dev/null @@ -1,25 +0,0 @@ -{%- extends "basic/layout.html" %} -{%- block extrahead %} - {{ super() }} - {% if theme_touch_icon %} - - {% endif %} - -{% endblock %} -{%- block relbar2 %}{% endblock %} -{% block header %} - {{ super() }} - {% if pagename == 'index' %} -
- {% endif %} -{% endblock %} -{%- block footer %} - - {% if pagename == 'index' %} -
- {% endif %} -{%- endblock %} diff --git a/docs/source/_themes/flask/relations.html b/docs/source/_themes/flask/relations.html deleted file mode 100644 index 3bbcde8..0000000 --- a/docs/source/_themes/flask/relations.html +++ /dev/null @@ -1,19 +0,0 @@ -

Related Topics

- diff --git a/docs/source/_themes/flask/static/flasky.css_t b/docs/source/_themes/flask/static/flasky.css_t deleted file mode 100644 index b5ca39b..0000000 --- a/docs/source/_themes/flask/static/flasky.css_t +++ /dev/null @@ -1,395 +0,0 @@ -/* - * flasky.css_t - * ~~~~~~~~~~~~ - * - * :copyright: Copyright 2010 by Armin Ronacher. - * :license: Flask Design License, see LICENSE for details. - */ - -{% set page_width = '940px' %} -{% set sidebar_width = '220px' %} - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: 'Georgia', serif; - font-size: 17px; - background-color: white; - color: #000; - margin: 0; - padding: 0; -} - -div.document { - width: {{ page_width }}; - margin: 30px auto 0 auto; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 {{ sidebar_width }}; -} - -div.sphinxsidebar { - width: {{ sidebar_width }}; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 0 30px; -} - -img.floatingflask { - padding: 0 0 10px 10px; - float: right; -} - -div.footer { - width: {{ page_width }}; - margin: 20px auto 30px auto; - font-size: 14px; - color: #888; - text-align: right; -} - -div.footer a { - color: #888; -} - -div.related { - display: none; -} - -div.sphinxsidebar a { - color: #444; - text-decoration: none; - border-bottom: 1px dotted #999; -} - -div.sphinxsidebar a:hover { - border-bottom: 1px solid #999; -} - -div.sphinxsidebar { - font-size: 14px; - line-height: 1.5; -} - -div.sphinxsidebarwrapper { - padding: 18px 10px; -} - -div.sphinxsidebarwrapper p.logo { - padding: 0 0 20px 0; - margin: 0; - text-align: center; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: 'Garamond', 'Georgia', serif; - color: #444; - font-size: 24px; - font-weight: normal; - margin: 0 0 5px 0; - padding: 0; -} - -div.sphinxsidebar h4 { - font-size: 20px; -} - -div.sphinxsidebar h3 a { - color: #444; -} - -div.sphinxsidebar p.logo a, -div.sphinxsidebar h3 a, -div.sphinxsidebar p.logo a:hover, -div.sphinxsidebar h3 a:hover { - border: none; -} - -div.sphinxsidebar p { - color: #555; - margin: 10px 0; -} - -div.sphinxsidebar ul { - margin: 10px 0; - padding: 0; - color: #000; -} - -div.sphinxsidebar input { - border: 1px solid #ccc; - font-family: 'Georgia', serif; - font-size: 1em; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -{% if theme_index_logo %} -div.indexwrapper h1 { - text-indent: -999999px; - background: url({{ theme_index_logo }}) no-repeat center center; - height: {{ theme_index_logo_height }}; -} -{% endif %} - -div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #ddd; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #eaeaea; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - background: #fafafa; - margin: 20px -30px; - padding: 10px 30px; - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; -} - -div.admonition tt.xref, div.admonition a tt { - border-bottom: 1px solid #fafafa; -} - -dd div.admonition { - margin-left: -60px; - padding-left: 60px; -} - -div.admonition p.admonition-title { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight { - background-color: white; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt { - font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; -} - -img.screenshot { -} - -tt.descname, tt.descclassname { - font-size: 0.95em; -} - -tt.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #eee; - background: #fdfdfd; - font-size: 0.9em; -} - -table.footnote + table.footnote { - margin-top: -15px; - border-top: none; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.footnote td.label { - width: 0px; - padding: 0.3em 0 0.3em 0.5em; -} - -table.footnote td { - padding: 0.3em 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -blockquote { - margin: 0 0 0 30px; - padding: 0; -} - -ul, ol { - margin: 10px 0 10px 30px; - padding: 0; -} - -pre { - background: #eee; - padding: 7px 30px; - margin: 15px -30px; - line-height: 1.3em; -} - -dl pre, blockquote pre, li pre { - margin-left: -60px; - padding-left: 60px; -} - -dl dl pre { - margin-left: -90px; - padding-left: 90px; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, a tt { - background-color: #FBFBFB; - border-bottom: 1px solid white; -} - -a.reference { - text-decoration: none; - border-bottom: 1px dotted #004B6B; -} - -a.reference:hover { - border-bottom: 1px solid #6D4100; -} - -a.footnote-reference { - text-decoration: none; - font-size: 0.7em; - vertical-align: top; - border-bottom: 1px dotted #004B6B; -} - -a.footnote-reference:hover { - border-bottom: 1px solid #6D4100; -} - -a:hover tt { - background: #EEE; -} diff --git a/docs/source/_themes/flask/static/small_flask.css b/docs/source/_themes/flask/static/small_flask.css deleted file mode 100644 index 1c6df30..0000000 --- a/docs/source/_themes/flask/static/small_flask.css +++ /dev/null @@ -1,70 +0,0 @@ -/* - * small_flask.css_t - * ~~~~~~~~~~~~~~~~~ - * - * :copyright: Copyright 2010 by Armin Ronacher. - * :license: Flask Design License, see LICENSE for details. - */ - -body { - margin: 0; - padding: 20px 30px; -} - -div.documentwrapper { - float: none; - background: white; -} - -div.sphinxsidebar { - display: block; - float: none; - width: 102.5%; - margin: 50px -30px -20px -30px; - padding: 10px 20px; - background: #333; - color: white; -} - -div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, -div.sphinxsidebar h3 a { - color: white; -} - -div.sphinxsidebar a { - color: #aaa; -} - -div.sphinxsidebar p.logo { - display: none; -} - -div.document { - width: 100%; - margin: 0; -} - -div.related { - display: block; - margin: 0; - padding: 10px 0 20px 0; -} - -div.related ul, -div.related ul li { - margin: 0; - padding: 0; -} - -div.footer { - display: none; -} - -div.bodywrapper { - margin: 0; -} - -div.body { - min-height: 0; - padding: 0; -} diff --git a/docs/source/_themes/flask/theme.conf b/docs/source/_themes/flask/theme.conf deleted file mode 100644 index 18c720f..0000000 --- a/docs/source/_themes/flask/theme.conf +++ /dev/null @@ -1,9 +0,0 @@ -[theme] -inherit = basic -stylesheet = flasky.css -pygments_style = flask_theme_support.FlaskyStyle - -[options] -index_logo = '' -index_logo_height = 120px -touch_icon = diff --git a/docs/source/_themes/flask_small/layout.html b/docs/source/_themes/flask_small/layout.html deleted file mode 100644 index 83e5213..0000000 --- a/docs/source/_themes/flask_small/layout.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "basic/layout.html" %} -{% block header %} - {{ super() }} - {% if pagename == 'index' %} -
- {% endif %} -{% endblock %} -{% block footer %} - {% if pagename == 'index' %} -
- {% endif %} -{% endblock %} -{# do not display relbars #} -{% block relbar1 %}{% endblock %} -{% block relbar2 %} - {% if theme_github_fork %} - Fork me on GitHub - {% endif %} -{% endblock %} -{% block sidebar1 %}{% endblock %} -{% block sidebar2 %}{% endblock %} diff --git a/docs/source/_themes/flask_small/static/flasky.css_t b/docs/source/_themes/flask_small/static/flasky.css_t deleted file mode 100644 index fe2141c..0000000 --- a/docs/source/_themes/flask_small/static/flasky.css_t +++ /dev/null @@ -1,287 +0,0 @@ -/* - * flasky.css_t - * ~~~~~~~~~~~~ - * - * Sphinx stylesheet -- flasky theme based on nature theme. - * - * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: 'Georgia', serif; - font-size: 17px; - color: #000; - background: white; - margin: 0; - padding: 0; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 40px auto 0 auto; - width: 700px; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 30px 30px; -} - -img.floatingflask { - padding: 0 0 10px 10px; - float: right; -} - -div.footer { - text-align: right; - color: #888; - padding: 10px; - font-size: 14px; - width: 650px; - margin: 0 auto 40px auto; -} - -div.footer a { - color: #888; - text-decoration: underline; -} - -div.related { - line-height: 32px; - color: #888; -} - -div.related ul { - padding: 0 0 0 10px; -} - -div.related a { - color: #444; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body { - padding-bottom: 40px; /* saved for footer */ -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -{% if theme_index_logo %} -div.indexwrapper h1 { - text-indent: -999999px; - background: url({{ theme_index_logo }}) no-repeat center center; - height: {{ theme_index_logo_height }}; -} -{% endif %} - -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: white; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #eaeaea; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - background: #fafafa; - margin: 20px -30px; - padding: 10px 30px; - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; -} - -div.admonition p.admonition-title { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight{ - background-color: white; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt { - font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.85em; -} - -img.screenshot { -} - -tt.descname, tt.descclassname { - font-size: 0.95em; -} - -tt.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #eee; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.footnote td { - padding: 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -pre { - padding: 0; - margin: 15px -30px; - padding: 8px; - line-height: 1.3em; - padding: 7px 30px; - background: #eee; - border-radius: 2px; - -moz-border-radius: 2px; - -webkit-border-radius: 2px; -} - -dl pre { - margin-left: -60px; - padding-left: 60px; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, a tt { - background-color: #FBFBFB; -} - -a:hover tt { - background: #EEE; -} diff --git a/docs/source/_themes/flask_small/theme.conf b/docs/source/_themes/flask_small/theme.conf deleted file mode 100644 index 542b462..0000000 --- a/docs/source/_themes/flask_small/theme.conf +++ /dev/null @@ -1,10 +0,0 @@ -[theme] -inherit = basic -stylesheet = flasky.css -nosidebar = true -pygments_style = flask_theme_support.FlaskyStyle - -[options] -index_logo = '' -index_logo_height = 120px -github_fork = '' diff --git a/docs/source/_themes/flask_theme_support.py b/docs/source/_themes/flask_theme_support.py deleted file mode 100644 index 33f4744..0000000 --- a/docs/source/_themes/flask_theme_support.py +++ /dev/null @@ -1,86 +0,0 @@ -# flasky extensions. flasky pygments style based on tango style -from pygments.style import Style -from pygments.token import Keyword, Name, Comment, String, Error, \ - Number, Operator, Generic, Whitespace, Punctuation, Other, Literal - - -class FlaskyStyle(Style): - background_color = "#f8f8f8" - default_style = "" - - styles = { - # No corresponding class for the following: - #Text: "", # class: '' - Whitespace: "underline #f8f8f8", # class: 'w' - Error: "#a40000 border:#ef2929", # class: 'err' - Other: "#000000", # class 'x' - - Comment: "italic #8f5902", # class: 'c' - Comment.Preproc: "noitalic", # class: 'cp' - - Keyword: "bold #004461", # class: 'k' - Keyword.Constant: "bold #004461", # class: 'kc' - Keyword.Declaration: "bold #004461", # class: 'kd' - Keyword.Namespace: "bold #004461", # class: 'kn' - Keyword.Pseudo: "bold #004461", # class: 'kp' - Keyword.Reserved: "bold #004461", # class: 'kr' - Keyword.Type: "bold #004461", # class: 'kt' - - Operator: "#582800", # class: 'o' - Operator.Word: "bold #004461", # class: 'ow' - like keywords - - Punctuation: "bold #000000", # class: 'p' - - # because special names such as Name.Class, Name.Function, etc. - # are not recognized as such later in the parsing, we choose them - # to look the same as ordinary variables. - Name: "#000000", # class: 'n' - Name.Attribute: "#c4a000", # class: 'na' - to be revised - Name.Builtin: "#004461", # class: 'nb' - Name.Builtin.Pseudo: "#3465a4", # class: 'bp' - Name.Class: "#000000", # class: 'nc' - to be revised - Name.Constant: "#000000", # class: 'no' - to be revised - Name.Decorator: "#888", # class: 'nd' - to be revised - Name.Entity: "#ce5c00", # class: 'ni' - Name.Exception: "bold #cc0000", # class: 'ne' - Name.Function: "#000000", # class: 'nf' - Name.Property: "#000000", # class: 'py' - Name.Label: "#f57900", # class: 'nl' - Name.Namespace: "#000000", # class: 'nn' - to be revised - Name.Other: "#000000", # class: 'nx' - Name.Tag: "bold #004461", # class: 'nt' - like a keyword - Name.Variable: "#000000", # class: 'nv' - to be revised - Name.Variable.Class: "#000000", # class: 'vc' - to be revised - Name.Variable.Global: "#000000", # class: 'vg' - to be revised - Name.Variable.Instance: "#000000", # class: 'vi' - to be revised - - Number: "#990000", # class: 'm' - - Literal: "#000000", # class: 'l' - Literal.Date: "#000000", # class: 'ld' - - String: "#4e9a06", # class: 's' - String.Backtick: "#4e9a06", # class: 'sb' - String.Char: "#4e9a06", # class: 'sc' - String.Doc: "italic #8f5902", # class: 'sd' - like a comment - String.Double: "#4e9a06", # class: 's2' - String.Escape: "#4e9a06", # class: 'se' - String.Heredoc: "#4e9a06", # class: 'sh' - String.Interpol: "#4e9a06", # class: 'si' - String.Other: "#4e9a06", # class: 'sx' - String.Regex: "#4e9a06", # class: 'sr' - String.Single: "#4e9a06", # class: 's1' - String.Symbol: "#4e9a06", # class: 'ss' - - Generic: "#000000", # class: 'g' - Generic.Deleted: "#a40000", # class: 'gd' - Generic.Emph: "italic #000000", # class: 'ge' - Generic.Error: "#ef2929", # class: 'gr' - Generic.Heading: "bold #000080", # class: 'gh' - Generic.Inserted: "#00A000", # class: 'gi' - Generic.Output: "#888", # class: 'go' - Generic.Prompt: "#745334", # class: 'gp' - Generic.Strong: "bold #000000", # class: 'gs' - Generic.Subheading: "bold #800080", # class: 'gu' - Generic.Traceback: "bold #a40000", # class: 'gt' - } diff --git a/docs/source/api.rst b/docs/source/api.rst deleted file mode 100644 index 2b4e5f6..0000000 --- a/docs/source/api.rst +++ /dev/null @@ -1,74 +0,0 @@ -.. _api: - -API -=== - -.. module:: multipart - -This section of the documentation covers all of the public interfaces of -python-multipart. - - -Helper Functions ----------------- - -.. currentmodule:: multipart.multipart - -.. autofunction:: parse_form - -.. autofunction:: create_form_parser - - -Main Class ----------- - -.. currentmodule:: multipart.multipart - -.. autoclass:: FormParser - :members: - - -Parsers -------- - -.. currentmodule:: multipart.multipart - -.. autoclass:: BaseParser - :members: - -.. autoclass:: OctetStreamParser - :members: - -.. autoclass:: QuerystringParser - :members: - -.. autoclass:: MultipartParser - :members: - - -Support Classes ---------------- - -.. currentmodule:: multipart.multipart - -.. autoclass:: Field - :members: - -.. autoclass:: File - :members: - - -Decoders --------- - -.. automodule:: multipart.decoders - :members: - - -Exceptions ----------- - -The following are all custom exceptions that python-multipart will raise, for various cases. Each method that will raise an exception will document it in this documentation. - -.. automodule:: multipart.exceptions - :members: diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index d13072e..0000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,256 +0,0 @@ -# python-multipart documentation build configuration file, created by -# sphinx-quickstart on Fri Apr 5 20:24:27 2013. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath(os.path.join('..', '..'))) -import multipart -from multipart import __version__ - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.ifconfig'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'python-multipart' -copyright = '2013, Andrew Dunham' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -from multipart import __version__ -# The short X.Y version. -version = __version__ -# The full version, including alpha/beta/rc tags. -release = version - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -sys.path.append(os.path.abspath('_themes')) - -html_theme = 'flask' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -html_theme_options = { -} - -# Add any paths that contain custom themes here, relative to this directory. -html_theme_path = ['_themes'] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -html_show_sourcelink = False - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -html_show_sphinx = False - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'python-multipartdoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'python-multipart.tex', 'python-multipart Documentation', - 'Andrew Dunham', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'python-multipart', 'python-multipart Documentation', - ['Andrew Dunham'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'python-multipart', 'python-multipart Documentation', - 'Andrew Dunham', 'python-multipart', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/docs/source/contents.rst.inc b/docs/source/contents.rst.inc deleted file mode 100644 index fa92a94..0000000 --- a/docs/source/contents.rst.inc +++ /dev/null @@ -1,20 +0,0 @@ -User's Guide ------------- - -.. toctree:: - :maxdepth: 2 - - quickstart - - -API Reference -------------- - -If you are looking for information on a specific function, class or -method, this part of the documentation is for you. - -.. toctree:: - :maxdepth: 2 - - api - diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index edf594c..0000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,10 +0,0 @@ -:orphan: - -Python-Multipart -================ - -.. module:: multipart - -Python-Multipart is a streaming multipart parser for Python. - -.. include:: contents.rst.inc diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst deleted file mode 100644 index b68b06f..0000000 --- a/docs/source/quickstart.rst +++ /dev/null @@ -1,195 +0,0 @@ -.. _quickstart: - -Quickstart -========== - -.. currentmodule:: multipart - -Python-Multipart foo bar baz - - -Simple Example --------------- - -The following example shows a quick example of parsing an incoming request body -in a simple WSGI application:: - - import multipart - - def simple_app(environ, start_response): - ret = [] - - # The following two callbacks just append the name to the return value. - def on_field(field): - ret.append(b"Parsed field named: %s" % (field.field_name,)) - - def on_file(file): - ret.append(b"Parsed file named: %s" % (file.field_name,)) - - # Create headers object. We need to convert from WSGI to the actual - # name of the header, since this library does not assume that you are - # using WSGI. - headers = {'Content-Type': environ['CONTENT_TYPE']} - if 'HTTP_X_FILE_NAME' in environ: - headers['X-File-Name'] = environ['HTTP_X_FILE_NAME'] - if 'CONTENT_LENGTH' in environ: - headers['Content-Length'] = environ['CONTENT_LENGTH'] - - # Parse the form. - multipart.parse_form(headers, environ['wsgi.input'], on_field, on_file) - - # Return something. - start_response('200 OK', [('Content-type', 'text/plain')]) - ret.append(b'\n') - return ret - - from wsgiref.simple_server import make_server - from wsgiref.validate import validator - - httpd = make_server('', 8123, simple_app) - print("Serving on port 8123...") - httpd.serve_forever() - -If you test this with curl, you can see that the parser works: - -.. code-block:: bash - - $ curl -ik -F "foo=bar" http://localhost:8123/ - HTTP/1.0 200 OK - Date: Sun, 07 Apr 2013 01:40:52 GMT - Server: WSGIServer/0.1 Python/2.7.3 - Content-type: text/plain - - Parsed field named: foo - -For a more in-depth example showing how the various parts fit together, check -out the next section. - - -In-Depth Example ----------------- - -In this section, we'll build an application that computes the SHA-256 hash of -all uploaded files in a streaming manner. - -To start, we need a simple WSGI application. We could do this with a framework -like Flask, Django, or Tornado, but for now let's stick to plain WSGI: - -.. code-block:: python - - import multipart - - def simple_app(environ, start_response): - start_response('200 OK', [('Content-type', 'text/plain')]) - return ['Hashes:\n'] - - from wsgiref.simple_server import make_server - httpd = make_server('', 8123, simple_app) - print("Serving on port 8123...") - httpd.serve_forever() - -You can run this and check with curl that it works properly: - -.. code-block:: bash - - $ curl -ik http://localhost:8123/ - HTTP/1.0 200 OK - Date: Sun, 07 Apr 2013 01:49:03 GMT - Server: WSGIServer/0.1 Python/2.7.3 - Content-type: text/plain - Content-Length: 8 - - Hashes: - -Good! It works. Now, let's add some of the code that we need. What we need -to do, essentially, is set up the appropriate parser and callbacks so that we -can access each portion of the request as it arrives, without needing to store -any parts in memory. - -We can start off by checking if we need to create the parser at all - if the -Content-Type isn't `multipart/form-data`, then we're not going to do anything. - - - - -The final code should look like this: - -.. code-block:: python - - import hashlib - import multipart - from multipart.multipart import parse_options_header - - def simple_app(environ, start_response): - ret = [] - - # Python 2 doesn't have the "nonlocal" keyword from Python 3, so we get - # around it by setting attributes on a dummy object. - class g(object): - hash = None - - # This is called when a new part arrives. We create a new hash object - # in this callback. - def on_part_begin(): - g.hash = hashlib.sha256() - - # We got some data! Update our hash. - def on_part_data(data, start, end): - g.hash.update(data[start:end]) - - # Our current part is done, so we can finish the hash. - def on_part_end(): - ret.append("Part hash: %s" % (g.hash.hexdigest(),)) - - # Parse the Content-Type header to get the multipart boundary. - content_type, params = parse_options_header(environ['CONTENT_TYPE']) - boundary = params.get(b'boundary') - - # Callbacks dictionary. - callbacks = { - 'on_part_begin': on_part_begin, - 'on_part_data': on_part_data, - 'on_part_end': on_part_end, - } - - # Create the parser. - parser = multipart.MultipartParser(boundary, callbacks) - - # The input stream is from the WSGI environ. - inp = environ['wsgi.input'] - - # Feed the parser with data from the request. - size = int(environ['CONTENT_LENGTH']) - while size > 0: - to_read = min(size, 1024 * 1024) - data = inp.read(to_read) - parser.write(data) - - size -= len(data) - if len(data) != to_read: - break - - start_response('200 OK', [('Content-type', 'text/plain')]) - return ret - - from wsgiref.simple_server import make_server - httpd = make_server('', 8123, simple_app) - print("Serving on port 8123...") - httpd.serve_forever() - - -And you can see that this works: - -.. code-block:: bash - - $ echo "Foo bar" > /tmp/test.txt - $ shasum -a 256 /tmp/test.txt - 0b64696c0f7ddb9e3435341720988d5455b3b0f0724688f98ec8e6019af3d931 /tmp/test.txt - $ curl -ik -F file=@/tmp/test.txt http://localhost:8123/ - HTTP/1.0 200 OK - Date: Sun, 07 Apr 2013 02:09:10 GMT - Server: WSGIServer/0.1 Python/2.7.3 - Content-type: text/plain - - Hashes: - Part hash: 0b64696c0f7ddb9e3435341720988d5455b3b0f0724688f98ec8e6019af3d931 diff --git a/docs/support-classess.md b/docs/support-classess.md new file mode 100644 index 0000000..e69de29 diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..b94b6da --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,107 @@ +site_name: python-multipart +site_url: https://kludex.github.io/python-multipart/ +site_author: Marcelo Trylesinski,Andrew Dunham +site_description: A streaming multipart parser for Python. +repo_name: kludex/python-multipart +repo_url: https://github.com//kludex/python-multipart +edit_uri: https://github.com/kludex/python-multipart/tree/master/docs +copyright: Python-multipart 2024. All rights reserved. + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/kludex/python-multipart + - icon: fontawesome/brands/python + link: https://pypi.org/project/python-multipart + + + + +nav: + - Home: index.md + - API: + - Helper-functions: helper-functions.md + - Main-class: main-class.md + - Parsers: parsers.md + - Support-classes: support-classes.md + - Decoders: decoders.md + - Exceptions: exceptions.md + - Contribute: + - License: license.md + - Changelog: changelog.md + + +theme: + name: 'material' + custom_dir: docs/theme + features: + - navigation.tracking + - content.code.copy + - content.tooltips + - content.code.annotate + - navigation.tabs + + palette: + # Palette for light mode + - scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + + # Palette toggle for dark mode + - scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to light mode + font: + text: Roboto + code: Roboto Mono + features: + - content.code.copy + - content.code.annotate + +plugins: + - search + - mkdocstrings: + default_handler: python + handlers: + python: + options: + parameter_headings: true + paths: [python-multipart] + load_external_modules: true + allow_inspection: true + show_bases: true + group_by_category: true + docstring_style: google + show_symbol_type_heading: true + show_symbol_type_toc: true + show_category_heading: true + domains: [std, py] + - git-committers: + repository: kludex/python-multipart + branch: develop + token: !ENV ["GITHUB_TOKEN"] + - git-revision-date-localized: + enable_creation_date: true + + + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - attr_list + - md_in_html + - pymdownx.tabbed: + alternate_style: true + - toc: + permalink: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.snippets: + check_paths: true + - pymdownx.highlight: + anchor_linenums: true + diff --git a/multipart/multipart.py b/multipart/multipart.py index 21d9ac1..d8dd855 100644 --- a/multipart/multipart.py +++ b/multipart/multipart.py @@ -211,7 +211,7 @@ class Field: will be called when data is written to the Field, and when the Field is finalized, respectively. - :param name: the name of the form field + name: the name of the form field """ def __init__(self, name: bytes): @@ -227,8 +227,8 @@ def from_value(cls, name: bytes, value: bytes | None) -> Field: value - either None or an actual value. This method will also finalize the Field itself. - :param name: the name of the form field - :param value: the value of the form field - either a bytestring or + name: the name of the form field + value: the value of the form field - either a bytestring or None """ @@ -243,7 +243,7 @@ def from_value(cls, name: bytes, value: bytes | None) -> Field: def write(self, data: bytes) -> int: """Write some data into the form field. - :param data: a bytestring + data: a bytestring """ return self.on_data(data) @@ -251,7 +251,7 @@ def on_data(self, data: bytes) -> int: """This method is a callback that will be called whenever data is written to the Field. - :param data: a bytestring + data: a bytestring """ self._value.append(data) self._cache = _missing @@ -363,13 +363,13 @@ class File: value to an appropriately large value (or, for example, infinity, such as `float('inf')`. - :param file_name: The name of the file that this :class:`File` represents + file_name: The name of the file that this :class:`File` represents - :param field_name: The field name that uploaded this file. Note that this + field_name: The field name that uploaded this file. Note that this can be None, if, for example, the file was uploaded with Content-Type application/octet-stream - :param config: The configuration for this File. See above for valid + config: The configuration for this File. See above for valid configuration keys and their corresponding values. """ @@ -537,7 +537,7 @@ def _get_disk_file(self): def write(self, data: bytes): """Write some data to the File. - :param data: a bytestring + data: a bytestring """ return self.on_data(data) @@ -545,7 +545,7 @@ def on_data(self, data: bytes): """This method is a callback that will be called whenever data is written to the File. - :param data: a bytestring + data: a bytestring """ pos = self._fileobj.tell() bwritten = self._fileobj.write(data) @@ -625,15 +625,16 @@ def callback(self, name: str, data: bytes | None = None, start: int | None = Non """This function calls a provided callback with some data. If the callback is not set, will do nothing. - :param name: The name of the callback to call (as a string). + Parameters: + name: The name of the callback to call (as a string). - :param data: Data to pass to the callback. If None, then it is + data: Data to pass to the callback. If None, then it is assumed that the callback is a notification callback, and no parameters are given. - :param end: An integer that is passed to the data callback. + end: An integer that is passed to the data callback. - :param start: An integer that is passed to the data callback. + start: An integer that is passed to the data callback. """ name = "on_" + name func = self.callbacks.get(name) @@ -656,9 +657,10 @@ def set_callback(self, name: str, new_func): """Update the function for a callback. Removes from the callbacks dict if new_func is None. - :param name: The name of the callback to call (as a string). + Parameters: + name: The name of the callback to call (as a string). - :param new_func: The new function for the callback. If None, then the + new_func: The new function for the callback. If None, then the callback will be removed (with no error if it does not exist). """ @@ -698,10 +700,11 @@ class OctetStreamParser(BaseParser): - None - Called when the parser is finished parsing all data. - :param callbacks: A dictionary of callbacks. See the documentation for + Parameters: + callbacks: A dictionary of callbacks. See the documentation for :class:`BaseParser`. - :param max_size: The maximum size of body to parse. Defaults to infinity - + max_size: The maximum size of body to parse. Defaults to infinity - i.e. unbounded. """ @@ -719,7 +722,7 @@ def write(self, data: bytes): """Write some data to the parser, which will perform size verification, and then pass the data to the underlying callback. - :param data: a bytestring + data: a bytestring """ if not self._started: self.callback("start") @@ -781,10 +784,11 @@ class QuerystringParser(BaseParser): - None - Called when the parser is finished parsing all data. - :param callbacks: A dictionary of callbacks. See the documentation for + Parameters: + callbacks: A dictionary of callbacks. See the documentation for :class:`BaseParser`. - :param strict_parsing: Whether or not to parse the body strictly. Defaults + strict_parsing: Whether or not to parse the body strictly. Defaults to False. If this is set to True, then the behavior of the parser changes as the following: if a field has a value with an equal sign (e.g. "foo=bar", or @@ -796,7 +800,7 @@ class QuerystringParser(BaseParser): :class:`multipart.exceptions.QuerystringParseError` will be raised. - :param max_size: The maximum size of body to parse. Defaults to infinity - + max_size: The maximum size of body to parse. Defaults to infinity - i.e. unbounded. """ @@ -826,7 +830,7 @@ def write(self, data: bytes) -> int: "offset" attribute of the raised exception will be set to the offset in the input data chunk (NOT the overall stream) that caused the error. - :param data: a bytestring + data: a bytestring """ # Handle sizing. data_len = len(data) @@ -1047,15 +1051,15 @@ class MultipartParser(BaseParser): - None - Called when the parser is finished parsing all data. - - :param boundary: The multipart boundary. This is required, and must match + Parameters: + boundary: The multipart boundary. This is required, and must match what is given in the HTTP request - usually in the Content-Type header. - :param callbacks: A dictionary of callbacks. See the documentation for + callbacks: A dictionary of callbacks. See the documentation for :class:`BaseParser`. - :param max_size: The maximum size of body to parse. Defaults to infinity - + max_size: The maximum size of body to parse. Defaults to infinity - i.e. unbounded. """ @@ -1106,7 +1110,7 @@ def write(self, data: bytes) -> int: attribute on the raised exception will be set to the offset of the byte in the input chunk that caused the error. - :param data: a bytestring + data: a bytestring """ # Handle sizing. data_len = len(data) @@ -1548,28 +1552,29 @@ class FormParser: is parsed, and call the two given callbacks with each field and file as they become available. - :param content_type: The Content-Type of the incoming request. This is + Parameters: + content_type: The Content-Type of the incoming request. This is used to select the appropriate parser. - :param on_field: The callback to call when a field has been parsed and is + on_field: The callback to call when a field has been parsed and is ready for usage. See above for parameters. - :param on_file: The callback to call when a file has been parsed and is + on_file: The callback to call when a file has been parsed and is ready for usage. See above for parameters. - :param on_end: An optional callback to call when all fields and files in a + on_end: An optional callback to call when all fields and files in a request has been parsed. Can be None. - :param boundary: If the request is a multipart/form-data request, this + boundary: If the request is a multipart/form-data request, this should be the boundary of the request, as given in the Content-Type header, as a bytestring. - :param file_name: If the request is of type application/octet-stream, then + file_name: If the request is of type application/octet-stream, then the body of the request will not contain any information about the uploaded file. In such cases, you can provide the file name of the uploaded file manually. - :param FileClass: The class to use for uploaded files. Defaults to + FileClass: The class to use for uploaded files. Defaults to :class:`File`, but you can provide your own class if you wish to customize behaviour. The class will be instantiated as FileClass(file_name, field_name), and it @@ -1578,7 +1583,7 @@ class FormParser: file_instance.finalize() file_instance.close() - :param FieldClass: The class to use for uploaded fields. Defaults to + FieldClass: The class to use for uploaded fields. Defaults to :class:`Field`, but you can provide your own class if you wish to customize behaviour. The class will be instantiated as FieldClass(field_name), and it must @@ -1588,7 +1593,7 @@ class FormParser: field_instance.close() field_instance.set_none() - :param config: Configuration to use for this FormParser. The default + config: Configuration to use for this FormParser. The default values are taken from the DEFAULT_CONFIG value, and then any keys present in this dictionary will overwrite the default values. @@ -1835,7 +1840,7 @@ def write(self, data: bytes): """Write some data. The parser will forward this to the appropriate underlying parser. - :param data: a bytestring + data: a bytestring """ self.bytes_received += len(data) # TODO: check the parser's return value for errors? @@ -1862,18 +1867,19 @@ def create_form_parser(headers, on_field, on_file, trust_x_headers=False, config appropriate values and given callbacks, and then return the corresponding parser. - :param headers: A dictionary-like object of HTTP headers. The only + Parameters: + headers: A dictionary-like object of HTTP headers. The only required header is Content-Type. - :param on_field: Callback to call with each parsed field. + on_field: Callback to call with each parsed field. - :param on_file: Callback to call with each parsed file. + on_file: Callback to call with each parsed file. - :param trust_x_headers: Whether or not to trust information received from + trust_x_headers: Whether or not to trust information received from certain X-Headers - for example, the file name from X-File-Name. - :param config: Configuration variables to pass to the FormParser. + config: Configuration variables to pass to the FormParser. """ content_type = headers.get("Content-Type") if content_type is None: @@ -1904,17 +1910,18 @@ def parse_form(headers, input_stream, on_field, on_file, chunk_size=1048576, **k headers, and a file-like object for the input stream, along with two callbacks that will get called whenever a field or file is parsed. - :param headers: A dictionary-like object of HTTP headers. The only + Parameters: + headers: A dictionary-like object of HTTP headers. The only required header is Content-Type. - :param input_stream: A file-like object that represents the request body. + input_stream: A file-like object that represents the request body. The read() method must return bytestrings. - :param on_field: Callback to call with each parsed field. + on_field: Callback to call with each parsed field. - :param on_file: Callback to call with each parsed file. + on_file: Callback to call with each parsed file. - :param chunk_size: The maximum size to read from the input stream and write + chunk_size: The maximum size to read from the input stream and write to the parser at one time. Defaults to 1 MiB. """ diff --git a/pyproject.toml b/pyproject.toml index e53399c..400a26b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,8 @@ doc = [ "pymdown-extensions==10.7", "mkdocstrings==0.24.0", "mkdocstrings-python==1.8.0", - "mkdocs-autorefs=0.5.0" + "mkdocs-autorefs=0.5.0", + "mkdocs-git-committers-plugin==2-2.2.3" ] [project.urls] From 30ac6dd2e8027438fb59503960b978a0ebd886f3 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Sun, 11 Feb 2024 19:55:08 +0300 Subject: [PATCH 05/10] =?UTF-8?q?docs:=20=E2=9C=A8=20set=20sphinx=20doc=20?= =?UTF-8?q?style=20as=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- docs/helper-functions.md | 2 + mkdocs.yml | 2 +- multipart/multipart.py | 103 ++++++++++++++++++--------------------- 3 files changed, 51 insertions(+), 56 deletions(-) diff --git a/docs/helper-functions.md b/docs/helper-functions.md index 460fa3b..f1eea48 100644 --- a/docs/helper-functions.md +++ b/docs/helper-functions.md @@ -1,4 +1,6 @@ # Helper Functions +## multipart.parse_form + :::multipart.multipart.parse_form diff --git a/mkdocs.yml b/mkdocs.yml index b94b6da..ba56080 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -73,7 +73,7 @@ plugins: allow_inspection: true show_bases: true group_by_category: true - docstring_style: google + docstring_style: sphinx show_symbol_type_heading: true show_symbol_type_toc: true show_category_heading: true diff --git a/multipart/multipart.py b/multipart/multipart.py index d8dd855..21d9ac1 100644 --- a/multipart/multipart.py +++ b/multipart/multipart.py @@ -211,7 +211,7 @@ class Field: will be called when data is written to the Field, and when the Field is finalized, respectively. - name: the name of the form field + :param name: the name of the form field """ def __init__(self, name: bytes): @@ -227,8 +227,8 @@ def from_value(cls, name: bytes, value: bytes | None) -> Field: value - either None or an actual value. This method will also finalize the Field itself. - name: the name of the form field - value: the value of the form field - either a bytestring or + :param name: the name of the form field + :param value: the value of the form field - either a bytestring or None """ @@ -243,7 +243,7 @@ def from_value(cls, name: bytes, value: bytes | None) -> Field: def write(self, data: bytes) -> int: """Write some data into the form field. - data: a bytestring + :param data: a bytestring """ return self.on_data(data) @@ -251,7 +251,7 @@ def on_data(self, data: bytes) -> int: """This method is a callback that will be called whenever data is written to the Field. - data: a bytestring + :param data: a bytestring """ self._value.append(data) self._cache = _missing @@ -363,13 +363,13 @@ class File: value to an appropriately large value (or, for example, infinity, such as `float('inf')`. - file_name: The name of the file that this :class:`File` represents + :param file_name: The name of the file that this :class:`File` represents - field_name: The field name that uploaded this file. Note that this + :param field_name: The field name that uploaded this file. Note that this can be None, if, for example, the file was uploaded with Content-Type application/octet-stream - config: The configuration for this File. See above for valid + :param config: The configuration for this File. See above for valid configuration keys and their corresponding values. """ @@ -537,7 +537,7 @@ def _get_disk_file(self): def write(self, data: bytes): """Write some data to the File. - data: a bytestring + :param data: a bytestring """ return self.on_data(data) @@ -545,7 +545,7 @@ def on_data(self, data: bytes): """This method is a callback that will be called whenever data is written to the File. - data: a bytestring + :param data: a bytestring """ pos = self._fileobj.tell() bwritten = self._fileobj.write(data) @@ -625,16 +625,15 @@ def callback(self, name: str, data: bytes | None = None, start: int | None = Non """This function calls a provided callback with some data. If the callback is not set, will do nothing. - Parameters: - name: The name of the callback to call (as a string). + :param name: The name of the callback to call (as a string). - data: Data to pass to the callback. If None, then it is + :param data: Data to pass to the callback. If None, then it is assumed that the callback is a notification callback, and no parameters are given. - end: An integer that is passed to the data callback. + :param end: An integer that is passed to the data callback. - start: An integer that is passed to the data callback. + :param start: An integer that is passed to the data callback. """ name = "on_" + name func = self.callbacks.get(name) @@ -657,10 +656,9 @@ def set_callback(self, name: str, new_func): """Update the function for a callback. Removes from the callbacks dict if new_func is None. - Parameters: - name: The name of the callback to call (as a string). + :param name: The name of the callback to call (as a string). - new_func: The new function for the callback. If None, then the + :param new_func: The new function for the callback. If None, then the callback will be removed (with no error if it does not exist). """ @@ -700,11 +698,10 @@ class OctetStreamParser(BaseParser): - None - Called when the parser is finished parsing all data. - Parameters: - callbacks: A dictionary of callbacks. See the documentation for + :param callbacks: A dictionary of callbacks. See the documentation for :class:`BaseParser`. - max_size: The maximum size of body to parse. Defaults to infinity - + :param max_size: The maximum size of body to parse. Defaults to infinity - i.e. unbounded. """ @@ -722,7 +719,7 @@ def write(self, data: bytes): """Write some data to the parser, which will perform size verification, and then pass the data to the underlying callback. - data: a bytestring + :param data: a bytestring """ if not self._started: self.callback("start") @@ -784,11 +781,10 @@ class QuerystringParser(BaseParser): - None - Called when the parser is finished parsing all data. - Parameters: - callbacks: A dictionary of callbacks. See the documentation for + :param callbacks: A dictionary of callbacks. See the documentation for :class:`BaseParser`. - strict_parsing: Whether or not to parse the body strictly. Defaults + :param strict_parsing: Whether or not to parse the body strictly. Defaults to False. If this is set to True, then the behavior of the parser changes as the following: if a field has a value with an equal sign (e.g. "foo=bar", or @@ -800,7 +796,7 @@ class QuerystringParser(BaseParser): :class:`multipart.exceptions.QuerystringParseError` will be raised. - max_size: The maximum size of body to parse. Defaults to infinity - + :param max_size: The maximum size of body to parse. Defaults to infinity - i.e. unbounded. """ @@ -830,7 +826,7 @@ def write(self, data: bytes) -> int: "offset" attribute of the raised exception will be set to the offset in the input data chunk (NOT the overall stream) that caused the error. - data: a bytestring + :param data: a bytestring """ # Handle sizing. data_len = len(data) @@ -1051,15 +1047,15 @@ class MultipartParser(BaseParser): - None - Called when the parser is finished parsing all data. - Parameters: - boundary: The multipart boundary. This is required, and must match + + :param boundary: The multipart boundary. This is required, and must match what is given in the HTTP request - usually in the Content-Type header. - callbacks: A dictionary of callbacks. See the documentation for + :param callbacks: A dictionary of callbacks. See the documentation for :class:`BaseParser`. - max_size: The maximum size of body to parse. Defaults to infinity - + :param max_size: The maximum size of body to parse. Defaults to infinity - i.e. unbounded. """ @@ -1110,7 +1106,7 @@ def write(self, data: bytes) -> int: attribute on the raised exception will be set to the offset of the byte in the input chunk that caused the error. - data: a bytestring + :param data: a bytestring """ # Handle sizing. data_len = len(data) @@ -1552,29 +1548,28 @@ class FormParser: is parsed, and call the two given callbacks with each field and file as they become available. - Parameters: - content_type: The Content-Type of the incoming request. This is + :param content_type: The Content-Type of the incoming request. This is used to select the appropriate parser. - on_field: The callback to call when a field has been parsed and is + :param on_field: The callback to call when a field has been parsed and is ready for usage. See above for parameters. - on_file: The callback to call when a file has been parsed and is + :param on_file: The callback to call when a file has been parsed and is ready for usage. See above for parameters. - on_end: An optional callback to call when all fields and files in a + :param on_end: An optional callback to call when all fields and files in a request has been parsed. Can be None. - boundary: If the request is a multipart/form-data request, this + :param boundary: If the request is a multipart/form-data request, this should be the boundary of the request, as given in the Content-Type header, as a bytestring. - file_name: If the request is of type application/octet-stream, then + :param file_name: If the request is of type application/octet-stream, then the body of the request will not contain any information about the uploaded file. In such cases, you can provide the file name of the uploaded file manually. - FileClass: The class to use for uploaded files. Defaults to + :param FileClass: The class to use for uploaded files. Defaults to :class:`File`, but you can provide your own class if you wish to customize behaviour. The class will be instantiated as FileClass(file_name, field_name), and it @@ -1583,7 +1578,7 @@ class FormParser: file_instance.finalize() file_instance.close() - FieldClass: The class to use for uploaded fields. Defaults to + :param FieldClass: The class to use for uploaded fields. Defaults to :class:`Field`, but you can provide your own class if you wish to customize behaviour. The class will be instantiated as FieldClass(field_name), and it must @@ -1593,7 +1588,7 @@ class FormParser: field_instance.close() field_instance.set_none() - config: Configuration to use for this FormParser. The default + :param config: Configuration to use for this FormParser. The default values are taken from the DEFAULT_CONFIG value, and then any keys present in this dictionary will overwrite the default values. @@ -1840,7 +1835,7 @@ def write(self, data: bytes): """Write some data. The parser will forward this to the appropriate underlying parser. - data: a bytestring + :param data: a bytestring """ self.bytes_received += len(data) # TODO: check the parser's return value for errors? @@ -1867,19 +1862,18 @@ def create_form_parser(headers, on_field, on_file, trust_x_headers=False, config appropriate values and given callbacks, and then return the corresponding parser. - Parameters: - headers: A dictionary-like object of HTTP headers. The only + :param headers: A dictionary-like object of HTTP headers. The only required header is Content-Type. - on_field: Callback to call with each parsed field. + :param on_field: Callback to call with each parsed field. - on_file: Callback to call with each parsed file. + :param on_file: Callback to call with each parsed file. - trust_x_headers: Whether or not to trust information received from + :param trust_x_headers: Whether or not to trust information received from certain X-Headers - for example, the file name from X-File-Name. - config: Configuration variables to pass to the FormParser. + :param config: Configuration variables to pass to the FormParser. """ content_type = headers.get("Content-Type") if content_type is None: @@ -1910,18 +1904,17 @@ def parse_form(headers, input_stream, on_field, on_file, chunk_size=1048576, **k headers, and a file-like object for the input stream, along with two callbacks that will get called whenever a field or file is parsed. - Parameters: - headers: A dictionary-like object of HTTP headers. The only + :param headers: A dictionary-like object of HTTP headers. The only required header is Content-Type. - input_stream: A file-like object that represents the request body. + :param input_stream: A file-like object that represents the request body. The read() method must return bytestrings. - on_field: Callback to call with each parsed field. + :param on_field: Callback to call with each parsed field. - on_file: Callback to call with each parsed file. + :param on_file: Callback to call with each parsed file. - chunk_size: The maximum size to read from the input stream and write + :param chunk_size: The maximum size to read from the input stream and write to the parser at one time. Defaults to 1 MiB. """ From a421478a7a65a852b4f73b10ea42b45a2d78ad34 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Sun, 11 Feb 2024 20:15:15 +0300 Subject: [PATCH 06/10] =?UTF-8?q?docs:=20=E2=9C=A8=20decoders,exceptions,h?= =?UTF-8?q?elper-functions,main-class,parsers,support-classes=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- docs/decoders.md | 7 +++++++ docs/exceptions.md | 45 ++++++++++++++++++++++++++++++++++++++++ docs/helper-functions.md | 4 ++++ docs/main-class.md | 8 +++++++ docs/parsers.md | 8 +++++++ docs/support-classes.md | 7 +++++++ docs/support-classess.md | 0 7 files changed, 79 insertions(+) create mode 100644 docs/support-classes.md delete mode 100644 docs/support-classess.md diff --git a/docs/decoders.md b/docs/decoders.md index e69de29..349597c 100644 --- a/docs/decoders.md +++ b/docs/decoders.md @@ -0,0 +1,7 @@ +# Decoders + + + +:::multipart.decoders.Base64Decoder diff --git a/docs/exceptions.md b/docs/exceptions.md index e69de29..abc2b2a 100644 --- a/docs/exceptions.md +++ b/docs/exceptions.md @@ -0,0 +1,45 @@ +# Exceptions + +The following are all custom exceptions that python-multipart will raise, for various cases. Each method that will raise an exception will document it in this documentation. + + + +:::multipart.exceptions.DecodeError +This exception is raised when there is a decoding error - for example with the Base64Decoder or QuotedPrintableDecoder. + + + +:::multipart.exceptions.FileError +Exception class for problems with the File class. + + + +:::multipart.exceptions.FormParserError +Base error class for our form parser. + + + +:::multipart.exceptions.MultipartParseError + + + +:::multipart.exceptions.ParseError + +offset = -1 +This is the offset in the input data chunk (NOT the overall stream) in which the parse error occured. It will be -1 if not specified. + + + +:::multipart.exceptions.QuerystringParseError diff --git a/docs/helper-functions.md b/docs/helper-functions.md index f1eea48..289984c 100644 --- a/docs/helper-functions.md +++ b/docs/helper-functions.md @@ -4,3 +4,7 @@ ## multipart.parse_form :::multipart.multipart.parse_form + +## multipart.create_form_parser + +:::multipart.multipart.create_form_parser diff --git a/docs/main-class.md b/docs/main-class.md index e69de29..e493822 100644 --- a/docs/main-class.md +++ b/docs/main-class.md @@ -0,0 +1,8 @@ + +# Main Class + + + +:::multipart.multipart.FormParser diff --git a/docs/parsers.md b/docs/parsers.md index e69de29..0b707e1 100644 --- a/docs/parsers.md +++ b/docs/parsers.md @@ -0,0 +1,8 @@ + +# Parsers + + + +:::multipart.multipart.BaseParser diff --git a/docs/support-classes.md b/docs/support-classes.md new file mode 100644 index 0000000..e12d277 --- /dev/null +++ b/docs/support-classes.md @@ -0,0 +1,7 @@ +# Support Classes + +
+

Field

+
+ +:::multipart.multipart.Field diff --git a/docs/support-classess.md b/docs/support-classess.md deleted file mode 100644 index e69de29..0000000 From 46b67b2ebe2289157aecd629e80531067b1f9dea Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Sun, 11 Feb 2024 20:22:06 +0300 Subject: [PATCH 07/10] =?UTF-8?q?fix:=20=F0=9F=90=9E=20package=20typo=20fi?= =?UTF-8?q?x?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 400a26b..1fab76e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ doc = [ "mkdocstrings==0.24.0", "mkdocstrings-python==1.8.0", "mkdocs-autorefs=0.5.0", - "mkdocs-git-committers-plugin==2-2.2.3" + "mkdocs-git-committers-plugin-2==2.2.3" ] [project.urls] From 843f3290beda9b8668fcf323f2d14339c3f62a5f Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Sun, 11 Feb 2024 20:38:09 +0300 Subject: [PATCH 08/10] =?UTF-8?q?docs:=20=E2=9C=A8=20home=20page,=20quicks?= =?UTF-8?q?tart=20sections=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- docs/index.md | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/docs/index.md b/docs/index.md index e69de29..0c7cff0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -0,0 +1,185 @@ +--- +hide: + - navigation +--- + +# Python-Multipart + +Python-Multipart is a streaming multipart parser for Python. + +## Quickstart + +Python-Multipart foo bar baz + +### Simple Example + +The following example shows a quick example of parsing an incoming request body in a simple WSGI application: + +```python +import multipart + +def simple_app(environ, start_response): + ret = [] + + # The following two callbacks just append the name to the return value. + def on_field(field): + ret.append(b"Parsed field named: %s" % (field.field_name,)) + + def on_file(file): + ret.append(b"Parsed file named: %s" % (file.field_name,)) + + # Create headers object. We need to convert from WSGI to the actual + # name of the header, since this library does not assume that you are + # using WSGI. + headers = {'Content-Type': environ['CONTENT_TYPE']} + if 'HTTP_X_FILE_NAME' in environ: + headers['X-File-Name'] = environ['HTTP_X_FILE_NAME'] + if 'CONTENT_LENGTH' in environ: + headers['Content-Length'] = environ['CONTENT_LENGTH'] + + # Parse the form. + multipart.parse_form(headers, environ['wsgi.input'], on_field, on_file) + + # Return something. + start_response('200 OK', [('Content-type', 'text/plain')]) + ret.append(b'\n') + return ret + +from wsgiref.simple_server import make_server +from wsgiref.validate import validator + +httpd = make_server('', 8123, simple_app) +print("Serving on port 8123...") +httpd.serve_forever() +``` + +If you test this with curl, you can see that the parser works: + +```console +$ curl -ik -F "foo=bar" http://localhost:8123/ +HTTP/1.0 200 OK +Date: Sun, 07 Apr 2013 01:40:52 GMT +Server: WSGIServer/0.1 Python/2.7.3 +Content-type: text/plain + +Parsed field named: foo +``` + +For a more in-depth example showing how the various parts fit together, check out the next section. + +### In-Depth Example + +In this section, we’ll build an application that computes the SHA-256 hash of all uploaded files in a streaming manner. + +To start, we need a simple WSGI application. We could do this with a framework like Flask, Django, or Tornado, but for now let’s stick to plain WSGI: + +```python +import multipart + +def simple_app(environ, start_response): + start_response('200 OK', [('Content-type', 'text/plain')]) + return ['Hashes:\n'] + +from wsgiref.simple_server import make_server +httpd = make_server('', 8123, simple_app) +print("Serving on port 8123...") +httpd.serve_forever() +``` + +You can run this and check with curl that it works properly: + +```console +$ curl -ik http://localhost:8123/ +HTTP/1.0 200 OK +Date: Sun, 07 Apr 2013 01:49:03 GMT +Server: WSGIServer/0.1 Python/2.7.3 +Content-type: text/plain +Content-Length: 8 + +Hashes: +``` + +Good! It works. Now, let’s add some of the code that we need. What we need to do, essentially, is set up the appropriate parser and callbacks so that we can access each portion of the request as it arrives, without needing to store any parts in memory. + +We can start off by checking if we need to create the parser at all - if the Content-Type isn’t multipart/form-data, then we’re not going to do anything. + +The final code should look like this: + +```python +import hashlib +import multipart +from multipart.multipart import parse_options_header + +def simple_app(environ, start_response): + ret = [] + + # Python 2 doesn't have the "nonlocal" keyword from Python 3, so we get + # around it by setting attributes on a dummy object. + class g(object): + hash = None + + # This is called when a new part arrives. We create a new hash object + # in this callback. + def on_part_begin(): + g.hash = hashlib.sha256() + + # We got some data! Update our hash. + def on_part_data(data, start, end): + g.hash.update(data[start:end]) + + # Our current part is done, so we can finish the hash. + def on_part_end(): + ret.append("Part hash: %s" % (g.hash.hexdigest(),)) + + # Parse the Content-Type header to get the multipart boundary. + content_type, params = parse_options_header(environ['CONTENT_TYPE']) + boundary = params.get(b'boundary') + + # Callbacks dictionary. + callbacks = { + 'on_part_begin': on_part_begin, + 'on_part_data': on_part_data, + 'on_part_end': on_part_end, + } + + # Create the parser. + parser = multipart.MultipartParser(boundary, callbacks) + + # The input stream is from the WSGI environ. + inp = environ['wsgi.input'] + + # Feed the parser with data from the request. + size = int(environ['CONTENT_LENGTH']) + while size > 0: + to_read = min(size, 1024 * 1024) + data = inp.read(to_read) + parser.write(data) + + size -= len(data) + if len(data) != to_read: + break + + start_response('200 OK', [('Content-type', 'text/plain')]) + return ret + +from wsgiref.simple_server import make_server +httpd = make_server('', 8123, simple_app) +print("Serving on port 8123...") +httpd.serve_forever() +``` + +And you can see that this works: + +```console +$ echo "Foo bar" > /tmp/test.txt +$ shasum -a 256 /tmp/test.txt +0b64696c0f7ddb9e3435341720988d5455b3b0f0724688f98ec8e6019af3d931 /tmp/test.txt +$ curl -ik -F file=@/tmp/test.txt http://localhost:8123/ +HTTP/1.0 200 OK +Date: Sun, 07 Apr 2013 02:09:10 GMT +Server: WSGIServer/0.1 Python/2.7.3 +Content-type: text/plain + +Hashes: +Part hash: 0b64696c0f7ddb9e3435341720988d5455b3b0f0724688f98ec8e6019af3d931 +``` From 6e1f3c5713ba4b3200559d76977a0741227f2761 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Sun, 11 Feb 2024 20:49:49 +0300 Subject: [PATCH 09/10] =?UTF-8?q?ci:=20=F0=9F=91=B7=20docs=20workflow=20ad?= =?UTF-8?q?ded?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- .github/workflows/docs.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/docs.yaml diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 0000000..b9379c8 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,26 @@ +name: 📚 Docs WorkFlow 📚 + +on: + push: + branches: + - master + +permissions: + contents: write + pages: write + pull-requests: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: 🔄 Checking out code + uses: actions/checkout@v4 + - name: 🐍 Setting up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: 📦 Installing dependencies + run: pip install mkdocs mkdocs-material mkdocs-git-authors-plugin mkdocs-git-revision-date-localized-plugin mkdocs-material-extensions pymdown-extensions mkdocstrings mkdocstrings-python mkdocs-autorefs mkdocs-git-committers-plugin-2 + - name: 🚀 Deploying docs + run: mkdocs gh-deploy --force \ No newline at end of file From f37d15ef695e40a07299293f264d4753d672a68d Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sun, 11 Feb 2024 21:19:05 +0100 Subject: [PATCH 10/10] Apply my opinion --- .github/workflows/docs.yaml | 26 ----------- .github/workflows/{main.yaml => main.yml} | 0 .../workflows/{publish.yaml => publish.yml} | 6 +-- docs/api.md | 1 + docs/decoders.md | 7 --- docs/exceptions.md | 45 ------------------- docs/helper-functions.md | 10 ----- docs/index.md | 7 --- docs/license.md | 5 --- docs/main-class.md | 8 ---- docs/parsers.md | 8 ---- docs/support-classes.md | 7 --- mkdocs.yml | 39 ++++------------ pyproject.toml | 9 +--- 14 files changed, 14 insertions(+), 164 deletions(-) delete mode 100644 .github/workflows/docs.yaml rename .github/workflows/{main.yaml => main.yml} (100%) rename .github/workflows/{publish.yaml => publish.yml} (91%) create mode 100644 docs/api.md delete mode 100644 docs/decoders.md delete mode 100644 docs/exceptions.md delete mode 100644 docs/helper-functions.md delete mode 100644 docs/license.md delete mode 100644 docs/main-class.md delete mode 100644 docs/parsers.md delete mode 100644 docs/support-classes.md diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml deleted file mode 100644 index b9379c8..0000000 --- a/.github/workflows/docs.yaml +++ /dev/null @@ -1,26 +0,0 @@ -name: 📚 Docs WorkFlow 📚 - -on: - push: - branches: - - master - -permissions: - contents: write - pages: write - pull-requests: write - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: 🔄 Checking out code - uses: actions/checkout@v4 - - name: 🐍 Setting up Python - uses: actions/setup-python@v5 - with: - python-version: 3.x - - name: 📦 Installing dependencies - run: pip install mkdocs mkdocs-material mkdocs-git-authors-plugin mkdocs-git-revision-date-localized-plugin mkdocs-material-extensions pymdown-extensions mkdocstrings mkdocstrings-python mkdocs-autorefs mkdocs-git-committers-plugin-2 - - name: 🚀 Deploying docs - run: mkdocs gh-deploy --force \ No newline at end of file diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yml similarity index 100% rename from .github/workflows/main.yaml rename to .github/workflows/main.yml diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yml similarity index 91% rename from .github/workflows/publish.yaml rename to .github/workflows/publish.yml index cc38611..9a96079 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yml @@ -26,17 +26,17 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.10" - - name: Install dependencies run: | python -m pip install --upgrade pip pip install build - + pip install -e '.[docs]' - name: Build package run: python -m build - - name: Publish package uses: pypa/gh-action-pypi-publish@v1.8.11 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} + - name: Publish docs + run: mkdocs gh-deploy --force diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..5e833b6 --- /dev/null +++ b/docs/api.md @@ -0,0 +1 @@ +:::multipart diff --git a/docs/decoders.md b/docs/decoders.md deleted file mode 100644 index 349597c..0000000 --- a/docs/decoders.md +++ /dev/null @@ -1,7 +0,0 @@ -# Decoders - - - -:::multipart.decoders.Base64Decoder diff --git a/docs/exceptions.md b/docs/exceptions.md deleted file mode 100644 index abc2b2a..0000000 --- a/docs/exceptions.md +++ /dev/null @@ -1,45 +0,0 @@ -# Exceptions - -The following are all custom exceptions that python-multipart will raise, for various cases. Each method that will raise an exception will document it in this documentation. - - - -:::multipart.exceptions.DecodeError -This exception is raised when there is a decoding error - for example with the Base64Decoder or QuotedPrintableDecoder. - - - -:::multipart.exceptions.FileError -Exception class for problems with the File class. - - - -:::multipart.exceptions.FormParserError -Base error class for our form parser. - - - -:::multipart.exceptions.MultipartParseError - - - -:::multipart.exceptions.ParseError - -offset = -1 -This is the offset in the input data chunk (NOT the overall stream) in which the parse error occured. It will be -1 if not specified. - - - -:::multipart.exceptions.QuerystringParseError diff --git a/docs/helper-functions.md b/docs/helper-functions.md deleted file mode 100644 index 289984c..0000000 --- a/docs/helper-functions.md +++ /dev/null @@ -1,10 +0,0 @@ - -# Helper Functions - -## multipart.parse_form - -:::multipart.multipart.parse_form - -## multipart.create_form_parser - -:::multipart.multipart.create_form_parser diff --git a/docs/index.md b/docs/index.md index 0c7cff0..0640374 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,16 +1,9 @@ ---- -hide: - - navigation ---- - # Python-Multipart Python-Multipart is a streaming multipart parser for Python. ## Quickstart -Python-Multipart foo bar baz - ### Simple Example The following example shows a quick example of parsing an incoming request body in a simple WSGI application: diff --git a/docs/license.md b/docs/license.md deleted file mode 100644 index 03843e9..0000000 --- a/docs/license.md +++ /dev/null @@ -1,5 +0,0 @@ -# License - -``` ---8<-- "LICENSE.txt" -``` diff --git a/docs/main-class.md b/docs/main-class.md deleted file mode 100644 index e493822..0000000 --- a/docs/main-class.md +++ /dev/null @@ -1,8 +0,0 @@ - -# Main Class - - - -:::multipart.multipart.FormParser diff --git a/docs/parsers.md b/docs/parsers.md deleted file mode 100644 index 0b707e1..0000000 --- a/docs/parsers.md +++ /dev/null @@ -1,8 +0,0 @@ - -# Parsers - - - -:::multipart.multipart.BaseParser diff --git a/docs/support-classes.md b/docs/support-classes.md deleted file mode 100644 index e12d277..0000000 --- a/docs/support-classes.md +++ /dev/null @@ -1,7 +0,0 @@ -# Support Classes - -
-

Field

-
- -:::multipart.multipart.Field diff --git a/mkdocs.yml b/mkdocs.yml index ba56080..e4b624d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,11 +1,10 @@ -site_name: python-multipart +site_name: Python Multipart site_url: https://kludex.github.io/python-multipart/ site_author: Marcelo Trylesinski,Andrew Dunham site_description: A streaming multipart parser for Python. repo_name: kludex/python-multipart repo_url: https://github.com//kludex/python-multipart edit_uri: https://github.com/kludex/python-multipart/tree/master/docs -copyright: Python-multipart 2024. All rights reserved. extra: social: @@ -14,32 +13,14 @@ extra: - icon: fontawesome/brands/python link: https://pypi.org/project/python-multipart - - - -nav: - - Home: index.md - - API: - - Helper-functions: helper-functions.md - - Main-class: main-class.md - - Parsers: parsers.md - - Support-classes: support-classes.md - - Decoders: decoders.md - - Exceptions: exceptions.md - - Contribute: - - License: license.md - - Changelog: changelog.md - - theme: - name: 'material' - custom_dir: docs/theme + name: "material" features: - navigation.tracking - content.code.copy - content.tooltips - content.code.annotate - - navigation.tabs + - navigation.sections palette: # Palette for light mode @@ -60,6 +41,11 @@ theme: - content.code.copy - content.code.annotate +nav: + - Introduction: index.md + - API Reference: api.md + - Changelog: changelog.md + plugins: - search - mkdocstrings: @@ -78,14 +64,6 @@ plugins: show_symbol_type_toc: true show_category_heading: true domains: [std, py] - - git-committers: - repository: kludex/python-multipart - branch: develop - token: !ENV ["GITHUB_TOKEN"] - - git-revision-date-localized: - enable_creation_date: true - - markdown_extensions: - admonition @@ -104,4 +82,3 @@ markdown_extensions: check_paths: true - pymdownx.highlight: anchor_linenums: true - diff --git a/pyproject.toml b/pyproject.toml index 1fab76e..13f9c28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,17 +47,12 @@ dev = [ "ruff==0.2.1", "hatch", ] -doc = [ +docs = [ "mkdocs==1.5.3", "mkdocs-material==9.5.9", - "mkdocs-git-authors-plugin==0.7.2", - "mkdocs-git-revision-date-localized-plugin==1.2.4", - "mkdocs-material-extensions==1.3.1", - "pymdown-extensions==10.7", "mkdocstrings==0.24.0", "mkdocstrings-python==1.8.0", - "mkdocs-autorefs=0.5.0", - "mkdocs-git-committers-plugin-2==2.2.3" + "mkdocs-autorefs==0.5.0", ] [project.urls]