Unverified Commit 6330d626 authored by James Lamb's avatar James Lamb Committed by GitHub
Browse files

[ci] enable ruff-format on some files, add pre-commit config (#6308)

parent cc733f85
...@@ -32,25 +32,26 @@ def get_runs(trigger_phrase): ...@@ -32,25 +32,26 @@ def get_runs(trigger_phrase):
""" """
pr_runs = [] pr_runs = []
if environ.get("GITHUB_EVENT_NAME", "") == "pull_request": if environ.get("GITHUB_EVENT_NAME", "") == "pull_request":
pr_number = int(environ.get("GITHUB_REF").split('/')[-2]) pr_number = int(environ.get("GITHUB_REF").split("/")[-2])
page = 1 page = 1
while True: while True:
req = request.Request( req = request.Request(
url="{}/repos/microsoft/LightGBM/issues/{}/comments?page={}&per_page=100".format( url="{}/repos/microsoft/LightGBM/issues/{}/comments?page={}&per_page=100".format(
environ.get("GITHUB_API_URL"), environ.get("GITHUB_API_URL"), pr_number, page
pr_number,
page
), ),
headers={"Accept": "application/vnd.github.v3+json"} headers={"Accept": "application/vnd.github.v3+json"},
) )
url = request.urlopen(req) url = request.urlopen(req)
data = json.loads(url.read().decode('utf-8')) data = json.loads(url.read().decode("utf-8"))
url.close() url.close()
if not data: if not data:
break break
runs_on_page = [i for i in data runs_on_page = [
if i['author_association'].lower() in {'owner', 'member', 'collaborator'} i
and i['body'].startswith('/gha run {}'.format(trigger_phrase))] for i in data
if i["author_association"].lower() in {"owner", "member", "collaborator"}
and i["body"].startswith("/gha run {}".format(trigger_phrase))
]
pr_runs.extend(runs_on_page) pr_runs.extend(runs_on_page)
page += 1 page += 1
return pr_runs[::-1] return pr_runs[::-1]
...@@ -70,20 +71,20 @@ def get_status(runs): ...@@ -70,20 +71,20 @@ def get_status(runs):
The most recent status of workflow. The most recent status of workflow.
Can be 'success', 'failure' or 'in-progress'. Can be 'success', 'failure' or 'in-progress'.
""" """
status = 'success' status = "success"
for run in runs: for run in runs:
body = run['body'] body = run["body"]
if "Status: " in body: if "Status: " in body:
if "Status: skipped" in body: if "Status: skipped" in body:
continue continue
if "Status: failure" in body: if "Status: failure" in body:
status = 'failure' status = "failure"
break break
if "Status: success" in body: if "Status: success" in body:
status = 'success' status = "success"
break break
else: else:
status = 'in-progress' status = "in-progress"
break break
return status return status
...@@ -92,8 +93,8 @@ if __name__ == "__main__": ...@@ -92,8 +93,8 @@ if __name__ == "__main__":
trigger_phrase = argv[1] trigger_phrase = argv[1]
while True: while True:
status = get_status(get_runs(trigger_phrase)) status = get_status(get_runs(trigger_phrase))
if status != 'in-progress': if status != "in-progress":
break break
sleep(60) sleep(60)
if status == 'failure': if status == "failure":
exit(1) exit(1)
#!/bin/sh #!/bin/sh
echo "running ruff" echo "running pre-commit checks"
ruff check \ pre-commit run --all-files || exit 1
--config=./python-package/pyproject.toml \ echo "done running pre-commit checks"
. \
|| exit 1
echo "done running ruff"
echo "running isort"
isort \
--check-only \
--settings-path=./python-package/pyproject.toml \
. \
|| exit 1
echo "done running isort"
echo "running mypy" echo "running mypy"
mypy \ mypy \
......
...@@ -74,10 +74,9 @@ if [[ $TASK == "lint" ]]; then ...@@ -74,10 +74,9 @@ if [[ $TASK == "lint" ]]; then
${CONDA_PYTHON_REQUIREMENT} \ ${CONDA_PYTHON_REQUIREMENT} \
cmakelint \ cmakelint \
cpplint \ cpplint \
isort \
mypy \ mypy \
'r-lintr>=3.1' \ 'pre-commit>=3.6.0' \
ruff 'r-lintr>=3.1'
source activate $CONDA_ENV source activate $CONDA_ENV
echo "Linting Python code" echo "Linting Python code"
sh ${BUILD_DIRECTORY}/.ci/lint-python.sh || exit 1 sh ${BUILD_DIRECTORY}/.ci/lint-python.sh || exit 1
......
...@@ -20,7 +20,7 @@ if __name__ == "__main__": ...@@ -20,7 +20,7 @@ if __name__ == "__main__":
copyfile(source / "lib_lightgbm.dylib", osx_folder_path / "lib_lightgbm.dylib") copyfile(source / "lib_lightgbm.dylib", osx_folder_path / "lib_lightgbm.dylib")
copyfile(source / "lib_lightgbm.dll", windows_folder_path / "lib_lightgbm.dll") copyfile(source / "lib_lightgbm.dll", windows_folder_path / "lib_lightgbm.dll")
copyfile(source / "lightgbm.exe", windows_folder_path / "lightgbm.exe") copyfile(source / "lightgbm.exe", windows_folder_path / "lightgbm.exe")
version = (current_dir.parent / 'VERSION.txt').read_text(encoding='utf-8').strip().replace('rc', '-rc') version = (current_dir.parent / "VERSION.txt").read_text(encoding="utf-8").strip().replace("rc", "-rc")
nuget_str = rf"""<?xml version="1.0"?> nuget_str = rf"""<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata> <metadata>
...@@ -75,6 +75,6 @@ if __name__ == "__main__": ...@@ -75,6 +75,6 @@ if __name__ == "__main__":
</Target> </Target>
</Project> </Project>
""" """
(current_dir / "LightGBM.nuspec").write_text(nuget_str, encoding='utf-8') (current_dir / "LightGBM.nuspec").write_text(nuget_str, encoding="utf-8")
(current_dir / "build" / "LightGBM.props").write_text(prop_str, encoding='utf-8') (current_dir / "build" / "LightGBM.props").write_text(prop_str, encoding="utf-8")
(current_dir / "build" / "LightGBM.targets").write_text(target_str, encoding='utf-8') (current_dir / "build" / "LightGBM.targets").write_text(target_str, encoding="utf-8")
exclude: |
(?x)^(
build|
external_libs|
lightgbm-python|
lightgbm_r|
)$
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.2.1
hooks:
# Run the linter.
- id: ruff
args: ["--config", "python-package/pyproject.toml"]
# Run the formatter.
- id: ruff-format
args: ["--config", "python-package/pyproject.toml"]
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
name: isort (python)
args: ["--settings-path", "python-package/pyproject.toml"]
LightGBM has been developed and used by many active community members. Your help is very valuable to make it better for everyone. # contributing
LightGBM has been developed and used by many active community members.
Your help is very valuable to make it better for everyone.
## How to Contribute
- Check for the [Roadmap](https://github.com/microsoft/LightGBM/projects/1) and the [Feature Requests Hub](https://github.com/microsoft/LightGBM/issues/2302), and submit pull requests to address chosen issue. If you need development guideline, you can check the [Development Guide](https://github.com/microsoft/LightGBM/blob/master/docs/Development-Guide.rst) or directly ask us in Issues/Pull Requests. - Check for the [Roadmap](https://github.com/microsoft/LightGBM/projects/1) and the [Feature Requests Hub](https://github.com/microsoft/LightGBM/issues/2302), and submit pull requests to address chosen issue. If you need development guideline, you can check the [Development Guide](https://github.com/microsoft/LightGBM/blob/master/docs/Development-Guide.rst) or directly ask us in Issues/Pull Requests.
- Contribute to the [tests](https://github.com/microsoft/LightGBM/tree/master/tests) to make it more reliable. - Contribute to the [tests](https://github.com/microsoft/LightGBM/tree/master/tests) to make it more reliable.
...@@ -6,3 +12,17 @@ LightGBM has been developed and used by many active community members. Your help ...@@ -6,3 +12,17 @@ LightGBM has been developed and used by many active community members. Your help
- Contribute to the [examples](https://github.com/microsoft/LightGBM/tree/master/examples) to share your experience with other users. - Contribute to the [examples](https://github.com/microsoft/LightGBM/tree/master/examples) to share your experience with other users.
- Add your stories and experience to [Awesome LightGBM](https://github.com/microsoft/LightGBM/blob/master/examples/README.md). If LightGBM helped you in a machine learning competition or some research application, we want to hear about it! - Add your stories and experience to [Awesome LightGBM](https://github.com/microsoft/LightGBM/blob/master/examples/README.md). If LightGBM helped you in a machine learning competition or some research application, we want to hear about it!
- [Open an issue](https://github.com/microsoft/LightGBM/issues) to report problems or recommend new features. - [Open an issue](https://github.com/microsoft/LightGBM/issues) to report problems or recommend new features.
## Development Guide
### Linting
Every commit in the repository is tested with multiple static analyzers.
When developing locally, run some of them using `pre-commit` ([pre-commit docs](https://pre-commit.com/)).
```shell
pre-commit run --all-files
```
That command will check for some issues and automatically reformat the code.
...@@ -34,7 +34,7 @@ from sphinx.application import Sphinx ...@@ -34,7 +34,7 @@ from sphinx.application import Sphinx
from sphinx.errors import VersionRequirementError from sphinx.errors import VersionRequirementError
CURR_PATH = Path(__file__).absolute().parent CURR_PATH = Path(__file__).absolute().parent
LIB_PATH = CURR_PATH.parent / 'python-package' LIB_PATH = CURR_PATH.parent / "python-package"
sys.path.insert(0, str(LIB_PATH)) sys.path.insert(0, str(LIB_PATH))
INTERNAL_REF_REGEX = compile(r"(?P<url>\.\/.+)(?P<extension>\.rst)(?P<anchor>$|#)") INTERNAL_REF_REGEX = compile(r"(?P<url>\.\/.+)(?P<extension>\.rst)(?P<anchor>$|#)")
...@@ -65,29 +65,29 @@ class IgnoredDirective(Directive): ...@@ -65,29 +65,29 @@ class IgnoredDirective(Directive):
# -- General configuration ------------------------------------------------ # -- General configuration ------------------------------------------------
os.environ['LIGHTGBM_BUILD_DOC'] = '1' os.environ["LIGHTGBM_BUILD_DOC"] = "1"
C_API = os.environ.get('C_API', '').lower().strip() != 'no' C_API = os.environ.get("C_API", "").lower().strip() != "no"
RTD = bool(os.environ.get('READTHEDOCS', '')) RTD = bool(os.environ.get("READTHEDOCS", ""))
# If your documentation needs a minimal Sphinx version, state it here. # If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = '2.1.0' # Due to sphinx.ext.napoleon, autodoc_typehints needs_sphinx = "2.1.0" # Due to sphinx.ext.napoleon, autodoc_typehints
if needs_sphinx > sphinx.__version__: if needs_sphinx > sphinx.__version__:
message = f'This project needs at least Sphinx v{needs_sphinx}' message = f"This project needs at least Sphinx v{needs_sphinx}"
raise VersionRequirementError(message) raise VersionRequirementError(message)
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ extensions = [
'sphinx.ext.autodoc', "sphinx.ext.autodoc",
'sphinx.ext.autosummary', "sphinx.ext.autosummary",
'sphinx.ext.todo', "sphinx.ext.todo",
'sphinx.ext.viewcode', "sphinx.ext.viewcode",
'sphinx.ext.napoleon', "sphinx.ext.napoleon",
"sphinx.ext.intersphinx", "sphinx.ext.intersphinx",
] ]
autodoc_default_flags = ['members', 'inherited-members', 'show-inheritance'] autodoc_default_flags = ["members", "inherited-members", "show-inheritance"]
autodoc_default_options = { autodoc_default_options = {
"members": True, "members": True,
"inherited-members": True, "inherited-members": True,
...@@ -95,54 +95,54 @@ autodoc_default_options = { ...@@ -95,54 +95,54 @@ autodoc_default_options = {
} }
# mock out modules # mock out modules
autodoc_mock_imports = [ autodoc_mock_imports = [
'dask', "dask",
'dask.distributed', "dask.distributed",
'datatable', "datatable",
'graphviz', "graphviz",
'matplotlib', "matplotlib",
'numpy', "numpy",
'pandas', "pandas",
'scipy', "scipy",
'scipy.sparse', "scipy.sparse",
] ]
try: try:
import sklearn # noqa: F401 import sklearn # noqa: F401
except ImportError: except ImportError:
autodoc_mock_imports.append('sklearn') autodoc_mock_imports.append("sklearn")
# hide type hints in API docs # hide type hints in API docs
autodoc_typehints = "none" autodoc_typehints = "none"
# Generate autosummary pages. Output should be set with: `:toctree: pythonapi/` # Generate autosummary pages. Output should be set with: `:toctree: pythonapi/`
autosummary_generate = ['Python-API.rst'] autosummary_generate = ["Python-API.rst"]
# Only the class' docstring is inserted. # Only the class' docstring is inserted.
autoclass_content = 'class' autoclass_content = "class"
# If true, `todo` and `todoList` produce output, else they produce nothing. # If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False todo_include_todos = False
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = "index"
# General information about the project. # General information about the project.
project = 'LightGBM' project = "LightGBM"
copyright = f'{datetime.datetime.now().year}, Microsoft Corporation' copyright = f"{datetime.datetime.now().year}, Microsoft Corporation"
author = 'Microsoft Corporation' author = "Microsoft Corporation"
# The name of an image file (relative to this directory) to place at the top # The name of an image file (relative to this directory) to place at the top
# of the sidebar. # of the sidebar.
html_logo = str(CURR_PATH / 'logo' / 'LightGBM_logo_grey_text.svg') html_logo = str(CURR_PATH / "logo" / "LightGBM_logo_grey_text.svg")
# The name of an image file (relative to this directory) to use as a favicon of # The name of an image file (relative to this directory) to use as a favicon of
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large. # pixels large.
html_favicon = str(CURR_PATH / '_static' / 'images' / 'favicon.ico') html_favicon = str(CURR_PATH / "_static" / "images" / "favicon.ico")
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# The short X.Y version. # The short X.Y version.
version = (CURR_PATH.parent / 'VERSION.txt').read_text(encoding='utf-8').strip().replace('rc', '-rc') version = (CURR_PATH.parent / "VERSION.txt").read_text(encoding="utf-8").strip().replace("rc", "-rc")
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = version release = version
...@@ -151,67 +151,68 @@ release = version ...@@ -151,67 +151,68 @@ release = version
# #
# This is also used if you do content translation via gettext catalogs. # This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases. # Usually you set "language" from the command line for these cases.
language = 'en' language = "en"
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path # This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'default' pygments_style = "default"
# -- Configuration for C API docs generation ------------------------------ # -- Configuration for C API docs generation ------------------------------
if C_API: if C_API:
extensions.extend([ extensions.extend(
'breathe', [
]) "breathe",
breathe_projects = { ]
"LightGBM": str(CURR_PATH / 'doxyoutput' / 'xml') )
} breathe_projects = {"LightGBM": str(CURR_PATH / "doxyoutput" / "xml")}
breathe_default_project = "LightGBM" breathe_default_project = "LightGBM"
breathe_domain_by_extension = { breathe_domain_by_extension = {
"h": "c", "h": "c",
} }
breathe_show_define_initializer = True breathe_show_define_initializer = True
c_id_attributes = ['LIGHTGBM_C_EXPORT'] c_id_attributes = ["LIGHTGBM_C_EXPORT"]
# -- Options for HTML output ---------------------------------------------- # -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
html_theme = 'sphinx_rtd_theme' html_theme = "sphinx_rtd_theme"
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the
# documentation. # documentation.
html_theme_options = { html_theme_options = {
'includehidden': False, "includehidden": False,
'logo_only': True, "logo_only": True,
} }
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static'] html_static_path = ["_static"]
# -- Options for HTMLHelp output ------------------------------------------ # -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = 'LightGBMdoc' htmlhelp_basename = "LightGBMdoc"
# -- Options for LaTeX output --------------------------------------------- # -- Options for LaTeX output ---------------------------------------------
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
# the title page. # the title page.
latex_logo = str(CURR_PATH / 'logo' / 'LightGBM_logo_black_text_small.png') latex_logo = str(CURR_PATH / "logo" / "LightGBM_logo_black_text_small.png")
# intersphinx configuration # intersphinx configuration
intersphinx_mapping = { intersphinx_mapping = {
"sklearn": ("https://scikit-learn.org/stable/", None), "sklearn": ("https://scikit-learn.org/stable/", None),
} }
def generate_doxygen_xml(app: Sphinx) -> None: def generate_doxygen_xml(app: Sphinx) -> None:
"""Generate XML documentation for C API by Doxygen. """Generate XML documentation for C API by Doxygen.
...@@ -238,18 +239,17 @@ def generate_doxygen_xml(app: Sphinx) -> None: ...@@ -238,18 +239,17 @@ def generate_doxygen_xml(app: Sphinx) -> None:
"SORT_BRIEF_DOCS=YES", "SORT_BRIEF_DOCS=YES",
"WARN_AS_ERROR=YES", "WARN_AS_ERROR=YES",
] ]
doxygen_input = '\n'.join(doxygen_args) doxygen_input = "\n".join(doxygen_args)
doxygen_input = bytes(doxygen_input, "utf-8") doxygen_input = bytes(doxygen_input, "utf-8")
(CURR_PATH / 'doxyoutput').mkdir(parents=True, exist_ok=True) (CURR_PATH / "doxyoutput").mkdir(parents=True, exist_ok=True)
try: try:
# Warning! The following code can cause buffer overflows on RTD. # Warning! The following code can cause buffer overflows on RTD.
# Consider suppressing output completely if RTD project silently fails. # Consider suppressing output completely if RTD project silently fails.
# Refer to https://github.com/svenevs/exhale # Refer to https://github.com/svenevs/exhale
# /blob/fe7644829057af622e467bb529db6c03a830da99/exhale/deploy.py#L99-L111 # /blob/fe7644829057af622e467bb529db6c03a830da99/exhale/deploy.py#L99-L111
process = Popen(["doxygen", "-"], process = Popen(["doxygen", "-"], stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdout, stderr = process.communicate(doxygen_input) stdout, stderr = process.communicate(doxygen_input)
output = '\n'.join([i.decode('utf-8') for i in (stdout, stderr) if i is not None]) output = "\n".join([i.decode("utf-8") for i in (stdout, stderr) if i is not None])
if process.returncode != 0: if process.returncode != 0:
raise RuntimeError(output) raise RuntimeError(output)
else: else:
...@@ -296,11 +296,9 @@ def generate_r_docs(app: Sphinx) -> None: ...@@ -296,11 +296,9 @@ def generate_r_docs(app: Sphinx) -> None:
# Consider suppressing output completely if RTD project silently fails. # Consider suppressing output completely if RTD project silently fails.
# Refer to https://github.com/svenevs/exhale # Refer to https://github.com/svenevs/exhale
# /blob/fe7644829057af622e467bb529db6c03a830da99/exhale/deploy.py#L99-L111 # /blob/fe7644829057af622e467bb529db6c03a830da99/exhale/deploy.py#L99-L111
process = Popen(['/bin/bash'], process = Popen(["/bin/bash"], stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True)
stdin=PIPE, stdout=PIPE, stderr=PIPE,
universal_newlines=True)
stdout, stderr = process.communicate(commands) stdout, stderr = process.communicate(commands)
output = '\n'.join([i for i in (stdout, stderr) if i is not None]) output = "\n".join([i for i in (stdout, stderr) if i is not None])
if process.returncode != 0: if process.returncode != 0:
raise RuntimeError(output) raise RuntimeError(output)
else: else:
...@@ -318,19 +316,19 @@ def setup(app: Sphinx) -> None: ...@@ -318,19 +316,19 @@ def setup(app: Sphinx) -> None:
app : sphinx.application.Sphinx app : sphinx.application.Sphinx
The application object representing the Sphinx process. The application object representing the Sphinx process.
""" """
first_run = not (CURR_PATH / '_FIRST_RUN.flag').exists() first_run = not (CURR_PATH / "_FIRST_RUN.flag").exists()
if first_run and RTD: if first_run and RTD:
(CURR_PATH / '_FIRST_RUN.flag').touch() (CURR_PATH / "_FIRST_RUN.flag").touch()
if C_API: if C_API:
app.connect("builder-inited", generate_doxygen_xml) app.connect("builder-inited", generate_doxygen_xml)
else: else:
app.add_directive('doxygenfile', IgnoredDirective) app.add_directive("doxygenfile", IgnoredDirective)
if RTD: # build R docs only on Read the Docs site if RTD: # build R docs only on Read the Docs site
if first_run: if first_run:
app.connect("builder-inited", generate_r_docs) app.connect("builder-inited", generate_r_docs)
app.connect("build-finished", app.connect(
lambda app, _: copytree(CURR_PATH.parent / "lightgbm_r" / "docs", "build-finished", lambda app, _: copytree(CURR_PATH.parent / "lightgbm_r" / "docs", Path(app.outdir) / "R")
Path(app.outdir) / "R")) )
app.add_transform(InternalRefTransform) app.add_transform(InternalRefTransform)
add_js_file = getattr(app, 'add_js_file', False) or app.add_javascript add_js_file = getattr(app, "add_js_file", False) or app.add_javascript
add_js_file("js/script.js") add_js_file("js/script.js")
...@@ -25,7 +25,7 @@ def check_dependencies(objdump_string: str) -> None: ...@@ -25,7 +25,7 @@ def check_dependencies(objdump_string: str) -> None:
objdump_string : str objdump_string : str
The dynamic symbol table entries of the file (result of `objdump -T` command). The dynamic symbol table entries of the file (result of `objdump -T` command).
""" """
GLIBC_version = re.compile(r'0{16}[ \(\t]+GLIBC_(\d{1,2})[.](\d{1,3})[.]?\d{,3}[ \)\t]+') GLIBC_version = re.compile(r"0{16}[ \(\t]+GLIBC_(\d{1,2})[.](\d{1,3})[.]?\d{,3}[ \)\t]+")
versions = GLIBC_version.findall(objdump_string) versions = GLIBC_version.findall(objdump_string)
assert len(versions) > 1 assert len(versions) > 1
for major, minor in versions: for major, minor in versions:
...@@ -33,16 +33,16 @@ def check_dependencies(objdump_string: str) -> None: ...@@ -33,16 +33,16 @@ def check_dependencies(objdump_string: str) -> None:
assert int(major) <= 2, error_msg assert int(major) <= 2, error_msg
assert int(minor) <= 28, error_msg assert int(minor) <= 28, error_msg
GLIBCXX_version = re.compile(r'0{16}[ \(\t]+GLIBCXX_(\d{1,2})[.](\d{1,2})[.]?(\d{,3})[ \)\t]+') GLIBCXX_version = re.compile(r"0{16}[ \(\t]+GLIBCXX_(\d{1,2})[.](\d{1,2})[.]?(\d{,3})[ \)\t]+")
versions = GLIBCXX_version.findall(objdump_string) versions = GLIBCXX_version.findall(objdump_string)
assert len(versions) > 1 assert len(versions) > 1
for major, minor, patch in versions: for major, minor, patch in versions:
error_msg = f"found unexpected GLIBCXX version: '{major}.{minor}.{patch}'" error_msg = f"found unexpected GLIBCXX version: '{major}.{minor}.{patch}'"
assert int(major) == 3, error_msg assert int(major) == 3, error_msg
assert int(minor) == 4, error_msg assert int(minor) == 4, error_msg
assert patch == '' or int(patch) <= 22, error_msg assert patch == "" or int(patch) <= 22, error_msg
GOMP_version = re.compile(r'0{16}[ \(\t]+G?OMP_(\d{1,2})[.](\d{1,2})[.]?\d{,3}[ \)\t]+') GOMP_version = re.compile(r"0{16}[ \(\t]+G?OMP_(\d{1,2})[.](\d{1,2})[.]?\d{,3}[ \)\t]+")
versions = GOMP_version.findall(objdump_string) versions = GOMP_version.findall(objdump_string)
assert len(versions) > 1 assert len(versions) > 1
for major, minor in versions: for major, minor in versions:
...@@ -52,4 +52,4 @@ def check_dependencies(objdump_string: str) -> None: ...@@ -52,4 +52,4 @@ def check_dependencies(objdump_string: str) -> None:
if __name__ == "__main__": if __name__ == "__main__":
check_dependencies(Path(sys.argv[1]).read_text(encoding='utf-8')) check_dependencies(Path(sys.argv[1]).read_text(encoding="utf-8"))
...@@ -12,9 +12,7 @@ from pathlib import Path ...@@ -12,9 +12,7 @@ from pathlib import Path
from typing import Dict, List, Tuple from typing import Dict, List, Tuple
def get_parameter_infos( def get_parameter_infos(config_hpp: Path) -> Tuple[List[Tuple[str, int]], List[List[Dict[str, List]]]]:
config_hpp: Path
) -> Tuple[List[Tuple[str, int]], List[List[Dict[str, List]]]]:
"""Parse config header file. """Parse config header file.
Parameters Parameters
...@@ -44,7 +42,7 @@ def get_parameter_infos( ...@@ -44,7 +42,7 @@ def get_parameter_infos(
cur_key = line.split("region")[1].strip() cur_key = line.split("region")[1].strip()
keys.append((cur_key, key_lvl)) keys.append((cur_key, key_lvl))
member_infos.append([]) member_infos.append([])
elif '#pragma endregion' in line: elif "#pragma endregion" in line:
key_lvl -= 1 key_lvl -= 1
if cur_key is not None: if cur_key is not None:
cur_key = None cur_key = None
...@@ -87,9 +85,7 @@ def get_parameter_infos( ...@@ -87,9 +85,7 @@ def get_parameter_infos(
return keys, member_infos return keys, member_infos
def get_names( def get_names(infos: List[List[Dict[str, List]]]) -> List[str]:
infos: List[List[Dict[str, List]]]
) -> List[str]:
"""Get names of all parameters. """Get names of all parameters.
Parameters Parameters
...@@ -109,9 +105,7 @@ def get_names( ...@@ -109,9 +105,7 @@ def get_names(
return names return names
def get_alias( def get_alias(infos: List[List[Dict[str, List]]]) -> List[Tuple[str, str]]:
infos: List[List[Dict[str, List]]]
) -> List[Tuple[str, str]]:
"""Get aliases of all parameters. """Get aliases of all parameters.
Parameters Parameters
...@@ -129,16 +123,13 @@ def get_alias( ...@@ -129,16 +123,13 @@ def get_alias(
for y in x: for y in x:
if "alias" in y: if "alias" in y:
name = y["name"][0] name = y["name"][0]
alias = y["alias"][0].split(',') alias = y["alias"][0].split(",")
for name2 in alias: for name2 in alias:
pairs.append((name2.strip(), name)) pairs.append((name2.strip(), name))
return pairs return pairs
def parse_check( def parse_check(check: str, reverse: bool = False) -> Tuple[str, str]:
check: str,
reverse: bool = False
) -> Tuple[str, str]:
"""Parse the constraint. """Parse the constraint.
Parameters Parameters
...@@ -160,17 +151,13 @@ def parse_check( ...@@ -160,17 +151,13 @@ def parse_check(
idx = 2 idx = 2
float(check[idx:]) float(check[idx:])
if reverse: if reverse:
reversed_sign = {'<': '>', '>': '<', '<=': '>=', '>=': '<='} reversed_sign = {"<": ">", ">": "<", "<=": ">=", ">=": "<="}
return check[idx:], reversed_sign[check[:idx]] return check[idx:], reversed_sign[check[:idx]]
else: else:
return check[idx:], check[:idx] return check[idx:], check[:idx]
def set_one_var_from_string( def set_one_var_from_string(name: str, param_type: str, checks: List[str]) -> str:
name: str,
param_type: str,
checks: List[str]
) -> str:
"""Construct code for auto config file for one param value. """Construct code for auto config file for one param value.
Parameters Parameters
...@@ -209,9 +196,7 @@ def set_one_var_from_string( ...@@ -209,9 +196,7 @@ def set_one_var_from_string(
def gen_parameter_description( def gen_parameter_description(
sections: List[Tuple[str, int]], sections: List[Tuple[str, int]], descriptions: List[List[Dict[str, List]]], params_rst: Path
descriptions: List[List[Dict[str, List]]],
params_rst: Path
) -> None: ) -> None:
"""Write descriptions of parameters to the documentation file. """Write descriptions of parameters to the documentation file.
...@@ -225,58 +210,57 @@ def gen_parameter_description( ...@@ -225,58 +210,57 @@ def gen_parameter_description(
Path to the file with parameters documentation. Path to the file with parameters documentation.
""" """
params_to_write = [] params_to_write = []
lvl_mapper = {1: '-', 2: '~'} lvl_mapper = {1: "-", 2: "~"}
for (section_name, section_lvl), section_params in zip(sections, descriptions): for (section_name, section_lvl), section_params in zip(sections, descriptions):
heading_sign = lvl_mapper[section_lvl] heading_sign = lvl_mapper[section_lvl]
params_to_write.append(f'{section_name}\n{heading_sign * len(section_name)}') params_to_write.append(f"{section_name}\n{heading_sign * len(section_name)}")
for param_desc in section_params: for param_desc in section_params:
name = param_desc['name'][0] name = param_desc["name"][0]
default_raw = param_desc['default'][0] default_raw = param_desc["default"][0]
default = default_raw.strip('"') if len(default_raw.strip('"')) > 0 else default_raw default = default_raw.strip('"') if len(default_raw.strip('"')) > 0 else default_raw
param_type = param_desc.get('type', param_desc['inner_type'])[0].split(':')[-1].split('<')[-1].strip('>') param_type = param_desc.get("type", param_desc["inner_type"])[0].split(":")[-1].split("<")[-1].strip(">")
options = param_desc.get('options', []) options = param_desc.get("options", [])
if len(options) > 0: if len(options) > 0:
opts = '``, ``'.join([x.strip() for x in options[0].split(',')]) opts = "``, ``".join([x.strip() for x in options[0].split(",")])
options_str = f', options: ``{opts}``' options_str = f", options: ``{opts}``"
else: else:
options_str = '' options_str = ""
aliases = param_desc.get('alias', []) aliases = param_desc.get("alias", [])
if len(aliases) > 0: if len(aliases) > 0:
aliases_joined = '``, ``'.join([x.strip() for x in aliases[0].split(',')]) aliases_joined = "``, ``".join([x.strip() for x in aliases[0].split(",")])
aliases_str = f', aliases: ``{aliases_joined}``' aliases_str = f", aliases: ``{aliases_joined}``"
else: else:
aliases_str = '' aliases_str = ""
checks = sorted(param_desc.get('check', [])) checks = sorted(param_desc.get("check", []))
checks_len = len(checks) checks_len = len(checks)
if checks_len > 1: if checks_len > 1:
number1, sign1 = parse_check(checks[0]) number1, sign1 = parse_check(checks[0])
number2, sign2 = parse_check(checks[1], reverse=True) number2, sign2 = parse_check(checks[1], reverse=True)
checks_str = f', constraints: ``{number2} {sign2} {name} {sign1} {number1}``' checks_str = f", constraints: ``{number2} {sign2} {name} {sign1} {number1}``"
elif checks_len == 1: elif checks_len == 1:
number, sign = parse_check(checks[0]) number, sign = parse_check(checks[0])
checks_str = f', constraints: ``{name} {sign} {number}``' checks_str = f", constraints: ``{name} {sign} {number}``"
else: else:
checks_str = '' checks_str = ""
main_desc = f'- ``{name}`` :raw-html:`<a id="{name}" title="Permalink to this parameter" href="#{name}">&#x1F517;&#xFE0E;</a>`, default = ``{default}``, type = {param_type}{options_str}{aliases_str}{checks_str}' main_desc = f'- ``{name}`` :raw-html:`<a id="{name}" title="Permalink to this parameter" href="#{name}">&#x1F517;&#xFE0E;</a>`, default = ``{default}``, type = {param_type}{options_str}{aliases_str}{checks_str}'
params_to_write.append(main_desc) params_to_write.append(main_desc)
params_to_write.extend([f"{' ' * 3 * int(desc[0][-1])}- {desc[1]}" for desc in param_desc['desc']]) params_to_write.extend([f"{' ' * 3 * int(desc[0][-1])}- {desc[1]}" for desc in param_desc["desc"]])
with open(params_rst) as original_params_file: with open(params_rst) as original_params_file:
all_lines = original_params_file.read() all_lines = original_params_file.read()
before, start_sep, _ = all_lines.partition('.. start params list\n\n') before, start_sep, _ = all_lines.partition(".. start params list\n\n")
_, end_sep, after = all_lines.partition('\n\n.. end params list') _, end_sep, after = all_lines.partition("\n\n.. end params list")
with open(params_rst, "w") as new_params_file: with open(params_rst, "w") as new_params_file:
new_params_file.write(before) new_params_file.write(before)
new_params_file.write(start_sep) new_params_file.write(start_sep)
new_params_file.write('\n\n'.join(params_to_write)) new_params_file.write("\n\n".join(params_to_write))
new_params_file.write(end_sep) new_params_file.write(end_sep)
new_params_file.write(after) new_params_file.write(after)
def gen_parameter_code( def gen_parameter_code(
config_hpp: Path, config_hpp: Path, config_out_cpp: Path
config_out_cpp: Path
) -> Tuple[List[Tuple[str, int]], List[List[Dict[str, List]]]]: ) -> Tuple[List[Tuple[str, int]], List[List[Dict[str, List]]]]:
"""Generate auto config file. """Generate auto config file.
...@@ -367,7 +351,7 @@ def gen_parameter_code( ...@@ -367,7 +351,7 @@ def gen_parameter_code(
if names_with_aliases[name]: if names_with_aliases[name]:
str_to_write += '{"' + '", "'.join(names_with_aliases[name]) + '"}},' str_to_write += '{"' + '", "'.join(names_with_aliases[name]) + '"}},'
else: else:
str_to_write += '{}},' str_to_write += "{}},"
str_to_write += """ str_to_write += """
}); });
return map; return map;
...@@ -376,22 +360,22 @@ def gen_parameter_code( ...@@ -376,22 +360,22 @@ def gen_parameter_code(
""" """
str_to_write += """const std::unordered_map<std::string, std::string>& Config::ParameterTypes() { str_to_write += """const std::unordered_map<std::string, std::string>& Config::ParameterTypes() {
static std::unordered_map<std::string, std::string> map({""" static std::unordered_map<std::string, std::string> map({"""
int_t_pat = re.compile(r'int\d+_t') int_t_pat = re.compile(r"int\d+_t")
# the following are stored as comma separated strings but are arrays in the wrappers # the following are stored as comma separated strings but are arrays in the wrappers
overrides = { overrides = {
'categorical_feature': 'vector<int>', "categorical_feature": "vector<int>",
'ignore_column': 'vector<int>', "ignore_column": "vector<int>",
'interaction_constraints': 'vector<vector<int>>', "interaction_constraints": "vector<vector<int>>",
} }
for x in infos: for x in infos:
for y in x: for y in x:
name = y["name"][0] name = y["name"][0]
if name == 'task': if name == "task":
continue continue
if name in overrides: if name in overrides:
param_type = overrides[name] param_type = overrides[name]
else: else:
param_type = int_t_pat.sub('int', y["inner_type"][0]).replace('std::', '') param_type = int_t_pat.sub("int", y["inner_type"][0]).replace("std::", "")
str_to_write += '\n {"' + name + '", "' + param_type + '"},' str_to_write += '\n {"' + name + '", "' + param_type + '"},'
str_to_write += """ str_to_write += """
}); });
...@@ -409,8 +393,8 @@ def gen_parameter_code( ...@@ -409,8 +393,8 @@ def gen_parameter_code(
if __name__ == "__main__": if __name__ == "__main__":
current_dir = Path(__file__).absolute().parent current_dir = Path(__file__).absolute().parent
config_hpp = current_dir.parent / 'include' / 'LightGBM' / 'config.h' config_hpp = current_dir.parent / "include" / "LightGBM" / "config.h"
config_out_cpp = current_dir.parent / 'src' / 'io' / 'config_auto.cpp' config_out_cpp = current_dir.parent / "src" / "io" / "config_auto.cpp"
params_rst = current_dir.parent / 'docs' / 'Parameters.rst' params_rst = current_dir.parent / "docs" / "Parameters.rst"
sections, descriptions = gen_parameter_code(config_hpp, config_out_cpp) sections, descriptions = gen_parameter_code(config_hpp, config_out_cpp)
gen_parameter_description(sections, descriptions, params_rst) gen_parameter_description(sections, descriptions, params_rst)
...@@ -95,11 +95,29 @@ ignore_missing_imports = true ...@@ -95,11 +95,29 @@ ignore_missing_imports = true
exclude = [ exclude = [
"build", "build",
"compile", "compile",
"docs",
"external_libs", "external_libs",
"lightgbm-python", "lightgbm-python",
"setup.py"
] ]
line-length = 120
# this should be set to the oldest version of python LightGBM supports
target-version = "py37"
[tool.ruff.format]
docstring-code-format = false
exclude = [
"build/*.py",
"compile/*.py",
"examples/*.py",
"external_libs/*.py",
"lightgbm-python/*.py",
"python-package/*.py",
"tests/*.py"
]
indent-style = "space"
quote-style = "double"
[tool.ruff.lint]
ignore = [ ignore = [
# (pydocstyle) Missing docstring in magic method # (pydocstyle) Missing docstring in magic method
"D105", "D105",
...@@ -125,10 +143,13 @@ select = [ ...@@ -125,10 +143,13 @@ select = [
"T", "T",
] ]
# this should be set to the oldest version of python LightGBM supports [tool.ruff.lint.per-file-ignores]
target-version = "py37" "docs/conf.py" = [
# (flake8-bugbear) raise exceptions with "raise ... from errr"
[tool.ruff.per-file-ignores] "B904",
# (flake8-print) flake8-print
"T"
]
"examples/*" = [ "examples/*" = [
# pydocstyle # pydocstyle
"D", "D",
...@@ -144,6 +165,5 @@ target-version = "py37" ...@@ -144,6 +165,5 @@ target-version = "py37"
"T" "T"
] ]
[tool.ruff.pydocstyle] [tool.ruff.lint.pydocstyle]
convention = "numpy" convention = "numpy"
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment