diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index da934315..6f7fdd0b 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -16,7 +16,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.7, 3.8, 3.9, "3.10", "3.11"] + python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 diff --git a/docs/generate_examples.py b/docs/generate_examples.py index 001c2d18..a9b9ba52 100644 --- a/docs/generate_examples.py +++ b/docs/generate_examples.py @@ -1,9 +1,10 @@ """Run this to populate the example cases from the code to Jekyll folder""" + import os import re import sys from pathlib import Path -from typing import TypedDict, List +from typing import List, TypedDict import yaml @@ -13,7 +14,8 @@ from json_schema_for_humans.const import FileLikeType from json_schema_for_humans.generate import generate_from_filename -from json_schema_for_humans.generation_configuration import GenerationConfiguration +from json_schema_for_humans.generation_configuration import \ + GenerationConfiguration EXAMPLES_DIR = os.path.join(CURRENT_DIR, "examples") JSON_EXAMPLES_DIR = os.path.join(EXAMPLES_DIR, "cases") @@ -191,18 +193,20 @@ def generate_each_template(examples_md_file: FileLikeType, case_path: str, case_ """ print(f"Generating example {case_name}") - examples_md_file.write(f"\n## --{case_name}--") + examples_md_file.write(f"\n## --{case_name}--") # type:ignore[arg-type] if case_name in CASES_DESCRIPTION_DICT: case_data = CASES_DESCRIPTION_DICT[case_name] case_display_name = case_data["display_name"] case_description = case_data["description"] - examples_md_file.write(f"\n### {case_display_name}\n\n") + examples_md_file.write(f"\n### {case_display_name}\n\n") # type:ignore[arg-type] if case_description: - examples_md_file.write(f"**Description:** {case_description}\n") + examples_md_file.write(f"**Description:** {case_description}\n") # type:ignore[arg-type] else: - examples_md_file.write(f"\n### {case_name}\n") + examples_md_file.write(f"\n### {case_name}\n") # type:ignore[arg-type] - examples_md_file.write(MD_EXAMPLE_JSON_TEMPLATE.format(file_url=case_url, title="Json schema")) + examples_md_file.write( + MD_EXAMPLE_JSON_TEMPLATE.format(file_url=case_url, title="Json schema") # type:ignore[arg-type] + ) for config in CONFIGURATIONS: template_configuration = config["config"] example_dir_name = config["dir_name"] @@ -211,7 +215,7 @@ def generate_each_template(examples_md_file: FileLikeType, case_path: str, case_ examples_md_file.write( config["md_example_template"].format( file_url=f"examples/{example_dir_name}/{example_file_name}", title=config["title"] - ) + ) # type:ignore[arg-type] ) example_dest_dir = os.path.join(EXAMPLES_DIR, example_dir_name) diff --git a/json_schema_for_humans/cli.py b/json_schema_for_humans/cli.py index 4784aadc..44e78613 100644 --- a/json_schema_for_humans/cli.py +++ b/json_schema_for_humans/cli.py @@ -1,10 +1,10 @@ from pathlib import Path -from typing import Optional, List, Union +from typing import List, Optional, Union import click -from json_schema_for_humans.const import FileLikeType, DEFAULT_CSS_FILE_NAME, DEFAULT_JS_FILE_NAME -from json_schema_for_humans.generate import generate_schemas_doc, copy_additional_files_to_target +from json_schema_for_humans.const import DEFAULT_CSS_FILE_NAME, DEFAULT_JS_FILE_NAME, FileLikeType +from json_schema_for_humans.generate import copy_additional_files_to_target, generate_schemas_doc from json_schema_for_humans.generation_configuration import get_final_config from json_schema_for_humans.schema.schema_importer import get_result_output, get_schemas_to_render from json_schema_for_humans.schema.schema_to_render import SchemaToRender @@ -56,7 +56,7 @@ def main( copy_js: bool, link_to_reused_ref: bool, ) -> None: - config = get_final_config( + final_config = get_final_config( minify=minify, deprecated_from_description=deprecated_from_description, default_from_description=default_from_description, @@ -69,22 +69,25 @@ def main( ) schemas_to_render = get_schemas_to_render_from_cli_arguments( - schema_files_or_dir, output_path_or_file, config.result_extension + schema_files_or_dir, output_path_or_file, final_config.result_extension ) - template_renderer = TemplateRenderer(config) + template_renderer = TemplateRenderer(final_config) generate_schemas_doc(schemas_to_render, template_renderer) - copy_additional_files_to_target(schemas_to_render, config) + copy_additional_files_to_target(schemas_to_render, final_config) def get_schemas_to_render_from_cli_arguments( schema_files_or_dir: Union[str, Path], output_path_or_file: Optional[Path], result_extension: str ) -> List[SchemaToRender]: - schema_files_or_dir = schema_files_or_dir.split(",") - result_output = get_result_output(output_path_or_file, schema_files_or_dir, result_extension) + if isinstance(schema_files_or_dir, Path): + schema_files_or_dir_to_render = [str(schema_files_or_dir)] + else: + schema_files_or_dir_to_render = schema_files_or_dir.split(",") + result_output = get_result_output(output_path_or_file, schema_files_or_dir_to_render, result_extension) schemas_to_render: List[SchemaToRender] = [] - for schema_file_or_dir_part in schema_files_or_dir: + for schema_file_or_dir_part in schema_files_or_dir_to_render: schemas_to_render += get_schemas_to_render(schema_file_or_dir_part, result_output, result_extension) return schemas_to_render diff --git a/json_schema_for_humans/const.py b/json_schema_for_humans/const.py index f2005a94..8d203fe5 100644 --- a/json_schema_for_humans/const.py +++ b/json_schema_for_humans/const.py @@ -1,6 +1,6 @@ from enum import Enum -from io import TextIOWrapper, FileIO -from typing import Union, TextIO +from io import FileIO, TextIOWrapper +from typing import TextIO, Union TYPE_ARRAY = "array" TYPE_BOOLEAN = "boolean" @@ -45,6 +45,7 @@ def result_extension(self) -> str: return "html" if self in [self.MD, self.MD_NESTED]: return "md" + return "html" DEFAULT_TEMPLATE_FILE_NAME = "base.html" diff --git a/json_schema_for_humans/generate.py b/json_schema_for_humans/generate.py index 2bac8aae..2ddb515f 100644 --- a/json_schema_for_humans/generate.py +++ b/json_schema_for_humans/generate.py @@ -1,6 +1,7 @@ import shutil from datetime import datetime from pathlib import Path +from tempfile import _TemporaryFileWrapper from typing import Any, Dict, List, Optional, Union from json_schema_for_humans.const import FileLikeType @@ -18,7 +19,7 @@ def generate_from_schema( default_from_description: bool = False, expand_buttons: bool = False, link_to_reused_ref: bool = True, - config: GenerationConfiguration = None, + config: Optional[GenerationConfiguration] = None, ) -> str: config = config or get_final_config( minify=minify, @@ -34,6 +35,8 @@ def generate_from_schema( for rendered_content in generate_schemas_doc(schemas_to_render, template_renderer, loaded_schemas).items(): return rendered_content[1] + raise ValueError(f"Unable to find a schema to render from {schema_file}") + def generate_from_filename( schema_file_name: Union[str, Path], @@ -45,7 +48,7 @@ def generate_from_filename( copy_css: bool = True, copy_js: bool = True, link_to_reused_ref: bool = True, - config: GenerationConfiguration = None, + config: Optional[GenerationConfiguration] = None, ) -> None: """Generate the schema documentation from a filename""" config = config or get_final_config( @@ -66,8 +69,8 @@ def generate_from_filename( def generate_from_file_object( - schema_file: FileLikeType, - result_file: FileLikeType, + schema_file: Union[FileLikeType, _TemporaryFileWrapper], + result_file: Union[FileLikeType, _TemporaryFileWrapper], minify: bool = True, deprecated_from_description: bool = False, default_from_description: bool = False, @@ -75,7 +78,7 @@ def generate_from_file_object( copy_css: bool = True, copy_js: bool = True, link_to_reused_ref: bool = True, - config: GenerationConfiguration = None, + config: Optional[GenerationConfiguration] = None, ) -> None: """Generate the JSON schema documentation from opened file objects for both input and output files. The result_file should be opened in write mode. diff --git a/json_schema_for_humans/generation_configuration.py b/json_schema_for_humans/generation_configuration.py index 996c6225..a08cb7f4 100644 --- a/json_schema_for_humans/generation_configuration.py +++ b/json_schema_for_humans/generation_configuration.py @@ -4,19 +4,19 @@ from dataclasses import dataclass from json import JSONDecodeError from pathlib import Path -from typing import Any, Union, Dict, List, Optional +from typing import Any, Dict, List, Optional, Union import yaml from dataclasses_json import dataclass_json from json_schema_for_humans.const import ( - DocumentationTemplate, - FileLikeType, DEFAULT_CSS_FILE_NAME, DEFAULT_JS_FILE_NAME, OFFLINE_CSS_FILE_NAMES, OFFLINE_FONT_FILE_NAMES, OFFLINE_JS_FILE_NAMES, + DocumentationTemplate, + FileLikeType, ) DEFAULT_PROPERTIES_TABLE_COLUMNS = [ @@ -114,7 +114,7 @@ def result_extension(self) -> str: raise ValueError("Trying to get extension for configuration with no template") @property - def template_path(self) -> Optional[Path]: + def template_path(self) -> Path: if self.custom_template_path: return Path(self.custom_template_path) @@ -140,7 +140,7 @@ def get_final_config( copy_css: bool = False, copy_js: bool = False, config: Optional[Union[str, Path, FileLikeType, Dict[str, Any], GenerationConfiguration]] = None, - config_parameters: List[str] = None, + config_parameters: Optional[List[str]] = None, ) -> GenerationConfiguration: if config: final_config = _load_config(config) @@ -214,5 +214,4 @@ def _apply_config_cli_parameters( parameter_value = True current_configuration_as_dict[parameter_name] = parameter_value - # type: ignore - return GenerationConfiguration.from_dict(current_configuration_as_dict) + return GenerationConfiguration.from_dict(current_configuration_as_dict) # type: ignore[attr-defined] diff --git a/json_schema_for_humans/jinja_filters.py b/json_schema_for_humans/jinja_filters.py index 98b763a7..dc8b4a61 100644 --- a/json_schema_for_humans/jinja_filters.py +++ b/json_schema_for_humans/jinja_filters.py @@ -1,22 +1,23 @@ -import re import json - -import yaml +import re from datetime import datetime -from typing import List, Any +from typing import Any, List, Optional, cast -from jinja2 import pass_environment, Environment -from markdown2 import Markdown -from markupsafe import Markup, escape as markupsafe_escape +import yaml +from jinja2 import Environment, pass_environment +from markdown2 import Markdown # type: ignore +from markupsafe import Markup +from markupsafe import escape as markupsafe_escape from pygments import highlight from pygments.formatters.html import HtmlFormatter -from pygments.lexers.javascript import JavascriptLexer from pygments.lexers.data import YamlLexer +from pygments.lexers.javascript import JavascriptLexer from pytz import reference from json_schema_for_humans import const from json_schema_for_humans.generation_configuration import GenerationConfiguration from json_schema_for_humans.schema.schema_node import SchemaNode +from json_schema_for_humans.templating_utils import schema_keyword_convert_to_str, schema_keyword_to_str SHORT_DESCRIPTION_NUMBER_OF_LINES = 8 DEFAULT_PATTERN = r"(\[Default - `([^`]+)`\])" @@ -47,13 +48,14 @@ def is_deprecated_look_in_description(schema_node: SchemaNode) -> bool: if const.DESCRIPTION not in schema_node.keywords: return False - return bool(DEPRECATED_MARKER in schema_node.keywords[const.DESCRIPTION].literal) + return bool(DEPRECATED_MARKER in (schema_node.keywords[const.DESCRIPTION].literal_str or "")) def get_required_properties(schema_node: SchemaNode) -> List[str]: - required_properties = schema_node.keywords.get("required") or [] - if required_properties: - required_properties = [p.literal for p in required_properties.array_items] + required_properties_node: Optional[SchemaNode] = schema_node.keywords.get("required") + required_properties: List[str] = [] + if required_properties_node: + required_properties = [p.literal_str for p in required_properties_node.array_items if p.literal_str] return required_properties @@ -96,7 +98,7 @@ def get_description(env: Environment, schema_node: SchemaNode) -> str: def get_description_literal(env: Environment, description: str) -> str: """Filter. Get the description of a property or an empty string""" - config: GenerationConfiguration = env.globals["jsfh_config"] + config: GenerationConfiguration = cast(GenerationConfiguration, env.globals["jsfh_config"]) if config.default_from_description: match = re.match(DEFAULT_PATTERN, description) if match: @@ -112,7 +114,7 @@ def get_description_literal(env: Environment, description: str) -> str: def get_default(schema_node: SchemaNode) -> str: """Filter. Return the default value for a property""" - return schema_node.default_value + return str(schema_node.default_value) if schema_node.default_value is not None else "" def get_default_look_in_description(schema_node: SchemaNode) -> str: @@ -121,10 +123,9 @@ def get_default_look_in_description(schema_node: SchemaNode) -> str: if default_value: return default_value - description = schema_node.keywords.get(const.DESCRIPTION) + description = schema_keyword_to_str(schema_node, const.DESCRIPTION) if not description: return "" - description = description.literal match = re.match(DEFAULT_PATTERN, description) if not match: @@ -135,21 +136,11 @@ def get_default_look_in_description(schema_node: SchemaNode) -> str: def get_numeric_restrictions_text(schema_node: SchemaNode, before_value: str = "", after_value: str = "") -> str: """Filter. Get the text to display about restrictions on a numeric type(integer or number)""" - multiple_of = schema_node.keywords.get(const.MULTIPLE_OF) - if multiple_of: - multiple_of = multiple_of.literal - maximum = schema_node.keywords.get(const.MAXIMUM) - if maximum: - maximum = maximum.literal - exclusive_maximum = schema_node.keywords.get(const.EXCLUSIVE_MAXIMUM) - if exclusive_maximum: - exclusive_maximum = exclusive_maximum.literal - minimum = schema_node.keywords.get(const.MINIMUM) - if minimum: - minimum = minimum.literal - exclusive_minimum = schema_node.keywords.get(const.EXCLUSIVE_MINIMUM) - if exclusive_minimum: - exclusive_minimum = exclusive_minimum.literal + multiple_of = schema_keyword_convert_to_str(schema_node, const.MULTIPLE_OF) + maximum = schema_keyword_convert_to_str(schema_node, const.MAXIMUM) + exclusive_maximum = schema_keyword_convert_to_str(schema_node, const.EXCLUSIVE_MAXIMUM) + minimum = schema_keyword_convert_to_str(schema_node, const.MINIMUM) + exclusive_minimum = schema_keyword_convert_to_str(schema_node, const.EXCLUSIVE_MINIMUM) # Fix minimum and exclusive_minimum both there if minimum is not None and exclusive_minimum is not None: diff --git a/json_schema_for_humans/md_template.py b/json_schema_for_humans/md_template.py index 0528cfa4..f98ed26f 100644 --- a/json_schema_for_humans/md_template.py +++ b/json_schema_for_humans/md_template.py @@ -1,20 +1,17 @@ -from typing import Dict, List, Union, Optional -from urllib.parse import quote_plus, quote +from typing import Dict, List, Optional, Union +from urllib.parse import quote, quote_plus import jinja2 from json_schema_for_humans import const, jinja_filters from json_schema_for_humans.schema.schema_node import SchemaNode +from json_schema_for_humans.templating_utils import schema_keyword_to_int def get_numeric_maximum_restriction(schema_node: SchemaNode, default: str = "N/A") -> str: """Filter. Get the text to display about maximum restriction on a numeric type(integer or number)""" - maximum = schema_node.keywords.get(const.MAXIMUM) - if maximum: - maximum = maximum.literal - exclusive_maximum = schema_node.keywords.get(const.EXCLUSIVE_MAXIMUM) - if exclusive_maximum: - exclusive_maximum = exclusive_maximum.literal + maximum = schema_keyword_to_int(schema_node, const.MAXIMUM) + exclusive_maximum = schema_keyword_to_int(schema_node, const.EXCLUSIVE_MAXIMUM) # Fix maximum and exclusive_maximum both there if maximum is not None and exclusive_maximum is not None: @@ -36,17 +33,13 @@ def escape_for_table(example_text: Optional[str]) -> str: """Filter. escape characters('|', '`') in string to be inserted into markdown table""" if example_text is None: return "" - return example_text.translate(str.maketrans({"|": "\\|", "`": "\\`", "\n": "
"})) + return example_text.translate(str.maketrans({"|": "\\|", "`": "\\`", "\n": "
"})) # type:ignore[arg-type] def get_numeric_minimum_restriction(schema_node: SchemaNode, default: str = "N/A") -> str: """Filter. Get the text to display about minimum restriction on a numeric type (integer or number)""" - minimum = schema_node.keywords.get(const.MINIMUM) - if minimum: - minimum = minimum.literal - exclusive_minimum = schema_node.keywords.get(const.EXCLUSIVE_MINIMUM) - if exclusive_minimum: - exclusive_minimum = exclusive_minimum.literal + minimum = schema_keyword_to_int(schema_node, const.MINIMUM) + exclusive_minimum = schema_keyword_to_int(schema_node, const.EXCLUSIVE_MINIMUM) # Fix minimum and exclusive_minimum both there if minimum is not None and exclusive_minimum is not None: @@ -109,9 +102,9 @@ def restrictions_table(schema: SchemaNode) -> List[List[str]]: restrictions.append(["**Min length**", str(schema.kw_min_length.literal)]) if schema.kw_max_length: restrictions.append(["**Max length**", str(schema.kw_max_length.literal)]) - if schema.kw_pattern: - pattern_code = schema.kw_pattern.literal.replace("|", "\\|") - pattern_url = quote_plus(schema.kw_pattern.literal) + if schema.kw_pattern and schema.kw_pattern.literal_str: + pattern_code = schema.kw_pattern.literal_str.replace("|", "\\|") + pattern_url = quote_plus(schema.kw_pattern.literal_str) example_url = "" if len(schema.examples) > 0: example_url = "&testString=" + quote_plus(schema.examples[0]) @@ -121,11 +114,11 @@ def restrictions_table(schema: SchemaNode) -> List[List[str]]: f"```{pattern_code}``` [Test](https://regex101.com/?regex={pattern_url}{example_url})", ] ) - if schema.keywords.get("multipleOf"): - restrictions.append(["**Multiple of**", str(schema.keywords.get("multipleOf").literal)]) - if schema.keywords.get("minimum") or schema.keywords.get("exclusiveMinimum"): + if schema.kw_multiple_of and schema.kw_multiple_of.literal_to_str: + restrictions.append(["**Multiple of**", schema.kw_multiple_of.literal_to_str]) + if schema.kw_minimum or schema.kw_exclusive_minimum: restrictions.append(["**Minimum**", str(get_numeric_minimum_restriction(schema))]) - if schema.keywords.get("maximum") or schema.keywords.get("exclusiveMaximum"): + if schema.kw_maximum or schema.kw_exclusive_maximum: restrictions.append(["**Maximum**", str(get_numeric_maximum_restriction(schema))]) if len(restrictions) > 0: @@ -153,7 +146,7 @@ def array_items(schema: SchemaNode, title: str) -> List[List[str]]: def first_line_fixed(example_text: str) -> str: """first_line but replace ` with ' to avoid unbalanced `s in markdown""" - return jinja_filters.first_line(example_text).translate(str.maketrans({"`": "'"})) + return jinja_filters.first_line(example_text).translate(str.maketrans({"`": "'"})) # type:ignore[arg-type] def array_items_restrictions(schema: SchemaNode) -> List[List[str]]: @@ -350,6 +343,7 @@ def properties_table(self, schema: SchemaNode) -> List[List]: elif field == "Definition": # Link if sub_property.should_be_a_link(self.config): + assert sub_property.links_to line.append( "Same as " + self.format_link(sub_property.links_to.link_name, sub_property.links_to.html_id) @@ -392,6 +386,7 @@ def type_info_table(self, schema: SchemaNode) -> List[List]: schema_format = schema.format merged_schema = schema if not schema.refers_to else schema.refers_to_merged + assert merged_schema type_info.append(["", ""]) type_info.append( @@ -409,6 +404,7 @@ def type_info_table(self, schema: SchemaNode) -> List[List]: if default_value: type_info.append(["**Default**", f"`{default_value}`"]) if schema.should_be_a_link(self.config): + assert schema.links_to schema_link_name = schema.links_to.link_name html_id = schema.links_to.html_id type_info.append(["**Same definition as**", f"[{ schema_link_name }](#{ html_id })"]) @@ -465,10 +461,12 @@ def array_restrictions(self, schema: SchemaNode) -> List[List[str]]: ], [ "**Tuple validation**", - "See below" - if schema.array_items_def - or schema.tuple_validation_items - or (schema.kw_contains and schema.kw_contains.literal != {}) - else "N/A", + ( + "See below" + if schema.array_items_def + or schema.tuple_validation_items + or (schema.kw_contains and schema.kw_contains.literal != {}) + else "N/A" + ), ], ] diff --git a/json_schema_for_humans/schema/intermediate_representation.py b/json_schema_for_humans/schema/intermediate_representation.py index b720ec58..eb0dcafa 100644 --- a/json_schema_for_humans/schema/intermediate_representation.py +++ b/json_schema_for_humans/schema/intermediate_representation.py @@ -6,6 +6,7 @@ import urllib.parse from collections import defaultdict from pathlib import Path +from tempfile import _TemporaryFileWrapper from typing import Any, Dict, List, Optional, Tuple, Union import requests @@ -55,7 +56,7 @@ def _escape_html_id(config: GenerationConfiguration, html_id: str) -> str: def build_intermediate_representation( - schema_path: Union[str, Path, FileLikeType], + schema_path: Union[str, Path, FileLikeType, _TemporaryFileWrapper], config: GenerationConfiguration, loaded_schemas: Optional[Dict[str, Any]] = None, ) -> SchemaNode: @@ -98,11 +99,11 @@ def defaultdict_list() -> Dict[Any, List]: def _record_ref( resolved_references: Dict[str, Dict[str, SchemaNode]], schema_real_path: str, - path_to_element: List[Union[str, int]], + path_to_element: List[str], current_node: SchemaNode, ) -> None: """Record that the node is describing the schema at the provided path""" - path_to_element_str = "/".join(str(e) for e in path_to_element) + path_to_element_str = "/".join(path_to_element) resolved_references[schema_real_path][path_to_element_str] = current_node @@ -152,7 +153,7 @@ def _find_ref( return None, None # Check if the node has already been documented (also for infinite loops) - already_resolved = resolved_references.get(referenced_schema_path, {}).get(anchor_part, {}) + already_resolved: Optional[SchemaNode] = resolved_references.get(referenced_schema_path, {}).get(anchor_part) if already_resolved: # The node is already documented elsewhere. Let's link to it current_node.is_displayed = False @@ -187,6 +188,8 @@ def _find_ref( other_is_better = False i_am_better = True + assert other_user + # There is at least one other node having the same reference as the current node. if other_is_better: # The other referencing node is nearer to the user, so it will now be displayed @@ -316,7 +319,7 @@ def _resolve_ref( return new_reference, new_reference -def _get_schema_path(schema_path: Union[str, Path, FileLikeType]) -> str: +def _get_schema_path(schema_path: Union[str, Path, FileLikeType, _TemporaryFileWrapper]) -> str: if isinstance(schema_path, Path): return str(schema_path.resolve()) elif isinstance(schema_path, str): @@ -358,7 +361,7 @@ def _load_schema_from_uri(schema_uri: str, loaded_schemas: Dict[str, Any]) -> Op def _load_schema( - schema_uri: str, path_to_element: List[Union[str, int]], loaded_schemas: Dict[str, Any] + schema_uri: str, path_to_element: List[str], loaded_schemas: Dict[str, Any] ) -> Optional[Union[Dict, List, int, str]]: """Load the schema at the provided path or URL. @@ -369,20 +372,28 @@ def _load_schema( loaded_schema = _load_schema_from_uri(schema_uri, loaded_schemas) if path_to_element: + path_to_element_str = "/".join(path_to_element) for path_part in path_to_element: if not path_part: # Empty string continue - try: - path_part_int = int(path_part) - loaded_schema = loaded_schema[path_part_int] - continue - except (KeyError, ValueError): - # KeyError: The part looks like a int but it is a string (property named "0" for example - # ValueError: Normal case, the path part is a string + + if isinstance(loaded_schema, list): + try: + path_part_int = int(path_part) + loaded_schema = loaded_schema[path_part_int] + continue + except (IndexError, ValueError): + pass + elif isinstance(loaded_schema, dict): if path_part == ROOT_ID: return loaded_schema - loaded_schema = loaded_schema[path_part] + + if path_part in loaded_schema: + loaded_schema = loaded_schema[path_part] + continue + + logging.warning(f"Path {path_to_element_str} not found in schema at URI {schema_uri}") return loaded_schema @@ -409,7 +420,7 @@ def _build_node( breadcrumb_name: str, property_name: Optional[str], schema_file_path: str, - path_to_element: List[Union[str, int]], + path_to_element: List[str], schema: Optional[Union[Dict, List, int, str]], parent: Optional[SchemaNode] = None, parent_key: Optional[str] = None, @@ -488,15 +499,33 @@ def _build_node( # Examples are rendered in JSON because they will be represented that way in the documentation, # no need for a SchemaNode object if schema_key == SchemaKeyword.EXAMPLES.value: - keywords[schema_key] = [ - json.dumps(example, indent=4, separators=(",", ": "), ensure_ascii=False) - for example in schema_value - ] + keywords[schema_key] = SchemaNode( + depth=depth + 1, + file=schema_file_path, + path_to_element=path_to_element + [schema_key], + html_id=_add_html_id_part(html_id, schema_key), + array_items=[ + SchemaNode( + depth=depth + 1, + file=schema_file_path, + path_to_element=path_to_element + [schema_key], + html_id=_add_html_id_part(html_id, schema_key), + literal=json.dumps(example, indent=4, separators=(",", ": "), ensure_ascii=False), + ) + for example in schema_value + ], + ) continue # The default value will be printed as-is, no need for a SchemaNode object if schema_key == "default": - keywords[schema_key] = json.dumps(schema_value, ensure_ascii=False) + keywords[schema_key] = SchemaNode( + depth=depth + 1, + file=schema_file_path, + path_to_element=path_to_element + [schema_key], + html_id=_add_html_id_part(html_id, schema_key), + literal=json.dumps(schema_value, ensure_ascii=False), + ) continue if schema_key == SchemaKeyword.PROPERTIES.value: @@ -658,7 +687,7 @@ def _build_node( breadcrumb_name=f"item {i}", property_name=None, schema_file_path=schema_file_path, - path_to_element=path_to_element + [i], + path_to_element=path_to_element + [str(i)], schema=element, parent=new_node, ) diff --git a/json_schema_for_humans/schema/schema_importer.py b/json_schema_for_humans/schema/schema_importer.py index 73685725..521079dd 100644 --- a/json_schema_for_humans/schema/schema_importer.py +++ b/json_schema_for_humans/schema/schema_importer.py @@ -1,6 +1,6 @@ import os from pathlib import Path -from typing import Optional, List, Union +from typing import List, Optional, Union import click @@ -38,6 +38,9 @@ def get_schemas_to_render( if not result_file_or_dir: return [SchemaToRender(schema_file_path, None, None) for schema_file_path in schema_file_paths] + if isinstance(result_file_or_dir, str): + result_file_or_dir = Path(result_file_or_dir) + # Compute output_dir where additional files will be copied if necessary result_path_is_dir = Path(result_file_or_dir).is_dir() output_dir = result_file_or_dir if result_path_is_dir else result_file_or_dir.parent diff --git a/json_schema_for_humans/schema/schema_keyword.py b/json_schema_for_humans/schema/schema_keyword.py index fb9fad4e..9b103096 100644 --- a/json_schema_for_humans/schema/schema_keyword.py +++ b/json_schema_for_humans/schema/schema_keyword.py @@ -15,6 +15,11 @@ class SchemaKeyword(Enum): MIN_ITEMS = "minItems" MAX_LENGTH = "maxLength" MIN_LENGTH = "minLength" + MULTIPLE_OF = "multipleOf" + MINIMUM = "minimum" + EXCLUSIVE_MINIMUM = "exclusiveMinimum" + MAXIMUM = "maximum" + EXCLUSIVE_MAXIMUM = "exclusiveMaximum" PATTERN = "pattern" CONST = "const" ENUM = "enum" diff --git a/json_schema_for_humans/schema/schema_node.py b/json_schema_for_humans/schema/schema_node.py index 78bd486a..063577fd 100644 --- a/json_schema_for_humans/schema/schema_node.py +++ b/json_schema_for_humans/schema/schema_node.py @@ -3,9 +3,9 @@ from typing import Any, Dict, Iterable, Iterator, List, Optional, Set, Union, cast from json_schema_for_humans import const -from json_schema_for_humans.schema.schema_keyword import SchemaKeyword -from json_schema_for_humans.templating_utils import get_type_name from json_schema_for_humans.generation_configuration import GenerationConfiguration +from json_schema_for_humans.schema.schema_keyword import SchemaKeyword +from json_schema_for_humans.templating_utils import get_type_name, schema_keyword_to_str circular_references: Dict["SchemaNode", bool] = {} @@ -23,22 +23,22 @@ def __init__( self, depth: int, file: str, - path_to_element: List[Union[str, int]], + path_to_element: List[str], html_id: str, breadcrumb_name: str = "", ref_path="", - parent: "SchemaNode" = None, - parent_key: str = None, + parent: Optional["SchemaNode"] = None, + parent_key: Optional[str] = None, literal: Optional[Union[str, int, bool]] = None, - keywords: Dict[str, Union["SchemaNode", str, List[str]]] = None, - array_items: List["SchemaNode"] = None, + keywords: Optional[Dict[str, "SchemaNode"]] = None, + array_items: Optional[List["SchemaNode"]] = None, array_items_def: Optional["SchemaNode"] = None, array_additional_items_def: Optional["SchemaNode"] = None, array_additional_items: bool = False, tuple_validation_items: Optional[List["SchemaNode"]] = None, property_name: Optional[str] = None, - links_to: "SchemaNode" = None, - refers_to: "SchemaNode" = None, + links_to: Optional["SchemaNode"] = None, + refers_to: Optional["SchemaNode"] = None, is_displayed: bool = True, ): """ @@ -183,12 +183,12 @@ def required_properties(self) -> List[str]: if not required_properties: return [] - return [r.literal for r in required_properties.array_items] + return [r.literal_str for r in required_properties.array_items if r.literal_str] @property def is_required_property(self) -> bool: """Check if the current node represents a property and that this property is required by its parent""" - return self.parent and self.property_name in self.parent.required_properties + return bool(self.parent and self.property_name in self.parent.required_properties) @property def nodes_from_root(self) -> Iterator["SchemaNode"]: @@ -201,7 +201,7 @@ def nodes_from_root(self) -> Iterator["SchemaNode"]: if len(nodes) == 1: # Don't want to display "root" alone at the root - return [] + return iter([]) return reversed(nodes) @@ -218,7 +218,7 @@ def path_to_property(self) -> str: @property def flat_path(self) -> str: """String representation of the path to this node from the root of the current schema""" - return "/".join(str(part) for part in self.path_to_element) + return "/".join(self.path_to_element) @property def default_value(self) -> Optional[Any]: @@ -226,7 +226,7 @@ def _default_value(node: SchemaNode) -> Optional[Any]: default = node.keywords.get(const.DEFAULT) if isinstance(default, SchemaNode) and default.is_a_property_node: return None - return default + return default.literal if default else "" seen = set() current_node = self @@ -244,15 +244,12 @@ def _default_value(node: SchemaNode) -> Optional[Any]: def format(self) -> Optional[str]: format_val = self.keywords.get(const.FORMAT) if format_val: - return format_val.literal + return format_val.literal_str return None @property def description(self) -> str: - description = "" - description_node = self.keywords.get(const.DESCRIPTION) - if description_node: - description = description_node.literal + description = schema_keyword_to_str(self, const.DESCRIPTION) seen = set() current_node = self @@ -263,10 +260,24 @@ def description(self) -> str: referenced_schema = current_node.refers_to referenced_description_node = referenced_schema.keywords.get(const.DESCRIPTION) if referenced_description_node: - description = referenced_description_node.literal + description = referenced_description_node.literal_str current_node = referenced_schema - return description + return description or "" + + @property + def literal_str(self) -> Optional[str]: + """Return the literal value if it is a str""" + if isinstance(self.literal, str): + return self.literal + return None + + @property + def literal_to_str(self) -> Optional[str]: + """Return the literal value converted to str""" + if self.literal: + return str(self.literal) + return None @property def examples(self) -> List[str]: @@ -277,7 +288,7 @@ def examples(self) -> List[str]: if isinstance(possible_examples, SchemaNode) and possible_examples.is_a_property_node: return [] - return possible_examples + return [example.literal_str for example in possible_examples.array_items if example.literal_str] @property def refers_to_merged(self) -> Optional["SchemaNode"]: @@ -377,6 +388,26 @@ def kw_min_length(self) -> Optional["SchemaNode"]: def kw_max_length(self) -> Optional["SchemaNode"]: return self.get_keyword(SchemaKeyword.MAX_LENGTH) + @property + def kw_multiple_of(self) -> Optional["SchemaNode"]: + return self.get_keyword(SchemaKeyword.MULTIPLE_OF) + + @property + def kw_minimum(self) -> Optional["SchemaNode"]: + return self.get_keyword(SchemaKeyword.MINIMUM) + + @property + def kw_exclusive_minimum(self) -> Optional["SchemaNode"]: + return self.get_keyword(SchemaKeyword.EXCLUSIVE_MINIMUM) + + @property + def kw_maximum(self) -> Optional["SchemaNode"]: + return self.get_keyword(SchemaKeyword.MAXIMUM) + + @property + def kw_exclusive_maximum(self) -> Optional["SchemaNode"]: + return self.get_keyword(SchemaKeyword.EXCLUSIVE_MAXIMUM) + @property def kw_items(self) -> Optional[List["SchemaNode"]]: """items can be either an object either a list of object""" @@ -473,7 +504,10 @@ def enum_description(self, value: str) -> Optional["SchemaNode"]: meta_enum_node = self.get_keyword(SchemaKeyword.META_ENUM) if not meta_enum_node: return None - description = meta_enum_node.raw.get(value) + raw_node = meta_enum_node.raw + if not isinstance(raw_node, dict): + return None + description = raw_node.get(value) if not description: return None return description diff --git a/json_schema_for_humans/schema/schema_to_render.py b/json_schema_for_humans/schema/schema_to_render.py index 8ac4934a..182e05d8 100644 --- a/json_schema_for_humans/schema/schema_to_render.py +++ b/json_schema_for_humans/schema/schema_to_render.py @@ -1,6 +1,8 @@ from dataclasses import dataclass +from io import FileIO from pathlib import Path -from typing import Optional, Dict, Any, Union +from tempfile import _TemporaryFileWrapper +from typing import Any, Dict, Optional, TextIO, Union from json_schema_for_humans.const import FileLikeType from json_schema_for_humans.schema.intermediate_representation import build_intermediate_representation @@ -15,8 +17,8 @@ class NoResultFileError(EnvironmentError): class SchemaToRender: def __init__( self, - schema_file: Union[Path, FileLikeType], - result_file: Optional[Union[Path, FileLikeType]], + schema_file: Union[Path, FileLikeType, _TemporaryFileWrapper], + result_file: Optional[Union[Path, FileLikeType, _TemporaryFileWrapper]], output_dir: Optional[Path], ): self.schema_file = schema_file @@ -68,6 +70,8 @@ def _write_to_disk(self, rendered: str): if isinstance(self.result_file, Path): with self.result_file.open("w", encoding="utf-8") as fp: fp.write(rendered) + elif isinstance(self.result_file, FileIO): + self.result_file.write(rendered.encode("utf-8")) else: # self.result_file is file-like object self.result_file.write(rendered) diff --git a/json_schema_for_humans/template_renderer.py b/json_schema_for_humans/template_renderer.py index 6245916d..6f25b630 100644 --- a/json_schema_for_humans/template_renderer.py +++ b/json_schema_for_humans/template_renderer.py @@ -1,8 +1,9 @@ import re +from typing import Optional -import htmlmin +import htmlmin # type: ignore # No stubs available import jinja2 -import markdown2 +import markdown2 # type: ignore # No stubs available from jinja2 import FileSystemLoader, Template from jinja2.ext import loopcontrols @@ -21,7 +22,7 @@ def _minify(rendered: str, is_markdown: bool, is_html: bool) -> str: class TemplateRenderer: - def __init__(self, config: GenerationConfiguration, template: Template = None): + def __init__(self, config: GenerationConfiguration, template: Optional[Template] = None): self.config = config self.template = template or self._get_jinja_template() diff --git a/json_schema_for_humans/templating_utils.py b/json_schema_for_humans/templating_utils.py index e3cb1cca..8030f600 100644 --- a/json_schema_for_humans/templating_utils.py +++ b/json_schema_for_humans/templating_utils.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Type, Union, TYPE_CHECKING +from typing import TYPE_CHECKING, List, Optional, Type, Union from json_schema_for_humans import const @@ -6,10 +6,38 @@ from json_schema_for_humans.schema.schema_node import SchemaNode +def schema_keyword_to_int(schema_node: "SchemaNode", keyword: str) -> Optional[int]: + """Extract an integer from a schema keyword""" + keyword_value = schema_node.keywords.get(keyword) + if keyword_value: + keyword_value_literal = keyword_value.literal + if isinstance(keyword_value_literal, int): + return keyword_value_literal + return None + + +def schema_keyword_to_str(schema_node: "SchemaNode", keyword: str) -> Optional[str]: + """Extract a str from a schema keyword or return None""" + keyword_value = schema_node.keywords.get(keyword) + if keyword_value: + keyword_value_literal = keyword_value.literal + if isinstance(keyword_value_literal, str): + return keyword_value_literal + return None + + +def schema_keyword_convert_to_str(schema_node: "SchemaNode", keyword: str) -> Optional[str]: + """Extract and convert to str from a schema keyword or return None""" + keyword_value = schema_node.keywords.get(keyword) + if keyword_value: + return str(keyword_value.literal) + return None + + def get_type_name(schema_node: "SchemaNode") -> Optional[str]: """Filter. Return the type of a property taking into account the type of items for array and enum""" - def _python_type_to_json_type(python_type: Type[Union[str, int, float, bool, list, dict]]) -> str: + def _python_type_to_json_type(python_type: Type[Union[str, int, float, bool, list, dict, None]]) -> str: return { str: const.TYPE_STRING, int: const.TYPE_INTEGER, @@ -36,9 +64,10 @@ def _add_subtype_if_array(type_name: str): if not items: return type_name - subtype = items.keywords.get(const.TYPE) - if subtype: - subtype = subtype.literal + subtype_node = items.keywords.get(const.TYPE) + subtype: Optional[str] = None + if subtype_node: + subtype = subtype_node.literal_str if const.TYPE_ENUM in items.keywords: subtype = _enum_type(items.keywords[const.TYPE_ENUM].array_items) @@ -56,16 +85,20 @@ def _add_subtype_if_array(type_name: str): return _enum_type(schema_node.keywords[const.TYPE_ENUM].array_items) type_node = schema_node.keywords.get(const.TYPE) + type_names: List[str] = [] if type_node: if isinstance(type_node, str): type_names = [type_node] elif type_node.array_items: - type_names = [node.literal for node in type_node.array_items] - else: - type_names = [type_node.literal] + type_names = [node.literal_str for node in type_node.array_items if node.literal_str] + elif type_node.literal_str: + type_names = [type_node.literal_str] else: return None + if not type_names: + return None + type_names = [_add_subtype_if_array(type_name) for type_name in type_names] return ", ".join(type_names[:-1]) + (" or " if len(type_names) > 1 else "") + type_names[-1] diff --git a/poetry.lock b/poetry.lock index 944c2476..5bc9108b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "atomicwrites" @@ -12,40 +12,41 @@ files = [ [[package]] name = "attrs" -version = "23.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] +dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "beautifulsoup4" -version = "4.12.2" +version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, ] [package.dependencies] soupsieve = ">1.2" [package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] @@ -76,7 +77,6 @@ mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} -typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] @@ -87,13 +87,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -208,7 +208,6 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" @@ -252,35 +251,15 @@ files = [ [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, -] - -[[package]] -name = "importlib-metadata" -version = "6.7.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, - {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -294,13 +273,13 @@ files = [ [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.3" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, ] [package.dependencies] @@ -311,13 +290,13 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "markdown2" -version = "2.4.12" +version = "2.4.13" description = "A fast and complete Python implementation of Markdown" optional = false python-versions = ">=3.5, <4" files = [ - {file = "markdown2-2.4.12-py2.py3-none-any.whl", hash = "sha256:98f47591006f0ace0644cbece03fed6f3845513286f6c6e9f8bcf6a575174e2c"}, - {file = "markdown2-2.4.12.tar.gz", hash = "sha256:1bc8692696954d597778e0e25713c14ca56d87992070dedd95c17eddaf709204"}, + {file = "markdown2-2.4.13-py2.py3-none-any.whl", hash = "sha256:855bde5cbcceb9beda7c80efdf7f406c23e6079172c497fcfce22fdce998e892"}, + {file = "markdown2-2.4.13.tar.gz", hash = "sha256:18ceb56590da77f2c22382e55be48c15b3c8f0c71d6398def387275e6c347a9f"}, ] [package.extras] @@ -327,81 +306,90 @@ wavedrom = ["wavedrom"] [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] name = "marshmallow" -version = "3.19.0" +version = "3.21.2" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "marshmallow-3.19.0-py3-none-any.whl", hash = "sha256:93f0958568da045b0021ec6aeb7ac37c81bfcccbb9a0e7ed8559885070b3a19b"}, - {file = "marshmallow-3.19.0.tar.gz", hash = "sha256:90032c0fd650ce94b6ec6dc8dfeb0e3ff50c144586462c389b81a07205bedb78"}, + {file = "marshmallow-3.21.2-py3-none-any.whl", hash = "sha256:70b54a6282f4704d12c0a41599682c5c5450e843b9ec406308653b47c59648a1"}, + {file = "marshmallow-3.21.2.tar.gz", hash = "sha256:82408deadd8b33d56338d2182d455db632c6313aa2af61916672146bb32edc56"}, ] [package.dependencies] packaging = ">=17.0" [package.extras] -dev = ["flake8 (==5.0.4)", "flake8-bugbear (==22.10.25)", "mypy (==0.990)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.9)", "sphinx (==5.3.0)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["flake8 (==5.0.4)", "flake8-bugbear (==22.10.25)", "mypy (==0.990)", "pre-commit (>=2.4,<3.0)"] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -418,6 +406,53 @@ files = [ [package.dependencies] marshmallow = ">=2.0.0" +[[package]] +name = "mypy" +version = "1.10.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, + {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, + {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, + {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, + {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, + {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, + {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, + {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, + {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, + {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, + {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, + {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, + {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, + {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, + {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, + {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, + {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, + {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + [[package]] name = "mypy-extensions" version = "1.0.0" @@ -431,58 +466,53 @@ files = [ [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] name = "pathspec" -version = "0.11.2" +version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "platformdirs" -version = "4.0.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, - {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.7.1", markers = "python_version < \"3.8\""} - [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.2.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] @@ -528,7 +558,6 @@ files = [ atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" @@ -540,13 +569,13 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytz" -version = "2023.3.post1" +version = "2024.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] [[package]] @@ -621,13 +650,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "soupsieve" -version = "2.4.1" +version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, - {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, ] [[package]] @@ -653,64 +682,112 @@ files = [ ] [[package]] -name = "typed-ast" -version = "1.5.5" -description = "a fork of Python 2 and 3 ast modules with type comment support" +name = "types-beautifulsoup4" +version = "4.12.0.20240229" +description = "Typing stubs for beautifulsoup4" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +files = [ + {file = "types-beautifulsoup4-4.12.0.20240229.tar.gz", hash = "sha256:e37e4cfa11b03b01775732e56d2c010cb24ee107786277bae6bc0fa3e305b686"}, + {file = "types_beautifulsoup4-4.12.0.20240229-py3-none-any.whl", hash = "sha256:000cdddb8aee4effb45a04be95654de8629fb8594a4f2f1231cff81108977324"}, +] + +[package.dependencies] +types-html5lib = "*" + +[[package]] +name = "types-docutils" +version = "0.21.0.20240423" +description = "Typing stubs for docutils" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-docutils-0.21.0.20240423.tar.gz", hash = "sha256:7716ec6c68b5179b7ba1738cace2f1326e64df9f44b7ab08d9904d32c23fc15f"}, + {file = "types_docutils-0.21.0.20240423-py3-none-any.whl", hash = "sha256:7f6e84ba8fcd2454c5b8bb8d77384d091a901929cc2b31079316e10eb346580a"}, +] + +[[package]] +name = "types-html5lib" +version = "1.1.11.20240228" +description = "Typing stubs for html5lib" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-html5lib-1.1.11.20240228.tar.gz", hash = "sha256:22736b7299e605ec4ba539d48691e905fd0c61c3ea610acc59922232dc84cede"}, + {file = "types_html5lib-1.1.11.20240228-py3-none-any.whl", hash = "sha256:af5de0125cb0fe5667543b158db83849b22e25c0e36c9149836b095548bf1020"}, +] + +[[package]] +name = "types-pygments" +version = "2.17.0.20240310" +description = "Typing stubs for Pygments" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-Pygments-2.17.0.20240310.tar.gz", hash = "sha256:b1d97e905ce36343c7283b0319182ae6d4f967188f361f45502a18ae43e03e1f"}, + {file = "types_Pygments-2.17.0.20240310-py3-none-any.whl", hash = "sha256:b101ca9448aaff52af6966506f1fdd73b1e60a79b8a79a8bace3366cbf1f7ed9"}, +] + +[package.dependencies] +types-docutils = "*" +types-setuptools = "*" + +[[package]] +name = "types-pytz" +version = "2024.1.0.20240417" +description = "Typing stubs for pytz" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-pytz-2024.1.0.20240417.tar.gz", hash = "sha256:6810c8a1f68f21fdf0f4f374a432487c77645a0ac0b31de4bf4690cf21ad3981"}, + {file = "types_pytz-2024.1.0.20240417-py3-none-any.whl", hash = "sha256:8335d443310e2db7b74e007414e74c4f53b67452c0cb0d228ca359ccfba59659"}, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20240311" +description = "Typing stubs for PyYAML" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-PyYAML-6.0.12.20240311.tar.gz", hash = "sha256:a9e0f0f88dc835739b0c1ca51ee90d04ca2a897a71af79de9aec5f38cb0a5342"}, + {file = "types_PyYAML-6.0.12.20240311-py3-none-any.whl", hash = "sha256:b845b06a1c7e54b8e5b4c683043de0d9caf205e7434b3edc678ff2411979b8f6"}, +] + +[[package]] +name = "types-requests" +version = "2.31.0.20240406" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-requests-2.31.0.20240406.tar.gz", hash = "sha256:4428df33c5503945c74b3f42e82b181e86ec7b724620419a2966e2de604ce1a1"}, + {file = "types_requests-2.31.0.20240406-py3-none-any.whl", hash = "sha256:6216cdac377c6b9a040ac1c0404f7284bd13199c0e1bb235f4324627e8898cf5"}, +] + +[package.dependencies] +urllib3 = ">=2" + +[[package]] +name = "types-setuptools" +version = "69.5.0.20240423" +description = "Typing stubs for setuptools" +optional = false +python-versions = ">=3.8" files = [ - {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, - {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, - {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, - {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, - {file = "typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5"}, - {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c"}, - {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa"}, - {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f"}, - {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d"}, - {file = "typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5"}, - {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, - {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, - {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, - {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, - {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, + {file = "types-setuptools-69.5.0.20240423.tar.gz", hash = "sha256:a7ba908f1746c4337d13f027fa0f4a5bcad6d1d92048219ba792b3295c58586d"}, + {file = "types_setuptools-69.5.0.20240423-py3-none-any.whl", hash = "sha256:a4381e041510755a6c9210e26ad55b1629bc10237aeb9cb8b6bd24996b73db48"}, ] [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.11.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] @@ -730,37 +807,22 @@ typing-extensions = ">=3.7.4" [[package]] name = "urllib3" -version = "2.0.7" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] -[[package]] -name = "zipp" -version = "3.15.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.7" -files = [ - {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, - {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - [metadata] lock-version = "2.0" -python-versions = "^3.7" -content-hash = "0bc0bc8334b3b0641dff9f7ce6115212e73a066f75198a773ca90e8c61c2a3f1" +python-versions = "^3.8" +content-hash = "365de98ef1c44eb069b28f0b2df72c2778f048ee830166a26b38c4c2f68fea95" diff --git a/pyproject.toml b/pyproject.toml index efe1b90d..1f6c445c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ readme = "README.md" repository = "https://github.com/coveooss/json-schema-for-humans" classifiers = [ "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Development Status :: 4 - Beta", @@ -23,7 +23,7 @@ include = [ [tool.poetry.dependencies] -python = "^3.7" +python = "^3.8" click = "^8.0.1" dataclasses-json = "^0.5.6" htmlmin = "^0.1.12" @@ -34,12 +34,9 @@ pytz = "*" PyYAML = ">=5.4.1,<7" requests = "^2.31.0" MarkupSafe = "^2.0" - - -[tool.poetry.dev-dependencies] -beautifulsoup4 = "^4.10.0" -black = "^22.10" -pytest = "^6.2.5" +types-beautifulsoup4 = "^4.12.0.20240229" +types-pyyaml = "^6.0.12.20240311" +types-pytz = "^2024.1.0.20240417" @@ -47,6 +44,15 @@ pytest = "^6.2.5" generate-schema-doc = "json_schema_for_humans.cli:main" +[tool.poetry.group.dev.dependencies] +beautifulsoup4 = "^4.10.0" +black = "^22.10" +pytest = "^6.2.5" +mypy = "^1.10.0" +types-requests = "^2.31.0" +types-pygments = "^2.17.0.20240310" + + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" @@ -54,7 +60,7 @@ build-backend = "poetry.core.masonry.api" [tool.black] line-length = 120 -target-version = ["py36", "py37"] +target-version = ["py311"] include = "(json_schema_for_humans|tests).*\\.pyi?$|setup.py" exclude = """ ( diff --git a/tests/cli_test.py b/tests/cli_test.py index d97e147a..feaf2f34 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -9,8 +9,8 @@ from tests.test_utils import ( assert_css_and_js_copied, assert_css_and_js_not_copied, - get_test_case_path, get_nonexistent_output_path, + get_test_case_path, ) diff --git a/tests/generate_test.py b/tests/generate_test.py index 57a4e3ed..afafffe9 100644 --- a/tests/generate_test.py +++ b/tests/generate_test.py @@ -1,16 +1,18 @@ import os import tempfile -from typing import Dict, Any +from io import TextIOWrapper +from typing import Any, Dict, cast +from unittest.mock import Mock import pytest from bs4 import BeautifulSoup -from unittest.mock import Mock import tests.html_schema_doc_asserts -from json_schema_for_humans.schema.schema_to_render import SchemaToRender -from json_schema_for_humans.template_renderer import TemplateRenderer +from json_schema_for_humans.const import FileLikeType from json_schema_for_humans.generate import generate_from_file_object from json_schema_for_humans.generation_configuration import GenerationConfiguration +from json_schema_for_humans.schema.schema_to_render import SchemaToRender +from json_schema_for_humans.template_renderer import TemplateRenderer from tests.test_utils import generate_case current_dir = os.path.abspath(os.path.dirname(__file__)) diff --git a/tests/generation_configuration_test.py b/tests/generation_configuration_test.py index 4689292d..2f3835a3 100644 --- a/tests/generation_configuration_test.py +++ b/tests/generation_configuration_test.py @@ -1,5 +1,5 @@ -from json_schema_for_humans.generation_configuration import GenerationConfiguration from json_schema_for_humans.const import DocumentationTemplate +from json_schema_for_humans.generation_configuration import GenerationConfiguration def test_default_values() -> None: @@ -80,7 +80,7 @@ def test_override_template_md_options() -> None: # override badge_as_image key config = GenerationConfiguration( deprecated_from_description=True, - template_name=DocumentationTemplate.MD, + template_name=DocumentationTemplate.MD.value, template_md_options={"badge_as_image": "test"}, ) assert config.template_md_options is not None @@ -89,7 +89,7 @@ def test_override_template_md_options() -> None: # override badge_as_image key config = GenerationConfiguration( deprecated_from_description=True, - template_name=DocumentationTemplate.MD, + template_name=DocumentationTemplate.MD.value, template_md_options={"badge_as_image": True}, ) assert config.template_md_options is not None diff --git a/tests/html_schema_doc_asserts.py b/tests/html_schema_doc_asserts.py index 19320f9e..bda28063 100644 --- a/tests/html_schema_doc_asserts.py +++ b/tests/html_schema_doc_asserts.py @@ -1,6 +1,6 @@ -from typing import List, Optional +from typing import List, Optional, Union -from bs4 import BeautifulSoup, Tag +from bs4 import BeautifulSoup, NavigableString, Tag def assert_soup_results_text(soup: BeautifulSoup, class_name: str, texts: List[str]) -> None: @@ -28,7 +28,11 @@ def assert_enum_values(soup: BeautifulSoup, enum_values: List[List[str]]) -> Non def assert_title(soup: BeautifulSoup, title: str) -> None: """Assert the result file contains the provided title""" + assert soup.head + assert soup.head.title assert soup.head.title.string == title + assert soup.body + assert soup.body.h1 assert soup.body.h1.string == title @@ -98,7 +102,7 @@ def assert_basic_case(soup: BeautifulSoup) -> None: assert_required(soup, [False] * 4) -def get_ref_link(soup: BeautifulSoup, ref_html_id: str) -> Optional[Tag]: +def get_ref_link(soup: BeautifulSoup, ref_html_id: str) -> Optional[Union[Tag, NavigableString]]: """Get a link for a reused ref that redirects to a specified HTML id""" return soup.find("a", href=ref_html_id, class_="ref-link") diff --git a/tests/interface_test.py b/tests/interface_test.py index c9cb882b..0b44ccab 100644 --- a/tests/interface_test.py +++ b/tests/interface_test.py @@ -1,7 +1,7 @@ import logging import os from pathlib import Path -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch import pytest import yaml @@ -16,10 +16,7 @@ generate_from_schema, generate_schemas_doc, ) -from json_schema_for_humans.generation_configuration import ( - GenerationConfiguration, - CONFIG_DEPRECATION_MESSAGE, -) +from json_schema_for_humans.generation_configuration import CONFIG_DEPRECATION_MESSAGE, GenerationConfiguration from json_schema_for_humans.schema.schema_importer import get_schemas_to_render from json_schema_for_humans.template_renderer import TemplateRenderer from tests.html_schema_doc_asserts import assert_basic_case diff --git a/tests/md_utils_asserts.py b/tests/md_utils_asserts.py index 736b1dce..d713669d 100644 --- a/tests/md_utils_asserts.py +++ b/tests/md_utils_asserts.py @@ -1,9 +1,10 @@ import os +import re +from typing import Optional from json_schema_for_humans.generate import generate_from_schema from json_schema_for_humans.generation_configuration import GenerationConfiguration from tests.test_utils import get_test_case_path -import re GENERATED_TIMESTAMP_REGEXP = re.compile( r"on [0-9]{4}-[0-9]{2}-[0-9]{2} at [0-9]{2}:[0-9]{2}:[0-9]{2} \+[0-9]{4}", re.IGNORECASE | re.MULTILINE @@ -15,7 +16,7 @@ def get_expected_test_case_path(self, example_dir: str, name: str) -> str: """Get the expected md for a test case""" return os.path.realpath(os.path.join(example_dir, f"{name}.md")) - def generate_case(self, case_name: str, config: GenerationConfiguration = None) -> str: + def generate_case(self, case_name: str, config: Optional[GenerationConfiguration] = None) -> str: """Get the generated markdown schema string for a given schema test case""" return generate_from_schema(get_test_case_path(case_name), None, config=config) @@ -29,7 +30,7 @@ def get_expected_case(self, example_dir: str, test_case: str, case_name: str) -> return content def assert_case_equals( - self, example_dir: str, test_case: str, case_name: str, config: GenerationConfiguration = None + self, example_dir: str, test_case: str, case_name: str, config: Optional[GenerationConfiguration] = None ) -> None: content = self.generate_case(case_name, config) expected_content = self.get_expected_case(example_dir, test_case, case_name) diff --git a/tests/schema/schema_importer_test.py b/tests/schema/schema_importer_test.py index a4a65a09..8157774b 100644 --- a/tests/schema/schema_importer_test.py +++ b/tests/schema/schema_importer_test.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from pathlib import Path -from typing import Union, Dict, Any, List, Tuple +from typing import Any, Dict, List, Tuple, Union import pytest from _pytest.monkeypatch import MonkeyPatch @@ -78,8 +78,8 @@ def test_get_schema_paths(is_path: bool, case: GetSchemaPathsTestCase, tmpdir: P monkeypatch.chdir(tmpdir) - get_schema_path_arg = Path(case.input_path) if is_path else str(case.input_path) - assert [str(p) for p in _get_schema_paths(get_schema_path_arg)] == [ + get_schema_path_arg = Path(case.input_path) if is_path else case.input_path + assert [str(p) for p in _get_schema_paths(get_schema_path_arg)] == [ # type:ignore[arg-type] # Convert to path and back to ensure normalization (e.g. running on Windows) str(Path(p).absolute()) for p in case.expected_schema_paths diff --git a/tests/test_utils.py b/tests/test_utils.py index 9a0174f9..b98c5d24 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,9 +1,10 @@ import os from pathlib import Path +from typing import Optional from bs4 import BeautifulSoup -from json_schema_for_humans.const import DEFAULT_JS_FILE_NAME, DEFAULT_CSS_FILE_NAME +from json_schema_for_humans.const import DEFAULT_CSS_FILE_NAME, DEFAULT_JS_FILE_NAME from json_schema_for_humans.generate import generate_from_schema from json_schema_for_humans.generation_configuration import GenerationConfiguration @@ -39,7 +40,7 @@ def get_nonexistent_output_path(name: str) -> str: return os.path.realpath(os.path.join(parent_dir, "not", "a", "path", f"{name}.html")) -def generate_case(case_name: str, config: GenerationConfiguration = None) -> BeautifulSoup: +def generate_case(case_name: str, config: Optional[GenerationConfiguration] = None) -> BeautifulSoup: """Get the BeautifulSoup object for a test case""" return BeautifulSoup( generate_from_schema(get_test_case_path(case_name), None, config=config), diff --git a/tox.ini b/tox.ini index 20889869..5ae1b522 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{3.7,3.8,3.9,3.10} +envlist = py{3.8,3.9,3.10,3.11,3.12} isolated_build = True allowlist_externals = poetry @@ -10,7 +10,9 @@ commands_pre = allowlist_externals = {[tox]allowlist_externals} black + mypy pytest commands = black . --line-length 120 --check + mypy . pytest {posargs}