diff --git a/404.html b/404.html index 6ff9388..31107b4 100644 --- a/404.html +++ b/404.html @@ -11,7 +11,7 @@ - + @@ -81,7 +81,7 @@ @@ -503,63 +520,17 @@ - - - - -

Api references

- - -
- - - -

- envers - - -

- -
- -

Envers.

- - - -
- - - - - +

SUMMARY

-

Functions

- -
- - -

- get_version() - -

- - -
- -

Return the program version.

- -
- -
- - - -
- -
- -
+ diff --git a/api/envers/cli/cli.md b/api/envers/cli/cli.md new file mode 100644 index 0000000..fcf557b --- /dev/null +++ b/api/envers/cli/cli.md @@ -0,0 +1 @@ +::: envers.cli \ No newline at end of file diff --git a/api/envers/cli/index.html b/api/envers/cli/index.html new file mode 100644 index 0000000..fe65c99 --- /dev/null +++ b/api/envers/cli/index.html @@ -0,0 +1,1263 @@ + + + + + + + + + + + + + + + + + + + + + + + + + cli - Envers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +
+ + + +

+ cli + + +

+ +
+ +

Definition of the CLI structure.

+ + + +
+ + + + + + + + + +
+ + +

+ deploy + + +

+
deploy(profile: Annotated[str, Option(help='The name of the profile to set values for.')] = '', spec: Annotated[str, Option(help='The version of the spec to use.')] = '') -> None
+
+ +
+ +

Deploy a specific version from the spec file.

+ +
+ Source code in src/envers/cli.py +
62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
@app.command()
+def deploy(
+    profile: Annotated[
+        str, typer.Option(help="The name of the profile to set values for.")
+    ] = "",
+    spec: Annotated[
+        str, typer.Option(help="The version of the spec to use.")
+    ] = "",
+) -> None:
+    """Deploy a specific version from the spec file."""
+    envers = Envers()
+    envers.deploy(profile, spec)
+
+
+
+ +
+ +
+ + +

+ draft + + +

+
draft(version: str, from_spec: str = '', from_env: str = '') -> None
+
+ +
+ +

Create a new version draft in the spec file.

+ +
+ Source code in src/envers/cli.py +
76
+77
+78
+79
+80
@app.command()
+def draft(version: str, from_spec: str = "", from_env: str = "") -> None:
+    """Create a new version draft in the spec file."""
+    envers = Envers()
+    envers.draft(version, from_spec, from_env)
+
+
+
+ +
+ +
+ + +

+ init + + +

+
init(path: str = '.') -> None
+
+ +
+ +

Initialize the .envers directory and specs file.

+

Initialize the envers environment at the given path. This includes creating +a .envers folder and a spec.yaml file within it with default content.

+ + +

Parameters:

+
    +
  • + path + (str, default: + '.' +) + – +
    +

    The directory path where the envers environment will be initialized. +Defaults to the current directory (".").

    +
    +
  • +
+ + +

Returns:

+
    +
  • + None + – +
    + +
    +
  • +
+ +
+ Source code in src/envers/cli.py +
39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
@app.command()
+def init(path: str = ".") -> None:
+    """
+    Initialize the .envers directory and specs file.
+
+    Initialize the envers environment at the given path. This includes creating
+    a .envers folder and a spec.yaml file within it with default content.
+
+    Parameters
+    ----------
+    path : str, optional
+        The directory path where the envers environment will be initialized.
+        Defaults to the current directory (".").
+
+    Returns
+    -------
+    None
+
+    """
+    envers = Envers()
+    envers.init(Path(path))
+
+
+
+ +
+ +
+ + +

+ main + + +

+
main(ctx: Context, version: bool = Option(None, '--version', '-v', is_flag=True, help='Show the version and exit.')) -> None
+
+ +
+ +

Process envers for specific flags, otherwise show the help menu.

+ +
+ Source code in src/envers/cli.py +
18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
@app.callback(invoke_without_command=True)
+def main(
+    ctx: Context,
+    version: bool = Option(
+        None,
+        "--version",
+        "-v",
+        is_flag=True,
+        help="Show the version and exit.",
+    ),
+) -> None:
+    """Process envers for specific flags, otherwise show the help menu."""
+    if version:
+        typer.echo(f"Version: {__version__}")
+        raise typer.Exit()
+
+    if ctx.invoked_subcommand is None:
+        typer.echo(ctx.get_help())
+        raise typer.Exit(0)
+
+
+
+ +
+ +
+ + +

