|
from __future__ import annotations |
|
|
|
import inspect |
|
import re |
|
import types |
|
import typing |
|
from subprocess import PIPE, Popen |
|
|
|
|
|
def find_first_non_return_key(some_dict): |
|
"""Finds the first key in a dictionary that is not "return".""" |
|
for key, value in some_dict.items(): |
|
if key != "return": |
|
return value |
|
return None |
|
|
|
|
|
def format(code: str, type: str): |
|
"""Formats code using ruff.""" |
|
if type == "value": |
|
code = f"value = {code}" |
|
|
|
ruff_args = ["ruff", "format", "-", "--line-length=60"] |
|
|
|
process = Popen( |
|
ruff_args, |
|
stdin=PIPE, |
|
stdout=PIPE, |
|
stderr=PIPE, |
|
universal_newlines=True, |
|
) |
|
|
|
formatted_code, err = process.communicate(input=str(code)) |
|
|
|
if type == "value": |
|
formatted_code = re.sub( |
|
r"^\s*value =\s*", "", formatted_code, flags=re.MULTILINE |
|
) |
|
|
|
stripped_source = re.search(r"^\s*\((.*)\)\s*$", formatted_code, re.DOTALL) |
|
|
|
if stripped_source: |
|
return stripped_source.group(1).strip() |
|
elif formatted_code.strip() == "": |
|
return code |
|
else: |
|
return formatted_code.strip() |
|
|
|
|
|
def get_param_name(param): |
|
"""Gets the name of a parameter.""" |
|
|
|
if isinstance(param, str): |
|
return f'"{param}"' |
|
if inspect.isclass(param) and param.__module__ == "builtins": |
|
p = getattr(param, "__name__", None) |
|
if p is None and inspect.isclass(param): |
|
p = f"{param.__module__}.{param.__name__}" |
|
return p |
|
|
|
if inspect.isclass(param): |
|
return f"{param.__module__}.{param.__name__}" |
|
|
|
param_name = getattr(param, "__name__", None) |
|
|
|
if param_name is None: |
|
param_name = str(param) |
|
|
|
return param_name |
|
|
|
|
|
def format_none(value): |
|
"""Formats None and NonType values.""" |
|
if value is None or value is type(None) or value == "None" or value == "NoneType": |
|
return "None" |
|
return value |
|
|
|
|
|
def format_value(value): |
|
"""Formats a value.""" |
|
if value is None: |
|
return "None" |
|
if isinstance(value, str): |
|
return f'"{value}"' |
|
return str(value) |
|
|
|
|
|
def get_parameter_docstring(docstring: str, parameter_name: str): |
|
"""Gets the docstring for a parameter.""" |
|
pattern = rf"\b{parameter_name}\b:[ \t]*(.*?)(?=\n|$)" |
|
|
|
match = re.search(pattern, docstring, flags=re.DOTALL) |
|
if match: |
|
return match.group(1).strip() |
|
|
|
return None |
|
|
|
|
|
def get_return_docstring(docstring: str): |
|
"""Gets the docstring for a return value.""" |
|
pattern = r"\bReturn(?:s){0,1}\b:[ \t\n]*(.*?)(?=\n|$)" |
|
|
|
match = re.search(pattern, docstring, flags=re.DOTALL | re.IGNORECASE) |
|
if match: |
|
return match.group(1).strip() |
|
|
|
return None |
|
|
|
|
|
def add_value(obj: dict, key: str, value: typing.Any): |
|
"""Adds a value to a dictionary.""" |
|
type = "value" if key == "default" else "type" |
|
|
|
obj[key] = format(value, type) |
|
|
|
return obj |
|
|
|
|
|
def set_deep(dictionary: dict, keys: list[str], value: typing.Any): |
|
"""Sets a value in a nested dictionary for a key path that may not exist""" |
|
for key in keys[:-1]: |
|
dictionary = dictionary.setdefault(key, {}) |
|
dictionary[keys[-1]] = value |
|
|
|
|
|
def get_deep(dictionary: dict, keys: list[str], default=None): |
|
"""Gets a value from a nested dictionary without erroring if the key doesn't exist.""" |
|
try: |
|
for key in keys: |
|
dictionary = dictionary[key] |
|
return dictionary |
|
except KeyError: |
|
return default |
|
|
|
|
|
def get_type_arguments(type_hint) -> tuple: |
|
"""Gets the type arguments for a type hint.""" |
|
if hasattr(type_hint, "__args__"): |
|
return type_hint.__args__ |
|
elif hasattr(type_hint, "__extra__"): |
|
return type_hint.__extra__.__args__ |
|
else: |
|
return typing.get_args(type_hint) |
|
|
|
|
|
def get_container_name(arg): |
|
"""Gets a human readable name for a type.""" |
|
if inspect.isclass(arg): |
|
return arg.__name__ |
|
if isinstance(arg, types.GenericAlias): |
|
return arg.__origin__.__name__ |
|
elif isinstance(arg, types.UnionType): |
|
return "Union" |
|
elif getattr(arg, "__origin__", None) is typing.Literal: |
|
return "Literal" |
|
else: |
|
return str(arg) |
|
|
|
|
|
def format_type(_type: list[typing.Any]): |
|
"""Pretty formats a possibly nested type hint.""" |
|
|
|
s = [] |
|
_current = None |
|
for t in _type: |
|
if isinstance(t, str): |
|
_current = format_none(t) |
|
continue |
|
|
|
elif isinstance(t, list): |
|
if len(t) == 0: |
|
continue |
|
s.append(f"{format_type(t)}") |
|
else: |
|
s.append(t) |
|
if len(s) == 0: |
|
return _current |
|
elif _current in ("Literal", "Union"): |
|
return "| ".join(s) |
|
else: |
|
return f"{_current}[{','.join(s)}]" |
|
|
|
|
|
def get_type_hints(param, module): |
|
"""Gets the type hints for a parameter.""" |
|
|
|
def extract_args( |
|
arg, |
|
module_name_prefix, |
|
additional_interfaces, |
|
user_fn_refs: list[str], |
|
append=True, |
|
arg_of=None, |
|
): |
|
"""Recursively extracts the arguments from a type hint.""" |
|
arg_names = [] |
|
args = get_type_arguments(arg) |
|
|
|
|
|
if inspect.isclass(arg) and arg.__module__.startswith(module_name_prefix): |
|
|
|
|
|
source_code = inspect.getsource(arg) |
|
source_code = format( |
|
re.sub(r"(\"\"\".*?\"\"\")", "", source_code, flags=re.DOTALL), "other" |
|
) |
|
|
|
if arg_of is not None: |
|
refs = get_deep(additional_interfaces, [arg_of, "refs"]) |
|
|
|
if refs is None: |
|
refs = [arg.__name__] |
|
elif isinstance(refs, list) and arg.__name__ not in refs: |
|
refs.append(arg.__name__) |
|
|
|
set_deep(additional_interfaces, [arg_of, "refs"], refs) |
|
|
|
if get_deep(additional_interfaces, [arg.__name__, "source"]) is None: |
|
set_deep(additional_interfaces, [arg.__name__, "source"], source_code) |
|
|
|
for field_type in typing.get_type_hints(arg).values(): |
|
|
|
new_args = extract_args( |
|
field_type, |
|
module_name_prefix, |
|
additional_interfaces, |
|
user_fn_refs, |
|
False, |
|
arg.__name__, |
|
) |
|
|
|
if len(new_args) > 0: |
|
arg_names.append(new_args) |
|
|
|
if append: |
|
arg_names.append(arg.__name__) |
|
if arg.__name__ not in user_fn_refs: |
|
user_fn_refs.append(arg.__name__) |
|
elif len(args) > 0: |
|
if append: |
|
arg_names.append(get_container_name(arg)) |
|
for inner_arg in list(args): |
|
new_args = extract_args( |
|
inner_arg, |
|
module_name_prefix, |
|
additional_interfaces, |
|
user_fn_refs, |
|
append, |
|
arg_of, |
|
) |
|
|
|
if len(new_args) > 0: |
|
arg_names.append(new_args) |
|
else: |
|
if append: |
|
arg_names.append(get_param_name(arg)) |
|
return arg_names |
|
|
|
module_name_prefix = module.__name__ + "." |
|
additional_interfaces = {} |
|
user_fn_refs = [] |
|
|
|
args = extract_args( |
|
param, |
|
module_name_prefix, |
|
additional_interfaces, |
|
user_fn_refs, |
|
True, |
|
) |
|
|
|
formatted_type = format_type(args) |
|
|
|
return (formatted_type, additional_interfaces, user_fn_refs) |
|
|
|
|
|
def extract_docstrings(module): |
|
docs = {} |
|
global_type_mode = "complex" |
|
for name, obj in inspect.getmembers(module): |
|
|
|
if name.startswith("_"): |
|
continue |
|
|
|
if inspect.isfunction(obj) or inspect.isclass(obj): |
|
docs[name] = {} |
|
|
|
main_docstring = inspect.getdoc(obj) or "" |
|
cleaned_docstring = str.join( |
|
"\n", |
|
[s for s in main_docstring.split("\n") if not re.match(r"^\S+:", s)], |
|
) |
|
|
|
docs[name]["description"] = cleaned_docstring |
|
docs[name]["members"] = {} |
|
docs["__meta__"] = {"additional_interfaces": {}} |
|
for member_name, member in inspect.getmembers(obj): |
|
if inspect.ismethod(member) or inspect.isfunction(member): |
|
|
|
if member_name not in ("__init__", "preprocess", "postprocess"): |
|
continue |
|
|
|
docs[name]["members"][member_name] = {} |
|
|
|
member_docstring = inspect.getdoc(member) or "" |
|
type_mode = "complex" |
|
try: |
|
hints = typing.get_type_hints(member) |
|
except Exception: |
|
type_mode = "simple" |
|
hints = member.__annotations__ |
|
global_type_mode = "simple" |
|
|
|
signature = inspect.signature(member) |
|
|
|
|
|
for param_name, param in hints.items(): |
|
if ( |
|
param_name == "return" and member_name == "postprocess" |
|
) or (param_name != "return" and member_name == "preprocess"): |
|
continue |
|
|
|
if type_mode == "simple": |
|
arg_names = hints.get(param_name, "") |
|
additional_interfaces = {} |
|
user_fn_refs = [] |
|
else: |
|
( |
|
arg_names, |
|
additional_interfaces, |
|
user_fn_refs, |
|
) = get_type_hints(param, module) |
|
|
|
|
|
docs["__meta__"]["additional_interfaces"].update( |
|
additional_interfaces |
|
) |
|
|
|
docs[name]["members"][member_name][param_name] = {} |
|
|
|
if param_name == "return": |
|
docstring = get_return_docstring(member_docstring) |
|
else: |
|
docstring = get_parameter_docstring( |
|
member_docstring, param_name |
|
) |
|
|
|
add_value( |
|
docs[name]["members"][member_name][param_name], |
|
"type", |
|
arg_names, |
|
) |
|
|
|
if signature.parameters.get(param_name, None) is not None: |
|
default_value = signature.parameters[param_name].default |
|
if default_value is not inspect._empty: |
|
add_value( |
|
docs[name]["members"][member_name][param_name], |
|
"default", |
|
format_value(default_value), |
|
) |
|
|
|
docs[name]["members"][member_name][param_name][ |
|
"description" |
|
] = docstring |
|
|
|
|
|
if member_name in ("postprocess", "preprocess"): |
|
docs[name]["members"][member_name]["value"] = ( |
|
find_first_non_return_key( |
|
docs[name]["members"][member_name] |
|
) |
|
) |
|
additional_refs = get_deep( |
|
docs, ["__meta__", "user_fn_refs", name] |
|
) |
|
if additional_refs is None: |
|
set_deep( |
|
docs, |
|
["__meta__", "user_fn_refs", name], |
|
set(user_fn_refs), |
|
) |
|
else: |
|
additional_refs = set(additional_refs) |
|
additional_refs.update(user_fn_refs) |
|
set_deep( |
|
docs, |
|
["__meta__", "user_fn_refs", name], |
|
additional_refs, |
|
) |
|
if member_name == "EVENTS": |
|
docs[name]["events"] = {} |
|
if isinstance(member, list): |
|
for event in member: |
|
docs[name]["events"][str(event)] = { |
|
"type": None, |
|
"default": None, |
|
"description": event.doc.replace( |
|
"{{ component }}", name |
|
), |
|
} |
|
final_user_fn_refs = get_deep(docs, ["__meta__", "user_fn_refs", name]) |
|
if final_user_fn_refs is not None: |
|
set_deep(docs, ["__meta__", "user_fn_refs", name], list(final_user_fn_refs)) |
|
|
|
return (docs, global_type_mode) |
|
|
|
|
|
class AdditionalInterface(typing.TypedDict): |
|
refs: list[str] |
|
source: str |
|
|
|
|
|
def make_js( |
|
interfaces: dict[str, AdditionalInterface] | None = None, |
|
user_fn_refs: dict[str, list[str]] | None = None, |
|
): |
|
"""Makes the javascript code for the additional interfaces.""" |
|
js_obj_interfaces = "{" |
|
if interfaces is not None: |
|
for interface_name, interface in interfaces.items(): |
|
js_obj_interfaces += f""" |
|
{interface_name}: {interface.get("refs", None) or "[]"}, """ |
|
js_obj_interfaces += "}" |
|
|
|
js_obj_user_fn_refs = "{" |
|
if user_fn_refs is not None: |
|
for class_name, refs in user_fn_refs.items(): |
|
js_obj_user_fn_refs += f""" |
|
{class_name}: {refs}, """ |
|
|
|
js_obj_user_fn_refs += "}" |
|
|
|
return rf"""function() {{ |
|
const refs = {js_obj_interfaces}; |
|
const user_fn_refs = {js_obj_user_fn_refs}; |
|
requestAnimationFrame(() => {{ |
|
|
|
Object.entries(user_fn_refs).forEach(([key, refs]) => {{ |
|
if (refs.length > 0) {{ |
|
const el = document.querySelector(`.${{key}}-user-fn`); |
|
if (!el) return; |
|
refs.forEach(ref => {{ |
|
el.innerHTML = el.innerHTML.replace( |
|
new RegExp("\\b"+ref+"\\b", "g"), |
|
`<a href="#h-${{ref.toLowerCase()}}">${{ref}}</a>` |
|
); |
|
}}) |
|
}} |
|
}}) |
|
|
|
Object.entries(refs).forEach(([key, refs]) => {{ |
|
if (refs.length > 0) {{ |
|
const el = document.querySelector(`.${{key}}`); |
|
if (!el) return; |
|
refs.forEach(ref => {{ |
|
el.innerHTML = el.innerHTML.replace( |
|
new RegExp("\\b"+ref+"\\b", "g"), |
|
`<a href="#h-${{ref.toLowerCase()}}">${{ref}}</a>` |
|
); |
|
}}) |
|
}} |
|
}}) |
|
}}) |
|
}} |
|
""" |
|
|
|
|
|
def render_additional_interfaces(interfaces): |
|
"""Renders additional helper classes or types that were extracted earlier.""" |
|
|
|
source = "" |
|
for interface_name, interface in interfaces.items(): |
|
source += f""" |
|
code_{interface_name} = gr.Markdown(\"\"\" |
|
## `{interface_name}` |
|
```python |
|
{interface["source"]} |
|
```\"\"\", elem_classes=["md-custom", "{interface_name}"], header_links=True) |
|
""" |
|
return source |
|
|
|
|
|
def render_additional_interfaces_markdown(interfaces): |
|
"""Renders additional helper classes or types that were extracted earlier.""" |
|
|
|
source = "" |
|
for interface_name, interface in interfaces.items(): |
|
source += f""" |
|
## `{interface_name}` |
|
```python |
|
{interface["source"]} |
|
``` |
|
""" |
|
return source |
|
|
|
|
|
def render_version_badge(pypi_exists, local_version, name): |
|
"""Renders a version badge for the package. PyPi badge if it exists, otherwise a static badge.""" |
|
if pypi_exists: |
|
return f"""<a href="https://pypi.org/project/{name}/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/{name}"></a>""" |
|
else: |
|
return f"""<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%20{local_version}%20-%20orange">""" |
|
|
|
|
|
def render_github_badge(repo): |
|
"""Renders a github badge for the package if a repo is specified.""" |
|
if repo is None: |
|
return "" |
|
else: |
|
return f"""<a href="{repo}/issues" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/Issues-white?logo=github&logoColor=black"></a>""" |
|
|
|
|
|
def render_discuss_badge(space): |
|
"""Renders a discuss badge for the package if a space is specified.""" |
|
if space is None: |
|
return "" |
|
else: |
|
return f"""<a href="{space}/discussions" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/%F0%9F%A4%97%20Discuss-%23097EFF?style=flat&logoColor=black"></a>""" |
|
|
|
|
|
def render_class_events(events: dict, name): |
|
"""Renders the events for a class.""" |
|
if len(events) == 0: |
|
return "" |
|
|
|
else: |
|
return f""" |
|
gr.Markdown("### Events") |
|
gr.ParamViewer(value=_docs["{name}"]["events"], linkify={["Event"]}) |
|
|
|
""" |
|
|
|
|
|
def make_user_fn( |
|
class_name, |
|
user_fn_input_type, |
|
user_fn_input_description, |
|
user_fn_output_type, |
|
user_fn_output_description, |
|
): |
|
"""Makes the user function for the class.""" |
|
if ( |
|
user_fn_input_type is None |
|
and user_fn_output_type is None |
|
and user_fn_input_description is None |
|
and user_fn_output_description is None |
|
): |
|
return "" |
|
|
|
md = """ |
|
gr.Markdown(\"\"\" |
|
|
|
### User function |
|
|
|
The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both). |
|
|
|
- When used as an Input, the component only impacts the input signature of the user function. |
|
- When used as an output, the component only impacts the return signature of the user function. |
|
|
|
The code snippet below is accurate in cases where the component is used as both an input and an output. |
|
|
|
""" |
|
|
|
md += ( |
|
f"- **As input:** Is passed, {format_description(user_fn_input_description)}\n" |
|
if user_fn_input_description |
|
else "" |
|
) |
|
|
|
md += ( |
|
f"- **As output:** Should return, {format_description(user_fn_output_description)}" |
|
if user_fn_output_description |
|
else "" |
|
) |
|
|
|
if user_fn_input_type is not None or user_fn_output_type is not None: |
|
md += f""" |
|
|
|
```python |
|
def predict( |
|
value: {user_fn_input_type or "Unknown"} |
|
) -> {user_fn_output_type or "Unknown"}: |
|
return value |
|
```""" |
|
return f"""{md} |
|
\"\"\", elem_classes=["md-custom", "{class_name}-user-fn"], header_links=True) |
|
""" |
|
|
|
|
|
def format_description(description): |
|
description = description[0].lower() + description[1:] |
|
description = description.rstrip(".") + "." |
|
return description |
|
|
|
|
|
def make_user_fn_markdown( |
|
user_fn_input_type, |
|
user_fn_input_description, |
|
user_fn_output_type, |
|
user_fn_output_description, |
|
): |
|
"""Makes the user function for the class.""" |
|
if ( |
|
user_fn_input_type is None |
|
and user_fn_output_type is None |
|
and user_fn_input_description is None |
|
and user_fn_output_description is None |
|
): |
|
return "" |
|
|
|
md = """ |
|
### User function |
|
|
|
The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both). |
|
|
|
- When used as an Input, the component only impacts the input signature of the user function. |
|
- When used as an output, the component only impacts the return signature of the user function. |
|
|
|
The code snippet below is accurate in cases where the component is used as both an input and an output. |
|
|
|
""" |
|
|
|
md += ( |
|
f"- **As output:** Is passed, {format_description(user_fn_input_description)}\n" |
|
if user_fn_input_description |
|
else "" |
|
) |
|
|
|
md += ( |
|
f"- **As input:** Should return, {format_description(user_fn_output_description)}" |
|
if user_fn_output_description |
|
else "" |
|
) |
|
|
|
if user_fn_input_type is not None or user_fn_output_type is not None: |
|
md += f""" |
|
|
|
```python |
|
def predict( |
|
value: {user_fn_input_type or "Unknown"} |
|
) -> {user_fn_output_type or "Unknown"}: |
|
return value |
|
``` |
|
""" |
|
return md |
|
|
|
|
|
def render_class_events_markdown(events): |
|
"""Renders the events for a class.""" |
|
if len(events) == 0: |
|
return "" |
|
|
|
event_table = """ |
|
### Events |
|
|
|
| name | description | |
|
|:-----|:------------| |
|
""" |
|
|
|
for event_name, event in events.items(): |
|
event_table += f"| `{event_name}` | {event['description']} |\n" |
|
|
|
return event_table |
|
|
|
|
|
def render_class_docs(exports, docs): |
|
"""Renders the class documentation for the package.""" |
|
docs_classes = "" |
|
for class_name in exports: |
|
user_fn_input_type = get_deep( |
|
docs, [class_name, "members", "preprocess", "return", "type"] |
|
) |
|
user_fn_input_description = get_deep( |
|
docs, [class_name, "members", "preprocess", "return", "description"] |
|
) |
|
user_fn_output_type = get_deep( |
|
docs, [class_name, "members", "postprocess", "value", "type"] |
|
) |
|
user_fn_output_description = get_deep( |
|
docs, [class_name, "members", "postprocess", "value", "description"] |
|
) |
|
|
|
linkify = get_deep(docs, ["__meta__", "additional_interfaces"], {}) or {} |
|
|
|
docs_classes += f""" |
|
gr.Markdown(\"\"\" |
|
## `{class_name}` |
|
|
|
### Initialization |
|
\"\"\", elem_classes=["md-custom"], header_links=True) |
|
|
|
gr.ParamViewer(value=_docs["{class_name}"]["members"]["__init__"], linkify={list(linkify.keys())}) |
|
|
|
{render_class_events(docs[class_name].get("events", None), class_name)} |
|
|
|
{make_user_fn( |
|
class_name, |
|
user_fn_input_type, |
|
user_fn_input_description, |
|
user_fn_output_type, |
|
user_fn_output_description, |
|
)} |
|
""" |
|
return docs_classes |
|
|
|
|
|
html = """ |
|
<table> |
|
<thead> |
|
<tr> |
|
<th align="left">name</th> |
|
<th align="left">type</th> |
|
<th align="left">default</th> |
|
<th align="left">description</th> |
|
</tr> |
|
</thead> |
|
<tbody><tr> |
|
<td align="left"><code>value</code></td> |
|
<td align="left"><code>list[Parameter] | None</code></td> |
|
<td align="left"><code>None</code></td> |
|
<td align="left">A list of dictionaries with keys "type", "description", and "default" for each parameter.</td> |
|
</tr> |
|
</tbody></table> |
|
""" |
|
|
|
|
|
def render_param_table(params): |
|
"""Renders the parameter table for the package.""" |
|
table = """<table> |
|
<thead> |
|
<tr> |
|
<th align="left">name</th> |
|
<th align="left" style="width: 25%;">type</th> |
|
<th align="left">default</th> |
|
<th align="left">description</th> |
|
</tr> |
|
</thead> |
|
<tbody>""" |
|
|
|
|
|
|
|
|
|
for param_name, param in params.items(): |
|
table += f""" |
|
<tr> |
|
<td align="left"><code>{param_name}</code></td> |
|
<td align="left" style="width: 25%;"> |
|
|
|
```python |
|
{param["type"]} |
|
``` |
|
|
|
</td> |
|
<td align="left"><code>{param["default"]}</code></td> |
|
<td align="left">{param['description']}</td> |
|
</tr> |
|
""" |
|
return table + "</tbody></table>" |
|
|
|
|
|
def render_class_docs_markdown(exports, docs): |
|
"""Renders the class documentation for the package.""" |
|
docs_classes = "" |
|
for class_name in exports: |
|
user_fn_input_type = get_deep( |
|
docs, [class_name, "members", "preprocess", "return", "type"] |
|
) |
|
user_fn_input_description = get_deep( |
|
docs, [class_name, "members", "preprocess", "return", "description"] |
|
) |
|
user_fn_output_type = get_deep( |
|
docs, [class_name, "members", "postprocess", "value", "type"] |
|
) |
|
user_fn_output_description = get_deep( |
|
docs, [class_name, "members", "postprocess", "value", "description"] |
|
) |
|
docs_classes += f""" |
|
## `{class_name}` |
|
|
|
### Initialization |
|
|
|
{render_param_table(docs[class_name]["members"]["__init__"])} |
|
|
|
{render_class_events_markdown(docs[class_name].get("events", None))} |
|
|
|
{make_user_fn_markdown( |
|
user_fn_input_type, |
|
user_fn_input_description, |
|
user_fn_output_type, |
|
user_fn_output_description, |
|
)} |
|
""" |
|
return docs_classes |
|
|
|
|
|
def make_space( |
|
docs: dict, |
|
name: str, |
|
description: str, |
|
local_version: str | None, |
|
demo: str, |
|
space: str | None, |
|
repo: str | None, |
|
pypi_exists: bool, |
|
suppress_demo_check: bool = False, |
|
): |
|
filtered_keys = [key for key in docs if key != "__meta__"] |
|
|
|
if not suppress_demo_check and ( |
|
demo.find("if __name__ == '__main__'") == -1 |
|
and demo.find('if __name__ == "__main__"') == -1 |
|
): |
|
raise ValueError( |
|
"""The demo must be launched using `if __name__ == '__main__'`, otherwise the docs page will not function correctly. |
|
|
|
To fix this error, launch the demo inside of an if statement like this: |
|
|
|
if __name__ == '__main__': |
|
demo.launch() |
|
|
|
To ignore this error pass `--suppress-demo-check` to the docs command.""" |
|
) |
|
demo = demo.replace('"""', '\\"\\"\\"') |
|
|
|
source = """ |
|
import gradio as gr |
|
from app import demo as app |
|
import os |
|
""" |
|
|
|
docs_classes = render_class_docs(filtered_keys, docs) |
|
|
|
source += f""" |
|
_docs = {docs} |
|
|
|
abs_path = os.path.join(os.path.dirname(__file__), "css.css") |
|
|
|
with gr.Blocks( |
|
css=abs_path, |
|
theme=gr.themes.Default( |
|
font_mono=[ |
|
gr.themes.GoogleFont("Inconsolata"), |
|
"monospace", |
|
], |
|
), |
|
) as demo: |
|
gr.Markdown( |
|
\"\"\" |
|
# `{name}` |
|
|
|
<div style="display: flex; gap: 7px;"> |
|
{render_version_badge(pypi_exists, local_version, name)} {render_github_badge(repo)} {render_discuss_badge(space)} |
|
</div> |
|
|
|
{description} |
|
\"\"\", elem_classes=["md-custom"], header_links=True) |
|
app.render() |
|
gr.Markdown( |
|
\"\"\" |
|
## Installation |
|
|
|
```bash |
|
pip install {name} |
|
``` |
|
|
|
## Usage |
|
|
|
```python |
|
{demo} |
|
``` |
|
\"\"\", elem_classes=["md-custom"], header_links=True) |
|
|
|
{docs_classes} |
|
|
|
{render_additional_interfaces(docs["__meta__"]["additional_interfaces"])} |
|
demo.load(None, js=r\"\"\"{make_js(get_deep(docs, ["__meta__", "additional_interfaces"]),get_deep( docs, ["__meta__", "user_fn_refs"]))} |
|
\"\"\") |
|
|
|
demo.launch() |
|
""" |
|
|
|
return source |
|
|
|
|
|
def make_markdown( |
|
docs, name, description, local_version, demo, space, repo, pypi_exists |
|
): |
|
filtered_keys = [key for key in docs if key != "__meta__"] |
|
|
|
source = f""" |
|
# `{name}` |
|
{render_version_badge(pypi_exists, local_version, name)} {render_github_badge(repo)} {render_discuss_badge(space)} |
|
|
|
{description} |
|
|
|
## Installation |
|
|
|
```bash |
|
pip install {name} |
|
``` |
|
|
|
## Usage |
|
|
|
```python |
|
{demo} |
|
``` |
|
""" |
|
|
|
docs_classes = render_class_docs_markdown(filtered_keys, docs) |
|
|
|
source += docs_classes |
|
|
|
source += render_additional_interfaces_markdown( |
|
docs["__meta__"]["additional_interfaces"] |
|
) |
|
|
|
return source |
|
|