Features¶
ModCFG features the following:
Note
In this documentation, there will be several codeblocks like
...
...
and
...
Please interpret the code in ModCFG Document
as the DOC
variable given in the Python script
s.
Enumerations¶
Let’s say you have an enum MyEnum defined as
import enum
class MyEnum(enum.Enum):
is_cool = "we are swag"
isnt_cool = "we are not swag"
And we want our clients to use that enum somewhere in the configuration format. Like this (in YAML format):
bob:
personality: is_cool # Pretend this is the enumeration
hair color: brown
loves yaml: no
Before we look at ModCFG, let’s check out some other ways to implement an enumeration system.
JSON¶
JSON has no built-in support for enums so you’ll need to turn to post-processors like JSONSchema.
YAML, TOML, INI, etc¶
The same goes for YAML, TOML, INI, etc. They all don’t have built-in enumeration support. You still have to turn to post-processing in order to implement an enum system.
ModCFG¶
But it’s really easy to do with ModCFG’s built-in enumeration support:
module "Bob":
personality: :is_cool
'hair color': brown
'loves yaml': no
import modcfg, enum
class MyEnum(enum.Enum): # Define the enum
is_cool = "we are swag"
isnt_cool = "we are not swag"
output = modcfg.loads(
DOC,
enums=[MyEnum], # Use the enum
)
print(output)
[
Module(
name="Bob",
contents={
"personality": <MyEnum.is_cool: 'we are swag'>,
"hair color": "brown",
"loves yaml": "no",
},
)
]
Epic, right? ModCFG has an exhaustive enumeration resolver which never fails silently unless explicitly requested so.
Here’s another example:
module ThatXliner:
personality = :is_cool
hair_color => black
coder = true
module SomePythoniast:
hates = :polymorphism
loves = :duck_typing
import modcfg, enum
class MyFirstEnum(enum.Enum):
is_cool = "we are swag"
isnt_cool = "we are not swag"
class MySecondEnum(enum.Enum):
polymorphism = "sucks"
duck_typing = "is cool"
print(
modcfg.loads(
DOC,
enums=[MyFirstEnum, MySecondEnum],
)
)
[
Module(
name="ThatXliner",
contents={
"personality": <MyFirstEnum.is_cool: 'we are swag'>,
"hair_color": "black",
"coder": True,
},
),
Module(
name="SomePythoniast",
contents={
"hates": <MySecondEnum.polymorphism: 'sucks'>,
"loves": <MySecondEnum.duck_typing: 'is cool'>,
},
),
]
But there are cases were there are ambiguous enums. If that’s the case, ModCFG will, if you didn’t explicitly silence it, fail on ambiguous enums
Ambigous Enumerations¶
If there’s ambiguous enums, ModCFG’s exhaustive enumeration resolver will raise a modcfg.errors.EnumResolveError
like so:
module Story:
is_made_by_a: :duck_typing # NANI!?
mod Python:
has = :duck_typing
import modcfg, enum
class Enum1(enum.Enum):
duck_typing = "DUCKS CAN TYPE!!?"
human_typing = "Much better"
class Enum2(enum.Enum):
polymorphism = "sucks"
duck_typing = "is cool"
print(
modcfg.loads(
DOC,
enums=[Enum1, Enum2],
)
)
Traceback (most recent call last):
...
modcfg.errors.EnumResolveError: Ambigous enumerations: <Enum1.duck_typing: 'DUCKS CAN TYPE!!?'>, <Enum2.duck_typing: 'is cool'>
Really, there shouldn’t have ambiguous keys.
But if you really need to, you can silence the ambiguity checker by setting the enum_ambiguity_check
parameter to False for modcfg.loads()
. It’ll return the first found result instead (i.e. making ModCFG guess which one you want, in the order of enums given in the enums argument).
But if you do find the need for enumerations with ambiguous keys and don’t want ModCFG to guess, you can use the enum namespace specifier like so:
module Story:
is_made_by_a: :Enum1.duck_typing # Can ducks type?
mod Python:
has = :Enum2.duck_typing
import modcfg, enum
class Enum1(enum.Enum):
duck_typing = "DUCKS CAN TYPE!!?"
human_typing = "Much better"
class Enum2(enum.Enum):
polymorphism = "sucks"
duck_typing = "is cool"
print(
modcfg.loads(
DOC,
enums=[Enum1, Enum2],
)
)
[
Module(
name="Story",
contents={"is_made_by_a": <Enum1.duck_typing: 'DUCKS CAN TYPE!!?'>},
),
Module(
name="Python", contents={"has": <Enum2.duck_typing: 'is cool'>},
),
]
Modules¶
ModCFG features serialization of hashable objects called modules. They’re actually NamedTuple
s that contain 2 slots:
name: The name of the module
contents: The module contents
The thing is, ModCFG currently doesn’t treat a list of modules like dictionaries where one module with the same name as one defined earlier can overwrite said module defined earlier.
Not required¶
Instead of limiting ModCFG to a configuration format that requires modules, I decided I wanted it to also be a data format: a YAML-like alternative:
>>> import modcfg
>>> DOC = """
... bruh:
... yaml: {
... kinda:
... - works
... - { this: [is,
...
... insane]
...
... }
... }
"""
>>> print(modcfg.loads(DOC))
[{'bruh': {'yaml': {'kinda': ['works', {'this': ['is', 'insane']}]}}}]
Want to remove those unnecessary brackets around it which denotes a list? Set the inline parameter of modcfg.loads()
to True. What this does is that if, and only if, the parsed and evaluated values is a one-element list, then it’ll return that one element.
Why is the default a list? That way, one can define multiple modules in a document, like this:
module "Module one!":
some: content
mod "Module 2.":
some: 'more content!'
import modcfg
print(modcfg.loads(DOC))
[
Module(name="Module one!", contents={"some": "content"}),
Module(name="Module 2.", contents={"some": "more content!"}),
]
Or even (shudder)
module "Module one!":
some: content
[loving]
{weird: stuff}
import modcfg
print(modcfg.loads(DOC))
[
Module(name="Module one!", contents={"some": "content"}),
["loving"],
{"weird": "stuff"},
]
Datetimes and dates¶
ModCFG supports datetimes and dates:
mod 'Date example':
today = datetime(2021-04-18 14:50:55.016922)
tomorrow = date(2021-04-19)
import modcfg
print(modcfg.loads(DOC))
[
Module(
name="Date example",
contents=[
{
"today": datetime.datetime(2021, 4, 18, 14, 50, 55, 16922),
"tomorrow": datetime.date(2021, 4, 19),
}
],
)
]
Strings¶
Single-line strings can be single quoted ('
) or double quoted ("
). You can leave out the quotes if the string contents matches the following regex:
[a-zA-Z_]\w*
Multiline strings can also be single or double quoted. They are denoted with 2 quote characters.
Both (multiline or single-line) strings may be prefixed with t
, l
, or r
this is what they mean:
- t
Dedent the string’s contents (uses
textwrap.dedent()
).- l
Run
str.lstrip()
on the string’s contents.- r
Run
str.rstrip()
on the string’s contents. Very similar tol
.
Warning
When prefixing, you must put t
, l
, or r
in that exact order.
Escaping characters¶
You can create unicode characters using the \uXXXX
escape sequence. The u
is case-insensitive
Example:
>>> import modcfg
>>> print(modcfg.loads(R'{main: "\U0001f642"}'))
[{'main': '🙂'}]
Future restraints¶
Key-value separation characters¶
Currently, I can’t decide whether the key separation character (the : in {hello: world}) should be which character. So I made it the following characters letting you decide.
: Good ol’ colon
=> Fat arrow
-> Skinny arrow
= Equal sign
I may make a poll in the future.
Module definition keywords¶
Currently, you can use either mod
or module
to define a module.
In the future, I plan to limit it to either one or the other.