From fe48041039162576e3095b5d8c9709a9dc6b4354 Mon Sep 17 00:00:00 2001 From: Mark Joshwel Date: Sat, 2 Sep 2023 13:42:21 +0000 Subject: [PATCH] s+: defaultable Behaviour and only-exception Results --- playground.ipynb | 14 +++--- surplus.py | 113 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 92 insertions(+), 35 deletions(-) diff --git a/playground.ipynb b/playground.ipynb index 9eb924c..06156dd 100644 --- a/playground.ipynb +++ b/playground.ipynb @@ -42,7 +42,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -60,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -68,7 +68,6 @@ "output_type": "stream", "text": [ "True\tNone \t3\n", - "False\t'stest' \t-1\n", "False\tZeroDivisionError('division by zero') \tdivision by zero (ZeroDivisionError)\n" ] }, @@ -79,9 +78,9 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/home/m/works/surplus/surplus.future.ipynb Cell 5\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m\"\u001b[39m\u001b[39m{}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{:<40}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{}\u001b[39;00m\u001b[39m\"\u001b[39m\u001b[39m.\u001b[39mformat(\u001b[39mbool\u001b[39m(err_result), \u001b[39mrepr\u001b[39m(err_result\u001b[39m.\u001b[39merror), err_result\u001b[39m.\u001b[39mget()))\n\u001b[1;32m 12\u001b[0m \u001b[39mprint\u001b[39m(\n\u001b[1;32m 13\u001b[0m \u001b[39m\"\u001b[39m\u001b[39m{}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{:<40}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{}\u001b[39;00m\u001b[39m\"\u001b[39m\u001b[39m.\u001b[39mformat(\n\u001b[1;32m 14\u001b[0m \u001b[39mbool\u001b[39m(exc_result), \u001b[39mrepr\u001b[39m(exc_result\u001b[39m.\u001b[39merror), exc_result\u001b[39m.\u001b[39mcry(string\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m)\n\u001b[1;32m 15\u001b[0m )\n\u001b[1;32m 16\u001b[0m )\n\u001b[0;32m---> 17\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m\"\u001b[39m\u001b[39m{}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{:<40}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{}\u001b[39;00m\u001b[39m\"\u001b[39m\u001b[39m.\u001b[39mformat(\u001b[39mbool\u001b[39m(exc_result), \u001b[39mrepr\u001b[39m(exc_result\u001b[39m.\u001b[39merror), exc_result\u001b[39m.\u001b[39;49mget()))\n", - "File \u001b[0;32m~/works/surplus/surplus.py:202\u001b[0m, in \u001b[0;36mResult.get\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 200\u001b[0m \u001b[39m\"\"\"method that returns self.value if Result is non-erroneous else raises error\"\"\"\u001b[39;00m\n\u001b[1;32m 201\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39misinstance\u001b[39m(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39merror, \u001b[39mBaseException\u001b[39;00m):\n\u001b[0;32m--> 202\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39merror\n\u001b[1;32m 204\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mvalue\n", - "\u001b[1;32m/home/m/works/surplus/surplus.future.ipynb Cell 5\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 3\u001b[0m err_result \u001b[39m=\u001b[39m Result[\u001b[39mint\u001b[39m](\u001b[39m-\u001b[39m\u001b[39m1\u001b[39m, error\u001b[39m=\u001b[39m\u001b[39m\"\u001b[39m\u001b[39mstest\u001b[39m\u001b[39m\"\u001b[39m)\n\u001b[1;32m 5\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m----> 6\u001b[0m \u001b[39m1\u001b[39;49m \u001b[39m/\u001b[39;49m \u001b[39m0\u001b[39;49m\n\u001b[1;32m 7\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mException\u001b[39;00m \u001b[39mas\u001b[39;00m exc:\n\u001b[1;32m 8\u001b[0m exc_result \u001b[39m=\u001b[39m Result[\u001b[39mint\u001b[39m](\u001b[39m-\u001b[39m\u001b[39m1\u001b[39m, error\u001b[39m=\u001b[39mexc)\n", + "\u001b[1;32m/home/m/works/surplus/playground.ipynb Cell 5\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m\"\u001b[39m\u001b[39m{}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{:<40}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{}\u001b[39;00m\u001b[39m\"\u001b[39m\u001b[39m.\u001b[39mformat(\u001b[39mbool\u001b[39m(nom_result), \u001b[39mrepr\u001b[39m(nom_result\u001b[39m.\u001b[39merror), nom_result\u001b[39m.\u001b[39mget()))\n\u001b[1;32m 9\u001b[0m \u001b[39mprint\u001b[39m(\n\u001b[1;32m 10\u001b[0m \u001b[39m\"\u001b[39m\u001b[39m{}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{:<40}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{}\u001b[39;00m\u001b[39m\"\u001b[39m\u001b[39m.\u001b[39mformat(\n\u001b[1;32m 11\u001b[0m \u001b[39mbool\u001b[39m(exc_result), \u001b[39mrepr\u001b[39m(exc_result\u001b[39m.\u001b[39merror), exc_result\u001b[39m.\u001b[39mcry(string\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m)\n\u001b[1;32m 12\u001b[0m )\n\u001b[1;32m 13\u001b[0m )\n\u001b[0;32m---> 14\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m\"\u001b[39m\u001b[39m{}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{:<40}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{}\u001b[39;00m\u001b[39m\"\u001b[39m\u001b[39m.\u001b[39mformat(\u001b[39mbool\u001b[39m(exc_result), \u001b[39mrepr\u001b[39m(exc_result\u001b[39m.\u001b[39merror), exc_result\u001b[39m.\u001b[39;49mget()))\n", + "File \u001b[0;32m~/works/surplus/surplus.py:247\u001b[0m, in \u001b[0;36mResult.get\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 245\u001b[0m \u001b[39m\"\"\"method that returns self.value if Result is non-erroneous else raises error\"\"\"\u001b[39;00m\n\u001b[1;32m 246\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39misinstance\u001b[39m(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39merror, \u001b[39mBaseException\u001b[39;00m):\n\u001b[0;32m--> 247\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39merror\n\u001b[1;32m 248\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mvalue\n", + "\u001b[1;32m/home/m/works/surplus/playground.ipynb Cell 5\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m nom_result \u001b[39m=\u001b[39m Result[\u001b[39mint\u001b[39m](\u001b[39m3\u001b[39m)\n\u001b[1;32m 3\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m----> 4\u001b[0m \u001b[39m1\u001b[39;49m \u001b[39m/\u001b[39;49m \u001b[39m0\u001b[39;49m\n\u001b[1;32m 5\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mException\u001b[39;00m \u001b[39mas\u001b[39;00m exc:\n\u001b[1;32m 6\u001b[0m exc_result \u001b[39m=\u001b[39m Result[\u001b[39mint\u001b[39m](\u001b[39m-\u001b[39m\u001b[39m1\u001b[39m, error\u001b[39m=\u001b[39mexc)\n", "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero" ] } @@ -89,15 +88,12 @@ "source": [ "nom_result = Result[int](3)\n", "\n", - "err_result = Result[int](-1, error=\"stest\")\n", - "\n", "try:\n", " 1 / 0\n", "except Exception as exc:\n", " exc_result = Result[int](-1, error=exc)\n", "\n", "print(\"{}\\t{:<40}\\t{}\".format(bool(nom_result), repr(nom_result.error), nom_result.get()))\n", - "print(\"{}\\t{:<40}\\t{}\".format(bool(err_result), repr(err_result.error), err_result.get()))\n", "print(\n", " \"{}\\t{:<40}\\t{}\".format(\n", " bool(exc_result), repr(exc_result.error), exc_result.cry(string=True)\n", diff --git a/surplus.py b/surplus.py index dfe7acc..d2b5a31 100644 --- a/surplus.py +++ b/surplus.py @@ -123,11 +123,33 @@ SHAREABLE_TEXT_NAMES: Final[tuple[str, ...]] = ( # exceptions -class NoSuitableLocationError(Exception): +class SurplusException(Exception): + """base skeleton exception for handling and typing surplus exception classes""" + ... -class IncompletePlusCodeError(Exception): +class NoSuitableLocationError(SurplusException): + ... + + +class IncompletePlusCodeError(SurplusException): + ... + + +class PlusCodeNotFoundError(SurplusException): + ... + + +class LatlongParseError(SurplusException): + ... + + +class EmptyQueryError(SurplusException): + ... + + +class UnavailableFeatureError(SurplusException): ... @@ -135,7 +157,15 @@ class IncompletePlusCodeError(Exception): class ConversionResultTypeEnum(Enum): - """enum representing what the result type of conversion should be""" + """ + enum representing what the result type of conversion should be + + values + PLUS_CODE: str = "pluscode" + LOCAL_CODE: str = "localcode" + LATLONG: str = "latlong" + SHAREABLE_TEXT: str = "shareabletext" + """ PLUS_CODE = "pluscode" LOCAL_CODE = "localcode" @@ -153,8 +183,8 @@ class Result(NamedTuple, Generic[ResultType]): arguments value: ResultType value to return or fallback value if erroneous - error: BaseException | str | None = None - exception if any, or an error message + error: BaseException | None = None + exception if any methods def __bool__(self) -> bool: ... @@ -181,7 +211,7 @@ class Result(NamedTuple, Generic[ResultType]): """ value: ResultType - error: BaseException | str | None = None + error: BaseException | None = None def __bool__(self) -> bool: """method that returns True if self.error is not None""" @@ -356,7 +386,7 @@ class LocalCodeQuery(NamedTuple): return Result[Latlong]( PlusCodeQuery(recovered_pluscode.get()) .to_lat_long_coord(geocoder=geocoder) - .get() # PlusCodeQuery can get latlong coord offline, so no need to handle + .get() # PlusCodeQuery can get latlong coord safely, so no need to handle ) @@ -467,13 +497,13 @@ class Behaviour(NamedTuple): typing.NamedTuple representing expected behaviour of surplus arguments - query: str | list[str] + query: str | list[str] = "" str: original user-passed query string list[str]: original user-passed query string split by spaces - geocoder: Callable[[str], Latlong] + geocoder: Callable[[str], Latlong] = default_geocoder name string to location function, must take in a string and return a Latlong. exceptions are handled by the caller. - reverser: Callable[[str], dict[str, Any]] + reverser: Callable[[str], dict[str, Any]] = default_reverser Latlong object to dictionary function, must take in a string and return a dict. keys found in SHAREABLE_TEXT_LINE_*_KEYS used to access address details are placed top-level in the dict. exceptions are handled by the caller. see @@ -490,7 +520,7 @@ class Behaviour(NamedTuple): what type to convert query to """ - query: str | list[str] + query: str | list[str] = "" geocoder: Callable[[str], Latlong] = default_geocoder reverser: Callable[[Latlong], dict[str, Any]] = default_reverser stderr: TextIO = stderr @@ -532,14 +562,14 @@ def parse_query( original_query: str = "" split_query: list[str] = [] - if isinstance(behaviour.query, str): - original_query = behaviour.query - split_query = behaviour.query.split(" ") - - else: + if isinstance(behaviour.query, list): original_query = " ".join(behaviour.query) split_query = behaviour.query + else: + original_query = str(behaviour.query) + split_query = behaviour.query.split(" ") + for word in split_query: if validator.is_valid(word): portion_plus_code = word @@ -553,7 +583,7 @@ def parse_query( if portion_plus_code == "": return Result[Query]( LatlongQuery(EMPTY_LATLONG), - error="unable to find a Plus Code", + error=PlusCodeNotFoundError("unable to find a Plus Code"), ) # found a plus code! @@ -601,7 +631,7 @@ def parse_query( if (behaviour.query == []) or (behaviour.query == ""): return Result[Query]( LatlongQuery(EMPTY_LATLONG), - error="query is empty", + error=EmptyQueryError("behaviour.query is empty"), ) # try to find a plus/local code @@ -644,7 +674,7 @@ def parse_query( if len(comma_split_single) > 2: return Result[Query]( LatlongQuery(EMPTY_LATLONG), - error="unable to parse latlong coord", + error=LatlongParseError("unable to parse latlong coord"), ) try: # try to type cast query @@ -883,21 +913,41 @@ def _generate_text( def surplus( - query: PlusCodeQuery | LocalCodeQuery | LatlongQuery | StringQuery, + query: Query | str, behaviour: Behaviour, ) -> Result[str]: """ query to shareable text conversion function - query: PlusCodeQuery | LocalCodeQuery | LatlongQuery | StringQuery - query object to convert, see respective docstrings for more information on each - type of query object + query: Query | str + Query: query object to convert, see respective docstrings for more information on + each type of query object + str: string to attempt to query for behaviour: Behaviour program behaviour namedtuple returns Result[str] """ + if not isinstance(query, (PlusCodeQuery, LocalCodeQuery, LatlongQuery, StringQuery)): + query_result = parse_query( + behaviour=Behaviour( + query=str(query), + geocoder=behaviour.geocoder, + reverser=behaviour.reverser, + stderr=behaviour.stderr, + stdout=behaviour.stdout, + debug=behaviour.debug, + version_header=behaviour.version_header, + convert_to_type=behaviour.convert_to_type, + ) + ) + + if not query_result: + return Result[str]("", error=query_result.error) + + query = query_result.get() + # operate on query text: str = "" @@ -943,18 +993,29 @@ def surplus( case ConversionResultTypeEnum.PLUS_CODE: # TODO: https://github.com/markjoshwel/surplus/issues/18 return Result[str]( - text, error="converting to Plus Code is not implemented yet" + text, + error=UnavailableFeatureError( + "converting to Plus Code is not implemented yet" + ), ) case ConversionResultTypeEnum.LOCAL_CODE: # TODO: https://github.com/markjoshwel/surplus/issues/18 return Result[str]( - text, error="converting to Plus Code is not implemented yet" + text, + error=UnavailableFeatureError( + "converting to Plus Code is not implemented yet" + ), ) case ConversionResultTypeEnum.LATLONG: # TODO: https://github.com/markjoshwel/surplus/issues/18 - return Result[str](text, error="converting to Latlong is not implemented yet") + return Result[str]( + text, + error=UnavailableFeatureError( + "converting to Latlong is not implemented yet" + ), + ) case _: return Result[str](