conduto 0.1.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.
conduto-0.1.0/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ <<<<<<< HEAD
4
+ Copyright (c) 2026 joao.goncalves
5
+ =======
6
+ Copyright (c) 2026 João Pedro Zanetti Gonçalves
7
+ >>>>>>> 227aebe99bb2d74d03118fc37243d86dbb20d069
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
conduto-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: conduto
3
+ Version: 0.1.0
4
+ Summary: CLI to scaffold data migration/ELT projects with YAML schemas and a uv-managed environment.
5
+ Author: joao.goncalves
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/joaopedrozg/conduto
8
+ Project-URL: Repository, https://github.com/joaopedrozg/conduto
9
+ Project-URL: Issues, https://github.com/joaopedrozg/conduto/issues
10
+ Keywords: etl,elt,dagster,database,migration,schemas,cli
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Database
18
+ Classifier: Topic :: Software Development :: Code Generators
19
+ Requires-Python: >=3.14
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: jinja2>=3.1.6
23
+ Requires-Dist: questionary>=2.1.1
24
+ Requires-Dist: rich>=15.0.0
25
+ Requires-Dist: typer>=0.27.1
26
+ Dynamic: license-file
27
+
28
+ # conduto
29
+
30
+ CLI para criar projetos de migração/ELT de dados: gera o `.env` com as credenciais dos bancos, o manifesto `main.yml`, os schemas YAML das tabelas e configura o ambiente com `uv` (`pyyaml`, `jinja2`, `polars`, `dagster`).
31
+
32
+ **Repositório:** [github.com/joaopedrozg/conduto](https://github.com/joaopedrozg/conduto)
33
+
34
+ ## Instalação
35
+
36
+ ```bash
37
+ pip install conduto
38
+ ```
39
+
40
+ ## Uso
41
+
42
+ ```bash
43
+ conduto init meu_projeto
44
+ ```
45
+
46
+ O comando pergunta as credenciais dos bancos de origem e destino e gera a estrutura:
47
+
48
+ ```text
49
+ meu_projeto/
50
+ ├── .env
51
+ ├── main.yml
52
+ ├── schemas/
53
+ │ ├── clientes.yml
54
+ │ ├── pedidos.yml
55
+ │ └── produtos.yml
56
+ └── ambiente uv (pyyaml, jinja2, polars, dagster)
57
+ ```
58
+
59
+ ## Desenvolvimento
60
+
61
+ ```bash
62
+ uv sync
63
+ uv build
64
+ uv publish
65
+ ```
@@ -0,0 +1,38 @@
1
+ # conduto
2
+
3
+ CLI para criar projetos de migração/ELT de dados: gera o `.env` com as credenciais dos bancos, o manifesto `main.yml`, os schemas YAML das tabelas e configura o ambiente com `uv` (`pyyaml`, `jinja2`, `polars`, `dagster`).
4
+
5
+ **Repositório:** [github.com/joaopedrozg/conduto](https://github.com/joaopedrozg/conduto)
6
+
7
+ ## Instalação
8
+
9
+ ```bash
10
+ pip install conduto
11
+ ```
12
+
13
+ ## Uso
14
+
15
+ ```bash
16
+ conduto init meu_projeto
17
+ ```
18
+
19
+ O comando pergunta as credenciais dos bancos de origem e destino e gera a estrutura:
20
+
21
+ ```text
22
+ meu_projeto/
23
+ ├── .env
24
+ ├── main.yml
25
+ ├── schemas/
26
+ │ ├── clientes.yml
27
+ │ ├── pedidos.yml
28
+ │ └── produtos.yml
29
+ └── ambiente uv (pyyaml, jinja2, polars, dagster)
30
+ ```
31
+
32
+ ## Desenvolvimento
33
+
34
+ ```bash
35
+ uv sync
36
+ uv build
37
+ uv publish
38
+ ```
@@ -0,0 +1,46 @@
1
+ [project]
2
+ name = "conduto"
3
+ version = "0.1.0"
4
+ description = "CLI to scaffold data migration/ELT projects with YAML schemas and a uv-managed environment."
5
+ readme = "README.md"
6
+ requires-python = ">=3.14"
7
+ license = "MIT"
8
+ authors = [{ name = "joao.goncalves" }]
9
+ keywords = ["etl", "elt", "dagster", "database", "migration", "schemas", "cli"]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Environment :: Console",
13
+ "Intended Audience :: Developers",
14
+ "Operating System :: OS Independent",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.14",
17
+ "Topic :: Database",
18
+ "Topic :: Software Development :: Code Generators",
19
+ ]
20
+ dependencies = [
21
+ "jinja2>=3.1.6",
22
+ "questionary>=2.1.1",
23
+ "rich>=15.0.0",
24
+ "typer>=0.27.1",
25
+ ]
26
+
27
+ [project.scripts]
28
+ conduto = "conduto.cli:app"
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/joaopedrozg/conduto"
32
+ Repository = "https://github.com/joaopedrozg/conduto"
33
+ Issues = "https://github.com/joaopedrozg/conduto/issues"
34
+
35
+ [build-system]
36
+ requires = ["setuptools>=68"]
37
+ build-backend = "setuptools.build_meta"
38
+
39
+ [tool.setuptools]
40
+ include-package-data = true
41
+
42
+ [tool.setuptools.packages.find]
43
+ where = ["src"]
44
+
45
+ [tool.setuptools.package-data]
46
+ conduto = ["templates/**/*.jinja"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,118 @@
1
+ import typer
2
+ import questionary
3
+ from pathlib import Path
4
+ from rich.console import Console
5
+ from rich.text import Text
6
+ from rich.panel import Panel
7
+ from rich.rule import Rule
8
+ from rich.prompt import Prompt
9
+ from .env_render import env_render
10
+ from .gerar_project import setup_uv_environment
11
+ from .schemas_render import schemas_render
12
+
13
+ app = typer.Typer()
14
+ console = Console()
15
+
16
+ custom_style = questionary.Style([
17
+ ('pointer', 'fg:cyan bold'), # Cor da setinha (ponteiro) e negrito
18
+ ('highlighted', 'fg:green bold'), # Cor do texto do item atualmente selecionado
19
+ ('answer', 'fg:yellow bold'), # Cor após confirmar a escolha
20
+ ])
21
+
22
+ @app.command()
23
+ def init(project_name: str):
24
+ # Cria o texto principal
25
+ texto_titulo = Text("Bem vindo ao Conduto!", style="bold cyan")
26
+
27
+ # Cria a mensagem de instrução
28
+ texto_instrucao = Text("\nVamos criar um novo projeto chamado: ", style="dim white")
29
+
30
+ # Cria o nome do projeto destacado (como uma "arte" simples em texto)
31
+ nome_projeto = Text(f" {project_name} ", style="bold white on blue")
32
+
33
+ # Combina o texto
34
+ mensagem_completa = texto_titulo + texto_instrucao + nome_projeto
35
+
36
+ # Exibe dentro de um painel com bordas
37
+ console.print(Panel(mensagem_completa, border_style="green", expand=False, width=100))
38
+
39
+ console.print(Rule(style="dim blue"))
40
+
41
+ db_origem = questionary.select(
42
+ "Selecione o SGBD de origem:",
43
+ choices=[
44
+ "1) PostgreSQL",
45
+ "2) MySQL",
46
+ "3) SQLServer"
47
+ ],
48
+ style=custom_style
49
+ ).ask()
50
+ console.print(Rule(style="dim blue"))
51
+ console.print(Text('Credenciais do banco de dados de origem', style="bold yellow"))
52
+
53
+ host_db_origem = Prompt.ask("HOST:", default="localhost")
54
+ port_db_origem = Prompt.ask("PORT:", default="5432")
55
+ database_db_origem = Prompt.ask("DATABASE:", default="postgres")
56
+ user_db_origem = Prompt.ask("USERNAME:", default="postgres")
57
+ senha_db_origem = Prompt.ask("PASSWORD:", default="postgres", password=True)
58
+
59
+ console.print(Rule(style="dim blue"))
60
+
61
+ db_destino = questionary.select(
62
+ "Selecione o SGBD de destino:",
63
+ choices=[
64
+ "1) PostgreSQL",
65
+ "2) MySQL",
66
+ "3) SQLServer"
67
+ ],
68
+ style=custom_style
69
+ ).ask()
70
+
71
+ console.print(Rule(style="dim blue"))
72
+
73
+ console.print(Text('Credenciais do banco de dados de destino', style="bold yellow"))
74
+
75
+ host_db_destino = Prompt.ask("HOST:", default="localhost")
76
+ port_db_destino = Prompt.ask("PORT:", default="5432")
77
+ database_db_destino = Prompt.ask("DATABASE:", default="postgres")
78
+ user_db_destino = Prompt.ask("USERNAME:", default="postgres")
79
+ senha_db_destino = Prompt.ask("PASSWORD:", default="postgres", password=True)
80
+
81
+ context = {
82
+ "project_name": project_name,
83
+ "db_destino_tipo": db_destino,
84
+ "origem": {
85
+ "host": host_db_origem,
86
+ "port": port_db_origem,
87
+ "database": database_db_origem,
88
+ "user": user_db_origem,
89
+ "password": senha_db_origem,
90
+ },
91
+ "destino": {
92
+ "host": host_db_destino,
93
+ "port": port_db_destino,
94
+ "database": database_db_destino,
95
+ "user": user_db_destino,
96
+ "password": senha_db_destino,
97
+ }
98
+ }
99
+
100
+ project_dir = Path.cwd() / project_name
101
+ project_dir.mkdir(exist_ok=True)
102
+
103
+ env_path = env_render(context, output_dir=project_dir)
104
+ schemas_render(project_dir, context)
105
+ if env_path is not None and env_path.exists():
106
+ setup_uv_environment(project_dir)
107
+
108
+ @app.command()
109
+ def build():
110
+ """
111
+ Build the project.
112
+ """
113
+ typer.echo("Building the project...")
114
+ # Add logic to build the project here
115
+
116
+
117
+ if __name__ == "__main__":
118
+ app()
@@ -0,0 +1,33 @@
1
+
2
+ from jinja2 import Template
3
+ from pathlib import Path
4
+
5
+ from rich import console
6
+ from rich.console import Console
7
+
8
+ console = Console()
9
+
10
+ def env_render(context, output_dir=None):
11
+ base_dir = Path(__file__).resolve().parent
12
+ template_path = base_dir / "templates" / "env" / "env-example.jinja"
13
+
14
+ if not template_path.exists():
15
+ console.print(
16
+ f"[bold red]Erro:[/bold red] O template não foi encontrado no caminho: [yellow]{template_path}[/yellow]")
17
+ raise FileNotFoundError(f"Template não encontrado: {template_path}")
18
+
19
+
20
+ with open(template_path, "r", encoding="utf-8") as f:
21
+ template_content = f.read()
22
+
23
+ template = Template(template_content)
24
+ rendered_env = template.render(context)
25
+
26
+ # Salva o arquivo final no diretorio do projeto
27
+ target_dir = Path(output_dir) if output_dir else Path.cwd()
28
+ env_path = target_dir / ".env"
29
+ with open(env_path, "w", encoding="utf-8") as f:
30
+ f.write(rendered_env)
31
+ console.print(f"[bold green]Sucesso:[/bold green] Arquivo .env gerado em: [yellow]{env_path}[/yellow]")
32
+
33
+ return env_path
@@ -0,0 +1,66 @@
1
+ from pathlib import Path
2
+ import subprocess
3
+
4
+ from rich.console import Console
5
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
6
+
7
+ console = Console()
8
+
9
+
10
+ def setup_uv_environment(base_path: Path):
11
+ # 1. Inicializar projeto uv (caso pyproject.toml nao exista)
12
+ pyproject = base_path / "pyproject.toml"
13
+ if not pyproject.exists():
14
+ console.print("Executando: uv init --no-readme")
15
+ init_result = subprocess.run(
16
+ ["uv", "init", "--no-readme"],
17
+ cwd=base_path,
18
+ capture_output=True,
19
+ text=True,
20
+ encoding="utf-8",
21
+ errors="replace",
22
+ )
23
+ if init_result.returncode != 0:
24
+ if init_result.stderr:
25
+ console.print(init_result.stderr, style="bold red")
26
+ raise subprocess.CalledProcessError(init_result.returncode, init_result.args)
27
+ else:
28
+ console.print("pyproject.toml ja existe, pulando 'uv init'.")
29
+
30
+ # 2. Instalar dependencias essenciais para parse e manipulacao
31
+ dependencies = ["pyyaml", "jinja2", "polars", "dagster"]
32
+ cmd = ["uv", "add"] + dependencies
33
+
34
+ def _run():
35
+ return subprocess.run(
36
+ cmd,
37
+ cwd=base_path,
38
+ capture_output=True,
39
+ text=True,
40
+ encoding="utf-8",
41
+ errors="replace",
42
+ )
43
+
44
+ if console.is_terminal:
45
+ with Progress(
46
+ SpinnerColumn(),
47
+ BarColumn(bar_width=40),
48
+ TextColumn("[progress.description]{task.description}"),
49
+ console=console,
50
+ ) as progress:
51
+ progress.add_task(
52
+ f"Instalando dependencias: {', '.join(dependencies)}", total=None
53
+ )
54
+ result = _run()
55
+ else:
56
+ console.print(f"Instalando dependencias: {', '.join(dependencies)}")
57
+ result = _run()
58
+
59
+ if result.returncode != 0:
60
+ if result.stdout:
61
+ console.print(result.stdout)
62
+ if result.stderr:
63
+ console.print(result.stderr, style="bold red")
64
+ raise subprocess.CalledProcessError(result.returncode, cmd)
65
+
66
+ console.print("Ambiente configurado com sucesso!")
@@ -0,0 +1,40 @@
1
+ from pathlib import Path
2
+
3
+ from jinja2 import Template
4
+ from rich.console import Console
5
+
6
+ console = Console()
7
+
8
+
9
+ def schemas_render(project_dir, context):
10
+ base_dir = Path(__file__).resolve().parent
11
+ templates_dir = base_dir / "templates" / "schemas"
12
+ target_dir = Path(project_dir)
13
+ schemas_dir = target_dir / "schemas"
14
+ schemas_dir.mkdir(parents=True, exist_ok=True)
15
+
16
+ template_paths = sorted(templates_dir.glob("*.jinja"))
17
+ if not template_paths:
18
+ console.print(
19
+ f"[bold red]Erro:[/bold red] Nenhum template encontrado em: [yellow]{templates_dir}[/yellow]"
20
+ )
21
+ raise FileNotFoundError(f"Nenhum template encontrado em: {templates_dir}")
22
+
23
+ for template_path in template_paths:
24
+ output_name = template_path.name.removesuffix("-example.yml.jinja") + ".yml"
25
+ output_path = (
26
+ target_dir / output_name
27
+ if output_name == "main.yml"
28
+ else schemas_dir / output_name
29
+ )
30
+
31
+ with open(template_path, "r", encoding="utf-8") as f:
32
+ template = Template(f.read())
33
+ rendered = template.render(context)
34
+
35
+ with open(output_path, "w", encoding="utf-8") as f:
36
+ f.write(rendered)
37
+
38
+ console.print(f"[bold green]Gerado:[/bold green] [yellow]{output_path}[/yellow]")
39
+
40
+ return target_dir / "main.yml"
@@ -0,0 +1,11 @@
1
+ DB_ORIGEM_HOST={{ origem.host }}
2
+ DB_ORIGEM_PORT={{ origem.port }}
3
+ DB_ORIGEM_NAME={{ origem.database }}
4
+ DB_ORIGEM_USER={{ origem.user }}
5
+ DB_ORIGEM_PASSWORD={{ origem.password }}
6
+
7
+ DB_DESTINO_HOST={{ destino.host }}
8
+ DB_DESTINO_PORT={{ destino.port }}
9
+ DB_DESTINO_NAME={{ destino.database }}
10
+ DB_DESTINO_USER={{ destino.user }}
11
+ DB_DESTINO_PASSWORD={{ destino.password }}
@@ -0,0 +1,17 @@
1
+ table: clientes
2
+ schema: public
3
+ description: "Tabela de cadastro de clientes"
4
+ columns:
5
+ - name: id
6
+ type: integer
7
+ primary_key: true
8
+ nullable: false
9
+ - name: nome
10
+ type: varchar(255)
11
+ nullable: false
12
+ - name: email
13
+ type: varchar(255)
14
+ unique: true
15
+ - name: criado_em
16
+ type: timestamp
17
+ default: CURRENT_TIMESTAMP
@@ -0,0 +1,8 @@
1
+ version: "1.0"
2
+ project: {{ project_name }}
3
+
4
+ # Lista de tabelas na ordem correta de dependência (ex: clientes antes de pedidos)
5
+ tables:
6
+ - path: "schemas/clientes.yml"
7
+ - path: "schemas/pedidos.yml"
8
+ - path: "schemas/produtos.yml"
@@ -0,0 +1,18 @@
1
+ table: pedidos
2
+ schema: public
3
+ description: "Tabela transacional de pedidos"
4
+ columns:
5
+ - name: id
6
+ type: integer
7
+ primary_key: true
8
+ nullable: false
9
+ - name: cliente_id
10
+ type: integer
11
+ foreign_key: clientes(id)
12
+ nullable: false
13
+ - name: valor_total
14
+ type: numeric(10, 2)
15
+ nullable: false
16
+ - name: data_pedido
17
+ type: timestamp
18
+ default: CURRENT_TIMESTAMP
@@ -0,0 +1,24 @@
1
+ table: produtos
2
+ schema: public
3
+ description: "Tabela de catálogo de produtos"
4
+ columns:
5
+ - name: id
6
+ type: integer
7
+ primary_key: true
8
+ nullable: false
9
+ - name: sku
10
+ type: varchar(50)
11
+ unique: true
12
+ nullable: false
13
+ - name: nome
14
+ type: varchar(255)
15
+ nullable: false
16
+ - name: preco_unitario
17
+ type: numeric(12, 2)
18
+ nullable: false
19
+ - name: categoria
20
+ type: varchar(100)
21
+ nullable: true
22
+ - name: ativo
23
+ type: boolean
24
+ default: true
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: conduto
3
+ Version: 0.1.0
4
+ Summary: CLI to scaffold data migration/ELT projects with YAML schemas and a uv-managed environment.
5
+ Author: joao.goncalves
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/joaopedrozg/conduto
8
+ Project-URL: Repository, https://github.com/joaopedrozg/conduto
9
+ Project-URL: Issues, https://github.com/joaopedrozg/conduto/issues
10
+ Keywords: etl,elt,dagster,database,migration,schemas,cli
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Database
18
+ Classifier: Topic :: Software Development :: Code Generators
19
+ Requires-Python: >=3.14
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: jinja2>=3.1.6
23
+ Requires-Dist: questionary>=2.1.1
24
+ Requires-Dist: rich>=15.0.0
25
+ Requires-Dist: typer>=0.27.1
26
+ Dynamic: license-file
27
+
28
+ # conduto
29
+
30
+ CLI para criar projetos de migração/ELT de dados: gera o `.env` com as credenciais dos bancos, o manifesto `main.yml`, os schemas YAML das tabelas e configura o ambiente com `uv` (`pyyaml`, `jinja2`, `polars`, `dagster`).
31
+
32
+ **Repositório:** [github.com/joaopedrozg/conduto](https://github.com/joaopedrozg/conduto)
33
+
34
+ ## Instalação
35
+
36
+ ```bash
37
+ pip install conduto
38
+ ```
39
+
40
+ ## Uso
41
+
42
+ ```bash
43
+ conduto init meu_projeto
44
+ ```
45
+
46
+ O comando pergunta as credenciais dos bancos de origem e destino e gera a estrutura:
47
+
48
+ ```text
49
+ meu_projeto/
50
+ ├── .env
51
+ ├── main.yml
52
+ ├── schemas/
53
+ │ ├── clientes.yml
54
+ │ ├── pedidos.yml
55
+ │ └── produtos.yml
56
+ └── ambiente uv (pyyaml, jinja2, polars, dagster)
57
+ ```
58
+
59
+ ## Desenvolvimento
60
+
61
+ ```bash
62
+ uv sync
63
+ uv build
64
+ uv publish
65
+ ```
@@ -0,0 +1,19 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/conduto/__init__.py
5
+ src/conduto/cli.py
6
+ src/conduto/env_render.py
7
+ src/conduto/gerar_project.py
8
+ src/conduto/schemas_render.py
9
+ src/conduto.egg-info/PKG-INFO
10
+ src/conduto.egg-info/SOURCES.txt
11
+ src/conduto.egg-info/dependency_links.txt
12
+ src/conduto.egg-info/entry_points.txt
13
+ src/conduto.egg-info/requires.txt
14
+ src/conduto.egg-info/top_level.txt
15
+ src/conduto/templates/env/env-example.jinja
16
+ src/conduto/templates/schemas/clientes-example.yml.jinja
17
+ src/conduto/templates/schemas/main-example.yml.jinja
18
+ src/conduto/templates/schemas/pedidos-example.yml.jinja
19
+ src/conduto/templates/schemas/produtos-example.yml.jinja
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ conduto = conduto.cli:app
@@ -0,0 +1,4 @@
1
+ jinja2>=3.1.6
2
+ questionary>=2.1.1
3
+ rich>=15.0.0
4
+ typer>=0.27.1
@@ -0,0 +1 @@
1
+ conduto