+ profile_load + + +

+
profile_load(profile: Annotated[str, Option(help='The name of the profile to set values for.')] = '', spec: Annotated[str, Option(help='The version of the spec to use.')] = '') -> None
+
+ +
+ +

Load a specific environment profile to files.

+ +
+ Source code in src/envers/cli.py +
110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
@app.command()
+def profile_load(
+    profile: Annotated[
+        str, typer.Option(help="The name of the profile to set values for.")
+    ] = "",
+    spec: Annotated[
+        str, typer.Option(help="The version of the spec to use.")
+    ] = "",
+) -> None:
+    """Load a specific environment profile to files."""
+    envers = Envers()
+    envers.profile_load(profile, spec)
+
+
+
+ +
+ +
+ + +

+ profile_set + + +

+
profile_set(profile: Annotated[str, Option(help='The name of the profile to set values for.')] = '', spec: Annotated[str, Option(help='The version of the spec to use.')] = '') -> None
+
+ +
+ +

Set the profile values for a given spec version.

+ + +

Parameters:

+
    +
  • + profile + (str, default: + '' +) + – +
    +

    The name of the profile to set values for.

    +
    +
  • +
  • + spec + (str, default: + '' +) + – +
    +

    The version of the spec to use.

    +
    +
  • +
+ + +

Returns:

+
    +
  • + None + – +
    + +
    +
  • +
+ +
+ Source code in src/envers/cli.py +
 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
@app.command()
+def profile_set(
+    profile: Annotated[
+        str, typer.Option(help="The name of the profile to set values for.")
+    ] = "",
+    spec: Annotated[
+        str, typer.Option(help="The version of the spec to use.")
+    ] = "",
+) -> None:
+    """
+    Set the profile values for a given spec version.
+
+    Parameters
+    ----------
+    profile : str
+        The name of the profile to set values for.
+    spec : str
+        The version of the spec to use.
+
+    Returns
+    -------
+    None
+    """
+    envers = Envers()
+    envers.profile_set(profile, spec)
+
+
+
+ +
+ +
+ + +

+ profile_versions + + +

+
profile_versions(profile_name: str, spec_version: str) -> None
+
+ +
+ +

Return the profile's version.

+

Return all the versions for the contents for a specific profile and spec +version.

+ +
+ Source code in src/envers/cli.py +
124
+125
+126
+127
+128
+129
+130
+131
+132
@app.command()
+def profile_versions(profile_name: str, spec_version: str) -> None:
+    """
+    Return the profile's version.
+
+    Return all the versions for the contents for a specific profile and spec
+    version.
+    """
+    print(profile_name, spec_version)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/envers/core/core.md b/api/envers/core/core.md new file mode 100644 index 0000000..b508fa4 --- /dev/null +++ b/api/envers/core/core.md @@ -0,0 +1 @@ +::: envers.core \ No newline at end of file diff --git a/api/envers/core/index.html b/api/envers/core/index.html new file mode 100644 index 0000000..b0855c1 --- /dev/null +++ b/api/envers/core/index.html @@ -0,0 +1,2099 @@ + + + + + + + + + + + + + + + + + + + + + + + + + core - Envers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +
+ + + +

+ core + + +

+ +
+ +

Envers class for containers.

+ + + +
+ + + + + + + + +
+ + + +

+ Envers + + +

+ + +
+ + +

EnversBase defined the base structure for the Envers classes.

+ + + + +
+ + + + + + + + + +
+ + +

+ deploy + + +

+
deploy(profile: str, spec: str, password: Optional[str] = None) -> None
+
+ +
+ +

Deploy a specific version, updating the .envers/data.lock file.

+ + +

Parameters:

+
    +
  • + profile + (str) + – +
    +

    The profile to be deployed.

    +
    +
  • +
  • + spec + (str) + – +
    +

    The version number to be deployed.

    +
    +
  • +
  • + password + (Optional[str], default: + None +) + – +
    +

    The password to be used for that profile.

    +
    +
  • +
+ + +

Returns:

