parseapi 1.9.0__tar.gz → 1.9.2__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.
Files changed (35) hide show
  1. {parseapi-1.9.0 → parseapi-1.9.2}/CHANGELOG.md +5 -0
  2. {parseapi-1.9.0 → parseapi-1.9.2}/PKG-INFO +35 -1
  3. {parseapi-1.9.0 → parseapi-1.9.2}/README.md +34 -0
  4. {parseapi-1.9.0 → parseapi-1.9.2}/api/public.json +420 -0
  5. {parseapi-1.9.0 → parseapi-1.9.2}/pyproject.toml +1 -1
  6. {parseapi-1.9.0 → parseapi-1.9.2}/src/parseapi/__init__.py +1 -1
  7. {parseapi-1.9.0 → parseapi-1.9.2}/src/parseapi/_client.py +54 -8
  8. parseapi-1.9.2/tests/fixtures/emoji-artwork.json +184 -0
  9. parseapi-1.9.2/tests/fixtures/emoji-catalog.json +95 -0
  10. parseapi-1.9.2/tests/fixtures/measure-response.json +51 -0
  11. parseapi-1.9.2/tests/test_emoji.py +101 -0
  12. parseapi-1.9.2/tests/test_measure_response.py +18 -0
  13. {parseapi-1.9.0 → parseapi-1.9.2}/.github/workflows/publish.yml +0 -0
  14. {parseapi-1.9.0 → parseapi-1.9.2}/.github/workflows/test.yml +0 -0
  15. {parseapi-1.9.0 → parseapi-1.9.2}/.gitignore +0 -0
  16. {parseapi-1.9.0 → parseapi-1.9.2}/LICENSE +0 -0
  17. {parseapi-1.9.0 → parseapi-1.9.2}/scripts/public_api.py +0 -0
  18. {parseapi-1.9.0 → parseapi-1.9.2}/smoke/smoke.py +0 -0
  19. {parseapi-1.9.0 → parseapi-1.9.2}/src/parseapi/py.typed +0 -0
  20. {parseapi-1.9.0 → parseapi-1.9.2}/tests/bank-fixtures.json +0 -0
  21. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_bank.py +0 -0
  22. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_card_dx.py +0 -0
  23. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_client.py +0 -0
  24. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_company_directory.py +0 -0
  25. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_email_enrichment.py +0 -0
  26. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_language.py +0 -0
  27. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_name_formatting.py +0 -0
  28. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_postal_localities.py +0 -0
  29. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_public_api.py +0 -0
  30. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_stack.py +0 -0
  31. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_tariff.py +0 -0
  32. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_time_complete.py +0 -0
  33. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_time_gaps.py +0 -0
  34. {parseapi-1.9.0 → parseapi-1.9.2}/tests/test_version.py +0 -0
  35. {parseapi-1.9.0 → parseapi-1.9.2}/tests/typing_company_directory.py +0 -0
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.1 — 2026-10-12
4
+
5
+ - Add Emoji search/browse, categories, catalog, metadata, parsing and conversion; keep existing lookup calls and response decoding compatible.
6
+ - Support optional SVG/PNG artwork and translated names. Preserve API 2.0.0 selection, plans and existing non-Emoji behavior.
7
+
3
8
  ## 1.9.0 — 2026-09-29
4
9
 
5
10
  - Add Company stable-ID lookup, candidate search and coverage for the current public-company cohort.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: parseapi
3
- Version: 1.9.0
3
+ Version: 1.9.2
4
4
  Summary: Official ParseAPI client for Python. One key, minimal JSON, fast.
5
5
  Project-URL: Homepage, https://parseapi.com
6
6
  Project-URL: Documentation, https://parseapi.com/docs
@@ -260,6 +260,8 @@ units = parse.measure.units(unit="m")
260
260
 
261
261
  `amount` is a decimal string, such as `"180.34"`. Without `to`, the API returns the canonical unit for the measurement type. Pass `locale` for number formatting and `system` (`us` or `imperial`) when a customary unit needs context. Ambiguous input returns `valid: false`, a `reason`, and available `choices`. Invalid or incompatible target units use the normal API error.
262
262
 
263
+ Successful HTTP JSON contains `measure`, `valid`, `type`, `amount`, and `unit`. Invalid input adds `reason`. `choices` appears only when unit alternatives are available.
264
+
263
265
  Unit discovery accepts optional `query`, `type`, and `unit` filters. `unit` selects compatible targets. Omit the filters for the reviewed catalog. Both operations use pooled requests.
264
266
 
265
267
  ## Place statistics and optional detail
@@ -448,3 +450,35 @@ Successful checks may be reused for up to 24 hours. `pretty` optionally formats
448
450
  Stack defaults to a 35-second transport timeout so a first scan has time to finish. Other lookups retain their 10-second default. An explicit client timeout takes precedence.
449
451
 
450
452
  Vehicle lookups use `vin` as the input and response field. Existing VIN methods remain available for compatibility.
453
+
454
+ ## Emoji catalog additions
455
+
456
+ Resolve Unicode emoji or a named shortcode, search or browse a bounded catalog,
457
+ parse emoji positions in text, and convert between Unicode and a selected shortcode source.
458
+ The ordinary lookup remains four fields:
459
+
460
+ ```http
461
+ GET /emoji/rocket
462
+ ```
463
+
464
+ ```json
465
+ {"emoji":"🚀","name":"Rocket","shortcodes":[":rocket:","rocket"],"category":"Travel & Places"}
466
+ ```
467
+
468
+ Use `deep=true` for Unicode facts, keywords, tone families and available artwork.
469
+ Artwork is keyed by style: `deep.artwork.openmoji.svg` is the SVG URL, and
470
+ `deep.artwork.openmoji.png["72"]` is the 72×72 PNG URL (use your language’s map
471
+ access syntax). The same shape applies to `noto` and `twemoji`. Missing styles
472
+ and formats are omitted. URLs pin immutable artwork editions and retain their bytes.
473
+ Core values stay unchanged. Text parsing returns `emojis` with UTF-16 `positions`;
474
+ conversion returns `text` and unresolved tokens with positions and reasons.
475
+
476
+ The compact JSON catalog contains offline identity, qualification-form and
477
+ shortcode indexes. Request `profile=full` when artwork is needed and `lang` when
478
+ localized names and keywords are needed. `/emoji/metadata` carries dataset
479
+ coverage and shared source notices once. Artwork supports Google Noto, Twemoji
480
+ and OpenMoji, with real SVG/PNG availability and immutable first-party URLs.
481
+ A device's emoji font remains the ordinary display.
482
+
483
+ These additions are a local release candidate until the matching SDK and
484
+ backend are published. Existing lookup and search contracts are preserved.
@@ -241,6 +241,8 @@ units = parse.measure.units(unit="m")
241
241
 
