ossapi 5.0.2__tar.gz → 5.1.0__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 (66) hide show
  1. {ossapi-5.0.2 → ossapi-5.1.0}/PKG-INFO +2 -2
  2. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi/enums.py +5 -0
  3. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi/models.py +61 -11
  4. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi/ossapiv2.py +38 -8
  5. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi/ossapiv2_async.py +27 -4
  6. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi.egg-info/PKG-INFO +2 -2
  7. {ossapi-5.0.2 → ossapi-5.1.0}/pyproject.toml +1 -1
  8. {ossapi-5.0.2 → ossapi-5.1.0}/tests/test_endpoints.py +21 -1
  9. {ossapi-5.0.2 → ossapi-5.1.0}/.gitignore +0 -0
  10. {ossapi-5.0.2 → ossapi-5.1.0}/.nojekyll +0 -0
  11. {ossapi-5.0.2 → ossapi-5.1.0}/LICENSE +0 -0
  12. {ossapi-5.0.2 → ossapi-5.1.0}/Makefile +0 -0
  13. {ossapi-5.0.2 → ossapi-5.1.0}/README.md +0 -0
  14. {ossapi-5.0.2 → ossapi-5.1.0}/docs/_static/custom.css +0 -0
  15. {ossapi-5.0.2 → ossapi-5.1.0}/docs/advanced.rst +0 -0
  16. {ossapi-5.0.2 → ossapi-5.1.0}/docs/api-reference.rst +0 -0
  17. {ossapi-5.0.2 → ossapi-5.1.0}/docs/async.rst +0 -0
  18. {ossapi-5.0.2 → ossapi-5.1.0}/docs/beatmap packs.rst +0 -0
  19. {ossapi-5.0.2 → ossapi-5.1.0}/docs/beatmaps.rst +0 -0
  20. {ossapi-5.0.2 → ossapi-5.1.0}/docs/beatmapsets.rst +0 -0
  21. {ossapi-5.0.2 → ossapi-5.1.0}/docs/changelog.rst +0 -0
  22. {ossapi-5.0.2 → ossapi-5.1.0}/docs/chat.rst +0 -0
  23. {ossapi-5.0.2 → ossapi-5.1.0}/docs/comments.rst +0 -0
  24. {ossapi-5.0.2 → ossapi-5.1.0}/docs/conf.py +0 -0
  25. {ossapi-5.0.2 → ossapi-5.1.0}/docs/creating-a-client.rst +0 -0
  26. {ossapi-5.0.2 → ossapi-5.1.0}/docs/domains.rst +0 -0
  27. {ossapi-5.0.2 → ossapi-5.1.0}/docs/endpoints.rst +0 -0
  28. {ossapi-5.0.2 → ossapi-5.1.0}/docs/events.rst +0 -0
  29. {ossapi-5.0.2 → ossapi-5.1.0}/docs/expandable-models.rst +0 -0
  30. {ossapi-5.0.2 → ossapi-5.1.0}/docs/foreign-keys.rst +0 -0
  31. {ossapi-5.0.2 → ossapi-5.1.0}/docs/forums.rst +0 -0
  32. {ossapi-5.0.2 → ossapi-5.1.0}/docs/friends.rst +0 -0
  33. {ossapi-5.0.2 → ossapi-5.1.0}/docs/generate_docs.py +0 -0
  34. {ossapi-5.0.2 → ossapi-5.1.0}/docs/generate_readme_list.py +0 -0
  35. {ossapi-5.0.2 → ossapi-5.1.0}/docs/grants.rst +0 -0
  36. {ossapi-5.0.2 → ossapi-5.1.0}/docs/home.rst +0 -0
  37. {ossapi-5.0.2 → ossapi-5.1.0}/docs/index.rst +0 -0
  38. {ossapi-5.0.2 → ossapi-5.1.0}/docs/matches.rst +0 -0
  39. {ossapi-5.0.2 → ossapi-5.1.0}/docs/me.rst +0 -0
  40. {ossapi-5.0.2 → ossapi-5.1.0}/docs/news.rst +0 -0
  41. {ossapi-5.0.2 → ossapi-5.1.0}/docs/oauth.rst +0 -0
  42. {ossapi-5.0.2 → ossapi-5.1.0}/docs/pagination.rst +0 -0
  43. {ossapi-5.0.2 → ossapi-5.1.0}/docs/quickstart.rst +0 -0
  44. {ossapi-5.0.2 → ossapi-5.1.0}/docs/rankings.rst +0 -0
  45. {ossapi-5.0.2 → ossapi-5.1.0}/docs/rooms.rst +0 -0
  46. {ossapi-5.0.2 → ossapi-5.1.0}/docs/scores.rst +0 -0
  47. {ossapi-5.0.2 → ossapi-5.1.0}/docs/seasonal backgrounds.rst +0 -0
  48. {ossapi-5.0.2 → ossapi-5.1.0}/docs/serializing-models.rst +0 -0
  49. {ossapi-5.0.2 → ossapi-5.1.0}/docs/spotlights.rst +0 -0
  50. {ossapi-5.0.2 → ossapi-5.1.0}/docs/users.rst +0 -0
  51. {ossapi-5.0.2 → ossapi-5.1.0}/docs/wiki.rst +0 -0
  52. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi/__init__.py +0 -0
  53. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi/encoder.py +0 -0
  54. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi/mod.py +0 -0
  55. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi/ossapi.py +0 -0
  56. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi/replay.py +0 -0
  57. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi/utils.py +0 -0
  58. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi.egg-info/SOURCES.txt +0 -0
  59. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi.egg-info/dependency_links.txt +0 -0
  60. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi.egg-info/requires.txt +0 -0
  61. {ossapi-5.0.2 → ossapi-5.1.0}/ossapi.egg-info/top_level.txt +0 -0
  62. {ossapi-5.0.2 → ossapi-5.1.0}/setup.cfg +0 -0
  63. {ossapi-5.0.2 → ossapi-5.1.0}/tests/__init__.py +0 -0
  64. {ossapi-5.0.2 → ossapi-5.1.0}/tests/test_cursor.py +0 -0
  65. {ossapi-5.0.2 → ossapi-5.1.0}/tests/test_models.py +0 -0
  66. {ossapi-5.0.2 → ossapi-5.1.0}/tests/test_v1.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: ossapi
