text: from abc import ABCMeta, abstractmethod
  try:
      from flask import (
          Flask as flask_app,
          has_request_context as flask_has_request_context,
          session as flask_session,
          request as flask_request,
          redirect as flask_redirect,
          g as flask_g,
          url_for as flask_url_for
          )
      from .flask_blueprint import FlaskAADEndpoints # this is where our auth-related endpoints are defined
  except:
      pass
  
  from .context import IdentityContextData
  from typing import Any
  from functools import wraps
  
  # decorator to make sure access within request context
  def require_request_context(f):
      @wraps(f)
      def assert_context(self, *args, **kwargs):
          if not flask_has_request_context():
              self.logger.info(f"{self.__class__.__name__}.{f.__name__}: No request context, aborting")
          else:
              return f(self, *args, **kwargs)
      return assert_context
  
  class IdentityWebContextAdapter(metaclass=ABCMeta):
      """Context Adapter abstract base class. Extend this to enable IdentityWebPython to
      work within any environment (e.g. Flask, Django, Windows Desktop app, etc) """
      @abstractmethod
      def __init__(self) -> None:
          pass
      
      @abstractmethod
      def _on_request_init(self) -> None: 
          pass
  
      @abstractmethod
      def _on_request_end(self) -> None:
          pass
  
      # TODO: make this dictionary key name configurable on app init
      @abstractmethod
      def attach_identity_web_util(self, identity_web: 'IdentityWebPython') -> None:
          pass
  
      @abstractmethod # @property
      def has_context(self) -> bool:
          pass
      
      @abstractmethod # @property
      @require_request_context
      def identity_context_data(self) -> 'IdentityContextData':
          pass
      
  
      @abstractmethod # @property
      @require_request_context
      def session(self) -> None:
          # TODO: set session attr so can concrete implement here?
          pass
  
      @abstractmethod
      @require_request_context
      def clear_session(self) -> None:
          # TODO: clear ONLY msidweb session stuff
          # TODO: concrete implement here instead?
          pass
  
      @require_request_context
      def get_value_from_session(self, key: str, default: Any = None) -> Any:
          return self.session.get(key, default)
  
      
      @require_request_context
      def get_request_param(self, key: str, default: Any = None) -> Any:
          return self._get_request_params_as_dict(key, default)
  
      @abstractmethod
      @require_request_context
      def redirect_to_absolute_url(self, absolute_url: str) -> None:
          #TODO: set attr redirect on init, so concrete method can be used for all frmwrks
          pass
  
      @abstractmethod
      @require_request_context
      def get_request_params_as_dict(self) -> dict:
          pass
  
      @abstractmethod
      @require_request_context
      def _deserialize_identity_context_data_from_session(self) -> 'IdentityContextData':
          pass
  
      @abstractmethod
      @require_request_context
      def _serialize_identity_context_data_to_session(self) -> None:
          pass
  
  class FlaskContextAdapter(IdentityWebContextAdapter):
      """Context Adapter to enable IdentityWebPython to work within the Flask environment"""
      def __init__(self, app) -> None:
          assert isinstance(app, flask_app)
          super().__init__()
          self.app = app
          with self.app.app_context():
              self.logger = app.logger
              app.before_request(self._on_request_init)
              app.after_request(self._on_request_end)
  
      @property
      @require_request_context
      def identity_context_data(self) -> 'IdentityContextData':
          # TODO: make the key name configurable
          self.logger.debug("Getting identity_context from g")
          identity_context_data = flask_g.get(IdentityContextData.SESSION_KEY)
          if not identity_context_data:
              identity_context_data = self._deserialize_identity_context_data_from_session()
              setattr(flask_g, IdentityContextData.SESSION_KEY, identity_context_data)
          return identity_context_data
  
      # method is called when flask gets an app/request context
      # Flask-specific startup here?    
      def _on_request_init(self) -> None:
          try:
              idx = self.identity_context_data # initialize it so it is available to request context
          except Exception as ex:
              self.logger.error(f'Adapter failed @ _on_request_init\n{ex}')
  
      # this is for saving any changes to the identity_context_data
      def _on_request_end(self, response_to_return=None) -> None:
          try:
              if IdentityContextData.SESSION_KEY in flask_g:
                  self._serialize_identity_context_data_to_session()
          except Exception as ex:
              self.logger.error(f'flask adapter failed @ _on_request_ended\n{ex}')
  
          return response_to_return
  
      # TODO: order is reveresed? create id web first, then attach flask adapter to it!?
      def attach_identity_web_util(self, identity_web: 'IdentityWebPython') -> None:
          """attach the identity web instance to session so it is accessible everywhere.
          e.g., ms_id_web = current_app.config.get("ms_identity_web")\n
          Also attaches the application logger."""
          aad_config = identity_web.aad_config
          config_key = aad_config.flask.id_web_configs
  
          with self.app.app_context():
              self.app.config[config_key] = aad_config
  
          identity_web.set_logger(self.logger)
          auth_endpoints = FlaskAADEndpoints(identity_web)
          self.app.context_processor(lambda: dict(ms_id_url_for=auth_endpoints.url_for))
          self.app.register_blueprint(auth_endpoints)        
  
      @property
      def has_context(self) -> bool:
          return flask_has_request_context()
  
      ### not sure if this method needs to be public yet :/
      @property
      @require_request_context
      def session(self) -> None:
          return flask_session
  
      # TODO: only clear IdWebPy vars
      @require_request_context
      def clear_session(self) -> None:
          """this function clears the session and refreshes context. TODO: only clear IdWebPy vars"""
          self.identity_context_data.clear()
  
      @require_request_context
      def redirect_to_absolute_url(self, absolute_url: str) -> None:
          """this function redirects to an absolute url"""
          return flask_redirect(absolute_url)
  
      @require_request_context
      def get_request_params_as_dict(self) -> dict:
          """this function returns the params dict from any flask request"""
          try:
              # this is query and form-post params merged,
              # preferring query param if there is a key collision
              return flask_request.values
          except:
              if self.logger is not None:
                  self.logger.warning("failed to get param dict from request, substituting empty dict instead")
              return dict()
  
      # does this need to be public method?
      @require_request_context
      def _deserialize_identity_context_data_from_session(self) -> 'IdentityContextData':
          new_id_context_data = IdentityContextData()
          try:
              id_context_from_session = self.session.get(IdentityContextData.SESSION_KEY, dict())
              new_id_context_data.__dict__.update(id_context_from_session)
          except Exception as exception:
              self.logger.warning(f"failed to deserialize identity context from session: creating empty one\n{exception}")
          return new_id_context_data
  
      # does this need to be public method?
      @require_request_context
      def _serialize_identity_context_data_to_session(self) -> None:
          try:
              identity_context = self.identity_context_data
              if identity_context.has_changed:
                  identity_context.has_changed = False
                  identity_context = identity_context.__dict__
                  self.session[IdentityContextData.SESSION_KEY] = identity_context
          except Exception as exception:
              self.logger.error(f"failed to serialize identity context to session.\n{exception}")
  
      
  
  
  
  
  
  
  # the following class is incomplete
  class DjangoContextAdapter(object):
      """Context Adapter to enable IdentityWebPython to work within the Django environment"""
      def __init__(self):
          raise NotImplementedError("not yet implemented")
  
      # method is called when getting app/request context
      def _on_context_init(self) -> None:
          self._has_context = True
      
      def _on_context_teardown(self, exception) -> None: 
          self._has_context = False
          if self.identity_context_data.has_changed:
              self.identity_context_data._save_to_session()
  
      # this function returns Django's request params.
      @require_request_context
      def get_request_params_as_dict(self, request: 'request' = None) -> dict:
          try:
              if request.method == "GET":
                  return request.GET.dict()
              elif request.method == "POST" :
                  return request.POST.dict()
              else:
                  raise ValueError("Django request must be POST or GET")
          except:
              if self.logger is not None:
                  self.logger.warning("Failed to get param dict, substituting empty dict instead")
              return dict()
  