242
242
  `amount` is a decimal string, such as `"180.34"`. Without `to`, the API returns the canonical unit for the measurement type. Pass `locale` for number formatting and `system` (`us` or `imperial`) when a customary unit needs context. Ambiguous input returns `valid: false`, a `reason`, and available `choices`. Invalid or incompatible target units use the normal API error.
243
243
 
244
+ Successful HTTP JSON contains `measure`, `valid`, `type`, `amount`, and `unit`. Invalid input adds `reason`. `choices` appears only when unit alternatives are available.
245
+
244
246
  Unit discovery accepts optional `query`, `type`, and `unit` filters. `unit` selects compatible targets. Omit the filters for the reviewed catalog. Both operations use pooled requests.
245
247
 
246
248
  ## Place statistics and optional detail
@@ -429,3 +431,35 @@ Successful checks may be reused for up to 24 hours. `pretty` optionally formats
429
431
  Stack defaults to a 35-second transport timeout so a first scan has time to finish. Other lookups retain their 10-second default. An explicit client timeout takes precedence.
430
432
 
431
433
  Vehicle lookups use `vin` as the input and response field. Existing VIN methods remain available for compatibility.
434
+
435
+ ## Emoji catalog additions
436
+
437
+ Resolve Unicode emoji or a named shortcode, search or browse a bounded catalog,
438
+ parse emoji positions in text, and convert between Unicode and a selected shortcode source.
439
+ The ordinary lookup remains four fields:
440
+
441
+ ```http
442
+ GET /emoji/rocket
443
+ ```
444
+
445
+ ```json
446
+ {"emoji":"🚀","name":"Rocket","shortcodes":[":rocket:","rocket"],"category":"Travel & Places"}
447
+ ```
448
+
449
+ Use `deep=true` for Unicode facts, keywords, tone families and available artwork.
450
+ Artwork is keyed by style: `deep.artwork.openmoji.svg` is the SVG URL, and
451
+ `deep.artwork.openmoji.png["72"]` is the 72×72 PNG URL (use your language’s map
452
+ access syntax). The same shape applies to `noto` and `twemoji`. Missing styles
453
+ and formats are omitted. URLs pin immutable artwork editions and retain their bytes.
454
+ Core values stay unchanged. Text parsing returns `emojis` with UTF-16 `positions`;
455
+ conversion returns `text` and unresolved tokens with positions and reasons.
456
+
457
+ The compact JSON catalog contains offline identity, qualification-form and
458
+ shortcode indexes. Request `profile=full` when artwork is needed and `lang` when
459
+ localized names and keywords are needed. `/emoji/metadata` carries dataset
460
+ coverage and shared source notices once. Artwork supports Google Noto, Twemoji
461
+ and OpenMoji, with real SVG/PNG availability and immutable first-party URLs.
462
+ A device's emoji font remains the ordinary display.
463
+
464
+ These additions are a local release candidate until the matching SDK and
465
+ backend are published. Existing lookup and search contracts are preserved.
@@ -888,6 +888,180 @@
888
888
  "name": "deep",
889
889
  "type": "bool"
890
890
  },
891
+ {
892
+ "default": null,
893
+ "kind": "KEYWORD_ONLY",
894
+ "name": "lang",
895
+ "type": "Optional[str]"
896
+ },
897
+ {
898
+ "default": null,
899
+ "kind": "KEYWORD_ONLY",
900
+ "name": "shortcode_source",
901
+ "type": "Optional[str]"
902
+ }
903
+ ],
904
+ "returns": "Json"
905
+ },
906
+ "emoji.browse": {
907
+ "async": true,
908
+ "parameters": [
909
+ {
910
+ "default": null,
911
+ "kind": "KEYWORD_ONLY",
912
+ "name": "limit",
913
+ "type": "Optional[int]"
914
+ },
915
+ {
916
+ "default": false,
917
+ "kind": "KEYWORD_ONLY",
918
+ "name": "deep",
919
+ "type": "bool"
920
+ },
921
+ {
922
+ "default": null,
923
+ "kind": "KEYWORD_ONLY",
924
+ "name": "lang",
925
+ "type": "Optional[str]"
926
+ },
927
+ {
928
+ "default": null,
929
+ "kind": "KEYWORD_ONLY",
930
+ "name": "category",
931
+ "type": "Optional[str]"
932
+ },
933
+ {
934
+ "default": null,
935
+ "kind": "KEYWORD_ONLY",
936
+ "name": "subcategory",
937
+ "type": "Optional[str]"
938
+ },
939
+ {
940
+ "default": null,
941
+ "kind": "KEYWORD_ONLY",
942
+ "name": "version",
943
+ "type": "Optional[str]"
944
+ },
945
+ {
946
+ "default": null,
947
+ "kind": "KEYWORD_ONLY",
948
+ "name": "max_version",
949
+ "type": "Optional[str]"
950
+ },
951
+ {
952
+ "default": null,
953
+ "kind": "KEYWORD_ONLY",
954
+ "name": "include_skin_tones",
955
+ "type": "Optional[bool]"
956
+ },
957
+ {
958
+ "default": null,
959
+ "kind": "KEYWORD_ONLY",
960
+ "name": "offset",
961
+ "type": "Optional[int]"
962
+ }
963
+ ],
964
+ "returns": "Json"
965
+ },
966
+ "emoji.catalog": {
967
+ "async": true,
968
+ "parameters": [
969
+ {
970
+ "default": true,
971
+ "kind": "KEYWORD_ONLY",
972
+ "name": "deep",
973
+ "type": "bool"
974
+ },
975
+ {
976
+ "default": null,
977
+ "kind": "KEYWORD_ONLY",
978
+ "name": "lang",
979
+ "type": "Optional[str]"
980
+ },
981
+ {
982
+ "default": null,
983
+ "kind": "KEYWORD_ONLY",
984
+ "name": "category",
985
+ "type": "Optional[str]"
986
+ },
987
+ {
988
+ "default": null,
989
+ "kind": "KEYWORD_ONLY",
990
+ "name": "subcategory",
991
+ "type": "Optional[str]"
992
+ },
993
+ {
994
+ "default": null,
995
+ "kind": "KEYWORD_ONLY",
996
+ "name": "version",
997
+ "type": "Optional[str]"
998
+ },
999
+ {
1000
+ "default": null,
1001
+ "kind": "KEYWORD_ONLY",
1002
+ "name": "max_version",
1003
+ "type": "Optional[str]"
1004
+ },
1005
+ {
1006
+ "default": null,
1007
+ "kind": "KEYWORD_ONLY",
1008
+ "name": "include_skin_tones",
1009
+ "type": "Optional[bool]"
1010
+ },
1011
+ {
1012
+ "default": null,
1013
+ "kind": "KEYWORD_ONLY",
1014
+ "name": "profile",
1015
+ "type": "Optional[str]"
1016
+ }
1017
+ ],
1018
+ "returns": "Json"
1019
+ },
1020
+ "emoji.categories": {
1021
+ "async": true,
1022
+ "parameters": [],
1023
+ "returns": "Json"
1024
+ },
1025
+ "emoji.convert": {
1026
+ "async": true,
1027
+ "parameters": [
1028
+ {
1029
+ "kind": "POSITIONAL_OR_KEYWORD",
1030
+ "name": "text",
1031
+ "type": "str"
1032
+ },
1033
+ {
1034
+ "kind": "KEYWORD_ONLY",
1035
+ "name": "direction",
1036
+ "type": "str"
1037
+ },
1038
+ {
1039
+ "kind": "KEYWORD_ONLY",
1040
+ "name": "shortcode_source",
1041
+ "type": "str"
1042
+ }
1043
+ ],
1044
+ "returns": "Json"
1045
+ },
1046
+ "emoji.metadata": {
1047
+ "async": true,
1048
+ "parameters": [],
1049
+ "returns": "Json"
1050
+ },
1051
+ "emoji.parse": {
1052
+ "async": true,
1053
+ "parameters": [
1054
+ {
1055
+ "kind": "POSITIONAL_OR_KEYWORD",
1056
+ "name": "text",
1057
+ "type": "str"
1058
+ },
1059
+ {
1060
+ "default": false,
1061
+ "kind": "KEYWORD_ONLY",
1062
+ "name": "deep",
1063
+ "type": "bool"
1064
+ },
891
1065
  {
892
1066
  "default": null,
893
1067
  "kind": "KEYWORD_ONLY",
@@ -922,6 +1096,42 @@
922
1096
  "kind": "KEYWORD_ONLY",
923
1097
  "name": "lang",
924
1098
  "type": "Optional[str]"
1099
+ },
1100
+ {
1101
+ "default": null,
1102
+ "kind": "KEYWORD_ONLY",
1103
+ "name": "category",
1104
+ "type": "Optional[str]"
1105
+ },
1106
+ {
1107
+ "default": null,
1108
+ "kind": "KEYWORD_ONLY",
1109
+ "name": "subcategory",
1110
+ "type": "Optional[str]"
1111
+ },
1112
+ {
1113
+ "default": null,
1114
+ "kind": "KEYWORD_ONLY",
1115
+ "name": "version",
1116
+ "type": "Optional[str]"
1117
+ },
1118
+ {
1119
+ "default": null,
1120
+ "kind": "KEYWORD_ONLY",
1121
+ "name": "max_version",
1122
+ "type": "Optional[str]"
1123
+ },
1124
+ {
1125
+ "default": null,
1126
+ "kind": "KEYWORD_ONLY",
1127
+ "name": "include_skin_tones",
1128
+ "type": "Optional[bool]"
1129
+ },
1130
+ {
1131
+ "default": null,
1132
+ "kind": "KEYWORD_ONLY",
1133
+ "name": "offset",
1134
+ "type": "Optional[int]"
925
1135
  }
926
1136
  ],
927
1137
  "returns": "Json"
@@ -2880,6 +3090,180 @@
2880
3090
  "name": "deep",
2881
3091
  "type": "bool"
2882
3092
  },
