diff --git a/apps/_dashboard/diff2kryten.py b/apps/_dashboard/diff2kryten.py index 8ce9332bc..22866c692 100644 --- a/apps/_dashboard/diff2kryten.py +++ b/apps/_dashboard/diff2kryten.py @@ -1,6 +1,5 @@ import sys - # Note, when changing the Highlight.js css, # the background color of .file .diff should match the # background in the .hljs class in gitlog.min.css diff --git a/apps/_dashboard/utils.py b/apps/_dashboard/utils.py index 312a5aeb3..3a602b130 100644 --- a/apps/_dashboard/utils.py +++ b/apps/_dashboard/utils.py @@ -9,14 +9,13 @@ --------------- """ +import glob +import gzip +import logging import os import re -import tarfile -import glob import shutil -import logging -import gzip - +import tarfile __all__ = ( "safe_join", diff --git a/apps/_default/__init__.py b/apps/_default/__init__.py index a6344d6aa..49d739257 100644 --- a/apps/_default/__init__.py +++ b/apps/_default/__init__.py @@ -1,5 +1,7 @@ import os -from py4web import action, Cache + +from py4web import Cache, action + cache = Cache(size=1000) @action("index") diff --git a/apps/_documentation/__init__.py b/apps/_documentation/__init__.py index ac8e1bb0c..e73d11518 100644 --- a/apps/_documentation/__init__.py +++ b/apps/_documentation/__init__.py @@ -1,4 +1,4 @@ -from py4web import action, redirect, URL +from py4web import URL, action, redirect @action("index") diff --git a/apps/_scaffold/__init__.py b/apps/_scaffold/__init__.py index 91ea9f4bb..7a4f6e28c 100644 --- a/apps/_scaffold/__init__.py +++ b/apps/_scaffold/__init__.py @@ -3,15 +3,13 @@ assert py4web.check_compatible("0.1.20190709.1") +# by importing controllers you expose the actions defined in it +from . import controllers # by importing db you expose it to the _dashboard/dbadmin from .models import db - # import the scheduler from .tasks import scheduler -# by importing controllers you expose the actions defined in it -from . import controllers - # optional parameters __version__ = "0.0.0" __author__ = "you " diff --git a/apps/_scaffold/common.py b/apps/_scaffold/common.py index 428f7499c..ef8ac44e9 100644 --- a/apps/_scaffold/common.py +++ b/apps/_scaffold/common.py @@ -4,14 +4,17 @@ """ import os import sys -from py4web import Session, Cache, Translator, Flash, DAL, Field, action + +from pydal.tools.scheduler import Scheduler +from pydal.tools.tags import Tags + +from py4web import DAL, Cache, Field, Flash, Session, Translator, action from py4web.server_adapters.logging_utils import make_logger -from py4web.utils.mailer import Mailer from py4web.utils.auth import Auth from py4web.utils.downloader import downloader -from pydal.tools.tags import Tags -from pydal.tools.scheduler import Scheduler from py4web.utils.factories import ActionFactory +from py4web.utils.mailer import Mailer + from . import settings # ####################################################### @@ -56,7 +59,9 @@ session = Session(secret=settings.SESSION_SECRET_KEY, storage=conn) elif settings.SESSION_TYPE == "memcache": - import memcache, time + import time + + import memcache conn = memcache.Client(settings.MEMCACHE_CLIENTS, debug=0) session = Session(secret=settings.SESSION_SECRET_KEY, storage=conn) @@ -127,9 +132,8 @@ ) if settings.OAUTH2GOOGLE_SCOPED_CREDENTIALS_FILE: - from py4web.utils.auth_plugins.oauth2google_scoped import ( - OAuth2GoogleScoped, - ) # TESTED + from py4web.utils.auth_plugins.oauth2google_scoped import \ + OAuth2GoogleScoped # TESTED auth.register_plugin( OAuth2GoogleScoped( @@ -151,7 +155,8 @@ ) if settings.OAUTH2FACEBOOK_CLIENT_ID: - from py4web.utils.auth_plugins.oauth2facebook import OAuth2Facebook # UNTESTED + from py4web.utils.auth_plugins.oauth2facebook import \ + OAuth2Facebook # UNTESTED auth.register_plugin( OAuth2Facebook( diff --git a/apps/_scaffold/controllers.py b/apps/_scaffold/controllers.py index dafe2b793..7f7388264 100644 --- a/apps/_scaffold/controllers.py +++ b/apps/_scaffold/controllers.py @@ -25,19 +25,12 @@ Warning: Fixtures MUST be declared with @action.uses({fixtures}) else your app will result in undefined behavior """ -from py4web import action, request, abort, redirect, URL from yatl.helpers import A -from .common import ( - db, - session, - T, - cache, - auth, - logger, - authenticated, - unauthenticated, - flash, -) + +from py4web import URL, abort, action, redirect, request + +from .common import (T, auth, authenticated, cache, db, flash, logger, session, + unauthenticated) @action("index") diff --git a/apps/_scaffold/models.py b/apps/_scaffold/models.py index b896756b2..1b084b3e1 100644 --- a/apps/_scaffold/models.py +++ b/apps/_scaffold/models.py @@ -2,9 +2,10 @@ This file defines the database models """ -from .common import db, Field from pydal.validators import * +from .common import Field, db + ### Define your table below # # db.define_table('thing', Field('name')) diff --git a/apps/_scaffold/settings.py b/apps/_scaffold/settings.py index 0f81b1bf7..cd0330063 100644 --- a/apps/_scaffold/settings.py +++ b/apps/_scaffold/settings.py @@ -6,6 +6,7 @@ This file is provided as an example: """ import os + from py4web.core import required_folder # mode (default or development) diff --git a/apps/_scaffold/tasks.py b/apps/_scaffold/tasks.py index f061fdebb..6ccc5c6e8 100644 --- a/apps/_scaffold/tasks.py +++ b/apps/_scaffold/tasks.py @@ -1,4 +1,4 @@ -from .common import settings, scheduler +from .common import scheduler, settings from .models import db # ####################################################### diff --git a/apps/fadebook/__init__.py b/apps/fadebook/__init__.py index aecd78c30..c4e2e76da 100644 --- a/apps/fadebook/__init__.py +++ b/apps/fadebook/__init__.py @@ -3,11 +3,10 @@ assert py4web.check_compatible("0.1.20190709.1") -# by importing db you expose it to the _dashboard/dbadmin -from .models import db - # by importing controllers you expose the actions defined in it from . import controllers +# by importing db you expose it to the _dashboard/dbadmin +from .models import db # optional parameters __version__ = "0.0.0" diff --git a/apps/fadebook/common.py b/apps/fadebook/common.py index 428f7499c..ef8ac44e9 100644 --- a/apps/fadebook/common.py +++ b/apps/fadebook/common.py @@ -4,14 +4,17 @@ """ import os import sys -from py4web import Session, Cache, Translator, Flash, DAL, Field, action + +from pydal.tools.scheduler import Scheduler +from pydal.tools.tags import Tags + +from py4web import DAL, Cache, Field, Flash, Session, Translator, action from py4web.server_adapters.logging_utils import make_logger -from py4web.utils.mailer import Mailer from py4web.utils.auth import Auth from py4web.utils.downloader import downloader -from pydal.tools.tags import Tags -from pydal.tools.scheduler import Scheduler from py4web.utils.factories import ActionFactory +from py4web.utils.mailer import Mailer + from . import settings # ####################################################### @@ -56,7 +59,9 @@ session = Session(secret=settings.SESSION_SECRET_KEY, storage=conn) elif settings.SESSION_TYPE == "memcache": - import memcache, time + import time + + import memcache conn = memcache.Client(settings.MEMCACHE_CLIENTS, debug=0) session = Session(secret=settings.SESSION_SECRET_KEY, storage=conn) @@ -127,9 +132,8 @@ ) if settings.OAUTH2GOOGLE_SCOPED_CREDENTIALS_FILE: - from py4web.utils.auth_plugins.oauth2google_scoped import ( - OAuth2GoogleScoped, - ) # TESTED + from py4web.utils.auth_plugins.oauth2google_scoped import \ + OAuth2GoogleScoped # TESTED auth.register_plugin( OAuth2GoogleScoped( @@ -151,7 +155,8 @@ ) if settings.OAUTH2FACEBOOK_CLIENT_ID: - from py4web.utils.auth_plugins.oauth2facebook import OAuth2Facebook # UNTESTED + from py4web.utils.auth_plugins.oauth2facebook import \ + OAuth2Facebook # UNTESTED auth.register_plugin( OAuth2Facebook( diff --git a/apps/fadebook/controllers.py b/apps/fadebook/controllers.py index e6e09f480..d9bde0838 100644 --- a/apps/fadebook/controllers.py +++ b/apps/fadebook/controllers.py @@ -1,6 +1,7 @@ -from py4web import action, redirect, URL, Field, HTTP +from py4web import HTTP, URL, Field, action, redirect from py4web.utils.form import Form -from .common import flash, session, db, auth + +from .common import auth, db, flash, session from .make_up_data import make # diff --git a/apps/fadebook/make_up_data.py b/apps/fadebook/make_up_data.py index a0c40b76c..e1734de36 100644 --- a/apps/fadebook/make_up_data.py +++ b/apps/fadebook/make_up_data.py @@ -3,9 +3,10 @@ def make(): # prevent circular imports - from .common import db, action from py4web.utils.populate import populate + from .common import action, db + if db(db.auth_user).count() == 1: populate(db.auth_user, 10, contents={"is_active": True}) populate(db.feed_item, 100, contents={"is_active": True}) diff --git a/apps/fadebook/models.py b/apps/fadebook/models.py index 09eb7c1ba..2f44b7fd6 100644 --- a/apps/fadebook/models.py +++ b/apps/fadebook/models.py @@ -1,6 +1,7 @@ -from .common import * from pydal.validators import IS_NOT_EMPTY +from .common import * + db.define_table( "feed_item", Field("body", "text", requires=IS_NOT_EMPTY()), auth.signature ) diff --git a/apps/fadebook/settings.py b/apps/fadebook/settings.py index c881050fe..ff22e57f2 100644 --- a/apps/fadebook/settings.py +++ b/apps/fadebook/settings.py @@ -6,6 +6,7 @@ This file is provided as an example: """ import os + from py4web.core import required_folder # mode (default or development) diff --git a/apps/showcase/__init__.py b/apps/showcase/__init__.py index 9c3999dd0..b97dfeac2 100644 --- a/apps/showcase/__init__.py +++ b/apps/showcase/__init__.py @@ -20,11 +20,11 @@ from .examples.page_with_redirect import page_with_redirect, target from .examples.page_with_template import page_with_template from .examples.page_without_template import page_without_template +from .examples.rest import rest +from .examples.rpc import rpc from .examples.session_clear import session_clear from .examples.session_counter import session_counter from .examples.tagsinput_form import tagsinput_form -from .examples.rest import rest -from .examples.rpc import rpc from .vue_components_examples.vue_file_uploader import vue_file_uploader from .vue_components_examples.vue_grid import vue_grid from .vue_components_examples.vue_star_rater import vue_star_rater @@ -33,7 +33,6 @@ from .examples.auth_form import auth_form from .examples.auth_forms import auth_forms from .examples.create_form import create_form - from .examples.update_form import update_form from .examples.custom_form import custom_form from .examples.example_ajax_grid import example_ajax_grid from .examples.example_html_grid import example_html_grid @@ -43,11 +42,12 @@ from .examples.show_a_button import show_a_button from .examples.socketio import index as socketio from .examples.test_expose import test_expose1, test_expose2, test_expose3 - from .vue_components_examples.vue_view_form import vue_view_form - from .vue_components_examples.vue_insert_form import vue_insert_form + from .examples.update_form import update_form + from .examples.ws import index as ws from .vue_components_examples.vue_edit_form import vue_edit_form from .vue_components_examples.vue_grid_and_forms import vue_grid_and_forms - from .examples.ws import index as ws + from .vue_components_examples.vue_insert_form import vue_insert_form + from .vue_components_examples.vue_view_form import vue_view_form @action("index") diff --git a/apps/showcase/examples/example_html_grid.py b/apps/showcase/examples/example_html_grid.py index 52000459d..1a149d198 100644 --- a/apps/showcase/examples/example_html_grid.py +++ b/apps/showcase/examples/example_html_grid.py @@ -2,7 +2,7 @@ from yatl.helpers import A, I -from py4web import action, URL, redirect +from py4web import URL, action, redirect from py4web.utils.form import FormStyleDefault from py4web.utils.grid import Column, Grid, GridClassStyle diff --git a/apps/showcase/examples/rest.py b/apps/showcase/examples/rest.py index bdfa15897..67f7988a7 100644 --- a/apps/showcase/examples/rest.py +++ b/apps/showcase/examples/rest.py @@ -1,8 +1,9 @@ -import uuid import json -from py4web.core import action, request, response, Fixture -from .models import db, Field +import uuid + +from py4web.core import Fixture, action, request, response +from .models import Field, db # for demo purposes we create a table of tokens # in practice you want to link them to users diff --git a/apps/showcase/examples/rpc.py b/apps/showcase/examples/rpc.py index 38f923d2d..f662e5ebc 100644 --- a/apps/showcase/examples/rpc.py +++ b/apps/showcase/examples/rpc.py @@ -1,7 +1,8 @@ +import requests + from py4web import action, request from py4web.utils.jsonrpc import JsonRpc -import requests # define a function you want to expose def add(x, y): diff --git a/apps/tagged_posts/__init__.py b/apps/tagged_posts/__init__.py index aecd78c30..c4e2e76da 100644 --- a/apps/tagged_posts/__init__.py +++ b/apps/tagged_posts/__init__.py @@ -3,11 +3,10 @@ assert py4web.check_compatible("0.1.20190709.1") -# by importing db you expose it to the _dashboard/dbadmin -from .models import db - # by importing controllers you expose the actions defined in it from . import controllers +# by importing db you expose it to the _dashboard/dbadmin +from .models import db # optional parameters __version__ = "0.0.0" diff --git a/apps/tagged_posts/common.py b/apps/tagged_posts/common.py index 428f7499c..ef8ac44e9 100644 --- a/apps/tagged_posts/common.py +++ b/apps/tagged_posts/common.py @@ -4,14 +4,17 @@ """ import os import sys -from py4web import Session, Cache, Translator, Flash, DAL, Field, action + +from pydal.tools.scheduler import Scheduler +from pydal.tools.tags import Tags + +from py4web import DAL, Cache, Field, Flash, Session, Translator, action from py4web.server_adapters.logging_utils import make_logger -from py4web.utils.mailer import Mailer from py4web.utils.auth import Auth from py4web.utils.downloader import downloader -from pydal.tools.tags import Tags -from pydal.tools.scheduler import Scheduler from py4web.utils.factories import ActionFactory +from py4web.utils.mailer import Mailer + from . import settings # ####################################################### @@ -56,7 +59,9 @@ session = Session(secret=settings.SESSION_SECRET_KEY, storage=conn) elif settings.SESSION_TYPE == "memcache": - import memcache, time + import time + + import memcache conn = memcache.Client(settings.MEMCACHE_CLIENTS, debug=0) session = Session(secret=settings.SESSION_SECRET_KEY, storage=conn) @@ -127,9 +132,8 @@ ) if settings.OAUTH2GOOGLE_SCOPED_CREDENTIALS_FILE: - from py4web.utils.auth_plugins.oauth2google_scoped import ( - OAuth2GoogleScoped, - ) # TESTED + from py4web.utils.auth_plugins.oauth2google_scoped import \ + OAuth2GoogleScoped # TESTED auth.register_plugin( OAuth2GoogleScoped( @@ -151,7 +155,8 @@ ) if settings.OAUTH2FACEBOOK_CLIENT_ID: - from py4web.utils.auth_plugins.oauth2facebook import OAuth2Facebook # UNTESTED + from py4web.utils.auth_plugins.oauth2facebook import \ + OAuth2Facebook # UNTESTED auth.register_plugin( OAuth2Facebook( diff --git a/apps/tagged_posts/controllers.py b/apps/tagged_posts/controllers.py index 115ffc398..dfb60fb09 100644 --- a/apps/tagged_posts/controllers.py +++ b/apps/tagged_posts/controllers.py @@ -1,7 +1,9 @@ from py4web import action, request + from .common import auth from .models import db, parse_post_content + @action("index") @action.uses("index.html", auth.user) def index(): diff --git a/apps/tagged_posts/models.py b/apps/tagged_posts/models.py index 2117a8f82..e7bc10a19 100644 --- a/apps/tagged_posts/models.py +++ b/apps/tagged_posts/models.py @@ -2,10 +2,12 @@ This file defines the database models """ -from .common import db, Field, auth -from pydal.validators import * import re +from pydal.validators import * + +from .common import Field, auth, db + db.define_table( "post_item", Field("content", "text"), diff --git a/apps/tagged_posts/settings.py b/apps/tagged_posts/settings.py index c881050fe..ff22e57f2 100644 --- a/apps/tagged_posts/settings.py +++ b/apps/tagged_posts/settings.py @@ -6,6 +6,7 @@ This file is provided as an example: """ import os + from py4web.core import required_folder # mode (default or development) diff --git a/apps/tagged_posts/tasks.py b/apps/tagged_posts/tasks.py index 441839bdc..8048cc492 100644 --- a/apps/tagged_posts/tasks.py +++ b/apps/tagged_posts/tasks.py @@ -9,7 +9,8 @@ 5) Start "celery -A apps.{appname}.tasks worker --loglevel=info" for each worker """ -from .common import settings, scheduler, db, Field +from .common import Field, db, scheduler, settings + # example of task that needs db access @scheduler.task diff --git a/apps/todo/__init__.py b/apps/todo/__init__.py index 4766d536d..96f14f70a 100644 --- a/apps/todo/__init__.py +++ b/apps/todo/__init__.py @@ -1,5 +1,6 @@ import os -from py4web import action, request, DAL, Field, Session, Cache, Condition + +from py4web import DAL, Cache, Condition, Field, Session, action, request # define session and cache objects session = Session() diff --git a/deployment_tools/gae/main.py b/deployment_tools/gae/main.py index 3de0f7bc8..d15f2c945 100644 --- a/deployment_tools/gae/main.py +++ b/deployment_tools/gae/main.py @@ -1,12 +1,16 @@ import os import site import uuid -site.addsitedir(os.path.join(os.path.dirname(__file__), 'lib')) -from py4web.core import Reloader, bottle, Session -os.environ['PY4WEB_DASHBOARD_MODE'] = 'none' -os.environ['PY4WEB_SERVICE_DB_URI'] = 'sqlite:memory' -os.environ['PY4WEB_APPS_FOLDER'] = os.path.join(os.path.dirname(__file__), 'apps') -os.environ['PY4WEB_SERVICE_FOLDER'] = os.path.join(os.path.dirname(__file__), 'apps/.service') + +site.addsitedir(os.path.join(os.path.dirname(__file__), "lib")) +from py4web.core import Reloader, Session, bottle + +os.environ["PY4WEB_DASHBOARD_MODE"] = "none" +os.environ["PY4WEB_SERVICE_DB_URI"] = "sqlite:memory" +os.environ["PY4WEB_APPS_FOLDER"] = os.path.join(os.path.dirname(__file__), "apps") +os.environ["PY4WEB_SERVICE_FOLDER"] = os.path.join( + os.path.dirname(__file__), "apps/.service" +) Session.SECRET = str(uuid.uuid4()) Reloader.import_apps() app = bottle.default_app() diff --git a/deployment_tools/pythonanywhere.com/bottle_app.py b/deployment_tools/pythonanywhere.com/bottle_app.py index 642d74f25..66e568082 100644 --- a/deployment_tools/pythonanywhere.com/bottle_app.py +++ b/deployment_tools/pythonanywhere.com/bottle_app.py @@ -1,16 +1,20 @@ """ Documented here: https://youtu.be/Wxjl_vkLAEY """ + import os + from py4web.core import wsgi # BEGIN CONFIGURATION -PASSWORD_FILENAME = 'password.txt' -DASHBOARD_MODE = 'full' or 'demo' or 'none' -APPS_FOLDER = 'mysite/apps' +PASSWORD_FILENAME = "password.txt" +DASHBOARD_MODE = "full" or "demo" or "none" +APPS_FOLDER = "mysite/apps" # END CONFIGURATION -password_file = os.path.abspath(os.path.join(os.path.dirname(__file__), PASSWORD_FILENAME)) -application = wsgi(password_file=password_file, - dashboard_mode=DASHBOARD_MODE, - apps_folder=APPS_FOLDER) +password_file = os.path.abspath( + os.path.join(os.path.dirname(__file__), PASSWORD_FILENAME) +) +application = wsgi( + password_file=password_file, dashboard_mode=DASHBOARD_MODE, apps_folder=APPS_FOLDER +) diff --git a/docs/conf.py b/docs/conf.py index 99fc5e9aa..9eb11fd01 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,32 +5,31 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html import os - # -- Path setup -------------------------------------------------------------- # 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. -#import sys -#sys.path.insert(0, os.path.abspath('.')) +# import sys +# sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- -project = 'py4web' -copyright = '2020, BSDv3 License' -author = 'Massimo Di Pierro' +project = "py4web" +copyright = "2020, BSDv3 License" +author = "Massimo Di Pierro" # The full version, including alpha/beta/rc tags # get current_version from sources -pkg_init = '../py4web/__init__.py' -with open(pkg_init, 'r') as src: +pkg_init = "../py4web/__init__.py" +with open(pkg_init, "r") as src: for line in src: - if '__version__ = ' in line: - values = line.split(sep = ' = ') - current_version = values[1].strip('\n').strip('"') + if "__version__ = " in line: + values = line.split(sep=" = ") + current_version = values[1].strip("\n").strip('"') current_version = current_version[2:] current_version = current_version[:-2] break @@ -44,20 +43,20 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx_rtd_theme', - 'sphinx.ext.githubpages', - 'sphinx.ext.autosectionlabel', - 'sphinx_tabs.tabs', -# 'sphinxcontrib.spelling', + "sphinx_rtd_theme", + "sphinx.ext.githubpages", + "sphinx.ext.autosectionlabel", + "sphinx_tabs.tabs", + # 'sphinxcontrib.spelling', ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # How to format the current date, used as the replacement for |today| today_fmt = "%B %d, %Y" @@ -66,7 +65,7 @@ # The value should be a valid Pygments lexer name, # 'default' it is similar to 'python3'; it is mostly a superset of # 'python' but it fallbacks to 'none' without warning if failed. -highlight_language = 'none' +highlight_language = "none" # -- Options for HTML output ------------------------------------------------- @@ -74,19 +73,19 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # 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'] -html_logo = 'images/logo.png' -html_favicon = 'images/logo-32x32.ico' +html_static_path = ["_static"] +html_logo = "images/logo.png" +html_favicon = "images/logo-32x32.ico" html_theme_options = { - 'logo_only': False, + "logo_only": False, } # The master toctree document. -master_doc = 'index' +master_doc = "index" # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -100,56 +99,56 @@ # SETUP THE RTD LOWER-LEFT # ############################ try: - html_context + html_context except NameError: - html_context = dict() -html_context['display_lower_left'] = True + html_context = dict() +html_context["display_lower_left"] = True # SET CURRENT_LANGUAGE -if 'current_language' in os.environ: - # get the current_language env var set by buildDocs.sh - current_language = os.environ['current_language'] +if "current_language" in os.environ: + # get the current_language env var set by buildDocs.sh + current_language = os.environ["current_language"] else: - # the user is probably doing `make html` - # set this build's current language to english - current_language = 'en' + # the user is probably doing `make html` + # set this build's current language to english + current_language = "en" # tell the theme which language to we're currently building -html_context['current_language'] = current_language +html_context["current_language"] = current_language # tell the theme which version we're currently on ('current_version' affects # the lower-left rtd menu and 'version' affects the logo-area version) -html_context['current_version'] = current_version -html_context['version'] = current_version +html_context["current_version"] = current_version +html_context["version"] = current_version # POPULATE LINKS TO OTHER LANGUAGES -#html_context['languages'] = [ ('en', 'en/') ] -html_context['languages'] = [ ('en', '../en' + '/index.html') ] +# html_context['languages'] = [ ('en', 'en/') ] +html_context["languages"] = [("en", "../en" + "/index.html")] -languages = [lang.name for lang in os.scandir('locales') if lang.is_dir()] +languages = [lang.name for lang in os.scandir("locales") if lang.is_dir()] for lang in languages: - #html_context['languages'].append( (lang, lang + '/' ) ) - #html_context['languages'].append( (lang, '/../' + lang + '/' ) ) - html_context['languages'].append( (lang, '../' + lang + '/index.html') ) + # html_context['languages'].append( (lang, lang + '/' ) ) + # html_context['languages'].append( (lang, '/../' + lang + '/' ) ) + html_context["languages"].append((lang, "../" + lang + "/index.html")) # POPULATE LINKS TO OTHER VERSIONS -html_context['versions'] = list() -#html_context['versions'].append( ('master', current_language + '/' ) ) -html_context['versions'].append( ('current', 'index.html' ) ) -#html_context['versions'].append( ('current', '/' ) ) +html_context["versions"] = list() +# html_context['versions'].append( ('master', current_language + '/' ) ) +html_context["versions"].append(("current", "index.html")) +# html_context['versions'].append( ('current', '/' ) ) # POPULATE LINKS TO OTHER FORMATS/DOWNLOADS -html_context['downloads'] = list() -html_context['downloads'].append( ('pdf', project + '_' + current_language + '.pdf') ) -html_context['downloads'].append( ('epub', project + '_' + current_language + '.epub') ) +html_context["downloads"] = list() +html_context["downloads"].append(("pdf", project + "_" + current_language + ".pdf")) +html_context["downloads"].append(("epub", project + "_" + current_language + ".epub")) ########################## # "EDIT ON GITHUB" LINKS # ########################## -html_context['display_github'] = True -html_context['github_user'] = 'web2py' -html_context['github_repo'] = 'py4web' -html_context['github_version'] = 'master/docs/' +html_context["display_github"] = True +html_context["github_user"] = "web2py" +html_context["github_repo"] = "py4web" +html_context["github_version"] = "master/docs/" # -- Options for LaTeX output ------------------------------------------------ @@ -157,13 +156,10 @@ 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': '', - # Latex figure (float) alignment. #'figure_align': 'htbp', } @@ -172,19 +168,21 @@ # -- Options for PDF output -------------------------------------------------- # settings for creating PDF with rinoh -rinoh_documents = [dict( - doc = master_doc, - target = 'target', - title = project + ' Documentation', - date = '© ' + copyright, - logo = 'images/logo.png' -)] +rinoh_documents = [ + dict( + doc=master_doc, + target="target", + title=project + " Documentation", + date="© " + copyright, + logo="images/logo.png", + ) +] # -- Options for EPUB output ------------------------------------------------- -epub_basename = 'target' +epub_basename = "target" # ---- Options for spelling ------------------------------------------------- -spelling_lang='en_US' -spelling_word_list_filename='spelling_wordlist_en.txt' +spelling_lang = "en_US" +spelling_word_list_filename = "spelling_wordlist_en.txt" diff --git a/py4web/core.py b/py4web/core.py index da825f297..a6f8c5d51 100644 --- a/py4web/core.py +++ b/py4web/core.py @@ -48,7 +48,6 @@ gunicorn = None import click - # Third party modules import ombott as bottle import pluralize @@ -88,6 +87,7 @@ "wsgi", "Condition", "safely", + "utcnow", ] PY4WEB_CMD = sys.argv[0] @@ -138,6 +138,11 @@ def _before_request(*args, **kw): DEBUG = False +def utcnow(): + """returns the current time in utc""" + return datetime.datetime.now(datetime.timezone.utc) + + def module2filename(module): """given a module name as a string, convert to filename""" filename = os.path.join(*module.split(".")[1:]) @@ -1104,9 +1109,9 @@ def on_request(self, context): # Monkey Patch: Cookies ######################################################################################### -http.cookies.Morsel._reserved[ # pylint: disable=protected-access - "same-site" -] = "SameSite" +http.cookies.Morsel._reserved["same-site"] = ( # pylint: disable=protected-access + "SameSite" +) ######################################################################################### # Monkey Patch: ssl bug for gevent @@ -1168,7 +1173,7 @@ def get_error_snapshot(depth=5): etype = etype.__name__ data = {} - data["timestamp"] = datetime.datetime.utcnow().isoformat().replace("T", " ") + data["timestamp"] = utcnow().isoformat().replace("T", " ") data["python_version"] = sys.version platform_keys = [ "machine", @@ -1248,7 +1253,7 @@ def log(self, app_name, error_snapshot): app_name=app_name, method=request.method, path=request.path, - timestamp=datetime.datetime.utcnow(), + timestamp=utcnow(), client_ip=request.environ.get("REMOTE_ADDR"), error=error_snapshot["exception_value"], snapshot=error_snapshot, @@ -1741,6 +1746,7 @@ def kill_all(sig, _): bottle.run(**params) + def check_compatible(py4web_version): """To be called by apps to check if module version is compatible with py4web requirements""" from . import __version__ # pylint: disable=import-outside-toplevel diff --git a/py4web/server_adapters/logging_utils.py b/py4web/server_adapters/logging_utils.py index 684a2ddd3..fb4c4c8fb 100644 --- a/py4web/server_adapters/logging_utils.py +++ b/py4web/server_adapters/logging_utils.py @@ -17,14 +17,16 @@ def make_logger(name, loggers_info): logger = make_logger("py4web:appname", loggers_info) """ - default_formatter = "%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s" + default_formatter = ( + "%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s" + ) # reset loggers root = logging.getLogger(name) list(map(root.removeHandler, root.handlers)) list(map(root.removeFilter, root.filters)) for logger in loggers_info: - logger += (":stderr" if logger.count(":") == 0 else "") - logger += (":" if logger.count(":") == 1 else "") + logger += ":stderr" if logger.count(":") == 0 else "" + logger += ":" if logger.count(":") == 1 else "" level, filename, formatter = logger.split(":", 2) if not formatter: formatter = default_formatter diff --git a/py4web/utils/auth.py b/py4web/utils/auth.py index e8b501ebd..b79a3f483 100644 --- a/py4web/utils/auth.py +++ b/py4web/utils/auth.py @@ -13,7 +13,8 @@ from yatl.helpers import DIV, A from py4web import HTTP, URL, Field, action, redirect, request, response -from py4web.core import REGEX_APPJSON, Fixture, Flash, Template, Translator +from py4web.core import (REGEX_APPJSON, Fixture, Flash, Template, Translator, + utcnow) from py4web.utils.form import Form, FormStyleDefault from py4web.utils.param import Param @@ -109,24 +110,14 @@ def goto_login(self, message=""): def on_request(self, context): """Checks that we have a user in the session and the condition is met""" - user = self.auth.session.get("user") - if not user or not user.get("id"): + if "user" not in self.auth.session or "id" not in self.auth.session["user"]: self.auth.session["recent_activity"] = None self.goto_login(message="User not logged in") - activity = self.auth.session.get("recent_activity") - time_now = calendar.timegm(time.gmtime()) - # enforce the optionl auth session expiration time - if ( - self.auth.param.login_expiration_time - and activity - and time_now - activity > self.auth.param.login_expiration_time - ): - del self.auth.session["user"] + + self.auth.on_request(context) + + if "user" not in self.auth.session or "id" not in self.auth.session["user"]: self.goto_login(message="Login expired") - # record the time of the latest activity for logged in user (with throttling) - if not activity or time_now - activity > 6: - self.auth.session["recent_activity"] = time_now - self.auth.session["recent_timestamp"] = datetime.datetime.utcnow().isoformat() if callable(self.condition) and not self.condition(user): self.abort_or_redirect("not-authorized", "User not authorized") @@ -291,6 +282,24 @@ def __init__( self.form_source = DefaultAuthForms(self) self.fix_actions() + def on_request(self, context): + """Checks that we have a user in the session and + the condition is met""" + user = self.session.get("user") + if user: + activity = self.session.get("recent_activity") + time_now = calendar.timegm(time.gmtime()) + # enforce the optionl auth session expiration time + if ( + self.param.login_expiration_time + and activity + and time_now - activity > self.param.login_expiration_time + ): + del self.session["user"] + elif not activity or time_now - activity > 6: + self.session["recent_activity"] = time_now + self.session["recent_timestamp"] = utcnow().isoformat() + def allows(self, action_name): """Checks if the provided action is allowed on the Auth object""" return ( @@ -419,12 +428,13 @@ def define_tables(self): db.define_table( "auth_user", *(auth_fields + self.extra_auth_user_fields), - format=lambda u: f"{u.first_name} {u.last_name}") + format=lambda u: f"{u.first_name} {u.last_name}", + ) @property def signature(self): """Returns a list of fields for a table signature""" - now = lambda: datetime.datetime.utcnow() + now = lambda: utcnow() user = lambda s=self: s.user_id fields = [ Field( @@ -787,7 +797,7 @@ def change_password( past_passwords_hash=past_pwds ) num = db(db.auth_user.id == user.get("id")).update( - password=new_pwd, last_password_change=datetime.datetime.utcnow() + password=new_pwd, last_password_change=utcnow() ) return {"updated": num} @@ -1069,6 +1079,7 @@ def _(path, plugin=self.plugins[name], name=name): for item in exposed_form_routes: form_factory = getattr(self.form_source, item["form_name"]) template = Template(f"{route}.html", **self.param.template_args) + @action(item["form_route"], method=["GET", "POST"]) @action.uses(template, item["uses"], self.flash, *uses) def _( @@ -1677,7 +1688,9 @@ def _reset_two_factor(self): self.auth.session["auth.2fa_tries_left"] = self.auth.param.two_factor_tries def two_factor(self): - if (self.auth.param.two_factor_send is None) and (self.auth.param.two_factor_validate is None): + if (self.auth.param.two_factor_send is None) and ( + self.auth.param.two_factor_validate is None + ): raise HTTP(404) user_id = self.auth.session.get("auth.2fa_user") @@ -1702,13 +1715,19 @@ def two_factor_validate(form): # external validation outcome outcome = None if self.auth.param.two_factor_validate: - outcome = self.auth.param.two_factor_validate(user, form.vars['authentication_code']) + outcome = self.auth.param.two_factor_validate( + user, form.vars["authentication_code"] + ) # outcome: # True: external validation passed # False: external validation failed # None: external validation status unknown - check against the generated code - if outcome==False or ((outcome is None) and (form.vars['authentication_code']!=code)): - form.errors['authentication_code'] = self.auth.param.messages["errors"]["two_factor"] + if outcome == False or ( + (outcome is None) and (form.vars["authentication_code"] != code) + ): + form.errors["authentication_code"] = self.auth.param.messages["errors"][ + "two_factor" + ] form = Form( [ @@ -1981,7 +2000,10 @@ def profile(self, model=False): def logout(self, model=False): if model: return dict( - public=False, hidden=False, noform=True, href=URL(f"{self.auth.route}/api/logout") + public=False, + hidden=False, + noform=True, + href=URL(f"{self.auth.route}/api/logout"), ) """Process logout""" @@ -1993,7 +2015,10 @@ def logout(self, model=False): def verify_email(self, model=False): if model: return dict( - public=True, hidden=True, noform=True, href=URL(f"{self.auth.route}/api/verify_email") + public=True, + hidden=True, + noform=True, + href=URL(f"{self.auth.route}/api/verify_email"), ) """Process token in email verification""" diff --git a/py4web/utils/dbstore.py b/py4web/utils/dbstore.py index 292a9b7e4..959b1815e 100644 --- a/py4web/utils/dbstore.py +++ b/py4web/utils/dbstore.py @@ -1,5 +1,7 @@ from datetime import datetime, timedelta +from ..core import utcnow + class DBStore: def __init__(self, db, name="py4web_session"): @@ -19,7 +21,7 @@ def __init__(self, db, name="py4web_session"): self.table = db[name] def get(self, key): - db, table, now = self.db, self.table, datetime.utcnow() + db, table, now = self.db, self.table, utcnow() row = db(table.rkey == key).select().first() if not row: return None @@ -28,7 +30,7 @@ def get(self, key): return row.rvalue def set(self, key, value, expiration=None): - db, table, now = self.db, self.table, datetime.utcnow() + db, table, now = self.db, self.table, utcnow() db(table.expires_on < now).delete() row = db(table.rkey == key).select().first() expires_on = ( diff --git a/py4web/utils/form.py b/py4web/utils/form.py index 992382d20..d505735d4 100644 --- a/py4web/utils/form.py +++ b/py4web/utils/form.py @@ -7,20 +7,8 @@ import jwt from pydal._compat import to_native from pydal.objects import FieldVirtual -from yatl.helpers import ( - CAT, - DIV, - FORM, - INPUT, - LABEL, - OPTION, - SELECT, - SPAN, - TEXTAREA, - XML, - A, - P, -) +from yatl.helpers import (CAT, DIV, FORM, INPUT, LABEL, OPTION, SELECT, SPAN, + TEXTAREA, XML, A, P) from py4web import HTTP, request, response from py4web.utils.param import Param diff --git a/py4web/utils/grid.py b/py4web/utils/grid.py index 2e4c23748..5ef411f52 100644 --- a/py4web/utils/grid.py +++ b/py4web/utils/grid.py @@ -9,25 +9,8 @@ from urllib.parse import urlparse from pydal.objects import Expression, Field, FieldVirtual -from yatl.helpers import ( - CAT, - DIV, - FORM, - INPUT, - OPTION, - SELECT, - SPAN, - TABLE, - TAG, - TBODY, - TD, - TH, - THEAD, - TR, - XML, - A, - I, -) +from yatl.helpers import (CAT, DIV, FORM, INPUT, OPTION, SELECT, SPAN, TABLE, + TAG, TBODY, TD, TH, THEAD, TR, XML, A, I) from py4web import HTTP, URL, redirect, request, safely from py4web.utils.form import Form, FormStyleDefault, join_classes @@ -259,9 +242,7 @@ class Grid: ) ) if value and isinstance(value, datetime.datetime) - else value - if value - else "" + else value if value else "" ), "time": lambda value: ( XML( @@ -269,9 +250,7 @@ class Grid: % (value.hour, value.minute, value.second) ) if value and isinstance(value, datetime.time) - else value - if value - else "" + else value if value else "" ), "date": lambda value: ( XML( @@ -283,9 +262,7 @@ class Grid: ) ) if value and isinstance(value, datetime.date) - else value - if value - else "" + else value if value else "" ), "list": lambda value: ", ".join(x for x in value) if value else "", } @@ -976,9 +953,11 @@ def _make_table_body(self): [ self.param.grid_class_style.get( col.td_class_style, - col.td_class_style(row) - if callable(col.td_class_style) - else self.param.grid_class_style.get("grid-td"), + ( + col.td_class_style(row) + if callable(col.td_class_style) + else self.param.grid_class_style.get("grid-td") + ), ), f"grid-cell-{col.key}", ] diff --git a/py4web/utils/populate.py b/py4web/utils/populate.py index d9112c370..ce4c8ca24 100644 --- a/py4web/utils/populate.py +++ b/py4web/utils/populate.py @@ -280,7 +280,9 @@ def populate_generator(table, default=True, compute=False, contents=None, ell=No record[fieldname] = random.randint(2000, 2013) else: record[fieldname] = random.randint(0, 1000) - elif field.type in ("float", "double") or str(field.type).startswith("decimal"): + elif field.type in ("float", "double") or str(field.type).startswith( + "decimal" + ): if hasattr(field.requires, "minimum"): rand = random.random() if str(field.type).startswith("decimal"): diff --git a/tests/test_auth.py b/tests/test_auth.py index 5327e93dc..8b956f769 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -3,7 +3,8 @@ import unittest import uuid -from py4web.core import DAL, HTTP, Field, Fixture, Session, bottle, request, safely +from py4web.core import (DAL, HTTP, Field, Fixture, Session, bottle, request, + safely) from py4web.utils.auth import Auth, AuthAPI SECRET = str(uuid.uuid4()) diff --git a/tests/test_fixture.py b/tests/test_fixture.py index e1b4cbc26..3314b1936 100644 --- a/tests/test_fixture.py +++ b/tests/test_fixture.py @@ -1,4 +1,5 @@ import threading + import pytest from py4web.core import Fixture