Skip to content

Commit

Permalink
🔧 Add strict typing for sphinx_needs.directives.needtable
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisjsewell committed Aug 28, 2023
1 parent a0203e7 commit e369be1
Show file tree
Hide file tree
Showing 14 changed files with 42 additions and 60 deletions.
1 change: 0 additions & 1 deletion pyproject.toml
Expand Up @@ -106,7 +106,6 @@ module = [
'sphinx_needs.directives.needfilter',
'sphinx_needs.directives.needflow',
'sphinx_needs.directives.needpie',
'sphinx_needs.directives.needtable',
'sphinx_needs.directives.needuml',
'sphinx_needs.directives.utils',
'sphinx_needs.external_needs',
Expand Down
1 change: 0 additions & 1 deletion sphinx_needs/api/need.py
Expand Up @@ -309,7 +309,6 @@ def run():
"docname": docname,
"doctype": doctype,
"lineno": lineno,
# "target_node": target_node,
"target_id": need_id,
"external_url": external_url,
"content_node": None, # gets set after rst parsing
Expand Down
1 change: 1 addition & 0 deletions sphinx_needs/data.py
Expand Up @@ -362,6 +362,7 @@ class NeedsTableType(NeedsFilteredBaseType):
caption: None | str
classes: list[str]
columns: list[tuple[str, str]]
"""List of (name, title)"""
colwidths: list[int]
style: str
style_row: str
Expand Down
1 change: 0 additions & 1 deletion sphinx_needs/diagrams_common.py
Expand Up @@ -179,7 +179,6 @@ def calculate_link(app: Sphinx, need_info: Dict[str, Any], _fromdocname: str) ->
# only need to add ../ or ..\ to get out of the image folder
link = ".." + os.path.sep + need_info["external_url"]
else:
# link = "../" + builder.get_target_uri(need_info["docname"]) + "#" + need_info["target_node"]["refid"]
link = "../" + builder.get_target_uri(need_info["docname"]) + "#" + need_info["target_id"]
if need_info["is_part"]:
link = f"{link}.{need_info['id']}"
Expand Down
7 changes: 0 additions & 7 deletions sphinx_needs/directives/needextract.py
Expand Up @@ -111,13 +111,6 @@ def process_needextract(
found_needs = process_filters(app, all_needs.values(), current_needextract)

for need_info in found_needs:
# if "is_target" is True:
# extract_target_node = current_needextract['target_node']
# extract_target_node[ids=[need_info["id"]]]
#
# # Original need id replacement (needextract-{docname}-{id})
# need_info['target_node']['ids'] = [f"replaced_{need['id']}"]

# filter out need_part from found_needs, in order to generate
# copies of filtered needs with custom layout and style
if need_info["is_need"] and not need_info["is_part"]:
Expand Down
5 changes: 1 addition & 4 deletions sphinx_needs/directives/needfilter.py
Expand Up @@ -161,10 +161,7 @@ def process_needfilters(

line_block = nodes.line_block()
for need_info in found_needs:
if "target_node" in need_info:
target_id = need_info["target_node"]["refid"]
else:
target_id = need_info["target_id"]
target_id = need_info["target_id"]

if current_needfilter["layout"] == "list":
para = nodes.line()
Expand Down
6 changes: 3 additions & 3 deletions sphinx_needs/directives/needgantt.py
Expand Up @@ -204,9 +204,9 @@ def process_needgantt(app: Sphinx, doctree: nodes.document, fromdocname: str, fo
gantt_element = "[{}] as [{}] lasts 0 days\n".format(need["title"], need["id"])
else: # Normal gantt element handling
duration_option = current_needgantt["duration_option"]
duration = need[duration_option]
duration = need[duration_option] # type: ignore[literal-required]
complete_option = current_needgantt["completion_option"]
complete = need[complete_option]
complete = need[complete_option] # type: ignore[literal-required]
if not (duration and duration.isdigit()):
logger.warning(
"Duration not set or invalid for needgantt chart. "
Expand Down Expand Up @@ -258,7 +258,7 @@ def process_needgantt(app: Sphinx, doctree: nodes.document, fromdocname: str, fo
start_end_sync = "start"

for link_type in current_needgantt[con_type]: # type: ignore[literal-required]
start_with_links = need[link_type]
start_with_links = need[link_type] # type: ignore[literal-required]
for start_with_link in start_with_links:
start_need = all_needs_dict[start_with_link]
gantt_constraint = "[{}] {} at [{}]'s " "{}\n".format(
Expand Down
8 changes: 2 additions & 6 deletions sphinx_needs/directives/needlist.py
Expand Up @@ -107,6 +107,7 @@ def process_needlist(app: Sphinx, doctree: nodes.document, fromdocname: str, fou
if need_info["hide"]:
para += title
elif need_info["is_external"]:
assert need_info["external_url"] is not None, "External need without URL"
ref = nodes.reference("", "")

ref["refuri"] = check_and_calc_base_url_rel_path(need_info["external_url"], fromdocname)
Expand All @@ -115,12 +116,7 @@ def process_needlist(app: Sphinx, doctree: nodes.document, fromdocname: str, fou
ref.append(title)
para += ref
else:
# target_node should not be stored, but it may be still the case
if "target_node" in need_info:
target_id = need_info["target_node"]["refid"]
else:
target_id = need_info["target_id"]

target_id = need_info["target_id"]
ref = nodes.reference("", "")
ref["refdocname"] = need_info["docname"]
ref["refuri"] = builder.get_relative_uri(fromdocname, need_info["docname"])
Expand Down
41 changes: 23 additions & 18 deletions sphinx_needs/directives/needtable.py
@@ -1,13 +1,13 @@
import re
from typing import List, Sequence
from typing import Any, Callable, List, Sequence

from docutils import nodes
from docutils.parsers.rst import directives
from sphinx.application import Sphinx

from sphinx_needs.api.exceptions import NeedsInvalidException
from sphinx_needs.config import NeedsSphinxConfig
from sphinx_needs.data import SphinxNeedsData
from sphinx_needs.data import NeedsInfoType, SphinxNeedsData
from sphinx_needs.debug import measure_time
from sphinx_needs.directives.utils import (
get_option_list,
Expand Down Expand Up @@ -53,13 +53,15 @@ def run(self) -> Sequence[nodes.Node]:
targetid = "needtable-{docname}-{id}".format(docname=env.docname, id=env.new_serialno("needtable"))
targetnode = nodes.target("", "", ids=[targetid])

columns = str(self.options.get("columns", ""))
if len(columns) == 0:
columns = NeedsSphinxConfig(env.app.config).table_columns
if isinstance(columns, str):
columns = [col.strip() for col in re.split(";|,", columns)]
columns_str = str(self.options.get("columns", ""))
if len(columns_str) == 0:
columns_str = NeedsSphinxConfig(env.app.config).table_columns
if isinstance(columns_str, str):
_columns = [col.strip() for col in re.split(";|,", columns_str)]
else:
_columns = columns_str

columns = [get_title(col) for col in columns]
columns = [get_title(col) for col in _columns]

colwidths = str(self.options.get("colwidths", ""))
colwidths_list = []
Expand Down Expand Up @@ -213,24 +215,25 @@ def process_needtables(
except Exception as e:
raise e

def get_sorter(key):
def get_sorter(key: str) -> Callable[[NeedsInfoType], Any]:
"""
Returns a sort-function for a given need-key.
:param key: key of need object as string
:return: function to use in sort(key=x)
"""

def sort(need):
def sort(need: NeedsInfoType) -> Any:
"""
Returns a given value of need, which is used for list sorting.
:param need: need-element, which gets sort
:return: value of need
"""
if isinstance(need[key], str):
value = need[key] # type: ignore[literal-required]
if isinstance(value, str):
# if we filter for string (e.g. id) everything should be lowercase.
# Otherwise, "Z" will be above "a"
return need[key].lower()
return need[key]
return value.lower()
return value

return sort

Expand All @@ -246,7 +249,9 @@ def sort(need):
prefix = ""
else:
row = nodes.row(classes=["need_part", style_row])
temp_need["id"] = temp_need["id_complete"]
temp_need["id"] = temp_need[
"id_complete" # type: ignore[typeddict-item] # TODO this is set in prepare_need_list
]
prefix = needs_config.part_prefix
temp_need["title"] = temp_need["content"]

Expand Down Expand Up @@ -279,10 +284,10 @@ def sort(need):
for part in need_info["parts"].values():
# update the part with all information from its parent
# this is required to make ID links work
temp_part = part.copy() # The dict has to be manipulated, so that row_col_maker() can be used
temp_part = {**need_info, **temp_part}
temp_part["id_complete"] = f"{need_info['id']}.{temp_part['id']}"
temp_part["id_parent"] = need_info["id"]
# The dict has to be manipulated, so that row_col_maker() can be used
temp_part: NeedsInfoType = {**need_info, **part.copy()} # type: ignore[typeddict-unknown-key]
temp_part["id_complete"] = f"{need_info['id']}.{temp_part['id']}" # type: ignore[typeddict-unknown-key]
temp_part["id_parent"] = need_info["id"] # type: ignore[typeddict-unknown-key]
temp_part["docname"] = need_info["docname"]

row = nodes.row(classes=["need_part"])
Expand Down
14 changes: 4 additions & 10 deletions sphinx_needs/filter_common.py
Expand Up @@ -86,7 +86,7 @@ def collect_filter_attributes(self) -> FilterAttributesType:

def process_filters(
app: Sphinx, all_needs: List[NeedsInfoType], filter_data: NeedsFilteredBaseType, include_external: bool = True
):
) -> List[NeedsInfoType]:
"""
Filters all needs with given configuration.
Used by needlist, needtable and needflow.
Expand Down Expand Up @@ -205,13 +205,7 @@ def process_filters(
filter_list = SphinxNeedsData(env).get_or_create_filters()
found_needs_ids = [need["id_complete"] for need in found_needs]

if "target_node" in filter_data:
target_id = filter_data["target_node"]["refid"]
else:
target_id = filter_data["target_id"]

filter_list[target_id] = {
# "target_node": current_needlist["target_node"],
filter_list[filter_data["target_id"]] = {
"filter": filter_data["filter"] or "",
"status": filter_data["status"],
"tags": filter_data["tags"],
Expand Down Expand Up @@ -243,9 +237,9 @@ def prepare_need_list(need_list: List[NeedsInfoType]) -> List[NeedsInfoType]:

# Be sure extra attributes, which makes only sense for need_parts, are also available on
# need level so that no KeyError gets raised, if search/filter get executed on needs with a need-part argument.
if "id_parent" not in need.keys():
if "id_parent" not in need:
need["id_parent"] = need["id"]
if "id_complete" not in need.keys():
if "id_complete" not in need:
need["id_complete"] = need["id"]
return all_needs_incl_parts

Expand Down
2 changes: 1 addition & 1 deletion sphinx_needs/functions/functions.py
Expand Up @@ -174,7 +174,7 @@ def resolve_dynamic_values(env: BuildEnvironment):
needs = data.get_or_create_needs()
for need in needs.values():
for need_option in need:
if need_option in ["docname", "lineno", "target_node", "content", "content_node", "content_id"]:
if need_option in ["docname", "lineno", "content", "content_node", "content_id"]:
# dynamic values in this data are not allowed.
continue
if not isinstance(need[need_option], (list, set)):
Expand Down
2 changes: 0 additions & 2 deletions sphinx_needs/needsfile.py
Expand Up @@ -22,7 +22,6 @@ class NeedsList:
"links_back",
"type_color",
"hide_status",
"target_node",
"hide",
"type_prefix",
"lineno",
Expand All @@ -37,7 +36,6 @@ class NeedsList:
"links_back",
"type_color",
"hide_status",
"target_node",
"hide",
"type_prefix",
"lineno",
Expand Down
1 change: 0 additions & 1 deletion sphinx_needs/roles/need_incoming.py
Expand Up @@ -59,7 +59,6 @@ def process_need_incoming(
builder,
fromdocname,
target_need["docname"],
# target_need["target_node"]["refid"],
target_need["target_id"],
node_need_backref[0].deepcopy(),
node_need_backref["reftarget"],
Expand Down
12 changes: 7 additions & 5 deletions sphinx_needs/utils.py
Expand Up @@ -27,7 +27,6 @@
"docname",
"doctype",
"lineno",
"target_node",
"refid",
"content",
"pre_content",
Expand Down Expand Up @@ -87,12 +86,12 @@ def row_col_maker(
app: Sphinx,
fromdocname: str,
all_needs: Dict[str, NeedsInfoType],
need_info,
need_key,
need_info: NeedsInfoType,
need_key: str,
make_ref: bool = False,
ref_lookup: bool = False,
prefix: str = "",
):
) -> nodes.entry:
"""
Creates and returns a column.
Expand Down Expand Up @@ -254,7 +253,10 @@ def import_prefix_link_edit(needs: Dict[str, Any], id_prefix: str, needs_extra_l
need["description"] = need["description"].replace(id, "".join([id_prefix, id]))


def profile(keyword: str):
FuncT = TypeVar("FuncT")


def profile(keyword: str) -> Callable[[FuncT], FuncT]:
"""
Activate profiling for a specific function.
Expand Down

0 comments on commit e369be1

Please sign in to comment.