Skip to content

Commit a2d60a7

Browse files
Merge pull request #17 from Riminder/feature/add-parsing-eval-and-rate-limit
Feat : add parsing eval and rate limit
2 parents 69a2218 + 7e972d0 commit a2d60a7

55 files changed

Lines changed: 3185 additions & 195 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ test/*
1414
test_assets/*
1515
tests/assets
1616
.htpasswd
17+
test.ipynb
1718

1819
docker/dependencies/libs/*
1920

Documentation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ Here is an example on how to handle webhooks
605605
* Here an example on how to get help:
606606
607607
```python
608-
>>> from hrflow.hrflow.profile.parsing import ProfileParsing
608+
>>> from hrflow.profile.parsing import ProfileParsing
609609
>>> help(ProfileParsing.get)
610610
611611
#Help on function get in module hrflow.profile.parsing:

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Define variables
22
ARGS :=
33

4-
clean:
4+
clean: clean_cache
55
rm -rf build dist *.egg-info
66

77
clean_cache:

README.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,11 @@ with open("path_to_file.pdf", "rb") as f:
6565

6666
#Parse it using this method without reference:
6767
response = client.profile.parsing.add_file(
68-
source_key="INSERT_THE_TARGET_SOURCE_KEY",
69-
profile_file=file,
70-
sync_parsing=1, # This is to invoke real time parsing
71-
tags=[{"name": "application_reference", "value": "TS_X12345"}], # Attach an application tag to the profile to be parsed
72-
)
73-
68+
source_key="INSERT_THE_TARGET_SOURCE_KEY",
69+
profile_file=file,
70+
sync_parsing=1, # This is to invoke real time parsing
71+
tags=[{"name": "application_reference", "value": "TS_X12345"}], # Attach an application tag to the profile to be parsed
72+
)
7473
```
7574

7675

@@ -79,4 +78,4 @@ response = client.profile.parsing.add_file(
7978
- [HrFlow.ai Academy](https://www.youtube.com/@hrflow.aiacademy9534) on Youtube for videos on how to get started with HrFlow.ai
8079
- [Updates page](https://updates.hrflow.ai/) to keep you informed about our product releases
8180
- [Documentation](https://developers.hrflow.ai/reference/authentication) to provide information on HrFlow.ai features
82-
- [Our Roadmap](https://roadmap.hrflow.ai/) to show upcoming features or request new ones
81+
- [Our Roadmap](https://roadmap.hrflow.ai/) to show upcoming features or request new ones

hrflow/__init__.py

100755100644
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
__url__,
88
__version__,
99
)
10-
from .hrflow.hrflow import Hrflow
10+
from .hrflow import Hrflow
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import typing as t
22

3-
from ..utils import validate_key, validate_response
3+
from ..core.rate_limit import rate_limiter
4+
from ..core.validation import validate_key, validate_response
45

56
API_SECRET_REGEX = r"^ask[rw]?_[0-9a-f]{32}$"
67

@@ -9,6 +10,7 @@ class Auth:
910
def __init__(self, api):
1011
self.client = api
1112

13+
@rate_limiter
1214
def get(self) -> t.Dict[str, t.Any]:
1315
"""
1416
Try your API Keys. This endpoint allows you to learn how to add the right
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from ..utils import (
1+
from ..core.rate_limit import rate_limiter
2+
from ..core.validation import (
23
ORDER_BY_VALUES,
34
validate_key,
45
validate_limit,
@@ -12,6 +13,7 @@ class Board(object):
1213
def __init__(self, client):
1314
self.client = client
1415

16+
@rate_limiter
1517
def list(self, name=None, page=1, limit=30, sort_by="date", order_by="desc"):
1618
"""
1719
Search boards for given filters.
@@ -42,6 +44,7 @@ def list(self, name=None, page=1, limit=30, sort_by="date", order_by="desc"):
4244
response = self.client.get("boards", query_params)
4345
return validate_response(response)
4446

47+
@rate_limiter
4548
def get(self, key=None):
4649
"""
4750
Get source given a board key.

hrflow/core/__init__.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import os
2+
3+
from .validation import (
4+
is_valid_extension,
5+
is_valid_filename,
6+
validate_key,
7+
validate_reference,
8+
)
9+
10+
11+
def format_item_payload(item, provider_key, key, reference=None, email=None):
12+
provider = "source_key" if item == "profile" else "board_key"
13+
14+
payload = {provider: validate_key("provider", provider_key)}
15+
if key:
16+
payload["key"] = validate_key("item", key)
17+
if reference:
18+
payload["reference"] = validate_reference(reference)
19+
if email:
20+
payload["profile_email"] = email
21+
22+
return payload
23+
24+
25+
def get_files_from_dir(dir_path, is_recurcive):
26+
file_res = []
27+
files_path = os.listdir(dir_path)
28+
29+
for file_path in files_path:
30+
true_path = os.path.join(dir_path, file_path)
31+
if os.path.isdir(true_path) and is_recurcive:
32+
if is_valid_filename(true_path):
33+
file_res += get_files_from_dir(true_path, is_recurcive)
34+
continue
35+
if is_valid_extension(true_path):
36+
file_res.append(true_path)
37+
return file_res

hrflow/core/rate_limit.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from functools import wraps
2+
from time import sleep, time
3+
4+
DEFAULT_MAX_REQUESTS_PER_MINUTE = None
5+
DEFAULT_MIN_SLEEP_PER_REQUEST = 0
6+
SECONDS_IN_MINUTE = 60
7+
8+
9+
def rate_limiter(func):
10+
"""
11+
Decorator that applies rate limiting to a function.
12+
13+
Parameters in the decorated function:
14+
max_requests_per_minute: <int> The maximum number of requests that can be made
15+
in a minute. If None, there is no limit.
16+
min_sleep_per_request: <float> The minimum time to wait between requests.
17+
18+
Usage:
19+
>>> @rate_limiter()
20+
... def my_function(param1, param2):
21+
... pass
22+
... my_function(1, 2, max_requests_per_minute=10, min_sleep_per_request=0.1)
23+
... # The function will be called at most 10 times per minute
24+
... # with at least 0.1 seconds between each call
25+
"""
26+
requests_per_minute = 0
27+
last_reset_time = time()
28+
29+
@wraps(func)
30+
def wrapper(*args, **kwargs):
31+
max_requests_per_minute = kwargs.pop(
32+
"max_requests_per_minute", DEFAULT_MAX_REQUESTS_PER_MINUTE
33+
)
34+
min_sleep_per_request = kwargs.pop(
35+
"min_sleep_per_request", DEFAULT_MIN_SLEEP_PER_REQUEST
36+
)
37+
nonlocal requests_per_minute, last_reset_time
38+
39+
current_time = time()
40+
elapsed_time = current_time - last_reset_time
41+
42+
if elapsed_time < SECONDS_IN_MINUTE:
43+
requests_per_minute += 1
44+
if (
45+
max_requests_per_minute is not None
46+
and requests_per_minute > max_requests_per_minute
47+
):
48+
49+
sleep(SECONDS_IN_MINUTE - elapsed_time)
50+
requests_per_minute = 0
51+
last_reset_time = time()
52+
else:
53+
requests_per_minute = 0
54+
last_reset_time = current_time
55+
56+
sleep(min_sleep_per_request)
57+
return func(*args, **kwargs)
58+
59+
return wrapper
Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os
22
import re
3-
import typing as t
43

54
KEY_REGEX = r"^[0-9a-f]{40}$"
65
STAGE_VALUES = [None, "new", "yes", "later", "no"]
@@ -33,22 +32,6 @@
3332
]
3433
INVALID_FILENAME = [".", ".."]
3534

36-
ITEM_TYPE = ["profile", "job"]
37-
38-
39-
def format_item_payload(item, provider_key, key, reference=None, email=None):
40-
provider = "source_key" if item == "profile" else "board_key"
41-
42-
payload = {provider: validate_key("provider", provider_key)}
43-
if key:
44-
payload["key"] = validate_key("item", key)
45-
if reference:
46-
payload["reference"] = validate_reference(reference)
47-
if email:
48-
payload["profile_email"] = email
49-
50-
return payload
51-
5235

5336
def validate_boolean(name, value):
5437
"""
@@ -126,21 +109,6 @@ def is_valid_filename(file_path):
126109
return name not in INVALID_FILENAME
127110

128111

129-
def get_files_from_dir(dir_path, is_recurcive):
130-
file_res = []
131-
files_path = os.listdir(dir_path)
132-
133-
for file_path in files_path:
134-
true_path = os.path.join(dir_path, file_path)
135-
if os.path.isdir(true_path) and is_recurcive:
136-
if is_valid_filename(true_path):
137-
file_res += get_files_from_dir(true_path, is_recurcive)
138-
continue
139-
if is_valid_extension(true_path):
140-
file_res.append(true_path)
141-
return file_res
142-
143-
144112
def validate_response(response):
145113
if response.headers["Content-Type"] != "application/json":
146114
return {

0 commit comments

Comments
 (0)