Easier and more self documenting patterns for loading/saving Persistent
[python_utils.git] / site_config.py
1 #!/usr/bin/env python3
2
3 # © Copyright 2021-2022, Scott Gasch
4
5 """Location/site dependent data."""
6
7 import logging
8 import platform
9 from dataclasses import dataclass
10 from typing import Callable, Optional
11
12 # Note: this module is fairly early loaded.  Be aware of dependencies.
13 import config
14 from type.locations import Location
15
16 logger = logging.getLogger(__name__)
17
18 args = config.add_commandline_args(
19     f'Global Site Config ({__file__})',
20     'Args related to global site-specific configuration',
21 )
22 args.add_argument(
23     '--site_config_override_location',
24     default='NONE',
25     const='NONE',
26     nargs='?',
27     choices=['HOUSE', 'CABIN', 'NONE'],
28     help='Where are we, HOUSE, CABIN?  Overrides standard detection code.',
29 )
30
31
32 @dataclass
33 class SiteConfig(object):
34     """The set of information specific to where the program is running."""
35
36     location_name: str
37     """Either "HOUSE" or "CABIN" depending on where we're running"""
38
39     location: Location
40     """Same as above but as an enum value instead of a string"""
41
42     network: str
43     """The local network specification, e.g. 192.168.0.0/24."""
44
45     network_netmask: str
46     """The netmask of the local network, e.g. 255.255.255.0."""
47
48     network_router_ip: str
49     """The IP address of the local router, e.g. 192.168.0.1."""
50
51     presence_location: Location
52     """Same as location, above."""
53
54     is_anyone_present: Callable
55     """Returns a callable which, when invoked, will tell you if it detects
56     any person in your location by auditing network device MAC addresses."""
57
58     arper_minimum_device_count: int
59     """How many MAC addresses do we need to see for it to be considered a
60     successful scan?"""
61
62     arper_cache_file: str
63     """The location of the persisted IP-MAC address mappings."""
64
65
66 def get_location_name() -> str:
67     """
68     Where are we?
69
70     >>> location = get_location_name()
71     >>> location == 'HOUSE' or location == 'CABIN'
72     True
73
74     """
75     return get_config().location_name
76
77
78 def get_location() -> Location:
79     """
80     Returns location as an enum instead of a string.
81
82     >>> from type.locations import Location
83     >>> location = get_location()
84     >>> location == Location.HOUSE or location == Location.CABIN
85     True
86
87     """
88     return get_config().location
89
90
91 def _is_anyone_present_wrapper(location: Location):
92     import base_presence
93
94     p = base_presence.PresenceDetection()
95     return p.is_anyone_in_location_now(location)
96
97
98 def other_location() -> str:
99     """
100     Returns the location where this program is _NOT_ running.
101
102     >>> x = other_location()
103     >>> x in set(['HOUSE', 'CABIN'])
104     True
105
106     >>> y = this_location()
107     >>> x == y
108     False
109
110     """
111     this = this_location()
112     if this == 'HOUSE':
113         return 'CABIN'
114     elif this == 'CABIN':
115         return 'HOUSE'
116     else:
117         raise Exception(f"{this} doesn't tell me where I'm running?!")
118
119
120 def this_location() -> str:
121     """
122     Returns the location where this program _IS_ running.
123
124     >>> x = this_location()
125     >>> x in set(['HOUSE', 'CABIN'])
126     True
127
128     """
129     hostname = platform.node()
130     if '.house' in hostname:
131         location = 'HOUSE'
132     elif '.cabin' in hostname:
133         location = 'CABIN'
134     elif '.local' in hostname:
135         location = 'HOUSE'
136     else:
137         raise Exception(f"{hostname} doesn't help me know where I'm running?!")
138     return location
139
140
141 def effective_location(location_override: Optional[str] = None) -> str:
142     """Detects and returns a location taking into account two override
143     mechanisms.
144
145     >>> x = effective_location()
146     >>> x in set(['HOUSE', 'CABIN'])
147     True
148
149     >>> effective_location('HOUSE')
150     'HOUSE'
151
152     """
153     if location_override is None:
154         try:
155             location_override = config.config['site_config_override_location']
156         except KeyError:
157             location_override = None
158
159     if location_override is None or location_override == 'NONE':
160         location = this_location()
161     else:
162         logger.debug('site_config\'s location_override was set to: %s', location_override)
163         location = location_override
164     return location
165
166
167 def get_config(location_override: Optional[str] = None):
168     """
169     Get a configuration dataclass with information that is
170     site-specific including the current running location.
171
172     >>> cfg = get_config()
173     >>> cfg.location_name == 'HOUSE' or cfg.location_name == 'CABIN'
174     True
175
176     """
177     location = effective_location(location_override)
178     if location == 'HOUSE':
179         return SiteConfig(
180             location_name='HOUSE',
181             location=Location.HOUSE,
182             network='10.0.0.0/24',
183             network_netmask='255.255.255.0',
184             network_router_ip='10.0.0.1',
185             presence_location=Location.HOUSE,
186             is_anyone_present=lambda x=Location.HOUSE: _is_anyone_present_wrapper(x),
187             arper_minimum_device_count=50,
188             arper_cache_file='/home/scott/cache/.arp_table_cache_house',
189         )
190     elif location == 'CABIN':
191         return SiteConfig(
192             location_name='CABIN',
193             location=Location.CABIN,
194             network='192.168.0.0/24',
195             network_netmask='255.255.255.0',
196             network_router_ip='192.168.0.1',
197             presence_location=Location.CABIN,
198             is_anyone_present=lambda x=Location.CABIN: _is_anyone_present_wrapper(x),
199             arper_minimum_device_count=15,
200             arper_cache_file='/home/scott/cache/.arp_table_cache_cabin',
201         )
202     else:
203         raise Exception(f'Unknown site location: {location}')
204
205
206 if __name__ == '__main__':
207     import doctest
208
209     doctest.testmod()