+
    +
  • + None + – +
    + +
    +
  • +
+ +
+ Source code in src/envers/core.py +
223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
def deploy(
+    self, profile: str, spec: str, password: Optional[str] = None
+) -> None:
+    """
+    Deploy a specific version, updating the .envers/data.lock file.
+
+    Parameters
+    ----------
+    profile : str
+        The profile to be deployed.
+    spec : str
+        The version number to be deployed.
+    password : Optional[str]
+        The password to be used for that profile.
+
+    Returns
+    -------
+    None
+    """
+    specs_file = Path(".envers") / ENVERS_SPEC_FILENAME
+    data_file = Path(".envers") / "data" / f"{profile}.lock"
+
+    if not specs_file.exists():
+        raise_error("Spec file not found. Please initialize envers first.")
+
+    with open(specs_file, "r") as file:
+        specs = yaml.safe_load(file) or {}
+
+    if not specs.get("releases", {}).get(spec, ""):
+        raise_error(f"Version {spec} not found in specs.yaml.")
+
+    spec_data = copy.deepcopy(specs["releases"][spec])
+
+    # all data in the data.lock file are deployed
+    del spec_data["status"]
+
+    if data_file.exists():
+        if password is None:
+            password = crypt.get_password()
+
+        data_lock = self._read_data_file(profile, password)
+
+        if not data_lock:
+            typer.echo("data.lock is not valid. Creating a new file.")
+            data_lock = {
+                "version": spec_data["version"],
+                "releases": {},
+            }
+        data_lock["releases"][spec] = {"spec": spec_data, "data": {}}
+    else:
+        data_lock = {
+            "version": specs["version"],
+            "releases": {spec: {"spec": spec_data, "data": {}}},
+        }
+
+        if password is None:
+            new_password = crypt.get_password()
+            new_password_confirmation = crypt.get_password(
+                "Confirm your password"
+            )
+
+            if new_password != new_password_confirmation:
+                raise_error(
+                    "The password and confirmation do not match. "
+                    "Please try again."
+                )
+
+            password = new_password
+
+    # Populate data with default values
+    for profile_name in spec_data.get("profiles", []):
+        profile_data: dict["str", dict[str, Any]] = {"files": {}}
+        for file_path, file_info in (
+            spec_data.get("spec", {}).get("files", {}).items()
+        ):
+            file_data = {
+                "type": file_info.get("type", "dotenv"),
+                "vars": {},
+            }
+            for var_name, var_info in file_info.get("vars", {}).items():
+                default_value = var_info.get("default", "")
+                file_data["vars"][var_name] = default_value
+            profile_data["files"][file_path] = file_data
+        data_lock["releases"][spec]["data"][profile_name] = profile_data
+
+    self._write_data_file(profile, data_lock, password)
+
+    with open(specs_file, "w") as file:
+        specs["releases"][spec]["status"] = "deployed"
+        yaml.dump(specs, file, sort_keys=False)
+
+
+
+ +
+ +
+ + +

+ draft + + +

+
draft(version: str, from_spec: str = '', from_env: str = '') -> None
+
+ +
+ +

Create a new draft version in the spec file.

+ + +

Parameters:

+
    +
  • + version + (str) + – +
    +

    The version number for the new draft.

    +
    +
  • +
  • + from_spec + (str, default: + '' +) + – +
    +

    The version number from which to copy the spec.

    +
    +
  • +
  • + from_env + (str, default: + '' +) + – +
    +

    The .env file from which to load environment variables.

    +
    +
  • +
+ + +

Returns:

+
    +
  • + None + – +
    + +
    +
  • +