3
- Version: 5.0.2
3
+ Version: 5.1.0
4
4
  Summary: Complete python wrapper for osu! api v2 and v1.
5
5
  Author-email: Liam DeVoe <orionldevoe@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/tybug/ossapi
@@ -872,3 +872,8 @@ class RoomPlaylistItemStats(Model):
872
872
  class RoomDifficultyRange(Model):
873
873
  min: float
874
874
  max: float
875
+
876
+
877
+ class BeatmapOwner(Model):
878
+ id: int
879
+ username: str
@@ -68,6 +68,7 @@ from ossapi.enums import (
68
68
  BeatmapPackUserCompletionData,
69
69
  RoomPlaylistItemStats,
70
70
  RoomDifficultyRange,
71
+ BeatmapOwner,
71
72
  )
72
73
  from ossapi.utils import Datetime, Model, BaseModel, Field
73
74
 
@@ -292,6 +293,9 @@ class Beatmap(BeatmapCompact):
292
293
  # This is optional as a workaround until
293
294
  # https://github.com/ppy/osu-web/issues/9784 is resolved.
294
295
  owner: Field(name="user", type=Optional[UserCompact])
296
+ # TODO does the new addition of this owners attribute deprecate the owner
297
+ # attribute?
298
+ owners: Optional[List[BeatmapOwner]]
295
299
 
296
300
  # overridden fields
297
301
  # -----------------
@@ -737,7 +741,11 @@ class BeatmapsetDiscussion(Model):
737
741
  current_user_attributes: Any
738
742
  updated_at: Datetime
739
743
  deleted_at: Optional[Datetime]
740
- last_post_at: Datetime
744
+ # marked as required in the docs, but null in
745
+ # api.beatmapset_events(beatmapset_id=1112418)
746
+ # due to this post
747
+ # https://osu.ppy.sh/beatmapsets/1112418/discussion/-/generalAll#/1633002
748
+ last_post_at: Optional[Datetime]
741
749
  kudosu_denied: bool
742
750
  starting_post: Optional[BeatmapsetDiscussionPost]
743
751
  posts: Optional[List[BeatmapsetDiscussionPost]]
@@ -942,7 +950,7 @@ class ChangelogListing(Model):
942
950
 
943
951
  class MultiplayerScores(Model):
944
952
  cursor_string: CursorStringT
945
- params: str
953
+ params: Any
946
954
  scores: List[MultiplayerScore]
947
955
  total: Optional[int]
948
956
  user_score: Optional[MultiplayerScore]
@@ -954,7 +962,7 @@ class MultiplayerScore(Model):
954
962
  room_id: int
955
963
  playlist_item_id: int
956
964
  beatmap_id: int
957
- rank: int
965
+ rank: Grade
958
966
  total_score: int
959
967
  max_combo: int
960
968
  mods: List[Mod]
@@ -963,6 +971,28 @@ class MultiplayerScore(Model):
963
971
  position: Optional[int]
964
972
  scores_around: Optional[MultiplayerScoresAround]
965
973
  user: User
974
+ solo_score_id: int
975
+ classic_total_score: int
976
+ preserve: bool
977
+ processed: bool
978
+ ranked: bool
979
+ maximum_statistics: Statistics
980
+ total_score_without_mods: int
981
+ best_id: Optional[int]
982
+ type: str
983
+ accuracy: float
984
+ build_id: int
985
+ ended_at: Datetime
986
+ is_perfect_combo: bool
987
+ replay: bool
988
+ pp: float
989
+ started_at: Datetime
990
+ ruleset_id: int
991
+ current_user_attributes: Any
992
+ has_replay: bool
993
+ legacy_perfect: bool
994
+ legacy_score_id: int
995
+ legacy_total_score: int
966
996
 
967
997
  def beatmap(self):
968
998
  return self._fk_beatmap(self.beatmap_id)
@@ -1070,6 +1100,12 @@ class BeatmapPack(Model):
1070
1100
  user_completion_data: Optional[BeatmapPackUserCompletionData]
1071
1101
 
1072
1102
 
1103
+ class Scores(Model):
1104
+ cursor: CursorT
1105
+ cursor_string: CursorStringT
1106
+ scores: List[Score]
1107
+
1108
+
1073
1109
  # ================
1074
1110
  # Parameter Models
1075
1111
  # ================
@@ -1145,12 +1181,12 @@ class BeatmapsetEventComment(Model):
1145
1181
 
1146
1182
  class BeatmapsetEventCommentNoPost(Model):
1147
1183
  beatmap_discussion_id: int
1148
- beatmap_discussion_post_id: int
1184
+ beatmap_discussion_post_id: Optional[int]
1149
1185
 
1150
1186
 
1151
1187
  class BeatmapsetEventCommentNone(Model):
1152
- beatmap_discussion_id: int
1153
- beatmap_discussion_post_id: int
1188
+ beatmap_discussion_id: Optional[int]
1189
+ beatmap_discussion_post_id: Optional[int]
1154
1190
 
1155
1191
 
1156
1192
  class BeatmapsetEventCommentChange(Generic[S], BeatmapsetEventCommentNone):
@@ -1176,6 +1212,7 @@ class BeatmapsetEventCommentOwnerChange(BeatmapsetEventCommentNone):
1176
1212
  beatmap_version: str
1177
1213
  new_user_id: int
1178
1214
  new_user_username: str
1215
+ new_users: List[int]
1179
1216
 
1180
1217
 
1181
1218
  class BeatmapsetEventCommentNominate(Model):
@@ -1224,7 +1261,14 @@ class BeatmapsetEvent(Model):
1224
1261
  BeatmapsetEventType.DISCUSSION_RESTORE: BeatmapsetEventCommentNoPost,
1225
1262
  # same here
1226
1263
  # BeatmapsetEventType.DISCUSSION_UNLOCK: BeatmapsetEventComment,
1227
- BeatmapsetEventType.DISQUALIFY: BeatmapsetEventCommentWithNominators,
1264
+ # Some events have a comment that is *just a string*.
1265
+ # api.beatmapset_events(beatmapset_id=724033)
1266
+ # I've only seen this for "type": "disqualify", but who knows where
1267
+ # else it could happen. I've preemptively marked NOMINATION_RESET as
1268
+ # taking a string also.
1269
+ BeatmapsetEventType.DISQUALIFY: Union[
1270
+ BeatmapsetEventCommentWithNominators, str
1271
+ ],
1228
1272
  # same here
1229
1273
  # BeatmapsetEventType.DISQUALIFY_LEGACY: BeatmapsetEventComment
1230
1274
  BeatmapsetEventType.GENRE_EDIT: BeatmapsetEventCommentChange[str],
@@ -1240,7 +1284,9 @@ class BeatmapsetEvent(Model):
1240
1284
  BeatmapsetEventType.NOMINATE: BeatmapsetEventCommentNominate,
1241
1285
  # same here
1242
1286
  # BeatmapsetEventType.NOMINATE_MODES: BeatmapsetEventComment,
1243
- BeatmapsetEventType.NOMINATION_RESET: BeatmapsetEventCommentWithNominators,
1287
+ BeatmapsetEventType.NOMINATION_RESET: Union[
1288
+ BeatmapsetEventCommentWithNominators, str
1289
+ ],
1244
1290
  BeatmapsetEventType.NOMINATION_RESET_RECEIVED: BeatmapsetEventCommentWithSourceUser,
1245
1291
  BeatmapsetEventType.QUALIFY: type(None),
1246
1292
  BeatmapsetEventType.RANK: type(None),
@@ -1248,7 +1294,11 @@ class BeatmapsetEvent(Model):
1248
1294
  BeatmapsetEventType.NSFW_TOGGLE: BeatmapsetEventCommentChange[bool],
1249
1295
  }
1250
1296
  type_ = BeatmapsetEventType(data["type"])
1251
- return {"comment": mapping[type_]}
1297
+ # some events don't seem to have an associate comment, eg
1298
+ # api.beatmapset_events(beatmapset_id=692322)
1299
+ # I don't know under what circumstances this does or does not happen, so
1300
+ # I am marking all comments as optional.
1301
+ return {"comment": Optional[mapping[type_]]}
1252
1302
 
1253
1303
  def user(self) -> Optional[User]:
1254
1304
  return self._fk_user(self.user_id)
@@ -1402,7 +1452,7 @@ class _Room1(Model):
1402
1452
  type: RoomType
1403
1453
  user_id: int
1404
1454
  starts_at: Datetime
1405
- ends_at: Datetime
1455
+ ends_at: Optional[Datetime]
1406
1456
  max_attempts: Optional[int]
1407
1457
  participant_count: int
1408
1458
  channel_id: int
@@ -1422,7 +1472,7 @@ class Room(Model):
1422
1472
  type: RoomType
1423
1473
  user_id: int
1424
1474
  starts_at: Datetime
1425
- ends_at: Datetime
1475
+ ends_at: Optional[Datetime]
1426
1476
  max_attempts: Optional[int]
1427
1477
  participant_count: int
1428
1478
  channel_id: int
@@ -77,6 +77,7 @@ from ossapi.models import (
77
77
  Events,
78
78
  BeatmapPack,
79
79
  BeatmapPacks,
80
+ Scores,
80
81
  )
