From 8ad086f4685dedd82ec70845d652b54f69ab3091 Mon Sep 17 00:00:00 2001 From: Mark Joshwel Date: Fri, 16 Jun 2023 05:19:19 +0000 Subject: [PATCH 01/10] meta: bump ver --- README.md | 2 +- pyproject.toml | 2 +- surplus.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 16c1000..6c9126f 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Plus Code to iOS-Shortcuts-like shareable text ```text $ surplus 9R3J+R9 Singapore -surplus version 1.1.0 +surplus version 1.1.1 Thomson Plaza 301 Upper Thomson Road, Bishan 574408 diff --git a/pyproject.toml b/pyproject.toml index 8787f3c..13ac65f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "surplus" -version = "1.1.0" +version = "1.1.1" description = "Plus Code to iOS-Shortcuts-like shareable text" authors = ["Mark Joshwel "] license = "Unlicence" diff --git a/surplus.py b/surplus.py index 3d95219..b1c67ac 100644 --- a/surplus.py +++ b/surplus.py @@ -39,7 +39,7 @@ from pluscodes import PlusCode # type: ignore from pluscodes.openlocationcode import recoverNearest # type: ignore from pluscodes.validator import Validator # type: ignore -VERSION: Final[tuple[int, int, int]] = (1, 1, 0) +VERSION: Final[tuple[int, int, int]] = (1, 1, 1) class Localcode(NamedTuple): From 4fdb6b9fc8d8a7f443f51624c2fe5647628cf974 Mon Sep 17 00:00:00 2001 From: Mark Joshwel Date: Fri, 16 Jun 2023 05:38:58 +0000 Subject: [PATCH 02/10] code: fix #4 --- .github/workflows/slsa-publish.yml | 52 ------------------------------ surplus.py | 9 ++++-- 2 files changed, 7 insertions(+), 54 deletions(-) delete mode 100644 .github/workflows/slsa-publish.yml diff --git a/.github/workflows/slsa-publish.yml b/.github/workflows/slsa-publish.yml deleted file mode 100644 index dc633cb..0000000 --- a/.github/workflows/slsa-publish.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: release (slsa 3) -on: - push: - tags: - - '*' - -jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: write - outputs: - hashes: ${{ steps.hash.outputs.hashes }} - - steps: - - name: checkout - uses: actions/checkout@v3 - - - name: install devbox - uses: jetpack-io/devbox-install-action@v0.3.0 - - - name: install dependencies - run: devbox run poetry install - - - name: install dependencies - id: build - run: devbox run poetry build - - - name: generate provenance subjects - id: hash - run: | - cd dist - HASHES=$(sha256sum * | base64 -w0) - echo "hashes=$HASHES" >> "$GITHUB_OUTPUT" - - - name: release - uses: softprops/action-gh-release@v0.1.15 - if: startsWith(github.ref, 'refs/tags/') - with: - files: | - dist/*.whl - - provenance: - needs: [build] - permissions: - actions: read - id-token: write - contents: write - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.6.0 - with: - base64-subjects: "${{ needs.build.outputs.hashes }}" - upload-assets: true diff --git a/surplus.py b/surplus.py index b1c67ac..5796b02 100644 --- a/surplus.py +++ b/surplus.py @@ -30,6 +30,7 @@ For more information, please refer to """ from argparse import ArgumentParser +from collections import OrderedDict from sys import stderr from typing import Any, Callable, Final, NamedTuple @@ -188,7 +189,7 @@ def surplus( if debug: stderr.write(f"debug: {location=}\n") - data: list[str] = [ + text: list[str] = [ ( ",".join( [ @@ -241,7 +242,11 @@ def surplus( location["address"].get("country"), ] - return True, "\n".join([d for d in data if ((d != None) and d != "")]) + unique = OrderedDict() + for line in text: + unique.update({line: None}) + + return True, "\n".join([d for d in unique.keys() if ((d != None) and d != "")]) def parse_query( From dc77a31aa13b6f495feda9553bbf804be4426e7b Mon Sep 17 00:00:00 2001 From: Mark Joshwel Date: Fri, 16 Jun 2023 05:39:03 +0000 Subject: [PATCH 03/10] ci: rename slsa3 publish --- .github/workflows/publish-slsa3.yml | 53 +++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/publish-slsa3.yml diff --git a/.github/workflows/publish-slsa3.yml b/.github/workflows/publish-slsa3.yml new file mode 100644 index 0000000..b7001ae --- /dev/null +++ b/.github/workflows/publish-slsa3.yml @@ -0,0 +1,53 @@ +name: release with slsa 3 compliance + +on: + push: + tags: + - '*' + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + hashes: ${{ steps.hash.outputs.hashes }} + + steps: + - name: checkout + uses: actions/checkout@v3 + + - name: install devbox + uses: jetpack-io/devbox-install-action@v0.3.0 + + - name: install dependencies + run: devbox run poetry install + + - name: install dependencies + id: build + run: devbox run poetry build + + - name: generate provenance subjects + id: hash + run: | + cd dist + HASHES=$(sha256sum * | base64 -w0) + echo "hashes=$HASHES" >> "$GITHUB_OUTPUT" + + - name: release + uses: softprops/action-gh-release@v0.1.15 + if: startsWith(github.ref, 'refs/tags/') + with: + files: | + dist/*.whl + + provenance: + needs: [build] + permissions: + actions: read + id-token: write + contents: write + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.6.0 + with: + base64-subjects: "${{ needs.build.outputs.hashes }}" + upload-assets: true From 77b796b030ce45ba921b7668baf8ab3d749d7dcf Mon Sep 17 00:00:00 2001 From: Mark Joshwel Date: Fri, 16 Jun 2023 05:55:00 +0000 Subject: [PATCH 04/10] ci: add quality check workflow --- .github/workflows/check-quality.yml | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/check-quality.yml diff --git a/.github/workflows/check-quality.yml b/.github/workflows/check-quality.yml new file mode 100644 index 0000000..9cd6215 --- /dev/null +++ b/.github/workflows/check-quality.yml @@ -0,0 +1,31 @@ +name: qc (black/mypy/isort checks) + +on: + workflow_dispatch: + push: + +jobs: + analyse: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + + - name: install devbox + uses: jetpack-io/devbox-install-action@v0.3.0 + + - name: install dependencies + run: devbox run poetry install + + - name: install dependencies + id: build + run: devbox run poetry build + + - name: analyse with mypy + run: devbox run poetry run mypy surplus.py + + - name: check for black formatting compliance + run: devbox run poetry run black --check surplus.py + + - name: analyse isort compliance + run: devbox run poetry run isort --check surplus.py From 2a95265e0dd801db6e448b12cb7dcfbfc7441aba Mon Sep 17 00:00:00 2001 From: Mark Joshwel Date: Fri, 16 Jun 2023 05:55:10 +0000 Subject: [PATCH 05/10] code: implement #2 --- surplus.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/surplus.py b/surplus.py index 5796b02..472d08b 100644 --- a/surplus.py +++ b/surplus.py @@ -290,6 +290,9 @@ def parse_query( locality = oquery.replace(pcode, "") locality = locality.strip().strip(",").strip() + if debug: + stderr.wtite(f"debug: {pcode=}, {locality=}") + return True, Localcode(code=pcode, locality=locality) return False, "unable to find a pluscode/match to a format" From 5c56ce3dd4da739d4b9e15245c98a8f4886944cb Mon Sep 17 00:00:00 2001 From: Mark Joshwel Date: Fri, 16 Jun 2023 05:58:16 +0000 Subject: [PATCH 06/10] code: mypy comply --- surplus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/surplus.py b/surplus.py index 472d08b..8c50459 100644 --- a/surplus.py +++ b/surplus.py @@ -242,7 +242,7 @@ def surplus( location["address"].get("country"), ] - unique = OrderedDict() + unique: OrderedDict = OrderedDict() for line in text: unique.update({line: None}) @@ -291,7 +291,7 @@ def parse_query( locality = locality.strip().strip(",").strip() if debug: - stderr.wtite(f"debug: {pcode=}, {locality=}") + stderr.write(f"debug: {pcode=}, {locality=}\n") return True, Localcode(code=pcode, locality=locality) From 22f3a9a18c3a5041cc353251a29a453e994b37f6 Mon Sep 17 00:00:00 2001 From: Mark Joshwel Date: Fri, 16 Jun 2023 06:04:52 +0000 Subject: [PATCH 07/10] ci(qc): fix attempt 1 --- .github/workflows/check-quality.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-quality.yml b/.github/workflows/check-quality.yml index 9cd6215..c7eef14 100644 --- a/.github/workflows/check-quality.yml +++ b/.github/workflows/check-quality.yml @@ -25,7 +25,7 @@ jobs: run: devbox run poetry run mypy surplus.py - name: check for black formatting compliance - run: devbox run poetry run black --check surplus.py + run: devbox run poetry run "black --check surplus.py" - name: analyse isort compliance - run: devbox run poetry run isort --check surplus.py + run: devbox run poetry run "isort --check surplus.py" From 82c8cdf5f00bff94fb14484ed001d3c5b75a6f14 Mon Sep 17 00:00:00 2001 From: Mark Joshwel Date: Fri, 16 Jun 2023 07:05:05 +0000 Subject: [PATCH 08/10] code/docs: sep handle_query from surplus, add docs --- README.md | 74 +++++++++++++++++++++++++--------- surplus.py | 114 +++++++++++++++++++++++++++++++++-------------------- 2 files changed, 127 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 6c9126f..84ac9b7 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Plus Code to iOS-Shortcuts-like shareable text - [API Reference](#api-reference) - [surplus.surplus()](#surplussurplus) - [surplus.parse_query()](#surplusparse_query) + - [surplus.handle_query()](#surplushandle_query) - [surplus.Localcode](#surpluslocalcode) - [surplus.Latlong](#surpluslatlong) - [Developing](#developing) @@ -44,16 +45,19 @@ pip install git+https://github.com/markjoshwel/surplus ### Command-line Interface ```text -usage: surplus [-h] [-d] query +usage: surplus [-h] [-d] query [query ...] Plus Code to iOS-Shortcuts-like shareable text positional arguments: - query full-length Plus Code (6PH58QMF+FX), local codes (8QMF+FX Singapore), or latlong (1.3336875, 103.7749375) + query full-length Plus Code (6PH58QMF+FX), + local code (8QMF+FX Singapore), or + latlong (1.3336875, 103.7749375) options: -h, --help show this help message and exit - -d, --debug prints lat, long and reverser response dict to stderr + -d, --debug prints lat, long and reverser response + dict to stderr ``` ### API Reference @@ -76,9 +80,12 @@ pluscode to shareable text conversion function - arguments - `query: str | surplus.Localcode | surplus.Latlong` - str - normal longcode (6PH58QMF+FX) - surplus.Localcode - shortcode with locality (8QMF+FX Singapore) - surplus.Latlong - latlong + - str + normal longcode (6PH58QMF+FX) + - [`surplus.Localcode`](#surpluslocalcode) + shortcode with locality (8QMF+FX Singapore) + - [`surplus.Latlong`](#surpluslatlong) + latlong - `reverser: typing.Callable = geopy.geocoders.Nominatim(user_agent="surplus").reverser` latlong to location function, accesses a dict from .raw attribute of return object @@ -97,22 +104,22 @@ pluscode to shareable text conversion function - returns `tuple[bool, str]` - `(True, )` - conversion was successful, str is resultant text + conversion succeeded, second element is the resultant string - `(False, )` - conversion failed, str is error message + conversion failed, second element is an error message string --- #### `surplus.parse_query()` -function that parses a string Plus Code, local code or latlong into a str, surplus.Localcode or surplus.Latlong respectively +function that parses a string Plus Code, local code or latlong into a str, [`surplus.Localcode`](#surpluslocalcode) or [`surplus.Latlong`](#surpluslatlong) respectively - signature: ```python def parse_query( query: str, debug: bool = False - ) -> tuple[bool, str | Localcode | Latlong]: + ) -> tuple[Literal[True], str | Localcode | Latlong] | tuple[Literal[False], str]: ``` - arguments: @@ -120,12 +127,43 @@ function that parses a string Plus Code, local code or latlong into a str, surpl - `query: str` string Plus Code, local code or latlong -- returns `tuple[bool, str | Localcode | Latlong]` +- returns `tuple[Literal[True], str | Localcode | Latlong] | tuple[Literal[False], str]` - - `(True, )` - conversion was successful, second element is result + - `(True, )` + conversion succeeded, second element is resultant Plus code string, [`surplus.Localcode`](#surpluslocalcode) or [`surplus.Latlong`](#surpluslatlong) - `(False, )` - conversion failed, str is error message + conversion failed, second element is an error message string + +--- + +#### `surplus.handle_query()` + +function that gets returns a [surplus.Latlong](#surpluslatlong) from a Plus Code string, [`surplus.Localcode`](#surpluslocalcode) or [`surplus.Latlong`](#surpluslatlong) object. +used after [`surplus.parse_query()`](#surplusparse_query). + +- signature: + + ```python + def handle_query( + query: str | Localcode | Latlong, debug: bool = False + ) -> tuple[Literal[True], Latlong] | tuple[Literal[False], str]: + ``` + +- arguments: + + - `query: str | Localcode | Latlong` + - str + normal longcode (6PH58QMF+FX) + - [`surplus.Localcode`](#surpluslocalcode) + shortcode with locality (8QMF+FX Singapore) + - [`surplus.Latlong`](#surpluslatlong) + latlong + +- returns `tuple[Literal[True], Latlong] | tuple[Literal[False], str]` + - `(True, )` + conversion succeeded, second element is a [`surplus.Latlong`](#surpluslatlong) + - `(False, )` + conversion failed, second element is an error message string --- @@ -136,9 +174,9 @@ function that parses a string Plus Code, local code or latlong into a str, surpl - parameters: - `code: str` - Plus Code - e.g.: "8QMF+FX" + Plus Code - e.g.: `"8QMF+FX"` - `locality: str` - e.g.: "Singapore" + e.g.: `"Singapore"` --- @@ -162,9 +200,9 @@ method that calculates full-length Plus Code using locality - returns: - `(True, )` - conversion was successful, str is resultant Plus Code + conversion succeeded, second element is the resultant Plus Code string - `(False, )` - conversion failed, str is error message + conversion failed, second element is an error message string --- diff --git a/surplus.py b/surplus.py index 8c50459..654b6a6 100644 --- a/surplus.py +++ b/surplus.py @@ -32,7 +32,7 @@ For more information, please refer to from argparse import ArgumentParser from collections import OrderedDict from sys import stderr -from typing import Any, Callable, Final, NamedTuple +from typing import Any, Callable, Final, Literal, NamedTuple from geopy import Location # type: ignore from geopy.geocoders import Nominatim # type: ignore @@ -131,49 +131,17 @@ def surplus( (True, ) - conversion was successful, str is resultant text (False, ) - conversion failed, str is error message """ - lat: float = 0.0 - lon: float = 0.0 + _latlong = handle_query(query=query, debug=debug) - if isinstance(query, Latlong): - lat, lon = query.lat, query.long + if _latlong[0] is False: + assert isinstance(_latlong[1], str) + return False, _latlong[1] - else: # instances: str | Localcode - str_pcode: str = "" - - if isinstance(query, Localcode): - result = query.full_length() - - if not result[0]: - return False, result[1] - - str_pcode = result[1] - - else: - str_pcode = query - - try: - pcode = PlusCode(str_pcode) - - except KeyError: - return ( - False, - "enter full-length Plus Code including area code, e.g.: 6PH58QMF+FX", - ) - - except Exception as pcderr: - return ( - False, - f"error while decoding Plus Code: {pcderr.__class__.__name__} - {pcderr}", - ) - - lat = pcode.area.center().lat - lon = pcode.area.center().lon - - if debug: - stderr.write(f"debug: {lat=}, {lon=}\n") + assert isinstance(_latlong[1], Latlong) + latlong = _latlong[1] try: - _reversed: Location | None = reverser(f"{lat}, {lon}") + _reversed: Location | None = reverser(f"{latlong.lat}, {latlong.long}") if _reversed is None: raise Exception(f"reverser function returned None") @@ -183,7 +151,7 @@ def surplus( except Exception as reverr: return ( False, - f"error while reversing latlong ({lat},{lon}): {reverr.__class__.__name__} - {reverr}", + f"error while reversing latlong ({Latlong}): {reverr.__class__.__name__} - {reverr}", ) if debug: @@ -251,7 +219,7 @@ def surplus( def parse_query( query: str, debug: bool = False -) -> tuple[bool, str | Localcode | Latlong]: +) -> tuple[Literal[True], str | Localcode | Latlong] | tuple[Literal[False], str]: """ function that parses a string Plus Code, local code or latlong into a str, surplus.Localcode or surplus.Latlong respectively @@ -268,7 +236,7 @@ def parse_query( def _word_match( oquery: str, squery: list[str] - ) -> tuple[bool, str | Localcode | Latlong]: + ) -> tuple[Literal[True], str | Localcode | Latlong] | tuple[Literal[False], str]: """ internal helper code reuse function @@ -332,6 +300,66 @@ def parse_query( return _word_match(oquery=query, squery=squery) +def handle_query( + query: str | Localcode | Latlong, debug: bool = False +) -> tuple[Literal[True], Latlong] | tuple[Literal[False], str]: + """ + function that gets returns a surplus.Latlong from a Plus Code string, + surplus.Localcode or surplus.Latlong object. + used after surplus.parse_query(). + + query: str | Localcode | Latlong + + debug: bool = False + + returns tuple[bool, str | Latlong] + (True, Latlong) - conversion was successful, second element is latlong + (False, ) - conversion failed, str is error message + """ + lat: float = 0.0 + lon: float = 0.0 + + if isinstance(query, Latlong): + return True, query + + else: # instances: str | Localcode + str_pcode: str = "" + + if isinstance(query, Localcode): + result = query.full_length() + + if not result[0]: + return False, result[1] + + str_pcode = result[1] + + else: + str_pcode = query + + try: + pcode = PlusCode(str_pcode) + + except KeyError: + return ( + False, + "code given is not a full-length Plus Code (including area code), e.g.: 6PH58QMF+FX", + ) + + except Exception as pcderr: + return ( + False, + f"error while decoding Plus Code: {pcderr.__class__.__name__} - {pcderr}", + ) + + lat = pcode.area.center().lat + lon = pcode.area.center().lon + + if debug: + stderr.write(f"debug: {lat=}, {lon=}\n") + + return True, Latlong(lat=lat, long=lon) + + def cli() -> None: parser = ArgumentParser( prog="surplus", From b13a9bfd14ff9ddffa559c7bec991f070ab1e536 Mon Sep 17 00:00:00 2001 From: Mark Joshwel Date: Fri, 16 Jun 2023 07:07:11 +0000 Subject: [PATCH 09/10] ci(publish/slsa3): apply to only monitor main branch --- .github/workflows/publish-slsa3.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish-slsa3.yml b/.github/workflows/publish-slsa3.yml index b7001ae..8169e88 100644 --- a/.github/workflows/publish-slsa3.yml +++ b/.github/workflows/publish-slsa3.yml @@ -2,6 +2,8 @@ name: release with slsa 3 compliance on: push: + branches: + main tags: - '*' From ec8366be917087ef81dcc901e6b208cfcacd1c1b Mon Sep 17 00:00:00 2001 From: Mark Joshwel Date: Fri, 16 Jun 2023 07:53:41 +0000 Subject: [PATCH 10/10] code/docs: fix #6, update docs example --- README.md | 5 +- surplus.py | 157 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 104 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 84ac9b7..611d0f0 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,9 @@ $ surplus 9R3J+R9 Singapore surplus version 1.1.1 Thomson Plaza 301 Upper Thomson Road, Bishan +Sin Ming, Bishan 574408 -Singapore +Central, Singapore ``` ```python @@ -29,7 +30,7 @@ Singapore >>> Localcode(code="8RPQ+JW", locality="Singapore").full_length() (True, '6PH58RPQ+JW') >>> surplus("6PH58RPQ+JW") -(True, 'Caldecott Stn Exit 4\nToa Payoh Link\n298106\nSingapore') +(True, 'Caldecott Stn Exit 4\nToa Payoh Link\n298106\nCentral, Singapore') ``` ## Installing diff --git a/surplus.py b/surplus.py index 654b6a6..39c05d9 100644 --- a/surplus.py +++ b/surplus.py @@ -131,6 +131,13 @@ def surplus( (True, ) - conversion was successful, str is resultant text (False, ) - conversion failed, str is error message """ + + def _unique(l: list[str]) -> list[str]: + unique: OrderedDict = OrderedDict() + for line in l: + unique.update({line: None}) + return list(unique.keys()) + _latlong = handle_query(query=query, debug=debug) if _latlong[0] is False: @@ -157,64 +164,102 @@ def surplus( if debug: stderr.write(f"debug: {location=}\n") - text: list[str] = [ - ( - ",".join( - [ - location["address"].get(detail, "") - for detail in ( - "emergency, historic, military, natural, landuse, place, railway," - "man_made, aerialway, boundary, amenity, aeroway, club, craft," - "leisure, office, mountain_pass, shop, tourism, bridge, tunnel, waterway" - ).split(", ") - ] - ) - ).strip(","), - # location["address"].get("leisure"), - # location["address"].get("shop"), - # location["address"].get("railway"), - ( - location["address"].get("building") - if ( + text: list[str] = _unique( + [ + ( + ", ".join( + [ + d + for d in _unique( + [ + location["address"].get(detail, None) + for detail in ( + "emergency, historic, military, natural, landuse, place, railway, " + "man_made, aerialway, boundary, amenity, aeroway, club, craft, " + "leisure, office, mountain_pass, shop, tourism, bridge, tunnel, waterway" + ).split(", ") + ] + ) + if d is not None + ] + ) + ).strip(", "), + ( location["address"].get("building") - != location["address"].get("house_number") - ) - else None - ), - location["address"].get("highway"), - ( - location["address"].get("house_number", "") - + (" " + location["address"].get("house_name", "")).strip() - + " " - + location["address"].get("road", "") - + ( - ", " + location["address"].get("suburb", "") - # dont repeat if suburb is mentioned in the road itself - # 'Toa Payoh' in 'Lorong 1A Toa Payoh' - if location["address"].get("suburb", "") - not in location["address"].get("road", "") - else "" - ) - ).strip(), - ( - ",".join( - [ - location["address"].get(detail, "") - for detail in ( - "residential, neighbourhood, allotments, quarter" - ).split(", ") - ] - ) - ).strip(","), - location["address"].get("postcode"), - location["address"].get("country"), - ] + if ( + location["address"].get("building") + != location["address"].get("house_number") + ) + else None + ), + location["address"].get("highway"), + ( + location["address"].get("house_number", "") + + (" " + location["address"].get("house_name", "")).strip() + + " " + + location["address"].get("road", "") + # + ( + # ", " + location["address"].get("suburb", "") + # # dont repeat if suburb is mentioned in the road itself + # # 'Toa Payoh' in 'Lorong 1A Toa Payoh' + # if location["address"].get("suburb", "") + # not in location["address"].get("road", "") + # else None + # ) + ).strip(), + ( + ", ".join( + [ + d + for d in _unique( + [ + location["address"].get(detail, "") + for detail in ( + "residential, neighbourhood, allotments, quarter, " + "city_district, district, borough, suburb, subdivision, " + "municipality, city, town, village" + ).split(", ") + ] + ) + if all( + [ + d != "", + d not in location["address"].get("road", ""), + d + not in [ + location["address"].get(detail, "") + for detail in ( + "region, state, state_district, county, " + "state, country, continent" + ).split(", ") + ], + ] + ) + ] + ) + ).strip(","), + location["address"].get("postcode"), + ( + ", ".join( + [ + d + for d in _unique( + [ + location["address"].get(detail, None) + for detail in ( + "region, state, state_district, county, " + "state, country, continent" + ).split(", ") + ] + ) + if d is not None + ] + ) + ), + ] + ) - unique: OrderedDict = OrderedDict() - for line in text: - unique.update({line: None}) - - return True, "\n".join([d for d in unique.keys() if ((d != None) and d != "")]) + return True, "\n".join([d for d in text if ((d != None) and d != "")]) def parse_query(