surplus v2.2.0 (#38)

* build(deps-dev): bump black from 23.7.0 to 23.9.1 (#33)

Bumps [black](https://github.com/psf/black) from 23.7.0 to 23.9.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.7.0...23.9.1)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* meta: update devbox files

* ctt: type analyse

* ci: update devbox-install-action (#35)

* import protocol from typing straight (#36)

* ctt: update uq and np tests

* s+,docs: add termux-location json support (#37)

docs: update api accordingly

* meta: bump ver

* s+,ctt: fix italian addressing + add test (#34)

* st: fix regression in last commit + add per-line settings dict

* ctt: update modena test

* docs: update api docs for the new st constants

* ci,devbox: fix for ci

* ctt: update modena test

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Mark Joshwel 2023-10-15 04:06:30 +08:00 committed by GitHub
parent 533cdb2ce4
commit fdf3a86d84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 511 additions and 265 deletions

View file

@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@v3
- name: install devbox
uses: jetpack-io/devbox-install-action@v0.3.0
uses: jetpack-io/devbox-install-action@v0.7.0
- name: install dependencies
run: devbox run poetry install
@ -28,10 +28,10 @@ jobs:
run: devbox run poetry run mypy .
- name: check for black formatting compliance
run: devbox run poetry run "black --check ."
run: devbox run poetry run black --check .
- name: analyse isort compliance
run: devbox run poetry run "isort --check *.py **/*.py"
run: devbox run poetry run isort --check *.py **/*.py
test:
runs-on: ubuntu-latest
@ -40,7 +40,7 @@ jobs:
uses: actions/checkout@v3
- name: install devbox
uses: jetpack-io/devbox-install-action@v0.3.0
uses: jetpack-io/devbox-install-action@v0.7.0
- name: install dependencies
run: devbox run poetry install

View file

@ -24,7 +24,7 @@ jobs:
uses: tj-actions/branch-names@v7
- name: install devbox
uses: jetpack-io/devbox-install-action@v0.6.1
uses: jetpack-io/devbox-install-action@v0.7.0
- name: install dependencies
run: devbox run poetry install

View file

@ -22,7 +22,7 @@ jobs:
uses: tj-actions/branch-names@v7
- name: install devbox
uses: jetpack-io/devbox-install-action@v0.6.1
uses: jetpack-io/devbox-install-action@v0.7.0
- name: install dependencies
run: devbox run poetry install

View file

@ -20,7 +20,7 @@ to iOS Shortcuts-like shareable text.
```text
$ surplus 9R3J+R9 Singapore
surplus version 2.1.1
surplus version 2.2.0
Thomson Plaza
301 Upper Thomson Road
Sin Ming, Bishan
@ -57,7 +57,7 @@ see [licence](#licence) for licensing information.
```text
usage: surplus [-h] [-d] [-v] [-c {pluscode,localcode,latlong,sharetext}]
[-u USER_AGENT]
[-u USER_AGENT] [-t]
[query ...]
Google Maps Plus Code to iOS Shortcuts-like shareable text
@ -79,6 +79,9 @@ options:
-u USER_AGENT, --user-agent USER_AGENT
user agent string to use for geocoding service,
defaults to fingerprinted user agent string
-t, --using-termux-location
treats input as a termux-location output json
string, and parses it accordingly
```
### example api usage
@ -260,7 +263,7 @@ of incorrect outputs.
```text
$ s+ --debug 8QJF+RP Singapore
surplus version 2.1.1, debug mode (latest@future, Tue 05 Sep 2023 23:38:59 +0800)
surplus version 2.2.0, debug mode (latest@future, Tue 05 Sep 2023 23:38:59 +0800)
debug: parse_query: behaviour.query=['8QJF+RP', 'Singapore']
debug: _match_plus_code: portion_plus_code='8QJF+RP', portion_locality='Singapore'
debug: cli: query=Result(value=LocalCodeQuery(code='8QJF+RP', locality='Singapore'), error=None)
@ -602,24 +605,62 @@ line breakdown of shareable text output, accompanied by their Nominatim keys:
> this constant only affects the default surplus Nominatim geocoding functions. custom
> functions do not read from this, unless deliberately programmed to do so
- `SHAREABLE_TEXT_LINE_0_KEYS: typing.Final[tuple[str, ...]]`
`SHAREABLE_TEXT_LINE_1_KEYS: typing.Final[tuple[str, ...]]`
`SHAREABLE_TEXT_LINE_2_KEYS: typing.Final[tuple[str, ...]]`
`SHAREABLE_TEXT_LINE_3_KEYS: typing.Final[tuple[str, ...]]`
`SHAREABLE_TEXT_LINE_4_KEYS: typing.Final[tuple[str, ...]]`
`SHAREABLE_TEXT_LINE_5_KEYS: typing.Final[tuple[str, ...]]`
`SHAREABLE_TEXT_LINE_6_KEYS: typing.Final[tuple[str, ...]]`
- `SHAREABLE_TEXT_LINE_0_KEYS: dict[str, tuple[str, ...]]`
`SHAREABLE_TEXT_LINE_1_KEYS: dict[str, tuple[str, ...]]`
`SHAREABLE_TEXT_LINE_2_KEYS: dict[str, tuple[str, ...]]`
`SHAREABLE_TEXT_LINE_3_KEYS: dict[str, tuple[str, ...]]`
`SHAREABLE_TEXT_LINE_4_KEYS: dict[str, tuple[str, ...]]`
`SHAREABLE_TEXT_LINE_5_KEYS: dict[str, tuple[str, ...]]`
`SHAREABLE_TEXT_LINE_6_KEYS: dict[str, tuple[str, ...]]`
a tuple of strings containing Nominatim keys used in shareable text line 0-6
a dictionary of iso3166-2 country-portion string keys with a tuple of Nominatim keys
used in shareable text line 0-6 as their values
- `SHAREABLE_TEXT_NAMES: typing.Final[tuple[str, ...]]`
```python
{
"default": (...),
"SG": (...,),
...
}
```
- `SHAREABLE_TEXT_LINE_SETTINGS: dict[str, dict[int, tuple[str, bool]]]`
a dictionary of iso3166-2 country-portion string keys with a dictionary as their values
the dictionary values are dictionaries with integers as keys, and a tuple of two strings
the first string is the separator string to use, and the second string is a boolean flag
that if `True` will check the line for seen names
```python
{
"default": {
0: (", ", False),
...
6: (", ", False),
},
"IT": {
0: (", ", False),
...
6: (", ", False),
},
...
}
```
- `SHAREABLE_TEXT_NAMES: dict[str, tuple[str, ...]]`
a dictionary of iso3166-2 country-portion string keys with a tuple of strings as their
values
a tuple of strings containing Nominatim keys used in shareable text line 0-2 and
special keys in line 3
used for seen name checks
- `SHAREABLE_TEXT_LOCALITY: dict[str, tuple[str, ...]]`
a dictionary of iso3166-2 country-portion strings with a tuples of strings as their
a dictionary of iso3166-2 country-portion string keys with a tuple of strings as their
values
used when generating the locality portions of shortened Plus Codes/local codes
@ -632,6 +673,9 @@ line breakdown of shareable text output, accompanied by their Nominatim keys:
}
```
- `SHAREABLE_TEXT_DEFAULT: typing.Final[str]`
constant for what is the "default" key in the `SHAREABLE*` constants
- `EMPTY_LATLONG: typing.Final[Latlong]`
a constant for an empty latlong coordinate, with latitude and longitude set to 0.0
@ -793,6 +837,9 @@ attributes
- `convert_to_type: ConversionResultTypeEnum = ConversionResultTypeEnum.SHAREABLE_TEXT`
what type to convert the query to
- `using_termux_location: bool = False`
treats query as a termux-location output json string, and parses it accordingly
### `class SurplusDefaultGeocoding`
> [!IMPORTANT]
@ -1288,7 +1335,7 @@ it contains the following, in order, alongside an example:
1. `version` - the surplus version alongside a suffix, if any
```text
2.1.1-local
2.2.0-local
```
2. `system_info` - generic machine and operating system information
@ -1312,7 +1359,7 @@ it contains the following, in order, alongside an example:
after hashing, this string becomes a 12 character hexadecimal string, as shown below:
```text
surplus/2.1.1-local (1fdbfa0b0cfb)
surplus/2.2.0-local (1fdbfa0b0cfb)
^^^^^^^^^^^^
this is the hashed result of unique_info
```

View file

@ -1,14 +1,11 @@
{
"packages": [
"python311",
"python311Packages.ipykernel",
"poetry"
"python311@latest",
"python311Packages.ipykernel@latest",
"poetry@latest"
],
"shell": {
"init_hook": [
"poetry env use $(which python)",
"poetry shell"
]
"init_hook": ["poetry install"]
},
"nixpkgs": {
"commit": "f80ac848e3d6f0c12c52758c0f25c10c97ca3b62"

View file

@ -1,15 +1,67 @@
{
"lockfile_version": "1",
"packages": {
"poetry": {
"resolved": "github:NixOS/nixpkgs/f80ac848e3d6f0c12c52758c0f25c10c97ca3b62#poetry"
"poetry@latest": {
"last_modified": "2023-10-04T02:19:08Z",
"plugin_version": "0.0.3",
"resolved": "github:NixOS/nixpkgs/d1c9180c6d1f8fce9469436f48c1cb8180d7087d#poetry",
"source": "devbox-search",
"version": "1.6.1",
"systems": {
"aarch64-darwin": {
"store_path": "/nix/store/i7q6kxa3ac7c57zalr4vwa04c1bll3xd-python3.10-poetry-1.6.1"
},
"python311": {
"aarch64-linux": {
"store_path": "/nix/store/b0gmqd0y5pich25mk5p649g1bbbyxp0v-python3.10-poetry-1.6.1"
},
"x86_64-darwin": {
"store_path": "/nix/store/q5zqqp3k58z46q4imgmj5pxbinmzjyqf-python3.10-poetry-1.6.1"
},
"x86_64-linux": {
"store_path": "/nix/store/6v2zvmag2il359sbd8j1q7b3c20gs6yd-python3.10-poetry-1.6.1"
}
}
},
"python311@latest": {
"last_modified": "2023-09-27T18:02:17Z",
"plugin_version": "0.0.1",
"resolved": "github:NixOS/nixpkgs/f80ac848e3d6f0c12c52758c0f25c10c97ca3b62#python311"
"resolved": "github:NixOS/nixpkgs/517501bcf14ae6ec47efd6a17dda0ca8e6d866f9#python311",
"source": "devbox-search",
"version": "3.11.4",
"systems": {
"aarch64-darwin": {
"store_path": "/nix/store/rycxjkclx801wrhwrgllak0302xzjdvx-python3-3.11.4"
},
"python311Packages.ipykernel": {
"resolved": "github:NixOS/nixpkgs/f80ac848e3d6f0c12c52758c0f25c10c97ca3b62#python311Packages.ipykernel"
"aarch64-linux": {
"store_path": "/nix/store/h4xi1djsmhk7bjdipz58xkfnf8lc9mpm-python3-3.11.4"
},
"x86_64-darwin": {
"store_path": "/nix/store/yqs2zxkxn61xzimdaz1pbgawk2lnm0d8-python3-3.11.4"
},
"x86_64-linux": {
"store_path": "/nix/store/3k7is7nc2xbav8a48vx7arad522d8czx-python3-3.11.4"
}
}
},
"python311Packages.ipykernel@latest": {
"last_modified": "2023-09-29T09:08:59Z",
"resolved": "github:NixOS/nixpkgs/bd9b686c0168041aea600222be0805a0de6e6ab8#python311Packages.ipykernel",
"source": "devbox-search",
"version": "6.21.2",
"systems": {
"aarch64-darwin": {
"store_path": "/nix/store/78gi0qk9lmrb4rk5ignww29p5j0l79gs-python3.11-ipykernel-6.21.2"
},
"aarch64-linux": {
"store_path": "/nix/store/74m55prblwmsyx1ylv135iqw2wqj4c6a-python3.11-ipykernel-6.21.2"
},
"x86_64-darwin": {
"store_path": "/nix/store/j0wpnwkdcqfp2bmp2b9pgzdngnmxzjj9-python3.11-ipykernel-6.21.2"
},
"x86_64-linux": {
"store_path": "/nix/store/3kqcf56q2q46hj0g5n4rxzzy1c9vdiqa-python3.11-ipykernel-6.21.2"
}
}
}
}
}

83
poetry.lock generated
View file

@ -1,10 +1,9 @@
# This file is automatically @generated by Poetry and should not be changed by hand.
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]]
name = "appnope"
version = "0.1.3"
description = "Disable App Nap on macOS >= 10.9"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -16,7 +15,6 @@ files = [
name = "asttokens"
version = "2.4.0"
description = "Annotate AST trees with source code positions"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -34,7 +32,6 @@ test = ["astroid", "pytest"]
name = "backcall"
version = "0.2.0"
description = "Specifications for callback functions passed in to an API"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -44,34 +41,33 @@ files = [
[[package]]
name = "black"
version = "23.7.0"
version = "23.9.1"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
{file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"},
{file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"},
{file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"},
{file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"},
{file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"},
{file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"},
{file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"},
{file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"},
{file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"},
{file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"},
{file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"},
{file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"},
{file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"},
{file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"},
{file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"},
{file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"},
{file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"},
{file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"},
{file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"},
{file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"},
{file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"},
{file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"},
{file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"},
{file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"},
{file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"},
{file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"},
{file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"},
{file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"},
{file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"},
{file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"},
{file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"},
{file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"},
{file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"},
{file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"},
{file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"},
{file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"},
{file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"},
{file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"},
{file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"},
{file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"},
{file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"},
{file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"},
{file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"},
{file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"},
]
[package.dependencies]
@ -93,7 +89,6 @@ uvloop = ["uvloop (>=0.15.2)"]
name = "click"
version = "8.1.7"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -108,7 +103,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
@ -120,7 +114,6 @@ files = [
name = "decorator"
version = "5.1.1"
description = "Decorators for Humans"
category = "dev"
optional = false
python-versions = ">=3.5"
files = [
@ -132,7 +125,6 @@ files = [
name = "executing"
version = "1.2.0"
description = "Get the currently executing AST node of a frame, and other information"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -147,7 +139,6 @@ tests = ["asttokens", "littleutils", "pytest", "rich"]
name = "geographiclib"
version = "2.0"
description = "The geodesic routines from GeographicLib"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@ -159,7 +150,6 @@ files = [
name = "geopy"
version = "2.4.0"
description = "Python Geocoding Toolbox"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@ -183,7 +173,6 @@ timezone = ["pytz"]
name = "ipython"
version = "8.15.0"
description = "IPython: Productive Interactive Computing"
category = "dev"
optional = false
python-versions = ">=3.9"
files = [
@ -222,7 +211,6 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pa
name = "isort"
version = "5.12.0"
description = "A Python utility / library to sort Python imports."
category = "dev"
optional = false
python-versions = ">=3.8.0"
files = [
@ -240,7 +228,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"]
name = "jedi"
version = "0.19.0"
description = "An autocompletion tool for Python that can be used for text editors."
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@ -260,7 +247,6 @@ testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
name = "matplotlib-inline"
version = "0.1.6"
description = "Inline Matplotlib backend for Jupyter"
category = "dev"
optional = false
python-versions = ">=3.5"
files = [
@ -275,7 +261,6 @@ traitlets = "*"
name = "mypy"
version = "1.5.1"
description = "Optional static typing for Python"
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
@ -321,7 +306,6 @@ reports = ["lxml"]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
category = "dev"
optional = false
python-versions = ">=3.5"
files = [
@ -333,7 +317,6 @@ files = [
name = "packaging"
version = "23.1"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -345,7 +328,6 @@ files = [
name = "parso"
version = "0.8.3"
description = "A Python Parser"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
@ -361,7 +343,6 @@ testing = ["docopt", "pytest (<6.0.0)"]
name = "pathspec"
version = "0.11.2"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -373,7 +354,6 @@ files = [
name = "pexpect"
version = "4.8.0"
description = "Pexpect allows easy control of interactive console applications."
category = "dev"
optional = false
python-versions = "*"
files = [
@ -388,7 +368,6 @@ ptyprocess = ">=0.5"
name = "pickleshare"
version = "0.7.5"
description = "Tiny 'shelve'-like database with concurrency support"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -400,7 +379,6 @@ files = [
name = "platformdirs"
version = "3.10.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -416,7 +394,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co
name = "pluscodes"
version = "2022.1.3"
description = "Compute Plus Codes (Open Location Codes)."
category = "main"
optional = false
python-versions = ">=3.10"
files = [
@ -430,7 +407,6 @@ dev = ["black (==22.3.0)", "build (==0.8.0)", "coverage (==6.4)", "isort (==5.8.
name = "prompt-toolkit"
version = "3.0.39"
description = "Library for building powerful interactive command lines in Python"
category = "dev"
optional = false
python-versions = ">=3.7.0"
files = [
@ -445,7 +421,6 @@ wcwidth = "*"
name = "ptyprocess"
version = "0.7.0"
description = "Run a subprocess in a pseudo terminal"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -457,7 +432,6 @@ files = [
name = "pure-eval"
version = "0.2.2"
description = "Safely evaluate AST nodes without side effects"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -472,7 +446,6 @@ tests = ["pytest"]
name = "pygments"
version = "2.16.1"
description = "Pygments is a syntax highlighting package written in Python."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -487,7 +460,6 @@ plugins = ["importlib-metadata"]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
@ -499,7 +471,6 @@ files = [
name = "stack-data"
version = "0.6.2"
description = "Extract data from python stack frames and tracebacks for informative displays"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -519,7 +490,6 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
name = "tokenize-rt"
version = "5.2.0"
description = "A wrapper around the stdlib `tokenize` which roundtrips."
category = "dev"
optional = false
python-versions = ">=3.8"
files = [
@ -531,7 +501,6 @@ files = [
name = "traitlets"
version = "5.9.0"
description = "Traitlets Python configuration system"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -547,7 +516,6 @@ test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"]
name = "typing-extensions"
version = "4.7.1"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
@ -559,7 +527,6 @@ files = [
name = "wcwidth"
version = "0.2.6"
description = "Measures the displayed width of unicode strings in a terminal"
category = "dev"
optional = false
python-versions = "*"
files = [
@ -570,4 +537,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "f9270d7fb45c708e923f4afe041b26f77becb8e4adfcd517742e67f6b8f9d6b9"
content-hash = "3f2847d76a104121389f12d4f82841153b114d479c8ddba0c5a78994b544d0d6"

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "surplus"
version = "2.1.1"
version = "2.2.0"
description = "Python script to convert Google Maps Plus Codes to iOS Shortcuts-like shareable text."
authors = ["Mark Joshwel <mark@joshwel.co>"]
license = "Unlicense"
@ -17,7 +17,7 @@ pluscodes = "^2022.1.3"
geopy = "^2.3.0"
[tool.poetry.group.dev.dependencies]
black = {extras = ["jupyter"], version = "^23.7.0"}
black = {extras = ["jupyter"], version = "^23.9.1"}
mypy = "^1.5.1"
isort = "^5.12.0"

View file

@ -31,11 +31,14 @@ For more information, please refer to <http://unlicense.org/>
from argparse import ArgumentParser
from collections import OrderedDict
from copy import deepcopy
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from enum import Enum
from functools import lru_cache
from hashlib import shake_256
from json import loads as json_loads
from json.decoder import JSONDecodeError
from platform import platform
from socket import gethostname
from sys import stderr, stdin, stdout
@ -45,6 +48,7 @@ from typing import (
Final,
Generic,
NamedTuple,
Protocol,
Sequence,
TextIO,
TypeAlias,
@ -55,12 +59,9 @@ from uuid import getnode
from geopy import Location as _geopy_Location # type: ignore
from geopy.extra.rate_limiter import RateLimiter as _geopy_RateLimiter # type: ignore
from geopy.geocoders import Nominatim as _geopy_Nominatim # type: ignore
from pluscodes import Area as _PlusCode_Area # type: ignore
from pluscodes import PlusCode as _PlusCode # type: ignore
from pluscodes import decode as _PlusCode_decode # type: ignore
from pluscodes import encode as _PlusCode_encode # type: ignore
from pluscodes.validator import Validator as _PlusCode_Validator # type: ignore
from typing_extensions import Protocol
from pluscodes.openlocationcode import ( # type: ignore # isort: skip
recoverNearest as _PlusCode_recoverNearest,
@ -68,14 +69,19 @@ from pluscodes.openlocationcode import ( # type: ignore # isort: skip
# constants
VERSION: Final[tuple[int, int, int]] = (2, 1, 1)
VERSION: Final[tuple[int, int, int]] = (2, 2, 0)
VERSION_SUFFIX: Final[str] = "-local"
BUILD_BRANCH: Final[str] = "future"
BUILD_COMMIT: Final[str] = "latest"
BUILD_DATETIME: Final[datetime] = datetime.now(timezone(timedelta(hours=8))) # using SGT
CONNECTION_MAX_RETRIES: int = 9
CONNECTION_WAIT_SECONDS: int = 10
SHAREABLE_TEXT_LINE_0_KEYS: Final[tuple[str, ...]] = (
LOCALITY_GEOCODER_LEVEL: int = 13 # adjusts geocoder zoom level when
# geocoding latlong into an address
# default shareable text line keys
SHAREABLE_TEXT_LINE_0_KEYS: dict[str, tuple[str, ...]] = {
"default": (
"emergency",
"historic",
"military",
@ -98,23 +104,23 @@ SHAREABLE_TEXT_LINE_0_KEYS: Final[tuple[str, ...]] = (
"bridge",
"tunnel",
"waterway",
)
SHAREABLE_TEXT_LINE_1_KEYS: Final[tuple[str, ...]] = ("building",)
SHAREABLE_TEXT_LINE_2_KEYS: Final[tuple[str, ...]] = ("highway",)
SHAREABLE_TEXT_LINE_3_KEYS: Final[tuple[str, ...]] = (
),
}
SHAREABLE_TEXT_LINE_1_KEYS: dict[str, tuple[str, ...]] = {
"default": ("building",),
}
SHAREABLE_TEXT_LINE_2_KEYS: dict[str, tuple[str, ...]] = {
"default": ("highway",),
}
SHAREABLE_TEXT_LINE_3_KEYS: dict[str, tuple[str, ...]] = {
"default": (
"house_number",
"house_name",
"road",
)
# special line 3 keys for Italian addresses (IT)
SHAREABLE_TEXT_LINE_3_KEYS_IT: Final[tuple[str, ...]] = (
"road",
"house_number",
"house_name",
)
SHAREABLE_TEXT_LINE_4_KEYS: Final[tuple[str, ...]] = (
),
}
SHAREABLE_TEXT_LINE_4_KEYS: dict[str, tuple[str, ...]] = {
"default": (
"residential",
"neighbourhood",
"allotments",
@ -128,29 +134,112 @@ SHAREABLE_TEXT_LINE_4_KEYS: Final[tuple[str, ...]] = (
"city",
"town",
"village",
)
SHAREABLE_TEXT_LINE_5_KEYS: Final[tuple[str, ...]] = ("postcode",)
SHAREABLE_TEXT_LINE_6_KEYS: Final[tuple[str, ...]] = (
),
}
SHAREABLE_TEXT_LINE_5_KEYS: dict[str, tuple[str, ...]] = {
"default": ("postcode",),
}
SHAREABLE_TEXT_LINE_6_KEYS: dict[str, tuple[str, ...]] = {
"default": (
"region",
"county",
"state",
"state_district",
"country",
"continent",
)
SHAREABLE_TEXT_NAMES: Final[tuple[str, ...]] = (
SHAREABLE_TEXT_LINE_0_KEYS
+ SHAREABLE_TEXT_LINE_1_KEYS
+ SHAREABLE_TEXT_LINE_2_KEYS
+ ("house_name", "road")
)
SHAREABLE_TEXT_LOCALITY: dict[str, tuple[str, ...]] = {
"default": ("city_district", "district", "city", *SHAREABLE_TEXT_LINE_6_KEYS),
"SG": ("country",),
),
}
SHAREABLE_TEXT_LINE_SETTINGS: dict[str, dict[int, tuple[str, bool]]] = {
# line number: (string separator to use, whether to check for seen names)
"default": {
0: (", ", False),
1: (", ", False),
2: (", ", False),
3: (" ", False),
4: (", ", True),
5: (", ", False),
6: (", ", False),
},
}
SHAREABLE_TEXT_NAMES: dict[str, tuple[str, ...]] = {
"default": (
SHAREABLE_TEXT_LINE_0_KEYS["default"]
+ SHAREABLE_TEXT_LINE_1_KEYS["default"]
+ SHAREABLE_TEXT_LINE_2_KEYS["default"]
+ ("house_name", "road")
),
}
SHAREABLE_TEXT_LOCALITY: dict[str, tuple[str, ...]] = {
"default": (
"city_district",
"district",
"city",
*SHAREABLE_TEXT_LINE_6_KEYS["default"],
),
}
SHAREABLE_TEXT_DEFAULT: Final[str] = "default"
# special per-country key arrangements for SG/Singapore
SHAREABLE_TEXT_LOCALITY.update(
{
"SG": ("country",),
}
)
# special per-country key arrangements for IT/Italy
SHAREABLE_TEXT_LINE_3_KEYS.update(
{
"IT": (
"road",
"house_number",
"house_name",
),
}
)
SHAREABLE_TEXT_LINE_5_KEYS.update(
{
"IT": (
"postcode",
"region",
"county",
"state",
"state_district",
),
},
)
SHAREABLE_TEXT_LINE_6_KEYS.update(
{
"IT": (
"country",
"continent",
),
}
)
SHAREABLE_TEXT_LINE_SETTINGS.update(
{"IT": deepcopy(SHAREABLE_TEXT_LINE_SETTINGS)["default"]}
)
SHAREABLE_TEXT_LINE_SETTINGS["IT"][5] = (" ", False)
# special per-country key arrangements for MY/Malaysia
SHAREABLE_TEXT_LINE_4_KEYS.update(
{
"MY": tuple(),
},
)
SHAREABLE_TEXT_LINE_5_KEYS.update(
{
"MY": (
"postcode",
*SHAREABLE_TEXT_LINE_4_KEYS["default"],
),
},
)
SHAREABLE_TEXT_LINE_SETTINGS.update(
{"MY": deepcopy(SHAREABLE_TEXT_LINE_SETTINGS)["default"]}
)
SHAREABLE_TEXT_LINE_SETTINGS["MY"][4] = (" ", False)
SHAREABLE_TEXT_LINE_SETTINGS["MY"][5] = (" ", True)
# adjusts geocoder zoom level when geocoding latlong into an address
LOCALITY_GEOCODER_LEVEL: int = 13
# exceptions
@ -819,6 +908,8 @@ class Behaviour(NamedTuple):
whether to print version information and exit
convert_to_type: ConversionResultTypeEnum = ConversionResultTypeEnum.SHAREABLE_TEXT
what type to convert query to
using_termux_location: bool = False
treats query as a termux-location output json string, and parses it accordingly
"""
query: str | list[str] = ""
@ -829,6 +920,7 @@ class Behaviour(NamedTuple):
debug: bool = False
version_header: bool = False
convert_to_type: ConversionResultTypeEnum = ConversionResultTypeEnum.SHAREABLE_TEXT
using_termux_location: bool = False
# functions
@ -967,7 +1059,46 @@ def parse_query(behaviour: Behaviour) -> Result[Query]:
file=behaviour.stderr,
)
# not a plus/local code, try to match for latlong or string query
# check if termux-location json
if behaviour.using_termux_location:
try:
termux_location_json = json_loads(original_query)
if not isinstance(termux_location_json, dict):
raise ValueError("parsed termux-location json is not a dict")
return Result[Query](
LatlongQuery(
Latlong(
latitude=termux_location_json["latitude"],
longitude=termux_location_json["longitude"],
)
)
)
except (JSONDecodeError, TypeError) as exc:
return Result[Query](
LatlongQuery(EMPTY_LATLONG),
error=ValueError("could not parse termux-location json"),
)
except KeyError as exc:
return Result[Query](
LatlongQuery(EMPTY_LATLONG),
error=ValueError(
"could not get 'latitude' or 'longitude' keys from termux-location json"
),
)
except Exception as exc:
return Result[Query](
LatlongQuery(EMPTY_LATLONG),
error=exc,
)
# unreachable
# not a plus/local code/termux-location json,
# try to match for latlong or string query
match split_query:
case [single]:
# possibly a:
@ -1081,11 +1212,19 @@ def handle_args() -> Behaviour:
help=f"user agent string to use for geocoding service, defaults to fingerprinted user agent string",
default=default_fingerprint,
)
parser.add_argument(
"-t",
"--using-termux-location",
action="store_true",
default=False,
help="treats input as a termux-location output json string, and parses it accordingly",
)
# initialisation
args = parser.parse_args()
query: str | list[str] = ""
# "-" stdin check
if args.query == ["-"]:
stdin_query: list[str] = []
@ -1097,8 +1236,8 @@ def handle_args() -> Behaviour:
else:
query = args.query
# setup structures and return
geocoding = SurplusDefaultGeocoding(args.user_agent)
behaviour = Behaviour(
query=query,
geocoder=geocoding.geocoder,
@ -1108,8 +1247,8 @@ def handle_args() -> Behaviour:
debug=args.debug,
version_header=args.version,
convert_to_type=ConversionResultTypeEnum(args.convert_to),
using_termux_location=args.using_termux_location,
)
return behaviour
@ -1147,7 +1286,7 @@ def _generate_text(
def _generate_text_line(
line_number: int,
line_keys: Sequence[str],
seperator: str = ", ",
separator: str = ", ",
filter: Callable[[str], list[bool]] = lambda e: [True],
) -> str:
"""
@ -1158,8 +1297,8 @@ def _generate_text(
line number to prefix with
line_keys: Sequence[str]
list of keys to .get() from location dict
seperator: str = ", "
seperator to join elements with
separator: str = ", "
separator to join elements with
filter: Callable[[str], list[bool]] = lambda e: True
function that takes in a string and returns a list of bools, used to
filter elements from line_keys. list will be passed to all(). if all
@ -1199,9 +1338,37 @@ def _generate_text(
)
continue
line = line_prefix + seperator.join(basket)
line = line_prefix + separator.join(basket)
return (line + "\n") if (line != "") else ""
C = TypeVar("C")
def stget(split_iso3166_2: list[str], line_keys: dict[str, C]) -> tuple[bool, C]:
"""
(internal function)
arguments:
split_iso3166_2: list[str]
the dash-split iso 3166-2 country code
line_keys:
the shareable text line keys dict to use
returns tuple[bool, C]
bool: whether the a special key arrangement was used
C: dict content
"""
DEFAULT = "default"
country: str = DEFAULT
if len(iso3166_2) >= 1:
country = split_iso3166_2[0]
if country not in line_keys:
return False, line_keys[DEFAULT]
else:
return True, line_keys[country]
# iso3166-2 handling: this allows surplus to have special key arrangements for a
# specific iso3166-2 code for edge cases
# (https://en.wikipedia.org/wiki/ISO_3166-2)
@ -1220,49 +1387,46 @@ def _generate_text(
file=behaviour.stderr,
)
# skeleton code to allow for changing keys based on iso3166-2 code
st_line0_keys = SHAREABLE_TEXT_LINE_0_KEYS
st_line1_keys = SHAREABLE_TEXT_LINE_1_KEYS
st_line2_keys = SHAREABLE_TEXT_LINE_2_KEYS
st_line3_keys = SHAREABLE_TEXT_LINE_3_KEYS
st_line4_keys = SHAREABLE_TEXT_LINE_4_KEYS
st_line5_keys = SHAREABLE_TEXT_LINE_5_KEYS
st_line6_keys = SHAREABLE_TEXT_LINE_6_KEYS
st_names = SHAREABLE_TEXT_NAMES
st_locality: tuple[str, ...] = ()
n_used_special: int = 0 # number of special key arrangements used
# special key arrangements for edge cases in local/regional address formats
match split_iso3166_2:
case ["SG", *_]: # Singapore
if debug:
# skeleton code to allow for changing keys based on iso3166-2 code
used_special, st_line0_keys = stget(split_iso3166_2, SHAREABLE_TEXT_LINE_0_KEYS)
n_used_special += used_special
used_special, st_line1_keys = stget(split_iso3166_2, SHAREABLE_TEXT_LINE_1_KEYS)
n_used_special += used_special
used_special, st_line2_keys = stget(split_iso3166_2, SHAREABLE_TEXT_LINE_2_KEYS)
n_used_special += used_special
used_special, st_line3_keys = stget(split_iso3166_2, SHAREABLE_TEXT_LINE_3_KEYS)
n_used_special += used_special
used_special, st_line4_keys = stget(split_iso3166_2, SHAREABLE_TEXT_LINE_4_KEYS)
n_used_special += used_special
used_special, st_line5_keys = stget(split_iso3166_2, SHAREABLE_TEXT_LINE_5_KEYS)
n_used_special += used_special
used_special, st_line6_keys = stget(split_iso3166_2, SHAREABLE_TEXT_LINE_6_KEYS)
n_used_special += used_special
used_special, st_names = stget(split_iso3166_2, SHAREABLE_TEXT_NAMES)
n_used_special += used_special
used_special, st_locality = stget(split_iso3166_2, SHAREABLE_TEXT_LOCALITY)
n_used_special += used_special
used_special, st_line_settings = stget(split_iso3166_2, SHAREABLE_TEXT_LINE_SETTINGS)
n_used_special += used_special
if n_used_special and debug:
print(
"debug: _generate_text: "
f"using special key arrangements for '{iso3166_2}' (Singapore)",
file=behaviour.stderr,
)
st_locality = SHAREABLE_TEXT_LOCALITY[split_iso3166_2[0]]
case ["IT", *_]: # Italy
if debug:
print(
"debug: _generate_text: "
f"using special key arrangements for '{iso3166_2}' (Italy)",
file=behaviour.stderr,
)
st_line3_keys = SHAREABLE_TEXT_LINE_3_KEYS_IT
case _: # default
if debug:
print(
"debug: _generate_text: "
f"using default key arrangements for '{iso3166_2}'",
file=behaviour.stderr,
)
st_locality = SHAREABLE_TEXT_LOCALITY["default"]
# start generating text
match mode:
case TextGenerationEnum.SHAREABLE_TEXT:
@ -1283,52 +1447,37 @@ def _generate_text(
str(location.get(detail, "")) for detail in st_line6_keys
]
text.append(
_generate_text_line(
line_number=0,
line_keys=st_line0_keys,
)
)
text.append(
_generate_text_line(
line_number=1,
line_keys=st_line1_keys,
)
)
text.append(
_generate_text_line(
line_number=2,
line_keys=st_line2_keys,
)
)
text.append(
_generate_text_line(
line_number=3,
line_keys=st_line3_keys,
seperator=" ",
)
)
text.append(
_generate_text_line(
line_number=4,
line_keys=st_line4_keys,
filter=lambda ak: [
# everything here should be True if the element is to be kept
for (
line_number,
line_keys,
) in enumerate(
[
st_line0_keys,
st_line1_keys,
st_line2_keys,
st_line3_keys,
st_line4_keys,
st_line5_keys,
st_line6_keys,
]
):
line_separator, line_filter = st_line_settings[line_number]
# filter: everything here should be True if the element is to be kept
if line_filter is False:
_filter = lambda e: [True]
else:
_filter = lambda ak: [
ak not in general_global_info,
not any(True if (ak in sn) else False for sn in seen_names),
],
)
)
]
text.append(
_generate_text_line(
line_number=5,
line_keys=st_line5_keys,
)
)
text.append(
_generate_text_line(
line_number=6,
line_keys=st_line6_keys,
line_number=line_number,
line_keys=line_keys,
separator=line_separator,
filter=_filter,
)
)
@ -1602,9 +1751,9 @@ def surplus(query: Query | str, behaviour: Behaviour) -> Result[str]:
def cli() -> int:
"""command-line entry point, returns an exit code int"""
# handle arguments and print version header
behaviour = handle_args()
# handle arguments and print version header
print(
f"surplus version {'.'.join([str(v) for v in VERSION])}{VERSION_SUFFIX}"
+ (f", debug mode" if behaviour.debug else "")

42
test.py
View file

@ -1,5 +1,3 @@
# type: ignore
"""
surplus test runner
-------------------
@ -58,7 +56,7 @@ class TestFailure(NamedTuple):
tests: list[ContinuityTest] = [
ContinuityTest(
query="8R3M+F8 Singapore",
expected=("Wisma Atria\n" "435 Orchard Road\n" "238877\n" "Central, Singapore"),
expected=[("Wisma Atria\n" "435 Orchard Road\n" "238877\n" "Central, Singapore")],
),
ContinuityTest(
query="9R3J+R9 Singapore",
@ -96,6 +94,13 @@ tests: list[ContinuityTest] = [
"4072\n"
"Queensland, Australia"
),
(
"The University of Queensland\n"
"Hawken Drive\n"
"St Lucia, Greater Brisbane\n"
"4072\n"
"Queensland, Australia"
),
],
),
ContinuityTest(
@ -107,7 +112,14 @@ tests: list[ContinuityTest] = [
"Bukit Timah\n"
"599489\n"
"Northwest, Singapore"
)
),
(
"Ngee Ann Polytechnic\n"
"535 Clementi Road\n"
"Ewart Park, Bukit Timah\n"
"599489\n"
"Northwest, Singapore"
),
],
),
ContinuityTest(
@ -139,6 +151,28 @@ tests: list[ContinuityTest] = [
),
],
),
ContinuityTest(
query="J286+WV San Cesario sul Panaro, Modena, Italy",
expected=[
(
"Via Emilia 1193a\n"
"Unione dei comuni del Sorbara, Sant'Anna\n"
"41018 Modena Emilia-Romagna\n"
"Italia"
),
],
),
ContinuityTest(
query="GQ2G+GX Johor Bahru, Johor, Malaysia",
expected=[
(
"The Mall, Mid Valley Southkey\n"
"Jalan Bakar Batu\n"
"81100 Taman Sentosa Johor Bahru\n"
"Iskandar Malaysia, Johor, Malaysia"
),
],
),
]