text: import zeep
  
  from lxml import etree
  from xmljson import badgerfish, XMLData
  from urllib.parse import urlparse
  
  from collections import OrderedDict
  
  
  class UnescapingPlugin(zeep.Plugin):
      def egress(self, envelope, http_headers, operation, binding_options):
          xml_string = etree.tostring(envelope).decode()
          xml_string = xml_string.replace('&amp;', '&')
          new_envelope = etree.fromstring(xml_string)
          return new_envelope, http_headers
  
  
  class StudentVue:
      """The StudentVue API class"""
  
      def __init__(self,
                   username: str, password: str, district_domain: str,
                   xmljson_serializer: XMLData = badgerfish,
                   zeep_transport: zeep.Transport = None,
                   zeep_settings: zeep.Settings = None,
                   debug: bool = False):
          """
          :param username: student's username
          :type username: str
          :param password: student's password
          :type password: str
          :param district_domain: domain that the  school district hosts StudentVue on
          :type district_domain: str
          :param xmljson_serializer: (optional) serializer used to serialize xml to json
          :type xmljson_serializer: xmljson.XMLData
          :param zeep_transport: (optional) custom zeep client transport
          :type zeep_transport: zeep.Transport
          :param zeep_settings: (optional) custom zeep client settings
          :type zeep_settings: zeep.Settings
          :param debug: if set to true, turns on debug logging from zeep
          :type debug: bool
          """
          if debug:
              self._setup_debug()
          else:
              self._suppress_warnings()
  
          self._username = username
          self._password = password
  
          parse_result = urlparse(district_domain)
          if parse_result.scheme:
              self.district_domain = parse_result.netloc
          else:
              self.district_domain = parse_result.path
              if self.district_domain[len(self.district_domain) - 1] == '/':
                  self.district_domain = self.district_domain[:-1]
  
          self.xmljson_serializer = xmljson_serializer
          self.client = zeep.Client(
              'https://{0}/Service/PXPCommunication.asmx?WSDL'.format(self.district_domain),
              plugins=[UnescapingPlugin()],
              transport=zeep_transport,
              settings=zeep_settings
          )
  
      def get_messages(self) -> OrderedDict:
          """
          :return: student's messages
          :rtype: OrderedDict
          """
          return self._xml_json_serialize(self._make_service_request('GetPXPMessages'))
  
      def get_calendar(self) -> OrderedDict:
          """
          :return: student's assignments / events calendar
          :rtype: OrderedDict
          """
          return self._xml_json_serialize(self._make_service_request('StudentCalendar'))
  
      def get_attendance(self) -> OrderedDict:
          """
          :return: student's attendance
          :rtype: OrderedDict
          """
          return self._xml_json_serialize(self._make_service_request('Attendance'))
  
      def get_gradebook(self, report_period: int = None) -> OrderedDict:
          """
          :param report_period: (optional) report period to fetch gradebook for
          :type report_period: int
          :return: student's gradebook for the specified report period
          :rtype: OrderedDict
          """
          params = {}
  
          if report_period is not None:
              params['ReportPeriod'] = report_period
  
          return self._xml_json_serialize(self._make_service_request('Gradebook', **params))
  
      def get_class_notes(self) -> OrderedDict:
          """
          :return: student's class notes
          :rtype: OrderedDict
          """
          return self._xml_json_serialize(self._make_service_request('StudentHWNotes'))
  
      def get_student_info(self) -> OrderedDict:
          """
          :return: student's information
          :rtype: OrderedDict
          """
          return self._xml_json_serialize(self._make_service_request('StudentInfo'))
  
      def get_schedule(self, term_index: int = None) -> OrderedDict:
          """
          :param term_index: (optional) term index to fetch schedule for
          :type term_index: int
          :return: student's schedule for the specified term
          :rtype: OrderedDict
          """
          params = {}
  
          if term_index is not None:
              params['TermIndex'] = term_index
  
          return self._xml_json_serialize(self._make_service_request('StudentClassList', **params))
  
      def get_school_info(self) -> OrderedDict:
          """
          :return: student's school information
          :rtype: OrderedDict
          """
          return self._xml_json_serialize(self._make_service_request('StudentSchoolInfo'))
  
      def list_report_cards(self) -> OrderedDict:
          """
          :return: list of student's report cards
          :rtype: OrderedDict
          """
          return self._xml_json_serialize(self._make_service_request('GetReportCardInitialData'))
  
      def get_report_card(self, document_guid: str) -> OrderedDict:
          """
          :param document_guid: id of the report card to fetch, found using :func:`~studentvue.StudentVue.list_report_cards`
          :type document_guid: str
          :return: content of the specified report card
          :rtype: OrderedDict
          """
          return self._xml_json_serialize(self._make_service_request('GetReportCardDocumentData', DocumentGU=document_guid))
  
      def list_documents(self) -> OrderedDict:
          """
          :return: list of student's documents
          :rtype: OrderedDict
          """
          return self._xml_json_serialize(self._make_service_request('GetStudentDocumentInitialData'))
  
      def get_document(self, document_guid: str) -> OrderedDict:
          """
          :param document_guid: id of the document to fetch, found using :func:`~studentvue.StudentVue.list_documents`
          :type document_guid: str
          :return: content of the specified document
          :rtype: OrderedDict
          """
          return self._xml_json_serialize(self._make_service_request('GetContentOfAttachedDoc', DocumentGU=document_guid))
  
      @staticmethod
      def _suppress_warnings():
          import logging
          logging.getLogger('zeep').setLevel(logging.ERROR)
  
      @staticmethod
      def _setup_debug():
          import logging.config
  
          logging.config.dictConfig({
              'version': 1,
              'formatters': {
                  'verbose': {
                      'format': '%(name)s: %(message)s'
                  }
              },
              'handlers': {
                  'console': {
                      'level': 'DEBUG',
                      'class': 'logging.StreamHandler',
                      'formatter': 'verbose',
                  },
              },
              'loggers': {
                  'zeep.transports': {
                      'level': 'DEBUG',
                      'propagate': True,
                      'handlers': ['console'],
                  },
              }
          })
  
      def _make_service_request(self, method_name, **kwargs) -> str:
          param_str = '&lt;Parms&gt;'
          for key, value in kwargs.items():
              param_str += '&lt;' + key + '&gt;'
              param_str += str(value)
              param_str += '&lt;/' + key + '&gt;'
          param_str += '&lt;/Parms&gt;'
  
          return self.client.service.ProcessWebServiceRequest(
              userID=self._username,
              password=self._password,
              skipLoginLog=1,
              parent=0,
              webServiceHandleName='PXPWebServices',
              methodName=method_name,
              paramStr=param_str
          )
  
      def _xml_json_serialize(self, xml_string: str) -> OrderedDict:
          return self.xmljson_serializer.data(etree.fromstring(xml_string))
  