pip install -e .setup.py ni MANIFEST.insrc/__version__# ─── Build system ──────────────────────────────────────────
[build-system]
requires = ["hatchling>=1.18"]
build-backend = "hatchling.build"
# ─── Proyecto ───────────────────────────────────────────────
[project]
name = "cultiva-nlp"
version = "0.1.0"
description = "Utilidades NLP en español para proyectos CULTIVA IA"
readme = "README.md"
requires-python = ">=3.9"
license = { text = "MIT" }
authors = [{ name = "Álvaro Gimeno", email = "alvaro@cultivaia.es" }]
keywords = ["nlp", "español", "text-processing", "ner", "langdetect"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Natural Language :: Spanish",
]
dependencies = [
"spacy>=3.5,<4.0",
"langdetect>=1.0.9",
"click>=8.1",
]
[project.optional-dependencies]
dev = [
"pytest>=7.4",
"pytest-cov>=4.1",
"ruff>=0.3",
"mypy>=1.8",
"types-requests",
]
docs = ["mkdocs>=1.5", "mkdocs-material>=9.4"]
[project.urls]
Homepage = "https://github.com/cultivaia/cultiva-nlp"
Documentation = "https://cultivaia.github.io/cultiva-nlp"
"Bug Tracker" = "https://github.com/cultivaia/cultiva-nlp/issues"
[project.scripts]
cultiva-nlp = "cultiva_nlp.cli:main"
# ─── Hatchling: empaqueta todo src/ ─────────────────────────
[tool.hatch.build.targets.wheel]
packages = ["src/cultiva_nlp"]
# ─── Ruff (linter + formatter) ──────────────────────────────
[tool.ruff]
line-length = 100
target-version = "py39"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "ANN"]
ignore = ["ANN101"]
# ─── Mypy ───────────────────────────────────────────────────
[tool.mypy]
python_version = "3.9"
disallow_untyped_defs = true
warn_return_any = true
# ─── Pytest ─────────────────────────────────────────────────
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --cov=cultiva_nlp --cov-report=term-missing"
[tool.coverage.run]
source = ["src"]
omit = ["*/tests/*"]
from importlib.metadata import version
__version__ = version("cultiva-nlp")
__author__ = "Álvaro Gimeno"
__email__ = "alvaro@cultivaia.es"
from .cleaner import TextCleaner
from .detector import LanguageDetector
from .extractor import EntityExtractor
__all__ = [
"TextCleaner",
"LanguageDetector",
"EntityExtractor",
"__version__",
]
import click
from . import TextCleaner, LanguageDetector, EntityExtractor
@click.group()
@click.version_option()
def cli() -> None:
"""Herramientas NLP para texto en español."""
@cli.command()
@click.argument("text")
def clean(text: str) -> None:
"""Limpia y normaliza TEXT."""
result = TextCleaner().clean(text)
click.echo(result)
@cli.command("detect-lang")
@click.argument("text")
def detect_lang(text: str) -> None:
"""Detecta el idioma de TEXT."""
lang = LanguageDetector().detect(text)
click.echo(lang)
@cli.command()
@click.argument("text")
@click.option("--type", "entity_type",
default="all",
type=click.Choice(["all","ORG","PER","DATE"]))
def extract(text: str, entity_type: str) -> None:
"""Extrae entidades de TEXT."""
entities = EntityExtractor().extract(text, entity_type)
for e in entities:
click.echo(f"{e['type']:8} {e['text']}")
def main() -> None:
cli()
# 1. Instalar en modo editable (desarrollo)
pip install -e ".[dev]"
# 2. Verificar que el CLI funciona
cultiva-nlp --version
# → cultiva-nlp, version 0.1.0
cultiva-nlp clean "Hola!! Esto es un texto con espacios 😎"
# → Hola esto es un texto con espacios
cultiva-nlp detect-lang "Bonjour tout le monde"
# → fr
cultiva-nlp extract "Apple compró Beats en 2014 por 3.000M€" --type all
# → ORG Apple
# → ORG Beats
# → DATE 2014
# 3. Linting y tipos
ruff check src/ tests/
mypy src/
# 4. Tests con cobertura
pytest --cov=cultiva_nlp --cov-report=html
# 5. Build artefactos
python -m build
# dist/
# cultiva_nlp-0.1.0.tar.gz (sdist)
# cultiva_nlp-0.1.0-py3-none-any.whl (wheel)
# 6. Validar paquete
twine check dist/*
# 7. Publicar en TestPyPI primero
twine upload --repository testpypi dist/*
# 8. Probar instalación desde TestPyPI
pip install --index-url https://test.pypi.org/simple/ cultiva-nlp
# 9. Publicar en PyPI (producción)
twine upload dist/*
name: Publish cultiva-nlp to PyPI
on:
release:
types: [created]
workflow_dispatch: # disparo manual
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install cultiva-nlp[dev]
run: pip install -e ".[dev]"
- name: Lint (ruff)
run: ruff check src/ tests/
- name: Type check (mypy)
run: mypy src/
- name: Tests
run: pytest
publish:
needs: test
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/cultiva-nlp
permissions:
id-token: write # OIDC trusted publishing
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Build
run: |
pip install build
python -m build
- name: Publish (OIDC — sin secrets)
uses: pypa/gh-action-pypi-publish@release/v1
pypa/gh-action-pypi-publish con permisos OIDC, eliminando la necesidad de almacenar PYPI_API_TOKEN como secreto. Requiere configurar el "trusted publisher" en la web de PyPI apuntando al repo.
| Backend | setup.py necesario | PEP 621 nativo | Editable installs | Extensiones C | Recomendado para |
|---|---|---|---|---|---|
| hatchling ⭐ | ✗ | ✓ | ✓ PEP 660 | ✗ | Paquetes Python puros modernos |
| setuptools | opcional | ✓ | ✓ | ✓ | Proyectos con C/Cython o legado |
| flit | ✗ | ✓ | ✓ | ✗ | Librerías Python puras minimalistas |
| poetry | ✗ | parcial | ✓ | ✗ | Gestión de deps + empaquetado todo-en-uno |
| maturin | ✗ | ✓ | ✓ | ✓ Rust | Extensiones Rust/PyO3 |
Crear cuenta en pypi.org y test.pypi.org con 2FA activado.
En PyPI > tu cuenta > Publishing: añadir repo cultivaia/cultiva-nlp, workflow publish.yml.
Comprobar que cultiva-nlp no existe: pip index versions cultiva-nlp
py.typedArchivo vacío en src/cultiva_nlp/py.typed para declarar soporte PEP 561 (type hints).
pyproject.tomlSeguir SemVer: MAJOR.MINOR.PATCH (0.1.0 → 0.2.0 → 1.0.0)
CHANGELOG.mdFecha, versión y lista de cambios (Added / Changed / Fixed / Removed).
Tag v0.2.0 → el workflow publish.yml se dispara automáticamente.
pip install cultiva-nlp==0.2.0 en entorno limpio.
[distutils]
index-servers =
pypi
testpypi
[pypi]
username = __token__
password = pypi-AgE...token_real_aqui
[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi-AgE...test_token_aqui
~/.pypirc al repositorio. Añade *.pypirc a tu ~/.gitignore global. En CI, usa OIDC Trusted Publishing en lugar de tokens.
pip install keyring y luego keyring set https://upload.pypi.org/legacy/ __token__ para guardar el token de forma segura en el llavero del sistema operativo.