appsettings2 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: appsettings2
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: A python library that unifies configuration sources into a Configuration object that can be bound to complex types, or accessed directly for configuration data.
5
5
  License: MIT
6
6
  Author: Shaun Wilson
@@ -55,21 +55,39 @@ Given the example above, this means that configuration data provided via Environ
55
55
 
56
56
  ## Accessing Configuration Data
57
57
 
58
- There are two methods of directly accessing Configuration data. The first is by using first-class attributes, and the second is by using a `get(...)` method.
58
+ There are multiple ways of accessing Configuration data:
59
59
 
60
- Consider the following code which demonstrates both methods, where all three of these statements are equivalent:
60
+ * Attributes attached to the `Configuration` object which represent your configuration data.
61
+ * Calling `get(...)` and `set(...)` methods, passing in the key of the configuration data.
62
+ * A dictionary-like interface exposing configuration data from an indexer.
63
+ * Binding the configuration data to a class/object you define.
64
+
65
+ ### Direct Access via `Configuration` object
66
+
67
+ Consider the following code which demonstrates the first three methods mentioned above. All three of these methods are equivalent and return the same underlying value.
61
68
 
62
69
  ```python
70
+ # access `Configuration` attributes
63
71
  value = configuration.ConnectionStrings.SampleDb
64
- value = configuration.get('ConnectionStrings:SampleDb')
72
+ # access using `get(...)`
65
73
  value = configuration.get('ConnectionStrings__SampleDb')
74
+ value = configuration.get('ConnectionStrings:SampleDb')
75
+ value = configuration.get('ConnectionStrings').get('SampleDb')
76
+ # access using indexer
77
+ value = configuration['ConnectionStrings__SampleDb']
78
+ value = configuration['ConnectionStrings:SampleDb']
79
+ value = configuration['ConnectionStrings']['SampleDb']
66
80
  ```
67
81
 
68
- When accessing hierarchical data with `get(...)` you can use a double-underscore delimiter or a colon delimiter, they are equivalent. Devs and Ops from different walks will likely prefer one form over the other, so both are supported.
82
+ When accessing hierarchical data by "key" you can use a double-underscore delimiter or a colon delimiter, they are equivalent.
83
+
84
+ Devs and Ops from different walks will likely prefer one form over the other, so both are supported.
69
85
 
70
- ### Binding to Configuration Data
86
+ Additionally, keys are case-insensitive (attribute names are not.)
71
87
 
72
- It's also possible to bind configuration data to complex types. While some of the implementation is currently naive, it should work well for the vast majority of use cases. If you find your particular case does not work well please reach out to me and I will work with you to implement a sensible solution. Consider the following Python code:
88
+ ### Binding `Configuration` to Objects
89
+
90
+ It's possible to bind configuration data to complex types. While some of the implementation is currently naive, it should work well for the vast majority of use cases. If you find your particular case does not work well please reach out to me and I will work with you to implement a sensible solution. Consider the following Python code:
73
91
 
74
92
  ```python
75
93
  json = """{
@@ -82,7 +100,7 @@ json = """{
82
100
 
83
101
  class ConnStrs:
84
102
  """An ugly class name to demonstrate the class name does not matter."""
85
- SampleDb:str = None
103
+ SampleDB:str = None
86
104
 
87
105
  class AppSettings:
88
106
  ConnectionStrings:ConnStrs = None
@@ -95,6 +113,8 @@ configuration = ConfigurationBuilder()\
95
113
 
96
114
  settings = AppSettings()
97
115
  configuration.bind(settings)
116
+
117
+ print(settings.ConnectionStrings.SampleDB) # prints "my_cxn_string"
98
118
  ```
99
119
 
100
120
  The resulting `settings` object will contain all of the configuration data properly typed according to type hints.
@@ -103,19 +123,28 @@ It is also possible to bind to a subset of a configuration, building upon the ab
103
123
 
104
124
  ```python
105
125
  connectionStrings = configuration.bind(ConnStrs(), 'ConnectionStrings')
106
- print(connectionStrings.SampleDb)
126
+ print(connectionStrings.SampleDB)
107
127
  ```
108
128
 
109
- ### Accessing Configuration Data as a Dictionary
129
+ Lastly, a cautious eye may have noticed that the input configuration and class definition have a casing difference. `SampleDb` vs `SampleDB` -- by design binding is case-insensitive. This ensures that automation systems which can only communicate in upper-case can still support developers that prefer to have readable code ... without burdening the developers with extra work.
130
+
131
+ ### Accessing `Configuration` Dictionary-like
110
132
 
