Skip to content

Commit

Permalink
Merge pull request #4 from ermakov-oleg/fix-deprecation-warnings
Browse files Browse the repository at this point in the history
Fixed deprecation warnings on python 3.12 and drop EOL python versions
  • Loading branch information
tkukushkin authored Jan 9, 2024
2 parents 5b61929 + 8cd6bc8 commit 025d9b3
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 50 deletions.
20 changes: 9 additions & 11 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
name: build

on: [push]
on: [push, pull_request]

jobs:

tests:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['2.7', '3.6', '3.7', '3.8', '3.9', '3.10']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
include:
- python-version: '2.7'
tox_python_version: '27'
- python-version: '3.6'
tox_python_version: '36'
- python-version: '3.7'
tox_python_version: '37'
- python-version: '3.8'
tox_python_version: '38'
- python-version: '3.9'
tox_python_version: '39'
- python-version: '3.10'
tox_python_version: '310'
- python-version: '3.11'
tox_python_version: '311'
- python-version: '3.12'
tox_python_version: '312'
env:
TOXENV: tests_py${{ matrix.tox_python_version }}
TOXENV: tests-py${{ matrix.tox_python_version }}

steps:
- uses: actions/checkout@v1
Expand All @@ -39,10 +37,10 @@ jobs:
tox -v
- name: Upload code coverage
uses: codecov/codecov-action@v1.0.3
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: .tox/tests_py${{ matrix.tox_python_version }}/log/coverage.xml
file: .tox/tests-py${{ matrix.tox_python_version }}/log/coverage.xml

lint:
runs-on: ubuntu-latest
Expand Down
10 changes: 3 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
setup(
name="attributes-doc",
version="0.3.0",
python_requires=">=3.8",
url="https://github.com/tkukushkin/attributes-doc",
author="Timofey Kukushkin",
author_email="tima@kukushkin.me",
Expand All @@ -20,22 +21,17 @@
packages=find_packages("src"),
package_dir={"": "src"},
include_package_data=True,
install_requires=[
'typing; python_version<"3"',
],
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
],
project_urls={
Expand Down
52 changes: 22 additions & 30 deletions src/attributes_doc/_base.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,22 @@
import ast
import inspect
import sys
import textwrap
from typing import TYPE_CHECKING, Any, Dict, Tuple, Type, TypeVar
from enum import Enum
from typing import Any, Dict, Optional, Type, TypeVar


__all__ = ["get_attributes_doc", "attributes_doc", "enum_doc", "get_doc"]

PY35 = sys.version_info[0:2] >= (3, 5)

T = TypeVar("T")

if TYPE_CHECKING: # pragma: no cover
from enum import Enum

TEnum = TypeVar("TEnum", bound=Enum)

assign_stmts = (ast.Assign,) # type: Tuple[Type[ast.stmt], ...]
if PY35:
assign_stmts = (ast.Assign, ast.AnnAssign)
TEnum = TypeVar("TEnum", bound=Enum)


class FStringFound(Exception):
pass


def get_attributes_doc(cls):
# type: (type) -> Dict[str, str]
def get_attributes_doc(cls: type) -> Dict[str, str]:
"""
Get a dictionary of attribute names to docstrings for the given class.
Expand All @@ -36,43 +26,46 @@ def get_attributes_doc(cls):
Returns:
Dict[str, str]: A dictionary of attribute names to docstrings.
"""
result = {} # type: Dict[str, str]
result: Dict[str, str] = {}
for parent in reversed(cls.mro()):
if cls is object:
continue
try:
source = inspect.getsource(parent)
except (TypeError, IOError):
except (TypeError, OSError):
continue
source = textwrap.dedent(source)
module = ast.parse(source)
cls_ast = module.body[0]
for stmt1, stmt2 in zip(cls_ast.body, cls_ast.body[1:]): # type: ignore
if not isinstance(stmt1, assign_stmts) or not isinstance(stmt2, ast.Expr):
if not isinstance(stmt1, (ast.Assign, ast.AnnAssign)) or not isinstance(stmt2, ast.Expr):
continue
doc_expr_value = stmt2.value
if PY35 and isinstance(doc_expr_value, ast.JoinedStr):
if isinstance(doc_expr_value, ast.JoinedStr):
raise FStringFound
if isinstance(doc_expr_value, ast.Str):
if PY35 and isinstance(stmt1, ast.AnnAssign):
if isinstance(doc_expr_value, ast.Constant):
if isinstance(stmt1, ast.AnnAssign):
attr_names = [stmt1.target.id] # type: ignore
else:
attr_names = [target.id for target in stmt1.targets]
attr_names = [target.id for target in stmt1.targets] # type: ignore

attr_doc_value = doc_expr_value.value
if not isinstance(attr_doc_value, str):
continue

for attr_name in attr_names:
result[attr_name] = doc_expr_value.s
result[attr_name] = attr_doc_value
return result


def attributes_doc(cls):
# type: (Type[T]) -> Type[T]
def attributes_doc(cls: Type[T]) -> Type[T]:
"""Store the docstings of the attributes of a class in attributes named `__doc_NAME__`."""
for attr_name, attr_doc in get_attributes_doc(cls).items():
setattr(cls, "__doc_{}__".format(attr_name), attr_doc)
setattr(cls, f"__doc_{attr_name}__", attr_doc)
return cls


def enum_doc(cls):
# type: (Type[TEnum]) -> Type[TEnum]
def enum_doc(cls: Type[TEnum]) -> Type[TEnum]:
"""Store the docstrings of the vaules of an enum in their `__doc__` attribute."""
docs = get_attributes_doc(cls)
for member in cls:
Expand All @@ -82,8 +75,7 @@ def enum_doc(cls):
return cls


def get_doc(obj, attr_name):
# type: (Any, str) -> str | None
def get_doc(obj: Any, attr_name: str) -> Optional[str]:
"""Get the docstring of a class attribute of a class or an instance of that class.
Args:
Expand All @@ -93,4 +85,4 @@ def get_doc(obj, attr_name):
Returns:
str | None: The docstring of the class attribute or None if no docstring was found.
"""
return getattr(obj, "__doc_{}__".format(attr_name), None)
return getattr(obj, f"__doc_{attr_name}__", None)
13 changes: 13 additions & 0 deletions tests/test_attributes_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,16 @@ class Foo4(object):
assert get_doc(Foo4, "b") == "a Doc"
assert not hasattr(Foo4, "__doc_c__")
assert get_doc(Foo4, "c") is None

def test__non_string_docs__no_doc_attributes(self):
# act
@attributes_doc
class Foo1(object):
a = 1
123
b = 2
("1", 2)

# assert
assert not hasattr(Foo1, "__doc_a__")
assert not hasattr(Foo1, "__doc_b__")
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tox]
envlist = tests_py{27,36,37,38,39,310}, lint
envlist = tests-py{38,39,310,311,312}, lint

[testenv:tests_py{27,36,37,38,39,310}]
[testenv:tests-py{38,39,310,311,312}]
deps =
.
-r requirements/tests.txt
Expand Down

0 comments on commit 025d9b3

Please sign in to comment.