Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple inheritance for doc generation #406

Merged
merged 41 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
62bfb03
Refactor workaround for setting Helm app `nameOverride`
disrupted Jan 2, 2024
55089be
Update snapshot assertions
disrupted Jan 2, 2024
8d9bb60
Use consistent naming for app values
disrupted Jan 2, 2024
934e4ed
Rename fixture
disrupted Jan 2, 2024
1c4e313
Refactor
disrupted Jan 2, 2024
2309a7b
Rename
disrupted Jan 2, 2024
7c8ff89
Merge remote-tracking branch 'origin/v3' into refactor/helm-nameoverride
disrupted Jan 2, 2024
f67a23f
Refactor streams-bootstrap cleanup
disrupted Jan 2, 2024
0578cb0
Update tests
disrupted Jan 2, 2024
44195e7
Inherit from common streams-bootstrap app
disrupted Jan 2, 2024
a2a8418
Remove desc
disrupted Jan 2, 2024
3457d1f
Fix failing hooks
disrupted Jan 2, 2024
6ac39da
Merge remote-tracking branch 'origin/v3' into refactor/streams-bootst…
disrupted Jan 2, 2024
a725f03
Cosmetic
disrupted Jan 2, 2024
26ac7f9
Cosmetic
disrupted Jan 2, 2024
b193c03
Rename run to clean
disrupted Jan 2, 2024
9d27adc
Fix returned object type
disrupted Jan 3, 2024
c517a3b
Add pydocs and todo
disrupted Jan 3, 2024
11dcc1e
Create streams-bootstrap base for all components based on its Helm
disrupted Jan 3, 2024
d9786dd
Cleanup KPOps components import
disrupted Jan 3, 2024
a49b551
Fix inheritance order of streams-boostrap apps
disrupted Jan 3, 2024
2e2c7ba
Update components hierarchy diagram
disrupted Jan 3, 2024
02c0ef7
Fix docs
disrupted Jan 4, 2024
70cf1f3
Merge branch 'v3' into refactor/streams-bootstrap-cleanup
disrupted Jan 8, 2024
83ac398
Update defaults schema
disrupted Jan 8, 2024
45c58d8
Add docs for streams-bootstrap
disrupted Jan 8, 2024
7c92421
Add docs for streams-bootstrap
disrupted Jan 8, 2024
18cece3
refactor: extract function to utils
sujuka99 Jan 9, 2024
d9808f8
refactor: enable multiple inheritance for doc gen
sujuka99 Jan 10, 2024
87c24de
style: add TODO
sujuka99 Jan 10, 2024
372a0e5
refactor: Use mro to collect parents
sujuka99 Jan 10, 2024
a83d607
style: improve naming
sujuka99 Jan 10, 2024
34584cb
refactor: allow issubclass to take second arg
sujuka99 Jan 10, 2024
c601fa4
refactor: move parents logic to PipelineComponent,
sujuka99 Jan 10, 2024
358f843
fix: import exception
sujuka99 Jan 10, 2024
2f2d61b
refactor: get parents returns list of classes
sujuka99 Jan 11, 2024
d54e7d1
Refactor parents method as classproperty & remove self
disrupted Jan 11, 2024
6e6f625
Merge remote-tracking branch 'origin/v3' into ci/support-multiple-inh…
sujuka99 Jan 11, 2024
26e6893
Merge branch 'ci/support-multiple-inheritance' of github.com:bakdata/…
sujuka99 Jan 11, 2024
e27b6fb
copy-paste from backup branch
sujuka99 Jan 11, 2024
ef9f3fd
run pre-commit
sujuka99 Jan 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,57 @@ kpops_components_fields:
- repo_config
- version
kpops_components_inheritance_ref:
helm-app: kubernetes-app
kafka-app: helm-app
kafka-connector: pipeline-component
kafka-sink-connector: kafka-connector
kafka-source-connector: kafka-connector
kubernetes-app: pipeline-component
pipeline-component: base-defaults-component
producer-app: kafka-app
streams-app: kafka-app
helm-app:
bases:
- kubernetes-app
parents:
- kubernetes-app
- pipeline-component
kafka-app:
bases:
- helm-app
parents:
- helm-app
- kubernetes-app
- pipeline-component
kafka-connector:
bases:
- pipeline-component
parents:
- pipeline-component
kafka-sink-connector:
bases:
- kafka-connector
parents:
- kafka-connector
- pipeline-component
kafka-source-connector:
bases:
- kafka-connector
parents:
- kafka-connector
- pipeline-component
kubernetes-app:
bases:
- pipeline-component
parents:
- pipeline-component
pipeline-component:
bases: []
parents: []
producer-app:
bases:
- kafka-app
parents:
- kafka-app
- helm-app
- kubernetes-app
- pipeline-component
streams-app:
bases:
- kafka-app
parents:
- kafka-app
- helm-app
- kubernetes-app
- pipeline-component
48 changes: 31 additions & 17 deletions hooks/gen_docs/gen_docs_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from kpops.cli.registry import _find_classes
from kpops.components import KafkaConnector, PipelineComponent
from kpops.utils.colorify import redify, yellowify
from kpops.utils.pydantic import issubclass_patched
from kpops.utils.yaml import load_yaml_file

PATH_KPOPS_MAIN = ROOT / "kpops/cli/main.py"
Expand All @@ -33,14 +34,6 @@
)

