Skip to content

Commit 7249fcb

Browse files
authored
feat: add type hints (#71)
1 parent a252ff1 commit 7249fcb

File tree

15 files changed

+735
-415
lines changed

15 files changed

+735
-415
lines changed

.github/workflows/pypi-publish.yml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
11
name: Publish to PyPi
22

3+
permissions:
4+
contents: read
5+
36
on:
47
push:
58
tags:
69
- "v*"
710

811
jobs:
9-
lint:
10-
uses: prosegrinder/.github/.github/workflows/poetry-lint.yaml@main
12+
black:
13+
uses: prosegrinder/.github/.github/workflows/poetry-black.yaml@main
14+
15+
pylint:
16+
uses: prosegrinder/.github/.github/workflows/poetry-pylint.yaml@main
17+
18+
mypy:
19+
uses: prosegrinder/.github/.github/workflows/poetry-mypy.yaml@main
20+
21+
bandit:
22+
uses: prosegrinder/.github/.github/workflows/poetry-bandit.yaml@main
1123

1224
test:
13-
needs: lint
25+
needs:
26+
- pylint
27+
- black
28+
- mypy
29+
- bandit
1430
uses: prosegrinder/.github/.github/workflows/poetry-test.yaml@main
1531

1632
publish:

.github/workflows/python-ci.yml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
name: Python Poetry CI
22

3+
permissions:
4+
contents: read
5+
36
on:
47
pull_request:
58

@@ -8,11 +11,24 @@ concurrency:
811
cancel-in-progress: true
912

1013
jobs:
11-
lint:
12-
uses: prosegrinder/.github/.github/workflows/poetry-lint.yaml@main
14+
black:
15+
uses: prosegrinder/.github/.github/workflows/poetry-black.yaml@main
16+
17+
pylint:
18+
uses: prosegrinder/.github/.github/workflows/poetry-pylint.yaml@main
19+
20+
mypy:
21+
uses: prosegrinder/.github/.github/workflows/poetry-mypy.yaml@main
22+
23+
bandit:
24+
uses: prosegrinder/.github/.github/workflows/poetry-bandit.yaml@main
1325

1426
test:
15-
needs: lint
27+
needs:
28+
- pylint
29+
- black
30+
- mypy
31+
- bandit
1632
uses: prosegrinder/.github/.github/workflows/poetry-test.yaml@main
1733

1834
cz-dry-run:
Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,40 @@
11
name: Bump Version and Create Release
22

3+
permissions:
4+
contents: read
5+
36
on:
47
push:
58
branches:
69
- main
710

811
jobs:
12+
black:
13+
uses: prosegrinder/.github/.github/workflows/poetry-black.yaml@main
14+
15+
pylint:
16+
uses: prosegrinder/.github/.github/workflows/poetry-pylint.yaml@main
17+
18+
mypy:
19+
uses: prosegrinder/.github/.github/workflows/poetry-mypy.yaml@main
20+
21+
bandit:
22+
uses: prosegrinder/.github/.github/workflows/poetry-bandit.yaml@main
23+
24+
test:
25+
needs:
26+
- pylint
27+
- black
28+
- mypy
29+
- bandit
30+
uses: prosegrinder/.github/.github/workflows/poetry-test.yaml@main
31+
932
release:
10-
if: "!startsWith(github.event.head_commit.message, 'bump:')"
33+
needs: test
34+
if: ${{ !startsWith(github.event.head_commit.message, 'bump:') }}
1135
# Don't run 'bump:'
36+
permissions:
37+
contents: write
1238
uses: prosegrinder/.github/.github/workflows/poetry-release.yaml@main
1339
secrets:
1440
VERSION_BUMP_TAG_TOKEN: "${{ secrets.VERSION_BUMP_TAG_TOKEN }}"

poetry.lock

Lines changed: 502 additions & 307 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,60 @@
1+
[project]
2+
name = "prosegrinder"
3+
dynamic = ["version", "classifiers"]
4+
description = "A text analytics library for prose fiction."
5+
license = {text = "GPLv3"}
6+
readme = "README.md"
7+
requires-python = ">=3.9,<4.0"
8+
authors = [
9+
{name = "David L. Day", email = "[email protected]"}
10+
]
11+
keywords = ["text analytics", "prose fiction", "natural language processing", "NLP", "linguistics"]
12+
dependencies = [
13+
"importlib-metadata>=5.1.0",
14+
"cmudict>=1.0.11",
15+
"narrative>=1.1.1",
16+
"pointofview>=1.0.2",
17+
"syllables>=1.0.4",
18+
"click>=8.1.3"
19+
]
20+
21+
[project.scripts]
22+
prosegrinder = "prosegrinder.__main__:cli"
23+
24+
[project.urls]
25+
homepage = "https://github.com/prosegrinder/python-prosegrinder"
26+
repository = "https://github.com/prosegrinder/python-prosegrinder"
27+
"Bug Tracker" = "https://github.com/prosegrinder/python-prosegrinder/issues"
28+
129
[tool.commitizen]
2-
version = "1.3.8"
330
tag_format = "v$version"
431
update_changelog_on_bump = true
532
changelog_incremental = true
633
bump_message = "bump: $current_version → $new_version"
7-
version_files = [
8-
"pyproject.toml:version",
9-
]
34+
version_provider = "poetry"
1035

1136
[build-system]
1237
requires = ["poetry-core"]
1338
build-backend = "poetry.core.masonry.api"
1439

1540
[tool.poetry]
16-
name = "prosegrinder"
1741
version = "1.3.8"
18-
description = "A text analytics library for prose fiction."
19-
authors = ["David L. Day <[email protected]>"]
20-
license = "GPLv3"
21-
readme = "README.md"
22-
23-
[tool.poetry.dependencies]
24-
python = "^3.7.2"
25-
importlib-metadata = "^5.1.0"
26-
cmudict = "^1.0.11"
27-
narrative = "^1.1.1"
28-
pointofview = "^1.0.2"
29-
syllables = "^1.0.4"
30-
click = "^8.1.3"
42+
classifiers = [
43+
"Development Status :: 5 - Production/Stable",
44+
"Intended Audience :: Developers",
45+
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
46+
"Natural Language :: English",
47+
"Operating System :: OS Independent",
48+
"Programming Language :: Python :: 3",
49+
"Topic :: Text Processing"
50+
]
3151

3252
[tool.poetry.group.dev.dependencies]
33-
pytest = "^7.2.0"
34-
pylint = "^2.15.8"
3553
black = ">=22.12,<24.0"
54+
mypy = "^1.17.0"
55+
bandit = "^1.8.6"
56+
pylint = "^3.3.7"
57+
pytest = "^8.4.1"
3658

37-
[tool.poetry.scripts]
38-
prosegrinder = "prosegrinder.__main__:cli"
39-
40-
[tool.pylint."messages control"]
41-
disable = ["duplicate-code", "too-many-instance-attributes", "too-many-arguments"]
59+
[tool.pylint.'MESSAGES CONTROL']
60+
disable = "similarities"

src/prosegrinder/__main__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@
2020
@click.option(
2121
"-s", "--save", required=False, type=click.File("w"), help="File to save output to."
2222
)
23-
def cli(files, save, indent):
23+
def cli(files: list[click.File], save: click.File, indent: int) -> None:
2424
"""Setup the command line interface"""
2525
processed_files = []
2626
for file in files:
2727
filename = click.format_filename(file.name)
28-
text = file.read()
28+
text = file.read() # type: ignore
2929
_p = Prose(text)
3030
_d = {"filename": filename, "statistics": _p.stats}
3131
processed_files.append(_d)
3232
_j = json.dumps(processed_files, indent=indent)
3333
if save:
34-
save.write(_j)
34+
save.write(_j) # type: ignore
3535
else:
3636
click.echo(_j)

src/prosegrinder/fragment.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Fragment class for prosegrinder."""
2+
23
import re
34
from collections import Counter
45

@@ -8,10 +9,10 @@
89
from prosegrinder.word import Word
910

1011

11-
class Fragment:
12+
class Fragment: # pylint: disable=too-many-instance-attributes
1213
"""A fragment of text."""
1314

14-
def __init__(self, text, dictionary=Dictionary()):
15+
def __init__(self, text: str, dictionary: Dictionary = Dictionary()):
1516
self.text = text
1617
self.dictionary = dictionary
1718
self.normalized_sentence = dictionary.normalize_text(text)
@@ -21,8 +22,8 @@ def __init__(self, text, dictionary=Dictionary()):
2122
]
2223
self.word_count = len(self.words)
2324
self.word_character_count = sum(word.character_count for word in self.words)
24-
_pf = Counter()
25-
_pc = 0
25+
_pf: Counter = Counter()
26+
_pc: int = 0
2627
for word in self.words:
2728
_pf.update(word.phone_frequency)
2829
_pc += word.phone_count
@@ -41,7 +42,7 @@ def __init__(self, text, dictionary=Dictionary()):
4142
self.third_person_word_count = sum(
4243
word.is_third_person_word for word in self.words
4344
)
44-
self.word_frequency = dict(Counter(self.words))
45+
self.word_frequency = dict(Counter(word.text for word in self.words))
4546
self.unique_words = self.word_frequency.keys()
4647
self.unique_word_count = len(self.unique_words)
4748
self.pov = pointofview.NONE
@@ -52,17 +53,19 @@ def __init__(self, text, dictionary=Dictionary()):
5253
elif self.third_person_word_count > 0:
5354
self.pov = pointofview.THIRD
5455

55-
def __eq__(self, other):
56+
def __eq__(self, other: object) -> bool:
5657
"""Equality operator for instance variables."""
58+
if not isinstance(other, Fragment):
59+
return False
5760
return self.text == other.text
5861

59-
def __hash__(self):
62+
def __hash__(self) -> int:
6063
"""Hash operator for instance variables."""
6164
return hash(self.text)
6265

63-
def frequency(self, word_string):
66+
def frequency(self, word_string: str) -> int:
6467
"""Returns the frequency of a word in the fragment."""
65-
return self.word_frequency[word_string]
68+
return self.word_frequency.get(word_string, 0)
6669

6770
@property
6871
def stats(self):

src/prosegrinder/fragment_container.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
"""Fragment container class for prosegrinder."""
2+
23
from collections import Counter
4+
from collections.abc import Sequence
35

46
import pointofview
57

68
from prosegrinder.dictionary import Dictionary
9+
from prosegrinder.fragment import Fragment
710

811

9-
class FragmentContainer:
12+
class FragmentContainer: # pylint: disable=too-many-instance-attributes
1013
"""A container for fragments."""
1114

12-
def __init__(self, fragments, dictionary=Dictionary()):
15+
def __init__(
16+
self, fragments: Sequence[Fragment], dictionary: Dictionary = Dictionary()
17+
):
1318
self.dictionary = dictionary
1419
self.fragments = fragments or []
1520
self.fragment_count = len(self.fragments)
@@ -38,9 +43,9 @@ def __init__(self, fragments, dictionary=Dictionary()):
3843
self.third_person_word_count = sum(
3944
fragment.third_person_word_count for fragment in self.fragments
4045
)
41-
_wf = Counter()
42-
_pf = Counter()
43-
_pc = 0
46+
_wf: Counter = Counter()
47+
_pf: Counter = Counter()
48+
_pc: int = 0
4449
for fragment in self.fragments:
4550
_wf.update(fragment.words)
4651
_pf.update(fragment.phone_frequency)
@@ -58,14 +63,16 @@ def __init__(self, fragments, dictionary=Dictionary()):
5863
elif self.third_person_word_count > 0:
5964
self.pov = pointofview.THIRD
6065

61-
def __eq__(self, other):
66+
def __eq__(self, other: object) -> bool:
6267
"""Equality operator for instance variables."""
68+
if not isinstance(other, FragmentContainer):
69+
return False
6370
return self.fragments == other.fragments
6471

65-
def __hash__(self):
72+
def __hash__(self) -> int:
6673
"""Hash operator for instance variables."""
6774
return hash(self.fragments)
6875

69-
def frequency(self, word_string):
76+
def frequency(self, word_string: str) -> int:
7077
"""Gets the frequency of a word in the fragment container."""
7178
return self.word_frequency[self.dictionary.get_word(word_string)]

src/prosegrinder/paragraph.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Paragraph class for prosegrinder."""
2+
23
import re
34

45
import narrative
@@ -14,7 +15,7 @@ class Paragraph(FragmentContainer):
1415

1516
RE_PARAGRAPH = re.compile(".*(?=\\n|$)")
1617

17-
def __init__(self, text, dictionary=Dictionary()):
18+
def __init__(self, text: str, dictionary: Dictionary = Dictionary()):
1819
self.text = text
1920
self.dictionary = dictionary
2021
self.sentences = Sentence.parse_sentences(self.text, self.dictionary)
@@ -29,12 +30,16 @@ def __init__(self, text, dictionary=Dictionary()):
2930
self.pov = self.narrative.pov
3031
super().__init__(self.sentences, self.dictionary)
3132

32-
def __eq__(self, other):
33+
def __eq__(self, other: object) -> bool:
3334
"""Equality operator for instance variables."""
35+
if not isinstance(other, Paragraph):
36+
return False
3437
return self.text == other.text
3538

3639
@staticmethod
37-
def parse_paragraphs(text, dictionary=Dictionary()):
40+
def parse_paragraphs(
41+
text: str, dictionary: Dictionary = Dictionary()
42+
) -> list["Paragraph"]:
3843
"""Parses a text into a list of Paragraph objects."""
3944
return [
4045
Paragraph(paragraph, dictionary)

0 commit comments

Comments
 (0)