lechuga 0.3__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.
- lechuga-0.3/LICENSE +21 -0
- lechuga-0.3/PKG-INFO +103 -0
- lechuga-0.3/README.md +77 -0
- lechuga-0.3/lechuga/__init__.py +0 -0
- lechuga-0.3/lechuga/config.py +28 -0
- lechuga-0.3/lechuga/lechuga.py +106 -0
- lechuga-0.3/lechuga/models.py +30 -0
- lechuga-0.3/pyproject.toml +32 -0
lechuga-0.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Teofilo Sibileau
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
lechuga-0.3/PKG-INFO
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: lechuga
|
|
3
|
+
Version: 0.3
|
|
4
|
+
Summary: retrieves AR$ rates from fixer.io API
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Teofilo Sibileau
|
|
7
|
+
Author-email: teo.sibileau@gmail.com
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Requires-Dist: click
|
|
16
|
+
Requires-Dist: colorama
|
|
17
|
+
Requires-Dist: pydantic (>=2.12.5,<3.0.0)
|
|
18
|
+
Requires-Dist: python-dotenv (>=1.2.2,<2.0.0)
|
|
19
|
+
Requires-Dist: requests
|
|
20
|
+
Requires-Dist: requests-cache
|
|
21
|
+
Requires-Dist: simplejson
|
|
22
|
+
Requires-Dist: tabulate
|
|
23
|
+
Requires-Dist: tenacity (>=9.1.4,<10.0.0)
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# Lechuga
|
|
27
|
+
|
|
28
|
+
:leaves:
|
|
29
|
+
|
|
30
|
+
"Lechuga" (lettuce) is a commonly used slang financial term in Argentina to refer to US Dollars.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
Requirements:
|
|
35
|
+
|
|
36
|
+
+ A [fixer.io](https://fixer.io/quickstart) free account
|
|
37
|
+
+ python 3.7+
|
|
38
|
+
+ [Poetry](https://python-poetry.org/)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
$ poetry add git+https://github.com/drkloc/lechuga.git
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or install directly with pip:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
$ pip install git+https://github.com/drkloc/lechuga.git
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Setup an environment variable with the Fixer IO API access key:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
export FIXERIOKEY=YOUR_API_KEY
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## What it does
|
|
57
|
+
|
|
58
|
+
It retrieves data from the `fixer.io` API && prints it using pretty colors right to the cli output.
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## How it works
|
|
62
|
+
|
|
63
|
+
### Help
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
$ lechuga --help
|
|
67
|
+
|
|
68
|
+
Usage: lechuga.py [OPTIONS]
|
|
69
|
+
|
|
70
|
+
Options:
|
|
71
|
+
--n INTEGER How far in the past should we go?
|
|
72
|
+
--help Show this message and exit.
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Get latest n rates
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
➔ lechuga --n 20
|
|
79
|
+
|
|
80
|
+
Fecha USD EURO
|
|
81
|
+
---------- ------- --------
|
|
82
|
+
2018-11-11 35.42 40.1
|
|
83
|
+
2018-11-12 35.54 39.92
|
|
84
|
+
2018-11-13 36.01 40.73
|
|
85
|
+
2018-11-14 35.89 40.6
|
|
86
|
+
2018-11-15 36.04 40.82
|
|
87
|
+
2018-11-16 35.92 41.02
|
|
88
|
+
2018-11-17 35.76 40.84
|
|
89
|
+
2018-11-18 35.78 40.84
|
|
90
|
+
2018-11-19 35.91 41.13
|
|
91
|
+
2018-11-20 36.18 41.14
|
|
92
|
+
2018-11-21 36.27 41.3
|
|
93
|
+
2018-11-22 36.43 41.54
|
|
94
|
+
2018-11-23 37.56 42.61
|
|
95
|
+
2018-11-24 37.56 42.61
|
|
96
|
+
2018-11-25 37.58 42.61
|
|
97
|
+
2018-11-26 39.08 44.29
|
|
98
|
+
2018-11-27 38.54 43.55
|
|
99
|
+
2018-11-28 38.45 43.71
|
|
100
|
+
2018-11-29 37.73 42.98
|
|
101
|
+
2018-11-30 37.73 42.67
|
|
102
|
+
```
|
|
103
|
+
|
lechuga-0.3/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Lechuga
|
|
2
|
+
|
|
3
|
+
:leaves:
|
|
4
|
+
|
|
5
|
+
"Lechuga" (lettuce) is a commonly used slang financial term in Argentina to refer to US Dollars.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Requirements:
|
|
10
|
+
|
|
11
|
+
+ A [fixer.io](https://fixer.io/quickstart) free account
|
|
12
|
+
+ python 3.7+
|
|
13
|
+
+ [Poetry](https://python-poetry.org/)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
$ poetry add git+https://github.com/drkloc/lechuga.git
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install directly with pip:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
$ pip install git+https://github.com/drkloc/lechuga.git
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Setup an environment variable with the Fixer IO API access key:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
export FIXERIOKEY=YOUR_API_KEY
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## What it does
|
|
32
|
+
|
|
33
|
+
It retrieves data from the `fixer.io` API && prints it using pretty colors right to the cli output.
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## How it works
|
|
37
|
+
|
|
38
|
+
### Help
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
$ lechuga --help
|
|
42
|
+
|
|
43
|
+
Usage: lechuga.py [OPTIONS]
|
|
44
|
+
|
|
45
|
+
Options:
|
|
46
|
+
--n INTEGER How far in the past should we go?
|
|
47
|
+
--help Show this message and exit.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Get latest n rates
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
➔ lechuga --n 20
|
|
54
|
+
|
|
55
|
+
Fecha USD EURO
|
|
56
|
+
---------- ------- --------
|
|
57
|
+
2018-11-11 35.42 40.1
|
|
58
|
+
2018-11-12 35.54 39.92
|
|
59
|
+
2018-11-13 36.01 40.73
|
|
60
|
+
2018-11-14 35.89 40.6
|
|
61
|
+
2018-11-15 36.04 40.82
|
|
62
|
+
2018-11-16 35.92 41.02
|
|
63
|
+
2018-11-17 35.76 40.84
|
|
64
|
+
2018-11-18 35.78 40.84
|
|
65
|
+
2018-11-19 35.91 41.13
|
|
66
|
+
2018-11-20 36.18 41.14
|
|
67
|
+
2018-11-21 36.27 41.3
|
|
68
|
+
2018-11-22 36.43 41.54
|
|
69
|
+
2018-11-23 37.56 42.61
|
|
70
|
+
2018-11-24 37.56 42.61
|
|
71
|
+
2018-11-25 37.58 42.61
|
|
72
|
+
2018-11-26 39.08 44.29
|
|
73
|
+
2018-11-27 38.54 43.55
|
|
74
|
+
2018-11-28 38.45 43.71
|
|
75
|
+
2018-11-29 37.73 42.98
|
|
76
|
+
2018-11-30 37.73 42.67
|
|
77
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sqlite3
|
|
3
|
+
import requests_cache
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
from colorama import init
|
|
6
|
+
|
|
7
|
+
load_dotenv()
|
|
8
|
+
|
|
9
|
+
SCRIPT_ROOT = os.path.dirname(os.path.realpath(__file__))
|
|
10
|
+
DB_PATH = os.path.join(SCRIPT_ROOT, "lechuga.sqlite")
|
|
11
|
+
|
|
12
|
+
init(autoreset=True)
|
|
13
|
+
requests_cache.install_cache(
|
|
14
|
+
os.path.join(SCRIPT_ROOT, "lechuga"), backend="sqlite", expire_after=60
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_db_connection(db_path=None):
|
|
19
|
+
conn = sqlite3.connect(db_path or DB_PATH)
|
|
20
|
+
conn.execute("""
|
|
21
|
+
CREATE TABLE IF NOT EXISTS rates (
|
|
22
|
+
date TEXT PRIMARY KEY,
|
|
23
|
+
usd REAL NOT NULL,
|
|
24
|
+
euro REAL NOT NULL
|
|
25
|
+
)
|
|
26
|
+
""")
|
|
27
|
+
conn.commit()
|
|
28
|
+
return conn
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from datetime import date, datetime, timedelta
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
5
|
+
import requests
|
|
6
|
+
from requests.exceptions import HTTPError
|
|
7
|
+
from colorama import Fore, Back, Style
|
|
8
|
+
from tabulate import tabulate
|
|
9
|
+
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception
|
|
10
|
+
|
|
11
|
+
from lechuga.config import get_db_connection
|
|
12
|
+
from lechuga.models import Rate, cached_rate
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _is_retryable(exception):
|
|
16
|
+
return (
|
|
17
|
+
isinstance(exception, HTTPError)
|
|
18
|
+
and exception.response is not None
|
|
19
|
+
and exception.response.status_code == 429
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _trend(current, previous):
|
|
24
|
+
pct = (current - previous) / previous * 100
|
|
25
|
+
if pct > 3:
|
|
26
|
+
return " 🚀"
|
|
27
|
+
if pct > 0:
|
|
28
|
+
return " 📈"
|
|
29
|
+
if pct < -3:
|
|
30
|
+
return " 💥"
|
|
31
|
+
if pct < 0:
|
|
32
|
+
return " 📉"
|
|
33
|
+
return " ➡️"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Lechuga:
|
|
37
|
+
def __init__(self, depth=1):
|
|
38
|
+
self.api_key = os.environ.get("FIXERIOKEY", False)
|
|
39
|
+
if not self.api_key:
|
|
40
|
+
raise Exception("Please set the FIXERIOKEY environ variable")
|
|
41
|
+
self.depth = depth
|
|
42
|
+
self.p = []
|
|
43
|
+
self.db_conn = get_db_connection()
|
|
44
|
+
self.refresh()
|
|
45
|
+
|
|
46
|
+
@cached_rate
|
|
47
|
+
@retry(
|
|
48
|
+
retry=retry_if_exception(_is_retryable),
|
|
49
|
+
wait=wait_exponential(multiplier=1, min=1, max=10),
|
|
50
|
+
stop=stop_after_attempt(3),
|
|
51
|
+
)
|
|
52
|
+
def fetch_rate(self, date_str):
|
|
53
|
+
API = "http://data.fixer.io/api/%s?access_key=%s&format=1&symbols=USD,ARS"
|
|
54
|
+
uri = API % (date_str, self.api_key)
|
|
55
|
+
r = requests.get(uri)
|
|
56
|
+
r.raise_for_status()
|
|
57
|
+
r = r.json()
|
|
58
|
+
return Rate(
|
|
59
|
+
date=r["date"],
|
|
60
|
+
usd=r["rates"]["ARS"] / r["rates"]["USD"],
|
|
61
|
+
euro=r["rates"]["ARS"],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def refresh(self):
|
|
65
|
+
last = False
|
|
66
|
+
while self.depth:
|
|
67
|
+
date_str = (
|
|
68
|
+
date.today().strftime("%Y-%m-%d")
|
|
69
|
+
if not last
|
|
70
|
+
else last.strftime("%Y-%m-%d")
|
|
71
|
+
)
|
|
72
|
+
rate = self.fetch_rate(date_str)
|
|
73
|
+
self.p.append(rate.model_dump())
|
|
74
|
+
last = datetime.strptime(rate.date, "%Y-%m-%d") - timedelta(days=1)
|
|
75
|
+
self.depth -= 1
|
|
76
|
+
|
|
77
|
+
def print_it(self):
|
|
78
|
+
print("")
|
|
79
|
+
h = [
|
|
80
|
+
Back.YELLOW + Fore.BLACK + " Fecha " + Style.RESET_ALL,
|
|
81
|
+
Back.GREEN + Fore.WHITE + " USD " + Style.RESET_ALL,
|
|
82
|
+
Back.BLUE + Fore.WHITE + " EURO " + Style.RESET_ALL,
|
|
83
|
+
]
|
|
84
|
+
rows = list(reversed(self.p))
|
|
85
|
+
o = []
|
|
86
|
+
for idx, i in enumerate(rows):
|
|
87
|
+
usd_str = "%.2f" % i["usd"]
|
|
88
|
+
euro_str = "%.2f" % i["euro"]
|
|
89
|
+
if idx > 0:
|
|
90
|
+
usd_str += _trend(i["usd"], rows[idx - 1]["usd"])
|
|
91
|
+
euro_str += _trend(i["euro"], rows[idx - 1]["euro"])
|
|
92
|
+
o.append([i["date"], usd_str, euro_str])
|
|
93
|
+
print(tabulate(o, headers=h))
|
|
94
|
+
print("")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@click.command()
|
|
98
|
+
@click.option("--n", default=4, help="How far in the past should we go?")
|
|
99
|
+
def run(n):
|
|
100
|
+
client = Lechuga(n)
|
|
101
|
+
client.print_it()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
logging.captureWarnings(True)
|
|
106
|
+
run()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Rate(BaseModel):
|
|
7
|
+
date: str
|
|
8
|
+
usd: float
|
|
9
|
+
euro: float
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def cached_rate(fn):
|
|
13
|
+
@functools.wraps(fn)
|
|
14
|
+
def wrapper(self, date_str):
|
|
15
|
+
row = self.db_conn.execute(
|
|
16
|
+
"SELECT date, usd, euro FROM rates WHERE date = ?", (date_str,)
|
|
17
|
+
).fetchone()
|
|
18
|
+
if row:
|
|
19
|
+
return Rate(date=row[0], usd=row[1], euro=row[2])
|
|
20
|
+
|
|
21
|
+
rate = fn(self, date_str)
|
|
22
|
+
|
|
23
|
+
self.db_conn.execute(
|
|
24
|
+
"INSERT OR IGNORE INTO rates (date, usd, euro) VALUES (?, ?, ?)",
|
|
25
|
+
(rate.date, rate.usd, rate.euro),
|
|
26
|
+
)
|
|
27
|
+
self.db_conn.commit()
|
|
28
|
+
return rate
|
|
29
|
+
|
|
30
|
+
return wrapper
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "lechuga"
|
|
3
|
+
version = "0.3"
|
|
4
|
+
description = "retrieves AR$ rates from fixer.io API"
|
|
5
|
+
authors = ["Teofilo Sibileau <teo.sibileau@gmail.com>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
packages = [{include = "lechuga"}]
|
|
9
|
+
|
|
10
|
+
[tool.poetry.scripts]
|
|
11
|
+
lechuga = "lechuga.lechuga:run"
|
|
12
|
+
|
|
13
|
+
[tool.poetry.dependencies]
|
|
14
|
+
python = "^3.10"
|
|
15
|
+
click = "*"
|
|
16
|
+
requests = "*"
|
|
17
|
+
requests-cache = "*"
|
|
18
|
+
colorama = "*"
|
|
19
|
+
tabulate = "*"
|
|
20
|
+
simplejson = "*"
|
|
21
|
+
python-dotenv = "^1.2.2"
|
|
22
|
+
pydantic = "^2.12.5"
|
|
23
|
+
tenacity = "^9.1.4"
|
|
24
|
+
|
|
25
|
+
[tool.poetry.group.dev.dependencies]
|
|
26
|
+
ipython = "*"
|
|
27
|
+
ipdb = "^0.13.13"
|
|
28
|
+
pytest = "^9.0.2"
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["poetry-core"]
|
|
32
|
+
build-backend = "poetry.core.masonry.api"
|