+ +
+ Source code in src/envers/core.py +
142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
def draft(
+    self, version: str, from_spec: str = "", from_env: str = ""
+) -> None:
+    """
+    Create a new draft version in the spec file.
+
+    Parameters
+    ----------
+    version : str
+        The version number for the new draft.
+    from_spec : str, optional
+        The version number from which to copy the spec.
+    from_env : str, optional
+        The .env file from which to load environment variables.
+
+    Returns
+    -------
+    None
+    """
+    spec_file = Path(".envers") / ENVERS_SPEC_FILENAME
+
+    if not spec_file.exists():
+        raise_error("Spec file not found. Please initialize envers first.")
+
+    with open(spec_file, "r") as file:
+        specs = yaml.safe_load(file) or {}
+
+    if not specs.get("releases", {}):
+        specs["releases"] = {}
+
+    if specs.get("releases", {}).get("version", ""):
+        # warning
+        typer.echo(
+            f"The given version {version} is already defined in the "
+            "specs.yaml file."
+        )
+
+    if not specs["releases"].get(version, {}):
+        specs["releases"][version] = {
+            "docs": "",
+            "status": "draft",
+            "profiles": ["base"],
+            "spec": {"files": {}},
+        }
+
+    if from_spec:
+        if not specs.get("releases", {}).get(from_spec, ""):
+            raise_error(
+                f"Source version {from_spec} not found in specs.yaml."
+            )
+
+        specs["releases"][version] = merge_dicts(
+            specs["releases"][from_spec],
+            specs["releases"][version],
+        )
+
+    elif from_env:
+        env_path = Path(from_env)
+        if not env_path.exists():
+            raise_error(f".env file {from_env} not found.")
+
+        # Read .env file and populate variables
+        env_vars = dotenv_values(env_path)
+        file_spec = {
+            "docs": "",
+            "type": "dotenv",
+            "vars": {
+                var: {
+                    "docs": "",
+                    "type": "string",
+                    "default": value,
+                }
+                for var, value in env_vars.items()
+            },
+        }
+        spec_files = specs["releases"][version]["spec"]["files"]
+        spec_files[from_env] = file_spec
+
+    with open(spec_file, "w") as file:
+        yaml.dump(specs, file, sort_keys=False)
+
+
+
+ +
+ +
+ + +

+ init + + +

+
init(path: Path) -> None
+
+ +
+ +

Initialize Envers instance.

+

Initialize the envers environment at the given path. This includes +creating a .envers folder and a spec.yaml file within it with default +content.

+ + +

Parameters:

+
    +
  • + path + (str) + – +
    +

    The directory path where the envers environment will be +initialized. Defaults to the current directory (".").

    +
    +
  • +
+ + +

Returns:

+
    +
  • + None + – +
    + +
    +
  • +
+ +
+ Source code in src/envers/core.py +
111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
def init(self, path: Path) -> None:
+    """
+    Initialize Envers instance.
+
+    Initialize the envers environment at the given path. This includes
+    creating a .envers folder and a spec.yaml file within it with default
+    content.
+
+    Parameters
+    ----------
+    path : str, optional
+        The directory path where the envers environment will be
+        initialized. Defaults to the current directory (".").
+
+    Returns
+    -------
+    None
+    """
+    envers_path = path / ".envers"
+    spec_file = envers_path / ENVERS_SPEC_FILENAME
+
+    # Create .envers directory if it doesn't exist
+    os.makedirs(envers_path, exist_ok=True)
+
+    if spec_file.exists():
+        return
+
+    # Create and write the default content to spec.yaml
+    with open(spec_file, "w") as file:
+        file.write("version: '0.1'\nreleases:\n")
+
+
+
+ +
+ +
+ + +

+ profile_load + + +

+
profile_load(profile: str, spec: str, password: Optional[str] = None) -> None
+
+ +
+ +

Load a specific environment profile to files.

+

Load a specific environment profile to files based on the given +spec version.

+ + +

Parameters:

+
    +
  • + profile + (str) + – +
    +

    The name of the profile to load.

    +
    +
  • +
  • + spec + (str) + – +
    +

    The version of the spec to use.

    +
    +
  • +
  • + password + (Optional[str], default: + None +) + – +
    +

    The password to be used for that profile.

    +
    +
  • +
+ + +

Returns:

+
    +
  • + None + – +
    + +
    +
  • +
