''' Simple interface for https://factordb.com inspired by https://github.com/ihebski/factordb TODO: 1. Implement primality certificate generation, read this: https://reference.wolfram.com/language/PrimalityProving/ref/ProvablePrimeQ.html ''' import requests from enum import Enum, StrEnum _FDB_URI = 'https://factordb.com' # Generated by https://www.asciiart.eu/text-to-ascii-art # using "ANSI Shadow" font and "Box drawings double" border with 1 H. Padding _BANNER = ''' ╔═══════════════════════════════════════════════════╗ ║ ███████╗ ██████╗████████╗██████╗ ██████╗ ██████╗ ║ ║ ██╔════╝██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██╔══██╗ ║ ║ █████╗ ██║ ██║ ██████╔╝██║ ██║██████╔╝ ║ ║ ██╔══╝ ██║ ██║ ██╔══██╗██║ ██║██╔══██╗ ║ ║ ██║ ╚██████╗ ██║ ██║ ██║██████╔╝██████╔╝ ║ ║ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ║ ╚═══════════════════════════════════════════════════╝ ╚═══ cli by imbored ═══╝ '''.strip() # Enumeration of number types based on their factorisation class FType(Enum): Unit = 1 Composite = 2 Prime = 3 Unknown = 4 class FCertainty(Enum): Certain = 1 Partial = 2 Unknown = 3 # FactorDB result status codes class DBStatus(StrEnum): C = 'C' CF = 'CF' FF = 'FF' P = 'P' PRP = 'PRP' U = 'U' Unit = 'Unit' # just for 1 N = 'N' Add = '*' def is_unknown(self) -> bool: return (self in [DBStatus.U, DBStatus.N, DBStatus.Add]) def classify(self) -> tuple[FType, FCertainty]: return _STATUS_MAP[self] def msg_verbose(self) -> str: return _STATUS_MSG_VERBOSE[self] # Map of DB Status codes to their factorisation type and certainty _STATUS_MAP = { DBStatus.Unit: (FType.Unit, FCertainty.Certain), DBStatus.C: (FType.Composite, FCertainty.Unknown), DBStatus.CF: (FType.Composite, FCertainty.Partial), DBStatus.FF: (FType.Composite, FCertainty.Certain), DBStatus.P: (FType.Prime, FCertainty.Certain), DBStatus.PRP: (FType.Prime, FCertainty.Partial), DBStatus.U: (FType.Unknown, FCertainty.Unknown), DBStatus.N: (FType.Unknown, FCertainty.Unknown), DBStatus.Add: (FType.Unknown, FCertainty.Unknown), } # Reference: https://factordb.com/status.html # NOTE: my factor messages differ slightly from the reference _STATUS_MSG_VERBOSE = { DBStatus.Unit: 'Unit, trivial factorisation', DBStatus.C: 'Composite, no factors known', DBStatus.CF: 'Composite, *partially* factors', DBStatus.FF: 'Composite, fully factored', DBStatus.P: 'Prime', DBStatus.PRP: 'Probable prime', DBStatus.U: 'Unknown (*but in database)', DBStatus.N: 'Not in database (-not added due to your settings)', DBStatus.Add: 'Not in database (+added during request)', } # Struct for storing database results with named properties class DBResult: def __init__(self, status: DBStatus, factors: tuple[tuple[int, int]]) -> None: self.status = status self.factors = factors self.ftype, self.certainty = self.status.classify() def _make_cookie(fdbuser: str | None) -> dict[str, str]: return {} if fdbuser is None else {'fdbuser': fdbuser} def _get_key(by_id: bool): return 'id' if by_id else 'query' def _api_query(n: int, fdbuser: str | None, by_id: bool = False) -> requests.models.Response: key = _get_key(by_id) uri = f'{_FDB_URI}/api?{key}={n}' return requests.get(uri, cookies=_make_cookie(fdbuser)) def _report_factor(n: int, factor: int, fdbuser: str | None, by_id: bool = False) -> requests.models.Response: key = _get_key(by_id) uri = f'{_FDB_URI}/reportfactor.php?{key}={n}&factor={factor}' return requests.get(uri, cookies=_make_cookie(fdbuser)) ''' Attempts a query to FactorDB, returns a DBResult object on success, or None if the request failed due to the get request raising a RequestException. ''' def query(n: int, token: str | None = None, by_id: bool = False, cli: bool = False) -> DBResult | None: if cli: print(_BANNER) try: resp = _api_query(n, token, by_id=by_id) except requests.exceptions.RequestException: return None content = resp.json() result = DBResult( DBStatus(content['status']), tuple((int(F[0]), F[1]) for F in content['factors']) ) if cli: print(f'Status: {result.status.msg_verbose()}') print(result.factors) # ensure the unit has the trivial factorisation (for consistency) if result.status == DBStatus.Unit: result.factors = ((1, 1),) return result ''' Reports a known factor to FactorDB, also tests it is actually a factor to avoid wasting FactorDBs resources. ''' def report(n: int, factor: int, by_id: int, token: str | None = None) -> None: try: resp = _report_factor(n, factor, token) except requests.exceptions.RequestException: return None content = resp.json() print(content)