KPOPS_COMPONENTS = tuple(_find_classes("kpops.components", PipelineComponent))
KPOPS_COMPONENTS_INHERITANCE_REF = {
component.type: cast(
type[PipelineComponent],
component.__base__,
).type
for component in KPOPS_COMPONENTS
}

KPOPS_COMPONENTS_SECTIONS = {
component.type: [
field_name
Expand All @@ -49,6 +42,27 @@
]
for component in KPOPS_COMPONENTS
}
KPOPS_COMPONENTS_INHERITANCE_REF = {
component.type: {
"bases": [
cast(
type[PipelineComponent],
base,
).type
for base in component.__bases__
if issubclass_patched(base, PipelineComponent)
],
"parents": [
cast(
type[PipelineComponent],
parent,
).type
for parent in component.parents
],
}
for component in KPOPS_COMPONENTS
}

# Dependency files should not be changed manually
DANGEROUS_FILES_TO_CHANGE = {
PATH_DOCS_COMPONENTS_DEPENDENCIES,
Expand Down Expand Up @@ -92,14 +106,13 @@ def filter_sections(
if section := filter_section(component_name, sections, target_section):
component_sections.append(section)
elif include_inherited:
temp_component_name = component_name
while (
temp_component_name := KPOPS_COMPONENTS_INHERITANCE_REF[
temp_component_name
]
) != PipelineComponent.type:
for component in KPOPS_COMPONENTS_INHERITANCE_REF[component_name][
"parents"
]:
if component == PipelineComponent.type:
break
if section := filter_section(
temp_component_name,
component,
sections,
target_section,
):
Expand All @@ -123,11 +136,12 @@ def filter_section(
section = target_section + "-" + component_name + ".yaml"
if section in sections:
return section
if KPOPS_COMPONENTS_INHERITANCE_REF[component_name] == PipelineComponent.type:
if KPOPS_COMPONENTS_INHERITANCE_REF[component_name]["bases"] == [
PipelineComponent.type
]:
section = target_section + ".yaml"
if section in sections:
return section
return None
return None


Expand Down
23 changes: 2 additions & 21 deletions hooks/gen_docs/gen_docs_env_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from hooks.gen_docs import IterableStrEnum
from kpops.cli import main
from kpops.config import KpopsConfig
from kpops.utils.pydantic import issubclass_patched

PATH_DOCS_RESOURCES = ROOT / "docs/docs/resources"
PATH_DOCS_VARIABLES = PATH_DOCS_RESOURCES / "variables"
Expand Down Expand Up @@ -284,29 +285,9 @@ def collect_fields(model: type[BaseModel]) -> dict[str, Any]:
:param model: settings class
:return: ``dict`` of all fields in a settings class
"""

def patched_issubclass_of_basemodel(cls):
"""Pydantic breaks issubclass.

``issubclass(set[str], set) # True``
``issubclass(BaseSettings, BaseModel) # True``
``issubclass(set[str], BaseModel) # raises exception``

:param cls: class to check
:return: Whether cls is subclass of ``BaseModel``
"""
try:
return issubclass(cls, BaseModel)
except TypeError as e:
if str(e) == "issubclass() arg 1 must be a class":
return False
raise

seen_fields = {}
for field_name, field_value in model.model_fields.items():
if field_value.annotation and patched_issubclass_of_basemodel(
field_value.annotation
):
if field_value.annotation and issubclass_patched(field_value.annotation):
seen_fields[field_name] = collect_fields(field_value.annotation)
else:
seen_fields[field_name] = field_value
Expand Down
23 changes: 23 additions & 0 deletions kpops/components/base_components/pipeline_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@
TopicConfig,
ToSection,
)
from kpops.utils import cached_classproperty
from kpops.utils.docstring import describe_attr
from kpops.utils.pydantic import issubclass_patched

try:
from typing import Self
except ImportError:
from typing_extensions import Self


class PipelineComponent(BaseDefaultsComponent, ABC):
Expand Down Expand Up @@ -64,6 +71,22 @@ def __init__(self, **kwargs) -> None:
def full_name(self) -> str:
return self.prefix + self.name

@cached_classproperty
def parents(cls: type[Self]) -> tuple[type[PipelineComponent], ...]: # pyright: ignore[reportGeneralTypeIssues]
"""Get parent components.

:return: All ancestor KPOps components
"""

def gen_parents():
for base in cls.mro():
# skip class itself and non-component ancestors
if base is cls or not issubclass_patched(base, PipelineComponent):
continue
yield base

return tuple(gen_parents())

def add_input_topics(self, topics: list[str]) -> None:
"""Add given topics to the list of input topics.

Expand Down
21 changes: 21 additions & 0 deletions kpops/utils/pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,27 @@ def exclude_defaults(model: BaseModel, dumped_model: dict[str, _V]) -> dict[str,
}


def issubclass_patched(
__cls: type, __class_or_tuple: type | tuple[type, ...] = BaseModel
) -> bool:
"""Pydantic breaks ``issubclass``.

``issubclass(set[str], set) # True``
``issubclass(BaseSettings, BaseModel) # True``
``issubclass(set[str], BaseModel) # raises exception``

:param cls: class to check
:base: class(es) to check against, defaults to ``BaseModel``
:return: Whether 'cls' is derived from another class or is the same class.
"""
try:
return issubclass(__cls, __class_or_tuple)
except TypeError as e:
if str(e) == "issubclass() arg 1 must be a class":
return False
raise


class CamelCaseConfigModel(BaseModel):
model_config = ConfigDict(
alias_generator=to_camel,
Expand Down
Loading