<commit_before>import bme680
import time
import logging
from blinker import signal

class BME680:
    def __init__(self, address):
        logging.info('Initialising BME680 sensor with address {}'.format(address))

        if address == 0x77:
            address = bme680.I2C_ADDR_SECONDARY
            logging.debug('Using secondary address')
        else:
            address = bme680.I2C_ADDR_PRIMARY
            logging.debug('Using primary address')

        self._air_quality_baseline_calculated = False
        self._gas_baseline = None
        self._sensor = bme680.BME680(i2c_addr=address)
        self._sensor.set_humidity_oversample(bme680.OS_2X)
        self._sensor.set_pressure_oversample(bme680.OS_4X)
        self._sensor.set_temperature_oversample(bme680.OS_8X)
        self._sensor.set_filter(bme680.FILTER_SIZE_3)

    def get_temperature(self, mqtt_details):
        """
        Return measured temperature from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring temperature')
        pending_measurement = True
        temperature = None

        while pending_measurement:
            if self._sensor.get_sensor_data():
                temperature = self._sensor.data.temperature
                pending_measurement = False
                logging.info('Temperature received from sensor: {}'.format(temperature))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        logging.info('Broadcasting temperature: {}'.format(temperature))
        temperature_signal = signal('temperature')
        temperature_signal.send(self, temperature=temperature, mqtt_topic=mqtt_details['topic'])

    def get_humidity(self, mqtt_details):
        """
        Return measured humidity from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring humidity')
        pending_measurement = True
        humidity = None

        while pending_measurement:
            if self._sensor.get_sensor_data():
                humidity = self._sensor.data.humidity
                pending_measurement = False
                logging.info('Humidity received from sensor: {}'.format(humidity))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        logging.info('Broadcasting humidity: {}'.format(humidity))
        humidity_signal = signal('humidity')
        humidity_signal.send(self, humidity=humidity, mqtt_topic=mqtt_details['topic'])

    def get_pressure(self, mqtt_details):
        """
        Return measured pressure from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring pressure')
        pending_measurement = True
        pressure = None

        while pending_measurement:
            if self._sensor.get_sensor_data():
                pressure = self._sensor.data.pressure
                pending_measurement = False
                logging.info('Pressure received from sensor: {}'.format(pressure))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        logging.info('Broadcasting pressure: {}'.format(pressure))
        pressure_signal = signal('pressure')
        pressure_signal.send(self, pressure=pressure, mqtt_topic=mqtt_details['topic'])

    def get_air_quality(self, mqtt_details):
        """
        Return measured air quality from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring air quality')
        if self._air_quality_baseline_calculated is False:
            logging.info('Sensor has no air quality baseline, calculating now')
            self._calculate_air_quality_baseline()

        self._sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)
        self._sensor.set_gas_heater_temperature(320)
        self._sensor.set_gas_heater_duration(150)
        self._sensor.select_gas_heater_profile(0)

        pending_measurement = True
        pending_count = 0
        air_quality = None
        hum_baseline = 40.0
        hum_weighting = 0.25

        start_time = time.time()
        current_time = time.time()
        burn_in_time = 10
        burn_in_data = []

        while current_time - start_time < burn_in_time:
            current_time = time.time()
            if self._sensor.get_sensor_data() and self._sensor.data.heat_stable:
                burn_in_data.append(self._sensor.data.gas_resistance)
                time.sleep(1)

        self._sensor.set_gas_status(bme680.DISABLE_GAS_MEAS)

        while pending_measurement and pending_count < 15:
            if self._sensor.get_sensor_data():
                gas = sum(burn_in_data) / len(burn_in_data)
                gas_offset = self._gas_baseline - gas

                hum = self._sensor.data.humidity
                hum_offset = hum - hum_baseline

                # Calculate hum_score as the distance from the hum_baseline.
                if hum_offset > 0:
                    hum_score = (100 - hum_baseline - hum_offset) / (100 - hum_baseline) * (hum_weighting * 100)
                else:
                    hum_score = (hum_baseline + hum_offset) / hum_baseline * (hum_weighting * 100)

                # Calculate gas_score as the distance from the gas_baseline.
                if gas_offset > 0:
                    gas_score = (gas / self._gas_baseline) * (100 - (hum_weighting * 100))
                else:
                    gas_score = 100 - (hum_weighting * 100)

                # Calculate air_quality_score.
                air_quality = hum_score + gas_score
                pending_measurement = False
                logging.info('Air quality received from sensor: {}'.format(air_quality))
            logging.debug('Sensor data not ready yet, will try again...')
            pending_count = pending_count + 1
            time.sleep(0.5)

        logging.info('Broadcasting air quality: {}'.format(air_quality))
        air_quality_signal = signal('air_quality')
        air_quality_signal.send(self, air_quality=air_quality, mqtt_topic=mqtt_details['topic'])

    def _calculate_air_quality_baseline(self):
        """
        Calculates the current baseline for the air quality sensor on the device.

        :return:
        """
        logging.debug('Calculating air quality baseline')
        self._sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)
        self._sensor.set_gas_heater_temperature(320)
        self._sensor.set_gas_heater_duration(150)
        self._sensor.select_gas_heater_profile(0)

        start_time = time.time()
        current_time = time.time()
        burn_in_time = 60
        burn_in_data = []

        while current_time - start_time < burn_in_time:
            current_time = time.time()
            if self._sensor.get_sensor_data() and self._sensor.data.heat_stable:
                gas = self._sensor.data.gas_resistance
                burn_in_data.append(gas)
                time.sleep(1)

        self._gas_baseline = sum(burn_in_data[-50:]) / 50.0
        logging.info('Air quality baseline calculated: {}'.format(self._gas_baseline))
        self._air_quality_baseline_calculated = True
        self._sensor.set_gas_status(bme680.DISABLE_GAS_MEAS)
<commit_msg>Reinstate burn in time<commit_after>import bme680
import time
import logging
from blinker import signal

class BME680:
    def __init__(self, address):
        logging.info('Initialising BME680 sensor with address {}'.format(address))

        if address == 0x77:
            address = bme680.I2C_ADDR_SECONDARY
            logging.debug('Using secondary address')
        else:
            address = bme680.I2C_ADDR_PRIMARY
            logging.debug('Using primary address')

        self._air_quality_baseline_calculated = False
        self._gas_baseline = None
        self._sensor = bme680.BME680(i2c_addr=address)
        self._sensor.set_humidity_oversample(bme680.OS_2X)
        self._sensor.set_pressure_oversample(bme680.OS_4X)
        self._sensor.set_temperature_oversample(bme680.OS_8X)
        self._sensor.set_filter(bme680.FILTER_SIZE_3)

    def get_temperature(self, mqtt_details):
        """
        Return measured temperature from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring temperature')
        pending_measurement = True
        temperature = None

        while pending_measurement:
            if self._sensor.get_sensor_data():
                temperature = self._sensor.data.temperature
                pending_measurement = False
                logging.info('Temperature received from sensor: {}'.format(temperature))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        logging.info('Broadcasting temperature: {}'.format(temperature))
        temperature_signal = signal('temperature')
        temperature_signal.send(self, temperature=temperature, mqtt_topic=mqtt_details['topic'])

    def get_humidity(self, mqtt_details):
        """
        Return measured humidity from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring humidity')
        pending_measurement = True
        humidity = None

        while pending_measurement:
            if self._sensor.get_sensor_data():
                humidity = self._sensor.data.humidity
                pending_measurement = False
                logging.info('Humidity received from sensor: {}'.format(humidity))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        logging.info('Broadcasting humidity: {}'.format(humidity))
        humidity_signal = signal('humidity')
        humidity_signal.send(self, humidity=humidity, mqtt_topic=mqtt_details['topic'])

    def get_pressure(self, mqtt_details):
        """
        Return measured pressure from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring pressure')
        pending_measurement = True
        pressure = None

        while pending_measurement:
            if self._sensor.get_sensor_data():
                pressure = self._sensor.data.pressure
                pending_measurement = False
                logging.info('Pressure received from sensor: {}'.format(pressure))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        logging.info('Broadcasting pressure: {}'.format(pressure))
        pressure_signal = signal('pressure')
        pressure_signal.send(self, pressure=pressure, mqtt_topic=mqtt_details['topic'])

    def get_air_quality(self, mqtt_details):
        """
        Return measured air quality from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring air quality')
        if self._air_quality_baseline_calculated is False:
            logging.info('Sensor has no air quality baseline, calculating now')
            self._calculate_air_quality_baseline()

        self._sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)
        self._sensor.set_gas_heater_temperature(320)
        self._sensor.set_gas_heater_duration(150)
        self._sensor.select_gas_heater_profile(0)

        pending_measurement = True
        pending_count = 0
        air_quality = None
        hum_baseline = 40.0
        hum_weighting = 0.25

        start_time = time.time()
        current_time = time.time()
        burn_in_time = 10
        burn_in_data = []

        while current_time - start_time < burn_in_time:
            current_time = time.time()
            if self._sensor.get_sensor_data() and self._sensor.data.heat_stable:
                burn_in_data.append(self._sensor.data.gas_resistance)
                time.sleep(1)

        self._sensor.set_gas_status(bme680.DISABLE_GAS_MEAS)

        while pending_measurement and pending_count < 15:
            if self._sensor.get_sensor_data():
                gas = sum(burn_in_data) / len(burn_in_data)
                gas_offset = self._gas_baseline - gas

                hum = self._sensor.data.humidity
                hum_offset = hum - hum_baseline

                # Calculate hum_score as the distance from the hum_baseline.
                if hum_offset > 0:
                    hum_score = (100 - hum_baseline - hum_offset) / (100 - hum_baseline) * (hum_weighting * 100)
                else:
                    hum_score = (hum_baseline + hum_offset) / hum_baseline * (hum_weighting * 100)

                # Calculate gas_score as the distance from the gas_baseline.
                if gas_offset > 0:
                    gas_score = (gas / self._gas_baseline) * (100 - (hum_weighting * 100))
                else:
                    gas_score = 100 - (hum_weighting * 100)

                # Calculate air_quality_score.
                air_quality = hum_score + gas_score
                pending_measurement = False
                logging.info('Air quality received from sensor: {}'.format(air_quality))
            logging.debug('Sensor data not ready yet, will try again...')
            pending_count = pending_count + 1
            time.sleep(0.5)

        logging.info('Broadcasting air quality: {}'.format(air_quality))
        air_quality_signal = signal('air_quality')
        air_quality_signal.send(self, air_quality=air_quality, mqtt_topic=mqtt_details['topic'])

    def _calculate_air_quality_baseline(self):
        """
        Calculates the current baseline for the air quality sensor on the device.

        :return:
        """
        logging.debug('Calculating air quality baseline')
        self._sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)
        self._sensor.set_gas_heater_temperature(320)
        self._sensor.set_gas_heater_duration(150)
        self._sensor.select_gas_heater_profile(0)

        start_time = time.time()
        current_time = time.time()
        burn_in_time = 300
        burn_in_data = []

        while current_time - start_time < burn_in_time:
            current_time = time.time()
            if self._sensor.get_sensor_data() and self._sensor.data.heat_stable:
                gas = self._sensor.data.gas_resistance
                burn_in_data.append(gas)
                time.sleep(1)

        self._gas_baseline = sum(burn_in_data[-50:]) / 50.0
        logging.info('Air quality baseline calculated: {}'.format(self._gas_baseline))
        self._air_quality_baseline_calculated = True
        self._sensor.set_gas_status(bme680.DISABLE_GAS_MEAS)
