Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions changelog.d/53.refactor.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refactor :class:`~docbuild.models.lifecycle.LifecycleFlag`.
2 changes: 1 addition & 1 deletion src/docbuild/config/xml/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def list_all_deliverables(
if '*' not in dt.docset:
xpath += '[' + ' or '.join([f'@setid={d!r}' for d in dt.docset]) + ']'

if LifecycleFlag.UNKNOWN != dt.lifecycle: # type: ignore
if LifecycleFlag.unknown != dt.lifecycle: # type: ignore
xpath += (
'['
+ ' or '.join([f'@lifecycle={lc.name!r}' for lc in dt.lifecycle])
Expand Down
11 changes: 9 additions & 2 deletions src/docbuild/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pathlib import Path
import re

from .models.lifecycle import LifecycleFlag
from .models.serverroles import ServerRole

APP_NAME = 'docbuild'
Expand All @@ -24,13 +25,19 @@
# "testing", "test", "t",
# "staging", "stage", "s",
# )
SERVER_ROLES = tuple([role.value for role in ServerRole])
SERVER_ROLES = tuple(
[role.value for role in ServerRole] # type: ignore[call-arg]
)
"""The different server roles, including long and short spelling."""

DEFAULT_LIFECYCLE = 'supported'
"""The default lifecycle state for a docset."""

ALLOWED_LIFECYCLES = ('supported', 'beta', 'hidden', 'unsupported')
ALLOWED_LIFECYCLES: tuple[str] = tuple(
lc.name
for lc in LifecycleFlag # type: ignore[call-arg]
)
# ('supported', 'beta', 'hidden', 'unsupported')
"""The available lifecycle states for a docset."""


Expand Down
6 changes: 3 additions & 3 deletions src/docbuild/models/doctype.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from pydantic import BaseModel, Field, field_validator

from .language import LanguageCode
from .lifecycle import BaseLifecycleFlag, LifecycleFlag
from .lifecycle import LifecycleFlag
from .product import Product


Expand Down Expand Up @@ -185,7 +185,7 @@ def coerce_docset(cls, value: str | list[str]) -> list[str]:

@field_validator('lifecycle', mode='before')
@classmethod
def coerce_lifecycle(cls, value: str | LifecycleFlag) -> BaseLifecycleFlag:
def coerce_lifecycle(cls, value: str | LifecycleFlag) -> LifecycleFlag:
"""Convert a string into a LifecycleFlag."""
# value = "" if value is None else value
if isinstance(value, str):
Expand Down Expand Up @@ -261,7 +261,7 @@ def xpath(self) -> str:
[
f'@lifecycle={lc.name!r}'
for lc in self.lifecycle
if lc != LifecycleFlag.UNKNOWN
if lc != LifecycleFlag.unknown
]
)
if lifecycle:
Expand Down
57 changes: 32 additions & 25 deletions src/docbuild/models/lifecycle.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,46 @@
from typing import ClassVar, Generator
"""Lifecycle model for docbuild."""

from enum import Flag
from enum import Flag, auto
import re

from ..constants import ALLOWED_LIFECYCLES

# Separator between lifecycle flags in strings.
_SEPARATOR = re.compile(r'[|,]')


class BaseLifecycleFlag(Flag):
"""Base class for LifecycleFlag."""
class LifecycleFlag(Flag):
"""LifecycleFlag represents the lifecycle of a product."""

# Order is important here
unknown = 0
# UNKNOWN = 0
"""Unknown lifecycle state."""

supported = auto()
"""Supported lifecycle state."""

beta = auto()
"""Beta lifecycle state."""

hidden = auto()
"""Hidden lifecycle state."""

unsupported = auto()
"""Unsupported lifecycle state."""

@classmethod
def from_str(cls, value: str) -> 'BaseLifecycleFlag':
def from_str(cls, value: str) -> 'LifecycleFlag':
"""Convert a string to a LifecycleFlag object.

The string accepts the values 'supported', 'beta', 'hidden',
'unsupported', or a combination of them separated by a comma or pipe.
Addtionally, the class knows the values "UNKNOWN" and "unknown".
An empty string, "", is equivalent to "UNKNOWN".
Addtionally, the class knows the values "unknown".
An empty string, "", is equivalent to "unknown".

Examples:
>>> LifecycleFlag.from_str("supported")
<LifecycleFlag.supported: 2>
>>> LifecycleFlag.from_str("supported|beta")
>>> LifecycleFlag.from_str("supported,beta")
<LifecycleFlag.supported|beta: 6>
>>> LifecycleFlag.from_str("beta,supported|beta")
<LifecycleFlag.supported|beta: 6>
Expand All @@ -33,12 +50,14 @@ def from_str(cls, value: str) -> 'BaseLifecycleFlag':
"""
try:
flag = cls(0) # Start with an empty flag
parts = [v.strip() for v in _SEPARATOR.split(value) if v.strip()]
parts = [
v.strip() for v in _SEPARATOR.split(value) if v.strip()
]
if not parts:
return cls(0)

for part_name in parts:
flag |= cls[part_name]
flag |= cls.__members__[part_name]

return flag

Expand All @@ -51,9 +70,9 @@ def from_str(cls, value: str) -> 'BaseLifecycleFlag':
def __contains__(self, other: str | Flag) -> bool:
"""Return True if self has at least one of same flags set as other.

>>> "supported" in Lifecycle.beta
>>> "supported" in LifecycleFlag.beta
False
>>> "supported|beta" in Lifecycle.beta
>>> "supported|beta" in LifecycleFlag.beta
True
"""
if isinstance(other, str):
Expand All @@ -66,15 +85,3 @@ def __contains__(self, other: str | Flag) -> bool:
return False

return (self & item_flag) == item_flag


# Lifecycle is implemented as a Flag as different states can be combined
# An additional "unknown" state could be used if the state is unknown or not yet
# retrieved.
# TODO: Should we allow weird combination like "supported|unsupported"
LifecycleFlag = BaseLifecycleFlag(
'LifecycleFlag',
{'unknown': 0, 'UNKNOWN': 0}
| {item: (2 << index) for index, item in enumerate(ALLOWED_LIFECYCLES, 0)},
)
"""LifecycleFlag represents the lifecycle of a product."""
2 changes: 1 addition & 1 deletion tests/models/test_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def test_unknown_lifecycle():

def test_lifecycle_flag_from_str_with_empty_string():
instance = LifecycleFlag.from_str('')
assert instance == LifecycleFlag.UNKNOWN
assert instance == LifecycleFlag.unknown
assert instance.name == 'unknown'
assert instance.value == 0

Expand Down