+ +
+ Source code in src/envers/core.py +
382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
def profile_load(
+    self, profile: str, spec: str, password: Optional[str] = None
+) -> None:
+    """
+    Load a specific environment profile to files.
+
+    Load a specific environment profile to files based on the given
+    spec version.
+
+    Parameters
+    ----------
+    profile : str
+        The name of the profile to load.
+    spec : str
+        The version of the spec to use.
+    password : Optional[str]
+        The password to be used for that profile.
+
+    Returns
+    -------
+    None
+    """
+    data_file = Path(".envers") / "data" / f"{profile}.lock"
+
+    if not data_file.exists():
+        raise_error(
+            "Data lock file not found. Please deploy a version first."
+        )
+
+    if password is None:
+        password = crypt.get_password()
+
+    data_lock = self._read_data_file(profile, password)
+
+    if not data_lock.get("releases", {}).get(spec, ""):
+        raise_error(f"Version {spec} not found in data.lock.")
+
+    release_data = data_lock["releases"][spec]
+    profile_data = release_data.get("data", {}).get(profile, {"files": {}})
+
+    # Iterate over files and variables
+    for file_path, file_info in profile_data.get("files", {}).items():
+        file_content = ""
+        for var_name, var_value in file_info.get("vars", {}).items():
+            file_content += f"{var_name}={var_value}\n"
+
+        # Create or update the file
+        with open(file_path, "w") as file:
+            file.write(file_content)
+
+    typer.echo(
+        f"Environment files for profile '{profile}' and spec version "
+        f"'{spec}' have been created/updated."
+    )
+
+
+
+ +
+ +
+ + +

+ profile_set + + +

+
profile_set(profile: str, spec: str, password: Optional[str] = None) -> None
+
+ +
+ +

Set the profile values for a given spec version.

+ + +

Parameters:

+
    +
  • + profile + (str) + – +
    +

    The name of the profile to set values for.

    +
    +
  • +
  • + spec + (str) + – +
    +

    The version of the spec to use.

    +
    +
  • +
  • + password + (Optional[str], default: + None +) + – +
    +

    The password to be used for that profile.

    +
    +
  • +
+ + +

Returns:

+
    +
  • + None + – +
    + +
    +
  • +
+ +
+ Source code in src/envers/core.py +
314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
def profile_set(
+    self, profile: str, spec: str, password: Optional[str] = None
+) -> None:
+    """
+    Set the profile values for a given spec version.
+
+    Parameters
+    ----------
+    profile : str
+        The name of the profile to set values for.
+    spec : str
+        The version of the spec to use.
+    password : Optional[str]
+        The password to be used for that profile.
+
+    Returns
+    -------
+    None
+    """
+    data_file = Path(".envers") / "data" / f"{profile}.lock"
+
+    if not data_file.exists():
+        raise_error(
+            "Data lock file not found. Please deploy a version first."
+        )
+
+    if password is None:
+        password = crypt.get_password()
+
+    data_lock = self._read_data_file(profile, password)
+
+    if not data_lock.get("releases", {}).get(spec, ""):
+        raise_error(f"Version {spec} not found in data.lock.")
+
+    release_data = data_lock["releases"][spec]
+    profile_data = release_data.get("data", {}).get(profile, {})
+
+    if not (profile_data and profile_data.get("files", {})):
+        raise_error(
+            f"There is no data spec for version '{spec}' "
+            f"and profile '{profile}'"
+        )
+
+    # Iterate over files and variables
+    size = os.get_terminal_size()
+
+    profile_title = f" Profile: {profile} ".center(size.columns, "=")
+    typer.echo(f"\n{profile_title}\n")
+
+    for file_path, file_info in profile_data.get("files", {}).items():
+        file_title = f">>> File: {file_path} "
+        typer.echo(f"{file_title}\n")
+        for var_name, var_info in file_info.get("vars", {}).items():
+            current_value = var_info
+            new_value = typer.prompt(
+                f"Enter value for `{var_name}`",
+                default=current_value,
+            )
+            profile_data["files"][file_path]["vars"][var_name] = new_value
+
+        # update the size for each iteration
+        size = os.get_terminal_size()
+        typer.echo(f"\n{size.columns * '-'}\n")
+
+    # Update data.lock file
+    data_lock["releases"][spec]["data"][profile] = profile_data
+    self._write_data_file(profile, data_lock, password)
+
+
+
+ +
+ + + +
+ +
+ +
+ + +
+ + +

+ escape_template_tag + + +

+
escape_template_tag(v: str) -> str
+
+ +
+ +

Escape template tags for template rendering.

+ +
+ Source code in src/envers/core.py +
66
+67
+68
def escape_template_tag(v: str) -> str:
+    """Escape template tags for template rendering."""
+    return v.replace("{{", r"\{\{").replace("}}", r"\}\}")
+
+
+
+ +
+ +
+ + +