<|endoftext|>


<commit_before>import bme680
import time
import logging
from blinker import signal

class BME680:
    def __init__(self, address):
        logging.info('Initialising BME680 sensor with address {}'.format(address))

        if address == 0x77:
            address = bme680.I2C_ADDR_SECONDARY
            logging.debug('Using secondary address')
        else:
            address = bme680.I2C_ADDR_PRIMARY
            logging.debug('Using primary address')

        self._air_quality_baseline_calculated = False
        self._gas_baseline = None
        self._sensor = bme680.BME680(i2c_addr=address)
        self._sensor.set_humidity_oversample(bme680.OS_2X)
        self._sensor.set_pressure_oversample(bme680.OS_4X)
        self._sensor.set_temperature_oversample(bme680.OS_8X)
        self._sensor.set_filter(bme680.FILTER_SIZE_3)

    def get_temperature(self, mqtt_details):
        """
        Return measured temperature from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring temperature')
        pending_measurement = True
        temperature = None

        if self._sensor.get_gas_status():
            logging.info('Air quality is currently being measured, skipping temperature measurement')
            return

        while pending_measurement:
            if self._sensor.get_sensor_data():
                temperature = self._sensor.data.temperature
                pending_measurement = False
                logging.info('Temperature received from sensor: {}'.format(temperature))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        logging.info('Broadcasting temperature: {}'.format(temperature))
        temperature_signal = signal('temperature')
        temperature_signal.send(self, temperature=temperature, mqtt_topic=mqtt_details['topic'])

    def get_humidity(self, mqtt_details):
        """
        Return measured humidity from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring humidity')
        pending_measurement = True
        humidity = None

        if self._sensor.get_gas_status():
            logging.info('Air quality is currently being measured, skipping humidity measurement')
            return

        while pending_measurement:
            if self._sensor.get_sensor_data():
                humidity = self._sensor.data.humidity
                pending_measurement = False
                logging.info('Humidity received from sensor: {}'.format(humidity))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        logging.info('Broadcasting humidity: {}'.format(humidity))
        humidity_signal = signal('humidity')
        humidity_signal.send(self, humidity=humidity, mqtt_topic=mqtt_details['topic'])

    def get_pressure(self, mqtt_details):
        """
        Return measured pressure from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring pressure')
        pending_measurement = True
        pressure = None

        while pending_measurement:
            if self._sensor.get_sensor_data():
                pressure = self._sensor.data.pressure
                pending_measurement = False
                logging.info('Pressure received from sensor: {}'.format(pressure))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        logging.info('Broadcasting pressure: {}'.format(pressure))
        pressure_signal = signal('pressure')
        pressure_signal.send(self, pressure=pressure, mqtt_topic=mqtt_details['topic'])

    def get_air_quality(self, mqtt_details):
        """
        Return measured air quality from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring air quality')

        initial_temperature = None

        logging.debug('Measuring initial temperature and humidity readings')
        pending_measurement = True
        while pending_measurement:
            if self._sensor.get_sensor_data():
                initial_temperature = self._sensor.data.temperature
                pending_measurement = False
                logging.info('Temperature, Humidity received from sensor: {}, {}'.format(initial_temperature, initial_humidity))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        self._sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)
        self._sensor.set_gas_heater_temperature(320)
        self._sensor.set_gas_heater_duration(150)
        self._sensor.select_gas_heater_profile(0)

        if self._air_quality_baseline_calculated is False:
            logging.info('Sensor has no air quality baseline, calculating now')
            self._calculate_air_quality_baseline()

        pending_measurement = True
        pending_count = 0
        air_quality = None
        gas_resistance = None
        hum_baseline = 40.0
        hum_weighting = 0.25

        start_time = time.time()
        current_time = time.time()
        sample_time = 120
        sample_data = []

        while current_time - start_time < sample_time:
            current_time = time.time()
            if self._sensor.get_sensor_data() and self._sensor.data.heat_stable:
                sample_data.append(self._sensor.data.gas_resistance)
                time.sleep(1)

        self._sensor.set_gas_status(bme680.DISABLE_GAS_MEAS)

        while pending_measurement and pending_count < 15:
            if self._sensor.get_sensor_data():
                gas_resistance = sum(sample_data[-35:]) / 35.0
                gas_offset = self._gas_baseline - gas_resistance

                hum = self._sensor.data.humidity
                hum_offset = hum - hum_baseline

                # Calculate hum_score as the distance from the hum_baseline.
                if hum_offset > 0:
                    hum_score = (100 - hum_baseline - hum_offset) / (100 - hum_baseline) * (hum_weighting * 100)
                else:
                    hum_score = (hum_baseline + hum_offset) / hum_baseline * (hum_weighting * 100)

                # Calculate gas_score as the distance from the gas_baseline.
                if gas_offset > 0:
                    gas_score = (gas_resistance / self._gas_baseline) * (100 - (hum_weighting * 100))
                else:
                    gas_score = 100 - (hum_weighting * 100)

                # Calculate air_quality_score.
                air_quality = hum_score + gas_score
                pending_measurement = False
                logging.info('Air quality received from sensor: {}'.format(air_quality))
            logging.debug('Sensor data not ready yet, will try again...')
            pending_count = pending_count + 1
            time.sleep(0.5)

        logging.info('Broadcasting air quality: {}'.format(air_quality))
        air_quality_signal = signal('air_quality')
        air_quality_signal.send(self, air_quality=air_quality, gas=gas_resistance, mqtt_topic=mqtt_details['topic'])

        # Allow gas plate to cool down
        cooling = True
        while cooling:
            if self._sensor.get_sensor_data():
                if self._sensor.data.temperature <= initial_temperature + 0.25 or initial_temperature is None:
                    cooling = False
                    logging.debug('Sensor has cooled down sufficiently')
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

    def _calculate_air_quality_baseline(self):
        """
        Calculates the current baseline for the air quality sensor on the device.

        :return:
        """
        logging.debug('Calculating air quality baseline')

        start_time = time.time()
        current_time = time.time()
        burn_in_time = 300
        burn_in_data = []

        while current_time - start_time < burn_in_time:
            current_time = time.time()
            if self._sensor.get_sensor_data() and self._sensor.data.heat_stable:
                gas = self._sensor.data.gas_resistance
                burn_in_data.append(gas)
                time.sleep(1)

        self._gas_baseline = sum(burn_in_data[-50:]) / 50.0
        logging.info('Air quality baseline calculated: {}'.format(self._gas_baseline))
        self._air_quality_baseline_calculated = True
<commit_msg>Debug<commit_after>import bme680
import time
import logging
from blinker import signal

