├── .gitattributes ├── 9781484257920.jpg ├── Ch01 ├── apd.sensors-chapter01 │ ├── Pipfile │ ├── Pipfile.lock │ └── sensors.py ├── figure01-01-fizzbuzz.ipynb ├── figure01-04-versioninfo.ipynb ├── figure01-05-ip-address.ipynb ├── figure01-06-ip-address-joined.ipynb ├── figure01-07-multiple-datapoints.ipynb ├── figure01-08-temperature_and_humidity_remote.ipynb ├── figure01-09-temperature_and_humidity_local.ipynb ├── listing01-01-fizzbuzz.py ├── listing01-02-fizzbuzz_blank_lines.py ├── listing01-03-fizzbuzz_with_breakpoint.py ├── listing01-04-converted.py ├── listing01-05-serverstatus.py ├── listing01-06-sensors_argv.py ├── listing01-07-sensors_argparse.py ├── listing01-08-sensors_click.py ├── listing01-09-sensors_click_bold.py └── listing01-10-final-sensors.py ├── Ch02 ├── apd.sensors-chapter02-ex01 │ ├── Pipfile │ ├── Pipfile.lock │ ├── sensors.py │ └── tests │ │ ├── __init__.py │ │ └── test_sensors.py ├── apd.sensors-chapter02-pyi │ ├── .pre-commit-config.yaml │ ├── Pipfile │ ├── Pipfile.lock │ ├── pytest.ini │ ├── sensors.py │ ├── sensors.pyi │ ├── setup.cfg │ └── tests │ │ ├── __init__.py │ │ ├── test_acstatus.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py ├── listing02-01-temperature_sensor.py ├── listing02-02-temperature_conversion.ipynb ├── listing02-03-temperature_conversion_invalid_types.ipynb ├── listing02-04-temperature.py ├── listing02-05-unittest_temperature │ ├── __pycache__ │ │ └── temperature.cpython-37.pyc │ ├── temperature.py │ └── test_unittest.py ├── listing02-06-pytest_temperature │ ├── temperature.py │ └── test_pytest.py ├── listing02-07-sensors.py ├── listing02-08 │ ├── Pipfile │ ├── Pipfile.lock │ ├── sensors.py │ └── tests │ │ ├── __init__.py │ │ ├── test_pythonversion.py │ │ └── test_sensors.py ├── listing02-09-sensors,cover.py ├── listing02-10 │ ├── .pre-commit-config.yaml │ ├── Pipfile │ ├── Pipfile.lock │ ├── incorrect.py │ ├── pytest.ini │ ├── sensors.py │ ├── setup.cfg │ └── tests │ │ ├── __init__.py │ │ ├── test_acstatus.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py ├── listing02-11 │ ├── .pre-commit-config.yaml │ ├── Pipfile │ ├── Pipfile.lock │ ├── pytest.ini │ ├── sensors.py │ ├── setup.cfg │ └── tests │ │ ├── __init__.py │ │ ├── test_acstatus.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py ├── listing02-12 │ ├── .pre-commit-config.yaml │ ├── Pipfile │ ├── Pipfile.lock │ ├── pytest.ini │ ├── sensors.py │ ├── sensors.pyi │ ├── setup.cfg │ └── tests │ │ ├── __init__.py │ │ ├── test_acstatus.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py └── listing02-13-.pre-commit-config.yaml ├── Ch03 ├── apd.sensors-chapter03 │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ ├── py.typed │ │ │ └── sensors.py │ └── tests │ │ ├── __init__.py │ │ ├── test_acstatus.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py ├── listing03-01-setup.cfg ├── listing03-02-indexserver.service ├── listing03-03-cheatsheet.md ├── listing03-04-cheatsheet.rst └── listing03-05-readme.md ├── Ch04 ├── apd.sensors-chapter04-click-parsing │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ ├── cli.py │ │ │ └── sensors.py │ └── tests │ │ ├── __init__.py │ │ ├── test_acstatus.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py ├── apd.sensors-chapter04-click-subcommands │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ └── sensors.py │ └── tests │ │ ├── __init__.py │ │ ├── test_acstatus.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py ├── apd.sensors-chapter04-configparser-local │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── config.cfg │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ ├── cli.py │ │ │ ├── config_path_utils.py │ │ │ ├── py.typed │ │ │ └── sensors.py │ └── tests │ │ ├── default_test_config.cfg │ │ ├── test_acstatus.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py ├── apd.sensors-chapter04-configparser │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ ├── cli.py │ │ │ ├── py.typed │ │ │ └── sensors.py │ └── tests │ │ ├── test_acstatus.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py ├── apd.sensors-chapter04-ex01 │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ ├── cli.py │ │ │ └── sensors.py │ └── tests │ │ ├── __init__.py │ │ ├── test_acstatus.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py ├── apd.sensors-chapter04 │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── plugins │ │ └── apd.sunnyboy_solar │ │ │ ├── pyproject.toml │ │ │ ├── setup.cfg │ │ │ ├── setup.py │ │ │ └── src │ │ │ └── apd │ │ │ └── sunnyboy_solar │ │ │ ├── Pipfile │ │ │ ├── __init__.py │ │ │ └── sensor.py │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ ├── cli.py │ │ │ ├── py.typed │ │ │ ├── sensors.py │ │ │ └── wsgi.py │ └── tests │ │ ├── test_acstatus.py │ │ ├── test_api_server.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py └── listing04-01-solar_prototype.ipynb ├── Ch05 ├── apd.sensors-chapter05-pintbased │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── plugins │ │ └── apd.sunnyboy_solar │ │ │ ├── pyproject.toml │ │ │ ├── setup.cfg │ │ │ ├── setup.py │ │ │ └── src │ │ │ └── apd │ │ │ └── sunnyboy_solar │ │ │ ├── Pipfile │ │ │ ├── __init__.py │ │ │ └── sensor.py │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── cli.py │ │ │ ├── py.typed │ │ │ ├── sensors.py │ │ │ └── wsgi │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── serve.py │ │ │ ├── v10.py │ │ │ └── v20.py │ └── tests │ │ ├── test_acstatus.py │ │ ├── test_api_server.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py ├── apd.sensors-chapter05 │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── plugins │ │ └── apd.sunnyboy_solar │ │ │ ├── pyproject.toml │ │ │ ├── setup.cfg │ │ │ ├── setup.py │ │ │ └── src │ │ │ └── apd │ │ │ └── sunnyboy_solar │ │ │ ├── Pipfile │ │ │ ├── __init__.py │ │ │ └── sensor.py │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── cli.py │ │ │ ├── py.typed │ │ │ ├── sensors.py │ │ │ └── wsgi │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── serve.py │ │ │ ├── v10.py │ │ │ └── v20.py │ └── tests │ │ ├── test_acstatus.py │ │ ├── test_api_server.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py ├── listing05-01-helloworld.py ├── listing05-02-helloworld-incremental.py ├── listing05-03-apd_sensors_wsgi.py └── listing05-08-typing.py ├── Ch06 ├── apd.aggregation-chapter06 │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ └── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ └── database.py │ └── tests │ │ ├── __init__.py │ │ └── conftest.py ├── chapter06-ex1-generators.py └── listing06-03-descriptors.py ├── Ch07 ├── apd.aggregation-chapter07-aio │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ └── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ └── database.py │ └── tests │ │ ├── __init__.py │ │ └── conftest.py ├── apd.aggregation-chapter07-multiprocess │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ └── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ └── database.py │ └── tests │ │ ├── __init__.py │ │ └── conftest.py ├── apd.aggregation-chapter07-nbio │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ └── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ └── database.py │ └── tests │ │ ├── __init__.py │ │ └── conftest.py ├── apd.aggregation-chapter07-simple-threads │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ └── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ └── database.py │ └── tests │ │ ├── __init__.py │ │ └── conftest.py ├── listing07-01-nbioexample.py ├── listing07-02-increment_dis.py ├── listing07-05-threadpools-and-queues.py ├── listing07-06-reentrantlocks.py ├── listing07-07-conditions.py ├── listing07-08-barriers.py ├── listing07-09-events.py ├── listing07-10-semaphore.py ├── listing07-11-async-increment.py ├── listing07-12-list_of_awaitables.py ├── listing07-13-awaitable_list.py ├── listing07-14-async_for.py ├── listing07-15-awaitable_gather.py ├── listing07-16-async_increment_unsafe.py └── listing07-17-async_increment_safe.py ├── Ch08 ├── apd.aggregation-chapter08 │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ ├── 4b2df8a6e1ce_add_indexes_to_datapoints.py │ │ │ │ ├── 6962f8455a6d_add_daily_summary_view.py │ │ │ │ ├── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ │ └── d8d4cf6a178f_add_deployment_id_to_datapoint.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ ├── database.py │ │ │ └── query.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_http_get.py │ │ └── test_sensor_aggregation.py ├── apd.sensors-chapter08 │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── plugins │ │ └── apd.sunnyboy_solar │ │ │ ├── pyproject.toml │ │ │ ├── setup.cfg │ │ │ ├── setup.py │ │ │ └── src │ │ │ └── apd │ │ │ └── sunnyboy_solar │ │ │ ├── Pipfile │ │ │ ├── __init__.py │ │ │ └── sensor.py │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── cli.py │ │ │ ├── py.typed │ │ │ ├── sensors.py │ │ │ └── wsgi │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── serve.py │ │ │ ├── v10.py │ │ │ ├── v20.py │ │ │ └── v21.py │ └── tests │ │ ├── __init__.py │ │ ├── test_acstatus.py │ │ ├── test_api_server.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py ├── listing08-01-httpfixture.py ├── listing08-02-config_fixture.py ├── listing08-03-mocking.py ├── listing08-04-manual_mocks.py ├── listing08-05-apdaggregation_mocks.py ├── listing08-06-classic_sqlalchemy.py ├── listing08-07-datapoint_with_asdict.py ├── listing08-08-database_integration.py ├── listing08-09-full_datapoint.py ├── listing08-10-comparator.py ├── listing08-11-django.py ├── listing08-12-migration.py └── listing08-13-env.py ├── Ch09 ├── apd.aggregation-chapter09-ex01 │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ ├── 4b2df8a6e1ce_add_indexes_to_datapoints.py │ │ │ │ ├── 6962f8455a6d_add_daily_summary_view.py │ │ │ │ ├── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ │ └── d8d4cf6a178f_add_deployment_id_to_datapoint.py │ │ │ ├── analysis.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ ├── database.py │ │ │ └── query.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_analysis.py │ │ ├── test_http_get.py │ │ ├── test_query.py │ │ └── test_sensor_aggregation.py ├── apd.aggregation-chapter09-ex02 │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ ├── 4b2df8a6e1ce_add_indexes_to_datapoints.py │ │ │ │ ├── 6962f8455a6d_add_daily_summary_view.py │ │ │ │ ├── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ │ └── d8d4cf6a178f_add_deployment_id_to_datapoint.py │ │ │ ├── analysis.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ ├── database.py │ │ │ └── query.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_analysis.py │ │ ├── test_http_get.py │ │ ├── test_query.py │ │ └── test_sensor_aggregation.py ├── apd.aggregation-chapter09-ex03-complete │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ ├── 4b2df8a6e1ce_add_indexes_to_datapoints.py │ │ │ │ ├── 6962f8455a6d_add_daily_summary_view.py │ │ │ │ ├── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ │ ├── d8cdc709086b_add_deployment_table.py │ │ │ │ └── d8d4cf6a178f_add_deployment_id_to_datapoint.py │ │ │ ├── analysis.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ ├── database.py │ │ │ ├── query.py │ │ │ └── utils.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_analysis.py │ │ ├── test_cli.py │ │ ├── test_http_get.py │ │ ├── test_query.py │ │ └── test_sensor_aggregation.py ├── apd.aggregation-chapter09-ex03 │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ ├── 4b2df8a6e1ce_add_indexes_to_datapoints.py │ │ │ │ ├── 6962f8455a6d_add_daily_summary_view.py │ │ │ │ ├── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ │ ├── d8cdc709086b_add_deployment_table.py │ │ │ │ └── d8d4cf6a178f_add_deployment_id_to_datapoint.py │ │ │ ├── analysis.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ ├── database.py │ │ │ ├── query.py │ │ │ └── utils.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_analysis.py │ │ ├── test_cli.py │ │ ├── test_http_get.py │ │ ├── test_query.py │ │ └── test_sensor_aggregation.py ├── apd.aggregation-chapter09 │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── Connect to database.ipynb │ ├── LICENCE │ ├── Mapping.ipynb │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ ├── 4b2df8a6e1ce_add_indexes_to_datapoints.py │ │ │ │ ├── 6962f8455a6d_add_daily_summary_view.py │ │ │ │ ├── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ │ ├── d8cdc709086b_add_deployment_table.py │ │ │ │ └── d8d4cf6a178f_add_deployment_id_to_datapoint.py │ │ │ ├── analysis.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ ├── database.py │ │ │ ├── query.py │ │ │ └── utils.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_analysis.py │ │ ├── test_cli.py │ │ ├── test_http_get.py │ │ ├── test_query.py │ │ └── test_sensor_aggregation.py ├── chapter09-analysis.ipynb ├── chapter09-database.ipynb ├── chapter09-mapping.ipynb ├── listing09-01-query_contextmanager.py ├── listing09-02-getdata.py ├── listing09-03-count-datapoints.py ├── listing09-04-plot.py ├── listing09-05-filtering.py ├── listing09-06-multiplot.py ├── listing09-07-more_filtering.py ├── listing09-08-plot_with_helpers.py ├── listing09-09-async_groupby.py ├── listing09-10-new_get_data.py ├── listing09-11-database_fixtures.py ├── listing09-12-parameterisation.py ├── listing09-13-configs.py ├── listing09-14-two_plots.py ├── listing09-15-temperature_cleaner.py ├── listing09-16-chart_grid.py ├── listing09-17-sync_from_async.py ├── listing09-18-wrap_coroutine.py ├── listing09-19-interactable.py ├── listing09-20-genericised_plots.py ├── listing09-21-contours_and_scatter.py ├── listing09-22-get_data_config.py ├── listing09-23-generic_config.py └── listing09-24-custom_map_chart.py ├── Ch10 ├── apd.aggregation-chapter10-ex01 │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── Connect to database.ipynb │ ├── LICENCE │ ├── Mapping.ipynb │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ ├── 4b2df8a6e1ce_add_indexes_to_datapoints.py │ │ │ │ ├── 6962f8455a6d_add_daily_summary_view.py │ │ │ │ ├── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ │ ├── d8cdc709086b_add_deployment_table.py │ │ │ │ └── d8d4cf6a178f_add_deployment_id_to_datapoint.py │ │ │ ├── analysis.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ ├── database.py │ │ │ ├── query.py │ │ │ ├── typing.py │ │ │ └── utils.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_analysis.py │ │ ├── test_cli.py │ │ ├── test_http_get.py │ │ ├── test_query.py │ │ └── test_sensor_aggregation.py ├── apd.aggregation-chapter10 │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── Connect to database.ipynb │ ├── LICENCE │ ├── Mapping.ipynb │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── Yappi Profiling.ipynb │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ ├── 4b2df8a6e1ce_add_indexes_to_datapoints.py │ │ │ │ ├── 6962f8455a6d_add_daily_summary_view.py │ │ │ │ ├── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ │ ├── d8cdc709086b_add_deployment_table.py │ │ │ │ └── d8d4cf6a178f_add_deployment_id_to_datapoint.py │ │ │ ├── analysis.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ ├── database.py │ │ │ ├── query.py │ │ │ ├── typing.py │ │ │ └── utils.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_analysis.py │ │ ├── test_cli.py │ │ ├── test_http_get.py │ │ ├── test_query.py │ │ └── test_sensor_aggregation.py ├── apd.sensors-chapter10 │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── plugins │ │ └── apd.sunnyboy_solar │ │ │ ├── pyproject.toml │ │ │ ├── setup.cfg │ │ │ ├── setup.py │ │ │ └── src │ │ │ └── apd │ │ │ └── sunnyboy_solar │ │ │ ├── Pipfile │ │ │ ├── __init__.py │ │ │ └── sensor.py │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── cli.py │ │ │ ├── py.typed │ │ │ ├── sensors.py │ │ │ └── wsgi │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── serve.py │ │ │ ├── v10.py │ │ │ ├── v20.py │ │ │ └── v21.py │ └── tests │ │ ├── test_acstatus.py │ │ ├── test_api_server.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ └── test_sensors.py ├── chapter10-yappi.ipynb ├── listing10-01-profiling_wrapper.py ├── listing10-02-profile_with_yappi.py ├── listing10-03-memory_profiler.py ├── listing10-04-sql_filtering.py ├── listing10-05-python_filtering.py ├── listing10-06-consume_iterators.py ├── listing10-07-consume_iterators_singledispatch.py ├── listing10-08-typed_conversion.py ├── listing10-09-fahrenheit_chart.py └── listing10-10-minimal_cache.py ├── Ch11 ├── apd.aggregation-chapter11 │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── Connect to database.ipynb │ ├── LICENCE │ ├── Mapping.ipynb │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── Yappi Profiling.ipynb │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ ├── 4b2df8a6e1ce_add_indexes_to_datapoints.py │ │ │ │ ├── 6962f8455a6d_add_daily_summary_view.py │ │ │ │ ├── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ │ ├── d8cdc709086b_add_deployment_table.py │ │ │ │ └── d8d4cf6a178f_add_deployment_id_to_datapoint.py │ │ │ ├── analysis.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ ├── database.py │ │ │ ├── query.py │ │ │ ├── typing.py │ │ │ └── utils.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_analysis.py │ │ ├── test_cli.py │ │ ├── test_http_get.py │ │ ├── test_query.py │ │ └── test_sensor_aggregation.py ├── apd.sensors-chapter11-ex01 │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── plugins │ │ └── apd.sunnyboy_solar │ │ │ ├── pyproject.toml │ │ │ ├── setup.cfg │ │ │ ├── setup.py │ │ │ └── src │ │ │ └── apd │ │ │ └── sunnyboy_solar │ │ │ ├── Pipfile │ │ │ ├── __init__.py │ │ │ └── sensor.py │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ └── 0eeb2a54fea8_add_initial_sensor_table.py │ │ │ ├── base.py │ │ │ ├── cli.py │ │ │ ├── database.py │ │ │ ├── exceptions.py │ │ │ ├── py.typed │ │ │ ├── sensors.py │ │ │ ├── utils.py │ │ │ └── wsgi │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── serve.py │ │ │ ├── v10.py │ │ │ ├── v20.py │ │ │ ├── v21.py │ │ │ └── v30.py │ └── tests │ │ ├── __init__.py │ │ ├── test_acstatus.py │ │ ├── test_api_server.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ ├── test_sensors.py │ │ └── test_utils.py ├── apd.sensors-chapter11 │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── plugins │ │ └── apd.sunnyboy_solar │ │ │ ├── pyproject.toml │ │ │ ├── setup.cfg │ │ │ ├── setup.py │ │ │ └── src │ │ │ └── apd │ │ │ └── sunnyboy_solar │ │ │ ├── Pipfile │ │ │ ├── __init__.py │ │ │ └── sensor.py │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ └── 0eeb2a54fea8_add_initial_sensor_table.py │ │ │ ├── base.py │ │ │ ├── cli.py │ │ │ ├── database.py │ │ │ ├── exceptions.py │ │ │ ├── py.typed │ │ │ ├── sensors.py │ │ │ ├── utils.py │ │ │ └── wsgi │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── serve.py │ │ │ ├── v10.py │ │ │ ├── v20.py │ │ │ ├── v21.py │ │ │ └── v30.py │ └── tests │ │ ├── __init__.py │ │ ├── test_acstatus.py │ │ ├── test_api_server.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ ├── test_sensors.py │ │ └── test_utils.py ├── listing11-01-get_with_default.py ├── listing11-02-new_exceptions.py ├── listing11-03-retry_sensor.py ├── listing11-04-exception_with_metadata.py ├── listing11-05-dht_baseclass.py ├── listing11-06-cli_exceptions.py ├── listing11-07-failing_test_sensor.py ├── listing11-08-compatibility_test.py ├── listing11-09-mock-failingsensor.py ├── listing11-10-deprecationwarning.py ├── listing11-11-test_for_deprecation_warnings.py ├── listing11-12-logging_config.py ├── listing11-13-log_adapter.py ├── listing11-14-log_factory.py ├── listing11-15-log_filter.py ├── listing11-16-log_handler.py ├── listing11-17-log_config.ini ├── listing11-18-local_data_cache.py ├── listing11-19-local_data_cache_cli.py └── listing11-20-v3_api_additions.py ├── Ch12 ├── apd.aggregation-chapter12-ex01-complete │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── Connect to database.ipynb │ ├── LICENCE │ ├── Mapping.ipynb │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── Yappi Profiling.ipynb │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── actions │ │ │ ├── __init__.py │ │ │ ├── action.py │ │ │ ├── base.py │ │ │ ├── runner.py │ │ │ ├── source.py │ │ │ └── trigger.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ ├── 4b2df8a6e1ce_add_indexes_to_datapoints.py │ │ │ │ ├── 6962f8455a6d_add_daily_summary_view.py │ │ │ │ ├── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ │ ├── d8cdc709086b_add_deployment_table.py │ │ │ │ └── d8d4cf6a178f_add_deployment_id_to_datapoint.py │ │ │ ├── analysis.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ ├── database.py │ │ │ ├── exceptions.py │ │ │ ├── query.py │ │ │ ├── typing.py │ │ │ └── utils.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_actions_actions.py │ │ ├── test_actions_runner.py │ │ ├── test_actions_triggers.py │ │ ├── test_analysis.py │ │ ├── test_cli.py │ │ ├── test_http_get.py │ │ ├── test_query.py │ │ └── test_sensor_aggregation.py ├── apd.aggregation-chapter12-ex01 │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── Connect to database.ipynb │ ├── LICENCE │ ├── Mapping.ipynb │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── Yappi Profiling.ipynb │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── actions │ │ │ ├── __init__.py │ │ │ ├── action.py │ │ │ ├── base.py │ │ │ ├── runner.py │ │ │ ├── source.py │ │ │ └── trigger.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ ├── 4b2df8a6e1ce_add_indexes_to_datapoints.py │ │ │ │ ├── 6962f8455a6d_add_daily_summary_view.py │ │ │ │ ├── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ │ ├── d8cdc709086b_add_deployment_table.py │ │ │ │ └── d8d4cf6a178f_add_deployment_id_to_datapoint.py │ │ │ ├── analysis.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ ├── database.py │ │ │ ├── exceptions.py │ │ │ ├── query.py │ │ │ ├── typing.py │ │ │ └── utils.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_actions_actions.py │ │ ├── test_actions_runner.py │ │ ├── test_actions_triggers.py │ │ ├── test_analysis.py │ │ ├── test_cli.py │ │ ├── test_http_get.py │ │ ├── test_query.py │ │ └── test_sensor_aggregation.py ├── apd.aggregation-chapter12 │ ├── .coveragerc │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── Connect to database.ipynb │ ├── LICENCE │ ├── Mapping.ipynb │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── Yappi Profiling.ipynb │ ├── pyproject.toml │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── aggregation │ │ │ ├── __init__.py │ │ │ ├── actions │ │ │ ├── __init__.py │ │ │ ├── action.py │ │ │ ├── base.py │ │ │ ├── runner.py │ │ │ ├── source.py │ │ │ └── trigger.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ ├── 4b2df8a6e1ce_add_indexes_to_datapoints.py │ │ │ │ ├── 6962f8455a6d_add_daily_summary_view.py │ │ │ │ ├── 6d2eacd5da3f_create_sensor_values_table.py │ │ │ │ ├── d8cdc709086b_add_deployment_table.py │ │ │ │ └── d8d4cf6a178f_add_deployment_id_to_datapoint.py │ │ │ ├── analysis.py │ │ │ ├── cli.py │ │ │ ├── collect.py │ │ │ ├── database.py │ │ │ ├── exceptions.py │ │ │ ├── query.py │ │ │ ├── typing.py │ │ │ └── utils.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_actions_actions.py │ │ ├── test_actions_runner.py │ │ ├── test_actions_triggers.py │ │ ├── test_analysis.py │ │ ├── test_cli.py │ │ ├── test_http_get.py │ │ ├── test_query.py │ │ └── test_sensor_aggregation.py ├── apd.sensors-chapter12 │ ├── .pre-commit-config.yaml │ ├── CHANGES.md │ ├── LICENCE │ ├── Pipfile │ ├── Pipfile.lock │ ├── README.md │ ├── plugins │ │ └── apd.sunnyboy_solar │ │ │ ├── pyproject.toml │ │ │ ├── setup.cfg │ │ │ ├── setup.py │ │ │ └── src │ │ │ └── apd │ │ │ └── sunnyboy_solar │ │ │ ├── Pipfile │ │ │ ├── __init__.py │ │ │ └── sensor.py │ ├── pytest.ini │ ├── setup.cfg │ ├── setup.py │ ├── src │ │ └── apd │ │ │ └── sensors │ │ │ ├── __init__.py │ │ │ ├── alembic │ │ │ ├── README │ │ │ ├── env.py │ │ │ ├── script.py.mako │ │ │ └── versions │ │ │ │ └── 0eeb2a54fea8_add_initial_sensor_table.py │ │ │ ├── base.py │ │ │ ├── cli.py │ │ │ ├── database.py │ │ │ ├── exceptions.py │ │ │ ├── py.typed │ │ │ ├── sensors.py │ │ │ ├── utils.py │ │ │ └── wsgi │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── serve.py │ │ │ ├── v10.py │ │ │ ├── v20.py │ │ │ ├── v21.py │ │ │ └── v30.py │ └── tests │ │ ├── __init__.py │ │ ├── test_acstatus.py │ │ ├── test_api_server.py │ │ ├── test_cpuusage.py │ │ ├── test_dht.py │ │ ├── test_ipaddresses.py │ │ ├── test_pythonversion.py │ │ ├── test_ramusage.py │ │ ├── test_sensors.py │ │ └── test_utils.py ├── listing12-01-clean_passthrough.py ├── listing12-02-sum_ints.py ├── listing12-03-process_own_output.py ├── listing12-04-wrapper_generator.py ├── listing12-05-enhanced_generator.py ├── listing12-06-mean_finder.py ├── listing12-07-wrap_enhanced_generator.py ├── listing12-08-shared_state_by_return.py ├── listing12-09-mean_with_enhanced.py ├── listing12-10-coroutine_and_queue.py ├── listing12-11-dataprocessor.py ├── listing12-12-trigger_and_action.py ├── listing12-13-valuethreshold.py ├── listing12-14-webhook.py ├── listing12-15-loggingaction.py ├── listing12-16-get_data_repeatedly.py ├── listing12-17-actions_cli.py ├── listing12-18-config.py ├── listing12-19-dataprocessor_stats.py ├── listing12-20-stats_signals.py ├── listing12-21-better_stats_signals.py ├── listing12-22-time_taken_callback.py ├── listing12-23-refeed_getdata.py └── listing12-24-refeed_actions.py ├── Contributing.md ├── LICENSE.txt ├── README.md ├── apd.aggregation ├── HEAD ├── config ├── description ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── fsmonitor-watchman.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── pre-push.sample │ ├── pre-rebase.sample │ ├── pre-receive.sample │ ├── prepare-commit-msg.sample │ └── update.sample ├── info │ └── exclude ├── objects │ └── pack │ │ ├── pack-0e7fa669b06f00ec17d49d52ad382f7f44ef9af1.idx │ │ └── pack-0e7fa669b06f00ec17d49d52ad382f7f44ef9af1.pack └── packed-refs ├── apd.sensors ├── HEAD ├── config ├── description ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── fsmonitor-watchman.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── pre-push.sample │ ├── pre-rebase.sample │ ├── pre-receive.sample │ ├── prepare-commit-msg.sample │ └── update.sample ├── info │ └── exclude ├── objects │ └── pack │ │ ├── pack-2c612f3627aa76525a86c082a5393616dab67f82.idx │ │ └── pack-2c612f3627aa76525a86c082a5393616dab67f82.pack └── packed-refs └── errata.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /9781484257920.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/9781484257920.jpg -------------------------------------------------------------------------------- /Ch01/apd.sensors-chapter01/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | 15 | [packages] 16 | psutil = "*" 17 | click = "*" 18 | adafruit-circuitpython-dht = {markers = "'arm' in platform_machine",version = "*"} 19 | 20 | [requires] 21 | -------------------------------------------------------------------------------- /Ch01/listing01-01-fizzbuzz.py: -------------------------------------------------------------------------------- 1 | for num in range(1, 101): 2 | val = '' 3 | if num % 3 == 0: 4 | val += 'Fizz' 5 | if num % 5 == 0: 6 | val += 'Buzz' 7 | if not val: 8 | val = str(num) 9 | print(val) 10 | -------------------------------------------------------------------------------- /Ch01/listing01-02-fizzbuzz_blank_lines.py: -------------------------------------------------------------------------------- 1 | for num in range(1, 101): 2 | val = '' 3 | if num % 3 == 0: 4 | val += 'Fizz' 5 | if num % 5 == 0: 6 | val += 'Buzz' 7 | 8 | if not val: 9 | val = str(num) 10 | 11 | print(val) 12 | -------------------------------------------------------------------------------- /Ch01/listing01-03-fizzbuzz_with_breakpoint.py: -------------------------------------------------------------------------------- 1 | for num in range(1, 101): 2 | val = '' 3 | if num == 15: 4 | breakpoint() 5 | if num % 3 == 0: 6 | val += 'Fizz' 7 | if num % 5 == 0: 8 | val += 'Buzz' 9 | if not val: 10 | val = str(num) 11 | print(val) 12 | -------------------------------------------------------------------------------- /Ch01/listing01-04-converted.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # In[1]: 5 | 6 | 7 | import sys 8 | sys.version_info 9 | 10 | 11 | # In[4]: 12 | 13 | 14 | import socket 15 | hostname = socket.gethostname() 16 | 17 | addresses = socket.getaddrinfo(hostname, None) 18 | 19 | for address in addresses: 20 | print(address[0].name, address[4][0]) 21 | 22 | 23 | # In[5]: 24 | 25 | 26 | import psutil 27 | 28 | 29 | # In[6]: 30 | 31 | 32 | psutil.cpu_percent() 33 | 34 | 35 | # In[7]: 36 | 37 | 38 | psutil.virtual_memory().available 39 | 40 | 41 | # In[8]: 42 | 43 | 44 | psutil.sensors_battery().power_plugged 45 | 46 | 47 | # In[ ]: 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Ch01/listing01-05-serverstatus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import socket 4 | 5 | import psutil 6 | 7 | 8 | def python_version(): 9 | return sys.version_info 10 | 11 | def ip_addresses(): 12 | hostname = socket.gethostname() 13 | 14 | addresses = socket.getaddrinfo(hostname, None) 15 | address_info = [] 16 | for address in addresses: 17 | address_info.append(address[0].name, address[4][0]) 18 | return address_info 19 | 20 | def cpu_load(): 21 | return psutil.cpu_percent() 22 | 23 | def ram_available(): 24 | return psutil.virtual_memory().available 25 | 26 | def ac_connected(): 27 | return psutil.sensors_battery().power_plugged 28 | -------------------------------------------------------------------------------- /Ch02/apd.sensors-chapter02-ex01/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | ipykernel = "*" 8 | remote-ikernel = "*" 9 | pytest = "*" 10 | 11 | [packages] 12 | psutil = "*" 13 | click = "*" 14 | adafruit-circuitpython-dht = {markers = "'arm' in platform_machine",version = "*"} 15 | sysv-ipc = {markers = "platform_machine == 'armv61' and platform_system == 'Linux'",version = "*"} 16 | numpy = "*" 17 | 18 | [requires] 19 | -------------------------------------------------------------------------------- /Ch02/apd.sensors-chapter02-ex01/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch02/apd.sensors-chapter02-ex01/tests/__init__.py -------------------------------------------------------------------------------- /Ch02/apd.sensors-chapter02-ex01/tests/test_sensors.py: -------------------------------------------------------------------------------- 1 | import sensors 2 | 3 | def test_sensors(): 4 | assert hasattr(sensors, 'python_version') -------------------------------------------------------------------------------- /Ch02/apd.sensors-chapter02-pyi/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | entry: pipenv run mypy 15 | args: ["--follow-imports=skip"] 16 | language: system 17 | types: [python] 18 | 19 | - id: flake8 20 | name: flake8 21 | entry: pipenv run flake8 22 | language: system 23 | types: [python] 24 | -------------------------------------------------------------------------------- /Ch02/apd.sensors-chapter02-pyi/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "pypi" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | 21 | [packages] 22 | psutil = "*" 23 | click = "*" 24 | adafruit-circuitpython-dht = {markers = "'arm' in platform_machine",version = "*"} 25 | 26 | [requires] 27 | 28 | [pipenv] 29 | allow_prereleases = true 30 | -------------------------------------------------------------------------------- /Ch02/apd.sensors-chapter02-pyi/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch02/apd.sensors-chapter02-pyi/setup.cfg: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = True 3 | 4 | [flake8] 5 | max-line-length = 88 -------------------------------------------------------------------------------- /Ch02/apd.sensors-chapter02-pyi/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch02/apd.sensors-chapter02-pyi/tests/__init__.py -------------------------------------------------------------------------------- /Ch02/apd.sensors-chapter02-pyi/tests/test_sensors.py: -------------------------------------------------------------------------------- 1 | from click.testing import CliRunner 2 | 3 | import pytest 4 | 5 | import sensors 6 | 7 | 8 | def test_sensors(): 9 | assert hasattr(sensors, "PythonVersion") 10 | 11 | 12 | @pytest.mark.functional 13 | def test_python_version_is_first_two_lines_of_cli_output(): 14 | runner = CliRunner() 15 | result = runner.invoke(sensors.show_sensors) 16 | python_version = str(sensors.PythonVersion()) 17 | assert ["Python Version", python_version] == result.stdout.split("\n")[:2] 18 | -------------------------------------------------------------------------------- /Ch02/listing02-04-temperature.py: -------------------------------------------------------------------------------- 1 | def celsius_to_fahrenheit(celsius): 2 | return celsius * 9 / 5 + 32 3 | 4 | def celsius_to_kelvin(celsius): 5 | return 273.15 + celsius 6 | -------------------------------------------------------------------------------- /Ch02/listing02-05-unittest_temperature/__pycache__/temperature.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch02/listing02-05-unittest_temperature/__pycache__/temperature.cpython-37.pyc -------------------------------------------------------------------------------- /Ch02/listing02-05-unittest_temperature/temperature.py: -------------------------------------------------------------------------------- 1 | def celsius_to_fahrenheit(celsius): 2 | return celsius * 9 / 5 + 32 3 | 4 | def celsius_to_kelvin(celsius): 5 | return 273.15 + celsius 6 | -------------------------------------------------------------------------------- /Ch02/listing02-05-unittest_temperature/test_unittest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from temperature import celsius_to_fahrenheit 3 | 4 | 5 | class TestTemperatureConversion(unittest.TestCase): 6 | 7 | def test_celsius_to_fahrenheit(self): 8 | self.assertEqual(celsius_to_fahrenheit(21), 69.8) 9 | 10 | def test_celsius_to_fahrenheit_equivlance_point(self): 11 | self.assertEqual(celsius_to_fahrenheit(-40), -40) 12 | 13 | def test_celsius_to_fahrenheit_float(self): 14 | self.assertEqual(celsius_to_fahrenheit(21.2), 70.16) 15 | 16 | def test_celsius_to_fahrenheit_string(self): 17 | with self.assertRaises(TypeError): 18 | f = celsius_to_fahrenheit("21") 19 | 20 | 21 | if __name__ == '__main__': 22 | unittest.main() 23 | -------------------------------------------------------------------------------- /Ch02/listing02-06-pytest_temperature/temperature.py: -------------------------------------------------------------------------------- 1 | def celsius_to_fahrenheit(celsius): 2 | return celsius * 9 / 5 + 32 3 | 4 | def celsius_to_kelvin(celsius): 5 | return 273.15 + celsius 6 | -------------------------------------------------------------------------------- /Ch02/listing02-06-pytest_temperature/test_pytest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from .temperature import celsius_to_fahrenheit 3 | 4 | 5 | def test_celsius_to_fahrenheit(): 6 | c = 21 7 | f = celsius_to_fahrenheit(c) 8 | assert f == 69.8 9 | 10 | def test_celsius_to_fahrenheit_equivlance_point(): 11 | c = -40 12 | f = celsius_to_fahrenheit(c) 13 | assert f == -40 14 | 15 | def test_celsius_to_fahrenheit_float(): 16 | c = 21.2 17 | f = celsius_to_fahrenheit(c) 18 | assert f == 70.16 19 | 20 | def test_celsius_to_fahrenheit_string(): 21 | c = "21" 22 | with pytest.raises(TypeError): 23 | f = celsius_to_fahrenheit(c) 24 | -------------------------------------------------------------------------------- /Ch02/listing02-08/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "pypi" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | 16 | [packages] 17 | psutil = "*" 18 | click = "*" 19 | adafruit-circuitpython-dht = {markers = "'arm' in platform_machine",version = "*"} 20 | 21 | [requires] 22 | -------------------------------------------------------------------------------- /Ch02/listing02-08/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch02/listing02-08/tests/__init__.py -------------------------------------------------------------------------------- /Ch02/listing02-08/tests/test_sensors.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from click.testing import CliRunner 4 | import pytest 5 | 6 | import sensors 7 | 8 | 9 | def test_sensors(): 10 | assert hasattr(sensors, 'PythonVersion') 11 | 12 | @pytest.mark.functional 13 | def test_python_version_is_first_two_lines_of_cli_output(): 14 | runner = CliRunner() 15 | result = runner.invoke(sensors.show_sensors) 16 | python_version = str(sensors.PythonVersion()) 17 | assert ["Python Version", python_version] == result.stdout.split("\n")[:2] -------------------------------------------------------------------------------- /Ch02/listing02-10/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | entry: pipenv run mypy 15 | args: ["--follow-imports=skip"] 16 | language: system 17 | types: [python] 18 | 19 | - id: flake8 20 | name: flake8 21 | entry: pipenv run flake8 22 | language: system 23 | types: [python] 24 | -------------------------------------------------------------------------------- /Ch02/listing02-10/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "pypi" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | 21 | [packages] 22 | psutil = "*" 23 | click = "*" 24 | adafruit-circuitpython-dht = {markers = "'arm' in platform_machine",version = "*"} 25 | 26 | [requires] 27 | 28 | [pipenv] 29 | allow_prereleases = true 30 | -------------------------------------------------------------------------------- /Ch02/listing02-10/incorrect.py: -------------------------------------------------------------------------------- 1 | import sensors 2 | 3 | sensor = sensors.CPULoad() 4 | print("The CPU load is " + sensor.value()) 5 | -------------------------------------------------------------------------------- /Ch02/listing02-10/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch02/listing02-10/setup.cfg: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = True 3 | 4 | [flake8] 5 | max-line-length = 88 -------------------------------------------------------------------------------- /Ch02/listing02-10/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch02/listing02-10/tests/__init__.py -------------------------------------------------------------------------------- /Ch02/listing02-10/tests/test_sensors.py: -------------------------------------------------------------------------------- 1 | from click.testing import CliRunner 2 | 3 | import pytest 4 | 5 | import sensors 6 | 7 | 8 | def test_sensors(): 9 | assert hasattr(sensors, "PythonVersion") 10 | 11 | 12 | @pytest.mark.functional 13 | def test_python_version_is_first_two_lines_of_cli_output(): 14 | runner = CliRunner() 15 | result = runner.invoke(sensors.show_sensors) 16 | python_version = str(sensors.PythonVersion()) 17 | assert ["Python Version", python_version] == result.stdout.split("\n")[:2] 18 | -------------------------------------------------------------------------------- /Ch02/listing02-11/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | entry: pipenv run mypy 15 | args: ["--follow-imports=skip"] 16 | language: system 17 | types: [python] 18 | 19 | - id: flake8 20 | name: flake8 21 | entry: pipenv run flake8 22 | language: system 23 | types: [python] 24 | -------------------------------------------------------------------------------- /Ch02/listing02-11/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "pypi" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | 21 | [packages] 22 | psutil = "*" 23 | click = "*" 24 | adafruit-circuitpython-dht = {markers = "'arm' in platform_machine",version = "*"} 25 | 26 | [requires] 27 | 28 | [pipenv] 29 | allow_prereleases = true 30 | -------------------------------------------------------------------------------- /Ch02/listing02-11/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch02/listing02-11/setup.cfg: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = True 3 | 4 | [flake8] 5 | max-line-length = 88 -------------------------------------------------------------------------------- /Ch02/listing02-11/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch02/listing02-11/tests/__init__.py -------------------------------------------------------------------------------- /Ch02/listing02-11/tests/test_sensors.py: -------------------------------------------------------------------------------- 1 | from click.testing import CliRunner 2 | 3 | import pytest 4 | 5 | import sensors 6 | 7 | 8 | def test_sensors(): 9 | assert hasattr(sensors, "PythonVersion") 10 | 11 | 12 | @pytest.mark.functional 13 | def test_python_version_is_first_two_lines_of_cli_output(): 14 | runner = CliRunner() 15 | result = runner.invoke(sensors.show_sensors) 16 | python_version = str(sensors.PythonVersion()) 17 | assert ["Python Version", python_version] == result.stdout.split("\n")[:2] 18 | -------------------------------------------------------------------------------- /Ch02/listing02-12/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | entry: pipenv run mypy 15 | args: ["--follow-imports=skip"] 16 | language: system 17 | types: [python] 18 | 19 | - id: flake8 20 | name: flake8 21 | entry: pipenv run flake8 22 | language: system 23 | types: [python] 24 | -------------------------------------------------------------------------------- /Ch02/listing02-12/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "pypi" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | 21 | [packages] 22 | psutil = "*" 23 | click = "*" 24 | adafruit-circuitpython-dht = {markers = "'arm' in platform_machine",version = "*"} 25 | 26 | [requires] 27 | 28 | [pipenv] 29 | allow_prereleases = true 30 | -------------------------------------------------------------------------------- /Ch02/listing02-12/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch02/listing02-12/setup.cfg: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = True 3 | 4 | [flake8] 5 | max-line-length = 88 -------------------------------------------------------------------------------- /Ch02/listing02-12/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch02/listing02-12/tests/__init__.py -------------------------------------------------------------------------------- /Ch02/listing02-12/tests/test_sensors.py: -------------------------------------------------------------------------------- 1 | from click.testing import CliRunner 2 | 3 | import pytest 4 | 5 | import sensors 6 | 7 | 8 | def test_sensors(): 9 | assert hasattr(sensors, "PythonVersion") 10 | 11 | 12 | @pytest.mark.functional 13 | def test_python_version_is_first_two_lines_of_cli_output(): 14 | runner = CliRunner() 15 | result = runner.invoke(sensors.show_sensors) 16 | python_version = str(sensors.PythonVersion()) 17 | assert ["Python Version", python_version] == result.stdout.split("\n")[:2] 18 | -------------------------------------------------------------------------------- /Ch02/listing02-13-.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | entry: pipenv run mypy 15 | args: ["--follow-imports=skip"] 16 | language: system 17 | types: [python] 18 | 19 | - id: flake8 20 | name: flake8 21 | entry: pipenv run flake8 22 | language: system 23 | types: [python] 24 | -------------------------------------------------------------------------------- /Ch03/apd.sensors-chapter03/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | entry: pipenv run mypy 15 | args: ["--follow-imports=skip"] 16 | language: system 17 | types: [python] 18 | 19 | - id: flake8 20 | name: flake8 21 | entry: pipenv run flake8 22 | language: system 23 | types: [python] 24 | -------------------------------------------------------------------------------- /Ch03/apd.sensors-chapter03/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (2019-06-20) 4 | 5 | * Added initial sensors (Matthew Wilkes) 6 | -------------------------------------------------------------------------------- /Ch03/apd.sensors-chapter03/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | 23 | [packages] 24 | apd-sensors = {editable = true,path = "."} 25 | 26 | [requires] 27 | 28 | [pipenv] 29 | allow_prereleases = true 30 | -------------------------------------------------------------------------------- /Ch03/apd.sensors-chapter03/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch03/apd.sensors-chapter03/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch03/apd.sensors-chapter03/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" -------------------------------------------------------------------------------- /Ch03/apd.sensors-chapter03/src/apd/sensors/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch03/apd.sensors-chapter03/src/apd/sensors/py.typed -------------------------------------------------------------------------------- /Ch03/apd.sensors-chapter03/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch03/apd.sensors-chapter03/tests/__init__.py -------------------------------------------------------------------------------- /Ch03/apd.sensors-chapter03/tests/test_sensors.py: -------------------------------------------------------------------------------- 1 | from click.testing import CliRunner 2 | 3 | import pytest 4 | 5 | import apd.sensors.sensors 6 | 7 | 8 | def test_sensors(): 9 | assert hasattr(apd.sensors.sensors, "PythonVersion") 10 | 11 | 12 | @pytest.mark.functional 13 | def test_python_version_is_first_two_lines_of_cli_output(): 14 | runner = CliRunner() 15 | result = runner.invoke(apd.sensors.sensors.show_sensors) 16 | python_version = str(apd.sensors.sensors.PythonVersion()) 17 | assert ["Python Version", python_version] == result.stdout.split("\n")[:2] 18 | -------------------------------------------------------------------------------- /Ch03/listing03-01-setup.cfg: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = True 3 | 4 | [flake8] 5 | max-line-length = 88 6 | 7 | [metadata] 8 | name = apd.sensors 9 | version = attr: apd.sensors.VERSION 10 | description = APD Sensor package 11 | long_description = file: README.md, CHANGES.md, LICENCE 12 | keywords = iot 13 | license = MIT 14 | classifiers = 15 | Programming Language :: Python :: 3 16 | Programming Language :: Python :: 3.7 17 | 18 | [options] 19 | zip_safe = False 20 | include_package_data = True 21 | package-dir = 22 | =src 23 | packages = find-namespace: 24 | install_requires = 25 | psutil 26 | click 27 | adafruit-circuitpython-dht ; 'arm' in platform_machine 28 | 29 | [options.packages.find] 30 | where = src 31 | -------------------------------------------------------------------------------- /Ch03/listing03-02-indexserver.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Custom Index Server for Python distributions 3 | After=multi-user.target 4 | 5 | [Service] 6 | Type=idle 7 | User=indexserver 8 | WorkingDirectory=/home/indexserver/indexserver 9 | ExecStart=/home/indexserver/.local/bin/pipenv run pypi-server -p 8080 -P htaccess ../packages 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-parsing/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | entry: pipenv run mypy 15 | args: ["--follow-imports=skip"] 16 | language: system 17 | types: [python] 18 | 19 | - id: flake8 20 | name: flake8 21 | entry: pipenv run flake8 22 | language: system 23 | types: [python] 24 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-parsing/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (2019-06-20) 4 | 5 | * Added initial sensors (Matthew Wilkes) 6 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-parsing/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | 23 | [packages] 24 | apd-sensors = {editable = true,path = "."} 25 | 26 | [requires] 27 | 28 | [pipenv] 29 | allow_prereleases = true 30 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-parsing/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-parsing/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-parsing/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-parsing/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.1.0dev1" 2 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-parsing/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch04/apd.sensors-chapter04-click-parsing/tests/__init__.py -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-subcommands/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | entry: pipenv run mypy 15 | args: ["--follow-imports=skip"] 16 | language: system 17 | types: [python] 18 | 19 | - id: flake8 20 | name: flake8 21 | entry: pipenv run flake8 22 | language: system 23 | types: [python] 24 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-subcommands/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (2019-06-20) 4 | 5 | * Added initial sensors (Matthew Wilkes) 6 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-subcommands/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | 23 | [packages] 24 | apd-sensors = {editable = true,path = "."} 25 | 26 | [requires] 27 | 28 | [pipenv] 29 | allow_prereleases = true 30 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-subcommands/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-subcommands/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-subcommands/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-subcommands/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "2.0.0dev1" 2 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-subcommands/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch04/apd.sensors-chapter04-click-subcommands/tests/__init__.py -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-click-subcommands/tests/test_sensors.py: -------------------------------------------------------------------------------- 1 | from click.testing import CliRunner 2 | import pytest 3 | 4 | import apd.sensors.sensors 5 | 6 | 7 | def test_sensors(): 8 | assert hasattr(apd.sensors.sensors, "PythonVersion") 9 | 10 | 11 | @pytest.mark.functional 12 | def test_python_version_is_first_two_lines_of_cli_output(): 13 | runner = CliRunner() 14 | result = runner.invoke(apd.sensors.sensors.show_sensors) 15 | python_version = str(apd.sensors.sensors.PythonVersion()) 16 | assert ["Python Version", python_version] == result.stdout.split("\n")[:2] 17 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser-local/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | entry: pipenv run mypy 15 | args: ["--follow-imports=skip"] 16 | language: system 17 | types: [python] 18 | 19 | - id: flake8 20 | name: flake8 21 | entry: pipenv run flake8 22 | language: system 23 | types: [python] 24 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser-local/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (2019-06-20) 4 | 5 | * Added initial sensors (Matthew Wilkes) 6 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser-local/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | 23 | [packages] 24 | apd-sensors = {editable = true,path = "."} 25 | 26 | [requires] 27 | 28 | [pipenv] 29 | allow_prereleases = true 30 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser-local/config.cfg: -------------------------------------------------------------------------------- 1 | [config] 2 | plugins = 3 | PythonVersion 4 | IPAddress 5 | CPULoad 6 | RAMAvailable 7 | ACStatus 8 | Temperature 9 | Humidity 10 | 11 | [IPAddress] 12 | plugin = apd.sensors.sensors:IPAddresses 13 | 14 | [PythonVersion] 15 | plugin = apd.sensors.sensors:PythonVersion 16 | 17 | [CPULoad] 18 | plugin = apd.sensors.sensors:CPULoad 19 | 20 | [RAMAvailable] 21 | plugin = apd.sensors.sensors:RAMAvailable 22 | 23 | [ACStatus] 24 | plugin = apd.sensors.sensors:ACStatus 25 | 26 | [Temperature] 27 | plugin = apd.sensors.sensors:Temperature 28 | pin = D4 29 | 30 | [Humidity] 31 | plugin = apd.sensors.sensors:Temperature 32 | pin = D4 33 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser-local/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser-local/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser-local/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.1.0dev1" 2 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser-local/src/apd/sensors/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch04/apd.sensors-chapter04-configparser-local/src/apd/sensors/py.typed -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser-local/tests/default_test_config.cfg: -------------------------------------------------------------------------------- 1 | [config] 2 | plugins = 3 | PythonVersion 4 | IPAddress 5 | 6 | [PythonVersion] 7 | plugin = apd.sensors.sensors:PythonVersion 8 | 9 | [IPAddress] 10 | plugin = apd.sensors.sensors:IPAddresses 11 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | entry: pipenv run mypy 15 | args: ["--follow-imports=skip"] 16 | language: system 17 | types: [python] 18 | 19 | - id: flake8 20 | name: flake8 21 | entry: pipenv run flake8 22 | language: system 23 | types: [python] 24 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (2019-06-20) 4 | 5 | * Added initial sensors (Matthew Wilkes) 6 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | 23 | [packages] 24 | apd-sensors = {editable = true,path = "."} 25 | 26 | [requires] 27 | 28 | [pipenv] 29 | allow_prereleases = true 30 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.1.0dev1" 2 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-configparser/src/apd/sensors/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch04/apd.sensors-chapter04-configparser/src/apd/sensors/py.typed -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-ex01/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | entry: pipenv run mypy 15 | args: ["--follow-imports=skip"] 16 | language: system 17 | types: [python] 18 | 19 | - id: flake8 20 | name: flake8 21 | entry: pipenv run flake8 22 | language: system 23 | types: [python] 24 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-ex01/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (2019-06-20) 4 | 5 | * Added initial sensors (Matthew Wilkes) 6 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-ex01/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | 23 | [packages] 24 | apd-sensors = {editable = true,path = "."} 25 | 26 | [requires] 27 | 28 | [pipenv] 29 | allow_prereleases = true 30 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-ex01/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-ex01/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-ex01/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-ex01/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.1.0dev1" 2 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04-ex01/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch04/apd.sensors-chapter04-ex01/tests/__init__.py -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (2019-06-20) 4 | 5 | * Added initial sensors (Matthew Wilkes) 6 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | webtest = "*" 23 | 24 | [packages] 25 | apd-sensors = {editable = true,path = "."} 26 | apd-sunnyboy-solar = {editable = true,path = "./plugins/apd.sunnyboy_solar"} 27 | 28 | [requires] 29 | 30 | [pipenv] 31 | allow_prereleases = true 32 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04/plugins/apd.sunnyboy_solar/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04/plugins/apd.sunnyboy_solar/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch04/apd.sensors-chapter04/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.2.0" 2 | -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04/src/apd/sensors/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch04/apd.sensors-chapter04/src/apd/sensors/py.typed -------------------------------------------------------------------------------- /Ch04/apd.sensors-chapter04/tests/test_api_server.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from webtest import TestApp 3 | 4 | from apd.sensors.wsgi import sensor_values 5 | from apd.sensors.sensors import PythonVersion 6 | 7 | 8 | @pytest.fixture(scope="session") 9 | def subject(): 10 | return sensor_values 11 | 12 | 13 | @pytest.fixture(scope="session") 14 | def api_server(subject): 15 | return TestApp(subject) 16 | 17 | 18 | @pytest.mark.functional 19 | def test_sensor_values_returned_as_json(api_server): 20 | value = api_server.get("/sensors/").json 21 | python_version = PythonVersion().value() 22 | 23 | sensor_names = value.keys() 24 | assert "Python Version" in sensor_names 25 | assert value["Python Version"] == list(python_version) 26 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05-pintbased/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05-pintbased/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | webtest = "*" 23 | apd-sensors = {editable = true,extras = ["webapp"],path = "."} 24 | 25 | [packages] 26 | apd-sensors = {editable = true,path = "."} 27 | apd-sunnyboy-solar = {editable = true,path = "./plugins/apd.sunnyboy_solar"} 28 | waitress = "*" 29 | pint = "*" 30 | 31 | [requires] 32 | 33 | [pipenv] 34 | allow_prereleases = true 35 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05-pintbased/plugins/apd.sunnyboy_solar/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05-pintbased/plugins/apd.sunnyboy_solar/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05-pintbased/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch05/apd.sensors-chapter05-pintbased/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05-pintbased/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05-pintbased/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05-pintbased/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05-pintbased/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "2.0.0" 2 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05-pintbased/src/apd/sensors/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch05/apd.sensors-chapter05-pintbased/src/apd/sensors/py.typed -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05-pintbased/src/apd/sensors/wsgi/__init__.py: -------------------------------------------------------------------------------- 1 | import flask 2 | 3 | from .base import set_up_config 4 | from . import v10 5 | from . import v20 6 | 7 | 8 | __all__ = ["app", "set_up_config"] 9 | 10 | app = flask.Flask(__name__) 11 | app.register_blueprint(v10.version, url_prefix="/v/1.0") 12 | app.register_blueprint(v20.version, url_prefix="/v/2.0") 13 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05-pintbased/src/apd/sensors/wsgi/serve.py: -------------------------------------------------------------------------------- 1 | from . import app 2 | from .base import set_up_config 3 | 4 | if __name__ == "__main__": 5 | import wsgiref.simple_server 6 | 7 | set_up_config(None, app) 8 | 9 | with wsgiref.simple_server.make_server("", 8000, app) as server: 10 | server.serve_forever() 11 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 2.0.0 (2019-09-08) 4 | 5 | * Add `to_json_compatible` and `from_json_compatible` methods to Sensor 6 | to facilitate better HTTP API (Matthew Wilkes) 7 | 8 | * HTTP API is now versioned. The API from 1.3.0 is available at /v/1.0 9 | and an updated version is at /v/2.0 (Matthew Wilkes) 10 | 11 | ### 1.3.0 (2019-08-20) 12 | 13 | * WSGI HTTP API support added (Matthew Wilkes) 14 | 15 | ### 1.2.0 (2019-08-05) 16 | 17 | * Add external plugin support through `apd.sensors.sensors` entrypoint (Matthew Wilkes) 18 | 19 | ### 1.1.0 (2019-07-12) 20 | 21 | * Add --develop argument (Matthew Wilkes) 22 | 23 | ### 1.0.1 (2019-06-20) 24 | 25 | * Fix broken 1.0.0 release (Matthew Wilkes) 26 | 27 | ### 1.0.0 (2019-06-20) 28 | 29 | * Added initial sensors (Matthew Wilkes) 30 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | webtest = "*" 23 | apd-sensors = {editable = true,extras = ["webapp"],path = "."} 24 | 25 | [packages] 26 | apd-sensors = {editable = true,path = "."} 27 | apd-sunnyboy-solar = {editable = true,path = "./plugins/apd.sunnyboy_solar"} 28 | waitress = "*" 29 | pint = "*" 30 | 31 | [requires] 32 | 33 | [pipenv] 34 | allow_prereleases = true 35 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/plugins/apd.sunnyboy_solar/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/plugins/apd.sunnyboy_solar/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch05/apd.sensors-chapter05/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "2.0.0" 2 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/src/apd/sensors/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch05/apd.sensors-chapter05/src/apd/sensors/py.typed -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/src/apd/sensors/wsgi/__init__.py: -------------------------------------------------------------------------------- 1 | import flask 2 | 3 | from .base import set_up_config 4 | from . import v10 5 | from . import v20 6 | 7 | 8 | __all__ = ["app", "set_up_config"] 9 | 10 | app = flask.Flask(__name__) 11 | app.register_blueprint(v10.version, url_prefix="/v/1.0") 12 | app.register_blueprint(v20.version, url_prefix="/v/2.0") 13 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/src/apd/sensors/wsgi/serve.py: -------------------------------------------------------------------------------- 1 | from . import app 2 | from .base import set_up_config 3 | 4 | if __name__ == "__main__": 5 | import wsgiref.simple_server 6 | 7 | set_up_config(None, app) 8 | 9 | with wsgiref.simple_server.make_server("", 8000, app) as server: 10 | server.serve_forever() 11 | -------------------------------------------------------------------------------- /Ch05/apd.sensors-chapter05/src/apd/sensors/wsgi/v10.py: -------------------------------------------------------------------------------- 1 | import json 2 | import typing as t 3 | 4 | import flask 5 | 6 | from apd.sensors.cli import get_sensors 7 | from .base import require_api_key 8 | 9 | version = flask.Blueprint(__name__, __name__) 10 | 11 | 12 | @version.route("/sensors/") 13 | @require_api_key 14 | def sensor_values() -> t.Tuple[t.Dict[str, t.Any], int, t.Dict[str, str]]: 15 | headers = {"Content-Security-Policy": "default-src 'none'"} 16 | data = {} 17 | for sensor in get_sensors(): 18 | value = sensor.value() 19 | try: 20 | json.dumps(value) 21 | except TypeError: 22 | # This value isn't JSON serializable, skip it 23 | continue 24 | else: 25 | data[sensor.title] = sensor.value() 26 | return data, 200, headers 27 | -------------------------------------------------------------------------------- /Ch05/listing05-01-helloworld.py: -------------------------------------------------------------------------------- 1 | import wsgiref.simple_server 2 | 3 | def hello_world(environ, start_response): 4 | headers = [ 5 | ("Content-type", "text/plain; charset=utf-8"), 6 | ("Content-Security-Policy", "default-src 'none';"), 7 | ] 8 | start_response("200 OK", headers) 9 | return [b"hello", b" ", b"world"] 10 | 11 | if __name__ == "__main__": 12 | with wsgiref.simple_server.make_server("", 8000, hello_world) as server: 13 | server.serve_forever() 14 | -------------------------------------------------------------------------------- /Ch05/listing05-02-helloworld-incremental.py: -------------------------------------------------------------------------------- 1 | import time 2 | import wsgiref.simple_server 3 | 4 | 5 | def hello_world(environ, start_response): 6 | headers = [ 7 | ("Content-type", "text/html; charset=utf-8"), 8 | ("Content-Security-Policy", "default-src 'none';"), 9 | ] 10 | start_response("200 OK", headers) 11 | yield b"
" 12 | for i in range(20): 13 | yield b"hello world
" 14 | time.sleep(1) 15 | yield b"" 16 | 17 | if __name__ == "__main__": 18 | with wsgiref.simple_server.make_server("", 8000, hello_world) as server: 19 | server.serve_forever() 20 | -------------------------------------------------------------------------------- /Ch06/apd.aggregation-chapter06/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch06/apd.aggregation-chapter06/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch06/apd.aggregation-chapter06/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch06/apd.aggregation-chapter06/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | mypy = "*" 10 | flake8 = "*" 11 | black = "*" 12 | pre-commit = "*" 13 | wheel = "*" 14 | twine = "*" 15 | 16 | [packages] 17 | apd-aggregation = {editable = true,path = "."} 18 | 19 | [requires] 20 | 21 | [pipenv] 22 | allow_prereleases = true 23 | -------------------------------------------------------------------------------- /Ch06/apd.aggregation-chapter06/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch06/apd.aggregation-chapter06/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch06/apd.aggregation-chapter06/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch06/apd.aggregation-chapter06/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch06/apd.aggregation-chapter06/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch06/apd.aggregation-chapter06/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch06/apd.aggregation-chapter06/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch06/apd.aggregation-chapter06/tests/__init__.py -------------------------------------------------------------------------------- /Ch06/apd.aggregation-chapter06/tests/conftest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch06/apd.aggregation-chapter06/tests/conftest.py -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-aio/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-aio/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-aio/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-aio/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | mypy = "*" 10 | flake8 = "*" 11 | black = "*" 12 | pre-commit = "*" 13 | wheel = "*" 14 | twine = "*" 15 | 16 | [packages] 17 | apd-aggregation = {editable = true,path = "."} 18 | 19 | [requires] 20 | 21 | [pipenv] 22 | allow_prereleases = true 23 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-aio/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-aio/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-aio/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-aio/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-aio/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-aio/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-aio/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch07/apd.aggregation-chapter07-aio/tests/__init__.py -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-aio/tests/conftest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch07/apd.aggregation-chapter07-aio/tests/conftest.py -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-multiprocess/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-multiprocess/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-multiprocess/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-multiprocess/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | mypy = "*" 10 | flake8 = "*" 11 | black = "*" 12 | pre-commit = "*" 13 | wheel = "*" 14 | twine = "*" 15 | 16 | [packages] 17 | apd-aggregation = {editable = true,path = "."} 18 | 19 | [requires] 20 | 21 | [pipenv] 22 | allow_prereleases = true 23 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-multiprocess/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-multiprocess/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-multiprocess/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-multiprocess/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-multiprocess/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-multiprocess/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-multiprocess/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch07/apd.aggregation-chapter07-multiprocess/tests/__init__.py -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-multiprocess/tests/conftest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch07/apd.aggregation-chapter07-multiprocess/tests/conftest.py -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-nbio/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-nbio/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-nbio/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-nbio/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | mypy = "*" 10 | flake8 = "*" 11 | black = "*" 12 | pre-commit = "*" 13 | wheel = "*" 14 | twine = "*" 15 | 16 | [packages] 17 | apd-aggregation = {editable = true,path = "."} 18 | 19 | [requires] 20 | 21 | [pipenv] 22 | allow_prereleases = true 23 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-nbio/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-nbio/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-nbio/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-nbio/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-nbio/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-nbio/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-nbio/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch07/apd.aggregation-chapter07-nbio/tests/__init__.py -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-nbio/tests/conftest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch07/apd.aggregation-chapter07-nbio/tests/conftest.py -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-simple-threads/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-simple-threads/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-simple-threads/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-simple-threads/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | mypy = "*" 10 | flake8 = "*" 11 | black = "*" 12 | pre-commit = "*" 13 | wheel = "*" 14 | twine = "*" 15 | 16 | [packages] 17 | apd-aggregation = {editable = true,path = "."} 18 | 19 | [requires] 20 | 21 | [pipenv] 22 | allow_prereleases = true 23 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-simple-threads/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-simple-threads/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-simple-threads/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-simple-threads/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-simple-threads/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-simple-threads/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-simple-threads/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch07/apd.aggregation-chapter07-simple-threads/tests/__init__.py -------------------------------------------------------------------------------- /Ch07/apd.aggregation-chapter07-simple-threads/tests/conftest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch07/apd.aggregation-chapter07-simple-threads/tests/conftest.py -------------------------------------------------------------------------------- /Ch07/listing07-02-increment_dis.py: -------------------------------------------------------------------------------- 1 | num = 0 2 | 3 | def increment(): 4 | global num 5 | num += 1 # 5 0 LOAD_GLOBAL 0 (num) 6 | # 2 LOAD_CONST 1 (1) 7 | # 4 INPLACE_ADD 8 | # 6 STORE_GLOBAL 0 (num) 9 | 10 | return None # 10 8 LOAD_CONST 0 (None) 11 | # 10 RETURN_VALUE 12 | 13 | if __name__ == "__main__": 14 | import dis 15 | dis.dis(increment) -------------------------------------------------------------------------------- /Ch07/listing07-06-reentrantlocks.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import ThreadPoolExecutor 2 | import threading 3 | 4 | num = 0 5 | 6 | numlock = threading.RLock() 7 | 8 | def fiddle_with_num(): 9 | global num 10 | with numlock: 11 | if num == 4: 12 | num = -50 13 | 14 | def increment(): 15 | global num 16 | with numlock: 17 | num += 1 18 | fiddle_with_num() 19 | 20 | if __name__ == "__main__": 21 | with ThreadPoolExecutor() as pool: 22 | for i in range(8): 23 | pool.submit(increment) 24 | print(num) -------------------------------------------------------------------------------- /Ch07/listing07-11-async-increment.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | async def increment(): 4 | return 1 5 | 6 | async def decrement(): 7 | return -1 8 | 9 | async def onehundred(): 10 | num = 0 11 | for i in range(100): 12 | num += await increment() 13 | num += await decrement() 14 | return num 15 | 16 | if __name__ == "__main__": 17 | print(asyncio.run(onehundred())) 18 | -------------------------------------------------------------------------------- /Ch07/listing07-12-list_of_awaitables.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import typing as t 3 | 4 | 5 | async def number(num: int) -> int: 6 | return num 7 | 8 | def numbers() -> t.Iterable[t.Awaitable[int]]: 9 | return [number(2), number(3)] 10 | 11 | async def add_all(numbers: t.Iterable[t.Awaitable[int]]) -> int: 12 | total = 0 13 | for num in numbers: 14 | total += await num 15 | return total 16 | 17 | if __name__ == "__main__": 18 | to_add = numbers() 19 | result = asyncio.run(add_all(to_add)) 20 | print(result) 21 | -------------------------------------------------------------------------------- /Ch07/listing07-13-awaitable_list.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import typing as t 3 | 4 | 5 | async def number(num: int) -> int: 6 | return num 7 | 8 | async def numbers() -> t.Iterable[int]: 9 | return [await number(2), await number(3)] 10 | 11 | async def add_all(nums: t.Awaitable[t.Iterable[int]]) -> int: 12 | total = 0 13 | for num in await nums: 14 | total += num 15 | return total 16 | 17 | if __name__ == "__main__": 18 | to_add = numbers() 19 | result = asyncio.run(add_all(to_add)) 20 | print(result) -------------------------------------------------------------------------------- /Ch07/listing07-14-async_for.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import typing as t 3 | 4 | 5 | async def number(num: int) -> int: 6 | return num 7 | 8 | async def numbers() -> t.AsyncIterator[int]: 9 | yield await number(2) 10 | yield await number(3) 11 | 12 | async def add_all(nums: t.AsyncIterator[int]) -> int: 13 | total = 0 14 | async for num in nums: 15 | total += num 16 | return total 17 | 18 | if __name__ == "__main__": 19 | to_add = numbers() 20 | result = asyncio.run(add_all(to_add)) 21 | print(result) -------------------------------------------------------------------------------- /Ch07/listing07-15-awaitable_gather.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import typing as t 3 | 4 | 5 | async def number(num: int) -> int: 6 | return num 7 | 8 | async def numbers() -> t.Iterable[int]: 9 | return await asyncio.gather( 10 | number(2), 11 | number(3) 12 | ) 13 | 14 | async def add_all(nums: t.Awaitable[t.Iterable[int]]) -> int: 15 | total = 0 16 | for num in await nums: 17 | total += num 18 | return total 19 | 20 | if __name__ == "__main__": 21 | to_add = numbers() 22 | result = asyncio.run(add_all(to_add)) 23 | print(result) -------------------------------------------------------------------------------- /Ch07/listing07-16-async_increment_unsafe.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | 4 | num = 0 5 | 6 | async def offset(): 7 | await asyncio.sleep(0) 8 | return 1 9 | 10 | async def increment(): 11 | global num 12 | num += await offset() 13 | 14 | async def onehundred(): 15 | tasks = [] 16 | for i in range(100): 17 | tasks.append(increment()) 18 | await asyncio.gather(*tasks) 19 | return num 20 | 21 | if __name__ == "__main__": 22 | print(asyncio.run(onehundred())) 23 | -------------------------------------------------------------------------------- /Ch07/listing07-17-async_increment_safe.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | 4 | num = 0 5 | 6 | async def offset(): 7 | await asyncio.sleep(0) 8 | return 1 9 | 10 | async def increment(numlock): 11 | global num 12 | async with numlock: 13 | num += await offset() 14 | 15 | async def onehundred(): 16 | tasks = [] 17 | numlock = asyncio.Lock() 18 | 19 | for i in range(100): 20 | tasks.append(increment(numlock)) 21 | await asyncio.gather(*tasks) 22 | return num 23 | 24 | if __name__ == "__main__": 25 | print(asyncio.run(onehundred())) 26 | -------------------------------------------------------------------------------- /Ch08/apd.aggregation-chapter08/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch08/apd.aggregation-chapter08/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch08/apd.aggregation-chapter08/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch08/apd.aggregation-chapter08/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pytest-asyncio = "*" 10 | mypy = "*" 11 | flake8 = "*" 12 | black = "*" 13 | pre-commit = "*" 14 | wheel = "*" 15 | twine = "*" 16 | sqlalchemy-stubs = "*" 17 | 18 | [packages] 19 | apd-aggregation = {editable = true,path = "."} 20 | apd-sensors = {editable = true,extras = ["webapp"],path = "./../code"} 21 | 22 | [requires] 23 | 24 | [pipenv] 25 | allow_prereleases = true 26 | -------------------------------------------------------------------------------- /Ch08/apd.aggregation-chapter08/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch08/apd.aggregation-chapter08/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch08/apd.aggregation-chapter08/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they start a HTTP server -------------------------------------------------------------------------------- /Ch08/apd.aggregation-chapter08/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch08/apd.aggregation-chapter08/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch08/apd.aggregation-chapter08/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch08/apd.aggregation-chapter08/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch08/apd.aggregation-chapter08/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch08/apd.aggregation-chapter08/tests/__init__.py -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 2.0.0 (2019-09-08) 4 | 5 | * Add `to_json_compatible` and `from_json_compatible` methods to Sensor 6 | to facilitate better HTTP API (Matthew Wilkes) 7 | 8 | * HTTP API is now versioned. The API from 1.3.0 is available at /v/1.0 9 | and an updated version is at /v/2.0 (Matthew Wilkes) 10 | 11 | ### 1.3.0 (2019-08-20) 12 | 13 | * WSGI HTTP API support added (Matthew Wilkes) 14 | 15 | ### 1.2.0 (2019-08-05) 16 | 17 | * Add external plugin support through `apd.sensors.sensors` entrypoint (Matthew Wilkes) 18 | 19 | ### 1.1.0 (2019-07-12) 20 | 21 | * Add --develop argument (Matthew Wilkes) 22 | 23 | ### 1.0.1 (2019-06-20) 24 | 25 | * Fix broken 1.0.0 release (Matthew Wilkes) 26 | 27 | ### 1.0.0 (2019-06-20) 28 | 29 | * Added initial sensors (Matthew Wilkes) 30 | -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | webtest = "*" 23 | apd-sensors = {editable = true,extras = ["webapp"],path = "."} 24 | 25 | [packages] 26 | apd-sensors = {editable = true,path = "."} 27 | apd-sunnyboy-solar = {editable = true,path = "./plugins/apd.sunnyboy_solar"} 28 | waitress = "*" 29 | pint = "*" 30 | 31 | [requires] 32 | 33 | [pipenv] 34 | allow_prereleases = true 35 | -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/plugins/apd.sunnyboy_solar/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/plugins/apd.sunnyboy_solar/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch08/apd.sensors-chapter08/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "2.0.0" 2 | -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/src/apd/sensors/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch08/apd.sensors-chapter08/src/apd/sensors/py.typed -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/src/apd/sensors/wsgi/__init__.py: -------------------------------------------------------------------------------- 1 | import flask 2 | 3 | from .base import set_up_config 4 | from . import v10 5 | from . import v20 6 | from . import v21 7 | 8 | 9 | __all__ = ["app", "set_up_config"] 10 | 11 | app = flask.Flask(__name__) 12 | app.register_blueprint(v10.version, url_prefix="/v/1.0") 13 | app.register_blueprint(v20.version, url_prefix="/v/2.0") 14 | app.register_blueprint(v21.version, url_prefix="/v/2.1") 15 | -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/src/apd/sensors/wsgi/serve.py: -------------------------------------------------------------------------------- 1 | from . import app 2 | from .base import set_up_config 3 | 4 | if __name__ == "__main__": 5 | import wsgiref.simple_server 6 | 7 | set_up_config(None, app) 8 | 9 | with wsgiref.simple_server.make_server("", 8000, app) as server: 10 | server.serve_forever() 11 | -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/src/apd/sensors/wsgi/v10.py: -------------------------------------------------------------------------------- 1 | import json 2 | import typing as t 3 | 4 | import flask 5 | 6 | from apd.sensors.cli import get_sensors 7 | from .base import require_api_key 8 | 9 | version = flask.Blueprint(__name__, __name__) 10 | 11 | 12 | @version.route("/sensors/") 13 | @require_api_key 14 | def sensor_values() -> t.Tuple[t.Dict[str, t.Any], int, t.Dict[str, str]]: 15 | headers = {"Content-Security-Policy": "default-src 'none'"} 16 | data = {} 17 | for sensor in get_sensors(): 18 | value = sensor.value() 19 | try: 20 | json.dumps(value) 21 | except TypeError: 22 | # This value isn't JSON serializable, skip it 23 | continue 24 | else: 25 | data[sensor.title] = sensor.value() 26 | return data, 200, headers 27 | -------------------------------------------------------------------------------- /Ch08/apd.sensors-chapter08/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch08/apd.sensors-chapter08/tests/__init__.py -------------------------------------------------------------------------------- /Ch08/listing08-06-classic_sqlalchemy.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | import datetime 3 | import typing as t 4 | 5 | import sqlalchemy 6 | from sqlalchemy.dialects.postgresql import JSONB, TIMESTAMP 7 | from sqlalchemy.schema import Table 8 | 9 | 10 | metadata = sqlalchemy.MetaData() 11 | 12 | datapoint_table = Table( 13 | "sensor_values", 14 | metadata, 15 | sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True), 16 | sqlalchemy.Column("sensor_name", sqlalchemy.String), 17 | sqlalchemy.Column("collected_at", TIMESTAMP), 18 | sqlalchemy.Column("data", JSONB), 19 | ) 20 | 21 | @dataclass 22 | class DataPoint: 23 | sensor_name: str 24 | data: t.Dict[str, t.Any] 25 | id: int = None 26 | collected_at: datetime.datetime = field(default_factory=datetime.datetime.now) 27 | 28 | -------------------------------------------------------------------------------- /Ch08/listing08-07-datapoint_with_asdict.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field, asdict 2 | import datetime 3 | import typing as t 4 | 5 | 6 | @dataclass 7 | class DataPoint: 8 | sensor_name: str 9 | data: t.Dict[str, t.Any] 10 | id: int = None 11 | collected_at: datetime.datetime = field(default_factory=datetime.datetime.now) 12 | 13 | def _asdict(self): 14 | data = asdict(self) 15 | if data["id"] is None: 16 | del data["id"] 17 | return data 18 | 19 | -------------------------------------------------------------------------------- /Ch08/listing08-09-full_datapoint.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field, asdict 2 | import datetime 3 | import typing as t 4 | 5 | 6 | @dataclass 7 | class DataPoint: 8 | sensor_name: str 9 | data: t.Dict[str, t.Any] 10 | id: int = None 11 | collected_at: datetime.datetime = field(default_factory=datetime.datetime.now) 12 | 13 | @classmethod 14 | def from_sql_result(cls, result): 15 | return cls(**result._asdict()) 16 | 17 | def _asdict(self): 18 | data = asdict(self) 19 | if data["id"] is None: 20 | del data["id"] 21 | return data 22 | 23 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex01/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex01/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex01/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex01/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pytest-asyncio = "*" 10 | mypy = "*" 11 | flake8 = "*" 12 | black = "*" 13 | pre-commit = "*" 14 | wheel = "*" 15 | twine = "*" 16 | sqlalchemy-stubs = "*" 17 | 18 | [packages] 19 | apd-aggregation = {editable = true,path = "."} 20 | apd-sensors = {editable = true,extras = ["webapp"],path = "./../code"} 21 | 22 | [requires] 23 | 24 | [pipenv] 25 | allow_prereleases = true 26 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex01/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex01/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex01/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they start a HTTP server -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex01/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex01/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex01/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex01/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex01/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch09/apd.aggregation-chapter09-ex01/tests/__init__.py -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex02/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex02/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex02/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex02/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pytest-asyncio = "*" 10 | mypy = "*" 11 | flake8 = "*" 12 | black = "*" 13 | pre-commit = "*" 14 | wheel = "*" 15 | twine = "*" 16 | sqlalchemy-stubs = "*" 17 | 18 | [packages] 19 | apd-aggregation = {editable = true,extras = ["jupyter"],path = "."} 20 | apd-sensors = {editable = true,extras = ["webapp"],path = "./../code"} 21 | 22 | [requires] 23 | 24 | [pipenv] 25 | allow_prereleases = true 26 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex02/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex02/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex02/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they start a HTTP server -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex02/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex02/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex02/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex02/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex02/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch09/apd.aggregation-chapter09-ex02/tests/__init__.py -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03-complete/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03-complete/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03-complete/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03-complete/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pytest-asyncio = "*" 10 | mypy = "*" 11 | flake8 = "*" 12 | black = "*" 13 | pre-commit = "*" 14 | wheel = "*" 15 | twine = "*" 16 | sqlalchemy-stubs = "*" 17 | 18 | [packages] 19 | apd-aggregation = {editable = true,extras = ["jupyter"],path = "."} 20 | apd-sensors = {editable = true,extras = ["webapp"],path = "./../code"} 21 | 22 | [requires] 23 | 24 | [pipenv] 25 | allow_prereleases = true 26 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03-complete/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03-complete/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03-complete/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they start a HTTP server -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03-complete/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03-complete/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03-complete/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03-complete/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03-complete/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch09/apd.aggregation-chapter09-ex03-complete/tests/__init__.py -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pytest-asyncio = "*" 10 | mypy = "*" 11 | flake8 = "*" 12 | black = "*" 13 | pre-commit = "*" 14 | wheel = "*" 15 | twine = "*" 16 | sqlalchemy-stubs = "*" 17 | 18 | [packages] 19 | apd-aggregation = {editable = true,extras = ["jupyter"],path = "."} 20 | apd-sensors = {editable = true,extras = ["webapp"],path = "./../code"} 21 | 22 | [requires] 23 | 24 | [pipenv] 25 | allow_prereleases = true 26 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they start a HTTP server -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09-ex03/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch09/apd.aggregation-chapter09-ex03/tests/__init__.py -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pytest-asyncio = "*" 10 | mypy = "*" 11 | flake8 = "*" 12 | black = "*" 13 | pre-commit = "*" 14 | wheel = "*" 15 | twine = "*" 16 | sqlalchemy-stubs = "*" 17 | 18 | [packages] 19 | apd-aggregation = {editable = true,extras = ["jupyter"],path = "."} 20 | apd-sensors = {editable = true,extras = ["webapp"],path = "./../code"} 21 | 22 | [requires] 23 | 24 | [pipenv] 25 | allow_prereleases = true 26 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they start a HTTP server -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch09/apd.aggregation-chapter09/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch09/apd.aggregation-chapter09/tests/__init__.py -------------------------------------------------------------------------------- /Ch09/listing09-02-getdata.py: -------------------------------------------------------------------------------- 1 | import testing as t 2 | 3 | from apd.aggregation.database import DataPoint 4 | 5 | 6 | # Partial code from apd.aggregation package 7 | 8 | async def get_data() -> t.AsyncIterator[DataPoint]: 9 | db_session = db_session_var.get() 10 | loop = asyncio.get_running_loop() 11 | query = db_session.query(datapoint_table) 12 | rows = await loop.run_in_executor(None, query.all) 13 | for row in rows: 14 | yield DataPoint.from_sql_result(row) 15 | 16 | -------------------------------------------------------------------------------- /Ch09/listing09-03-count-datapoints.py: -------------------------------------------------------------------------------- 1 | from apd.aggregation.query import with_database, get_data 2 | 3 | with with_database("postgresql+psycopg2://apd@localhost/apd") as session: 4 | count = 0 5 | async for datapoint in get_data(): 6 | count += 1 7 | print(count) 8 | 9 | -------------------------------------------------------------------------------- /Ch09/listing09-04-plot.py: -------------------------------------------------------------------------------- 1 | from apd.aggregation.query import with_database, get_data 2 | 3 | from matplotlib import pyplot as plt 4 | 5 | async def plot(): 6 | points = [ 7 | (dp.collected_at, dp.data) 8 | async for dp in get_data() 9 | if dp.sensor_name=="RelativeHumidity" 10 | ] 11 | x, y = zip(*points) 12 | plt.plot_date(x, y, "o", xdate=True) 13 | 14 | with with_database("postgresql+psycopg2://apd@localhost/apd") as session: 15 | await plot() 16 | plt.show() 17 | 18 | -------------------------------------------------------------------------------- /Ch09/listing09-05-filtering.py: -------------------------------------------------------------------------------- 1 | from apd.aggregation.query import with_database, get_data 2 | 3 | from matplotlib import pyplot as plt 4 | 5 | async def plot(): 6 | points = [(dp.collected_at, dp.data) async for dp in get_data(sensor_name="RelativeHumidity")] 7 | x, y = zip(*points) 8 | plt.plot_date(x, y, "o", xdate=True) 9 | 10 | with with_database("postgresql+psycopg2://apd@localhost/apd") as session: 11 | await plot() 12 | plt.show() 13 | 14 | -------------------------------------------------------------------------------- /Ch09/listing09-06-multiplot.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | from apd.aggregation.query import with_database, get_data 4 | 5 | from matplotlib import pyplot as plt 6 | 7 | async def plot(): 8 | legends = collections.defaultdict(list) 9 | async for dp in get_data(sensor_name="RelativeHumidity"): 10 | legends[dp.deployment_id].append((dp.collected_at, dp.data)) 11 | 12 | for deployment_id, points in legends.items(): 13 | x, y = zip(*points) 14 | plt.plot_date(x, y, "o", xdate=True) 15 | 16 | with with_database("postgresql+psycopg2://apd@localhost/apd") as session: 17 | await plot() 18 | plt.show() 19 | 20 | -------------------------------------------------------------------------------- /Ch09/listing09-08-plot_with_helpers.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | from apd.aggregation.query import with_database, get_data, get_deployment_ids 4 | 5 | from matplotlib import pyplot as plt 6 | 7 | async def plot(deployment_id): 8 | points = [] 9 | async for dp in get_data(sensor_name="RelativeHumidity", deployment_id=deployment_id): 10 | points.append((dp.collected_at, dp.data)) 11 | 12 | x, y = zip(*points) 13 | plt.plot_date(x, y, "o", xdate=True) 14 | 15 | with with_database("postgresql+psycopg2://apd@localhost/apd") as session: 16 | deployment_ids = await get_deployment_ids() 17 | for deployment in deployment_ids: 18 | await plot(deployment) 19 | plt.show() 20 | 21 | -------------------------------------------------------------------------------- /Ch09/listing09-14-two_plots.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from matplotlib import pyplot as plt 4 | 5 | from apd.aggregation.query import with_database 6 | from apd.aggregation.analysis import get_known_configs, plot_sensor 7 | 8 | with with_database("postgresql+psycopg2://apd@localhost/apd") as session: 9 | coros = [] 10 | figure = plt.figure(figsize = (20, 5), dpi=300) 11 | configs = get_known_configs() 12 | to_display = configs["Relative humidity"], configs["RAM available"] 13 | for i, config in enumerate(to_display, start=1): 14 | plot = figure.add_subplot(1, 2, i) 15 | coros.append(plot_sensor(config, plot, {})) 16 | await asyncio.gather(*coros) 17 | 18 | display(figure) 19 | 20 | -------------------------------------------------------------------------------- /Ch09/listing09-17-sync_from_async.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | def add_number_from_callback(a: t.Callable[[], int], b: t.Callable[[], int]) -> int: 4 | return a() + b() 5 | 6 | def constant() -> int: 7 | return 5 8 | 9 | print(add_number_from_callback(constant, constant)) 10 | 11 | -------------------------------------------------------------------------------- /Ch09/listing09-18-wrap_coroutine.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from concurrent.futures import ThreadPoolExecutor 3 | import functools 4 | 5 | 6 | def wrap_coroutine(f): 7 | @functools.wraps(f) 8 | def run_in_thread(*args, **kwargs): 9 | loop = asyncio.new_event_loop() 10 | wrapped = f(*args, **kwargs) 11 | with ThreadPoolExecutor(max_workers=1) as pool: 12 | task = pool.submit(loop.run_until_complete, wrapped) 13 | return task.result() 14 | return run_in_thread 15 | 16 | async def main() -> None: 17 | print( 18 | add_number_from_callback( 19 | constant, wrap_coroutine(async_get_number_from_HTTP_request) 20 | ) 21 | ) 22 | 23 | 24 | -------------------------------------------------------------------------------- /Ch09/listing09-21-contours_and_scatter.py: -------------------------------------------------------------------------------- 1 | fig, ax = plt.subplots() 2 | 3 | lats = [ll[0] for ll in datapoints.keys()] 4 | lons = [ll[1] for ll in datapoints.keys()] 5 | temperatures = tuple(datapoints.values()) 6 | 7 | x = tuple(map(merc_x, lons)) 8 | y = tuple(map(merc_y, lats)) 9 | 10 | ax.tricontourf(x, y, temperatures) 11 | ax.plot(x, y, 'wo', ms=3) 12 | ax.set_aspect(1.0) 13 | plt.show() 14 | 15 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10-ex01/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10-ex01/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10-ex01/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10-ex01/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pytest-asyncio = "*" 10 | mypy = "*" 11 | flake8 = "*" 12 | black = "*" 13 | pre-commit = "*" 14 | wheel = "*" 15 | twine = "*" 16 | sqlalchemy-stubs = "*" 17 | 18 | [packages] 19 | apd-aggregation = {editable = true,extras = ["jupyter"],path = "."} 20 | apd-sensors = {editable = true,extras = ["webapp"],path = "./../code"} 21 | 22 | [requires] 23 | 24 | [pipenv] 25 | allow_prereleases = true 26 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10-ex01/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10-ex01/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10-ex01/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they start a HTTP server 4 | performance: very slow tests that provide performance guarantees -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10-ex01/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10-ex01/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10-ex01/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10-ex01/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10-ex01/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch10/apd.aggregation-chapter10-ex01/tests/__init__.py -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pytest-asyncio = "*" 10 | mypy = "*" 11 | flake8 = "*" 12 | black = "*" 13 | pre-commit = "*" 14 | wheel = "*" 15 | twine = "*" 16 | sqlalchemy-stubs = "*" 17 | yappi = "*" 18 | 19 | [packages] 20 | apd-aggregation = {editable = true,extras = ["jupyter"],path = "."} 21 | apd-sensors = {editable = true,extras = ["webapp"],path = "./../code"} 22 | 23 | [requires] 24 | 25 | [pipenv] 26 | allow_prereleases = true 27 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they start a HTTP server 4 | performance: very slow tests that provide performance guarantees -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch10/apd.aggregation-chapter10/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch10/apd.aggregation-chapter10/tests/__init__.py -------------------------------------------------------------------------------- /Ch10/apd.sensors-chapter10/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch10/apd.sensors-chapter10/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | webtest = "*" 23 | apd-sensors = {editable = true,extras = ["webapp"],path = "."} 24 | 25 | [packages] 26 | apd-sensors = {editable = true,path = "."} 27 | apd-sunnyboy-solar = {editable = true,path = "./plugins/apd.sunnyboy_solar"} 28 | waitress = "*" 29 | pint = "*" 30 | 31 | [requires] 32 | 33 | [pipenv] 34 | allow_prereleases = true 35 | -------------------------------------------------------------------------------- /Ch10/apd.sensors-chapter10/plugins/apd.sunnyboy_solar/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch10/apd.sensors-chapter10/plugins/apd.sunnyboy_solar/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch10/apd.sensors-chapter10/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch10/apd.sensors-chapter10/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile -------------------------------------------------------------------------------- /Ch10/apd.sensors-chapter10/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch10/apd.sensors-chapter10/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch10/apd.sensors-chapter10/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch10/apd.sensors-chapter10/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "2.1.0" 2 | -------------------------------------------------------------------------------- /Ch10/apd.sensors-chapter10/src/apd/sensors/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch10/apd.sensors-chapter10/src/apd/sensors/py.typed -------------------------------------------------------------------------------- /Ch10/apd.sensors-chapter10/src/apd/sensors/wsgi/__init__.py: -------------------------------------------------------------------------------- 1 | import flask 2 | 3 | from .base import set_up_config 4 | from . import v10 5 | from . import v20 6 | from . import v21 7 | 8 | 9 | __all__ = ["app", "set_up_config"] 10 | 11 | app = flask.Flask(__name__) 12 | app.register_blueprint(v10.version, url_prefix="/v/1.0") 13 | app.register_blueprint(v20.version, url_prefix="/v/2.0") 14 | app.register_blueprint(v21.version, url_prefix="/v/2.1") 15 | -------------------------------------------------------------------------------- /Ch10/apd.sensors-chapter10/src/apd/sensors/wsgi/serve.py: -------------------------------------------------------------------------------- 1 | from . import app 2 | from .base import set_up_config 3 | 4 | if __name__ == "__main__": 5 | import wsgiref.simple_server 6 | 7 | set_up_config(None, app) 8 | 9 | with wsgiref.simple_server.make_server("", 8000, app) as server: 10 | server.serve_forever() 11 | -------------------------------------------------------------------------------- /Ch10/listing10-02-profile_with_yappi.py: -------------------------------------------------------------------------------- 1 | from apd.aggregation.analysis import interactable_plot_multiple_charts, configs 2 | from apd.aggregation.utils import jupyter_page_file, profile_with_yappi, yappi_package_matches 3 | import yappi 4 | 5 | with profile_with_yappi(): 6 | plot = interactable_plot_multiple_charts() 7 | plot() 8 | 9 | with jupyter_page_file() as output: 10 | yappi.get_func_stats(filter_callback=lambda stat: 11 | yappi_package_matches(stat, ["apd.aggregation"]) 12 | ).print_all(output) 13 | 14 | -------------------------------------------------------------------------------- /Ch10/listing10-03-memory_profiler.py: -------------------------------------------------------------------------------- 1 | import tracemalloc 2 | 3 | from apd.aggregation.analysis import interactable_plot_multiple_charts 4 | 5 | 6 | tracemalloc.start() 7 | plot = interactable_plot_multiple_charts()() 8 | snapshot = tracemalloc.take_snapshot() 9 | tracemalloc.stop() 10 | for line in snapshot.statistics("lineno", cumulative=True): 11 | print(line) 12 | 13 | -------------------------------------------------------------------------------- /Ch10/listing10-04-sql_filtering.py: -------------------------------------------------------------------------------- 1 | import yappi 2 | 3 | from apd.aggregation.analysis import interactable_plot_multiple_charts, Config 4 | from apd.aggregation.analysis import clean_temperature_fluctuations, get_one_sensor_by_deployment 5 | from apd.aggregation.utils import profile_with_yappi 6 | 7 | yappi.set_clock_type("wall") 8 | 9 | filter_in_db = Config( 10 | clean=clean_temperature_fluctuations, 11 | title="Ambient temperature", 12 | ylabel="Degrees C", 13 | get_data=get_one_sensor_by_deployment("Temperature"), 14 | ) 15 | 16 | with profile_with_yappi(): 17 | plot = interactable_plot_multiple_charts(configs=[filter_in_db]) 18 | plot() 19 | 20 | yappi.get_func_stats().print_all() 21 | 22 | -------------------------------------------------------------------------------- /Ch10/listing10-06-consume_iterators.py: -------------------------------------------------------------------------------- 1 | def consume(input_iterator): 2 | items = [item for item in input_iterator] 3 | def inner_iterator(): 4 | for item in items: 5 | yield item 6 | return inner_iterator() 7 | 8 | async def consume_async(input_iterator): 9 | items = [item async for item in input_iterator] 10 | async def inner_iterator(): 11 | for item in items: 12 | yield item 13 | return inner_iterator() 14 | 15 | -------------------------------------------------------------------------------- /Ch10/listing10-07-consume_iterators_singledispatch.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | @functools.singledispatch 4 | def consume(input_iterator): 5 | items = [item for item in input_iterator] 6 | def inner_iterator(): 7 | for item in items: 8 | yield item 9 | return inner_iterator() 10 | 11 | @consume.register 12 | async def consume_async(input_iterator: collections.abc.AsyncIterator): 13 | items = [item async for item in input_iterator] 14 | async def inner_iterator(): 15 | for item in items: 16 | yield item 17 | return inner_iterator() 18 | 19 | -------------------------------------------------------------------------------- /Ch10/listing10-09-fahrenheit_chart.py: -------------------------------------------------------------------------------- 1 | import yappi 2 | from apd.aggregation.analysis import interactable_plot_multiple_charts, Config 3 | from apd.aggregation.analysis import convert_temperature_system, clean_temperature_fluctuations 4 | from apd.aggregation.analysis import get_one_sensor_by_deployment 5 | 6 | filter_in_db = Config( 7 | clean=convert_temperature_system(clean_temperature_fluctuations, "degF"), 8 | title="Ambient temperature", 9 | ylabel="Degrees F", 10 | get_data=get_one_sensor_by_deployment("Temperature"), 11 | ) 12 | display(interactable_plot_multiple_charts(configs=[filter_in_db])()) 13 | 14 | -------------------------------------------------------------------------------- /Ch10/listing10-10-minimal_cache.py: -------------------------------------------------------------------------------- 1 | def convert_temperature_system( 2 | cleaner: DT_FLOAT_CLEANER, temperature_unit: str, 3 | ) -> DT_FLOAT_CLEANER: 4 | async def converter(datapoints: t.AsyncIterator[DataPoint],) -> CLEANED_DT_FLOAT: 5 | temperatures = {} 6 | results = cleaner(datapoints) 7 | async for date, temp_c in results: 8 | if temp_c in temperatures: 9 | temp_f = temperatures[temp_c] 10 | else: 11 | temp_f = temperatures[temp_c] = convert_temperature(temp_c, "degC", temperature_unit) 12 | yield date, temp_f 13 | 14 | return converter 15 | 16 | 17 | -------------------------------------------------------------------------------- /Ch11/apd.aggregation-chapter11/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch11/apd.aggregation-chapter11/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch11/apd.aggregation-chapter11/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (Unreleased) 4 | 5 | * Generated from skeleton 6 | -------------------------------------------------------------------------------- /Ch11/apd.aggregation-chapter11/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pytest-asyncio = "*" 10 | mypy = "*" 11 | flake8 = "*" 12 | black = "*" 13 | pre-commit = "*" 14 | wheel = "*" 15 | twine = "*" 16 | sqlalchemy-stubs = "*" 17 | yappi = "*" 18 | 19 | [packages] 20 | apd-aggregation = {editable = true,extras = ["jupyter"],path = "."} 21 | apd-sensors = {editable = true,extras = ["webapp"],path = "./../code"} 22 | 23 | [requires] 24 | 25 | [pipenv] 26 | allow_prereleases = true 27 | -------------------------------------------------------------------------------- /Ch11/apd.aggregation-chapter11/README.md: -------------------------------------------------------------------------------- 1 | # APD Sensor aggregator 2 | 3 | A programme that queries apd.sensor endpoints and aggregates their results. 4 | 5 | -------------------------------------------------------------------------------- /Ch11/apd.aggregation-chapter11/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch11/apd.aggregation-chapter11/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they start a HTTP server 4 | performance: very slow tests that provide performance guarantees -------------------------------------------------------------------------------- /Ch11/apd.aggregation-chapter11/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch11/apd.aggregation-chapter11/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch11/apd.aggregation-chapter11/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. -------------------------------------------------------------------------------- /Ch11/apd.aggregation-chapter11/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch11/apd.aggregation-chapter11/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch11/apd.aggregation-chapter11/tests/__init__.py -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | webtest = "*" 23 | sqlalchemy-stubs = "*" 24 | 25 | [packages] 26 | apd-sensors = {editable = true,extras = ["scheduled", "webapp", "storedapi"],path = "."} 27 | apd-sunnyboy-solar = {editable = true,path = "./plugins/apd.sunnyboy_solar"} 28 | waitress = "*" 29 | pint = "==0.10.1" 30 | 31 | [requires] 32 | 33 | [pipenv] 34 | allow_prereleases = true 35 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/plugins/apd.sunnyboy_solar/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/plugins/apd.sunnyboy_solar/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch11/apd.sensors-chapter11-ex01/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script 4 | addopts = 5 | --ignore plugins -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "2.1.0" 2 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/src/apd/sensors/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/src/apd/sensors/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/src/apd/sensors/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch11/apd.sensors-chapter11-ex01/src/apd/sensors/py.typed -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/src/apd/sensors/wsgi/serve.py: -------------------------------------------------------------------------------- 1 | from . import app 2 | from .base import set_up_config 3 | 4 | if __name__ == "__main__": 5 | import wsgiref.simple_server 6 | 7 | set_up_config(None, app) 8 | 9 | with wsgiref.simple_server.make_server("", 8000, app) as server: 10 | server.serve_forever() 11 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11-ex01/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch11/apd.sensors-chapter11-ex01/tests/__init__.py -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | webtest = "*" 23 | sqlalchemy-stubs = "*" 24 | 25 | [packages] 26 | apd-sensors = {editable = true,extras = ["scheduled", "webapp", "storedapi"],path = "."} 27 | apd-sunnyboy-solar = {editable = true,path = "./plugins/apd.sunnyboy_solar"} 28 | waitress = "*" 29 | pint = "==0.9" 30 | 31 | [requires] 32 | 33 | [pipenv] 34 | allow_prereleases = true 35 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/plugins/apd.sunnyboy_solar/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/plugins/apd.sunnyboy_solar/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch11/apd.sensors-chapter11/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "2.1.0" 2 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/src/apd/sensors/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/src/apd/sensors/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/src/apd/sensors/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch11/apd.sensors-chapter11/src/apd/sensors/py.typed -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/src/apd/sensors/wsgi/serve.py: -------------------------------------------------------------------------------- 1 | from . import app 2 | from .base import set_up_config 3 | 4 | if __name__ == "__main__": 5 | import wsgiref.simple_server 6 | 7 | set_up_config(None, app) 8 | 9 | with wsgiref.simple_server.make_server("", 8000, app) as server: 10 | server.serve_forever() 11 | -------------------------------------------------------------------------------- /Ch11/apd.sensors-chapter11/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch11/apd.sensors-chapter11/tests/__init__.py -------------------------------------------------------------------------------- /Ch11/listing11-01-get_with_default.py: -------------------------------------------------------------------------------- 1 | def get_item(variable, key, default=None): 2 | try: 3 | return variable[key] 4 | except (KeyError, IndexError): 5 | # Key is invalid for variable, the error raised depends on the type of variable 6 | return default 7 | except TypeError: 8 | if hasattr(variable, "__getitem__"): 9 | return default 10 | else: 11 | raise 12 | 13 | -------------------------------------------------------------------------------- /Ch11/listing11-02-new_exceptions.py: -------------------------------------------------------------------------------- 1 | class APDSensorsError(Exception): 2 | """An exception base class for all exceptions raised by the 3 | sensor data collection system.""" 4 | 5 | class DataCollectionError(APDSensorsError, RuntimeError): 6 | """An error that represents the inability of a Sensor instance 7 | to retrieve a value""" 8 | 9 | class IntermittentSensorFailureError(DataCollectionError): 10 | """A DataCollectionError that is expected to resolve itself 11 | in short order""" 12 | 13 | class PersistentSensorFailureError(DataCollectionError): 14 | """A DataCollectionError that is unlikely to resolve itself 15 | if retried.""" 16 | 17 | -------------------------------------------------------------------------------- /Ch11/listing11-07-failing_test_sensor.py: -------------------------------------------------------------------------------- 1 | from apd.sensors.base import JSONSensor 2 | from apd.sensors.exceptions import IntermittentSensorFailureError 3 | 4 | 5 | class FailingSensor(JSONSensor[bool]): 6 | 7 | title = "Sensor which fails" 8 | name = "FailingSensor" 9 | 10 | def __init__(self, n: int=3, exception_type: Exception=IntermittentSensorFailureError): 11 | self.n = n 12 | self.exception_type = exception_type 13 | 14 | def value(self) -> bool: 15 | self.n -= 1 16 | if self.n: 17 | raise self.exception_type(f"Failing {self.n} more times") 18 | else: 19 | return True 20 | 21 | @classmethod 22 | def format(cls, value: bool) -> str: 23 | raise "Yes" if value else "No" 24 | 25 | -------------------------------------------------------------------------------- /Ch11/listing11-08-compatibility_test.py: -------------------------------------------------------------------------------- 1 | @pytest.mark.functional 2 | def test_erroring_sensor_shows_None(self, api_server, api_key): 3 | from .test_utils import FailingSensor 4 | 5 | with mock.patch("apd.sensors.cli.get_sensors") as get_sensors: 6 | # Ensure the failing sensor is first, to test that subsequent sensors 7 | # are still processed 8 | get_sensors.return_value = [FailingSensor(10), PythonVersion()] 9 | value = api_server.get("/sensors/", headers={"X-API-Key": api_key}).json 10 | assert value['Sensor which fails'] == None 11 | assert "Python Version" in value.keys() 12 | 13 | -------------------------------------------------------------------------------- /Ch11/listing11-09-mock-failingsensor.py: -------------------------------------------------------------------------------- 1 | from apd.sensors.base import Sensor 2 | from apd.sensors.exceptions import IntermittentSensorFailureError 3 | 4 | FailingSensor = mock.MagicMock(spec=Sensor) 5 | FailingSensor.title = "Sensor which fails" 6 | FailingSensor.name = "FailingSensor" 7 | FailingSensor.value.side_effect = IntermittentSensorFailureError("Failing sensor") 8 | FailingSensor.__str__.side_effect = IntermittentSensorFailureError("Failing sensor") 9 | 10 | -------------------------------------------------------------------------------- /Ch11/listing11-12-logging_config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | def set_logger_format(logger, format_str): 4 | """Set up a new stderr handler for the given logger 5 | and configure the formatter with the provided string 6 | """ 7 | logger.propagate = False 8 | formatter = logging.Formatter(format_str, None, "{") 9 | 10 | std_err_handler = logging.StreamHandler(None) 11 | std_err_handler.setFormatter(formatter) 12 | 13 | logger.handlers.clear() 14 | logger.addHandler(std_err_handler) 15 | return logger 16 | 17 | logger = set_logger_format( 18 | logging.getLogger(__name__), 19 | format_str="{asctime}: {levelname} - {message}", 20 | ) 21 | 22 | -------------------------------------------------------------------------------- /Ch11/listing11-13-log_adapter.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import logging 3 | 4 | class ExtraDefaultAdapter(logging.LoggerAdapter): 5 | def process(self, msg, kwargs): 6 | extra = copy.copy(self.extra) 7 | extra.update(kwargs.pop("extra", {})) 8 | kwargs["extra"] = extra 9 | return msg, kwargs 10 | 11 | def set_logger_format(logger, format_str): 12 | """Set up a new stderr handler for the given logger 13 | and configure the formatter with the provided string 14 | """ 15 | logger.propagate = False 16 | formatter = logging.Formatter(format_str, None, "{") 17 | 18 | std_err_handler = logging.StreamHandler(None) 19 | std_err_handler.setFormatter(formatter) 20 | 21 | logger.handlers.clear() 22 | logger.addHandler(std_err_handler) 23 | return logger 24 | 25 | -------------------------------------------------------------------------------- /Ch11/listing11-14-log_factory.py: -------------------------------------------------------------------------------- 1 | from contextvars import ContextVar 2 | import functools 3 | import logging 4 | 5 | sensorname_var = ContextVar("sensorname", default="none") 6 | 7 | def add_sensorname_record_factory(existing_factory, *args, **kwargs): 8 | record = existing_factory(*args, **kwargs) 9 | record.sensorname = sensorname_var.get() 10 | return record 11 | 12 | def add_record_factory_wrapper(fn): 13 | old_factory = logging.getLogRecordFactory() 14 | wrapped = functools.partial(fn, old_factory) 15 | logging.setLogRecordFactory(wrapped) 16 | 17 | add_record_factory_wrapper(add_sensorname_record_factory) 18 | logging.basicConfig( 19 | format="[{sensorname}/{levelname}] - {message}", style="{", level=logging.INFO 20 | ) 21 | 22 | -------------------------------------------------------------------------------- /Ch11/listing11-16-log_handler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | class AddSensorNameDefault(logging.Filter): 4 | def filter(self, record): 5 | if not hasattr(record, "sensorname"): 6 | record.sensorname = "none" 7 | return True 8 | 9 | class SensorNameStreamHandler(logging.StreamHandler): 10 | def __init__(self, *args, **kwargs): 11 | super().__init__() 12 | self.addFilter(AddSensorNameDefault()) 13 | 14 | -------------------------------------------------------------------------------- /Ch11/listing11-17-log_config.ini: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root 3 | 4 | [handlers] 5 | keys=stderr_with_sensorname 6 | 7 | [formatters] 8 | keys=sensorname 9 | 10 | [logger_root] 11 | level=INFO 12 | handlers=stderr_with_sensorname 13 | 14 | [handler_stderr_with_sensorname] 15 | class=apd.aggregation.utils.SensorNameStreamHandler 16 | formatter = sensorname 17 | 18 | [formatter_sensorname] 19 | format = {asctime}: [{sensorname}/{levelname}] - {message} 20 | style = { 21 | 22 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01-complete/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01-complete/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | language: system 19 | types: [python] 20 | 21 | - id: flake8 22 | name: flake8 23 | entry: pipenv run flake8 24 | language: system 25 | types: [python] 26 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01-complete/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (2020-01-27) 4 | 5 | * Added management of known sensor endpoints 6 | * Added CLI script to collate data 7 | * Added analysis tools for Jupyter 8 | * Added long-running data synthesis and actions system 9 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01-complete/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pytest-asyncio = "*" 10 | mypy = "*" 11 | flake8 = "*" 12 | black = "*" 13 | pre-commit = "*" 14 | wheel = "*" 15 | twine = "*" 16 | sqlalchemy-stubs = "*" 17 | yappi = "*" 18 | 19 | [packages] 20 | apd-aggregation = {editable = true,extras = ["jupyter"],path = "."} 21 | apd-sensors = {editable = true,extras = ["webapp"],path = "./../code"} 22 | 23 | [requires] 24 | 25 | [pipenv] 26 | allow_prereleases = true 27 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01-complete/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01-complete/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they start a HTTP server 4 | performance: very slow tests that provide performance guarantees -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01-complete/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01-complete/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01-complete/src/apd/aggregation/actions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch12/apd.aggregation-chapter12-ex01-complete/src/apd/aggregation/actions/__init__.py -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01-complete/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. 13 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01-complete/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01-complete/src/apd/aggregation/exceptions.py: -------------------------------------------------------------------------------- 1 | class NoDataForTrigger(ValueError): 2 | """An error that's raised when a trigger is passed 3 | a data point that cannot be handled due to an incompatible 4 | value being stored""" 5 | 6 | pass 7 | 8 | 9 | class IncompatibleTriggerError(NoDataForTrigger): 10 | """An error that's raised when a trigger is passed 11 | a data point that cannot be handled due to an incompatible 12 | value being stored""" 13 | 14 | pass 15 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01-complete/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch12/apd.aggregation-chapter12-ex01-complete/tests/__init__.py -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | language: system 19 | types: [python] 20 | 21 | - id: flake8 22 | name: flake8 23 | entry: pipenv run flake8 24 | language: system 25 | types: [python] 26 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (2020-01-27) 4 | 5 | * Added management of known sensor endpoints 6 | * Added CLI script to collate data 7 | * Added analysis tools for Jupyter 8 | * Added long-running data synthesis and actions system 9 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pytest-asyncio = "*" 10 | mypy = "*" 11 | flake8 = "*" 12 | black = "*" 13 | pre-commit = "*" 14 | wheel = "*" 15 | twine = "*" 16 | sqlalchemy-stubs = "*" 17 | yappi = "*" 18 | 19 | [packages] 20 | apd-aggregation = {editable = true,extras = ["jupyter"],path = "."} 21 | apd-sensors = {editable = true,extras = ["webapp"],path = "./../code"} 22 | 23 | [requires] 24 | 25 | [pipenv] 26 | allow_prereleases = true 27 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they start a HTTP server 4 | performance: very slow tests that provide performance guarantees -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01/src/apd/aggregation/actions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch12/apd.aggregation-chapter12-ex01/src/apd/aggregation/actions/__init__.py -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. 13 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01/src/apd/aggregation/exceptions.py: -------------------------------------------------------------------------------- 1 | class NoDataForTrigger(ValueError): 2 | """An error that's raised when a trigger is passed 3 | a data point that cannot be handled due to an incompatible 4 | value being stored""" 5 | 6 | pass 7 | 8 | 9 | class IncompatibleTriggerError(NoDataForTrigger): 10 | """An error that's raised when a trigger is passed 11 | a data point that cannot be handled due to an incompatible 12 | value being stored""" 13 | 14 | pass 15 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12-ex01/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch12/apd.aggregation-chapter12-ex01/tests/__init__.py -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = tests/* 4 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | language: system 19 | types: [python] 20 | 21 | - id: flake8 22 | name: flake8 23 | entry: pipenv run flake8 24 | language: system 25 | types: [python] 26 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12/CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | ### 1.0.0 (2020-01-27) 4 | 5 | * Added management of known sensor endpoints 6 | * Added CLI script to collate data 7 | * Added analysis tools for Jupyter 8 | * Added long-running data synthesis and actions system 9 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pytest-asyncio = "*" 10 | mypy = "*" 11 | flake8 = "*" 12 | black = "*" 13 | pre-commit = "*" 14 | wheel = "*" 15 | twine = "*" 16 | sqlalchemy-stubs = "*" 17 | yappi = "*" 18 | 19 | [packages] 20 | apd-aggregation = {editable = true,extras = ["jupyter"],path = "."} 21 | apd-sensors = {editable = true,extras = ["webapp"],path = "./../code"} 22 | 23 | [requires] 24 | 25 | [pipenv] 26 | allow_prereleases = true 27 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they start a HTTP server 4 | performance: very slow tests that provide performance guarantees -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12/src/apd/aggregation/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12/src/apd/aggregation/actions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch12/apd.aggregation-chapter12/src/apd/aggregation/actions/__init__.py -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12/src/apd/aggregation/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. 2 | 3 | 4 | # Database setup 5 | 6 | To generate the required database tables you must create an alembic.ini file, as follows: 7 | 8 | [alembic] 9 | script_location = apd.aggregation:alembic 10 | sqlalchemy.url = postgresql+psycopg2://apd@localhost/apd 11 | 12 | and run `alembic upgrade head`. This should also be done after every upgrade of the software. 13 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12/src/apd/aggregation/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12/src/apd/aggregation/exceptions.py: -------------------------------------------------------------------------------- 1 | class NoDataForTrigger(ValueError): 2 | """An error that's raised when a trigger is passed 3 | a data point that cannot be handled due to an incompatible 4 | value being stored""" 5 | 6 | pass 7 | 8 | 9 | class IncompatibleTriggerError(NoDataForTrigger): 10 | """An error that's raised when a trigger is passed 11 | a data point that cannot be handled due to an incompatible 12 | value being stored""" 13 | 14 | pass 15 | -------------------------------------------------------------------------------- /Ch12/apd.aggregation-chapter12/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch12/apd.aggregation-chapter12/tests/__init__.py -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: local 4 | hooks: 5 | - id: black 6 | name: black 7 | entry: pipenv run black 8 | args: [--quiet] 9 | language: system 10 | types: [python] 11 | 12 | - id: mypy 13 | name: mypy 14 | exclude: (?x)^( 15 | (.*)/setup.py 16 | )$ 17 | entry: pipenv run mypy 18 | args: ["--follow-imports=skip"] 19 | language: system 20 | types: [python] 21 | 22 | - id: flake8 23 | name: flake8 24 | entry: pipenv run flake8 25 | language: system 26 | types: [python] 27 | -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [[source]] 7 | name = "piwheels" 8 | url = "https://piwheels.org/simple" 9 | verify_ssl = true 10 | 11 | [dev-packages] 12 | ipykernel = "*" 13 | remote-ikernel = "*" 14 | pytest = "*" 15 | pytest-cov = "*" 16 | mypy = "*" 17 | flake8 = "*" 18 | black = "*" 19 | pre-commit = "*" 20 | wheel = "*" 21 | twine = "*" 22 | webtest = "*" 23 | sqlalchemy-stubs = "*" 24 | 25 | [packages] 26 | apd-sensors = {editable = true,extras = ["scheduled", "webapp", "storedapi"],path = "."} 27 | apd-sunnyboy-solar = {editable = true,path = "./plugins/apd.sunnyboy_solar"} 28 | waitress = "*" 29 | pint = "==0.10.1" 30 | 31 | [requires] 32 | 33 | [pipenv] 34 | allow_prereleases = true 35 | -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/plugins/apd.sunnyboy_solar/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/plugins/apd.sunnyboy_solar/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch12/apd.sensors-chapter12/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/Pipfile -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/plugins/apd.sunnyboy_solar/src/apd/sunnyboy_solar/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.0.0" 2 | -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | functional: these tests are significantly slower as they run the whole CLI script 4 | addopts = 5 | --ignore plugins -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/src/apd/sensors/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = "2.1.0" 2 | -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/src/apd/sensors/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/src/apd/sensors/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/src/apd/sensors/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch12/apd.sensors-chapter12/src/apd/sensors/py.typed -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/src/apd/sensors/wsgi/serve.py: -------------------------------------------------------------------------------- 1 | from . import app 2 | from .base import set_up_config 3 | 4 | if __name__ == "__main__": 5 | import wsgiref.simple_server 6 | 7 | set_up_config(None, app) 8 | 9 | with wsgiref.simple_server.make_server("", 8000, app) as server: 10 | server.serve_forever() 11 | -------------------------------------------------------------------------------- /Ch12/apd.sensors-chapter12/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/Ch12/apd.sensors-chapter12/tests/__init__.py -------------------------------------------------------------------------------- /Ch12/listing12-01-clean_passthrough.py: -------------------------------------------------------------------------------- 1 | async def clean_passthrough( 2 | datapoints: t.AsyncIterator[DataPoint], 3 | ) -> CLEANED_DT_FLOAT: 4 | async for datapoint in datapoints: 5 | if datapoint.data is None: 6 | continue 7 | else: 8 | yield datapoint.collected_at, datapoint.data 9 | 10 | -------------------------------------------------------------------------------- /Ch12/listing12-02-sum_ints.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | def sum_ints(source: t.Iterable[int]) -> t.Iterator[int]: 4 | """Yields a running total from the underlying iterator""" 5 | total = 0 6 | for num in source: 7 | total += num 8 | yield total 9 | 10 | def numbers() -> t.Iterator[int]: 11 | yield 1 12 | yield 1 13 | yield 1 14 | 15 | def test(): 16 | sums = sum_ints(numbers()) 17 | assert [a for a in sums] == [1, 2, 3] 18 | 19 | -------------------------------------------------------------------------------- /Ch12/listing12-03-process_own_output.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import typing as t 3 | 4 | def sum_ints(start: int) -> t.Iterator[int]: 5 | """Yields a running total with a given start value""" 6 | total = start 7 | while True: 8 | yield total 9 | total += total 10 | 11 | def test(): 12 | sums = sum_ints(1) 13 | # Limit an infinite iterator to the first 3 items 14 | # itertools.islice(iterable, [start,] stop, [step]) 15 | sums = itertools.islice(sums, 3) 16 | assert [a for a in sums] == [1, 2, 4] 17 | 18 | -------------------------------------------------------------------------------- /Ch12/listing12-05-enhanced_generator.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | def sum_ints() -> t.Generator[int, int, None]: 4 | """Yields a running total from the underlying iterator""" 5 | total = 0 6 | num = yield total 7 | while True: 8 | total += num 9 | num = yield total 10 | 11 | def test(): 12 | # Sum the iterable (1, ...) where ... is the results 13 | # of that iterable, stored with the wrap method 14 | sums = sum_ints() 15 | next(sums) # We can only send to yield lines, so advance to the first 16 | last = 1 17 | result = [] 18 | for n in range(3): 19 | last = sums.send(last) 20 | result.append(last) 21 | assert result == [1, 2, 4] 22 | 23 | 24 | test() 25 | 26 | -------------------------------------------------------------------------------- /Ch12/listing12-06-mean_finder.py: -------------------------------------------------------------------------------- 1 | class MeanFinder: 2 | def __init__(self): 3 | self.running_total = 0 4 | self.num_items = 0 5 | 6 | def add_item(self, num: float): 7 | self.running_total += num 8 | self.num_items += 1 9 | 10 | @property 11 | def mean(self): 12 | return self.running_total / self.num_items 13 | 14 | def test(): 15 | # Recursive mean from initial data 16 | mean = MeanFinder() 17 | to_add = 1 18 | for n in range(3): 19 | mean.add_item(to_add) 20 | to_add = mean.mean 21 | assert mean.mean == 1.0 22 | 23 | # Mean of a concrete data list 24 | mean = MeanFinder() 25 | for to_add in [1, 2, 3]: 26 | mean.add_item(to_add) 27 | assert mean.mean == 2.0 28 | 29 | 30 | -------------------------------------------------------------------------------- /Ch12/listing12-15-loggingaction.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | import logging 3 | 4 | from apd.aggregation.actions.base import Action 5 | from apd.aggregation.database import DataPoint 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class LoggingAction(Action): 11 | """An action that stores any generated data points back to the DB""" 12 | 13 | async def start(self) -> None: 14 | return 15 | 16 | async def handle(self, datapoint: DataPoint) -> bool: 17 | logger.warn(datapoint) 18 | return True 19 | 20 | -------------------------------------------------------------------------------- /Ch12/listing12-18-config.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from apd.aggregation.actions.action import ( 4 | OnlyOnChangeActionWrapper, 5 | LoggingAction, 6 | ) 7 | from apd.aggregation.actions.runner import DataProcessor 8 | from apd.aggregation.actions.trigger import ValueThresholdTrigger 9 | 10 | handlers = [ 11 | DataProcessor( 12 | name="TemperatureBelow18", 13 | action=OnlyOnChangeActionWrapper(LoggingAction()), 14 | trigger=ValueThresholdTrigger( 15 | name="TemperatureBelow18", 16 | threshold=18, 17 | comparator=operator.lt, 18 | sensor_name="Temperature", 19 | ), 20 | ) 21 | ] 22 | 23 | -------------------------------------------------------------------------------- /Ch12/listing12-20-stats_signals.py: -------------------------------------------------------------------------------- 1 | import signal 2 | def stats_signal_handler(sig, frame, data_processors=None): 3 | for data_processor in data_processors: 4 | click.echo( 5 | click.style(data_processor.name, bold=True, fg="red") + " " + data_processor.stats() 6 | ) 7 | return 8 | 9 | signal_handler = functools.partial(stats_signal_handler, data_processors=handlers) 10 | signal.signal(signal.SIGINFO, signal_handler) 11 | 12 | -------------------------------------------------------------------------------- /Ch12/listing12-24-refeed_actions.py: -------------------------------------------------------------------------------- 1 | from .source import refeed_queue_var 2 | 3 | class RefeedAction(Action): 4 | """An action that puts data points into a special queue to be consumed 5 | by the analysis programme""" 6 | 7 | async def start(self) -> None: 8 | return 9 | 10 | async def handle(self, datapoint: DataPoint) -> bool: 11 | refeed_queue = refeed_queue_var.get() 12 | if refeed_queue is None: 13 | logger.error("Refeed queue has not been initialised") 14 | return False 15 | else: 16 | await refeed_queue.put(datapoint) 17 | return True 18 | 19 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Apress Source Code 2 | 3 | Copyright for Apress source code belongs to the author(s). However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers. 4 | 5 | ## How to Contribute 6 | 7 | 1. Make sure you have a GitHub account. 8 | 2. Fork the repository for the relevant book. 9 | 3. Create a new branch on which to make your change, e.g. 10 | `git checkout -b my_code_contribution` 11 | 4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted. 12 | 5. Submit a pull request. 13 | 14 | Thank you for your contribution! -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apress Source Code 2 | 3 | This repository accompanies [*Advanced Python Development*](https://www.apress.com/9781484257920) by Matthew Wilkes (Apress, 2020). 4 | 5 | [comment]: #cover 6 |  7 | 8 | Download the files as a zip using the green button, or clone the repository to your machine using Git. 9 | 10 | ## Releases 11 | 12 | Release v1.0 corresponds to the code in the published book, without corrections or updates. 13 | 14 | ## Contributions 15 | 16 | See the file Contributing.md for more information on how you can contribute to this repository. -------------------------------------------------------------------------------- /apd.aggregation/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /apd.aggregation/config: -------------------------------------------------------------------------------- 1 | [core] 2 | repositoryformatversion = 0 3 | filemode = false 4 | bare = true 5 | ignorecase = true 6 | [remote "origin"] 7 | url = git@github.com:matthewwilkes/apd.aggregation.git 8 | -------------------------------------------------------------------------------- /apd.aggregation/description: -------------------------------------------------------------------------------- 1 | Unnamed repository; edit this file 'description' to name the repository. 2 | -------------------------------------------------------------------------------- /apd.aggregation/hooks/applypatch-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message taken by 4 | # applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. The hook is 8 | # allowed to edit the commit message file. 9 | # 10 | # To enable this hook, rename this file to "applypatch-msg". 11 | 12 | . git-sh-setup 13 | commitmsg="$(git rev-parse --git-path hooks/commit-msg)" 14 | test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} 15 | : 16 | -------------------------------------------------------------------------------- /apd.aggregation/hooks/post-update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare a packed repository for use over 4 | # dumb transports. 5 | # 6 | # To enable this hook, rename this file to "post-update". 7 | 8 | exec git update-server-info 9 | -------------------------------------------------------------------------------- /apd.aggregation/hooks/pre-applypatch.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed 4 | # by applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. 8 | # 9 | # To enable this hook, rename this file to "pre-applypatch". 10 | 11 | . git-sh-setup 12 | precommit="$(git rev-parse --git-path hooks/pre-commit)" 13 | test -x "$precommit" && exec "$precommit" ${1+"$@"} 14 | : 15 | -------------------------------------------------------------------------------- /apd.aggregation/hooks/pre-receive.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to make use of push options. 4 | # The example simply echoes all push options that start with 'echoback=' 5 | # and rejects all pushes when the "reject" push option is used. 6 | # 7 | # To enable this hook, rename this file to "pre-receive". 8 | 9 | if test -n "$GIT_PUSH_OPTION_COUNT" 10 | then 11 | i=0 12 | while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" 13 | do 14 | eval "value=\$GIT_PUSH_OPTION_$i" 15 | case "$value" in 16 | echoback=*) 17 | echo "echo from the pre-receive-hook: ${value#*=}" >&2 18 | ;; 19 | reject) 20 | exit 1 21 | esac 22 | i=$((i + 1)) 23 | done 24 | fi 25 | -------------------------------------------------------------------------------- /apd.aggregation/info/exclude: -------------------------------------------------------------------------------- 1 | # git ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | # *.[oa] 6 | # *~ 7 | -------------------------------------------------------------------------------- /apd.aggregation/objects/pack/pack-0e7fa669b06f00ec17d49d52ad382f7f44ef9af1.idx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/apd.aggregation/objects/pack/pack-0e7fa669b06f00ec17d49d52ad382f7f44ef9af1.idx -------------------------------------------------------------------------------- /apd.aggregation/objects/pack/pack-0e7fa669b06f00ec17d49d52ad382f7f44ef9af1.pack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/apd.aggregation/objects/pack/pack-0e7fa669b06f00ec17d49d52ad382f7f44ef9af1.pack -------------------------------------------------------------------------------- /apd.sensors/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /apd.sensors/config: -------------------------------------------------------------------------------- 1 | [core] 2 | repositoryformatversion = 0 3 | filemode = false 4 | bare = true 5 | ignorecase = true 6 | [remote "origin"] 7 | url = git@github.com:matthewwilkes/apd.sensors.git 8 | -------------------------------------------------------------------------------- /apd.sensors/description: -------------------------------------------------------------------------------- 1 | Unnamed repository; edit this file 'description' to name the repository. 2 | -------------------------------------------------------------------------------- /apd.sensors/hooks/applypatch-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message taken by 4 | # applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. The hook is 8 | # allowed to edit the commit message file. 9 | # 10 | # To enable this hook, rename this file to "applypatch-msg". 11 | 12 | . git-sh-setup 13 | commitmsg="$(git rev-parse --git-path hooks/commit-msg)" 14 | test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} 15 | : 16 | -------------------------------------------------------------------------------- /apd.sensors/hooks/post-update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare a packed repository for use over 4 | # dumb transports. 5 | # 6 | # To enable this hook, rename this file to "post-update". 7 | 8 | exec git update-server-info 9 | -------------------------------------------------------------------------------- /apd.sensors/hooks/pre-applypatch.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed 4 | # by applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. 8 | # 9 | # To enable this hook, rename this file to "pre-applypatch". 10 | 11 | . git-sh-setup 12 | precommit="$(git rev-parse --git-path hooks/pre-commit)" 13 | test -x "$precommit" && exec "$precommit" ${1+"$@"} 14 | : 15 | -------------------------------------------------------------------------------- /apd.sensors/hooks/pre-receive.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to make use of push options. 4 | # The example simply echoes all push options that start with 'echoback=' 5 | # and rejects all pushes when the "reject" push option is used. 6 | # 7 | # To enable this hook, rename this file to "pre-receive". 8 | 9 | if test -n "$GIT_PUSH_OPTION_COUNT" 10 | then 11 | i=0 12 | while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" 13 | do 14 | eval "value=\$GIT_PUSH_OPTION_$i" 15 | case "$value" in 16 | echoback=*) 17 | echo "echo from the pre-receive-hook: ${value#*=}" >&2 18 | ;; 19 | reject) 20 | exit 1 21 | esac 22 | i=$((i + 1)) 23 | done 24 | fi 25 | -------------------------------------------------------------------------------- /apd.sensors/info/exclude: -------------------------------------------------------------------------------- 1 | # git ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | # *.[oa] 6 | # *~ 7 | -------------------------------------------------------------------------------- /apd.sensors/objects/pack/pack-2c612f3627aa76525a86c082a5393616dab67f82.idx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/apd.sensors/objects/pack/pack-2c612f3627aa76525a86c082a5393616dab67f82.idx -------------------------------------------------------------------------------- /apd.sensors/objects/pack/pack-2c612f3627aa76525a86c082a5393616dab67f82.pack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/advanced-python-development/0bf7a8de49f840c34f5f90cceb6301a685e934df/apd.sensors/objects/pack/pack-2c612f3627aa76525a86c082a5393616dab67f82.pack -------------------------------------------------------------------------------- /errata.md: -------------------------------------------------------------------------------- 1 | # Errata for *Book Title* 2 | 3 | On **page xx** [Summary of error]: 4 | 5 | Details of error here. Highlight key pieces in **bold**. 6 | 7 | *** 8 | 9 | On **page xx** [Summary of error]: 10 | 11 | Details of error here. Highlight key pieces in **bold**. 12 | 13 | *** --------------------------------------------------------------------------------