111
- `Configuration` is dict-like (with minor exceptions such as not supporting dynamic views) and in most cases can be used as if it were a dict where strict type checks would not otherwise prevent it.
133
+ `Configuration` is dict-like and in most cases can be used as if it were a dict where strict type checks would not otherwise prevent it.
112
134
 
113
- Alternatively, you can project a `Configuration` instnace into a dictionary (as a copy.) This is primarily included as a debugging aid, but, if you have some chunk of code that can consume a dictionary but can't consume a Python object (it happens) then you can get at a proper dictionary instance as follows:
135
+ ### Transform `Configuration` to Dictionary
136
+
137
+ You can transform a `Configuration` instance into a dictionary (as a copy.) If you have some chunk of code that can consume a dictionary but can't consume a Python object (it happens) then you can get at a proper dictionary instance as follows:
114
138
 
115
139
  ```python
116
- config:Configuration = builder.build()
117
- my_dict = config.toDictionary()
118
- print(my_dict)
140
+ configuration = builder.build()
141
+ print(
142
+ json.dumps(
143
+ configuration.toDictionary()
144
+ )
145
+ )
146
+ # or, this short-hand for the same
147
+ print(configuration)
119
148
  ```
120
149
 
121
150
  ## Providers
@@ -156,7 +185,7 @@ It's also possible to use a colon in lieu of a double_underscore, but this may h
156
185
  'ConnectionStrings:SampleDb=my_connection_string'
157
186
  ```
158
187
 
159
- This provider was implemented in a way that it does not interfere with libraries such as `argparse`, and should work as expected with a well-formed command-line interface.
188
+ This provider was implemented in a way that it does not interfere with libraries such as `argparse`, and should work as expected with a well-formed command-line interface. An explicit goal of this provider was to not depend on a CLI library at all.
160
189
 
161
190
  ### Environment Variables
162
191
 
@@ -215,6 +244,23 @@ The `TomlConfigurationProvider` should be functionally identical to the JSON pro
215
244
 
216
245
  The `YamlConfigurationProvider` should be functionally identical to the JSON and TOML providers, with the obvious difference that it loads Configuration data from a yaml file.
217
246
 
247
+ ### Custom Provider Development
248
+
249
+ You can implement custom Configuration Providers by subclassing `ConfigurationProvider` and implementing `populateConfiguration(...)`.
250
+
251
+ For a peek at the simplicity of provider implementation, this is `ConfigurationProvider`:
252
+
253
+ ```python
254
+ class ConfigurationProvider(abstract):
255
+
256
+ @abstractmethod
257
+ def populateConfiguration(self, configuration:Configuration) -> None:
258
+ """The ConfigurationProvider will populate the provided Configuration instance."""
259
+ pass
260
+ ```
261
+
262
+ Essentially, you read your configuration source and write the configuration data into the specified `Configuration` object. Much of the complexity in dealing with hierarchy and allocation is encapsulated within the impl of `Configuration`. As a result, most providers are less than 20 lines of functional code.
263
+
218
264
  ## Special Attribute Name Handling
219
265
 
220
266
  ### Upper-Case Attribute Names
@@ -280,7 +326,7 @@ Although attribute names are transformed, keys are not. Therefore, given the abo
280
326
 
281
327
  ```python
282
328
  # access via object attributes
283
- configuration.query_tab.fragement_left.input
329
+ configuration.query_tab.fragment_left.input
284
330
  # access using indexers
285
331
  configuration['query-tab']['fragment#left']
286
332
  # access using other 'key' methods
@@ -302,3 +348,7 @@ Attempting to access these values will result in a value of `False` being return
302
348
 
303
349
  If you made it this far, this library is probably what you were looking for. I'm certain there are other libraries which accomplish much of what I am attempting to accomplish here, but I wanted something I can directly support over the coming years. That, and the nearest equivalent to what I wanted fell out of active development nearly a decade ago and I just didn't feel comfortable adopting it for new work.
304
350
 