class BME680:
    def __init__(self, address):
        logging.info('Initialising BME680 sensor with address {}'.format(address))

        if address == 0x77:
            address = bme680.I2C_ADDR_SECONDARY
            logging.debug('Using secondary address')
        else:
            address = bme680.I2C_ADDR_PRIMARY
            logging.debug('Using primary address')

        self._air_quality_baseline_calculated = False
        self._gas_baseline = None
        self._sensor = bme680.BME680(i2c_addr=address)
        self._sensor.set_gas_status(bme680.DISABLE_GAS_MEAS)
        self._sensor.set_humidity_oversample(bme680.OS_2X)
        self._sensor.set_pressure_oversample(bme680.OS_4X)
        self._sensor.set_temperature_oversample(bme680.OS_8X)
        self._sensor.set_filter(bme680.FILTER_SIZE_3)

    def get_temperature(self, mqtt_details):
        """
        Return measured temperature from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring temperature')
        pending_measurement = True
        temperature = None

        if self._sensor.get_gas_status():
            logging.info('Air quality is currently being measured, skipping temperature measurement')
            return

        while pending_measurement:
            if self._sensor.get_sensor_data():
                temperature = self._sensor.data.temperature
                pending_measurement = False
                logging.info('Temperature received from sensor: {}'.format(temperature))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        logging.info('Broadcasting temperature: {}'.format(temperature))
        temperature_signal = signal('temperature')
        temperature_signal.send(self, temperature=temperature, mqtt_topic=mqtt_details['topic'])

    def get_humidity(self, mqtt_details):
        """
        Return measured humidity from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring humidity')
        pending_measurement = True
        humidity = None

        if self._sensor.get_gas_status():
            logging.info('Air quality is currently being measured, skipping humidity measurement')
            return

        while pending_measurement:
            if self._sensor.get_sensor_data():
                humidity = self._sensor.data.humidity
                pending_measurement = False
                logging.info('Humidity received from sensor: {}'.format(humidity))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        logging.info('Broadcasting humidity: {}'.format(humidity))
        humidity_signal = signal('humidity')
        humidity_signal.send(self, humidity=humidity, mqtt_topic=mqtt_details['topic'])

    def get_pressure(self, mqtt_details):
        """
        Return measured pressure from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring pressure')
        pending_measurement = True
        pressure = None

        while pending_measurement:
            if self._sensor.get_sensor_data():
                pressure = self._sensor.data.pressure
                pending_measurement = False
                logging.info('Pressure received from sensor: {}'.format(pressure))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        logging.info('Broadcasting pressure: {}'.format(pressure))
        pressure_signal = signal('pressure')
        pressure_signal.send(self, pressure=pressure, mqtt_topic=mqtt_details['topic'])

    def get_air_quality(self, mqtt_details):
        """
        Return measured air quality from the device.

        :param dict mqtt_details: Relevant details for publishing to the MQTT broker
        :return:
        """
        logging.debug('Measuring air quality')

        initial_temperature = None

        logging.debug('Measuring initial temperature and humidity readings')
        pending_measurement = True
        while pending_measurement:
            if self._sensor.get_sensor_data():
                initial_temperature = self._sensor.data.temperature
                pending_measurement = False
                logging.info('Temperature, Humidity received from sensor: {}, {}'.format(initial_temperature, initial_humidity))
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

        self._sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)
        self._sensor.set_gas_heater_temperature(320)
        self._sensor.set_gas_heater_duration(150)
        self._sensor.select_gas_heater_profile(0)

        if self._air_quality_baseline_calculated is False:
            logging.info('Sensor has no air quality baseline, calculating now')
            self._calculate_air_quality_baseline()

        pending_measurement = True
        pending_count = 0
        air_quality = None
        gas_resistance = None
        hum_baseline = 40.0
        hum_weighting = 0.25

        start_time = time.time()
        current_time = time.time()
        sample_time = 10
        sample_data = []

        while current_time - start_time < sample_time:
            current_time = time.time()
            if self._sensor.get_sensor_data() and self._sensor.data.heat_stable:
                sample_data.append(self._sensor.data.gas_resistance)
                time.sleep(1)

        self._sensor.set_gas_status(bme680.DISABLE_GAS_MEAS)

        while pending_measurement and pending_count < 15:
            if self._sensor.get_sensor_data():
                gas_resistance = sum(sample_data[-35:]) / 35.0
                gas_offset = self._gas_baseline - gas_resistance

                hum = self._sensor.data.humidity
                hum_offset = hum - hum_baseline

                # Calculate hum_score as the distance from the hum_baseline.
                if hum_offset > 0:
                    hum_score = (100 - hum_baseline - hum_offset) / (100 - hum_baseline) * (hum_weighting * 100)
                else:
                    hum_score = (hum_baseline + hum_offset) / hum_baseline * (hum_weighting * 100)

                # Calculate gas_score as the distance from the gas_baseline.
                if gas_offset > 0:
                    gas_score = (gas_resistance / self._gas_baseline) * (100 - (hum_weighting * 100))
                else:
                    gas_score = 100 - (hum_weighting * 100)

                # Calculate air_quality_score.
                air_quality = hum_score + gas_score
                pending_measurement = False
                logging.info('Air quality received from sensor: {}'.format(air_quality))
            logging.debug('Sensor data not ready yet, will try again...')
            pending_count = pending_count + 1
            time.sleep(0.5)

        logging.info('Broadcasting air quality: {}'.format(air_quality))
        air_quality_signal = signal('air_quality')
        air_quality_signal.send(self, air_quality=air_quality, gas=gas_resistance, mqtt_topic=mqtt_details['topic'])

        # Allow gas plate to cool down
        cooling = True
        while cooling:
            if self._sensor.get_sensor_data():
                if self._sensor.data.temperature <= initial_temperature + 0.25 or initial_temperature is None:
                    cooling = False
                    logging.debug('Sensor has cooled down sufficiently')
            logging.debug('Sensor data not ready yet, will try again...')
            time.sleep(0.5)

    def _calculate_air_quality_baseline(self):
        """
        Calculates the current baseline for the air quality sensor on the device.

        :return:
        """
        logging.debug('Calculating air quality baseline')

        start_time = time.time()
        current_time = time.time()
        burn_in_time = 10
        burn_in_data = []

        while current_time - start_time < burn_in_time:
            current_time = time.time()
            if self._sensor.get_sensor_data() and self._sensor.data.heat_stable:
                gas = self._sensor.data.gas_resistance
                burn_in_data.append(gas)
                time.sleep(1)

        self._gas_baseline = sum(burn_in_data[-50:]) / 50.0
        logging.info('Air quality baseline calculated: {}'.format(self._gas_baseline))
        self._air_quality_baseline_calculated = True
<|endoftext|>


<commit_before>#     bcf_utils.py: utility classes and functions shared between BCF codes
#     Copyright (C) University of Manchester 2013 Peter Briggs
#
########################################################################
#
# bcf_utils.py
#
#########################################################################

"""bcf_utils

Utility classes and functions shared between BCF codes.