+ merge_dicts + + +

+
merge_dicts(dict_lhs: dict[str, Any], dict_rhs: dict[str, Any]) -> dict[str, Any]
+
+ +
+ +

Merge two dictionaries recursively.

+ + +

Parameters:

+
    +
  • + dict_lhs + (dict) + – +
    +

    The primary dictionary to retain values from.

    +
    +
  • +
  • + dict_rhs + (dict) + – +
    +

    The secondary dictionary to merge values from.

    +
    +
  • +
+ + +

Returns:

+
    +
  • + dict + – +
    +

    The merged dictionary.

    +
    +
  • +
+ +
+ Source code in src/envers/core.py +
28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
def merge_dicts(
+    dict_lhs: dict[str, Any], dict_rhs: dict[str, Any]
+) -> dict[str, Any]:
+    """
+    Merge two dictionaries recursively.
+
+    Parameters
+    ----------
+    dict_lhs : dict
+        The primary dictionary to retain values from.
+    dict_rhs : dict
+        The secondary dictionary to merge values from.
+
+    Returns
+    -------
+    dict
+        The merged dictionary.
+    """
+    dict_lhs = copy.deepcopy(dict_lhs)
+
+    for key in dict_rhs:
+        if key in dict_lhs:
+            if isinstance(dict_lhs[key], dict) and isinstance(
+                dict_rhs[key], dict
+            ):
+                merge_dicts(dict_lhs[key], dict_rhs[key])
+            else:
+                dict_lhs[key] = dict_rhs[key]
+        else:
+            dict_lhs[key] = dict_rhs[key]
+    return dict_lhs
+
+
+
+ +
+ +
+ + +

+ raise_error + + +

+
raise_error(message: str, exit_code: int = 1) -> None
+
+ +
+ +

Raise an error using typer.

+ +
+ Source code in src/envers/core.py +
21
+22
+23
+24
+25
def raise_error(message: str, exit_code: int = 1) -> None:
+    """Raise an error using typer."""
+    red_text = typer.style(message, fg=typer.colors.RED, bold=True)
+    typer.echo(red_text, err=True, color=True)
+    raise typer.Exit(exit_code)
+
+
+
+ +
+ +
+ + +

+ unescape_template_tag + + +

+
unescape_template_tag(v: str) -> str
+
+ +
+ +

Unescape template tags for template rendering.

+ +
+ Source code in src/envers/core.py +
71
+72
+73
def unescape_template_tag(v: str) -> str:
+    """Unescape template tags for template rendering."""
+    return v.replace(r"\{\{", "{{").replace(r"\}\}", "}}")
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/envers/crypt/crypt.md b/api/envers/crypt/crypt.md new file mode 100644 index 0000000..5ed6501 --- /dev/null +++ b/api/envers/crypt/crypt.md @@ -0,0 +1 @@ +::: envers.crypt \ No newline at end of file diff --git a/api/envers/crypt/index.html b/api/envers/crypt/index.html new file mode 100644 index 0000000..e965a1c --- /dev/null +++ b/api/envers/crypt/index.html @@ -0,0 +1,1008 @@ + + + + + + + + + + + + + + + + + + + + + + + + + crypt - Envers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +
+ + + +

+ crypt + + +

+ +
+ +

Functions for cryptography.

+ + + +
+ + + + + + + + + +
+ + +

+ create_fernet_key + + +

+
create_fernet_key(password: str, salt: bytes) -> bytes
+
+ +
+ +

Create a Fernet key.

+ +
+ Source code in src/envers/crypt.py +
21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
def create_fernet_key(password: str, salt: bytes) -> bytes:
+    """Create a Fernet key."""
+    # Use PBKDF2HMAC to derive a Fernet-compatible key from the user password
+    kdf = PBKDF2HMAC(
+        algorithm=hashes.SHA256(),
+        length=32,
+        salt=salt,
+        iterations=100000,
+        backend=default_backend(),
+    )
+    return base64.urlsafe_b64encode(kdf.derive(password.encode("utf-8")))
+
+
+
+ +
+ +
+ + +

+ decrypt_data + + +

