Skip to content

Commit

Permalink
chore!: Drop support for Python 3.7 (#241)
Browse files Browse the repository at this point in the history
chore: Add support for Python 3.12
ci: Add mypy and fix a bunch of problems
  • Loading branch information
dblanchette authored May 3, 2024
1 parent 160c36a commit f8529c5
Show file tree
Hide file tree
Showing 26 changed files with 576 additions and 393 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 12 additions & 8 deletions docs/generate_examples.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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")
Expand Down Expand Up @@ -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"]
Expand All @@ -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)
Expand Down
23 changes: 13 additions & 10 deletions json_schema_for_humans/cli.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions json_schema_for_humans/const.py
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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"
Expand Down
13 changes: 8 additions & 5 deletions json_schema_for_humans/generate.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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,
Expand All @@ -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],
Expand All @@ -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(
Expand All @@ -66,16 +69,16 @@ 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,
expand_buttons: bool = False,
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.
Expand Down
13 changes: 6 additions & 7 deletions json_schema_for_humans/generation_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down Expand Up @@ -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]
53 changes: 22 additions & 31 deletions json_schema_for_humans/jinja_filters.py
Original file line number Diff line number Diff line change
@@ -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 - `([^`]+)`\])"
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down
Loading

0 comments on commit f8529c5

Please sign in to comment.