diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index 9de5690..c0bb923 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -37,7 +37,7 @@ jobs: python -m pip install build - name: build the dist files - run: | + run: | python -m build . - name: Upload the dist files diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 1e2c168..4c8b45d 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -62,4 +62,4 @@ jobs: python -m pip install pytest - name: Run the tests run: | - pytest . \ No newline at end of file + pytest . diff --git a/docs/_themes/flask_small/static/flasky.css_t b/docs/_themes/flask_small/static/flasky.css_t index fe2141c..71961a2 100644 --- a/docs/_themes/flask_small/static/flasky.css_t +++ b/docs/_themes/flask_small/static/flasky.css_t @@ -8,11 +8,11 @@ * :license: BSD, see LICENSE for details. * */ - + @import url("basic.css"); - + /* -- page layout ----------------------------------------------------------- */ - + body { font-family: 'Georgia', serif; font-size: 17px; @@ -35,7 +35,7 @@ div.bodywrapper { hr { border: 1px solid #B1B4B6; } - + div.body { background-color: #ffffff; color: #3E4349; @@ -46,7 +46,7 @@ img.floatingflask { padding: 0 0 10px 10px; float: right; } - + div.footer { text-align: right; color: #888; @@ -55,12 +55,12 @@ div.footer { width: 650px; margin: 0 auto 40px auto; } - + div.footer a { color: #888; text-decoration: underline; } - + div.related { line-height: 32px; color: #888; @@ -69,18 +69,18 @@ div.related { 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; @@ -89,7 +89,7 @@ a:hover { div.body { padding-bottom: 40px; /* saved for footer */ } - + div.body h1, div.body h2, div.body h3, @@ -109,24 +109,24 @@ div.indexwrapper h1 { 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; } @@ -164,25 +164,25 @@ 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: ":"; } @@ -254,7 +254,7 @@ dl { dl dd { margin-left: 30px; } - + pre { padding: 0; margin: 15px -30px; diff --git a/docs/_themes/flask_theme_support.py b/docs/_themes/flask_theme_support.py index f14925d..3192864 100644 --- a/docs/_themes/flask_theme_support.py +++ b/docs/_themes/flask_theme_support.py @@ -1,7 +1,21 @@ # flasky extensions. flasky pygments style based on tango style +from __future__ import annotations + from pygments.style import Style -from pygments.token import Keyword, Name, Comment, String, Error, \ - Number, Operator, Generic, Whitespace, Punctuation, Other, Literal +from pygments.token import ( + Comment, + Error, + Generic, + Keyword, + Literal, + Name, + Number, + Operator, + Other, + Punctuation, + String, + Whitespace, +) class FlaskyStyle(Style): @@ -10,77 +24,68 @@ class FlaskyStyle(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" - + # 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" + 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/conf.py b/docs/conf.py index a1823a4..6e17794 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Flask-PyMongo documentation build configuration file, created by # sphinx-quickstart on Mon Dec 26 10:16:15 2011. @@ -10,8 +9,11 @@ # # All configuration values have a default; values that are commented out # serve to show the default. +from __future__ import annotations + +import os +import sys -import sys, os sys.path.insert(0, os.path.abspath("..")) from flask_pymongo._version import __version__ @@ -19,12 +21,12 @@ # 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(".")) +# sys.path.insert(0, os.path.abspath(".")) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = "1.0" +# 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. @@ -37,14 +39,14 @@ source_suffix = ".rst" # The encoding of source files. -#source_encoding = "utf-8-sig" +# source_encoding = "utf-8-sig" # The master toctree document. master_doc = "index" # General information about the project. -project = u"Flask-PyMongo" -copyright = u"2011 - 2017, Dan Crosta" +project = "Flask-PyMongo" +copyright = "2011 - 2017, Dan Crosta" # The version info for the project you"re documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -58,37 +60,37 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = "" +# today = "" # Else, today_fmt is used as the format for a strftime call. -#today_fmt = "%B %d, %Y" +# 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 +# default_role = None # If true, "()" will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# 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 +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# 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 = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- @@ -100,7 +102,7 @@ # 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 = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. sys.path.append(os.path.abspath("_themes")) @@ -108,25 +110,25 @@ # custom settings for flask theme html_theme_options = { - "index_logo": "", #TODO + "index_logo": "", # TODO "github_fork": "dcrosta/flask-pymongo", } # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# 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 +# 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 +# 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, @@ -135,44 +137,44 @@ # 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" +# 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 +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = 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 = "" +# html_use_opensearch = "" # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = "Flask-PyMongodoc" @@ -181,42 +183,45 @@ # -- 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": "", + # 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", "Flask-PyMongo.tex", u"Flask-PyMongo Documentation", - u"Dan Crosta", "manual"), + ( + "index", + "Flask-PyMongo.tex", + "Flask-PyMongo Documentation", + "Dan Crosta", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- @@ -224,12 +229,11 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ("index", "flask-pymongo", u"Flask-PyMongo Documentation", - [u"Dan Crosta"], 1) + ("index", "flask-pymongo", "Flask-PyMongo Documentation", ["Dan Crosta"], 1) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ @@ -238,19 +242,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ("index", "Flask-PyMongo", u"Flask-PyMongo Documentation", - u"Dan Crosta", "Flask-PyMongo", "One line description of project.", - "Miscellaneous"), + ( + "index", + "Flask-PyMongo", + "Flask-PyMongo Documentation", + "Dan Crosta", + "Flask-PyMongo", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: "footnote", "no", or "inline". -#texinfo_show_urls = "footnote" +# texinfo_show_urls = "footnote" # Example configuration for intersphinx: refer to the Python standard library. diff --git a/examples/wiki/wiki.py b/examples/wiki/wiki.py index 5e2ce26..67008af 100644 --- a/examples/wiki/wiki.py +++ b/examples/wiki/wiki.py @@ -1,7 +1,11 @@ +from __future__ import annotations + +import re + +import markdown2 from flask import Flask, redirect, render_template, request, url_for + from flask_pymongo import PyMongo -import markdown2 -import re app = Flask(__name__) mongo = PyMongo(app, "mongodb://localhost/wiki") @@ -9,14 +13,17 @@ WIKIPART = re.compile(r"([A-Z][a-z0-9_]+)") WIKIWORD = re.compile(r"([A-Z][a-z0-9_]+(?:[A-Z][a-z0-9_]+)+)") + @app.route("/", methods=["GET"]) def redirect_to_homepage(): return redirect(url_for("show_page", pagepath="HomePage")) + @app.template_filter() def totitle(value): return " ".join(WIKIPART.findall(value)) + @app.template_filter() def wikify(value): parts = WIKIWORD.split(value) @@ -30,17 +37,14 @@ def wikify(value): @app.route("/") def show_page(pagepath): page = mongo.db.pages.find_one_or_404({"_id": pagepath}) - return render_template("page.html", - page=page, - pagepath=pagepath) + return render_template("page.html", page=page, pagepath=pagepath) @app.route("/edit/", methods=["GET"]) def edit_page(pagepath): page = mongo.db.pages.find_one_or_404({"_id": pagepath}) - return render_template("edit.html", - page=page, - pagepath=pagepath) + return render_template("edit.html", page=page, pagepath=pagepath) + @app.route("/edit/", methods=["POST"]) def save_page(pagepath): @@ -48,22 +52,26 @@ def save_page(pagepath): mongo.db.pages.update( {"_id": pagepath}, {"$set": {"body": request.form["body"]}}, - w=1, upsert=True) + w=1, + upsert=True, + ) return redirect(url_for("show_page", pagepath=pagepath)) + @app.errorhandler(404) def new_page(error): pagepath = request.path.lstrip("/") if pagepath.startswith("uploads"): - filename = pagepath[len("uploads"):].lstrip("/") + filename = pagepath[len("uploads") :].lstrip("/") return render_template("upload.html", filename=filename) - else: - return render_template("edit.html", page=None, pagepath=pagepath) + return render_template("edit.html", page=None, pagepath=pagepath) + @app.route("/uploads/") def get_upload(filename): return mongo.send_file(filename) + @app.route("/uploads/", methods=["POST"]) def save_upload(filename): if request.files.get("file"): @@ -71,7 +79,9 @@ def save_upload(filename): return redirect(url_for("get_upload", filename=filename)) return render_template("upload.html", filename=filename) + if __name__ == "__main__": import doctest + doctest.testmod() app.run(debug=True) diff --git a/flask_pymongo/__init__.py b/flask_pymongo/__init__.py index f60400a..ccb4926 100644 --- a/flask_pymongo/__init__.py +++ b/flask_pymongo/__init__.py @@ -22,19 +22,20 @@ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. - +from __future__ import annotations __all__ = ("PyMongo", "ASCENDING", "DESCENDING") +import hashlib from functools import partial from mimetypes import guess_type -import hashlib +import pymongo from flask import abort, current_app, request from gridfs import GridFS, NoFile from pymongo import uri_parser from werkzeug.wsgi import wrap_file -import pymongo + # DriverInfo was added in PyMongo 3.7 try: from pymongo.driver_info import DriverInfo @@ -52,8 +53,7 @@ """Ascending sort order.""" -class PyMongo(object): - +class PyMongo: """Manages MongoDB connections for your Flask app. PyMongo objects provide access to the MongoDB server via the :attr:`db` @@ -66,7 +66,7 @@ class PyMongo(object): """ - def __init__(self, app=None, uri=None, *args, **kwargs): + def __init__(self, app=None, uri=None, *args, **kwargs): self.cx = None self.db = None self._json_provider = BSONProvider(app) @@ -170,7 +170,7 @@ def get_upload(filename): mimetype=content_type, direct_passthrough=True, ) - response.headers['Content-Disposition'] = 'attachment; filename={}'.format(filename) + response.headers["Content-Disposition"] = f"attachment; filename={filename}" response.content_length = fileobj.length response.last_modified = fileobj.upload_date # Compute the sha1 sum of the file for the etag. @@ -213,5 +213,7 @@ def save_upload(filename): content_type, _ = guess_type(filename) storage = GridFS(self.db, base) - id = storage.put(fileobj, filename=filename, content_type=content_type, **kwargs) + id = storage.put( + fileobj, filename=filename, content_type=content_type, **kwargs + ) return id diff --git a/flask_pymongo/helpers.py b/flask_pymongo/helpers.py index 8cdda23..42f5c3d 100644 --- a/flask_pymongo/helpers.py +++ b/flask_pymongo/helpers.py @@ -22,31 +22,28 @@ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. - +from __future__ import annotations __all__ = ("BSONObjectIdConverter", "BSONProvider") from bson import json_util from bson.errors import InvalidId +from bson.json_util import RELAXED_JSON_OPTIONS from bson.objectid import ObjectId from flask import abort from flask.json.provider import JSONProvider from werkzeug.routing import BaseConverter -import pymongo -from bson.json_util import RELAXED_JSON_OPTIONS def _iteritems(obj): if hasattr(obj, "iteritems"): return obj.iteritems() - elif hasattr(obj, "items"): + if hasattr(obj, "items"): return obj.items() - else: - raise TypeError("{!r} missing iteritems() and items()".format(obj)) + raise TypeError(f"{obj!r} missing iteritems() and items()") class BSONObjectIdConverter(BaseConverter): - """A simple converter for the RESTful URL routing system of Flask. .. code-block:: python @@ -79,7 +76,6 @@ def to_url(self, value): class BSONProvider(JSONProvider): - """A JSON encoder that uses :mod:`bson.json_util` for MongoDB documents. .. code-block:: python @@ -99,7 +95,7 @@ def json_route(cart_id): A :class:`~flask_pymongo.helpers.JSONProvider` is automatically automatically installed on the :class:`~flask_pymongo.PyMongo` instance at creation time, using - :const:`~bson.json_util.RELAXED_JSON_OPTIONS`. + :const:`~bson.json_util.RELAXED_JSON_OPTIONS`. """ def __init__(self, app): @@ -108,11 +104,9 @@ def __init__(self, app): super().__init__(app) def dumps(self, obj): - """Serialize MongoDB object types using :mod:`bson.json_util`. - """ + """Serialize MongoDB object types using :mod:`bson.json_util`.""" return json_util.dumps(obj) def loads(self, str_obj): - """Deserialize MongoDB object types using :mod:`bson.json_util`. - """ - return json_util.loads(str_obj) \ No newline at end of file + """Deserialize MongoDB object types using :mod:`bson.json_util`.""" + return json_util.loads(str_obj) diff --git a/flask_pymongo/tests/test_config.py b/flask_pymongo/tests/test_config.py index f2f5e5e..66ab157 100644 --- a/flask_pymongo/tests/test_config.py +++ b/flask_pymongo/tests/test_config.py @@ -1,11 +1,13 @@ -from contextlib import contextmanager +from __future__ import annotations + import time +from contextlib import contextmanager import pymongo import pytest -from flask_pymongo.tests.util import FlaskRequestTest import flask_pymongo +from flask_pymongo.tests.util import FlaskRequestTest class CouldNotConnect(Exception): @@ -17,11 +19,10 @@ def doesnt_raise(exc=BaseException): try: yield except exc: - pytest.fail("{} was raised but should not have been".format(exc)) + pytest.fail(f"{exc} was raised but should not have been") class FlaskPyMongoConfigTest(FlaskRequestTest): - def setUp(self): super(FlaskPyMongoConfigTest, self).setUp() @@ -37,23 +38,29 @@ def tearDown(self): conn.drop_database(self.dbname + "2") def test_config_with_uri_in_flask_conf_var(self): - uri = "mongodb://localhost:{}/{}".format(self.port, self.dbname) + uri = f"mongodb://localhost:{self.port}/{self.dbname}" self.app.config["MONGO_URI"] = uri mongo = flask_pymongo.PyMongo(self.app, connect=True) _wait_until_connected(mongo) assert mongo.db.name == self.dbname - assert ("localhost", self.port) == mongo.cx.address or ("127.0.0.1", self.port) == mongo.cx.address + assert ("localhost", self.port) == mongo.cx.address or ( + "127.0.0.1", + self.port, + ) == mongo.cx.address def test_config_with_uri_passed_directly(self): - uri = "mongodb://localhost:{}/{}".format(self.port, self.dbname) + uri = f"mongodb://localhost:{self.port}/{self.dbname}" mongo = flask_pymongo.PyMongo(self.app, uri, connect=True) _wait_until_connected(mongo) assert mongo.db.name == self.dbname - assert ("localhost", self.port) == mongo.cx.address or ("127.0.0.1", self.port) == mongo.cx.address + assert ("localhost", self.port) == mongo.cx.address or ( + "127.0.0.1", + self.port, + ) == mongo.cx.address def test_it_fails_with_no_uri(self): self.app.config.pop("MONGO_URI", None) @@ -62,7 +69,7 @@ def test_it_fails_with_no_uri(self): flask_pymongo.PyMongo(self.app) def test_multiple_pymongos(self): - uri1 = "mongodb://localhost:{}/{}".format(self.port, self.dbname) + uri1 = f"mongodb://localhost:{self.port}/{self.dbname}" uri2 = "mongodb://localhost:{}/{}".format(self.port, self.dbname + "2") mongo1 = flask_pymongo.PyMongo(self.app, uri1) # noqa: F841 unused variable @@ -74,7 +81,7 @@ def test_custom_document_class(self): class CustomDict(dict): pass - uri = "mongodb://localhost:{}/{}".format(self.port, self.dbname) + uri = f"mongodb://localhost:{self.port}/{self.dbname}" mongo = flask_pymongo.PyMongo(self.app, uri, document_class=CustomDict) assert mongo.db.things.find_one() is None, "precondition failed" @@ -83,7 +90,7 @@ class CustomDict(dict): assert type(mongo.db.things.find_one()) == CustomDict def test_it_doesnt_connect_by_default(self): - uri = "mongodb://localhost:{}/{}".format(self.port, self.dbname) + uri = f"mongodb://localhost:{self.port}/{self.dbname}" mongo = flask_pymongo.PyMongo(self.app, uri) @@ -91,7 +98,7 @@ def test_it_doesnt_connect_by_default(self): _wait_until_connected(mongo, timeout=0.2) def test_it_doesnt_require_db_name_in_uri(self): - uri = "mongodb://localhost:{}".format(self.port) + uri = f"mongodb://localhost:{self.port}" with doesnt_raise(Exception): mongo = flask_pymongo.PyMongo(self.app, uri) diff --git a/flask_pymongo/tests/test_gridfs.py b/flask_pymongo/tests/test_gridfs.py index ff742c5..9866307 100644 --- a/flask_pymongo/tests/test_gridfs.py +++ b/flask_pymongo/tests/test_gridfs.py @@ -1,16 +1,17 @@ +from __future__ import annotations + from hashlib import sha1 from io import BytesIO +import pytest from bson.objectid import ObjectId from gridfs import GridFS from werkzeug.exceptions import NotFound -import pytest from flask_pymongo.tests.util import FlaskPyMongoTest -class GridFSCleanupMixin(object): - +class GridFSCleanupMixin: def tearDown(self): gridfs = GridFS(self.mongo.db) files = list(gridfs.find()) @@ -21,7 +22,6 @@ def tearDown(self): class TestSaveFile(GridFSCleanupMixin, FlaskPyMongoTest): - def test_it_saves_files(self): fileobj = BytesIO(b"these are the bytes") @@ -48,7 +48,6 @@ def test_it_returns_id(self): class TestSendFile(GridFSCleanupMixin, FlaskPyMongoTest): - def setUp(self): super(TestSendFile, self).setUp() diff --git a/flask_pymongo/tests/test_json.py b/flask_pymongo/tests/test_json.py index 4f506d9..424d7b5 100644 --- a/flask_pymongo/tests/test_json.py +++ b/flask_pymongo/tests/test_json.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json from bson import ObjectId @@ -7,15 +9,14 @@ class JSONTest(FlaskPyMongoTest): - def test_it_encodes_json(self): resp = jsonify({"foo": "bar"}) - dumped = json.loads(resp.get_data().decode('utf-8')) + dumped = json.loads(resp.get_data().decode("utf-8")) self.assertEqual(dumped, {"foo": "bar"}) def test_it_handles_pymongo_types(self): resp = jsonify({"id": ObjectId("5cf29abb5167a14c9e6e12c4")}) - dumped = json.loads(resp.get_data().decode('utf-8')) + dumped = json.loads(resp.get_data().decode("utf-8")) self.assertEqual(dumped, {"id": {"$oid": "5cf29abb5167a14c9e6e12c4"}}) def test_it_jsonifies_a_cursor(self): @@ -24,5 +25,5 @@ def test_it_jsonifies_a_cursor(self): curs = self.mongo.db.rows.find(projection={"_id": False}).sort("foo") resp = jsonify(curs) - dumped = json.loads(resp.get_data().decode('utf-8')) + dumped = json.loads(resp.get_data().decode("utf-8")) self.assertEqual([{"foo": "bar"}, {"foo": "baz"}], dumped) diff --git a/flask_pymongo/tests/test_url_converter.py b/flask_pymongo/tests/test_url_converter.py index aeb349c..a7e4950 100644 --- a/flask_pymongo/tests/test_url_converter.py +++ b/flask_pymongo/tests/test_url_converter.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from bson import ObjectId from werkzeug.exceptions import NotFound @@ -6,12 +8,14 @@ class UrlConverterTest(FlaskPyMongoTest): - def test_bson_object_id_converter(self): converter = BSONObjectIdConverter("/") self.assertRaises(NotFound, converter.to_python, ("132")) - assert converter.to_python("4e4ac5cfffc84958fa1f45fb") == \ - ObjectId("4e4ac5cfffc84958fa1f45fb") - assert converter.to_url(ObjectId("4e4ac5cfffc84958fa1f45fb")) == \ + assert converter.to_python("4e4ac5cfffc84958fa1f45fb") == ObjectId( "4e4ac5cfffc84958fa1f45fb" + ) + assert ( + converter.to_url(ObjectId("4e4ac5cfffc84958fa1f45fb")) + == "4e4ac5cfffc84958fa1f45fb" + ) diff --git a/flask_pymongo/tests/test_wrappers.py b/flask_pymongo/tests/test_wrappers.py index 537887f..ca87b13 100644 --- a/flask_pymongo/tests/test_wrappers.py +++ b/flask_pymongo/tests/test_wrappers.py @@ -1,10 +1,11 @@ +from __future__ import annotations + from werkzeug.exceptions import HTTPException from flask_pymongo.tests.util import FlaskPyMongoTest class CollectionTest(FlaskPyMongoTest): - def test_find_one_or_404(self): self.mongo.db.things.delete_many({}) diff --git a/flask_pymongo/tests/util.py b/flask_pymongo/tests/util.py index b35cd9c..68c2a67 100644 --- a/flask_pymongo/tests/util.py +++ b/flask_pymongo/tests/util.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import unittest import flask @@ -5,9 +7,7 @@ import flask_pymongo - class FlaskRequestTest(unittest.TestCase): - def setUp(self): super(FlaskRequestTest, self).setUp() @@ -24,11 +24,10 @@ def tearDown(self): class FlaskPyMongoTest(FlaskRequestTest): - def setUp(self): super(FlaskPyMongoTest, self).setUp() - uri = "mongodb://localhost:{}/{}".format(self.port, self.dbname) + uri = f"mongodb://localhost:{self.port}/{self.dbname}" self.mongo = flask_pymongo.PyMongo(self.app, uri) def tearDown(self): diff --git a/flask_pymongo/wrappers.py b/flask_pymongo/wrappers.py index 5e82c54..7f6076d 100644 --- a/flask_pymongo/wrappers.py +++ b/flask_pymongo/wrappers.py @@ -22,15 +22,13 @@ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +from __future__ import annotations from flask import abort -from pymongo import collection -from pymongo import database -from pymongo import mongo_client +from pymongo import collection, database, mongo_client class MongoClient(mongo_client.MongoClient): - """Wrapper for :class:`~pymongo.mongo_client.MongoClient`. Returns instances of Flask-PyMongo @@ -39,13 +37,13 @@ class MongoClient(mongo_client.MongoClient): """ - def __getattr__(self, name): # noqa: D105 + def __getattr__(self, name): attr = super(MongoClient, self).__getattr__(name) if isinstance(attr, database.Database): return Database(self, name) return attr - def __getitem__(self, item): # noqa: D105 + def __getitem__(self, item): attr = super(MongoClient, self).__getitem__(item) if isinstance(attr, database.Database): return Database(self, item) @@ -53,7 +51,6 @@ def __getitem__(self, item): # noqa: D105 class Database(database.Database): - """Wrapper for :class:`~pymongo.database.Database`. Returns instances of Flask-PyMongo @@ -62,13 +59,13 @@ class Database(database.Database): """ - def __getattr__(self, name): # noqa: D105 + def __getattr__(self, name): attr = super(Database, self).__getattr__(name) if isinstance(attr, collection.Collection): return Collection(self, name) return attr - def __getitem__(self, item): # noqa: D105 + def __getitem__(self, item): item_ = super(Database, self).__getitem__(item) if isinstance(item_, collection.Collection): return Collection(self, item) @@ -76,19 +73,16 @@ def __getitem__(self, item): # noqa: D105 class Collection(collection.Collection): + """Sub-class of PyMongo :class:`~pymongo.collection.Collection` with helpers.""" - """Sub-class of PyMongo :class:`~pymongo.collection.Collection` with helpers. - - """ - - def __getattr__(self, name): # noqa: D105 + def __getattr__(self, name): attr = super(Collection, self).__getattr__(name) if isinstance(attr, collection.Collection): db = self._Collection__database return Collection(db, attr.name) return attr - def __getitem__(self, item): # noqa: D105 + def __getitem__(self, item): item_ = super(Collection, self).__getitem__(item) if isinstance(item_, collection.Collection): db = self._Collection__database