+
decrypt_data(data: str, password: Optional[str] = None) -> str
+
+ +
+ +

Decrypt the given data.

+ +
+ Source code in src/envers/crypt.py +
65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
def decrypt_data(data: str, password: Optional[str] = None) -> str:
+    """Decrypt the given data."""
+    if password is None:
+        password = get_password()
+
+    HEX_SALT_LENGTH = SALT_LENGTH * 2
+
+    salt_hex, data_clean = data[:HEX_SALT_LENGTH], data[HEX_SALT_LENGTH:]
+    salt = bytes.fromhex(salt_hex)
+
+    key = create_fernet_key(password, salt)
+    cipher_suite = Fernet(key)
+
+    return cipher_suite.decrypt(data_clean.encode("utf-8")).decode("utf-8")
+
+
+
+ +
+ +
+ + +

+ encrypt_data + + +

+
encrypt_data(data: str, password: Optional[str] = None) -> str
+
+ +
+ +

Encrypt the given data.

+ +
+ Source code in src/envers/crypt.py +
51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
def encrypt_data(data: str, password: Optional[str] = None) -> str:
+    """Encrypt the given data."""
+    if password is None:
+        password = get_password()
+
+    salt = generate_salt()
+    salt_hex = salt.hex()
+    key = create_fernet_key(password, salt)
+    cipher_suite = Fernet(key)
+
+    encrypted = cipher_suite.encrypt(data.encode("utf-8")).decode("utf-8")
+    return salt_hex + encrypted
+
+
+
+ +
+ +
+ + +

+ generate_salt + + +

+
generate_salt() -> bytes
+
+ +
+ +

Generate a salt in byte format.

+ +
+ Source code in src/envers/crypt.py +
46
+47
+48
def generate_salt() -> bytes:
+    """Generate a salt in byte format."""
+    return os.urandom(SALT_LENGTH)
+
+
+
+ +
+ +
+ + +

+ get_password + + +

+
get_password(message: str = '') -> str
+
+ +
+ +

Prompt a password.

+ +
+ Source code in src/envers/crypt.py +
34
+35
+36
+37
+38
+39
+40
+41
+42
+43
def get_password(message: str = "") -> str:
+    """Prompt a password."""
+    if sys.stdin.isatty():
+        # Interactive mode: Use Typer's prompt
+        message = "Enter your password" if not message else message
+        password = cast(str, typer.prompt(message, hide_input=True))
+    else:
+        # Non-interactive mode: Read from stdin
+        password = sys.stdin.readline().rstrip()
+    return password
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/envers/index.html b/api/envers/index.html new file mode 100644 index 0000000..502950e --- /dev/null +++ b/api/envers/index.html @@ -0,0 +1,711 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Index - Envers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +
+ + + +

+ envers + + +

+ +
+ +

Envers.

+ + + +
+ + + + + + + + + +
+ + +

+ get_version + + +

+
get_version() -> str
+
+ +
+ +

Return the program version.

+ +
+ Source code in src/envers/__init__.py +
 9
+10
+11
+12
+13
+14
def get_version() -> str:
+    """Return the program version."""
+    try:
+        return importlib_metadata.version(__name__)
+    except importlib_metadata.PackageNotFoundError:  # pragma: no cover
+        return "0.4.1"  # semantic-release
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + + + + +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/api/envers/index.md b/api/envers/index.md new file mode 100644 index 0000000..2898457 --- /dev/null +++ b/api/envers/index.md @@ -0,0 +1 @@ +::: envers \ No newline at end of file diff --git a/api/references.rst b/api/references.rst deleted file mode 100644 index ddb3d8a..0000000 --- a/api/references.rst +++ /dev/null @@ -1,8 +0,0 @@ -API references -============== - -.. automodule:: envers - :members: - -.. automodule:: envers.envers - :members: diff --git a/api/references/references.md b/api/references/references.md deleted file mode 100644 index fd18567..0000000 --- a/api/references/references.md +++ /dev/null @@ -1,3 +0,0 @@ -# Api references - -::: envers diff --git a/changelog/index.html b/changelog/index.html index d9fda1f..5d0b5f8 100644 --- a/changelog/index.html +++ b/changelog/index.html @@ -17,7 +17,7 @@ - + @@ -92,7 +92,7 @@