numclassify 0.3.3__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {numclassify-0.3.3 → numclassify-0.4.0}/CHANGELOG.md +19 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/PKG-INFO +2 -2
- numclassify-0.4.0/SECURITY.md +24 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/__init__.py +7 -1
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/_core/divisors.py +2 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/_core/exam_types.py +4 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/_core/powers.py +3 -1
- {numclassify-0.3.3 → numclassify-0.4.0}/pyproject.toml +2 -2
- {numclassify-0.3.3 → numclassify-0.4.0}/tests/test_registry.py +69 -13
- {numclassify-0.3.3 → numclassify-0.4.0}/.github/workflows/ci.yml +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/.github/workflows/docs.yml +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/.github/workflows/publish.yml +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/.gitignore +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/CONTRIBUTING.md +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/LICENSE +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/README.md +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/docs/api.md +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/docs/changelog.md +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/docs/cli.md +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/docs/extra/saffron.css +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/docs/index.md +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/docs/playground.css +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/docs/playground.html +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/docs/playground.js +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/examples/basic_usage.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/examples/custom_type.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/examples/find_perfect_numbers.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/examples/random_classify.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/examples/stream_large_range.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/mkdocs.yml +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/__main__.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/_core/__init__.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/_core/combinatorial.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/_core/digital.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/_core/figurate.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/_core/number_theory.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/_core/primes.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/_core/recreational.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/_core/sequences.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/_registry.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/cli.py +0 -0
- {numclassify-0.3.3 → numclassify-0.4.0}/numclassify/py.typed +0 -0
|
@@ -6,6 +6,25 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.4.0] - 2026-06-13
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `classify()` now returns `notable_score` field — score excluding figurate/centered-figurate
|
|
13
|
+
hits. Prevents misleading score inflation for n=1 (which is the first member of every
|
|
14
|
+
polygonal sequence).
|
|
15
|
+
- `is_unique(n)` now returns `False` for negative `n`. Previously returned `True` due to
|
|
16
|
+
`abs()` stripping the sign.
|
|
17
|
+
- `is_practical(0)` now returns `False`. Previously returned `True` because
|
|
18
|
+
`_factorization(0)` returns `[]` and the loop never ran.
|
|
19
|
+
- Removed leaked internal names (`Optional`, `_version`, `_PackageNotFoundError`) from
|
|
20
|
+
`dir(numclassify)`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- Development status updated to `5 - Production/Stable` in package classifiers.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- `SECURITY.md` with vulnerability reporting instructions.
|
|
27
|
+
|
|
9
28
|
## [0.3.3] - 2026-06-13
|
|
10
29
|
|
|
11
30
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: numclassify
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: The most comprehensive Python library for number classification - 3000+ number types
|
|
5
5
|
Project-URL: Homepage, https://github.com/aratrikghosh2011-tech/numclassify
|
|
6
6
|
Project-URL: Repository, https://github.com/aratrikghosh2011-tech/numclassify
|
|
@@ -10,7 +10,7 @@ Author-email: Aratrik Ghosh <aratrikghosh2011@gmail.com>
|
|
|
10
10
|
License: MIT
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Keywords: armstrong,classification,figurate,mathematics,number-theory,prime
|
|
13
|
-
Classifier: Development Status ::
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
14
|
Classifier: Intended Audience :: Education
|
|
15
15
|
Classifier: Intended Audience :: Science/Research
|
|
16
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
Only the latest release on PyPI is supported.
|
|
6
|
+
|
|
7
|
+
| Version | Supported |
|
|
8
|
+
|---------|-----------|
|
|
9
|
+
| 0.4.x | ✅ |
|
|
10
|
+
| < 0.4 | ❌ |
|
|
11
|
+
|
|
12
|
+
## Reporting a Vulnerability
|
|
13
|
+
|
|
14
|
+
To report a security vulnerability, email **aratrikghosh2011@gmail.com** with
|
|
15
|
+
the subject line `[numclassify] Security Report`.
|
|
16
|
+
|
|
17
|
+
Please include:
|
|
18
|
+
- A description of the vulnerability
|
|
19
|
+
- Steps to reproduce
|
|
20
|
+
- Potential impact
|
|
21
|
+
|
|
22
|
+
You can expect an acknowledgment within 48 hours. Since `numclassify` is a
|
|
23
|
+
pure computation library with no network access, no file system writes, and no
|
|
24
|
+
external dependencies, the attack surface is minimal.
|
|
@@ -115,6 +115,10 @@ def classify(n: int) -> dict:
|
|
|
115
115
|
"number": n,
|
|
116
116
|
"true_properties": true_props,
|
|
117
117
|
"score": len(true_props),
|
|
118
|
+
"notable_score": sum(
|
|
119
|
+
len(v) for k, v in categories.items()
|
|
120
|
+
if k not in ("figurate", "figurate_centered")
|
|
121
|
+
),
|
|
118
122
|
"categories": categories,
|
|
119
123
|
}
|
|
120
124
|
|
|
@@ -216,6 +220,7 @@ def stream(start: int, end: int, min_score: int = 0, has_property: Optional[str]
|
|
|
216
220
|
|
|
217
221
|
__all__ = [
|
|
218
222
|
"__version__",
|
|
223
|
+
"register",
|
|
219
224
|
"is_prime",
|
|
220
225
|
"is_armstrong",
|
|
221
226
|
"is_perfect",
|
|
@@ -237,4 +242,5 @@ __all__ = [
|
|
|
237
242
|
# Clean up internal names that leak into dir(nc)
|
|
238
243
|
del (_primes, _figurate, _digital, _recreational,
|
|
239
244
|
_divisors, _sequences, _powers, _number_theory,
|
|
240
|
-
_combinatorial, _exam_types, _core, _registry
|
|
245
|
+
_combinatorial, _exam_types, _core, _registry,
|
|
246
|
+
Optional, _version, _PackageNotFoundError)
|
|
@@ -375,10 +375,14 @@ def is_unique(n: int) -> bool:
|
|
|
375
375
|
True
|
|
376
376
|
>>> is_unique(0)
|
|
377
377
|
True
|
|
378
|
+
>>> is_unique(-5) # negative numbers are not unique
|
|
379
|
+
False
|
|
378
380
|
|
|
379
381
|
Notes
|
|
380
382
|
-----
|
|
381
383
|
Single-digit numbers and 0 are always unique.
|
|
382
384
|
"""
|
|
385
|
+
if n < 0:
|
|
386
|
+
return False
|
|
383
387
|
s = str(abs(n))
|
|
384
388
|
return len(s) == len(set(s))
|
|
@@ -225,7 +225,9 @@ def is_sum_of_three_squares(n: int) -> bool:
|
|
|
225
225
|
"""
|
|
226
226
|
if n < 0:
|
|
227
227
|
return False
|
|
228
|
-
|
|
228
|
+
if n == 0:
|
|
229
|
+
return True # 0 = 0^2 + 0^2 + 0^2
|
|
230
|
+
# Remove factors of 4 (Legendre's three-square theorem)
|
|
229
231
|
while n % 4 == 0:
|
|
230
232
|
n //= 4
|
|
231
233
|
return n % 8 != 7
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "numclassify"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "The most comprehensive Python library for number classification - 3000+ number types"
|
|
9
9
|
authors = [{name = "Aratrik Ghosh", email = "aratrikghosh2011@gmail.com"}]
|
|
10
10
|
readme = "README.md"
|
|
@@ -12,7 +12,7 @@ requires-python = ">=3.8"
|
|
|
12
12
|
license = {text = "MIT"}
|
|
13
13
|
keywords = ["number-theory", "mathematics", "classification", "armstrong", "prime", "figurate"]
|
|
14
14
|
classifiers = [
|
|
15
|
-
"Development Status ::
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
16
|
"Intended Audience :: Education",
|
|
17
17
|
"Intended Audience :: Science/Research",
|
|
18
18
|
"Topic :: Scientific/Engineering :: Mathematics",
|
|
@@ -353,26 +353,82 @@ def test_is_factorial():
|
|
|
353
353
|
# ---------------------------------------------------------------------------
|
|
354
354
|
|
|
355
355
|
def _get_all_registered_funcs():
|
|
356
|
-
"""Return list of (name, func) for
|
|
356
|
+
"""Return list of (name, func) for canonical registry entries.
|
|
357
|
+
|
|
358
|
+
Samples at most 10 entries from figurate / figurate_centered (since all
|
|
359
|
+
use the same parametric functions) and all entries from other categories.
|
|
360
|
+
"""
|
|
357
361
|
from numclassify._registry import REGISTRY, _normalize
|
|
358
362
|
seen = set()
|
|
359
363
|
result = []
|
|
364
|
+
sample = {"figurate", "figurate_centered"}
|
|
365
|
+
sample_count = {cat: 0 for cat in sample}
|
|
366
|
+
max_per_cat = 10
|
|
360
367
|
for key, entry in REGISTRY.items():
|
|
361
|
-
if key == _normalize(entry.name):
|
|
368
|
+
if key == _normalize(entry.name):
|
|
362
369
|
if key not in seen:
|
|
363
370
|
seen.add(key)
|
|
364
|
-
|
|
371
|
+
cat = entry.category
|
|
372
|
+
if cat in sample:
|
|
373
|
+
if sample_count[cat] < max_per_cat:
|
|
374
|
+
sample_count[cat] += 1
|
|
375
|
+
result.append((entry.name, entry.func))
|
|
376
|
+
else:
|
|
377
|
+
result.append((entry.name, entry.func))
|
|
365
378
|
return result
|
|
366
379
|
|
|
367
380
|
|
|
368
381
|
@pytest.mark.parametrize("name,func", _get_all_registered_funcs())
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
382
|
+
def test_no_crash_on_edge_inputs(name, func):
|
|
383
|
+
"""Every registered type must not raise on inputs 0, 1, 2."""
|
|
384
|
+
for n in [0, 1, 2]:
|
|
385
|
+
try:
|
|
386
|
+
result = func(n)
|
|
387
|
+
assert isinstance(result, bool), (
|
|
388
|
+
f"{name}({n}) returned {type(result)}, expected bool"
|
|
389
|
+
)
|
|
390
|
+
except Exception as e:
|
|
391
|
+
pytest.fail(f"{name}({n}) raised {type(e).__name__}: {e}")
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
# ---------------------------------------------------------------------------
|
|
395
|
+
# v0.4.0 regression tests
|
|
396
|
+
# ---------------------------------------------------------------------------
|
|
397
|
+
|
|
398
|
+
def test_classify_has_notable_score():
|
|
399
|
+
"""classify() must include 'notable_score' key."""
|
|
400
|
+
result = nc.classify(7)
|
|
401
|
+
assert "notable_score" in result
|
|
402
|
+
assert isinstance(result["notable_score"], int)
|
|
403
|
+
assert result["notable_score"] <= result["score"]
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def test_classify_n1_notable_score_reasonable():
|
|
407
|
+
"""n=1 notable_score must be much less than total score (no figurate inflation)."""
|
|
408
|
+
result = nc.classify(1)
|
|
409
|
+
# notable_score excludes figurate and figurate_centered
|
|
410
|
+
assert result["notable_score"] < 100
|
|
411
|
+
# Total score will still be large (mathematically correct)
|
|
412
|
+
assert result["score"] > 1000
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def test_is_unique_negative():
|
|
416
|
+
"""is_unique must return False for negative integers."""
|
|
417
|
+
from numclassify._core.exam_types import is_unique
|
|
418
|
+
assert is_unique(-5) is False
|
|
419
|
+
assert is_unique(-1) is False
|
|
420
|
+
assert is_unique(-123) is False
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def test_is_practical_zero():
|
|
424
|
+
"""is_practical(0) must return False."""
|
|
425
|
+
from numclassify._core.divisors import is_practical
|
|
426
|
+
assert is_practical(0) is False
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def test_no_leaked_names():
|
|
430
|
+
"""Optional, _version, _PackageNotFoundError must not be in dir(nc)."""
|
|
431
|
+
public_names = [n for n in dir(nc) if not n.startswith("__")]
|
|
432
|
+
assert "Optional" not in public_names
|
|
433
|
+
assert "_version" not in public_names
|
|
434
|
+
assert "_PackageNotFoundError" not in public_names
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|