Java application properties for Python
Project description
Python Application Properties
What is this
It is a simple library to inject non-sensitive configurations into class variables.
Basically, it's like BaseSettings
in pydantic
library but for constants in json
and yaml
formats.
py-app-properties
can work with different Python types (like tuple
, datetime
, dataclass
and so on) and recursively cast config values to them.
When to use
- If you deal with constants in your code, like error messages, default values for something, numeric coefficients, and so on.
- If you hate global variables, and you like non-python files to store static information.
- If you want to have an easy way to manage different constants depending on environments (like
test
,dev
,prod
). - If you like type hints and clean code.
How to install
To install this library just enter:
pip install py-app-properties
How to use
For injecting values you need only the decorator properties
under a target class.
By default, the library will search a config file application.yml
in the same directory
where your file with the used decorator is located, like below:
project_root
|---services
| | email_message_service.py
| | application.yml
|.....
Example:
services/application.yml
:
default_text_style:
size: 14
weight: bold
font: "Times New Roman"
color:
- 128
- 128
- 128
language_greetings:
- language: english
text: hello
- language: german
text: hallo
- language: french
text: bonjour
wellcome_message: "{greeting}! Thank you for registration, {username}!"
mailing_frequency:
days: 5
hours: 12
services/email_message_service.py
:
from typing import TypedDict
from dataclasses import dataclass
from datetime import timedelta
from app_properties import properties
@dataclass
class TextStyle:
size: int
weight: str
font: str
color: tuple[int, int, int] | str
class GreetingDict(TypedDict):
language: str
text: str
@properties
class EmailMessageService:
default_text_style: TextStyle
language_greetings: list[GreetingDict]
wellcome_message: str
mailing_frequency: timedelta | None
# And using these class variables in some methods...
And that's how will look an equivalent of the code above but with "hard-coded" constants, without config files and @properties
decorator:
class EmailMessageService:
default_text_style = TextStyle(
size=14, weight="bold", font="Times New Roman", color=(128, 128, 128)
)
language_greetings = [
GreetingDict(language="english", text="hello"),
GreetingDict(language="german", text="hallo"),
GreetingDict(language="french", text="bonjour"),
]
wellcome_message = "{greeting}! Thank you for registration, {username}!"
mailing_frequency = timedelta(days=5, hours=12)
# And using these class variables in some methods...
All config values will be inserted and cast according to the type annotations once during the application or script start. Additionally, the decorator takes such params:
filename
- the name of a file with config. By default, it isapplication.yml
. Use a relative path with../
to read the file from a parent directory;type_cast
- used to know whether you want to cast config values to the field type. By default, it'sTrue
, which means values in a config file will be cast according to the type hints. All types specified in the sectionsupported types
will be available for type casting. Also, nested types will be recursively cast. IfFalse
, type hinting is ignored, and available types are limited by a file format;override_default
- used to know whether you want to override the default values of class variables. By default, it isFalse
;lazy_init
- used to know whether you want to set config values immediately on the application start-up or on demand ("lazily") after calling the methodinit_props()
. By default, it isFalse
;root
- root key in the config. It's the way to create "namespaces" when you work with multiple classes but use a single config file. It could be a nested value with separation by dots, for example:
# example.yml
services:
email_service:
key: some value
auth_service:
key: another value
clients:
translation_client:
key: value
# and so on...
from app_properties import properties
@properties(filename="example.yml", root="services.email_service")
class EmailService:
key: str # will store "some value"
@properties(filename="example.yml", root="services.auth_service")
class AuthService:
key: str # will store "another value"
Different environments
Using this library it's easy to manage different environments and corresponding config files. It could be done like so:
import os
from app_properties import properties
@properties(filename=os.getenv("CONFIG_FILENAME", "application.yml"))
class SomeEnvDependingService:
env_depend_var: str
In this case, you set CONFIG_FILENAME=application-dev.yml
in env variables, and py-app-properties
will use that file.
Lazy initialization
If you want to create some dataclass instance with filled required data during init,
and then populated with config values, you can use the parameter lazy_init
for this purpose.
All file constants will be injected after calling the method init_props
:
# All definitions like in previous examples
@properties(lazy_init=True)
@dataclass
class EmailMessageServiceConfig:
default_text_style: TextStyle
language_greetings: list[GreetingDict]
mailing_frequency: timedelta | None = None
wellcome_message: str = "some_default_message"
email_config = EmailMessageServiceConfig(
default_text_style=TextStyle(
size=16, weight="normal", font="Arial", color="black"
),
language_greetings=[GreetingDict(language="english", text="hello")]
)
# it works like a normal dataclass instance
assert email_config.default_text_style == TextStyle(
size=16, weight="normal", font="Arial", color="black"
)
assert email_config.mailing_frequency is None
assert email_config.wellcome_message == "some_default_message"
# after calling `init_props`, config values will be injected.
# It also overrides all values that we set during initialize before.
email_config.init_props()
assert email_config.default_text_style == TextStyle(
size=14, weight="bold", font="Times New Roman", color=(128, 128, 128)
)
assert email_config.mailing_frequency == timedelta(days=5, hours=12)
assert email_config.wellcome_message == (
"{greeting}! Thank you for registration, {username}!"
)
Because there are 3 sources of data (default values, values passed during initialization, and config file values),
it could be hard to understand how we can resolve this conflict.
Bellow is the table to clarify the behavior of the init_props
method.
init | default | config | will be used |
---|---|---|---|
- | + | - | default |
- | + | + | config |
+ | ~ | - | init |
+ | ~ | + | init \ config |
+ - provided; - - missing; ~ - not affect. |
How you can see, when both init
and config
values provided, they are equally important,
but, by default, config
have higher priority and overrides init
.
If you, for some reason, don't want to override already initialized values, only defaults,
it's also possible with init_props(override_init=False)
Supported types
The table below shows how config values (json
syntax example) are cast to Python types:
Python type | Config file type | Config example |
---|---|---|
int |
int str |
10 "10" |
float |
float int str |
10.5 10 "10.5" |
str |
str |
"string value" |
bool |
bool int str |
true / false 1 / 0 "True" / "False" , "true" / "false" |
None |
null |
null |
dict |
dict |
{"key": "value"} |
list tuple set frozenset |
list |
["val1", "val2"] |
TypedDict |
dict |
{"str_var": "value"} |
NamedTuple |
list dict |
["value", 10] {"str_val": "value", "int_val": 10} |
dataclass |
dict |
{"str_val": "str", "int_val": 10} |
datetime.datetime |
str int list dict |
"2022-12-11T10:20:23" 1670754600 [2022, 12, 11, 10, 20, 23] {"year": 2022, "month": 12, "day": 11, "hour": 10, "minute": 20, "second": 23} |
datetime.date |
str list dict |
"2022-12-11" [2022, 12, 11] {"year": 2022, "month": 12, "day": 11} |
datetime.time |
str list dict |
"12:30:02" [12, 30, 2] {"hour": 12, "minute": 30, "second": 2} |
datetime.timedelta |
dict |
{"days": 1, "hours": 2, "minutes": 10} |
enum.Enum |
str int |
"VALUE" 10 |
re.Pattern |
str |
"\w+" |
About contributing
You will make py-app-properties
better if you open issues or create pull requests with improvements.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file py-app-properties-1.3.0.tar.gz
.
File metadata
- Download URL: py-app-properties-1.3.0.tar.gz
- Upload date:
- Size: 13.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.8.13
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5765c5e88a60f7f265335389d625a6ea73ec2fc9ea21cd830a4cc2fbe6cbee59 |
|
MD5 | 487faf741279d3f3dcd3a38f8815354d |
|
BLAKE2b-256 | c0e7fc8197d31245a56850567a83b8cacec5660210fa97bbcb3ca726b69f443b |