3093
+ {
3094
+ "default": null,
3095
+ "kind": "KEYWORD_ONLY",
3096
+ "name": "lang",
3097
+ "type": "Optional[str]"
3098
+ },
3099
+ {
3100
+ "default": null,
3101
+ "kind": "KEYWORD_ONLY",
3102
+ "name": "shortcode_source",
3103
+ "type": "Optional[str]"
3104
+ }
3105
+ ],
3106
+ "returns": "Json"
3107
+ },
3108
+ "emoji.browse": {
3109
+ "async": false,
3110
+ "parameters": [
3111
+ {
3112
+ "default": null,
3113
+ "kind": "KEYWORD_ONLY",
3114
+ "name": "limit",
3115
+ "type": "Optional[int]"
3116
+ },
3117
+ {
3118
+ "default": false,
3119
+ "kind": "KEYWORD_ONLY",
3120
+ "name": "deep",
3121
+ "type": "bool"
3122
+ },
3123
+ {
3124
+ "default": null,
3125
+ "kind": "KEYWORD_ONLY",
3126
+ "name": "lang",
3127
+ "type": "Optional[str]"
3128
+ },
3129
+ {
3130
+ "default": null,
3131
+ "kind": "KEYWORD_ONLY",
3132
+ "name": "category",
3133
+ "type": "Optional[str]"
3134
+ },
3135
+ {
3136
+ "default": null,
3137
+ "kind": "KEYWORD_ONLY",
3138
+ "name": "subcategory",
3139
+ "type": "Optional[str]"
3140
+ },
3141
+ {
3142
+ "default": null,
3143
+ "kind": "KEYWORD_ONLY",
3144
+ "name": "version",
3145
+ "type": "Optional[str]"
3146
+ },
3147
+ {
3148
+ "default": null,
3149
+ "kind": "KEYWORD_ONLY",
3150
+ "name": "max_version",
3151
+ "type": "Optional[str]"
3152
+ },
3153
+ {
3154
+ "default": null,
3155
+ "kind": "KEYWORD_ONLY",
3156
+ "name": "include_skin_tones",
3157
+ "type": "Optional[bool]"
3158
+ },
3159
+ {
3160
+ "default": null,
3161
+ "kind": "KEYWORD_ONLY",
3162
+ "name": "offset",
3163
+ "type": "Optional[int]"
3164
+ }
3165
+ ],
3166
+ "returns": "Json"
3167
+ },
3168
+ "emoji.catalog": {
3169
+ "async": false,
3170
+ "parameters": [
3171
+ {
3172
+ "default": true,
3173
+ "kind": "KEYWORD_ONLY",
3174
+ "name": "deep",
3175
+ "type": "bool"
3176
+ },
3177
+ {
3178
+ "default": null,
3179
+ "kind": "KEYWORD_ONLY",
3180
+ "name": "lang",
3181
+ "type": "Optional[str]"
3182
+ },
3183
+ {
3184
+ "default": null,
3185
+ "kind": "KEYWORD_ONLY",
3186
+ "name": "category",
3187
+ "type": "Optional[str]"
3188
+ },
3189
+ {
3190
+ "default": null,
3191
+ "kind": "KEYWORD_ONLY",
3192
+ "name": "subcategory",
3193
+ "type": "Optional[str]"
3194
+ },
3195
+ {
3196
+ "default": null,
3197
+ "kind": "KEYWORD_ONLY",
3198
+ "name": "version",
3199
+ "type": "Optional[str]"
3200
+ },
3201
+ {
3202
+ "default": null,
3203
+ "kind": "KEYWORD_ONLY",
3204
+ "name": "max_version",
3205
+ "type": "Optional[str]"
3206
+ },
3207
+ {
3208
+ "default": null,
3209
+ "kind": "KEYWORD_ONLY",
3210
+ "name": "include_skin_tones",
3211
+ "type": "Optional[bool]"
3212
+ },
3213
+ {
3214
+ "default": null,
3215
+ "kind": "KEYWORD_ONLY",
3216
+ "name": "profile",
3217
+ "type": "Optional[str]"
3218
+ }
3219
+ ],
3220
+ "returns": "Json"
3221
+ },
3222
+ "emoji.categories": {
3223
+ "async": false,
3224
+ "parameters": [],
3225
+ "returns": "Json"
3226
+ },
3227
+ "emoji.convert": {
3228
+ "async": false,
3229
+ "parameters": [
3230
+ {
3231
+ "kind": "POSITIONAL_OR_KEYWORD",
3232
+ "name": "text",
3233
+ "type": "str"
3234
+ },
3235
+ {
3236
+ "kind": "KEYWORD_ONLY",
3237
+ "name": "direction",
3238
+ "type": "str"
3239
+ },
3240
+ {
3241
+ "kind": "KEYWORD_ONLY",
3242
+ "name": "shortcode_source",
3243
+ "type": "str"
3244
+ }
3245
+ ],
3246
+ "returns": "Json"
3247
+ },
3248
+ "emoji.metadata": {
3249
+ "async": false,
3250
+ "parameters": [],
3251
+ "returns": "Json"
3252
+ },
3253
+ "emoji.parse": {
3254
+ "async": false,
3255
+ "parameters": [
3256
+ {
3257
+ "kind": "POSITIONAL_OR_KEYWORD",
3258
+ "name": "text",
3259
+ "type": "str"
3260
+ },
3261
+ {
3262
+ "default": false,
3263
+ "kind": "KEYWORD_ONLY",
3264
+ "name": "deep",
3265
+ "type": "bool"
3266
+ },
2883
3267
  {
2884
3268
  "default": null,
2885
3269
  "kind": "KEYWORD_ONLY",
@@ -2914,6 +3298,42 @@
2914
3298
  "kind": "KEYWORD_ONLY",
2915
3299
  "name": "lang",
2916
3300
  "type": "Optional[str]"
3301
+ },
3302
+ {
3303
+ "default": null,
3304
+ "kind": "KEYWORD_ONLY",
3305
+ "name": "category",
3306
+ "type": "Optional[str]"
3307
+ },
3308
+ {
3309
+ "default": null,
3310
+ "kind": "KEYWORD_ONLY",
3311
+ "name": "subcategory",
3312
+ "type": "Optional[str]"
3313
+ },
3314
+ {
3315
+ "default": null,
3316
+ "kind": "KEYWORD_ONLY",
3317
+ "name": "version",
3318
+ "type": "Optional[str]"
3319
+ },
3320
+ {
3321
+ "default": null,
3322
+ "kind": "KEYWORD_ONLY",
3323
+ "name": "max_version",
3324
+ "type": "Optional[str]"
3325
+ },
3326
+ {
3327
+ "default": null,
3328
+ "kind": "KEYWORD_ONLY",
3329
+ "name": "include_skin_tones",
3330
+ "type": "Optional[bool]"
3331
+ },
3332
+ {
3333
+ "default": null,
3334
+ "kind": "KEYWORD_ONLY",
3335
+ "name": "offset",
3336
+ "type": "Optional[int]"
2917
3337
  }
2918
3338
  ],
2919
3339
  "returns": "Json"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "parseapi"
7
- version = "1.9.0"
7
+ version = "1.9.2"
8
8
  description = "Official ParseAPI client for Python. One key, minimal JSON, fast."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -2,5 +2,5 @@
2
2
 
3
3
  from ._client import AsyncParseAPI, ParseAPI, ParseAPIError
4
4
 
5
- __version__ = "1.9.0"
5
+ __version__ = "1.9.2"
6
6
  __all__ = ["ParseAPI", "AsyncParseAPI", "ParseAPIError", "__version__"]
@@ -549,11 +549,33 @@ class _EmojiSync:
549
549
  def __init__(self, client: ParseAPI):
550
550
  self._client = client
551
551
 
552
- def __call__(self, emoji: str, *, deep: bool = False, lang: Optional[str] = None) -> Json:
553
- return self._client._get(f"/emoji/{_seg(emoji)}", {"deep": deep, "lang": lang})
552
+ def __call__(self, emoji: str, *, deep: bool = False, lang: Optional[str] = None, shortcode_source: Optional[str] = None) -> Json:
553
+ return self._client._get(f"/emoji/{_seg(emoji)}", {"deep": deep, "lang": lang, "shortcode_source": shortcode_source})
554
+
555
+ def search(self, query: str, *, limit: Optional[int] = None, deep: bool = False, lang: Optional[str] = None, category: Optional[str] = None, subcategory: Optional[str] = None, version: Optional[str] = None, max_version: Optional[str] = None, include_skin_tones: Optional[bool] = None, offset: Optional[int] = None) -> Json:
556
+ return self._client._get("/emoji", {"q": query, **{"limit": limit, "deep": deep, "lang": lang, "category": category, "subcategory": subcategory, "version": version, "max_version": max_version, "include_skin_tones": None if include_skin_tones is None else str(include_skin_tones).lower(), "offset": offset}})
557
+
558
+ def browse(self, *, limit: Optional[int] = None, deep: bool = False, lang: Optional[str] = None, category: Optional[str] = None, subcategory: Optional[str] = None, version: Optional[str] = None, max_version: Optional[str] = None, include_skin_tones: Optional[bool] = None, offset: Optional[int] = None) -> Json:
559
+ return self._client._get("/emoji", {"limit": limit, "deep": deep, "lang": lang, "category": category, "subcategory": subcategory, "version": version, "max_version": max_version, "include_skin_tones": None if include_skin_tones is None else str(include_skin_tones).lower(), "offset": offset})
560
+
561
+ def catalog(self, *, deep: bool = True, lang: Optional[str] = None, category: Optional[str] = None, subcategory: Optional[str] = None, version: Optional[str] = None, max_version: Optional[str] = None, include_skin_tones: Optional[bool] = None, profile: Optional[str] = None) -> Json:
562
+ """Download the complete filtered JSON inventory and offline resolution maps."""
563
+ return self._client._get("/emoji/catalog", {"profile": profile, "deep": str(deep).lower(), "lang": lang, "category": category, "subcategory": subcategory, "version": version, "max_version": max_version, "include_skin_tones": None if include_skin_tones is None else str(include_skin_tones).lower()})
564
+
565
+ def metadata(self) -> Json:
566
+ return self._client._get("/emoji/metadata")
567
+
568
+ def categories(self) -> Json:
569
+ return self._client._get("/emoji/categories")
570
+
571
+ def parse(self, text: str, *, deep: bool = False, lang: Optional[str] = None) -> Json:
572
+ """Extract complete emoji sequences. Positions are UTF-16 code units; end is exclusive."""
573
+ return self._client._get("/emoji/parse", {"deep": deep, "lang": lang}, json={"text": text})
574
+
575
+ def convert(self, text: str, *, direction: str, shortcode_source: str) -> Json:
576
+ """Convert complete sequences; unresolved codes stay literal. Offsets refer to input UTF-16."""
577
+ return self._client._get("/emoji/convert", {"direction": direction, "shortcode_source": shortcode_source}, json={"text": text})
554
578
 
555
- def search(self, query: str, *, limit: Optional[int] = None, deep: bool = False, lang: Optional[str] = None) -> Json:
556
- return self._client._get("/emoji", {"q": query, "limit": limit, "deep": deep, "lang": lang})
557
579
 
558
580
 
559
581
  class _IndustrySync:
@@ -971,11 +993,33 @@ class _EmojiAsync:
971
993
  def __init__(self, client: AsyncParseAPI):
972
994
  self._client = client
973
995
 
974
- async def __call__(self, emoji: str, *, deep: bool = False, lang: Optional[str] = None) -> Json:
975
- return await self._client._get(f"/emoji/{_seg(emoji)}", {"deep": deep, "lang": lang})
996
+ async def __call__(self, emoji: str, *, deep: bool = False, lang: Optional[str] = None, shortcode_source: Optional[str] = None) -> Json:
997
+ return await self._client._get(f"/emoji/{_seg(emoji)}", {"deep": deep, "lang": lang, "shortcode_source": shortcode_source})
998
+
999
+ async def search(self, query: str, *, limit: Optional[int] = None, deep: bool = False, lang: Optional[str] = None, category: Optional[str] = None, subcategory: Optional[str] = None, version: Optional[str] = None, max_version: Optional[str] = None, include_skin_tones: Optional[bool] = None, offset: Optional[int] = None) -> Json:
1000
+ return await self._client._get("/emoji", {"q": query, **{"limit": limit, "deep": deep, "lang": lang, "category": category, "subcategory": subcategory, "version": version, "max_version": max_version, "include_skin_tones": None if include_skin_tones is None else str(include_skin_tones).lower(), "offset": offset}})
1001
+
1002
+ async def browse(self, *, limit: Optional[int] = None, deep: bool = False, lang: Optional[str] = None, category: Optional[str] = None, subcategory: Optional[str] = None, version: Optional[str] = None, max_version: Optional[str] = None, include_skin_tones: Optional[bool] = None, offset: Optional[int] = None) -> Json:
1003
+ return await self._client._get("/emoji", {"limit": limit, "deep": deep, "lang": lang, "category": category, "subcategory": subcategory, "version": version, "max_version": max_version, "include_skin_tones": None if include_skin_tones is None else str(include_skin_tones).lower(), "offset": offset})
1004
+
1005
+ async def catalog(self, *, deep: bool = True, lang: Optional[str] = None, category: Optional[str] = None, subcategory: Optional[str] = None, version: Optional[str] = None, max_version: Optional[str] = None, include_skin_tones: Optional[bool] = None, profile: Optional[str] = None) -> Json:
1006
+ """Download the complete filtered JSON inventory and offline resolution maps."""
1007
+ return await self._client._get("/emoji/catalog", {"profile": profile, "deep": str(deep).lower(), "lang": lang, "category": category, "subcategory": subcategory, "version": version, "max_version": max_version, "include_skin_tones": None if include_skin_tones is None else str(include_skin_tones).lower()})
1008
+
1009
+ async def metadata(self) -> Json:
1010
+ return await self._client._get("/emoji/metadata")
1011
+
1012
+ async def categories(self) -> Json:
1013
+ return await self._client._get("/emoji/categories")
1014
+
1015
+ async def parse(self, text: str, *, deep: bool = False, lang: Optional[str] = None) -> Json:
1016
+ """Extract complete emoji sequences. Positions are UTF-16 code units; end is exclusive."""
1017
+ return await self._client._get("/emoji/parse", {"deep": deep, "lang": lang}, json={"text": text})
1018
+
1019
+ async def convert(self, text: str, *, direction: str, shortcode_source: str) -> Json:
1020
+ """Convert complete sequences; unresolved codes stay literal. Offsets refer to input UTF-16."""
1021
+ return await self._client._get("/emoji/convert", {"direction": direction, "shortcode_source": shortcode_source}, json={"text": text})
976
1022
 
977
- async def search(self, query: str, *, limit: Optional[int] = None, deep: bool = False, lang: Optional[str] = None) -> Json:
978
- return await self._client._get("/emoji", {"q": query, "limit": limit, "deep": deep, "lang": lang})
979
1023
 
980
1024
 
981
1025
  class _IndustryAsync:
@@ -1132,6 +1176,7 @@ class _MeasureSync:
1132
1176
  def __call__(self, measure: str, *, to: Optional[str] = None, locale: Optional[str] = None, system: Optional[str] = None) -> Json:
1133
1177
  """Parse or convert a measurement. Amount is a decimal string. Without to, use the
1134
1178
  type's canonical unit. Locale and system (us or imperial) resolve explicit ambiguity.
1179
+ Successful JSON omits reason and choices. Invalid input adds reason and alternatives add choices.
1135
1180
  Invalid measurements return valid=False and a reason. Invalid targets raise an API error.
1136
1181
  """
1137
1182
  return self._client._get(f"/measure/{_seg(measure)}", {"to": to, "locale": locale, "system": system})
@@ -1148,6 +1193,7 @@ class _MeasureAsync:
1148
1193
  async def __call__(self, measure: str, *, to: Optional[str] = None, locale: Optional[str] = None, system: Optional[str] = None) -> Json:
1149
1194
  """Parse or convert a measurement. Amount is a decimal string. Without to, use the
1150
1195
  type's canonical unit. Locale and system (us or imperial) resolve explicit ambiguity.
1196
+ Successful JSON omits reason and choices. Invalid input adds reason and alternatives add choices.
1151
1197
  Invalid measurements return valid=False and a reason. Invalid targets raise an API error.
1152
1198
  """
1153
1199
  return await self._client._get(f"/measure/{_seg(measure)}", {"to": to, "locale": locale, "system": system})
@@ -0,0 +1,184 @@
1
+ {
2
+ "metadata": {
3
+ "edition": "2e6b8d9c0d972f6e04e2252b55dc8fb57c3967b52c16cb5d91fceb51dcd27660",
4
+ "source_version": "18.0",
5
+ "max_version": "18.0",
6
+ "total": 3963,
7
+ "completeness": {
8
+ "missing_count": 0,
9
+ "unexpected_count": 0
10
+ },
11
+ "shortcode_sources": {
12
+ "cldr": {
13
+ "records": 3963,
14
+ "missing_records": 0,
15
+ "ambiguous_aliases": 1
16
+ },
17
+ "github": {
18
+ "records": 1870,
19
+ "missing_records": 2093,
20
+ "ambiguous_aliases": 0
21
+ },
22
+ "slack": {
23
+ "records": 1906,
24
+ "missing_records": 2057,
25
+ "ambiguous_aliases": 0
26
+ },
27
+ "discord": {
28
+ "records": 3773,
29
+ "missing_records": 190,
30
+ "ambiguous_aliases": 0
31
+ }
32
+ },
33
+ "sources": [
34
+ {
35
+ "id": "unicode",
36
+ "version": "18.0",
37
+ "source_url": "https://unicode.org/Public/18.0.0/emoji/emoji-test.txt",
38
+ "license": "Unicode-3.0",
39
+ "license_url": "https://www.unicode.org/license.txt"
40
+ },
41
+ {
42
+ "id": "cldr",
43
+ "version": null,
44
+ "source_url": "https://raw.githubusercontent.com/unicode-org/cldr/7cdeb9e7fac52c59f22fc7248b05f989ee5689c2/common/annotations/en.xml",
45
+ "license": "Unicode-3.0",
46
+ "license_url": "https://raw.githubusercontent.com/unicode-org/cldr/7cdeb9e7fac52c59f22fc7248b05f989ee5689c2/LICENSE"
47
+ },
48
+ {
49
+ "id": "emojibase",
50
+ "version": "17.0.0",
51
+ "source_url": "https://cdn.jsdelivr.net/npm/emojibase-data@17.0.0/en/data.json",
52
+ "license": "MIT",
53
+ "license_url": "https://cdn.jsdelivr.net/npm/emojibase-data@17.0.0/LICENSE"
54
+ }
55
+ ],
56
+ "notices": [
57
+ {
58
+ "id": "unicode",
59
+ "license": "Unicode-3.0",
60
+ "notice": "COPYRIGHT AND PERMISSION NOTICE\n\nCopyright © 2001–2026 Unicode, Inc.\n\nUnicode Technical Standard #35, Unicode Locale Data Markup Language\n(LDML) made available in this repository is protected by copyright, and\npermission must be obtained from Unicode, Inc. prior to any\nreproduction, modification, or other use not permitted by the Unicode\nTerms of Use <https://www.unicode.org/copyright.html>. Specifically, you\nmay make copies of this publication and may annotate and translate it\nsolely for personal or internal business purposes and not for public\ndistribution, provided that any such permitted copies and modifications\nfully reproduce all copyright and other legal notices contained in the\noriginal. You may not make copies of or modifications to this\npublication for public distribution, or incorporate it in whole or in\npart into any product or publication without the express written\npermission of Unicode. Notwithstanding the foregoing, you may within\nthis repository provide comments and propose changes to this standard\nand may make copies/forks solely for such purposes provided that you\nreproduce this copyright notice and these terms of use in any such\ncopy/fork.\n\nAll other content in this repo is released under UNICODE LICENSE V3\n\nNOTICE TO USER: Carefully read the following legal agreement. BY\nDOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR\nSOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE\nTERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT\nDOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.\n\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of data files and any associated documentation (the \"Data Files\") or\nsoftware and any associated documentation (the \"Software\") to deal in the\nData Files or Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, and/or sell\ncopies of the Data Files or Software, and to permit persons to whom the\nData Files or Software are furnished to do so, provided that either (a)\nthis copyright and permission notice appear with all copies of the Data\nFiles or Software, or (b) this copyright and permission notice appear in\nassociated Documentation.\n\nTHE DATA FILES AND SOFTWARE ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY\nKIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF\nTHIRD PARTY RIGHTS.\n\nIN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE\nBE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,\nOR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\nWHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\nARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA\nFILES OR SOFTWARE.\n\nExcept as contained in this notice, the name of a copyright holder shall\nnot be used in advertising or otherwise to promote the sale, use or other\ndealings in these Data Files or Software without prior written\nauthorization of the copyright holder.\n\nSPDX-License-Identifier: Unicode-3.0\n"
61
+ },
62
+ {
63
+ "id": "emojibase",
64
+ "license": "MIT",
65
+ "notice": "MIT License\n\nCopyright (c) 2017-2019 Miles Johnson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
66
+ }
67
+ ],
68
+ "artwork_sources": [
69
+ {
70
+ "style": "noto",
71
+ "version": "v2026-09-24-unicode18_0",
72
+ "unicode_version": "18.0",
73
+ "source_url": "https://github.com/googlefonts/noto-emoji/tree/e20cbc2bbec1926686be9f9bee7d1d2cfa1fea0e",
74
+ "license": "Apache-2.0",
75
+ "license_url": "https://www.apache.org/licenses/LICENSE-2.0",
76
+ "attribution": "Noto Emoji by Google and contributors.",
77
+ "notices": [
78
+ "https://cdn.parseapi.com/emoji/noto/18.0.0/notices/2D/svg/LICENSE",
79
+ "https://cdn.parseapi.com/emoji/noto/18.0.0/notices/LICENSE",
80
+ "https://cdn.parseapi.com/emoji/noto/18.0.0/notices/LICENSE-ARTWORK.txt",
81
+ "https://cdn.parseapi.com/emoji/noto/18.0.0/notices/NOTICE.txt",
82
+ "https://cdn.parseapi.com/emoji/noto/18.0.0/notices/third_party/region-flags/LICENSE"
83
+ ],
84
+ "flag_license": {
85
+ "license": "LicenseRef-Noto-Region-Flags",
86
+ "license_url": "https://github.com/googlefonts/noto-emoji/blob/e20cbc2bbec1926686be9f9bee7d1d2cfa1fea0e/third_party/region-flags/LICENSE",
87
+ "description": "Noto's retained flag-source notice records public-domain or copyright-exempt origins, including national-law exemptions."
88
+ },
89
+ "loaded_coverage": {
90
+ "svg": 3963,
91
+ "png": 3701
92
+ }
93
+ },
94
+ {
95
+ "style": "twemoji",
96
+ "version": "v17.0.3",
97
+ "unicode_version": "17.0",
98
+ "source_url": "https://github.com/jdecked/twemoji/tree/b6b55fef1e8636b540a6d016a4729ca8cdf2e60b",
99
+ "license": "CC-BY-4.0",
100
+ "license_url": "https://creativecommons.org/licenses/by/4.0/",
101
+ "attribution": "Twemoji graphics by Twitter, Inc. and other contributors, maintained by the Twemoji community.",
102
+ "notices": [
103
+ "https://cdn.parseapi.com/emoji/twemoji/17.0.3/notices/LICENSE",
104
+ "https://cdn.parseapi.com/emoji/twemoji/17.0.3/notices/LICENSE-ARTWORK.txt",
105
+ "https://cdn.parseapi.com/emoji/twemoji/17.0.3/notices/LICENSE-GRAPHICS",
106
+ "https://cdn.parseapi.com/emoji/twemoji/17.0.3/notices/NOTICE.txt"
107
+ ],
108
+ "loaded_coverage": {
109
+ "svg": 3944,
110
+ "png": 3944
111
+ }
112
+ },
113
+ {
114
+ "style": "openmoji",
115
+ "version": "17.0.0",
116
+ "unicode_version": "17.0",
117
+ "source_url": "https://github.com/hfg-gmuend/openmoji/tree/f9fc506a3f913be9897ab0181d611d4c910a4104",
118
+ "license": "CC-BY-SA-4.0",
119
+ "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
120
+ "attribution": "All emojis designed by OpenMoji – the open-source emoji and icon project. License: CC BY-SA 4.0.",
121
+ "notices": [
122
+ "https://cdn.parseapi.com/emoji/openmoji/17.0.0/notices/LICENSE-ARTWORK.txt",
123
+ "https://cdn.parseapi.com/emoji/openmoji/17.0.0/notices/LICENSE.txt",
124
+ "https://cdn.parseapi.com/emoji/openmoji/17.0.0/notices/NOTICE.txt"
125
+ ],
126
+ "loaded_coverage": {
127
+ "svg": 3944,
128
+ "png": 3944
129
+ }
130
+ }
131
+ ]
132
+ },
133
+ "emoji": {
134
+ "emoji": "🚀",
135
+ "name": "Rocket",
136
+ "shortcodes": [
137
+ ":rocket:",
138
+ "rocket"
139
+ ],
140
+ "category": "Travel & Places",
141
+ "deep": {
142
+ "codepoints": [
143
+ "U+1F680"
144
+ ],
145
+ "hex": "1F680",
146
+ "status": "fully-qualified",
147
+ "version": "0.6",
148
+ "keywords": [
149
+ "rocket",
150
+ "launch",
151
+ "rockets",
152
+ "space",
153
+ "travel",
154
+ "ship"
155
+ ],
156
+ "skins": [],
157
+ "subcategory": "transport-air",
158
+ "artwork": {
159
+ "noto": {
160
+ "svg": "https://cdn.parseapi.com/emoji/noto/18.0.0/1f680.svg",
161
+ "png": {
162
+ "32": "https://cdn.parseapi.com/emoji/noto/18.0.0/32/1f680.png",
163
+ "72": "https://cdn.parseapi.com/emoji/noto/18.0.0/72/1f680.png",
164
+ "128": "https://cdn.parseapi.com/emoji/noto/18.0.0/128/1f680.png",
165
+ "512": "https://cdn.parseapi.com/emoji/noto/18.0.0/512/1f680.png"
166
+ }
167
+ },
168
+ "twemoji": {
169
+ "svg": "https://cdn.parseapi.com/emoji/twemoji/17.0.3/1f680.svg",
170
+ "png": {
171
+ "72": "https://cdn.parseapi.com/emoji/twemoji/17.0.3/72/1f680.png"
172
+ }
173
+ },
174
+ "openmoji": {
175
+ "svg": "https://cdn.parseapi.com/emoji/openmoji/17.0.0/1f680.svg",
176
+ "png": {
177
+ "72": "https://cdn.parseapi.com/emoji/openmoji/17.0.0/72/1f680.png",
178
+ "618": "https://cdn.parseapi.com/emoji/openmoji/17.0.0/618/1f680.png"
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ }
@@ -0,0 +1,95 @@
1
+ {
2
+ "metadata": {
3
+ "edition": "test-edition",
4
+ "max_version": "18.0",
5
+ "source_version": "18.0",
6
+ "completeness": {
7
+ "missing_count": 1,
8
+ "unexpected_count": 0
9
+ },
10
+ "sources": [
11
+ {
12
+ "id": "unicode",
13
+ "version": "18.0",
14
+ "source_url": "https://unicode.org/emoji",
15
+ "license": "Unicode-3.0",
16
+ "license_url": "https://unicode.org/license"
17
+ }
18
+ ],
19
+ "notices": [
20
+ {
21
+ "id": "unicode",
22
+ "license": "Unicode-3.0",
23
+ "notice": "Full source notice"
24
+ }
25
+ ],
26
+ "artwork_sources": [],
27
+ "total": 1
28
+ },
29
+ "total": 1,
30
+ "emojis": [
31
+ {
32
+ "emoji": "❤️",
33
+ "name": "red heart",
34
+ "shortcodes": [
35
+ ":heart:",
36
+ "heart"
37
+ ],
38
+ "category": "Smileys & Emotion",
39
+ "deep": {
40
+ "version": "0.6",
41
+ "keywords": [
42
+ "heart",
43
+ "love"
44
+ ],
45
+ "subcategory": "emotion",
46
+ "artwork": {}
47
+ }
48
+ }
49
+ ],
50
+ "forms": {
51
+ "❤": "❤️",
52
+ "❤️": "❤️"
53
+ },
54
+ "shortcodes": {
55
+ "cldr": {
56
+ "red_heart": [
57
+ "❤️"
58
+ ]
59
+ },
60
+ "github": {
61
+ "heart": [
62
+ "❤️"
63
+ ]
64
+ },
65
+ "slack": {},
66
+ "discord": {}
67
+ },
68
+ "legacy": {
69
+ "heart": "❤️"
70
+ },
71
+ "scope": {
72
+ "filters": {},
73
+ "complete_inventory": true
74
+ },
75
+ "search": {
76
+ "native": {
77
+ "❤️": {
78
+ "name": "Heart",
79
+ "keywords": [
80
+ "love"
81
+ ],
82
+ "order": null
83
+ }
84
+ },
85
+ "localized_keywords": []
86
+ },
87
+ "localization": {
88
+ "requested_locale": "fr",
89
+ "resolved_locale": "fr",
90
+ "translated_names": 1,
91
+ "translated_keywords": 0,
92
+ "fallback_names": 0,
93
+ "fallback_keywords": 1
94
+ }
95
+ }
@@ -0,0 +1,51 @@
1
+ [
2
+ {
3
+ "measure": "0 m",
4
+ "valid": true,
5
+ "type": "length",
6
+ "amount": "0",
7
+ "unit": "m"
8
+ },
9
+ {
10
+ "measure": "1.00000000000000000001 m",
11
+ "valid": true,
12
+ "type": "length",
13
+ "amount": "1",
14
+ "unit": "m"
15
+ },
16
+ {
17
+ "measure": "180",
18
+ "valid": false,
19
+ "type": null,
20
+ "amount": null,
21
+ "unit": null,
22
+ "reason": "missing_unit"
23
+ },
24
+ {
25
+ "measure": "1 gallon",
26
+ "valid": false,
27
+ "type": null,
28
+ "amount": null,
29
+ "unit": null,
30
+ "reason": "ambiguous_unit",
31
+ "choices": [
32
+ {
33
+ "unit": "gal_us",
34
+ "name": "US gallon"
35
+ },
36
+ {
37
+ "unit": "gal_imp",
38
+ "name": "imperial gallon"
39
+ }
40
+ ]
41
+ },
42
+ {
43
+ "measure": "0 m",
44
+ "valid": true,
45
+ "type": "length",
46
+ "amount": "0",
47
+ "unit": "m",
48
+ "reason": null,
49
+ "choices": []
50
+ }
51
+ ]
@@ -0,0 +1,101 @@
1
+ import asyncio
2
+ import json
3
+
4
+ import httpx
5
+ import pytest
6
+ from parseapi import AsyncParseAPI, ParseAPI
7
+
8
+
9
+ def test_sync_and_async_emoji_catalog_and_text_body():
10
+ async def run(async_mode):
11
+ calls = []
12
+ payload = {"total": 2, "emojis": [{"emoji": "👍🏿", "name": "thumbs up", "shortcodes": [], "category": None, "occurrences": 2, "positions": [{"start": 2, "end": 6}, {"start": 7, "end": 11}], "deep": {"base": "👍", "variants": [{"emoji": "👍🏿", "tones": ["dark"]}]} }], "future": True}
13
+ def handle(request):
14
+ calls.append(request)
15
+ return httpx.Response(200, json=payload)
16
+ client_type = AsyncParseAPI if async_mode else ParseAPI
17
+ client = client_type("test", transport=httpx.MockTransport(handle))
18
+ async def call(method, *args, **kwargs):
19
+ value = method(*args, **kwargs)
20
+ return await value if async_mode else value
21
+ assert await call(client.emoji, ":cat:", shortcode_source="github") == payload
22
+ await call(client.emoji.search, "face", category="Smileys & Emotion", version="17.0", max_version="18.0", include_skin_tones=False, offset=0)
23
+ await call(client.emoji.browse, category="People & Body", include_skin_tones=False, offset=2)
24
+ await call(client.emoji.categories)
25
+ assert await call(client.emoji.parse, "x 👍🏿 👍🏿", deep=True, lang="fr") == payload
26
+ assert calls[0].url.path == "/emoji/:cat:"
27
+ assert dict(calls[0].url.params) == {"shortcode_source": "github"}
28
+ assert dict(calls[1].url.params) == {"q": "face", "category": "Smileys & Emotion", "version": "17.0", "max_version": "18.0", "include_skin_tones": "false", "offset": "0"}
29
+ assert dict(calls[2].url.params) == {"category": "People & Body", "include_skin_tones": "false", "offset": "2"}
30
+ assert calls[3].url.path == "/emoji/categories" and not calls[3].url.params
31
+ assert calls[4].url.path == "/emoji/parse" and calls[4].method == "POST"
32
+ assert json.loads(calls[4].content) == {"text": "x 👍🏿 👍🏿"}
33
+ assert dict(calls[4].url.params) == {"deep": "true", "lang": "fr"}
34
+ assert calls[4].headers["Parse-Version"] == "2.0.0"
35
+ if async_mode:
36
+ await client.close()
37
+ else:
38
+ client.close()
39
+ asyncio.run(run(False))
40
+ asyncio.run(run(True))
41
+
42
+ def test_sync_async_emoji_download_and_metadata():
43
+ from pathlib import Path
44
+ bundle = json.loads((Path(__file__).parent / 'fixtures/emoji-catalog.json').read_text())
45
+ async def run(async_mode):
46
+ calls = []
47
+ def handle(request):
48
+ calls.append(request)
49
+ return httpx.Response(200, json=bundle['metadata'] if request.url.path == '/emoji/metadata' else bundle)
50
+ client = (AsyncParseAPI if async_mode else ParseAPI)('test', transport=httpx.MockTransport(handle))
51
+ async def call(method, **kwargs):
52
+ value = method(**kwargs)
53
+ return await value if async_mode else value
54
+ result = await call(client.emoji.catalog, deep=False, include_skin_tones=False, lang='fr', max_version='17.0')
55
+ assert result['forms']['❤'] == '❤️'
56
+ assert result['emojis'][0]['deep']['artwork'] == {}
57
+ assert dict(calls[0].url.params) == {'deep':'false','include_skin_tones':'false','lang':'fr','max_version':'17.0'}
58
+ await call(client.emoji.catalog)
59
+ assert dict(calls[1].url.params) == {'deep':'true'}
60
+ assert (await call(client.emoji.metadata))['total'] == 1
61
+ assert calls[2].url.path == '/emoji/metadata' and not calls[2].url.params
62
+ if async_mode: await client.close()
63
+ else: client.close()
64
+ asyncio.run(run(False))
65
+ asyncio.run(run(True))
66
+
67
+
68
+
69
+ def test_sync_async_private_conversion():
70
+ async def run(async_mode):
71
+ calls=[]
72
+ def handle(request):
73
+ calls.append(request)
74
+ return httpx.Response(200,json={"text":"🚀","unresolved":[]})
75
+ client=(AsyncParseAPI if async_mode else ParseAPI)('test',transport=httpx.MockTransport(handle))
76
+ result=client.emoji.convert(':rocket:',direction='to_emoji',shortcode_source='github')
77
+ if async_mode: result=await result
78
+ assert result['text']=='🚀'
79
+ assert len(calls)==1 and calls[0].method=='POST'
80
+ assert json.loads(calls[0].content)=={'text':':rocket:'}
81
+ assert dict(calls[0].url.params)=={'direction':'to_emoji','shortcode_source':'github'}
82
+ if async_mode: await client.close()
83
+ else: client.close()
84
+ asyncio.run(run(False));asyncio.run(run(True))
85
+
86
+
87
+ def test_actual_keyed_artwork_sync_and_async():
88
+ from pathlib import Path
89
+ fixture = json.loads((Path(__file__).parent / 'fixtures/emoji-artwork.json').read_text())
90
+ async def run():
91
+ for mode in (False, True):
92
+ cls = AsyncParseAPI if mode else ParseAPI
93
+ client = cls('test', transport=httpx.MockTransport(lambda request: httpx.Response(200, json=fixture['emoji'])))
94
+ result = await client.emoji('rocket', deep=True) if mode else client.emoji('rocket', deep=True)
95
+ artwork = result['deep']['artwork']
96
+ assert list(artwork) == ['noto', 'twemoji', 'openmoji']
97
+ assert artwork['openmoji']['svg'] == 'https://cdn.parseapi.com/emoji/openmoji/17.0.0/1f680.svg'
98
+ assert artwork['openmoji']['png']['72'] == 'https://cdn.parseapi.com/emoji/openmoji/17.0.0/72/1f680.png'
99
+ if mode: await client.close()
100
+ else: client.close()
101
+ asyncio.run(run())
@@ -0,0 +1,18 @@
1
+ import asyncio
2
+ import json
3
+ from pathlib import Path
4
+
5
+ import httpx
6
+ from parseapi import AsyncParseAPI, ParseAPI
7
+
8
+ CASES = json.loads((Path(__file__).parent / 'fixtures/measure-response.json').read_text())
9
+
10
+ def test_clean_and_legacy_measure_responses_sync_and_async():
11
+ for body in CASES:
12
+ transport = httpx.MockTransport(lambda request: httpx.Response(200, json=body))
13
+ with ParseAPI('fixture', transport=transport) as client:
14
+ assert client.measure(body['measure']) == body
15
+ async def check():
16
+ async with AsyncParseAPI('fixture', transport=transport) as client:
17
+ assert await client.measure(body['measure']) == body
18
+ asyncio.run(check())
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