Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ on:
- push
- pull_request

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
publish-python-version: 3.12

Expand All @@ -28,6 +32,7 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr_towncrier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
env:
PR_NUMBER: ${{ github.event.number }}
run: |
if [ ! -f "changes/${PR_NUMBER}.feature" ] && [ ! -f "changes/${PR_NUMBER}.bugfix" ] && [ ! -f "changes/${PR_NUMBER}.doc" ] && [ ! -f "changes/${PR_NUMBER}.removal" ] && [ ! -f "changes/${PR_NUMBER}.misc" ]; then
if [ ! -f "changes/${PR_NUMBER}.feature.rst" ] && [ ! -f "changes/${PR_NUMBER}.bugfix.rst" ] && [ ! -f "changes/${PR_NUMBER}.doc.rst" ] && [ ! -f "changes/${PR_NUMBER}.removal.rst" ] && [ ! -f "changes/${PR_NUMBER}.misc.rst" ]; then
echo "Towncrier file for #${PR_NUMBER} not found. Please add a changes file to the `changes/` directory. See README.rst for more information."
exit 1
fi
34 changes: 14 additions & 20 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
ci:
autofix_prs: true
autoupdate_schedule: weekly

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 'v6.0.0'
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: '25.12.0'
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.15.1'
hooks:
- id: black
args:
- --safe
- --quiet
- id: ruff
args: [--fix]
files: ^(didl_lite|tests)/.+\.py$
- id: ruff-format
files: ^(didl_lite|tests)/.+\.py$
- repo: https://github.com/codespell-project/codespell
rev: 'v2.4.1'
Expand All @@ -21,20 +25,6 @@ repos:
- --quiet-level=2
exclude_types: [csv, json]
exclude: ^tests/fixtures/
- repo: https://github.com/pycqa/flake8
rev: '7.3.0'
hooks:
- id: flake8
additional_dependencies:
- flake8-docstrings==1.7.0
- pydocstyle==6.3.0
files: ^(didl_lite|tests)/.+\.py$
- repo: https://github.com/PyCQA/isort
rev: '7.0.0'
hooks:
- id: isort
args:
- --profile=black
- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v1.19.1'
hooks:
Expand All @@ -43,3 +33,7 @@ repos:
additional_dependencies:
- pytest==9.0.2
files: ^(didl_lite|tests)/.+\.py$
- repo: https://github.com/fpgmaas/deptry
rev: '0.24.0'
hooks:
- id: deptry
1 change: 1 addition & 0 deletions changes/39.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update CI and dependencies
1 change: 0 additions & 1 deletion didl_lite/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""DIDL-Lite (Digital Item Declaration Language) tools for Python."""

from didl_lite import didl_lite # noqa: F401
Expand Down
55 changes: 13 additions & 42 deletions didl_lite/didl_lite.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""DIDL-Lite (Digital Item Declaration Language) tools for Python."""
# pylint: disable=too-many-lines

