xploreai 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xploreai-0.2.0/PKG-INFO +149 -0
- xploreai-0.2.0/README.md +139 -0
- xploreai-0.2.0/pyproject.toml +29 -0
- xploreai-0.2.0/pyproject.toml.orig +32 -0
- xploreai-0.2.0/src/xploreai/__init__.py +2 -0
- xploreai-0.2.0/src/xploreai/environment/xai_localhost.py +20 -0
- xploreai-0.2.0/src/xploreai/log/xai_log.py +211 -0
- xploreai-0.2.0/src/xploreai/utils/xai_environment.py +48 -0
- xploreai-0.2.0/src/xploreai/utils/xai_memory.py +39 -0
xploreai-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: xploreai
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Library and cookbook codes for AI Engineering Projects
|
|
5
|
+
Author: Daniel Dominguete
|
|
6
|
+
Author-email: Daniel Dominguete <daniel.dominguete@gmail.com>
|
|
7
|
+
Requires-Dist: pytest>=9.2.0
|
|
8
|
+
Requires-Python: >=3.12, <3.13
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# XploreAI
|
|
12
|
+
|
|
13
|
+
Biblioteca Python e cookbook para experimentos em engenharia de AI
|
|
14
|
+
|
|
15
|
+
## Estrutura do repositório
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
src/xploreai/ → biblioteca (publicável no PyPI)
|
|
19
|
+
cookbook/ → scripts e notebooks de receitas locais (ex.: langchain/)
|
|
20
|
+
references/ → material de estudo externo (somente leitura)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Módulos da biblioteca (`src/xploreai/`)
|
|
24
|
+
|
|
25
|
+
Pacotes por domínio; cada módulo segue o prefixo **`xai_`** (Xplore AI).
|
|
26
|
+
|
|
27
|
+
| Pacote | Arquivo | Responsabilidade |
|
|
28
|
+
|--------|---------|------------------|
|
|
29
|
+
| `environment` | `xai_localhost.py` | Estrutura de pastas para runs locais |
|
|
30
|
+
| `log` | `xai_log.py` | Logging estruturado e perfil de ambiente em execuções |
|
|
31
|
+
| `utils` | `xai_environment.py` | Localizar a raiz do projeto (`pyproject.toml`) |
|
|
32
|
+
| `utils` | `xai_memory.py` | Conversão e utilitários de memória (unidades binárias) |
|
|
33
|
+
|
|
34
|
+
## Configuração do ambiente
|
|
35
|
+
|
|
36
|
+
Este projeto usa **[uv](https://docs.astral.sh/uv/)** para ambientes e dependências.
|
|
37
|
+
Não use `requirements.txt` na raiz nem `pip install` — tudo fica em **`pyproject.toml`** + **`uv.lock`**.
|
|
38
|
+
|
|
39
|
+
### 1. Instalar uv
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. Inicializar ambiente do projeto
|
|
46
|
+
|
|
47
|
+
Na raiz do repositório (onde está o `pyproject.toml`):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv sync --all-groups
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
O `uv sync` cria `.venv/`, instala dependências fixadas em `uv.lock` e registra **`xploreai`** em modo editável a partir de `src/xploreai/`.
|
|
54
|
+
|
|
55
|
+
Para **criar um projeto Python novo** com uv (fora deste repo):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv init --python 3.12
|
|
59
|
+
uv add pandas # exemplo: adiciona dependencia e atualiza pyproject.toml + uv.lock
|
|
60
|
+
uv sync
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. Notebooks sem output no Git (nbstripout)
|
|
64
|
+
|
|
65
|
+
Este repo **nao usa** o pacote [`pre-commit`](https://pre-commit.com/) para notebooks. A limpeza e feita pelo **filtro Git** do [`nbstripout`](https://github.com/kynan/nbstripout) (ja dependencia em `pyproject.toml`).
|
|
66
|
+
|
|
67
|
+
**Uma vez por clone** (depois de `uv sync`), na raiz do repo:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv run nbstripout --install --attributes .gitattributes
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Isso registra o filtro em `.git/config` e versiona regras em `.gitattributes` (`*.ipynb filter=nbstripout`). No `git add`, outputs sao removidos do que entra no indice; no working tree voce pode continuar executando o notebook normalmente.
|
|
74
|
+
|
|
75
|
+
Conferir se esta ativo:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
uv run nbstripout --is-installed
|
|
79
|
+
uv run nbstripout --status
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Se recriar o `.venv` ou mudar o caminho do Python, reinstale o filtro:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
uv run nbstripout --install --attributes .gitattributes --force
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Limpar notebooks ja versionados (one-shot), se necessario:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
uv run nbstripout $(git ls-files '*.ipynb')
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Validar CI/local sem alterar arquivos:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
uv run nbstripout --verify $(git ls-files '*.ipynb')
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 4. Execução
|
|
101
|
+
|
|
102
|
+
Validação do setup:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
uv run xploreai
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Testes:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
uv run pytest
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Exemplo de cookbook:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
uv run python cookbook/01_langchain/prompt_template.py
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Versionamento semântico (Commitizen)
|
|
121
|
+
|
|
122
|
+
Commits seguem **[Conventional Commits](https://www.conventionalcommits.org/)** via [Commitizen](https://commitizen-tools.github.io/commitizen/). A versão do pacote vive em `pyproject.toml` (`[project].version`); o `cz bump` atualiza essa versão, o `CHANGELOG.md` e cria a tag Git `vX.Y.Z`.
|
|
123
|
+
|
|
124
|
+
### Commits e push
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
git add .
|
|
128
|
+
uv run cz commit
|
|
129
|
+
git push # sem publicação de nova versão
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
| Tipo | Quando usar | Bump |
|
|
133
|
+
|------|-------------|------|
|
|
134
|
+
| `feat` | Nova funcionalidade | minor |
|
|
135
|
+
| `fix` | Correção de bug | patch |
|
|
136
|
+
| `docs` | Documentação | — |
|
|
137
|
+
| `refactor` | Refatoração | patch |
|
|
138
|
+
| `test` | Testes | — |
|
|
139
|
+
| `chore` | Manutenção | — |
|
|
140
|
+
|
|
141
|
+
### Release e publicação
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
uv run cz bump # bump de versão + CHANGELOG + tag
|
|
145
|
+
uv build # gera dist/*.whl
|
|
146
|
+
uv run --with xploreai --no-project -- python -c "import xploreai" # testar artefato
|
|
147
|
+
uv run --env-file .env --no-project -- uv publish # PyPI (token em UV_PUBLISH_TOKEN)
|
|
148
|
+
git push && git push --tags
|
|
149
|
+
```
|
xploreai-0.2.0/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# XploreAI
|
|
2
|
+
|
|
3
|
+
Biblioteca Python e cookbook para experimentos em engenharia de AI
|
|
4
|
+
|
|
5
|
+
## Estrutura do repositório
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/xploreai/ → biblioteca (publicável no PyPI)
|
|
9
|
+
cookbook/ → scripts e notebooks de receitas locais (ex.: langchain/)
|
|
10
|
+
references/ → material de estudo externo (somente leitura)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Módulos da biblioteca (`src/xploreai/`)
|
|
14
|
+
|
|
15
|
+
Pacotes por domínio; cada módulo segue o prefixo **`xai_`** (Xplore AI).
|
|
16
|
+
|
|
17
|
+
| Pacote | Arquivo | Responsabilidade |
|
|
18
|
+
|--------|---------|------------------|
|
|
19
|
+
| `environment` | `xai_localhost.py` | Estrutura de pastas para runs locais |
|
|
20
|
+
| `log` | `xai_log.py` | Logging estruturado e perfil de ambiente em execuções |
|
|
21
|
+
| `utils` | `xai_environment.py` | Localizar a raiz do projeto (`pyproject.toml`) |
|
|
22
|
+
| `utils` | `xai_memory.py` | Conversão e utilitários de memória (unidades binárias) |
|
|
23
|
+
|
|
24
|
+
## Configuração do ambiente
|
|
25
|
+
|
|
26
|
+
Este projeto usa **[uv](https://docs.astral.sh/uv/)** para ambientes e dependências.
|
|
27
|
+
Não use `requirements.txt` na raiz nem `pip install` — tudo fica em **`pyproject.toml`** + **`uv.lock`**.
|
|
28
|
+
|
|
29
|
+
### 1. Instalar uv
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Inicializar ambiente do projeto
|
|
36
|
+
|
|
37
|
+
Na raiz do repositório (onde está o `pyproject.toml`):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv sync --all-groups
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
O `uv sync` cria `.venv/`, instala dependências fixadas em `uv.lock` e registra **`xploreai`** em modo editável a partir de `src/xploreai/`.
|
|
44
|
+
|
|
45
|
+
Para **criar um projeto Python novo** com uv (fora deste repo):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv init --python 3.12
|
|
49
|
+
uv add pandas # exemplo: adiciona dependencia e atualiza pyproject.toml + uv.lock
|
|
50
|
+
uv sync
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. Notebooks sem output no Git (nbstripout)
|
|
54
|
+
|
|
55
|
+
Este repo **nao usa** o pacote [`pre-commit`](https://pre-commit.com/) para notebooks. A limpeza e feita pelo **filtro Git** do [`nbstripout`](https://github.com/kynan/nbstripout) (ja dependencia em `pyproject.toml`).
|
|
56
|
+
|
|
57
|
+
**Uma vez por clone** (depois de `uv sync`), na raiz do repo:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv run nbstripout --install --attributes .gitattributes
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Isso registra o filtro em `.git/config` e versiona regras em `.gitattributes` (`*.ipynb filter=nbstripout`). No `git add`, outputs sao removidos do que entra no indice; no working tree voce pode continuar executando o notebook normalmente.
|
|
64
|
+
|
|
65
|
+
Conferir se esta ativo:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv run nbstripout --is-installed
|
|
69
|
+
uv run nbstripout --status
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Se recriar o `.venv` ou mudar o caminho do Python, reinstale o filtro:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
uv run nbstripout --install --attributes .gitattributes --force
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Limpar notebooks ja versionados (one-shot), se necessario:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
uv run nbstripout $(git ls-files '*.ipynb')
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Validar CI/local sem alterar arquivos:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uv run nbstripout --verify $(git ls-files '*.ipynb')
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 4. Execução
|
|
91
|
+
|
|
92
|
+
Validação do setup:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
uv run xploreai
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Testes:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
uv run pytest
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Exemplo de cookbook:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv run python cookbook/01_langchain/prompt_template.py
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Versionamento semântico (Commitizen)
|
|
111
|
+
|
|
112
|
+
Commits seguem **[Conventional Commits](https://www.conventionalcommits.org/)** via [Commitizen](https://commitizen-tools.github.io/commitizen/). A versão do pacote vive em `pyproject.toml` (`[project].version`); o `cz bump` atualiza essa versão, o `CHANGELOG.md` e cria a tag Git `vX.Y.Z`.
|
|
113
|
+
|
|
114
|
+
### Commits e push
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
git add .
|
|
118
|
+
uv run cz commit
|
|
119
|
+
git push # sem publicação de nova versão
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
| Tipo | Quando usar | Bump |
|
|
123
|
+
|------|-------------|------|
|
|
124
|
+
| `feat` | Nova funcionalidade | minor |
|
|
125
|
+
| `fix` | Correção de bug | patch |
|
|
126
|
+
| `docs` | Documentação | — |
|
|
127
|
+
| `refactor` | Refatoração | patch |
|
|
128
|
+
| `test` | Testes | — |
|
|
129
|
+
| `chore` | Manutenção | — |
|
|
130
|
+
|
|
131
|
+
### Release e publicação
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
uv run cz bump # bump de versão + CHANGELOG + tag
|
|
135
|
+
uv build # gera dist/*.whl
|
|
136
|
+
uv run --with xploreai --no-project -- python -c "import xploreai" # testar artefato
|
|
137
|
+
uv run --env-file .env --no-project -- uv publish # PyPI (token em UV_PUBLISH_TOKEN)
|
|
138
|
+
git push && git push --tags
|
|
139
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "xploreai"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Library and cookbook codes for AI Engineering Projects"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12,<3.13"
|
|
7
|
+
dependencies = ["pytest>=9.2.0"]
|
|
8
|
+
|
|
9
|
+
[[project.authors]]
|
|
10
|
+
name = "Daniel Dominguete"
|
|
11
|
+
email = "daniel.dominguete@gmail.com"
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
xploreai = "xploreai:main"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["uv_build>=0.12.23,<0.13.0"]
|
|
18
|
+
build-backend = "uv_build"
|
|
19
|
+
|
|
20
|
+
[dependency-groups]
|
|
21
|
+
dev = ["commitizen>=4.19.3"]
|
|
22
|
+
|
|
23
|
+
[tool.commitizen]
|
|
24
|
+
name = "cz_conventional_commits"
|
|
25
|
+
version_provider = "pep621"
|
|
26
|
+
tag_format = "v$version"
|
|
27
|
+
update_changelog_on_bump = true
|
|
28
|
+
changelog_file = "CHANGELOG.md"
|
|
29
|
+
major_version_zero = true
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "xploreai"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Library and cookbook codes for AI Engineering Projects"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Daniel Dominguete", email = "daniel.dominguete@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12,<3.13"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"pytest>=9.2.0",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
xploreai = "xploreai:main"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["uv_build>=0.12.23,<0.13.0"]
|
|
19
|
+
build-backend = "uv_build"
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = [
|
|
23
|
+
"commitizen>=4.19.3",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[tool.commitizen]
|
|
27
|
+
name = "cz_conventional_commits"
|
|
28
|
+
version_provider = "pep621"
|
|
29
|
+
tag_format = "v$version"
|
|
30
|
+
update_changelog_on_bump = true
|
|
31
|
+
changelog_file = "CHANGELOG.md"
|
|
32
|
+
major_version_zero = true
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Xplore DS :: Localhost Tools Package
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# importando as bibliotecas padrao
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import sys, os
|
|
8
|
+
|
|
9
|
+
# Configurando path para raiz do projeto e setup de reconhecimento da pasta da lib
|
|
10
|
+
project_folder = Path(__file__).resolve().parents[2]
|
|
11
|
+
sys.path.append(str(project_folder))
|
|
12
|
+
|
|
13
|
+
from xploredev.io.file import create_folder
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class XploreDevLocalhost:
|
|
17
|
+
def __init__(self, run_folder: str) -> None:
|
|
18
|
+
|
|
19
|
+
# criando a pasta de runs caso não exista
|
|
20
|
+
create_folder(str(run_folder) + "/runs")
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Xplore DEV :: Logging Tools Package
|
|
3
|
+
|
|
4
|
+
Structured logging for scripts and cookbook runs, including environment profiling.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import platform
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
import warnings
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Final
|
|
18
|
+
|
|
19
|
+
import psutil
|
|
20
|
+
|
|
21
|
+
from xploreds.io.xds_file import create_folder
|
|
22
|
+
from xploreds.utils.xds_memory import convert_bytes
|
|
23
|
+
|
|
24
|
+
PathLike = str | Path
|
|
25
|
+
|
|
26
|
+
_LOGGER_NAME: Final = "xploreds"
|
|
27
|
+
_LOG_FORMAT: Final = "%(asctime)s - %(levelname)s - %(message)s"
|
|
28
|
+
_LOG_DATE_FORMAT: Final = "%d-%m-%Y %H:%M:%S"
|
|
29
|
+
_RUN_TIMESTAMP_FORMAT: Final = "%y%m%d%H%M%S"
|
|
30
|
+
|
|
31
|
+
_SEPARATOR_TITLE: Final = "*" * 82
|
|
32
|
+
_SEPARATOR_SUBTITLE: Final = "=" * 82
|
|
33
|
+
_SEPARATOR_SECTION: Final = "-" * 82
|
|
34
|
+
|
|
35
|
+
_VALID_WARNING_ACTIONS: Final = frozenset(
|
|
36
|
+
{"error", "ignore", "always", "default", "module", "once"}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _configure_python_warnings() -> None:
|
|
41
|
+
"""Apply warning filter from ``PYTHON_WARNINGS`` env var (default: ``default``)."""
|
|
42
|
+
action = os.getenv("PYTHON_WARNINGS", "default")
|
|
43
|
+
if action not in _VALID_WARNING_ACTIONS:
|
|
44
|
+
action = "default"
|
|
45
|
+
warnings.filterwarnings(action)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _log_bordered(logger: logging.Logger, message: str, separator: str) -> None:
|
|
49
|
+
"""Log a message framed by the same separator line above and below."""
|
|
50
|
+
logger.info(separator)
|
|
51
|
+
logger.info(message)
|
|
52
|
+
logger.info(separator)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class XploreDSLogging:
|
|
56
|
+
"""
|
|
57
|
+
Configure and write structured logs for a single script execution.
|
|
58
|
+
|
|
59
|
+
Each run creates a timestamped folder under ``runs/`` with a dedicated
|
|
60
|
+
``.log`` file. Use wrapper methods (``info``, ``title``, ``section``) to
|
|
61
|
+
keep log output consistent across projects.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
project_root: Root directory of the project.
|
|
65
|
+
script_name: Name or path of the script being executed (used in log paths).
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, project_root: PathLike, script_name: PathLike) -> None:
|
|
69
|
+
self.project_root = Path(project_root)
|
|
70
|
+
script_path = Path(script_name)
|
|
71
|
+
self.script_name = script_path.name
|
|
72
|
+
|
|
73
|
+
self.dt_init = datetime.now()
|
|
74
|
+
self.dt_init_str = self.dt_init.strftime(_RUN_TIMESTAMP_FORMAT)
|
|
75
|
+
self.ts_init = time.time()
|
|
76
|
+
|
|
77
|
+
script_stem = script_path.stem
|
|
78
|
+
self.log_run = f"{script_stem}_{self.dt_init_str}"
|
|
79
|
+
self.log_path = self.project_root / "runs" / self.log_run
|
|
80
|
+
self.log_name = f"{self.log_run}.log"
|
|
81
|
+
self.log_file = self.log_path / self.log_name
|
|
82
|
+
|
|
83
|
+
create_folder(self.log_path)
|
|
84
|
+
self.logger = self._setup_logger()
|
|
85
|
+
_configure_python_warnings()
|
|
86
|
+
|
|
87
|
+
def _setup_logger(self) -> logging.Logger:
|
|
88
|
+
"""Configure file and console handlers for the current run."""
|
|
89
|
+
logger = logging.getLogger(_LOGGER_NAME)
|
|
90
|
+
logger.setLevel(logging.INFO)
|
|
91
|
+
logger.handlers.clear()
|
|
92
|
+
logger.propagate = False
|
|
93
|
+
|
|
94
|
+
formatter = logging.Formatter(_LOG_FORMAT, datefmt=_LOG_DATE_FORMAT)
|
|
95
|
+
|
|
96
|
+
file_handler = logging.FileHandler(self.log_file)
|
|
97
|
+
stream_handler = logging.StreamHandler(sys.stderr)
|
|
98
|
+
for handler in (file_handler, stream_handler):
|
|
99
|
+
handler.setFormatter(formatter)
|
|
100
|
+
logger.addHandler(handler)
|
|
101
|
+
|
|
102
|
+
return logger
|
|
103
|
+
|
|
104
|
+
def info(self, message: str, *args: Any, **kwargs: Any) -> None:
|
|
105
|
+
"""Log an information-level message."""
|
|
106
|
+
self.logger.info(message, *args, **kwargs)
|
|
107
|
+
|
|
108
|
+
def warning(self, message: str, *args: Any, **kwargs: Any) -> None:
|
|
109
|
+
"""Log a warning-level message."""
|
|
110
|
+
self.logger.warning(message, *args, **kwargs)
|
|
111
|
+
|
|
112
|
+
def error(self, message: str, *args: Any, **kwargs: Any) -> None:
|
|
113
|
+
"""Log an error-level message."""
|
|
114
|
+
self.logger.error(message, *args, **kwargs)
|
|
115
|
+
|
|
116
|
+
def title(self, message: str) -> None:
|
|
117
|
+
"""Log a top-level title with a heavy separator."""
|
|
118
|
+
_log_bordered(self.logger, message, _SEPARATOR_TITLE)
|
|
119
|
+
|
|
120
|
+
def subtitle(self, message: str) -> None:
|
|
121
|
+
"""Log a subtitle with a medium separator."""
|
|
122
|
+
_log_bordered(self.logger, message, _SEPARATOR_SUBTITLE)
|
|
123
|
+
|
|
124
|
+
def section(self, message: str) -> None:
|
|
125
|
+
"""Log a section header with a light separator."""
|
|
126
|
+
_log_bordered(self.logger, message, _SEPARATOR_SECTION)
|
|
127
|
+
|
|
128
|
+
def init_run(self) -> None:
|
|
129
|
+
"""Log the start banner for the current run."""
|
|
130
|
+
_log_bordered(self.logger, f"Starting script: {self.log_run}", _SEPARATOR_TITLE)
|
|
131
|
+
|
|
132
|
+
def close_run(self) -> None:
|
|
133
|
+
"""Log the closing banner with execution duration."""
|
|
134
|
+
ended_at = datetime.now()
|
|
135
|
+
exec_hours = (time.time() - self.ts_init) / 3600
|
|
136
|
+
|
|
137
|
+
self.logger.info(_SEPARATOR_TITLE)
|
|
138
|
+
self.logger.info("Execution finished script: %s", self.log_run)
|
|
139
|
+
self.logger.info("Conclusion at %s", ended_at.strftime("%Y-%m-%d %H:%M"))
|
|
140
|
+
self.logger.info("Execution time: %.2f hours", exec_hours)
|
|
141
|
+
self.logger.info(_SEPARATOR_TITLE)
|
|
142
|
+
self.logger.info("%74sXploreDS", "")
|
|
143
|
+
self.logger.info(_SEPARATOR_TITLE)
|
|
144
|
+
|
|
145
|
+
def log_environment_setup(self) -> None:
|
|
146
|
+
"""
|
|
147
|
+
Log hardware, OS, Python, and run configuration details.
|
|
148
|
+
|
|
149
|
+
Note:
|
|
150
|
+
Requires ``psutil`` for memory and CPU reporting.
|
|
151
|
+
"""
|
|
152
|
+
self.logger.info(_SEPARATOR_TITLE)
|
|
153
|
+
self.logger.info("Environment Setup")
|
|
154
|
+
self._log_machine_setup()
|
|
155
|
+
self._log_os_setup()
|
|
156
|
+
self._log_python_setup()
|
|
157
|
+
self._log_run_setup()
|
|
158
|
+
self.logger.info(_SEPARATOR_TITLE)
|
|
159
|
+
|
|
160
|
+
def _log_section(self, title: str) -> None:
|
|
161
|
+
"""Log a labeled section with light separators."""
|
|
162
|
+
_log_bordered(self.logger, title, _SEPARATOR_SECTION)
|
|
163
|
+
|
|
164
|
+
def _log_machine_setup(self) -> None:
|
|
165
|
+
"""Log CPU and memory details."""
|
|
166
|
+
self._log_section("Machine Setup")
|
|
167
|
+
self.logger.info("Machine: %s", platform.machine())
|
|
168
|
+
self.logger.info("Processor: %s", platform.processor())
|
|
169
|
+
self.logger.info("Physical cores: %s", psutil.cpu_count(logical=False))
|
|
170
|
+
self.logger.info("Total cores: %s", psutil.cpu_count(logical=True))
|
|
171
|
+
memory_gb = convert_bytes(psutil.virtual_memory().total, "GB")
|
|
172
|
+
self.logger.info("Physical memory: %.2fGB", memory_gb)
|
|
173
|
+
|
|
174
|
+
def _log_os_setup(self) -> None:
|
|
175
|
+
"""Log operating system details."""
|
|
176
|
+
self._log_section("OS Setup")
|
|
177
|
+
self.logger.info("OS id: %s", platform.platform())
|
|
178
|
+
self.logger.info("OS System: %s", platform.system())
|
|
179
|
+
self.logger.info("OS Node Name: %s", platform.node())
|
|
180
|
+
self.logger.info("OS Release: %s", platform.release())
|
|
181
|
+
|
|
182
|
+
def _log_python_setup(self) -> None:
|
|
183
|
+
"""Log Python interpreter details."""
|
|
184
|
+
self._log_section("Python Setup")
|
|
185
|
+
self.logger.info("Python version: %s", sys.version)
|
|
186
|
+
self.logger.info("Python path: %s", sys.executable)
|
|
187
|
+
self.logger.info("Python warnings: %s", os.getenv("PYTHON_WARNINGS"))
|
|
188
|
+
|
|
189
|
+
def _log_run_setup(self) -> None:
|
|
190
|
+
"""Log paths and identifiers for the current run."""
|
|
191
|
+
self._log_section("Run Setup")
|
|
192
|
+
self.logger.info("Root folder: %s", self.project_root)
|
|
193
|
+
self.logger.info("Run name: %s", self.log_run)
|
|
194
|
+
self.logger.info("Artifacts folder: %s", self.log_path)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def create_logger(project_root: PathLike, script_path: PathLike) -> XploreDSLogging:
|
|
198
|
+
"""
|
|
199
|
+
Create the run logger for the current script.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
project_root: Root directory of the XploreDS project.
|
|
203
|
+
script_path: Path to the script file being executed.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Configured ``XploreDSLogging`` instance.
|
|
207
|
+
"""
|
|
208
|
+
return XploreDSLogging(
|
|
209
|
+
project_root=project_root,
|
|
210
|
+
script_name=script_path,
|
|
211
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Xplore DEV :: Environment Utils Package
|
|
3
|
+
|
|
4
|
+
Utilities for locating project roots and resolving runtime paths.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
PathLike = str | Path
|
|
10
|
+
|
|
11
|
+
DEFAULT_PROJECT_MARKER = "pyproject.toml"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def resolve_project_root(
|
|
15
|
+
start: PathLike | None = None,
|
|
16
|
+
*,
|
|
17
|
+
marker: str = DEFAULT_PROJECT_MARKER,
|
|
18
|
+
) -> Path:
|
|
19
|
+
"""
|
|
20
|
+
Locate a project root by walking parent directories for a marker file.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
start: Directory to begin the search. Defaults to the current
|
|
24
|
+
working directory when ``None``.
|
|
25
|
+
marker: Filename that identifies the project root. Defaults to
|
|
26
|
+
``pyproject.toml``.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Absolute path to the directory containing the marker file.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
RuntimeError: When ``marker`` cannot be found in any parent folder.
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
From a cookbook script::
|
|
36
|
+
|
|
37
|
+
project_root = resolve_project_root(Path(__file__).resolve().parent)
|
|
38
|
+
"""
|
|
39
|
+
current = Path(start).resolve() if start is not None else Path.cwd()
|
|
40
|
+
|
|
41
|
+
for path in (current, *current.parents):
|
|
42
|
+
if (path / marker).is_file():
|
|
43
|
+
return path
|
|
44
|
+
|
|
45
|
+
raise RuntimeError(
|
|
46
|
+
f"Could not find {marker!r} starting from {current}. "
|
|
47
|
+
"Run the script from within the project repository."
|
|
48
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Xplore DS :: Memory Utils Package
|
|
3
|
+
|
|
4
|
+
Utilities for converting and expressing memory sizes in binary units.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Final
|
|
8
|
+
|
|
9
|
+
BYTES_PER_UNIT: Final[int] = 1024
|
|
10
|
+
BINARY_UNITS: Final[tuple[str, ...]] = ("B", "KB", "MB", "GB", "TB")
|
|
11
|
+
_UNIT_EXPONENT: Final[dict[str, int]] = {
|
|
12
|
+
unit: exponent for exponent, unit in enumerate(BINARY_UNITS)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def convert_bytes(size_bytes: int | float, to_unit: str = "GB") -> float:
|
|
17
|
+
"""
|
|
18
|
+
Convert a byte count to the requested binary unit (KiB-based).
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
size_bytes: Size in bytes.
|
|
22
|
+
to_unit: Target unit. One of B, KB, MB, GB, or TB (case-insensitive).
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Size expressed in the requested unit.
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
ValueError: When ``to_unit`` is not supported.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
``convert_bytes(1073741824, "GB")`` returns ``1.0``.
|
|
32
|
+
"""
|
|
33
|
+
unit = to_unit.upper()
|
|
34
|
+
if unit not in _UNIT_EXPONENT:
|
|
35
|
+
supported = ", ".join(BINARY_UNITS)
|
|
36
|
+
raise ValueError(f"Invalid unit {to_unit!r}. Use one of: {supported}.")
|
|
37
|
+
|
|
38
|
+
exponent = _UNIT_EXPONENT[unit]
|
|
39
|
+
return float(size_bytes) / (BYTES_PER_UNIT**exponent)
|