81
82
  from ossapi.enums import (
82
83
  GameMode,
@@ -898,12 +899,21 @@ class Ossapi:
898
899
  # because if we got here that means we were passed a value for this
899
900
  # attribute, so we know it's defined and not optional.
900
901
  if is_optional(type_):
901
- # leaving these assertions in to help me catch errors in my
902
- # reasoning until I better understand python's typing.
903
- assert len(args) == 2
904
- type_ = args[0]
905
- origin = get_origin(type_)
906
- args = get_args(type_)
902
+ assert len(args) >= 2
903
+ # two args is the standard, for Optional[T] = Union[T, None].
904
+ # but we could also have Union[T, None, V] which passes is_optional
905
+ # but we want to unwrap differently to preserve the Union[T, V].
906
+ if len(args) == 2:
907
+ type_ = args[0]
908
+ origin = get_origin(type_)
909
+ args = get_args(type_)
910
+ else:
911
+ # should we be changing type_ here? it's not technically correct
912
+ # anymore, we'd have to remove None from the union. But I don't
913
+ # know if we rely on type_ instead of args anywhere, and I don't
914
+ # know how to create a new type instance.
915
+ origin = get_origin(type_)
916
+ args = tuple(t for t in get_args(type_) if t is not type(None))
907
917
 
908
918
  # validate that the values we're receiving are the types we expect them
909
919
  # to be
@@ -2423,8 +2433,6 @@ class Ossapi:
2423
2433
  # /rooms
2424
2434
  # ------
2425
2435
 
2426
- # TODO add test for this once I figure out values for room_id and
2427
- # playlist_id that actually produce a response lol
2428
2436
  @request(Scope.PUBLIC, category="rooms")
2429
2437
  def multiplayer_scores(
2430
2438
  self,
@@ -2562,6 +2570,28 @@ class Ossapi:
2562
2570
  """
2563
2571
  return self._get(Score, f"/scores/{score_id}")
2564
2572
 
2573
+ @request(Scope.PUBLIC, category="scores")
2574
+ def scores(
2575
+ self, mode: GameModeT, *, cursor_string: Optional[str] = None
2576
+ ) -> Scores:
2577
+ """
2578
+ Returns most recent 1000 passed scores across all users.
2579
+
2580
+ Parameters
2581
+ ----------
2582
+ mode
2583
+ The mode to get scores for.
2584
+ cursor_string
2585
+ Cursor for pagination.
2586
+
2587
+ Notes
2588
+ -----
2589
+ Implements the `Get Scores
2590
+ <https://osu.ppy.sh/docs/index.html#get-scores94>`__ endpoint.
2591
+ """
2592
+ params = {"mode": mode.value, "cursor_string": cursor_string}
2593
+ return self._get(Scores, "/scores", params)
2594
+
2565
2595
  @request(Scope.PUBLIC, category="scores")
2566
2596
  def score_mode(self, mode: GameModeT, score_id: int) -> Score:
2567
2597
  """
@@ -90,6 +90,7 @@ from ossapi.models import (
90
90
  Events,
91
91
  BeatmapPack,
92
92
  BeatmapPacks,
93
+ Scores,
93
94
  )
94
95
  from ossapi.enums import (
95
96
  GameMode,
@@ -2648,7 +2649,7 @@ class OssapiAsync:
2648
2649
  # -------
2649
2650
 
2650
2651
  @request(Scope.PUBLIC, category="scores")
2651
- def score(self, score_id: int) -> Score:
2652
+ async def score(self, score_id: int) -> Score:
2652
2653
  """
2653
2654
  Get a score. This corresponds to urls of the form https://osu.ppy.sh/scores/1312718771
2654
2655
  ("new id format").
@@ -2665,10 +2666,32 @@ class OssapiAsync:
2665
2666
  Implements the `Get Score
2666
2667
  <https://osu.ppy.sh/docs/index.html#scoresmodescore>`__ endpoint.
2667
2668
  """
2668
- return self._get(Score, f"/scores/{score_id}")
2669
+ return await self._get(Score, f"/scores/{score_id}")
2669
2670
 
2670
2671
  @request(Scope.PUBLIC, category="scores")
2671
- def score_mode(self, mode: GameModeT, score_id: int) -> Score:
2672
+ async def scores(
2673
+ self, mode: GameModeT, *, cursor_string: Optional[str] = None
2674
+ ) -> Scores:
2675
+ """
2676
+ Returns most recent 1000 passed scores across all users.
2677
+
2678
+ Parameters
2679
+ ----------
2680
+ mode
2681
+ The mode to get scores for.
2682
+ cursor_string
2683
+ Cursor for pagination.
2684
+
2685
+ Notes
2686
+ -----
2687
+ Implements the `Get Scores
2688
+ <https://osu.ppy.sh/docs/index.html#get-scores94>`__ endpoint.
2689
+ """
2690
+ params = {"mode": mode.value, "cursor_string": cursor_string}
2691
+ return await self._get(Scores, "/scores", params)
2692
+
2693
+ @request(Scope.PUBLIC, category="scores")
2694
+ async def score_mode(self, mode: GameModeT, score_id: int) -> Score:
2672
2695
  """
2673
2696
  Get a score, where the score id is specific to the gamemode. This
2674
2697
  corresponds to urls of the form https://osu.ppy.sh/scores/osu/4459998279
@@ -2688,7 +2711,7 @@ class OssapiAsync:
2688
2711
  Implements the `Get Score
2689
2712
  <https://osu.ppy.sh/docs/index.html#scoresmodescore>`__ endpoint.
2690
2713
  """
2691
- return self._get(Score, f"/scores/{mode.value}/{score_id}")
2714
+ return await self._get(Score, f"/scores/{mode.value}/{score_id}")
2692
2715
 
2693
2716
  async def _download_score(self, *, url, raw):
2694
2717
  from aiohttp import ClientSession, ContentTypeError
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: ossapi
3
- Version: 5.0.2
3
+ Version: 5.1.0
4
4
  Summary: Complete python wrapper for osu! api v2 and v1.
5
5
  Author-email: Liam DeVoe <orionldevoe@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/tybug/ossapi
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ossapi"
3
- version = "5.0.2"
3
+ version = "5.1.0"
4
4
  description = "Complete python wrapper for osu! api v2 and v1."
5
5
  readme = "README.md"
6
6
  keywords = ["osu!", "wrapper", "api", "python"]
@@ -56,8 +56,13 @@ class TestBeatmapScores(TestCase):
56
56
  class TestBeatmap(TestCase):
57
57
  def test_deserialize(self):
58
58
  api.beatmap(beatmap_id=221777)
59
+ # beatmap with multiple owners (.owners)
60
+ bm = api.beatmap(beatmap_id=4060023)
61
+ # see https://discord.com/channels/188630481301012481/
62
+ # 188630616286167041/1315396179655262239
63
+ assert bm.owner is None
59
64
 
60
- # beatmap with a diff owner
65
+ # beatmap with a diff owner (.owner)
61
66
  bm = api.beatmap(beatmap_id=1604098)
62
67
  # might need to be updated when
63
68
  # https://github.com/ppy/osu-web/issues/9784 is addressed.
@@ -72,6 +77,10 @@ class TestBeatmapset(TestCase):
72
77
  class TestBeatmapsetEvents(TestCase):
73
78
  def test_deserialize(self):
74
79
  api.beatmapset_events()
80
+ api.beatmapset_events(beatmapset_id=1339615)
81
+ api.beatmapset_events(beatmapset_id=692322)
82
+ api.beatmapset_events(beatmapset_id=724033)
83
+ api.beatmapset_events(beatmapset_id=1112418)
75
84
 
76
85
  def test_all_types(self):
77
86
  # beatmapset_events is a really complicated endpoint in terms of return
@@ -310,6 +319,17 @@ class TestBeatmapPack(TestCase):
310
319
  api.beatmap_pack("A1")
311
320
 
312
321
 
322
+ class TestMultiplayerScores(TestCase):
323
+ def test_deserialize(self):
324
+ api.multiplayer_scores(1057998, 11773230)
325
+
326
+
327
+ class TestScores(TestCase):
328
+ def test_deserialize(self):
329
+ scores = api.scores("osu")
330
+ api.scores("osu", cursor_string=scores.cursor_string)
331
+
332
+
313
333
  # ======================
314
334
  # api_full test cases
315
335
  # ======================
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
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
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
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
File without changes
File without changes
File without changes
File without changes
File without changes