Skip to content

Commit fda5f61

Browse files
committed
Update helper functions
1 parent 79048f0 commit fda5f61

8 files changed

Lines changed: 149 additions & 27 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ from rmp_client import (
116116
)
117117
```
118118

119-
- `normalize_comment(text)` — Normalize text for deduplication (lowercase, collapse whitespace)
120-
- `is_valid_comment(text, min_len=10)`Check if a comment is non-empty and meets a minimum length
119+
- `normalize_comment(text, *, strip_html=True, strip_punctuation=False)` — Normalize text for deduplication (trim, strip HTML, lowercase, collapse whitespace; optionally strip punctuation)
120+
- `is_valid_comment(text, *, min_len=10)`Validate a comment and return a `ValidationResult` with diagnostics (empty, too short, all caps, excessive repeats, no alpha)
121121
- `clean_course_label(raw)` — Clean scraped course labels (remove counts, normalize whitespace)
122122
- `build_course_mapping(scraped, valid)` — Map scraped labels to known course codes
123123
- `analyze_sentiment(text)` — Compute sentiment label from text (uses TextBlob)

docs/extras.html

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,42 @@ <h2 id="sentiment">Sentiment <a href="#sentiment" class="anchor">#</a></h2>
4646
<pre><code class="language-python">result = analyze_sentiment("Great prof, explains concepts clearly.")
4747
print(result.score, result.label) # e.g. 0.65 "positive"</code></pre>
4848

49-
<h2 id="dedupe-helpers">Dedupe Helpers <a href="#dedupe-helpers" class="anchor">#</a></h2>
50-
<p>Normalize comments for deduplication and filter out low-quality entries.</p>
51-
<pre><code class="language-python">raw = " This prof is AMAZING!!! "
52-
normalized = normalize_comment(raw) # "this prof is amazing!!!"
53-
if is_valid_comment(normalized, min_len=10):
54-
print("Valid comment")</code></pre>
49+
<h2 id="helpers">Helpers <a href="#helpers" class="anchor">#</a></h2>
50+
51+
<h3 id="normalize_comment">normalize_comment</h3>
52+
<div class="method-sig">normalize_comment(text: str, *, strip_html: bool = True, strip_punctuation: bool = False) -&gt; str</div>
53+
<p>Normalizes a comment for comparison or deduplication. Trims whitespace, strips HTML tags (opt-out), lowercases, and collapses runs of whitespace. Optionally strips punctuation for looser matching.</p>
54+
<table>
55+
<tr><th>Parameter</th><th>Type</th><th>Default</th><th>Description</th></tr>
56+
<tr><td><code>text</code></td><td><code>str</code></td><td>&mdash;</td><td>Comment text</td></tr>
57+
<tr><td><code>strip_html</code></td><td><code>bool</code></td><td><code>True</code></td><td>Remove HTML tags</td></tr>
58+
<tr><td><code>strip_punctuation</code></td><td><code>bool</code></td><td><code>False</code></td><td>Remove all punctuation</td></tr>
59+
</table>
60+
<pre><code class="language-python">a = normalize_comment(" Great Professor! ")
61+
b = normalize_comment("great professor!")
62+
assert a == b # True
63+
64+
normalize_comment("&lt;b&gt;Loved&lt;/b&gt; this class") # "loved this class"
65+
normalize_comment("Hello, world!", strip_punctuation=True) # "hello world"</code></pre>
66+
67+
<h3 id="is_valid_comment">is_valid_comment</h3>
68+
<div class="method-sig">is_valid_comment(text: str, *, min_len: int = 10) -&gt; ValidationResult</div>
69+
<p>Validates a comment and returns detailed diagnostics. Checks for empty text, insufficient length, all-caps, excessive repeated characters, and absence of alphabetic characters.</p>
70+
<table>
71+
<tr><th>Parameter</th><th>Type</th><th>Default</th><th>Description</th></tr>
72+
<tr><td><code>text</code></td><td><code>str</code></td><td>&mdash;</td><td>Comment text</td></tr>
73+
<tr><td><code>min_len</code></td><td><code>int</code></td><td><code>10</code></td><td>Minimum character length</td></tr>
74+
</table>
75+
<p><strong>Returns:</strong> <code>ValidationResult</code> with <code>valid</code> (bool) and <code>issues</code> (list of <code>CommentIssue</code>).</p>
76+
<p>Each issue has a <code>code</code> (<code>"empty"</code>, <code>"too_short"</code>, <code>"all_caps"</code>, <code>"excessive_repeats"</code>, <code>"no_alpha"</code>) and a human-readable <code>message</code>.</p>
77+
<pre><code class="language-python">result = is_valid_comment("Good")
78+
# ValidationResult(valid=False, issues=[CommentIssue(code="too_short", ...)])
79+
80+
result = is_valid_comment("Great class, learned a lot")
81+
# ValidationResult(valid=True, issues=[])
82+
83+
result = is_valid_comment("WORST PROF EVER!!!")
84+
# ValidationResult(valid=False, issues=[CommentIssue(code="all_caps", ...)])</code></pre>
5585

5686
<h2 id="course-code-helpers">Course Code Helpers <a href="#course-code-helpers" class="anchor">#</a></h2>
5787
<p>Map scraped RMP course labels to your course catalog.</p>

docs/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ <h2 id="features">Features <a href="#features" class="anchor">#</a></h2>
5555
<li>In-memory caching for ratings pages</li>
5656
<li>Cursor-based pagination for all list/search endpoints</li>
5757
<li>Clear error hierarchy for precise exception handling</li>
58-
<li>Built-in helpers for ingestion workflows (sentiment, dedupe, course codes)</li>
58+
<li>Built-in helpers for ingestion workflows (sentiment, comment validation, course codes)</li>
5959
</ul>
6060

6161
<h2 id="requirements">Requirements <a href="#requirements" class="anchor">#</a></h2>
@@ -82,7 +82,7 @@ <h2 id="documentation">Documentation <a href="#documentation" class="anchor">#</
8282
<li><a href="usage.html">Usage</a> &mdash; Quickstart examples for every endpoint</li>
8383
<li><a href="configuration.html">Configuration</a> &mdash; Tuning retries, rate limits, timeouts, and headers</li>
8484
<li><a href="reference.html">API Reference</a> &mdash; Full method and type reference</li>
85-
<li><a href="extras.html">Extras</a> &mdash; Ingestion helpers (sentiment, dedupe, course mapping)</li>
85+
<li><a href="extras.html">Extras</a> &mdash; Ingestion helpers (sentiment, comment validation, course mapping)</li>
8686
</ul>
8787
</main>
8888

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "ratemyprofessors-client"
7-
version = "2.0.0"
7+
version = "2.1.0"
88
description = "Typed, retrying, rate-limited unofficial Python client for the RateMyProfessors GraphQL API."
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/rmp_client/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from .extras import (
1616
SentimentResult,
1717
analyze_sentiment,
18+
CommentIssue,
19+
ValidationResult,
1820
is_valid_comment,
1921
normalize_comment,
2022
build_course_mapping,
@@ -34,6 +36,8 @@
3436
"TokenBucket",
3537
"SentimentResult",
3638
"analyze_sentiment",
39+
"CommentIssue",
40+
"ValidationResult",
3741
"is_valid_comment",
3842
"normalize_comment",
3943
"build_course_mapping",

src/rmp_client/extras/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
# Ingestion helpers: sentiment, dedupe, course_codes.
1+
# Ingestion helpers: sentiment, helpers, course_codes.
22
# Re-exported from rmp_client so you can: from rmp_client import analyze_sentiment, ...
33

44
from .sentiment import SentimentResult, analyze_sentiment
5-
from .dedupe import is_valid_comment, normalize_comment
5+
from .helpers import CommentIssue, ValidationResult, is_valid_comment, normalize_comment
66
from .course_codes import build_course_mapping, clean_course_label
77

88
__all__ = [
99
"SentimentResult",
1010
"analyze_sentiment",
11+
"CommentIssue",
12+
"ValidationResult",
1113
"is_valid_comment",
1214
"normalize_comment",
1315
"build_course_mapping",

src/rmp_client/extras/dedupe.py

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/rmp_client/extras/helpers.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""Helpers for normalizing and validating rating comments."""
2+
3+
from __future__ import annotations
4+
5+
import re
6+
from dataclasses import dataclass, field
7+
from typing import Literal
8+
9+
10+
def _strip_html(text: str) -> str:
11+
"""Strip HTML tags from text (RMP comments occasionally contain markup)."""
12+
return re.sub(r"<[^>]*>", "", text)
13+
14+
15+
def normalize_comment(
16+
text: str,
17+
*,
18+
strip_html: bool = True,
19+
strip_punctuation: bool = False,
20+
) -> str:
21+
"""Normalize a comment for comparison or deduplication.
22+
23+
- Trims leading/trailing whitespace
24+
- Strips HTML tags (opt-out via *strip_html*)
25+
- Lowercases
26+
- Collapses runs of whitespace to a single space
27+
- Optionally strips punctuation for looser matching
28+
"""
29+
out = text.strip()
30+
if strip_html:
31+
out = _strip_html(out)
32+
out = re.sub(r"\s+", " ", out.lower())
33+
if strip_punctuation:
34+
out = re.sub(r"[^\w\s]", "", out)
35+
return out
36+
37+
38+
IssueCode = Literal[
39+
"empty",
40+
"too_short",
41+
"all_caps",
42+
"excessive_repeats",
43+
"no_alpha",
44+
]
45+
46+
47+
@dataclass
48+
class CommentIssue:
49+
code: IssueCode
50+
message: str
51+
52+
53+
@dataclass
54+
class ValidationResult:
55+
valid: bool
56+
issues: list[CommentIssue] = field(default_factory=list)
57+
58+
59+
def is_valid_comment(text: str, *, min_len: int = 10) -> ValidationResult:
60+
"""Validate a comment and return detailed diagnostics.
61+
62+
Checks for:
63+
- Empty or whitespace-only text
64+
- Below minimum length (*min_len*, default 10)
65+
- All uppercase (shouting)
66+
- Excessive repeated characters (e.g. "aaaaaaa")
67+
- No alphabetic characters at all
68+
"""
69+
issues: list[CommentIssue] = []
70+
trimmed = (text or "").strip()
71+
72+
if not trimmed:
73+
issues.append(CommentIssue(code="empty", message="Comment is empty"))
74+
return ValidationResult(valid=False, issues=issues)
75+
76+
if len(trimmed) < min_len:
77+
issues.append(
78+
CommentIssue(
79+
code="too_short",
80+
message=f"Comment is {len(trimmed)} chars (minimum {min_len})",
81+
)
82+
)
83+
84+
if len(trimmed) > 3 and trimmed == trimmed.upper() and re.search(r"[A-Z]", trimmed):
85+
issues.append(CommentIssue(code="all_caps", message="Comment is all uppercase"))
86+
87+
if re.search(r"(.)\1{4,}", trimmed, re.IGNORECASE):
88+
issues.append(
89+
CommentIssue(
90+
code="excessive_repeats",
91+
message="Comment contains excessive repeated characters",
92+
)
93+
)
94+
95+
if not re.search(r"[a-zA-Z]", trimmed):
96+
issues.append(
97+
CommentIssue(code="no_alpha", message="Comment contains no alphabetic characters")
98+
)
99+
100+
return ValidationResult(valid=len(issues) == 0, issues=issues)

0 commit comments

Comments
 (0)