pipetasks 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.
@@ -0,0 +1,111 @@
1
+ version: 2.1
2
+ orbs:
3
+ node: circleci/node@4.3
4
+ gh: circleci/github-cli@1.0
5
+ jobs:
6
+ build_test:
7
+ docker:
8
+ - image: cimg/python:3.11.7
9
+ steps:
10
+ - checkout # checkout source code to working directory
11
+ - run:
12
+ name: Install uv
13
+ command: |
14
+ curl -LsSf https://astral.sh/uv/install.sh | sh
15
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> $BASH_ENV
16
+ - run:
17
+ name: Install dependencies
18
+ command: |
19
+ mkdir test-results
20
+ uv sync
21
+ - run:
22
+ name: Run tests
23
+ command: |
24
+ uv run pytest --junitxml=test-results/junit.xml --cov=./ --cov-report=xml
25
+ - run:
26
+ name: Run code coverage
27
+ command: |
28
+ curl -Os https://uploader.codecov.io/latest/linux/codecov
29
+ chmod +x codecov
30
+ ./codecov
31
+ - store_test_results:
32
+ path: test-results
33
+ build_verify:
34
+ docker:
35
+ - image: cimg/python:3.11.7
36
+ steps:
37
+ - checkout
38
+ - run:
39
+ name: Install uv
40
+ command: |
41
+ curl -LsSf https://astral.sh/uv/install.sh | sh
42
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> $BASH_ENV
43
+ - run:
44
+ name: Build package
45
+ command: uv build
46
+ - run:
47
+ name: Verify build artifacts
48
+ command: |
49
+ ls dist/
50
+ test -f dist/*.whl || (echo "Missing wheel" && exit 1)
51
+ test -f dist/*.tar.gz || (echo "Missing sdist" && exit 1)
52
+ - persist_to_workspace:
53
+ root: .
54
+ paths:
55
+ - dist/
56
+ pypi_publish:
57
+ docker:
58
+ - image: cimg/python:3.11.7
59
+ steps:
60
+ - checkout # checkout source code to working directory
61
+ - attach_workspace:
62
+ at: .
63
+ - run:
64
+ name: Install uv
65
+ command: |
66
+ curl -LsSf https://astral.sh/uv/install.sh | sh
67
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> $BASH_ENV
68
+ - run:
69
+ name: Build and publish to PyPI
70
+ command: |
71
+ uv publish \
72
+ --username $TWINE_USERNAME \
73
+ --password $TWINE_PASSWORD
74
+ gh_release:
75
+ docker:
76
+ - image: 'cimg/base:stable'
77
+ steps:
78
+ - checkout # checkout source code to working directory
79
+ - gh/setup
80
+ - run:
81
+ name: "Creating a GitHub Release"
82
+ command: |
83
+ export GITHUB_HOSTNAME="github.com"
84
+ VERSION=$(grep '^version' pyproject.toml | head -1 | cut -d'"' -f2)
85
+ gh release create "$VERSION" \
86
+ --notes-file "./CHANGELOG.md" \
87
+ --title "pipetasks v$VERSION" \
88
+ --repo "$(git config --get remote.origin.url)"
89
+ workflows:
90
+ build_test_publish:
91
+ jobs:
92
+ - build_test
93
+ - build_verify:
94
+ requires:
95
+ - build_test
96
+ filters:
97
+ branches:
98
+ only: [main]
99
+ - pypi_publish:
100
+ requires:
101
+ - build_verify
102
+ filters:
103
+ branches:
104
+ only: [main]
105
+ - gh_release:
106
+ requires:
107
+ - pypi_publish
108
+ context: [GITHUB_CREDENTIALS]
109
+ filters:
110
+ branches:
111
+ only: [main]
@@ -0,0 +1,32 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+ .ruff_cache/
9
+
10
+ # Virtual environments
11
+ .venv
12
+
13
+ node_modules
14
+
15
+ # Output
16
+ .output
17
+ .vercel
18
+ .netlify
19
+ .wrangler
20
+ /.svelte-kit
21
+ /build
22
+
23
+ # OS
24
+ .DS_Store
25
+ Thumbs.db
26
+
27
+ # Env
28
+ .env
29
+ .env.*
30
+ frontend/.env
31
+ !.env.example
32
+ !.env.test.example
@@ -0,0 +1,30 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.5.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-added-large-files
9
+ - repo: https://github.com/astral-sh/ruff-pre-commit
10
+ rev: v0.7.0
11
+ hooks:
12
+ - id: ruff
13
+ args: ["--fix"]
14
+ - id: ruff-format
15
+ - repo: https://github.com/pre-commit/mirrors-mypy
16
+ rev: v1.13.0
17
+ hooks:
18
+ - id: mypy
19
+ entry: uv run mypy --config-file=pyproject.toml .
20
+ language: system
21
+ pass_filenames: false
22
+ - repo: local
23
+ hooks:
24
+ - id: run-pytest
25
+ name: Run pytest
26
+ entry: .venv/bin/pytest
27
+ args: ["-c", "pyproject.toml", "--maxfail=1", "--disable-warnings"]
28
+ language: system
29
+ always_run: true
30
+ pass_filenames: false
@@ -0,0 +1 @@
1
+ 3.11.7
@@ -0,0 +1,23 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "name": "Python: Debug tests",
9
+ "type": "debugpy",
10
+ "request": "launch",
11
+ "module": "pytest",
12
+ "env": {
13
+ "PYTEST_ADDOPTS": "--no-cov"
14
+ },
15
+ "args": [
16
+ "-v",
17
+ ],
18
+ "console": "integratedTerminal",
19
+ "justMyCode": false,
20
+ "python": "${workspaceFolder}/.venv/bin/python"
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "python.testing.unittestEnabled": false,
3
+ "python.testing.pytestEnabled": true,
4
+ "python.testing.pytestPath": "${workspaceFolder}/.venv/bin/pytest",
5
+ "python.testing.cwd": "${workspaceFolder}",
6
+ "python.testing.pytestArgs": [
7
+ "tests"
8
+ ],
9
+ "python-envs.pythonProjects": [],
10
+ "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python"
11
+ }
@@ -0,0 +1,12 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
+ and this project adheres to [Semantic Versioning](http://semver.org/).
7
+
8
+ ## [0.1.0] - 2026-03-24
9
+
10
+ ### Added
11
+
12
+ - Initial release.
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: pipetasks
3
+ Version: 0.1.0
4
+ Summary: Utils to build pipelines
5
+ Author-email: Armando Ezequiel Puerta <armando.ezequiel.puerta@gmail.com>
6
+ License: MIT License
7
+ Classifier: Development Status :: 5 - Production/Stable
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Software Development
13
+ Requires-Python: >=3.11.7
14
+ Requires-Dist: selenium>=4.41.0
15
+ Requires-Dist: webdriver-manager>=4.0.2
16
+ Description-Content-Type: text/markdown
17
+
18
+ # Pipetasks
19
+
20
+ Utils to build pipelines
@@ -0,0 +1,3 @@
1
+ # Pipetasks
2
+
3
+ Utils to build pipelines
File without changes
@@ -0,0 +1,9 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class Pipeline(ABC):
5
+ name: str | None = None
6
+
7
+ @abstractmethod
8
+ def run(self) -> None:
9
+ pass
File without changes
@@ -0,0 +1,126 @@
1
+ import time
2
+
3
+ from selenium.common.exceptions import (
4
+ NoSuchElementException,
5
+ TimeoutException,
6
+ )
7
+ from selenium.webdriver.chrome.options import Options
8
+ from selenium.webdriver.chrome.service import Service
9
+ from selenium.webdriver.chrome.webdriver import WebDriver as ChromeDriver
10
+ from selenium.webdriver.common.by import By
11
+ from selenium.webdriver.remote.webelement import WebElement
12
+ from selenium.webdriver.support import expected_conditions as EC
13
+ from selenium.webdriver.support.ui import WebDriverWait
14
+ from webdriver_manager.chrome import ChromeDriverManager
15
+
16
+ from pipetasks.pipeline import Pipeline
17
+
18
+
19
+ class ScrapingPipeline(Pipeline):
20
+ TIMEOUT = 0.5
21
+ name = "Scrapper"
22
+
23
+ def __init__( # type: ignore[no-untyped-def]
24
+ self,
25
+ headless: bool = True,
26
+ *args,
27
+ **kwargs,
28
+ ):
29
+ self.driver = self.__init_driver(headless=headless)
30
+ super().__init__(*args, **kwargs)
31
+
32
+ def __init_driver(
33
+ self,
34
+ headless: bool = True,
35
+ ) -> ChromeDriver:
36
+ chrome_options = Options()
37
+ if headless:
38
+ chrome_options.add_argument("--headless=new")
39
+ chrome_options.add_argument("--disable-gpu")
40
+ chrome_options.add_argument("--no-sandbox")
41
+ chrome_options.add_argument("--disable-dev-shm-usage")
42
+
43
+ return ChromeDriver(
44
+ service=Service(ChromeDriverManager().install()),
45
+ options=chrome_options,
46
+ )
47
+
48
+ def sleep(self, seconds: float | None = None) -> None:
49
+ secs = seconds if seconds else self.TIMEOUT
50
+ time.sleep(secs)
51
+
52
+ def __timeout(self, seconds: float | None = None) -> float:
53
+ return seconds if seconds is not None else self.TIMEOUT
54
+
55
+ def get(self, dns: str) -> None:
56
+ self.driver.get(dns)
57
+ self.sleep()
58
+
59
+ def find_element(
60
+ self,
61
+ by: str,
62
+ value: str,
63
+ on: WebElement | None = None,
64
+ timeout: float | None = None,
65
+ ) -> WebElement:
66
+ if on:
67
+ return on.find_element(by, value)
68
+ else:
69
+ return WebDriverWait(
70
+ self.driver,
71
+ self.__timeout(timeout),
72
+ ).until(EC.presence_of_element_located((by, value)))
73
+
74
+ def find_elements(
75
+ self,
76
+ by: str,
77
+ value: str,
78
+ on: WebElement | None = None,
79
+ timeout: float | None = None,
80
+ ) -> list[WebElement]:
81
+ if on:
82
+ return on.find_elements(by, value)
83
+ else:
84
+ return WebDriverWait(
85
+ self.driver,
86
+ self.__timeout(timeout),
87
+ ).until(EC.presence_of_all_elements_located((by, value)))
88
+
89
+ def click(
90
+ self,
91
+ by: str,
92
+ value: str,
93
+ on: WebElement | None = None,
94
+ timeout: float | None = None,
95
+ ) -> None:
96
+ wait_timeout = self.__timeout(timeout)
97
+ element = self.find_element(
98
+ by,
99
+ value,
100
+ on=on,
101
+ timeout=wait_timeout,
102
+ )
103
+ WebDriverWait(self.driver, wait_timeout).until(
104
+ EC.element_to_be_clickable(element)
105
+ ).click()
106
+ self.sleep()
107
+
108
+ def retry(
109
+ self,
110
+ *xpaths: str,
111
+ timeout: float | None = None,
112
+ ) -> WebElement:
113
+ wait_timeout = self.__timeout(timeout)
114
+ for xpath in xpaths:
115
+ try:
116
+ return self.find_element(
117
+ By.XPATH,
118
+ xpath,
119
+ timeout=wait_timeout,
120
+ )
121
+ except TimeoutException:
122
+ continue
123
+ raise NoSuchElementException("No se encontró el elemento deseado.") from None
124
+
125
+ def run(self) -> None:
126
+ pass
@@ -0,0 +1,117 @@
1
+ [project]
2
+ name = "pipetasks"
3
+ version = "0.1.0"
4
+ description = "Utils to build pipelines"
5
+ authors = [
6
+ {name = "Armando Ezequiel Puerta", email = "armando.ezequiel.puerta@gmail.com"}
7
+ ]
8
+ license = {text = "MIT License"}
9
+ readme = "README.md"
10
+ classifiers = [
11
+ "Development Status :: 5 - Production/Stable",
12
+ "Programming Language :: Python :: 3",
13
+ "Intended Audience :: Developers",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Operating System :: OS Independent",
16
+ "Topic :: Software Development"]
17
+ requires-python = ">=3.11.7"
18
+ dependencies = [
19
+ "selenium>=4.41.0",
20
+ "webdriver-manager>=4.0.2",
21
+ ]
22
+
23
+ [dependency-groups]
24
+ dev = [
25
+ "mypy>=1.19.1",
26
+ "pre-commit>=4.5.1",
27
+ "pytest>=9.0.2",
28
+ "pytest-cov>=7.1.0",
29
+ "ruff>=0.15.7",
30
+ ]
31
+
32
+ [build-system]
33
+ requires = ["hatchling"]
34
+ build-backend = "hatchling.build"
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["pipetasks"]
38
+
39
+ [tool.pytest.ini_options]
40
+ markers = [
41
+ "integration: tests using external resources (Chrome, etc)",
42
+ ]
43
+ addopts = "-m 'not integration'"
44
+
45
+ #############################################
46
+ # RUFF
47
+ #############################################
48
+ [tool.ruff]
49
+ line-length = 88
50
+ target-version = "py312"
51
+ extend-exclude = []
52
+
53
+ # Esto activa reglas equivalentes a flake8, isort, pyupgrade,
54
+ # bugs típicos y mejores prácticas.
55
+ lint.select = [
56
+ "E", # errores estilo
57
+ "F", # errores de flake8
58
+ "I", # orden de imports (isort)
59
+ "B", # bugbear
60
+ "UP", # pyupgrade
61
+ "C4", # comprehensions
62
+ "W", # warnings
63
+ ]
64
+
65
+ lint.ignore = [
66
+ "E203", # compatibilidad con estilo Black
67
+ "E501",
68
+ ]
69
+
70
+ #############################################
71
+ # RUFF ISORT
72
+ #############################################
73
+ [tool.ruff.lint.isort]
74
+ combine-as-imports = true
75
+ known-first-party = ["core"]
76
+ force-single-line = false
77
+
78
+ #############################################
79
+ # RUFF FORMAT
80
+ #############################################
81
+ [tool.ruff.format]
82
+ quote-style = "double"
83
+ indent-style = "space"
84
+ line-ending = "auto"
85
+
86
+ #############################################
87
+ # COVERAGE
88
+ #############################################
89
+ [tool.coverage.run]
90
+ branch = true
91
+ source = ["."]
92
+ omit = [
93
+ "*/__init__.py"
94
+ ]
95
+
96
+ [tool.coverage.report]
97
+ show_missing = true
98
+ skip_covered = false
99
+
100
+ #############################################
101
+ # MYPY
102
+ #############################################
103
+ [tool.mypy]
104
+ python_version = "3.11"
105
+ strict = false
106
+ warn_unused_ignores = true
107
+ warn_redundant_casts = true
108
+ warn_return_any = true
109
+ disallow_any_generics = true
110
+ check_untyped_defs = true
111
+ no_implicit_reexport = true
112
+ disallow_untyped_defs = true
113
+ ignore_missing_imports = true
114
+
115
+ [[tool.mypy.overrides]]
116
+ module = "tests.*"
117
+ ignore_errors = true
File without changes
File without changes
@@ -0,0 +1,19 @@
1
+ from unittest.mock import MagicMock, patch
2
+
3
+ import pytest
4
+
5
+
6
+ @pytest.fixture
7
+ def mock_driver():
8
+ return MagicMock()
9
+
10
+
11
+ @pytest.fixture
12
+ def scraping_pipeline(mock_driver):
13
+ with (
14
+ patch("pipetasks.scraping.pipeline.ChromeDriverManager"),
15
+ patch("pipetasks.scraping.pipeline.ChromeDriver", return_value=mock_driver),
16
+ ):
17
+ from pipetasks.scraping.pipeline import ScrapingPipeline
18
+
19
+ yield ScrapingPipeline()