351
+ ## Contact
352
+
353
+ You can reach me on [Discord](https://discordapp.com/users/307684202080501761) or [open an Issue on Github](https://github.com/wilson0x4d/appsettings2/issues/new/choose).
354
+
@@ -34,21 +34,39 @@ Given the example above, this means that configuration data provided via Environ
34
34
 
35
35
  ## Accessing Configuration Data
36
36
 
37
- There are two methods of directly accessing Configuration data. The first is by using first-class attributes, and the second is by using a `get(...)` method.
37
+ There are multiple ways of accessing Configuration data:
38
38
 
39
- Consider the following code which demonstrates both methods, where all three of these statements are equivalent:
39
+ * Attributes attached to the `Configuration` object which represent your configuration data.
40
+ * Calling `get(...)` and `set(...)` methods, passing in the key of the configuration data.
41
+ * A dictionary-like interface exposing configuration data from an indexer.
42
+ * Binding the configuration data to a class/object you define.
43
+
44
+ ### Direct Access via `Configuration` object
45
+
46
+ Consider the following code which demonstrates the first three methods mentioned above. All three of these methods are equivalent and return the same underlying value.
40
47
 
41
48
  ```python
49
+ # access `Configuration` attributes
42
50
  value = configuration.ConnectionStrings.SampleDb
43
- value = configuration.get('ConnectionStrings:SampleDb')
51
+ # access using `get(...)`
44
52
  value = configuration.get('ConnectionStrings__SampleDb')
53
+ value = configuration.get('ConnectionStrings:SampleDb')
54
+ value = configuration.get('ConnectionStrings').get('SampleDb')
55
+ # access using indexer
56
+ value = configuration['ConnectionStrings__SampleDb']
57
+ value = configuration['ConnectionStrings:SampleDb']
58
+ value = configuration['ConnectionStrings']['SampleDb']
45
59
  ```
46
60
 
47
- When accessing hierarchical data with `get(...)` you can use a double-underscore delimiter or a colon delimiter, they are equivalent. Devs and Ops from different walks will likely prefer one form over the other, so both are supported.
61
+ When accessing hierarchical data by "key" you can use a double-underscore delimiter or a colon delimiter, they are equivalent.
62
+
63
+ Devs and Ops from different walks will likely prefer one form over the other, so both are supported.
48
64
 
49
- ### Binding to Configuration Data
65
+ Additionally, keys are case-insensitive (attribute names are not.)
50
66
 
51
- It's also possible to bind configuration data to complex types. While some of the implementation is currently naive, it should work well for the vast majority of use cases. If you find your particular case does not work well please reach out to me and I will work with you to implement a sensible solution. Consider the following Python code:
67
+ ### Binding `Configuration` to Objects
68
+
69
+ It's possible to bind configuration data to complex types. While some of the implementation is currently naive, it should work well for the vast majority of use cases. If you find your particular case does not work well please reach out to me and I will work with you to implement a sensible solution. Consider the following Python code:
52
70
 
53
71
  ```python
54
72
  json = """{
@@ -61,7 +79,7 @@ json = """{
61
79
 
62
80
  class ConnStrs:
63
81
  """An ugly class name to demonstrate the class name does not matter."""
64
- SampleDb:str = None
82
+ SampleDB:str = None
65
83
 
66
84
  class AppSettings:
67
85
  ConnectionStrings:ConnStrs = None
@@ -74,6 +92,8 @@ configuration = ConfigurationBuilder()\
74
92
 
75
93
  settings = AppSettings()
76
94
  configuration.bind(settings)
95
+
96
+ print(settings.ConnectionStrings.SampleDB) # prints "my_cxn_string"
77
97
  ```
78
98
 
79
99
  The resulting `settings` object will contain all of the configuration data properly typed according to type hints.
@@ -82,19 +102,28 @@ It is also possible to bind to a subset of a configuration, building upon the ab
82
102
 
83
103
  ```python
84
104
  connectionStrings = configuration.bind(ConnStrs(), 'ConnectionStrings')
85
- print(connectionStrings.SampleDb)
105
+ print(connectionStrings.SampleDB)
86
106
  ```
87
107
 
88
- ### Accessing Configuration Data as a Dictionary
108
+ Lastly, a cautious eye may have noticed that the input configuration and class definition have a casing difference. `SampleDb` vs `SampleDB` -- by design binding is case-insensitive. This ensures that automation systems which can only communicate in upper-case can still support developers that prefer to have readable code ... without burdening the developers with extra work.
109
+
110
+ ### Accessing `Configuration` Dictionary-like
89
111
 
90
- `Configuration` is dict-like (with minor exceptions such as not supporting dynamic views) and in most cases can be used as if it were a dict where strict type checks would not otherwise prevent it.
112
+ `Configuration` is dict-like and in most cases can be used as if it were a dict where strict type checks would not otherwise prevent it.
91
113
 
92
- Alternatively, you can project a `Configuration` instnace into a dictionary (as a copy.) This is primarily included as a debugging aid, but, if you have some chunk of code that can consume a dictionary but can't consume a Python object (it happens) then you can get at a proper dictionary instance as follows:
114
+ ### Transform `Configuration` to Dictionary
115
+
116
+ You can transform a `Configuration` instance into a dictionary (as a copy.) If you have some chunk of code that can consume a dictionary but can't consume a Python object (it happens) then you can get at a proper dictionary instance as follows:
93
117
 
94
118
  ```python
95
- config:Configuration = builder.build()
96
- my_dict = config.toDictionary()
97
- print(my_dict)
119
+ configuration = builder.build()
120
+ print(
121
+ json.dumps(
122
+ configuration.toDictionary()
123
+ )
124
+ )
125
+ # or, this short-hand for the same
126
+ print(configuration)
98
127
  ```
99
128
 
100
129
  ## Providers
@@ -135,7 +164,7 @@ It's also possible to use a colon in lieu of a double_underscore, but this may h
135
164
  'ConnectionStrings:SampleDb=my_connection_string'
136
165
  ```
137
166
 
138
- This provider was implemented in a way that it does not interfere with libraries such as `argparse`, and should work as expected with a well-formed command-line interface.
167
+ This provider was implemented in a way that it does not interfere with libraries such as `argparse`, and should work as expected with a well-formed command-line interface. An explicit goal of this provider was to not depend on a CLI library at all.
139
168
 
140
169
  ### Environment Variables
141
170
 
@@ -194,6 +223,23 @@ The `TomlConfigurationProvider` should be functionally identical to the JSON pro
194
223
 
195
224
  The `YamlConfigurationProvider` should be functionally identical to the JSON and TOML providers, with the obvious difference that it loads Configuration data from a yaml file.
196
225
 
226
+ ### Custom Provider Development
227
+
228
+ You can implement custom Configuration Providers by subclassing `ConfigurationProvider` and implementing `populateConfiguration(...)`.
229
+
230
+ For a peek at the simplicity of provider implementation, this is `ConfigurationProvider`:
231
+
232
+ ```python
233
+ class ConfigurationProvider(abstract):
234
+
235
+ @abstractmethod
236
+ def populateConfiguration(self, configuration:Configuration) -> None:
237
+ """The ConfigurationProvider will populate the provided Configuration instance."""
238
+ pass
239
+ ```
240
+
241
+ Essentially, you read your configuration source and write the configuration data into the specified `Configuration` object. Much of the complexity in dealing with hierarchy and allocation is encapsulated within the impl of `Configuration`. As a result, most providers are less than 20 lines of functional code.
242
+
197
243
  ## Special Attribute Name Handling
198
244
 
199
245
  ### Upper-Case Attribute Names
@@ -259,7 +305,7 @@ Although attribute names are transformed, keys are not. Therefore, given the abo
259
305
 
260
306
  ```python
261
307
  # access via object attributes
262
- configuration.query_tab.fragement_left.input
308
+ configuration.query_tab.fragment_left.input
263
309
  # access using indexers
264
310
  configuration['query-tab']['fragment#left']
265
311
  # access using other 'key' methods
@@ -280,3 +326,7 @@ Attempting to access these values will result in a value of `False` being return
280
326
  ## Conclusion
281
327
 
282
328
  If you made it this far, this library is probably what you were looking for. I'm certain there are other libraries which accomplish much of what I am attempting to accomplish here, but I wanted something I can directly support over the coming years. That, and the nearest equivalent to what I wanted fell out of active development nearly a decade ago and I just didn't feel comfortable adopting it for new work.
329
+
330
+ ## Contact
331
+
332
+ You can reach me on [Discord](https://discordapp.com/users/307684202080501761) or [open an Issue on Github](https://github.com/wilson0x4d/appsettings2/issues/new/choose).
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "appsettings2"
3
- version = "0.3.0"
3
+ version = "0.3.1"
4
4
  description = "A python library that unifies configuration sources into a Configuration object that can be bound to complex types, or accessed directly for configuration data."
5
5
  authors = ["Shaun Wilson <mrshaunwilson@msn.com>"]
6
6
  license = "MIT"
@@ -6,4 +6,4 @@ from .Configuration import Configuration
6
6
  from .ConfigurationBuilder import ConfigurationBuilder
7
7
  from .ConfigurationException import ConfigurationException
8
8
 
9
- __version__ = '0.3.0'
9
+ __version__ = '0.3.1'
File without changes