Expand Down Expand Up @@ -101,9 +100,7 @@ def __init__(
self.xml_el = xml_el
self.descriptors = descriptors if descriptors else []

def _ensure_required_properties(
self, strict: bool, properties: Mapping[str, Any]
) -> None:
def _ensure_required_properties(self, strict: bool, properties: Mapping[str, Any]) -> None:
"""Check if all required properties are given."""
if not strict:
return
Expand Down Expand Up @@ -184,9 +181,7 @@ def to_xml(self) -> ET.Element:
continue
key = didl_property_def_key(property_def)

if (
getattr(self, key) is None or key == "res"
): # no resources, handled later on
if getattr(self, key) is None or key == "res": # no resources, handled later on
continue

tag = property_def[0] + ":" + property_def[1]
Expand Down Expand Up @@ -245,11 +240,7 @@ def __setattr__(self, name: str, value: Any) -> None:
def __repr__(self) -> str:
"""Evaluatable string representation of this object."""
class_name = type(self).__name__
attr = ", ".join(
f"{key}={val!r}"
for key, val in self.__dict__.items()
if key not in ("class", "xml_el")
)
attr = ", ".join(f"{key}={val!r}" for key, val in self.__dict__.items() if key not in ("class", "xml_el"))
return f"{class_name}({attr})"


Expand Down Expand Up @@ -660,11 +651,7 @@ def to_xml(self) -> ET.Element:
def __repr__(self) -> str:
"""Evaluatable string representation of this object."""
class_name = type(self).__name__
attr = ", ".join(
f"{key}={val!r}"
for key, val in self.__dict__.items()
if key not in ("class", "xml_el")
)
attr = ", ".join(f"{key}={val!r}" for key, val in self.__dict__.items() if key not in ("class", "xml_el"))
children_repr = ", ".join(repr(child) for child in self)
return f"{class_name}({attr}, children=[{children_repr}])"

Expand Down Expand Up @@ -988,11 +975,7 @@ def to_xml(self) -> ET.Element:
def __repr__(self) -> str:
"""Evaluatable string representation of this object."""
class_name = type(self).__name__
attr = ", ".join(
f"{key}={val!r}"
for key, val in self.__dict__.items()
if val is not None and key != "xml_el"
)
attr = ", ".join(f"{key}={val!r}" for key, val in self.__dict__.items() if val is not None and key != "xml_el")
return f"{class_name}({attr})"


Expand Down Expand Up @@ -1045,11 +1028,7 @@ def __getattr__(self, name: str) -> Any:
def __repr__(self) -> str:
"""Evaluatable string representation of this object."""
class_name = type(self).__name__
attr = ", ".join(
f"{key}={val!r}"
for key, val in self.__dict__.items()
if val is not None and key != "xml_el"
)
attr = ", ".join(f"{key}={val!r}" for key, val in self.__dict__.items() if val is not None and key != "xml_el")
return f"{class_name}({attr})"


Expand All @@ -1071,9 +1050,7 @@ def to_xml_string(*objects: DidlObject) -> bytes:
return ET.tostring(root_el)


def from_xml_string(
xml_string: str, strict: bool = True
) -> List[Union[DidlObject, Descriptor]]:
def from_xml_string(xml_string: str, strict: bool = True) -> List[Union[DidlObject, Descriptor]]:
"""Parse DIDL-Lite XML string."""
if not strict:
# Find all prefixes used in tags, e.g., <prefix:tag ...>
Expand All @@ -1083,9 +1060,7 @@ def from_xml_string(
defined_prefixes = set(re.findall(r"xmlns:([a-zA-Z0-9]+)=", xml_string))

# Identify prefixes used but not defined.
missing_prefixes = (
used_prefixes - defined_prefixes - {"DIDL-Lite", "dc", "upnp", "dlna"}
)
missing_prefixes = used_prefixes - defined_prefixes - {"DIDL-Lite", "dc", "upnp", "dlna"}

# Remove the "if missing_prefixes:" line and just keep the for loop
for prefix in missing_prefixes:
Expand All @@ -1099,17 +1074,15 @@ def from_xml_string(
return from_xml_el(xml_el, strict)


def from_xml_el(
xml_el: ET.Element, strict: bool = True
) -> List[Union[DidlObject, Descriptor]]:
def from_xml_el(xml_el: ET.Element, strict: bool = True) -> List[Union[DidlObject, Descriptor]]:
"""Convert XML Element to DIDL Objects."""
didl_objects = [] # type: List[Union[DidlObject, Descriptor]]

# items and containers, in order
for child_el in xml_el:
if child_el.tag != expand_namespace_tag(
"didl_lite:item"
) and child_el.tag != expand_namespace_tag("didl_lite:container"):
if child_el.tag != expand_namespace_tag("didl_lite:item") and child_el.tag != expand_namespace_tag(
"didl_lite:container"
):
continue

# construct item
Expand Down Expand Up @@ -1139,9 +1112,7 @@ def from_xml_el(


# upnp_class to python type mapping
def type_by_upnp_class(
upnp_class: str, strict: bool = True
) -> Optional[Type[DidlObject]]:
def type_by_upnp_class(upnp_class: str, strict: bool = True) -> Optional[Type[DidlObject]]:
"""Get DidlObject-type by upnp_class.

When strict is False, the upnp_class lookup will be done ignoring string
Expand Down
2 changes: 0 additions & 2 deletions pylintrc

This file was deleted.

37 changes: 34 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ version = { attr = "didl_lite.__version__" }
[tool.setuptools.package-data]
didl_lite = ["py.typed"]

[tool.flake8]
max-line-length = 99

[tool.mypy]
check_untyped_defs = true
disallow_untyped_calls = true
Expand All @@ -61,3 +58,37 @@ warn_redundant_casts = true
warn_return_any = true
warn_unused_configs = true
warn_unused_ignores = true

[tool.ruff]
line-length = 119

[tool.ruff.lint]
select = ["C901", "D", "E", "F", "I", "W"]
ignore = ["D202"]

[tool.ruff.lint.mccabe]
max-complexity = 25

[tool.ruff.lint.pydocstyle]
convention = "pep257"

[tool.ruff.lint.isort]
known-first-party = ["didl_lite"]

[tool.deptry]
package_module_name_map = { "pytest" = "pytest" }

[tool.deptry.per_rule_ignores]
DEP002 = ["pytest"]

[tool.pylint.format]
max-line-length = 119

[tool.pylint.basic]
good-names = ["otherItem", "storageMedium"]

[tool.codespell]
ignore-words-list = "wan"

[tool.coverage.run]
source = ["didl_lite"]
37 changes: 0 additions & 37 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,3 @@ tag_name = {new_version}
[bumpversion:file:didl_lite/__init__.py]
search = __version__ = "{current_version}"
replace = __version__ = "{new_version}"

[bdist_wheel]
python-tag = py3

[metadata]
license_file = LICENSE.md

[flake8]
exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build
max-line-length = 119
max-complexity = 25
ignore =
E501,
W503,
E203,
D202,
W504
noqa-require-code = True

[mypy]
check_untyped_defs = true
disallow_untyped_calls = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_incomplete_stub = true
warn_redundant_casts = true
warn_return_any = true
warn_unused_configs = true
warn_unused_ignores = true

[codespell]
ignore-words-list = wan

[coverage:run]
source = didl_lite
21 changes: 5 additions & 16 deletions tests/test_didl_lite.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""Unit tests for didl_lite."""

import pytest
Expand Down Expand Up @@ -234,9 +233,7 @@ def test_container_from_xml(self) -> None:

def test_container_to_xml(self) -> None:
"""Test container to XML."""
container = didl_lite.Album(
id="0", parent_id="0", title="Audio Item Title", restricted="1"
)
container = didl_lite.Album(id="0", parent_id="0", title="Audio Item Title", restricted="1")
resource = didl_lite.Resource("url", "protocol_info")
item = didl_lite.AudioItem(
id="0",
Expand Down Expand Up @@ -285,9 +282,7 @@ def test_container_repr(self) -> None:
# pylint: disable=import-outside-toplevel
from didl_lite.didl_lite import Album, AudioItem, Resource

container = Album(
id="0", parent_id="0", title="Audio Item Title", restricted="1"
)
container = Album(id="0", parent_id="0", title="Audio Item Title", restricted="1")
resource = Resource("url", "protocol_info")
item = AudioItem(
id="0",
Expand Down Expand Up @@ -427,9 +422,7 @@ def test_descriptor_from_xml_container_item(self) -> None:

def test_descriptor_to_xml(self) -> None:
"""Test descriptor to XML."""
descriptor = didl_lite.Descriptor(
id="1", name_space="ns", type="type", text="Text"
)
descriptor = didl_lite.Descriptor(id="1", name_space="ns", type="type", text="Text")
item = didl_lite.AudioItem(
id="0",
parent_id="0",
Expand Down Expand Up @@ -468,9 +461,7 @@ def test_descriptor_repr(self) -> None:

descriptor_repr = repr(descriptor)
descriptor_remade = eval(descriptor_repr) # pylint: disable=eval-used
assert ET.tostring(descriptor.to_xml()) == ET.tostring(
descriptor_remade.to_xml()
)
assert ET.tostring(descriptor.to_xml()) == ET.tostring(descriptor_remade.to_xml())

def test_item_order(self) -> None:
"""Test item ordering."""
Expand Down Expand Up @@ -608,9 +599,7 @@ def test_extra_properties(self) -> None:

def test_default_properties_set(self) -> None:
"""Test defaults for item properties."""
item = didl_lite.VideoItem(
id="0", parent_id="0", title="Video Item Title", restricted="1"
)
item = didl_lite.VideoItem(id="0", parent_id="0", title="Video Item Title", restricted="1")
assert hasattr(item, "genre_type") # property is set

def test_property_case(self) -> None:
Expand Down
Loading
Loading