class CertificateSearch(object): """ Getting information about certificates issued by Ofgem requires accessing their webform. This class provides a simple way of doing that. Class that queries ofgem for certificate data. If it succeeds then There are 2 generator methods that allow iterating through the returned data, - each call to :func:`stations()` will return a list of :class:`Certificates` objects related to a single station. - each call to :func:`certificates()` will return a single :class:`Certificates` object. .. code:: >>> from pywind.ofgem.CertificateSearch import CertificateSearch >>> ocs = CertificateSearch() >>> ocs.start() True >>> ocs.set_period(201601) True >>> ocs.get_data() True >>> len(ocs) 4898 """ START_URL = 'ReportViewer.aspx?ReportPath=/DatawarehouseReports/' + \ 'CertificatesExternalPublicDataWarehouse&ReportVisibility=1&ReportCategory=2' NSMAP = {'a': 'CertificatesExternalPublicDataWarehouse'} def __init__(self, filename=None): self.has_data = False self.form = None self.certificate_records = [] self.station_records = {} if filename is not None: self.parse_filename(filename) else: self.form = OfgemForm(self.START_URL) def __len__(self): return len(self.certificate_records) def start(self): """ Retrieve the form from Ofgem website so we can start updating it. :returns: True or False :rtype: bool """ if self.form is not None: return self.form.get() return True def set_period(self, yearmonth): """ Set the year and month for certificates. :param yearmonth: Numeric period in YYYYMM format :returns: True or False :rtype: bool """ if not isinstance(yearmonth, int): yearmonth = int(yearmonth) year = int(yearmonth / 100) if self._set_year(year) is False: return False return self._set_month(yearmonth % year) def set_start_month(self, month): """ Set the start month for certificates :param month: Numeric month number :rtype: bool """ return self.form.set_value("output period \"month from\"", MONTHS[month - 1]) def set_finish_month(self, month): """ Set the finish month for certificates :param month: Numeric month number :rtype: bool """ return self.form.set_value("output period \"month to\"", MONTHS[month - 1]) def set_start_year(self, year): """ Set the start year for certificates :param year: Numeric year to be set :rtype: bool """ return self.form.set_value("output period \"year from\"", str(year)) def set_finish_year(self, year): """ Set the finish year for certificates :param year: Numeric year to be set :rtype: bool """ return self.form.set_value("output period \"year to\"", str(year)) def filter_technology(self, what): """ Filter certificates by technology group """ return self.form.set_value('technology group', what) def filter_generation_type(self, what): """ Filter certificates by generation type """ return self.form.set_value('generation type', what) def filter_scheme(self, what): """ Filter certificates by scheme :param what: Scheme abbreviation [REGO, RO] :rtype: bool """ return self.form.set_value('scheme', what.upper()) def filter_generator_id(self, acc_no): """ Filter certificates by generator id (accreditation number). .. note:: Values supplied are upper cased automatically. :param acc_no: Accreditation/Generation number :rtype: bool """ return self.form.set_value('accreditation no', acc_no.upper()) def get_data(self): """ Submit the form, get the results and parse them into :class:`Certificate` objects :rtype: bool """ self.certificate_records = [] self.station_records = {} if not self.form.submit(): return False try: xml = etree.fromstring(self.form.raw_data) except XMLSyntaxError: print("Invalid XML returned from Ofgem server.") return False for node in xml.xpath('.//a:Detail', namespaces=self.NSMAP): cert = Certificates(node) self.certificate_records.append(cert) self.station_records.setdefault(cert.name, []).append(cert) self.has_data = len(self.certificate_records) > 0 return self.has_data def save_original(self, filename): """ Save the downloaded certificate data into the filename provided. :param filename: Filename to save the file to. :rtype: bool """ return self.form.save_original(filename) # Generators to access data def rows(self): """ Generator function that returns a station each time it is called. :returns: A function that returns a dict containing information on one station. :rtype: generator """ for cert in self.certificate_records: yield {'CertificateRecord': cert.as_row()} def certificates(self): """ Generator that returns :class:`Certificates` objects. :returns: Certificates objects :rtype: Certificates """ for cert in self.certificate_records: yield cert def stations(self): """ Generator that returns a Return a list of stations related to the certificates """ for stat in sorted(self.station_records): yield self.station_records[stat] def parse_filename(self, filename): """Parse an Ofgem generated file of certificates. This parses downloaded Ofgem files. :param filename: The filename to be parsed :returns: True or False :rtype: bool """ with open(filename, 'r') as xfh: data = xfh.read() xml = etree.fromstring(data) for node in xml.xpath('.//a:Detail', namespaces=self.NSMAP): cert = Certificates(node) self.certificate_records.append(cert) self.station_records.setdefault(cert.name, []).append(cert) return len(self.certificate_records) > 0 # Internal functions def _set_year(self, year): """ Set both the start and finish year for certificates. :param year: Numeric year to set :rtype: bool """ if self.set_start_year(year) is False: return False return self.set_finish_year(year) def _set_month(self, month): """ Set both the start and finish months for certificates :param month: Numeric month number :returns: True or False :rtype: bool """ if self.set_start_month(month) is False: return False return self.set_finish_month(month)
class StationSearch(object): """ Performing a station search using the Ofgem website takes a while due to the 3.5M initial file and the 2M replies that are sent. Parsing these takes time, so patience is needed. .. code:: >>> from pywind.ofgem.StationSearch import StationSearch >>> oss = StationSearch() >>> oss.start() True >>> oss.filter_name('griffin') True >>> oss.get_data() True >>> len(oss) 4 """ START_URL = 'ReportViewer.aspx?ReportPath=/Renewables/Accreditation/' + \ 'AccreditedStationsExternalPublic&ReportVisibility=1&ReportCategory=1' def __init__(self): self.form = OfgemForm(self.START_URL) self.stations = [] def __len__(self): """ len(...) returns the number of stations available. """ return len(self.stations) def __getitem__(self, item): """ Get a station by name. """ if 0 >= item < len(self.stations): return self.stations[item] def start(self): """ Retrieve the form from Ofgem website so we can start updating it. """ if self.form is not None: self.form.get() def get_data(self): """ Get data from form. :rtype: bool """ if not self.form.submit(): return False parser = XMLParser(huge_tree=True) doc = etree.fromstring(self.form.raw_data, parser=parser) # There are a few stations with multiple generator id's, separated by '\n' so # capture them and add each as a separate entry. for detail in doc.xpath("//*[local-name()='Detail']"): stt = Station(detail) if '\n' in stt.generator_id: ids = [x.strip() for x in stt.generator_id.split('\n')] stt.generator_id = ids[0] for _id in ids[1:]: _st = copy.copy(stt) _st.generator_id = _id self.stations.append(_st) self.stations.append(stt) return len(self.stations) > 0 def filter_technology(self, what): """ Filter stations based on technology. :rtype: bool """ return self.form.set_value("technology", what) def filter_scheme(self, scheme): """ Filter stations based on scheme they are members of. :rtype: bool """ return self.form.set_value("scheme", scheme.upper()) def filter_name(self, name): """ Filter stations based on name. The search will return all stations containing the supplied name. :param name: The name to filter for :rtype: bool """ return self.form.set_value("generating station search", name) def filter_generator_id(self, accno): """ Filter stations based on generator id. :rtype: bool """ return self.form.set_value("accreditation search", accno) def filter_organisation(self, org_name): """ Filter stations based on generator id. :param org_name: Organisation name to filter :rtype: bool """ return self.form.set_value("organisation search", org_name) def save_original(self, filename): """ Save the downloaded station data into the filename provided. :param filename: Filename to save the file to. :rtype: bool """ return self.form.save_original(filename) def rows(self): """ Generator to return dicts of station information. :returns: Dict of station information :rtype: dict """ for station in self.stations: yield {'Station': station.as_row()}