text: """Source implementation for a spotify User"""
  
  import functools
  from functools import partial
  from base64 import b64encode
  from typing import (
      Optional,
      Dict,
      Union,
      List,
      Type,
      TypeVar,
      TYPE_CHECKING,
  )
  
  from ..utils import to_id
  from ..http import HTTPUserClient
  from . import (
      AsyncIterable,
      URIBase,
      Image,
      Device,
      Context,
      Player,
      Playlist,
      Track,
      Artist,
      Library,
  )
  
  if TYPE_CHECKING:
      import spotify
  
  T = TypeVar("T", Artist, Track)  # pylint: disable=invalid-name
  
  
  def ensure_http(func):
      func.__ensure_http__ = True
      return func
  
  
  class WrappedUser:
      def __init__(self, coro):
          self.coro = coro
          self.user = None
  
      async def __await__(self):
          return await self.coro
  
      async def __aenter__(self):
          if self.user is None:
              self.user = user = await self.coro
  
          return await user.__aenter__()
  
      async def __aexit__(self, exc_type, exc_value, traceback):
          await self.user.__aexit__(exc_type, exc_value, traceback)
  
  
  class User(URIBase, AsyncIterable):  # pylint: disable=too-many-instance-attributes
      """A Spotify User.
  
      Attributes
      ----------
      id : :class:`str`
          The Spotify user ID for the user.
      uri : :class:`str`
          The Spotify URI for the user.
      url : :class:`str`
          The open.spotify URL.
      href : :class:`str`
          A link to the Web API endpoint for this user.
      display_name : :class:`str`
          The name displayed on the user’s profile.
          `None` if not available.
      followers : :class:`int`
          The total number of followers.
      images : List[:class:`Image`]
          The user’s profile image.
      email : :class:`str`
          The user’s email address, as entered by the user when creating their account.
      country : :class:`str`
          The country of the user, as set in the user’s account profile. An ISO 3166-1 alpha-2 country code.
      birthdate : :class:`str`
          The user’s date-of-birth.
      product : :class:`str`
          The user’s Spotify subscription level: “premium”, “free”, etc.
          (The subscription level “open” can be considered the same as “free”.)
      """
  
      def __init__(self, client: "spotify.Client", data: dict, **kwargs):
          self.__client = self.client = client
  
          if "http" not in kwargs:
              self.library = None
              self.http = client.http
          else:
              self.http = kwargs.pop("http")
              self.library = Library(client, self)
  
          # Public user object attributes
          self.id = data.pop("id")  # pylint: disable=invalid-name
          self.uri = data.pop("uri")
          self.url = data.pop("external_urls").get("spotify", None)
          self.display_name = data.pop("display_name", None)
          self.href = data.pop("href")
          self.followers = data.pop("followers", {}).get("total", None)
          self.images = list(Image(**image) for image in data.pop("images", []))
  
          # Private user object attributes
          self.email = data.pop("email", None)
          self.country = data.pop("country", None)
          self.birthdate = data.pop("birthdate", None)
          self.product = data.pop("product", None)
  
          # AsyncIterable attrs
          self.__aiter_klass__ = Playlist
          self.__aiter_fetch__ = partial(
              self.__client.http.get_playlists, self.id, limit=50
          )
  
      def __repr__(self):
          return f"<spotify.User: {(self.display_name or self.id)!r}>"
  
      def __getattr__(self, attr):
          value = object.__getattribute__(self, attr)
  
          if (
              hasattr(value, "__ensure_http__")
              and getattr(self, "http", None) is not None
          ):
  
              @functools.wraps(value)
              def _raise(*args, **kwargs):
                  raise AttributeError(
                      "User has not HTTP presence to perform API requests."
                  )
  
              return _raise
          return value
  
      async def __aenter__(self) -> "User":
          return self
  
      async def __aexit__(self, _, __, ___):
          await self.http.close()
  
      # Internals
  
      async def _get_top(self, klass: Type[T], kwargs: dict) -> List[T]:
          target = {Artist: "artists", Track: "tracks"}[klass]
          data = {
              key: value
              for key, value in kwargs.items()
              if key in ("limit", "offset", "time_range")
          }
  
          resp = await self.http.top_artists_or_tracks(target, **data)  # type: ignore
  
          return [klass(self.__client, item) for item in resp["items"]]
  
      ### Alternate constructors
  
      @classmethod
      async def from_code(
          cls, client: "spotify.Client", code: str, *, redirect_uri: str,
      ):
          """Create a :class:`User` object from an authorization code.
  
          Parameters
          ----------
          client : :class:`spotify.Client`
              The spotify client to associate the user with.
          code : :class:`str`
              The authorization code to use to further authenticate the user.
          redirect_uri : :class:`str`
              The rediriect URI to use in tandem with the authorization code.
          """
          route = ("POST", "https://accounts.spotify.com/api/token")
          payload = {
              "redirect_uri": redirect_uri,
              "grant_type": "authorization_code",
              "code": code,
          }
  
          client_id = client.http.client_id
          client_secret = client.http.client_secret
  
          headers = {
              "Authorization": f"Basic {b64encode(':'.join((client_id, client_secret)).encode()).decode()}",
              "Content-Type": "application/x-www-form-urlencoded",
          }
  
          raw = await client.http.request(route, headers=headers, params=payload)
  
          token = raw["access_token"]
          refresh_token = raw["refresh_token"]
  
          return await cls.from_token(client, token, refresh_token)
  
      @classmethod
      def from_token(
          cls,
          client: "spotify.Client",
          token: Optional[str],
          refresh_token: Optional[str] = None,
      ) -> WrappedUser:
          """Create a :class:`User` object from an access token.
  
          Parameters
          ----------
          client : :class:`spotify.Client`
              The spotify client to associate the user with.
          token : :class:`str`
              The access token to use for http requests.
          refresh_token : :class:`str`
              Used to acquire new token when it expires.
          """
          client_id = client.http.client_id
          client_secret = client.http.client_secret
  
          async def constructor() -> "User":
              http = HTTPUserClient(client_id, client_secret, token, refresh_token)
              data = await http.current_user()
              return cls(client, data=data, http=http)
  
          return WrappedUser(coro=constructor())
  
      @classmethod
      async def from_refresh_token(cls, client: "spotify.Client", refresh_token: str):
          """Create a :class:`User` object from a refresh token.
          It will poll the spotify API for a new access token and
          use that to initialize the spotify user.
  
          Parameters
          ----------
          client : :class:`spotify.Client`
              The spotify client to associate the user with.
          refresh_token: str
              Used to acquire token.
          """
          return await cls.from_token(client, None, refresh_token)
  
      ### Contextual methods
  
      @ensure_http
      async def currently_playing(self) -> Dict[str, Union[Track, Context, str]]:
          """Get the users currently playing track.
  
          Returns
          -------
          context, track : Dict[str, Union[Track, Context, str]]
              A tuple of the context and track.
          """
          data = await self.http.currently_playing()  # type: ignore
  
          if "item" in data:
              context = data.pop("context", None)
  
              if context is not None:
                  data["context"] = Context(context)
              else:
                  data["context"] = None
  
              data["item"] = Track(self.__client, data.get("item", {}) or {})
  
          return data
  
      @ensure_http
      async def get_player(self) -> Player:
          """Get information about the users current playback.
  
          Returns
          -------
          player : :class:`Player`
              A player object representing the current playback.
          """
          player = Player(self.__client, self, await self.http.current_player())  # type: ignore
          return player
  
      @ensure_http
      async def get_devices(self) -> List[Device]:
          """Get information about the users avaliable devices.
  
          Returns
          -------
          devices : List[:class:`Device`]
              The devices the user has available.
          """
          data = await self.http.available_devices()  # type: ignore
          return [Device(item) for item in data["devices"]]
  
      @ensure_http
      async def recently_played(
          self,
          *,
          limit: int = 20,
          before: Optional[str] = None,
          after: Optional[str] = None,
      ) -> List[Dict[str, Union[Track, Context, str]]]:
          """Get tracks from the current users recently played tracks.
  
          Returns
          -------
          playlist_history : List[Dict[:class:`str`, Union[Track, Context, :class:`str`]]]
              A list of playlist history object.
              Each object is a dict with a timestamp, track and context field.
          """
          data = await self.http.recently_played(limit=limit, before=before, after=after)  # type: ignore
          client = self.__client
  
          # List[T] where T: {'track': Track, 'content': Context: 'timestamp': ISO8601}
          return [
              {
                  "played_at": track.get("played_at"),
                  "context": Context(track.get("context", {}) or {}),
                  "track": Track(client, track.get("track", {}) or {}),
              }
              for track in data["items"]
          ]
  
      ### Playlist track methods
  
      @ensure_http
      async def add_tracks(self, playlist: Union[str, Playlist], *tracks) -> str:
          """Add one or more tracks to a user’s playlist.
  
          Parameters
          ----------
          playlist : Union[:class:`str`, Playlist]
              The playlist to modify
          tracks : Sequence[Union[:class:`str`, Track]]
              Tracks to add to the playlist
  
          Returns
          -------
          snapshot_id : :class:`str`
              The snapshot id of the playlist.
          """
          data = await self.http.add_playlist_tracks(  # type: ignore
              to_id(str(playlist)), tracks=[str(track) for track in tracks]
          )
          return data["snapshot_id"]
  
      @ensure_http
      async def replace_tracks(self, playlist, *tracks) -> None:
          """Replace all the tracks in a playlist, overwriting its existing tracks.
  
          This powerful request can be useful for replacing tracks, re-ordering existing tracks, or clearing the playlist.
  
          Parameters
          ----------
          playlist : Union[:class:`str`, PLaylist]
              The playlist to modify
          tracks : Sequence[Union[:class:`str`, Track]]
              Tracks to place in the playlist
          """
          await self.http.replace_playlist_tracks(  # type: ignore
              to_id(str(playlist)), tracks=",".join(str(track) for track in tracks)
          )
  
      @ensure_http
      async def remove_tracks(self, playlist, *tracks):
          """Remove one or more tracks from a user’s playlist.
  
          Parameters
          ----------
          playlist : Union[:class:`str`, Playlist]
              The playlist to modify
          tracks : Sequence[Union[:class:`str`, Track]]
              Tracks to remove from the playlist
  
          Returns
          -------
          snapshot_id : :class:`str`
              The snapshot id of the playlist.
          """
          data = await self.http.remove_playlist_tracks(  # type: ignore
              to_id(str(playlist)), tracks=(str(track) for track in tracks)
          )
          return data["snapshot_id"]
  
      @ensure_http
      async def reorder_tracks(
          self, playlist, start, insert_before, length=1, *, snapshot_id=None
      ):
          """Reorder a track or a group of tracks in a playlist.
  
          Parameters
          ----------
          playlist : Union[:class:`str`, Playlist]
              The playlist to modify
          start : int
              The position of the first track to be reordered.
          insert_before : int
              The position where the tracks should be inserted.
          length : Optional[int]
              The amount of tracks to be reordered. Defaults to 1 if not set.
          snapshot_id : :class:`str`
              The playlist’s snapshot ID against which you want to make the changes.
  
          Returns
          -------
          snapshot_id : :class:`str`
              The snapshot id of the playlist.
          """
          data = await self.http.reorder_playlists_tracks(  # type: ignore
              to_id(str(playlist)), start, length, insert_before, snapshot_id=snapshot_id
          )
          return data["snapshot_id"]
  
      ### Playlist methods
  
      @ensure_http
      async def edit_playlist(
          self, playlist, *, name=None, public=None, collaborative=None, description=None
      ):
          """Change a playlist’s name and public/private, collaborative state and description.
  
          Parameters
          ----------
          playlist : Union[:class:`str`, Playlist]
              The playlist to modify
          name : Optional[:class:`str`]
              The new name of the playlist.
          public : Optional[bool]
              The public/private status of the playlist.
              `True` for public, `False` for private.
          collaborative : Optional[bool]
              If `True`, the playlist will become collaborative and other users will be able to modify the playlist.
          description : Optional[:class:`str`]
              The new playlist description
          """
  
          kwargs = {
              "name": name,
              "public": public,
              "collaborative": collaborative,
              "description": description,
          }
  
          await self.http.change_playlist_details(to_id(str(playlist)), **kwargs)  # type: ignore
  
      @ensure_http
      async def create_playlist(
          self, name, *, public=True, collaborative=False, description=None
      ):
          """Create a playlist for a Spotify user.
  
          Parameters
          ----------
          name : :class:`str`
              The name of the playlist.
          public : Optional[bool]
              The public/private status of the playlist.
              `True` for public, `False` for private.
          collaborative : Optional[bool]
              If `True`, the playlist will become collaborative and other users will be able to modify the playlist.
          description : Optional[:class:`str`]
              The playlist description
  
          Returns
          -------
          playlist : :class:`Playlist`
              The playlist that was created.
          """
          data = {"name": name, "public": public, "collaborative": collaborative}
  
          if description:
              data["description"] = description
  
          playlist_data = await self.http.create_playlist(self.id, **data)  # type: ignore
          return Playlist(self.__client, playlist_data, http=self.http)
  
      @ensure_http
      async def follow_playlist(
          self, playlist: Union[str, Playlist], *, public: bool = True
      ) -> None:
          """follow a playlist
  
          Parameters
          ----------
          playlist : Union[:class:`str`, Playlist]
              The playlist to modify
          public : Optional[bool]
              The public/private status of the playlist.
              `True` for public, `False` for private.
          """
          await self.http.follow_playlist(to_id(str(playlist)), public=public)  # type: ignore
  
      @ensure_http
      async def get_playlists(
          self, *, limit: int = 20, offset: int = 0
      ) -> List[Playlist]:
          """get the users playlists from spotify.
  
          Parameters
          ----------
           limit : Optional[int]
               The limit on how many playlists to retrieve for this user (default is 20).
           offset : Optional[int]
               The offset from where the api should start from in the playlists.
  
          Returns
          -------
          playlists : List[Playlist]
              A list of the users playlists.
          """
          data = await self.http.get_playlists(self.id, limit=limit, offset=offset)  # type: ignore
  
          return [
              Playlist(self.__client, playlist_data, http=self.http)
              for playlist_data in data["items"]
          ]
  
      @ensure_http
      async def get_all_playlists(self) -> List[Playlist]:
          """Get all of the users playlists from spotify.
  
          Returns
          -------
          playlists : List[:class:`Playlist`]
              A list of the users playlists.
          """
          playlists: List[Playlist] = []
          total = None
          offset = 0
  
          while True:
              data = await self.http.get_playlists(self.id, limit=50, offset=offset)  # type: ignore
  
              if total is None:
                  total = data["total"]
  
              offset += 50
              playlists += [
                  Playlist(self.__client, playlist_data, http=self.http)
                  for playlist_data in data["items"]
              ]
  
              if len(playlists) >= total:
                  break
  
          return playlists
  
      @ensure_http
      async def top_artists(self, **data) -> List[Artist]:
          """Get the current user’s top artists based on calculated affinity.
  
          Parameters
          ----------
          limit : Optional[int]
              The number of entities to return. Default: 20. Minimum: 1. Maximum: 50.
          offset : Optional[int]
              The index of the first entity to return. Default: 0
          time_range : Optional[:class:`str`]
              Over what time frame the affinities are computed. (long_term, short_term, medium_term)
  
          Returns
          -------
          tracks : List[Artist]
              The top artists for the user.
          """
          return await self._get_top(Artist, data)
  
      @ensure_http
      async def top_tracks(self, **data) -> List[Track]:
          """Get the current user’s top tracks based on calculated affinity.
  
          Parameters
          ----------
          limit : Optional[int]
              The number of entities to return. Default: 20. Minimum: 1. Maximum: 50.
          offset : Optional[int]
              The index of the first entity to return. Default: 0
          time_range : Optional[:class:`str`]
              Over what time frame the affinities are computed. (long_term, short_term, medium_term)
  
          Returns
          -------
          tracks : List[Track]
              The top tracks for the user.
          """
          return await self._get_top(Track, data)
  