diff options
author | sotech117 <michael_foiani@brown.edu> | 2025-07-31 17:27:24 -0400 |
---|---|---|
committer | sotech117 <michael_foiani@brown.edu> | 2025-07-31 17:27:24 -0400 |
commit | 5bf22fc7e3c392c8bd44315ca2d06d7dca7d084e (patch) | |
tree | 8dacb0f195df1c0788d36dd0064f6bbaa3143ede /venv/lib/python3.8/site-packages/dash/_pages.py | |
parent | b832d364da8c2efe09e3f75828caf73c50d01ce3 (diff) |
add code for analysis of data
Diffstat (limited to 'venv/lib/python3.8/site-packages/dash/_pages.py')
-rw-r--r-- | venv/lib/python3.8/site-packages/dash/_pages.py | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/venv/lib/python3.8/site-packages/dash/_pages.py b/venv/lib/python3.8/site-packages/dash/_pages.py new file mode 100644 index 0000000..4553854 --- /dev/null +++ b/venv/lib/python3.8/site-packages/dash/_pages.py @@ -0,0 +1,450 @@ +import collections +import importlib +import importlib.util # to make the type checker happy +import os +import re +import sys +from fnmatch import fnmatch +from os.path import isfile, join +from pathlib import Path +from urllib.parse import parse_qs + +import flask + +from . import _validate +from ._callback_context import context_value +from ._get_app import get_app +from ._get_paths import get_relative_path +from ._utils import AttributeDict + +CONFIG = AttributeDict() +PAGE_REGISTRY = collections.OrderedDict() + + +def _infer_image(module): + """ + Return: + - A page specific image: `assets/<module>.<extension>` is used, e.g. `assets/weekly_analytics.png` + - A generic app image at `assets/app.<extension>` + - A logo at `assets/logo.<extension>` + """ + assets_folder = CONFIG.assets_folder + valid_extensions = ["apng", "avif", "gif", "jpeg", "jpg", "png", "svg", "webp"] + page_id = module.split(".")[-1] + files_in_assets = [] + + if os.path.exists(assets_folder): + files_in_assets = [ + f for f in os.listdir(assets_folder) if isfile(join(assets_folder, f)) + ] + app_file = None + logo_file = None + for fn in files_in_assets: + fn_without_extension, _, extension = fn.partition(".") + if extension.lower() in valid_extensions: + if ( + fn_without_extension == page_id + or fn_without_extension == page_id.replace("_", "-") + ): + return fn + + if fn_without_extension == "app": + app_file = fn + + if fn_without_extension == "logo": + logo_file = fn + + if app_file: + return app_file + + return logo_file + + +def _module_name_to_page_name(module_name): + return module_name.split(".")[-1].replace("_", " ").capitalize() + + +def _infer_path(module_name, template): + if template is None: + if CONFIG.pages_folder: + pages_module = str(Path(CONFIG.pages_folder).name) + path = ( + module_name.split(pages_module)[-1] + .replace("_", "-") + .replace(".", "/") + .lower() + ) + else: + path = module_name.replace("_", "-").replace(".", "/").lower() + else: + # replace the variables in the template with "none" to create a default path if + # no path is supplied + path = re.sub("<.*?>", "none", template) + path = "/" + path if not path.startswith("/") else path + return path + + +def _module_name_is_package(module_name): + if module_name not in sys.modules: + return False + file = sys.modules[module_name].__file__ + return file and file.endswith("__init__.py") + + +def _path_to_module_name(path): + return str(path).replace(".py", "").strip(os.sep).replace(os.sep, ".") + + +def _infer_module_name(page_path): + relative_path = page_path.split(CONFIG.pages_folder)[-1] + module = _path_to_module_name(relative_path) + proj_root = flask.helpers.get_root_path(CONFIG.name) + if CONFIG.pages_folder.startswith(proj_root): + parent_path = CONFIG.pages_folder[len(proj_root) :] + else: + parent_path = CONFIG.pages_folder + parent_module = _path_to_module_name(parent_path) + + module_name = f"{parent_module}.{module}" + if _module_name_is_package(CONFIG.name): + # Only prefix with CONFIG.name when it's an imported package name + module_name = f"{CONFIG.name}.{module_name}" + return module_name + + +def _parse_query_string(search): + if not search or not search.startswith("?"): + return {} + + query_string = search[1:] + + parsed_qs = parse_qs(query_string, keep_blank_values=True) + + return {k: v[0] if len(v) == 1 else v for k, v in parsed_qs.items()} + + +def _parse_path_variables(pathname, path_template): + """ + creates the dict of path variables passed to the layout + e.g. path_template= "/asset/<asset_id>" + if pathname provided by the browser is "/assets/a100" + returns **{"asset_id": "a100"} + """ + + # parse variable definitions e.g. <var_name> from template + # and create pattern to match + wildcard_pattern = re.sub("<.*?>", "*", path_template) + var_pattern = re.sub("<.*?>", "(.*)", path_template) + + # check that static sections of the pathname match the template + if not fnmatch(pathname, wildcard_pattern): + return None + + # parse variable names e.g. var_name from template + var_names = re.findall("<(.*?)>", path_template) + + # parse variables from path + variables = re.findall(var_pattern, pathname) + variables = variables[0] if isinstance(variables[0], tuple) else variables + + return dict(zip(var_names, variables)) + + +def _create_redirect_function(redirect_to): + def redirect(): + return flask.redirect(redirect_to, code=301) + + return redirect + + +def _set_redirect(redirect_from, path): + app = get_app() + if redirect_from and len(redirect_from): + for redirect in redirect_from: + fullname = app.get_relative_path(redirect) + app.server.add_url_rule( + fullname, + fullname, + _create_redirect_function(app.get_relative_path(path)), + ) + + +def register_page( + module, + path=None, + path_template=None, + name=None, + order=None, + title=None, + description=None, + image=None, + image_url=None, + redirect_from=None, + layout=None, + **kwargs, +): + """ + Assigns the variables to `dash.page_registry` as an `OrderedDict` + (ordered by `order`). + + `dash.page_registry` is used by `pages_plugin` to set up the layouts as + a multi-page Dash app. This includes the URL routing callbacks + (using `dcc.Location`) and the HTML templates to include title, + meta description, and the meta description image. + + `dash.page_registry` can also be used by Dash developers to create the + page navigation links or by template authors. + + - `module`: + The module path where this page's `layout` is defined. Often `__name__`. + + - `path`: + URL Path, e.g. `/` or `/home-page`. + If not supplied, will be inferred from the `path_template` or `module`, + e.g. based on path_template: `/asset/<asset_id` to `/asset/none` + e.g. based on module: `pages.weekly_analytics` to `/weekly-analytics` + + - `relative_path`: + The path with `requests_pathname_prefix` prefixed before it. + Use this path when specifying local URL paths that will work + in environments regardless of what `requests_pathname_prefix` is. + In some deployment environments, like Dash Enterprise, + `requests_pathname_prefix` is set to the application name, + e.g. `my-dash-app`. + When working locally, `requests_pathname_prefix` might be unset and + so a relative URL like `/page-2` can just be `/page-2`. + However, when the app is deployed to a URL like `/my-dash-app`, then + `relative_path` will be `/my-dash-app/page-2`. + + - `path_template`: + Add variables to a URL by marking sections with <variable_name>. The layout function + then receives the <variable_name> as a keyword argument. + e.g. path_template= "/asset/<asset_id>" + then if pathname in browser is "/assets/a100" then layout will receive **{"asset_id":"a100"} + + - `name`: + The name of the link. + If not supplied, will be inferred from `module`, + e.g. `pages.weekly_analytics` to `Weekly analytics` + + - `order`: + The order of the pages in `page_registry`. + If not supplied, then the filename is used and the page with path `/` has + order `0` + + - `title`: + (string or function) Specifies the page title displayed in the browser tab. + If not supplied, the app's title is used if different from the default "Dash". + Otherwise, the title is the given `name` or inferred from the module name. + For example, `pages.weekly_analytics` is inferred as "Weekly Analytics". + + - `description`: + (string or function) The <meta type="description"></meta>. + If not defined, the application description will be used if available. + + - `image`: + The meta description image used by social media platforms. + If not supplied, then it looks for the following images in `assets/`: + - A page specific image: `assets/<module>.<extension>` is used, e.g. `assets/weekly_analytics.png` + - A generic app image at `assets/app.<extension>` + - A logo at `assets/logo.<extension>` + When inferring the image file, it will look for the following extensions: + APNG, AVIF, GIF, JPEG, JPG, PNG, SVG, WebP. + + - `image_url`: + Overrides the image property and sets the `<image>` meta tag to the provided image URL. + + - `redirect_from`: + A list of paths that should redirect to this page. + For example: `redirect_from=['/v2', '/v3']` + + - `layout`: + The layout function or component for this page. + If not supplied, then looks for `layout` from within the supplied `module`. + + - `**kwargs`: + Arbitrary keyword arguments that can be stored + + *** + + `page_registry` stores the original property that was passed in under + `supplied_<property>` and the coerced property under `<property>`. + For example, if this was called: + ``` + register_page( + 'pages.historical_outlook', + name='Our historical view', + custom_key='custom value' + ) + ``` + Then this will appear in `page_registry`: + ``` + OrderedDict([ + ( + 'pages.historical_outlook', + dict( + module='pages.historical_outlook', + + supplied_path=None, + path='/historical-outlook', + + supplied_name='Our historical view', + name='Our historical view', + + supplied_title=None, + title='Our historical view' + + supplied_layout=None, + layout=<function pages.historical_outlook.layout>, + + custom_key='custom value' + ) + ), + ]) + ``` + """ + if context_value.get().get("ignore_register_page"): + return + + _validate.validate_use_pages(CONFIG) + + page = dict( + module=_validate.validate_module_name(module), + supplied_path=path, + path_template=path_template, + path=path if path is not None else _infer_path(module, path_template), + supplied_name=name, + name=name if name is not None else _module_name_to_page_name(module), + ) + page.update( + supplied_title=title, + title=title + if title is not None + else CONFIG.title + if CONFIG.title != "Dash" + else page["name"], + ) + page.update( + description=description + if description + else CONFIG.description + if CONFIG.description + else "", + order=order, + supplied_order=order, + supplied_layout=layout, + **kwargs, + ) + page.update( + supplied_image=image, + image=(image if image is not None else _infer_image(module)), + image_url=image_url, + ) + page.update(redirect_from=_set_redirect(redirect_from, page["path"])) + + PAGE_REGISTRY[module] = page + + if page["path_template"]: + _validate.validate_template(page["path_template"]) + + if layout is not None: + # Override the layout found in the file set during `plug` + PAGE_REGISTRY[module]["layout"] = layout + + # set home page order + order_supplied = any( + p["supplied_order"] is not None for p in PAGE_REGISTRY.values() + ) + + for p in PAGE_REGISTRY.values(): + p["order"] = ( + 0 if p["path"] == "/" and not order_supplied else p["supplied_order"] + ) + p["relative_path"] = get_relative_path(p["path"]) + + # Sort numeric orders first, then string orders, then no order, + # finally by module name for matching orders + for page in sorted( + PAGE_REGISTRY.values(), + key=lambda i: ( + i["order"] is None, # False (order given) sorts before True + i["order"] if isinstance(i["order"], (int, float)) else float("inf"), + str(i["order"]), + i["module"], + ), + ): + PAGE_REGISTRY.move_to_end(page["module"]) + + +def _path_to_page(path_id): + path_variables = None + for page in PAGE_REGISTRY.values(): + if page["path_template"]: + template_id = page["path_template"].strip("/") + path_variables = _parse_path_variables(path_id, template_id) + if path_variables: + return page, path_variables + if path_id == page["path"].strip("/"): + return page, path_variables + return {}, None + + +def _page_meta_tags(app): + start_page, path_variables = _path_to_page(flask.request.path.strip("/")) + + # use the supplied image_url or create url based on image in the assets folder + image = start_page.get("image", "") + if image: + image = app.get_asset_url(image) + assets_image_url = ( + "".join([flask.request.url_root, image.lstrip("/")]) if image else None + ) + supplied_image_url = start_page.get("image_url") + image_url = supplied_image_url if supplied_image_url else assets_image_url + + title = start_page.get("title", app.title) + if callable(title): + title = title(**path_variables) if path_variables else title() + + description = start_page.get("description", "") + if callable(description): + description = description(**path_variables) if path_variables else description() + + return [ + {"name": "description", "content": description}, + {"property": "twitter:card", "content": "summary_large_image"}, + {"property": "twitter:url", "content": flask.request.url}, + {"property": "twitter:title", "content": title}, + {"property": "twitter:description", "content": description}, + {"property": "twitter:image", "content": image_url or ""}, + {"property": "og:title", "content": title}, + {"property": "og:type", "content": "website"}, + {"property": "og:description", "content": description}, + {"property": "og:image", "content": image_url or ""}, + ] + + +def _import_layouts_from_pages(pages_folder): + for root, dirs, files in os.walk(pages_folder): + dirs[:] = [d for d in dirs if not d.startswith(".") and not d.startswith("_")] + for file in files: + if file.startswith("_") or file.startswith(".") or not file.endswith(".py"): + continue + page_path = os.path.join(root, file) + with open(page_path, encoding="utf-8") as f: + content = f.read() + if "register_page" not in content: + continue + + module_name = _infer_module_name(page_path) + spec = importlib.util.spec_from_file_location(module_name, page_path) + page_module = importlib.util.module_from_spec(spec) # type: ignore[reportArgumentType] + spec.loader.exec_module(page_module) # type: ignore[reportOptionalMemberAccess] + sys.modules[module_name] = page_module + + if ( + module_name in PAGE_REGISTRY + and not PAGE_REGISTRY[module_name]["supplied_layout"] + ): + _validate.validate_pages_layout(module_name, page_module) + PAGE_REGISTRY[module_name]["layout"] = getattr(page_module, "layout") |