"""

#######################################################################
# Import modules that this module depends on
#######################################################################

import os
import logging
import string

#######################################################################
# Class definitions
#######################################################################

# None defined

#######################################################################
# Module Functions
#######################################################################

# File system wrappers and utilities

def mkdir(dirn,mode=None):
    """Make a directory

    Arguments:
      dirn: the path of the directory to be created
      mode: (optional) a mode specifier to be applied to the
        new directory once it has been created e.g. 0775 or 0664

    """
    logging.debug("Making %s" % dirn)
    if not os.path.isdir(dirn):
        os.mkdir(dirn)
        if mode is not None: chmod(dirn,mode)

def mklink(target,link_name,relative=False):
    """Make a symbolic link

    Arguments:
      target: the file or directory to link to
      link_name: name of the link
      relative: if True then make a relative link (if possible);
        otherwise link to the target as given (default)"""
    logging.debug("Linking to %s from %s" % (target,link_name))
    target_path = target
    if relative:
        # Try to construct relative link to target
        target_abs_path = os.path.abspath(target)
        link_abs_path = os.path.abspath(link_name)
        common_prefix = commonprefix(target_abs_path,link_abs_path)
        if common_prefix:
            # Use relpath to generate the relative path from the link
            # to the target
            target_path = os.path.relpath(target_abs_path,os.path.dirname(link_abs_path))
    os.symlink(target_path,link_name)

def chmod(target,mode):
    """Change mode of file or directory

    Arguments:
      target: file or directory to apply new mode to
      mode: a valid mode specifier e.g. 0775 or 0664
    """
    logging.debug("Changing mode of %s to %s" % (target,mode))
    if not os.path.islink(target):
        try:
            os.chmod(target,mode)
        except OSError, ex:
            logging.warning("Failed to change permissions on %s to %s: %s" % (target,mode,ex))
    else:
        logging.warning("Skipped chmod for symbolic link")

def commonprefix(path1,path2):
    """Determine common prefix path for path1 and path2

    Use this in preference to os.path.commonprefix as the version
    in os.path compares the two paths in a character-wise fashion
    and so can give counter-intuitive matches; this version compares
    path components which seems more sensible.

    For example: for two paths /mnt/dir1/file and /mnt/dir2/file,
    os.path.commonprefix will return /mnt/dir, whereas this function
    will return /mnt.

    Arguments:
      path1: first path in comparison
      path2: second path in comparison

    Returns:
      Leading part of path which is common to both input paths.
    """
    path1_components = str(path1).split(os.sep)
    path2_components = str(path2).split(os.sep)
    common_components = []
    ncomponents = min(len(path1_components),len(path2_components))
    for i in range(ncomponents):
        if path1_components[i] == path2_components[i]:
            common_components.append(path1_components[i])
        else:
            break
    commonprefix = "%s" % os.sep.join(common_components)
    return commonprefix

# Sample/library name utilities

def extract_initials(name):
    """Return leading initials from the library or sample name

    Conventionaly the experimenter's initials are the leading characters
    of the name e.g. 'DR' for 'DR1', 'EP' for 'EP_NCYC2669', 'CW' for
    'CW_TI' etc

    Arguments:
      name: the name of a sample or library

    Returns:
      The leading initials from the name.
    """
    initials = []
    for c in str(name):
        if c.isalpha():
            initials.append(c)
        else:
            break
    return ''.join(initials)

def extract_prefix(name):
    """Return the library or sample name prefix

    Arguments:
      name: the name of a sample or library

    Returns:
      The prefix consisting of the name with trailing numbers
      removed, e.g. 'LD_C' for 'LD_C1'
    """
    return str(name).rstrip(string.digits)

def extract_index_as_string(name):
    """Return the library or sample name index as a string

    Arguments:
      name: the name of a sample or library

    Returns:
      The index, consisting of the trailing numbers from the name. It is
      returned as a string to preserve leading zeroes, e.g. '1' for
      'LD_C1', '07' for 'DR07' etc
    """
    index = []
    chars = [c for c in str(name)]
    chars.reverse()
    for c in chars:
        if c.isdigit():
            index.append(c)
        else:
            break
    index.reverse()
    return ''.join(index)

def extract_index(name):
    """Return the library or sample name index as an integer

    Arguments:
      name: the name of a sample or library

    Returns:
      The index as an integer, or None if the index cannot be converted to
      integer format.
    """
    indx = extract_index_as_string(name)
    if indx == '':
        return None
    else:
        return int(indx)

def pretty_print_names(name_list):
    """Given a list of library or sample names, format for pretty printing.

    Arguments:
      name_list: a list or tuple of library or sample names

    Returns:
      String with a condensed description of the library
      names, for example:

      ['DR1', 'DR2', 'DR3', DR4'] -> 'DR1-4'
    """
    # Create a list of string-type names sorted into prefix and index order
    names = [str(x) for x in sorted(name_list,
                                    key=lambda n: (extract_prefix(n),extract_index(n)))]
    # Go through and group
    groups = []
    group = []
    last_index = None
    for name in names:
        # Check if this is next in sequence
        try:
            if extract_index(name) == last_index+1:
                # Next in sequence
                group.append(name)
                last_index = extract_index(name)
                continue
        except TypeError:
            # One or both of the indexes was None
            pass
        # Current name is not next in previous sequence
        # Tidy up and start new group
        if group:
            groups.append(group)
        group = [name]
        last_index = extract_index(name)
    # Capture last group
    if group:
        groups.append(group)
    # Pretty print
    out = []
    for group in groups:
        if len(group) == 1:
            # "group" of one
            out.append(group[0])
        else:
            # Group with at least two members
            out.append(group[0]+"-"+extract_index_as_string(group[-1]))
    # Concatenate and return
    return ', '.join(out)

#######################################################################
# Tests
#######################################################################
import unittest

class TestFileSystemFunctions(unittest.TestCase):
    """Unit tests for file system wrapper and utility functions

    """

    def test_commonprefix(self):
        self.assertEqual('/mnt/stuff',commonprefix('/mnt/stuff/dir1',
                                                   '/mnt/stuff/dir2'))
        self.assertEqual('',commonprefix('/mnt1/stuff/dir1',
                                         '/mnt2/stuff/dir2'))

class TestNameFunctions(unittest.TestCase):
    """Unit tests for name handling utility functions

    """

    def test_extract_initials(self):
        self.assertEqual('DR',extract_initials('DR1'))
        self.assertEqual('EP',extract_initials('EP_NCYC2669'))
        self.assertEqual('CW',extract_initials('CW_TI'))

    def test_extract_prefix(self):
        self.assertEqual('LD_C',extract_prefix('LD_C1'))

    def test_extract_index_as_string(self):
        self.assertEqual('1',extract_index_as_string('LD_C1'))
        self.assertEqual('07',extract_index_as_string('DR07'))
        self.assertEqual('',extract_index_as_string('DROHSEVEN'))

    def test_extract_index(self):
        self.assertEqual(1,extract_index('LD_C1'))
        self.assertEqual(7,extract_index('DR07'))
        self.assertEqual(None,extract_index('DROHSEVEN'))
        self.assertEqual(0,extract_index('HUES1A0'))

    def test_pretty_print_names(self):
        self.assertEqual('JC_SEQ26-29',pretty_print_names(('JC_SEQ26',
                                                           'JC_SEQ27',
                                                           'JC_SEQ28',
                                                           'JC_SEQ29')))
        self.assertEqual('JC_SEQ26',pretty_print_names(('JC_SEQ26',)))
        self.assertEqual('JC_SEQ26, JC_SEQ28-29',pretty_print_names(('JC_SEQ26',
                                                           'JC_SEQ28',
                                                           'JC_SEQ29')))
        self.assertEqual('JC_SEQ26, JC_SEQ28, JC_SEQ30',pretty_print_names(('JC_SEQ26',
                                                           'JC_SEQ28',
                                                           'JC_SEQ30')))

#######################################################################
# Main program
#######################################################################

if __name__ == "__main__":
    # Turn off most logging output for tests
    logging.getLogger().setLevel(logging.CRITICAL)
    # Run tests
    unittest.main()
<commit_msg>share/bcf_utils.py: new function format_file_sizes (converts bytes to human-readable form e.g. 4.0K etc).<commit_after>#     bcf_utils.py: utility classes and functions shared between BCF codes
#     Copyright (C) University of Manchester 2013 Peter Briggs
#
########################################################################
#
# bcf_utils.py
#
#########################################################################

"""bcf_utils

Utility classes and functions shared between BCF codes.

"""

#######################################################################
# Import modules that this module depends on
#######################################################################

import os
import logging
import string

#######################################################################
# Class definitions
#######################################################################

# None defined

#######################################################################
# Module Functions
#######################################################################

# File system wrappers and utilities

def mkdir(dirn,mode=None):
    """Make a directory

    Arguments:
      dirn: the path of the directory to be created
      mode: (optional) a mode specifier to be applied to the
        new directory once it has been created e.g. 0775 or 0664

    """
    logging.debug("Making %s" % dirn)
    if not os.path.isdir(dirn):
        os.mkdir(dirn)
        if mode is not None: chmod(dirn,mode)

def mklink(target,link_name,relative=False):
    """Make a symbolic link

    Arguments:
      target: the file or directory to link to
      link_name: name of the link
      relative: if True then make a relative link (if possible);
        otherwise link to the target as given (default)"""
    logging.debug("Linking to %s from %s" % (target,link_name))
    target_path = target
    if relative:
        # Try to construct relative link to target
        target_abs_path = os.path.abspath(target)
        link_abs_path = os.path.abspath(link_name)
        common_prefix = commonprefix(target_abs_path,link_abs_path)
        if common_prefix:
            # Use relpath to generate the relative path from the link
            # to the target
            target_path = os.path.relpath(target_abs_path,os.path.dirname(link_abs_path))
    os.symlink(target_path,link_name)

def chmod(target,mode):
    """Change mode of file or directory

    Arguments:
      target: file or directory to apply new mode to
      mode: a valid mode specifier e.g. 0775 or 0664
    """
    logging.debug("Changing mode of %s to %s" % (target,mode))
    if not os.path.islink(target):
        try:
            os.chmod(target,mode)
        except OSError, ex:
            logging.warning("Failed to change permissions on %s to %s: %s" % (target,mode,ex))
    else:
        logging.warning("Skipped chmod for symbolic link")

def format_file_size(fsize):
    """Format a file size from bytes to human-readable form

    Takes a file size in bytes and returns a human-readable
    string, e.g. 4.0K, 186M, 1.5G.

    Arguments:
      fsize: size in bytes

    Returns:
      Human-readable version of file size.

    """
    # Return size in human readable form
    fsize = float(fsize)/1024
    units = 'K'
    if fsize > 1024:
        fsize = fsize/1024
        units = 'M'
        if fsize > 1024:
            fsize = fsize/1024
            units = 'G'
    return "%.1f%s" % (fsize,units)

def commonprefix(path1,path2):
    """Determine common prefix path for path1 and path2

    Use this in preference to os.path.commonprefix as the version
    in os.path compares the two paths in a character-wise fashion
    and so can give counter-intuitive matches; this version compares
    path components which seems more sensible.

    For example: for two paths /mnt/dir1/file and /mnt/dir2/file,
    os.path.commonprefix will return /mnt/dir, whereas this function
    will return /mnt.

    Arguments:
      path1: first path in comparison
      path2: second path in comparison

    Returns:
      Leading part of path which is common to both input paths.
    """
    path1_components = str(path1).split(os.sep)
    path2_components = str(path2).split(os.sep)
    common_components = []
    ncomponents = min(len(path1_components),len(path2_components))
    for i in range(ncomponents):
        if path1_components[i] == path2_components[i]:
            common_components.append(path1_components[i])
        else:
            break
    commonprefix = "%s" % os.sep.join(common_components)
    return commonprefix

# Sample/library name utilities

def extract_initials(name):
    """Return leading initials from the library or sample name

    Conventionaly the experimenter's initials are the leading characters
    of the name e.g. 'DR' for 'DR1', 'EP' for 'EP_NCYC2669', 'CW' for
    'CW_TI' etc

    Arguments:
      name: the name of a sample or library

    Returns:
      The leading initials from the name.
    """
    initials = []
    for c in str(name):
        if c.isalpha():
            initials.append(c)
        else:
            break
    return ''.join(initials)

def extract_prefix(name):
    """Return the library or sample name prefix

    Arguments:
      name: the name of a sample or library

    Returns:
      The prefix consisting of the name with trailing numbers
      removed, e.g. 'LD_C' for 'LD_C1'
    """
    return str(name).rstrip(string.digits)

def extract_index_as_string(name):
    """Return the library or sample name index as a string

    Arguments:
      name: the name of a sample or library

    Returns:
      The index, consisting of the trailing numbers from the name. It is
      returned as a string to preserve leading zeroes, e.g. '1' for
      'LD_C1', '07' for 'DR07' etc
    """
    index = []
    chars = [c for c in str(name)]
    chars.reverse()
    for c in chars:
        if c.isdigit():
            index.append(c)
        else:
            break
    index.reverse()
    return ''.join(index)

def extract_index(name):
    """Return the library or sample name index as an integer

    Arguments:
      name: the name of a sample or library

    Returns:
      The index as an integer, or None if the index cannot be converted to
      integer format.
    """
    indx = extract_index_as_string(name)
    if indx == '':
        return None
    else:
        return int(indx)

def pretty_print_names(name_list):
    """Given a list of library or sample names, format for pretty printing.

    Arguments:
      name_list: a list or tuple of library or sample names

    Returns:
      String with a condensed description of the library
      names, for example:

      ['DR1', 'DR2', 'DR3', DR4'] -> 'DR1-4'
    """
    # Create a list of string-type names sorted into prefix and index order
    names = [str(x) for x in sorted(name_list,
                                    key=lambda n: (extract_prefix(n),extract_index(n)))]
    # Go through and group
    groups = []
    group = []
    last_index = None
    for name in names:
        # Check if this is next in sequence
        try:
            if extract_index(name) == last_index+1:
                # Next in sequence
                group.append(name)
                last_index = extract_index(name)
                continue
        except TypeError:
            # One or both of the indexes was None
            pass
        # Current name is not next in previous sequence
        # Tidy up and start new group
        if group:
            groups.append(group)
        group = [name]
        last_index = extract_index(name)
    # Capture last group
    if group:
        groups.append(group)
    # Pretty print
    out = []
    for group in groups:
        if len(group) == 1:
            # "group" of one
            out.append(group[0])
        else:
            # Group with at least two members
            out.append(group[0]+"-"+extract_index_as_string(group[-1]))
    # Concatenate and return
    return ', '.join(out)

#######################################################################
# Tests
#######################################################################
import unittest

class TestFileSystemFunctions(unittest.TestCase):
    """Unit tests for file system wrapper and utility functions

    """

    def test_commonprefix(self):
        self.assertEqual('/mnt/stuff',commonprefix('/mnt/stuff/dir1',
                                                   '/mnt/stuff/dir2'))
        self.assertEqual('',commonprefix('/mnt1/stuff/dir1',
                                         '/mnt2/stuff/dir2'))

class TestFormatFileSize(unittest.TestCase):
    """Unit tests for formatting file sizes

    """

    def test_bytes_to_kb(self):
        self.assertEqual("0.9K",format_file_size(900))
        self.assertEqual("4.0K",format_file_size(4096))

    def test_bytes_to_mb(self):
        self.assertEqual("186.0M",format_file_size(195035136))

    def test_bytes_to_gb(self):
        self.assertEqual("1.6G",format_file_size(1717986919))

class TestNameFunctions(unittest.TestCase):
    """Unit tests for name handling utility functions

    """

    def test_extract_initials(self):
        self.assertEqual('DR',extract_initials('DR1'))
        self.assertEqual('EP',extract_initials('EP_NCYC2669'))
        self.assertEqual('CW',extract_initials('CW_TI'))

    def test_extract_prefix(self):
        self.assertEqual('LD_C',extract_prefix('LD_C1'))

    def test_extract_index_as_string(self):
        self.assertEqual('1',extract_index_as_string('LD_C1'))
        self.assertEqual('07',extract_index_as_string('DR07'))
        self.assertEqual('',extract_index_as_string('DROHSEVEN'))

    def test_extract_index(self):
        self.assertEqual(1,extract_index('LD_C1'))
        self.assertEqual(7,extract_index('DR07'))
        self.assertEqual(None,extract_index('DROHSEVEN'))
        self.assertEqual(0,extract_index('HUES1A0'))

    def test_pretty_print_names(self):
        self.assertEqual('JC_SEQ26-29',pretty_print_names(('JC_SEQ26',
                                                           'JC_SEQ27',
                                                           'JC_SEQ28',
                                                           'JC_SEQ29')))
        self.assertEqual('JC_SEQ26',pretty_print_names(('JC_SEQ26',)))
        self.assertEqual('JC_SEQ26, JC_SEQ28-29',pretty_print_names(('JC_SEQ26',
                                                           'JC_SEQ28',
                                                           'JC_SEQ29')))
        self.assertEqual('JC_SEQ26, JC_SEQ28, JC_SEQ30',pretty_print_names(('JC_SEQ26',
                                                           'JC_SEQ28',
                                                           'JC_SEQ30')))

#######################################################################
# Main program
#######################################################################

if __name__ == "__main__":
    # Turn off most logging output for tests
    logging.getLogger().setLevel(logging.CRITICAL)
    # Run tests
    unittest.main()
<|endoftext|>


<commit_before>"""
Implement a concrete `Tiling` class on the basis of healpix tiles. The
heavy lifting is done by the package OpSimSummary.
"""
from __future__ import absolute_import, print_function, division
import healpy as hp
import numpy as np
import opsimsummary  as oss
import pandas as pd
from .tessellations import Tiling
from sqlalchemy import create_engine

__all__ = ['HealpixTiles']

class HealpixTiles(Tiling):
    """
    A concrete Tiling class based on Healpix Tiles. The user is
    allowed to choose the following parameters:

    Attributes
    ----------
    nside : int, power of 2, defaults to 256
        healpix nside parameter

    """
    def __init__(self,
                 nside=256,
                 healpixelizedOpSim=None,
                 preComputedMap=None):
        """
        nside : int, power of 2, defaults to 256
            nside parameter of healpix. determines the size of the tiles
            so that there are 12 * nside **2 equally sized tiles covering
            the sphere.
        """
        self.nside = nside
        self.npix = hp.nside2npix(nside)
        self._tileArea = hp.nside2pixarea(nside)
        self.hpOpSim = healpixelizedOpSim
        self._preComputedMap = preComputedMap
        if self.hpOpSim is None and self.preComputedMap is None:
            raise ValueError('hpOpSim and preComputedMap cannot both be None')
        self._preComputedEngine = None

    @property
    def preComputedMap(self):
        if self._preComputedMap is not None:
            if not self._preComputedMap.startswith('sqlite'):
                self._preComputedMap = 'sqlite:///' + self._preComputedMap

        return self._preComputedMap

    @property
    def preComputedEngine(self):
        engine = self._preComputedEngine
        if engine is None:
            engine = create_engine(self.preComputedMap, echo=False)
        return engine

    @property
    def tileIDSequence(self):
        return xrange(self.npix)

    def area(self, tileID):
        if tileID not in self.tileIDSequence:
            raise ValueError('parameter tileID not in set of healpixIDs')
        return self._tileArea * np.degrees(1.) * np.degrees(1.)

    def tileIDsForSN(self, ra, dec):
        """
        Parameters
        ----------
        ra : `numpyp.ndarray` or float, degrees, mandatory
        dec : `numpy.ndarray` or float, degrees, mandatory
        """
        # If scalar float or list, convert to numpy array
        dec = np.ravel(dec)
        ra = np.ravel(ra)

        # Convert to usual spherical coordinates
        theta = - np.radians(dec) + np.pi/2.
        phi = np.radians(ra)

        inds = hp.ang2pix(nside=self.nside, theta=theta, phi=phi, nest=True)
        return inds

    def _pointingFromPrecomputedDB(self, tileID, tableName='simlib'):

        sql = 'SELECT obsHistID FROM {0} WHERE ipix == {1}'\
            .format(tableName, tileID)
        return pd.read_sql_query(sql, con=self.preComputedEngine)\
            .values.flatten()

    def _pointingFromHpOpSim(self, tileID):
        return self.hpOpSim.obsHistIdsForTile(tileID)

    def _tileFromHpOpSim(self, pointing):
        return self.hpOpSim.set_index('obsHistID').ix(pointing)['hids']

    def _tileFromPreComputedDB(self, pointing, tableName='simlib'):
        sql = 'SELECT ipix FROM {0} WHERE obsHistID == {1}'\
            .format(tableName, pointing)
        return pd.read_sql_query(sql, con=self.preComputedEngine)\
            .values.flatten()

    def tilesForPointing(self, pointing, alltiles=None, **kwargs):
        """
        return a maximal sequence of tile ID s for a particular OpSim pointing
        """
        if self.preComputedMap is not None:
            return self._tileFromPreComputedDB(self, pointing, tableName='simlib')
        elif self.hpOpSim is not None:
            return self._tileFromHpOpSim(self, pointing)
        else:
            raise ValueError('both attributes preComputedMap and hpOpSim cannot'
                             ' be None')

    def pointingSequenceForTile(self, tileID, allPointings, **kwargs):
        """
        return a maximal sequence of pointings for a particular tileID
        """
        if self.preComputedMap is not None:
            return self._pointingFromPrecomputedDB(tileID, tableName='simlib')
        elif self.hpOpSim is not None:
            return self._pointingFromHpOpSim(tileID)
        else:
            raise ValueError('both attributes preComputedMap and hpOpSim cannot'
                             ' be None')

    def _angularSamples(self, phi_c, theta_c, radius, numSamples, tileID, rng):
        phi, theta = super(self.__class__, self).samplePatchOnSphere(phi=phi_c,
								     theta=theta_c,
                                                                     delta=radius,
                                                                     size=numSamples,
                                                                     degrees=False,
                                                                     rng=rng)
        tileIds = hp.ang2pix(nside=self.nside, theta=theta,
			     phi=phi, nest=True)
        inTile = tileIds == tileID
        return phi[inTile], theta[inTile]

    def positions(self, tileID, numSamples, rng=None):
        """
        Return a tuple of (res_phi, res_theta) where res_phi and res_theta are
        spatially uniform samples  of positions of size numSamples.

        Parameters
        ---------
        tileID : int, mandatory

        numSamples : number of positions required

        rng : instance of `np.random.RandomState`

        Returns
        -------
        """
        # set the random number generator seed
        if rng is None:
            rng = np.random.RandomState(tileID)

        # zero return arrays
        res_theta = np.zeros(numSamples)
        res_phi = np.zeros(numSamples)

        # Set the center of the patch at the vertex of the tile
        theta_c, phi_c = np.degrees(hp.pix2ang(nside=self.nside,
                                               ipix=tileID,
                                               nest=True)
                                   )
        radius = 2 * np.sqrt(self.area(tileID) / np.pi)

        # number of
        num_already = 0

        while numSamples > 0:
            phi, theta = self._angularSamples(phi_c, theta_c, radius=radius,
                                              numSamples=numSamples,
                                              tileID=tileID,
                                              rng=rng)
            # print(self.nside, tileID, self.area(tileID))
            s = rng.get_state()
            print(s)
            num_obtained = len(phi)
            res_phi[num_already:num_obtained + num_already] = phi
            res_theta[num_already:num_obtained + num_already] = theta
            num_already += num_obtained
            numSamples -= num_obtained

        return np.degrees(res_phi), np.degrees(res_theta)

<commit_msg>removed print function<commit_after>"""
Implement a concrete `Tiling` class on the basis of healpix tiles. The
heavy lifting is done by the package OpSimSummary.
"""
from __future__ import absolute_import, print_function, division
import healpy as hp
import numpy as np
import opsimsummary  as oss
import pandas as pd
from .tessellations import Tiling
from sqlalchemy import create_engine

__all__ = ['HealpixTiles']

class HealpixTiles(Tiling):
    """
    A concrete Tiling class based on Healpix Tiles. The user is
    allowed to choose the following parameters:

    Attributes
    ----------
    nside : int, power of 2, defaults to 256
        healpix nside parameter

    """
    def __init__(self,
                 nside=256,
                 healpixelizedOpSim=None,
                 preComputedMap=None):
        """
        nside : int, power of 2, defaults to 256
            nside parameter of healpix. determines the size of the tiles
            so that there are 12 * nside **2 equally sized tiles covering
            the sphere.
        """
        self.nside = nside
        self.npix = hp.nside2npix(nside)
        self._tileArea = hp.nside2pixarea(nside)
        self.hpOpSim = healpixelizedOpSim
        self._preComputedMap = preComputedMap
        if self.hpOpSim is None and self.preComputedMap is None:
            raise ValueError('hpOpSim and preComputedMap cannot both be None')
        self._preComputedEngine = None

    @property
    def preComputedMap(self):
        if self._preComputedMap is not None:
            if not self._preComputedMap.startswith('sqlite'):
                self._preComputedMap = 'sqlite:///' + self._preComputedMap

        return self._preComputedMap

    @property
    def preComputedEngine(self):
        engine = self._preComputedEngine
        if engine is None:
            engine = create_engine(self.preComputedMap, echo=False)
        return engine

    @property
    def tileIDSequence(self):
        return xrange(self.npix)

    def area(self, tileID):
        if tileID not in self.tileIDSequence:
            raise ValueError('parameter tileID not in set of healpixIDs')
        return self._tileArea * np.degrees(1.) * np.degrees(1.)

    def tileIDsForSN(self, ra, dec):
        """
        Parameters
        ----------
        ra : `numpyp.ndarray` or float, degrees, mandatory
        dec : `numpy.ndarray` or float, degrees, mandatory
        """
        # If scalar float or list, convert to numpy array
        dec = np.ravel(dec)
        ra = np.ravel(ra)

        # Convert to usual spherical coordinates
        theta = - np.radians(dec) + np.pi/2.
        phi = np.radians(ra)

        inds = hp.ang2pix(nside=self.nside, theta=theta, phi=phi, nest=True)
        return inds

    def _pointingFromPrecomputedDB(self, tileID, tableName='simlib'):

        sql = 'SELECT obsHistID FROM {0} WHERE ipix == {1}'\
            .format(tableName, tileID)
        return pd.read_sql_query(sql, con=self.preComputedEngine)\
            .values.flatten()

    def _pointingFromHpOpSim(self, tileID):
        return self.hpOpSim.obsHistIdsForTile(tileID)

    def _tileFromHpOpSim(self, pointing):
        return self.hpOpSim.set_index('obsHistID').ix(pointing)['hids']

    def _tileFromPreComputedDB(self, pointing, tableName='simlib'):
        sql = 'SELECT ipix FROM {0} WHERE obsHistID == {1}'\
            .format(tableName, pointing)
        return pd.read_sql_query(sql, con=self.preComputedEngine)\
            .values.flatten()

    def tilesForPointing(self, pointing, alltiles=None, **kwargs):
        """
        return a maximal sequence of tile ID s for a particular OpSim pointing
        """
        if self.preComputedMap is not None:
            return self._tileFromPreComputedDB(self, pointing, tableName='simlib')
        elif self.hpOpSim is not None:
            return self._tileFromHpOpSim(self, pointing)
        else:
            raise ValueError('both attributes preComputedMap and hpOpSim cannot'
                             ' be None')

    def pointingSequenceForTile(self, tileID, allPointings, **kwargs):
        """
        return a maximal sequence of pointings for a particular tileID
        """
        if self.preComputedMap is not None:
            return self._pointingFromPrecomputedDB(tileID, tableName='simlib')
        elif self.hpOpSim is not None:
            return self._pointingFromHpOpSim(tileID)
        else:
            raise ValueError('both attributes preComputedMap and hpOpSim cannot'
                             ' be None')

    def _angularSamples(self, phi_c, theta_c, radius, numSamples, tileID, rng):
        phi, theta = super(self.__class__, self).samplePatchOnSphere(phi=phi_c,
								     theta=theta_c,
                                                                     delta=radius,
                                                                     size=numSamples,
                                                                     degrees=False,
                                                                     rng=rng)
        tileIds = hp.ang2pix(nside=self.nside, theta=theta,
			     phi=phi, nest=True)
        inTile = tileIds == tileID
        return phi[inTile], theta[inTile]

    def positions(self, tileID, numSamples, rng=None):
        """
        Return a tuple of (res_phi, res_theta) where res_phi and res_theta are
        spatially uniform samples  of positions of size numSamples.

        Parameters
        ---------
        tileID : int, mandatory

        numSamples : number of positions required

        rng : instance of `np.random.RandomState`

        Returns
        -------
        """
        # set the random number generator seed
        if rng is None:
            rng = np.random.RandomState(tileID)

        # zero return arrays
        res_theta = np.zeros(numSamples)
        res_phi = np.zeros(numSamples)

        # Set the center of the patch at the vertex of the tile
        theta_c, phi_c = np.degrees(hp.pix2ang(nside=self.nside,
                                               ipix=tileID,
                                               nest=True)
                                   )
        radius = 2 * np.sqrt(self.area(tileID) / np.pi)

        # number of
        num_already = 0

        while numSamples > 0:
            phi, theta = self._angularSamples(phi_c, theta_c, radius=radius,
                                              numSamples=numSamples,
                                              tileID=tileID,
                                              rng=rng)
            # print(self.nside, tileID, self.area(tileID))
            s = rng.get_state()
            num_obtained = len(phi)
            res_phi[num_already:num_obtained + num_already] = phi
            res_theta[num_already:num_obtained + num_already] = theta
            num_already += num_obtained
            numSamples -= num_obtained

        return np.degrees(res_phi), np.degrees(res_theta)

<|endoftext|>


<commit_before>"""
python version compatibility code
"""
import functools
import inspect
import io
import os
import re
import sys
from contextlib import contextmanager
from inspect import Parameter
from inspect import signature
from typing import Any
from typing import Callable
from typing import Generic
from typing import Optional
from typing import overload
from typing import Tuple
from typing import TypeVar
from typing import Union

import attr
import py

from _pytest._io.saferepr import saferepr
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME

if sys.version_info < (3, 5, 2):
    TYPE_CHECKING = False  # type: bool
else:
    from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing import Type  # noqa: F401 (used in type string)

_T = TypeVar("_T")
_S = TypeVar("_S")

NOTSET = object()

MODULE_NOT_FOUND_ERROR = (
    "ModuleNotFoundError" if sys.version_info[:2] >= (3, 6) else "ImportError"
)

if sys.version_info >= (3, 8):
    from importlib import metadata as importlib_metadata
else:
    import importlib_metadata  # noqa: F401

def _format_args(func: Callable[..., Any]) -> str:
    return str(signature(func))

# The type of re.compile objects is not exposed in Python.
REGEX_TYPE = type(re.compile(""))

if sys.version_info < (3, 6):

    def fspath(p):
        """os.fspath replacement, useful to point out when we should replace it by the
        real function once we drop py35.
        """
        return str(p)

else:
    fspath = os.fspath

def is_generator(func: object) -> bool:
    genfunc = inspect.isgeneratorfunction(func)
    return genfunc and not iscoroutinefunction(func)

def iscoroutinefunction(func: object) -> bool:
    """
    Return True if func is a coroutine function (a function defined with async
    def syntax, and doesn't contain yield), or a function decorated with
    @asyncio.coroutine.

    Note: copied and modified from Python 3.5's builtin couroutines.py to avoid
    importing asyncio directly, which in turns also initializes the "logging"
    module as a side-effect (see issue #8).
    """
    return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False)

def getlocation(function, curdir=None) -> str:
    function = get_real_func(function)
    fn = py.path.local(inspect.getfile(function))
    lineno = function.__code__.co_firstlineno
    if curdir is not None:
        relfn = fn.relto(curdir)
        if relfn:
            return "%s:%d" % (relfn, lineno + 1)
    return "%s:%d" % (fn, lineno + 1)

def num_mock_patch_args(function) -> int:
    """ return number of arguments used up by mock arguments (if any) """
    patchings = getattr(function, "patchings", None)
    if not patchings:
        return 0

    mock_sentinel = getattr(sys.modules.get("mock"), "DEFAULT", object())
    ut_mock_sentinel = getattr(sys.modules.get("unittest.mock"), "DEFAULT", object())

    return len(
        [
            p
            for p in patchings
            if not p.attribute_name
            and (p.new is mock_sentinel or p.new is ut_mock_sentinel)
        ]
    )

def getfuncargnames(
    function: Callable[..., Any],
    *,
    name: str = "",
    is_method: bool = False,
    cls: Optional[type] = None
) -> Tuple[str, ...]:
    """Returns the names of a function's mandatory arguments.

    This should return the names of all function arguments that:
        * Aren't bound to an instance or type as in instance or class methods.
        * Don't have default values.
        * Aren't bound with functools.partial.
        * Aren't replaced with mocks.

    The is_method and cls arguments indicate that the function should
    be treated as a bound method even though it's not unless, only in
    the case of cls, the function is a static method.

    The name parameter should be the original name in which the function was collected.
    """
    # TODO(RonnyPfannschmidt): This function should be refactored when we
    # revisit fixtures. The fixture mechanism should ask the node for
    # the fixture names, and not try to obtain directly from the
    # function object well after collection has occurred.

    # The parameters attribute of a Signature object contains an
    # ordered mapping of parameter names to Parameter instances.  This
    # creates a tuple of the names of the parameters that don't have
    # defaults.
    try:
        parameters = signature(function).parameters
    except (ValueError, TypeError) as e:
        fail(
            "Could not determine arguments of {!r}: {}".format(function, e),
            pytrace=False,
        )

    arg_names = tuple(
        p.name
        for p in parameters.values()
        if (
            p.kind is Parameter.POSITIONAL_OR_KEYWORD
            or p.kind is Parameter.KEYWORD_ONLY
        )
        and p.default is Parameter.empty
    )
    if not name:
        name = function.__name__

    # If this function should be treated as a bound method even though
    # it's passed as an unbound method or function, remove the first
    # parameter name.
    if is_method or (
        cls and not isinstance(cls.__dict__.get(name, None), staticmethod)
    ):
        arg_names = arg_names[1:]
    # Remove any names that will be replaced with mocks.
    if hasattr(function, "__wrapped__"):
        arg_names = arg_names[num_mock_patch_args(function) :]
    return arg_names

if sys.version_info < (3, 7):

    @contextmanager
    def nullcontext():
        yield

else:
    from contextlib import nullcontext  # noqa

def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]:
    # Note: this code intentionally mirrors the code at the beginning of getfuncargnames,
    # to get the arguments which were excluded from its result because they had default values
    return tuple(
        p.name
        for p in signature(function).parameters.values()
        if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY)
        and p.default is not Parameter.empty
    )

_non_printable_ascii_translate_table = {
    i: "\\x{:02x}".format(i) for i in range(128) if i not in range(32, 127)
}
_non_printable_ascii_translate_table.update(
    {ord("\t"): "\\t", ord("\r"): "\\r", ord("\n"): "\\n"}
)

def _translate_non_printable(s: str) -> str:
    return s.translate(_non_printable_ascii_translate_table)

STRING_TYPES = bytes, str

def _bytes_to_ascii(val: bytes) -> str:
    return val.decode("ascii", "backslashreplace")

def ascii_escaped(val: Union[bytes, str]):
    """If val is pure ascii, returns it as a str().  Otherwise, escapes
    bytes objects into a sequence of escaped bytes:

    b'\xc3\xb4\xc5\xd6' -> '\\xc3\\xb4\\xc5\\xd6'

    and escapes unicode objects into a sequence of escaped unicode
    ids, e.g.:

    '4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944'

    note:
       the obvious "v.decode('unicode-escape')" will return
       valid utf-8 unicode if it finds them in bytes, but we
       want to return escaped bytes for any byte, even if they match
       a utf-8 string.

    """
    if isinstance(val, bytes):
        ret = _bytes_to_ascii(val)
    else:
        ret = val.encode("unicode_escape").decode("ascii")
    return _translate_non_printable(ret)

@attr.s
class _PytestWrapper:
    """Dummy wrapper around a function object for internal use only.

    Used to correctly unwrap the underlying function object
    when we are creating fixtures, because we wrap the function object ourselves with a decorator
    to issue warnings when the fixture function is called directly.
    """

    obj = attr.ib()

def get_real_func(obj):
    """ gets the real function object of the (possibly) wrapped object by
    functools.wraps or functools.partial.
    """
    start_obj = obj
    for i in range(100):
        # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function
        # to trigger a warning if it gets called directly instead of by pytest: we don't
        # want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774)
        new_obj = getattr(obj, "__pytest_wrapped__", None)
        if isinstance(new_obj, _PytestWrapper):
            obj = new_obj.obj
            break
        new_obj = getattr(obj, "__wrapped__", None)
        if new_obj is None:
            break
        obj = new_obj
    else:
        raise ValueError(
            ("could not find real function of {start}\nstopped at {current}").format(
                start=saferepr(start_obj), current=saferepr(obj)
            )
        )
    if isinstance(obj, functools.partial):
        obj = obj.func
    return obj

def get_real_method(obj, holder):
    """
    Attempts to obtain the real function object that might be wrapping ``obj``, while at the same time
    returning a bound method to ``holder`` if the original object was a bound method.
    """
    try:
        is_method = hasattr(obj, "__func__")
        obj = get_real_func(obj)
    except Exception:  # pragma: no cover
        return obj
    if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
        obj = obj.__get__(holder)
    return obj

def getfslineno(obj) -> Tuple[Union[str, py.path.local], int]:
    """(**Deprecated**, use _pytest._code.source.getfslineno directly)"""
    import _pytest._code.source

    return _pytest._code.source.getfslineno(obj)

def getimfunc(func):
    try:
        return func.__func__
    except AttributeError:
        return func

def safe_getattr(object: Any, name: str, default: Any) -> Any:
    """ Like getattr but return default upon any Exception or any OutcomeException.

    Attribute access can potentially fail for 'evil' Python objects.
    See issue #214.
    It catches OutcomeException because of #2490 (issue #580), new outcomes are derived from BaseException
    instead of Exception (for more details check #2707)
    """
    try:
        return getattr(object, name, default)
    except TEST_OUTCOME:
        return default

def safe_isclass(obj: object) -> bool:
    """Ignore any exception via isinstance on Python 3."""
    try:
        return inspect.isclass(obj)
    except Exception:
        return False

COLLECT_FAKEMODULE_ATTRIBUTES = (
    "Collector",
    "Module",
    "Function",
    "Instance",
    "Session",
    "Item",
    "Class",
    "File",
    "_fillfuncargs",
)

def _setup_collect_fakemodule() -> None:
    from types import ModuleType
    import pytest

    # Types ignored because the module is created dynamically.
    pytest.collect = ModuleType("pytest.collect")  # type: ignore
    pytest.collect.__all__ = []  # type: ignore  # used for setns
    for attr_name in COLLECT_FAKEMODULE_ATTRIBUTES:
        setattr(pytest.collect, attr_name, getattr(pytest, attr_name))  # type: ignore

class CaptureIO(io.TextIOWrapper):
    def __init__(self) -> None:
        super().__init__(io.BytesIO(), encoding="UTF-8", newline="", write_through=True)

    def getvalue(self) -> str:
        assert isinstance(self.buffer, io.BytesIO)
        return self.buffer.getvalue().decode("UTF-8")

if sys.version_info < (3, 5, 2):

    def overload(f):  # noqa: F811
        return f

if getattr(attr, "__version_info__", ()) >= (19, 2):
    ATTRS_EQ_FIELD = "eq"
else:
    ATTRS_EQ_FIELD = "cmp"

if sys.version_info >= (3, 8):
    from functools import cached_property
else:

    class cached_property(Generic[_S, _T]):
        __slots__ = ("func", "__doc__")

        def __init__(self, func: Callable[[_S], _T]) -> None:
            self.func = func
            self.__doc__ = func.__doc__

        @overload
        def __get__(
            self, instance: None, owner: Optional["Type[_S]"] = ...
        ) -> "cached_property[_S, _T]":
            raise NotImplementedError()

        @overload  # noqa: F811
        def __get__(  # noqa: F811
            self, instance: _S, owner: Optional["Type[_S]"] = ...
        ) -> _T:
            raise NotImplementedError()

        def __get__(self, instance, owner=None):  # noqa: F811
            if instance is None:
                return self
            value = instance.__dict__[self.func.__name__] = self.func(instance)
            return value
<commit_msg>Remove compat.getfslineno<commit_after>"""
python version compatibility code
"""
import functools
import inspect
import io
import os
import re
import sys
from contextlib import contextmanager
from inspect import Parameter
from inspect import signature
from typing import Any
from typing import Callable
from typing import Generic
from typing import Optional
from typing import overload
from typing import Tuple
from typing import TypeVar
from typing import Union

import attr
import py

from _pytest._io.saferepr import saferepr
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME

if sys.version_info < (3, 5, 2):
    TYPE_CHECKING = False  # type: bool
else:
    from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing import Type  # noqa: F401 (used in type string)

_T = TypeVar("_T")
_S = TypeVar("_S")

NOTSET = object()

MODULE_NOT_FOUND_ERROR = (
    "ModuleNotFoundError" if sys.version_info[:2] >= (3, 6) else "ImportError"
)

if sys.version_info >= (3, 8):
    from importlib import metadata as importlib_metadata
else:
    import importlib_metadata  # noqa: F401

def _format_args(func: Callable[..., Any]) -> str:
    return str(signature(func))

# The type of re.compile objects is not exposed in Python.
REGEX_TYPE = type(re.compile(""))

if sys.version_info < (3, 6):

    def fspath(p):
        """os.fspath replacement, useful to point out when we should replace it by the
        real function once we drop py35.
        """
        return str(p)

else:
    fspath = os.fspath

def is_generator(func: object) -> bool:
    genfunc = inspect.isgeneratorfunction(func)
    return genfunc and not iscoroutinefunction(func)

def iscoroutinefunction(func: object) -> bool:
    """
    Return True if func is a coroutine function (a function defined with async
    def syntax, and doesn't contain yield), or a function decorated with
    @asyncio.coroutine.

    Note: copied and modified from Python 3.5's builtin couroutines.py to avoid
    importing asyncio directly, which in turns also initializes the "logging"
    module as a side-effect (see issue #8).
    """
    return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False)

def getlocation(function, curdir=None) -> str:
    function = get_real_func(function)
    fn = py.path.local(inspect.getfile(function))
    lineno = function.__code__.co_firstlineno
    if curdir is not None:
        relfn = fn.relto(curdir)
        if relfn:
            return "%s:%d" % (relfn, lineno + 1)
    return "%s:%d" % (fn, lineno + 1)

def num_mock_patch_args(function) -> int:
    """ return number of arguments used up by mock arguments (if any) """
    patchings = getattr(function, "patchings", None)
    if not patchings:
        return 0

    mock_sentinel = getattr(sys.modules.get("mock"), "DEFAULT", object())
    ut_mock_sentinel = getattr(sys.modules.get("unittest.mock"), "DEFAULT", object())

    return len(
        [
            p
            for p in patchings
            if not p.attribute_name
            and (p.new is mock_sentinel or p.new is ut_mock_sentinel)
        ]
    )

def getfuncargnames(
    function: Callable[..., Any],
    *,
    name: str = "",
    is_method: bool = False,
    cls: Optional[type] = None
) -> Tuple[str, ...]:
    """Returns the names of a function's mandatory arguments.

    This should return the names of all function arguments that:
        * Aren't bound to an instance or type as in instance or class methods.
        * Don't have default values.
        * Aren't bound with functools.partial.
        * Aren't replaced with mocks.

    The is_method and cls arguments indicate that the function should
    be treated as a bound method even though it's not unless, only in
    the case of cls, the function is a static method.

    The name parameter should be the original name in which the function was collected.
    """
    # TODO(RonnyPfannschmidt): This function should be refactored when we
    # revisit fixtures. The fixture mechanism should ask the node for
    # the fixture names, and not try to obtain directly from the
    # function object well after collection has occurred.

    # The parameters attribute of a Signature object contains an
    # ordered mapping of parameter names to Parameter instances.  This
    # creates a tuple of the names of the parameters that don't have
    # defaults.
    try:
        parameters = signature(function).parameters
    except (ValueError, TypeError) as e:
        fail(
            "Could not determine arguments of {!r}: {}".format(function, e),
            pytrace=False,
        )

    arg_names = tuple(
        p.name
        for p in parameters.values()
        if (
            p.kind is Parameter.POSITIONAL_OR_KEYWORD
            or p.kind is Parameter.KEYWORD_ONLY
        )
        and p.default is Parameter.empty
    )
    if not name:
        name = function.__name__

    # If this function should be treated as a bound method even though
    # it's passed as an unbound method or function, remove the first
    # parameter name.
    if is_method or (
        cls and not isinstance(cls.__dict__.get(name, None), staticmethod)
    ):
        arg_names = arg_names[1:]
    # Remove any names that will be replaced with mocks.
    if hasattr(function, "__wrapped__"):
        arg_names = arg_names[num_mock_patch_args(function) :]
    return arg_names

if sys.version_info < (3, 7):

    @contextmanager
    def nullcontext():
        yield

else:
    from contextlib import nullcontext  # noqa

def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]:
    # Note: this code intentionally mirrors the code at the beginning of getfuncargnames,
    # to get the arguments which were excluded from its result because they had default values
    return tuple(
        p.name
        for p in signature(function).parameters.values()
        if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY)
        and p.default is not Parameter.empty
    )

_non_printable_ascii_translate_table = {
    i: "\\x{:02x}".format(i) for i in range(128) if i not in range(32, 127)
}
_non_printable_ascii_translate_table.update(
    {ord("\t"): "\\t", ord("\r"): "\\r", ord("\n"): "\\n"}
)

def _translate_non_printable(s: str) -> str:
    return s.translate(_non_printable_ascii_translate_table)

STRING_TYPES = bytes, str

def _bytes_to_ascii(val: bytes) -> str:
    return val.decode("ascii", "backslashreplace")

def ascii_escaped(val: Union[bytes, str]):
    """If val is pure ascii, returns it as a str().  Otherwise, escapes
    bytes objects into a sequence of escaped bytes:

    b'\xc3\xb4\xc5\xd6' -> '\\xc3\\xb4\\xc5\\xd6'

    and escapes unicode objects into a sequence of escaped unicode
    ids, e.g.:

    '4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944'

    note:
       the obvious "v.decode('unicode-escape')" will return
       valid utf-8 unicode if it finds them in bytes, but we
       want to return escaped bytes for any byte, even if they match
       a utf-8 string.

    """
    if isinstance(val, bytes):
        ret = _bytes_to_ascii(val)
    else:
        ret = val.encode("unicode_escape").decode("ascii")
    return _translate_non_printable(ret)

@attr.s
class _PytestWrapper:
    """Dummy wrapper around a function object for internal use only.

    Used to correctly unwrap the underlying function object
    when we are creating fixtures, because we wrap the function object ourselves with a decorator
    to issue warnings when the fixture function is called directly.
    """

    obj = attr.ib()

def get_real_func(obj):
    """ gets the real function object of the (possibly) wrapped object by
    functools.wraps or functools.partial.
    """
    start_obj = obj
    for i in range(100):
        # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function
        # to trigger a warning if it gets called directly instead of by pytest: we don't
        # want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774)
        new_obj = getattr(obj, "__pytest_wrapped__", None)
        if isinstance(new_obj, _PytestWrapper):
            obj = new_obj.obj
            break
        new_obj = getattr(obj, "__wrapped__", None)
        if new_obj is None:
            break
        obj = new_obj
    else:
        raise ValueError(
            ("could not find real function of {start}\nstopped at {current}").format(
                start=saferepr(start_obj), current=saferepr(obj)
            )
        )
    if isinstance(obj, functools.partial):
        obj = obj.func
    return obj

def get_real_method(obj, holder):
    """
    Attempts to obtain the real function object that might be wrapping ``obj``, while at the same time
    returning a bound method to ``holder`` if the original object was a bound method.
    """
    try:
        is_method = hasattr(obj, "__func__")
        obj = get_real_func(obj)
    except Exception:  # pragma: no cover
        return obj
    if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
        obj = obj.__get__(holder)
    return obj

def getimfunc(func):
    try:
        return func.__func__
    except AttributeError:
        return func

def safe_getattr(object: Any, name: str, default: Any) -> Any:
    """ Like getattr but return default upon any Exception or any OutcomeException.

    Attribute access can potentially fail for 'evil' Python objects.
    See issue #214.
    It catches OutcomeException because of #2490 (issue #580), new outcomes are derived from BaseException
    instead of Exception (for more details check #2707)
    """
    try:
        return getattr(object, name, default)
    except TEST_OUTCOME:
        return default

def safe_isclass(obj: object) -> bool:
    """Ignore any exception via isinstance on Python 3."""
    try:
        return inspect.isclass(obj)
    except Exception:
        return False

COLLECT_FAKEMODULE_ATTRIBUTES = (
    "Collector",
    "Module",
    "Function",
    "Instance",
    "Session",
    "Item",
    "Class",
    "File",
    "_fillfuncargs",
)

def _setup_collect_fakemodule() -> None:
    from types import ModuleType
    import pytest

    # Types ignored because the module is created dynamically.
    pytest.collect = ModuleType("pytest.collect")  # type: ignore
    pytest.collect.__all__ = []  # type: ignore  # used for setns
    for attr_name in COLLECT_FAKEMODULE_ATTRIBUTES:
        setattr(pytest.collect, attr_name, getattr(pytest, attr_name))  # type: ignore

class CaptureIO(io.TextIOWrapper):
    def __init__(self) -> None:
        super().__init__(io.BytesIO(), encoding="UTF-8", newline="", write_through=True)

    def getvalue(self) -> str:
        assert isinstance(self.buffer, io.BytesIO)
        return self.buffer.getvalue().decode("UTF-8")

if sys.version_info < (3, 5, 2):

    def overload(f):  # noqa: F811
        return f

if getattr(attr, "__version_info__", ()) >= (19, 2):
    ATTRS_EQ_FIELD = "eq"
else:
    ATTRS_EQ_FIELD = "cmp"

if sys.version_info >= (3, 8):
    from functools import cached_property
else:

    class cached_property(Generic[_S, _T]):
        __slots__ = ("func", "__doc__")

        def __init__(self, func: Callable[[_S], _T]) -> None:
            self.func = func
            self.__doc__ = func.__doc__

        @overload
        def __get__(
            self, instance: None, owner: Optional["Type[_S]"] = ...
        ) -> "cached_property[_S, _T]":
            raise NotImplementedError()

        @overload  # noqa: F811
        def __get__(  # noqa: F811
            self, instance: _S, owner: Optional["Type[_S]"] = ...
        ) -> _T:
            raise NotImplementedError()

        def __get__(self, instance, owner=None):  # noqa: F811
            if instance is None:
                return self
            value = instance.__dict__[self.func.__name__] = self.func(instance)
            return value
<|endoftext|>