Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix config #31

Merged
merged 3 commits into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .reuse/dep5
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Upstream-Name: somesy
Upstream-Contact: Mustafa Soylu <m.soylu@fz-juelich.de>
Source: https://github.com/Materials-Data-Science-and-Informatics/somesy

Files: .gitignore pyproject.toml poetry.lock .pre-commit-config.yaml .pre-commit-hooks.yaml .somesy.toml codemeta.json CITATION.cff README.md RELEASE_NOTES.md CHANGELOG.md CODE_OF_CONDUCT.md AUTHORS.md CONTRIBUTING.md .gitlab-ci.yml .gitlab/* .github/* mkdocs.yml docs/*
Files: .gitignore pyproject.toml poetry.lock .pre-commit-config.yaml .pre-commit-hooks.yaml .sourcery.yaml .somesy.toml codemeta.json CITATION.cff README.md RELEASE_NOTES.md CHANGELOG.md CODE_OF_CONDUCT.md AUTHORS.md CONTRIBUTING.md .gitlab-ci.yml .gitlab/* .github/* mkdocs.yml docs/*
Copyright: 2023 Forschungszentrum Jülich GmbH - Institute Materials Data Science and Informatics (IAS9) - Stefan Sandfeld (s.sandfeld@fz-juelich.de)
License: CC0-1.0

Expand Down
2 changes: 1 addition & 1 deletion .somesy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ contribution = "Valuable input concerning metadata standards and usability."
contribution_begin = "2023-03-01"
contribution_type = "ideas"

[config.cli]
[config]
no_sync_cff = false
cff_file = "CITATION.cff"
no_sync_pyproject = false
Expand Down
70 changes: 70 additions & 0 deletions .sourcery.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# 🪄 This is your project's Sourcery configuration file.

# You can use it to get Sourcery working in the way you want, such as
# ignoring specific refactorings, skipping directories in your project,
# or writing custom rules.

# 📚 For a complete reference to this file, see the documentation at
# https://docs.sourcery.ai/Configuration/Project-Settings/

# This file was auto-generated by Sourcery on 2023-07-19 at 10:22.

version: '1' # The schema version of this config file

ignore: # A list of paths or files which Sourcery will ignore.
- .git
- venv
- .venv
- env
- .env
- .tox
- node_modules
- vendor

rule_settings:
enable:
- default
disable: [] # A list of rule IDs Sourcery will never suggest.
rule_types:
- refactoring
- suggestion
- comment
python_version: '3.8' # A string specifying the lowest Python version your project supports. Sourcery will not suggest refactorings requiring a higher Python version.

# rules: # A list of custom rules Sourcery will include in its analysis.
# - id: no-print-statements
# description: Do not use print statements in the test directory.
# pattern: print(...)
# language: python
# replacement:
# condition:
# explanation:
# paths:
# include:
# - test
# exclude:
# - conftest.py
# tests: []
# tags: []

# rule_tags: {} # Additional rule tags.

# metrics:
# quality_threshold: 25.0

# github:
# labels: []
# ignore_labels:
# - sourcery-ignore
# request_review: author
# sourcery_branch: sourcery/{base_branch}

# clone_detection:
# min_lines: 3
# min_duplicates: 2
# identical_clones_only: false

# proxy:
# url:
# ssl_certs_file:
# no_ssl_verify: false
1,393 changes: 793 additions & 600 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ cffconvert = "^2.0.0"
wrapt = "^1.15.0"
packaging = "^23.1"
rdflib = "^6.3.2"
codemetapy = "^2.5.0"
codemetapy = { git = "https://github.com/proycon/codemetapy.git" }
# codemetapy = "^2.5.1" # TODO: use once released

[tool.poetry.group.dev.dependencies]
poethepoet = "^0.18.1"
Expand Down
111 changes: 52 additions & 59 deletions src/somesy/cli/init.py
Original file line number Diff line number Diff line change
@@ -1,78 +1,71 @@
"""Set config files for somesy."""
import logging
import traceback
from pathlib import Path

import typer

from somesy.commands import init_config
from somesy.core.discover import discover_input
from somesy.core.core import discover_input
from somesy.core.log import SomesyLogLevel, set_log_level

from .util import wrap_exceptions

logger = logging.getLogger("somesy")
app = typer.Typer()


@app.command()
@wrap_exceptions
def config():
"""Set CLI configs for somesy."""
try:
# check if input file exists, if not, try to find it from default list
input_file_default = discover_input()

options = {}

# prompt for inputs
input_file = typer.prompt("Input file path", default=input_file_default)
input_file = Path(input_file)
options["input_file"] = input_file
options["no_sync_cff"] = not typer.confirm(
"Do you want to sync CFF file?", default=True
)

cff_file = typer.prompt("CFF file path", default="CITATION.cff")
if cff_file is not None or cff_file != "":
options["cff_file"] = cff_file

options["no_sync_pyproject"] = not typer.confirm(
"Do you want to sync pyproject.toml file?", default=True
)

pyproject_file = typer.prompt(
"pyproject.toml file path", default="pyproject.toml"
# check if input file exists, if not, try to find it from default list
input_file_default = discover_input()

# prompt for inputs
input_file = typer.prompt("Input file path", default=input_file_default)
input_file = Path(input_file)
options = {
"input_file": input_file,
"no_sync_cff": not typer.confirm(
"Do you want to sync to a CFF file?", default=True
),
}
cff_file = typer.prompt("CFF file path", default="CITATION.cff")
if cff_file is not None or cff_file != "":
options["cff_file"] = cff_file

options["no_sync_pyproject"] = not typer.confirm(
"Do you want to sync to a pyproject.toml file?", default=True
)

pyproject_file = typer.prompt("pyproject.toml file path", default="pyproject.toml")
if pyproject_file is not None or pyproject_file != "":
options["pyproject_file"] = pyproject_file

options["no_sync_codemeta"] = not typer.confirm(
"Do you want to sync to a codemeta.json file?", default=True
)
codemeta_file = typer.prompt("codemeta.json file path", default="codemeta.json")
if codemeta_file is not None or codemeta_file != "":
options["codemeta_file"] = codemeta_file

options["show_info"] = typer.confirm(
"Do you want to show info about the sync process?"
)
options["verbose"] = typer.confirm("Do you want to show verbose logs?")
options["debug"] = typer.confirm("Do you want to show debug logs?")

set_log_level(
SomesyLogLevel.from_flags(
debug=options["debug"],
verbose=options["verbose"],
info=options["show_info"],
)
if pyproject_file is not None or pyproject_file != "":
options["pyproject_file"] = pyproject_file
)

options["no_sync_codemeta"] = not typer.confirm(
"Do you want to sync codemeta.json file?", default=True
)
codemeta_file = typer.prompt("codemeta.json file path", default="codemeta.json")
if codemeta_file is not None or codemeta_file != "":
options["codemeta_file"] = codemeta_file

options["show_info"] = typer.confirm(
"Do you want to show info about the sync process?"
)
options["verbose"] = typer.confirm("Do you want to show verbose logs?")
options["debug"] = typer.confirm("Do you want to show debug logs?")

set_log_level(
SomesyLogLevel.from_flags(
debug=options["debug"],
verbose=options["verbose"],
info=options["show_info"],
)
)

logger.debug(f"CLI options entered: {options}")

init_config(input_file, options)
logger.info(
f"[bold green]Input file is updated/created at {input_file}[/bold green]"
)
logger.debug(f"CLI options entered: {options}")

except Exception as e:
logger.error(f"[bold red]Error: {e}[/bold red]")
logger.debug(f"[red]{traceback.format_exc()}[/red]")
raise typer.Exit(code=1)
init_config(input_file, options)
logger.info(
f"[bold green]Input file is updated/created at {input_file}[/bold green]"
)
111 changes: 45 additions & 66 deletions src/somesy/cli/sync.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
"""Sync command for somesy."""
import logging
import traceback
from pathlib import Path
from typing import Optional

import typer
from rich.pretty import pretty_repr

from somesy.commands import sync as sync_command
from somesy.core.core import get_somesy_cli_config
from somesy.core.discover import discover_input
from somesy.core.core import discover_input
from somesy.core.log import SomesyLogLevel, get_log_level, set_log_level
from somesy.core.models import SomesyConfig
from somesy.core.models import SomesyConfig, SomesyInput

from .util import wrap_exceptions

logger = logging.getLogger("somesy")

app = typer.Typer()


@app.callback(invoke_without_command=True)
@wrap_exceptions
def sync(
input_file: Path = typer.Option(
None,
Expand Down Expand Up @@ -88,68 +89,46 @@ def sync(
),
):
"""Sync project metadata input with metadata files."""
try:
# ---------------
# config assembly

# cli_log_level is None if the user did not pass a log level (-> "default")
cli_log_level: Optional[SomesyLogLevel] = get_log_level()

# config from CLI (merged with possibly set CLI flags for logging)
curr_log_level: SomesyLogLevel = cli_log_level or SomesyLogLevel.SILENT
passed_cli_args = {
k: v
for k, v in dict(
no_sync_cff=no_sync_cff,
cff_file=cff_file,
no_sync_pyproject=no_sync_pyproject,
pyproject_file=pyproject_file,
no_sync_codemeta=no_sync_codemeta,
codemeta_file=codemeta_file,
show_info=curr_log_level == SomesyLogLevel.INFO,
verbose=curr_log_level == SomesyLogLevel.VERBOSE,
debug=curr_log_level == SomesyLogLevel.DEBUG,
).items()
if v is not None
}
cli_conf = SomesyConfig(**passed_cli_args)
cli_conf = cli_conf.dict()
logger.debug(f"CLI config (excluding defaults):\n{pretty_repr(cli_conf)}")

# ----
# now try to get config from a config file
# check if input file exists, if not, try to find it from default list
somesy_conf_file: Path = discover_input(input_file)
file_conf = get_somesy_cli_config(somesy_conf_file)

# convert logging flags into somesy log level (for comparison)
config_log_level = SomesyLogLevel.from_flags(
info=file_conf.show_info, verbose=file_conf.verbose, debug=file_conf.debug
)
# convert into dict
file_conf = file_conf.dict()
logger.debug(f"File config (excluding defaults):\n{pretty_repr(file_conf)}")

# prioritized combination of config settings (cli overrides config file)
conf = SomesyConfig(**{**file_conf, **cli_conf})

# if the log level was not initialized using a common CLI flag yet,
# set it according to the loaded configuration
if cli_log_level is None:
set_log_level(config_log_level)

logger.debug(f"Combined config (Defaults + File + CLI):\n{pretty_repr(conf)}")
# --------
run_sync(conf)

except Exception as e:
logger.error(f"[bold red]Error: {e}[/bold red]")
logger.debug(f"[red]{traceback.format_exc()}[/red]")
raise typer.Exit(code=1)


def run_sync(conf: SomesyConfig):
# ---------------
# config from CLI (merged with possibly set CLI flags for logging)
passed_cli_args = {
k: v
for k, v in dict(
input_file=discover_input(input_file),
no_sync_cff=no_sync_cff,
cff_file=cff_file,
no_sync_pyproject=no_sync_pyproject,
pyproject_file=pyproject_file,
no_sync_codemeta=no_sync_codemeta,
codemeta_file=codemeta_file,
).items()
if v is not None
}
somesy_conf = SomesyConfig(**passed_cli_args)

# cli_log_level is None if the user did not pass a log level (-> "default")
cli_log_level: Optional[SomesyLogLevel] = get_log_level()

if cli_log_level is not None:
# update log level flags if cli log level was set
somesy_conf.update_log_level(cli_log_level)

somesy_input: SomesyInput = somesy_conf.get_input()

if cli_log_level is None:
# no cli log level -> set it according to the loaded configuration
set_log_level(somesy_input.config.log_level())

logger.debug(
f"Combined config (Defaults + File + CLI):\n{pretty_repr(somesy_input.config)}"
)
# --------
run_sync(somesy_input)


def run_sync(somesy_input: SomesyInput):
"""Write log messages and run synchronization based on passed config."""
conf = somesy_input.config
logger.info("[bold green]Synchronizing project metadata...[/bold green]")
logger.info("Files to sync:")
if not conf.no_sync_pyproject:
Expand All @@ -163,6 +142,6 @@ def run_sync(conf: SomesyConfig):
f" - [italic]codemeta.json[/italic]:\t[grey]{conf.codemeta_file}[/grey]\n"
)
# ----
sync_command(conf)
sync_command(somesy_input)
# ----
logger.info("[bold green]Metadata synchronization completed.[/bold green]")
20 changes: 20 additions & 0 deletions src/somesy/cli/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Utility functions for CLI commands."""
import logging
import traceback

import typer
import wrapt

logger = logging.getLogger("somesy")


@wrapt.decorator
def wrap_exceptions(wrapped, instance, args, kwargs):
"""Format and log exceptions for cli commands."""
try:
return wrapped(*args, **kwargs)

except Exception as e:
logger.error(f"[bold red]Error: {e}[/bold red]")
logger.debug(f"[red]{traceback.format_exc()}[/red]")
raise typer.Exit(code=1) from e
Loading