Skip to content

Commit

Permalink
Merge branch 'dev' - release
Browse files Browse the repository at this point in the history
  • Loading branch information
sylvainHellin committed Sep 26, 2024
2 parents a8ed561 + 51d3cfe commit 3590286
Show file tree
Hide file tree
Showing 10 changed files with 398 additions and 135 deletions.
16 changes: 10 additions & 6 deletions Alasco/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@
This is an alpha version for submodules client, data_fetcher, data_transformer, document_downloader and utils.
Submodules document_uploader and data_updater are placeholders for now and will be implemented in the future.
Changelog zur Version 0.0.5:
- changed wrong implementation of document_downloader.download_change_orders.
- updated the verbose mode of the document_downloader sub-module.
- added method to download all documents from a project: document_downloader.batch_download_documents.
Changelog zur Version 0.0.7:
- moved get_documents methods to data_fetcher
- moved _prepaer_urls methods to utils
- add .drop_duplicates() after consolidating data in data_transformer
- added upload_document methods
TODO:
- N.A.
Author: sylvain hellin
Version: 0.0.6
Version: 0.0.8
"""

__version__ = "0.0.6"
__version__ = "0.0.8"
__author__ = "sylvain hellin"

# Import key classes or functions
Expand Down
18 changes: 14 additions & 4 deletions Alasco/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,22 @@
from alasco.utils import Utils

class Alasco:
def __init__(self, token, key, verbose = False, download_path: str | None = None):
def __init__(self, token: str, key: str, verbose = False, download_path: str | None = None):
"""
Initializes an instance of the Alasco class.
Parameters:
token (str): The token used for authentication.
key (str): The key used for authentication.
verbose (bool): A flag indicating whether to enable verbose mode. Defaults to False.
download_path (str | None): The path where documents will be downloaded. Defaults to None.
Returns:
None
"""

self.verbose = verbose

self.utils = Utils()
self.header = self.utils.headerParameters(token=token, key=key)

Expand All @@ -20,5 +32,3 @@ def __init__(self, token, key, verbose = False, download_path: str | None = None
self.data_updater = DataUpdater(self.header, verbose=verbose)
self.document_downloader = DocumentDownloader(header=self.header, verbose=verbose, download_path=download_path)
self.document_uploader = DocumentUploader(header=self.header, verbose=verbose)


128 changes: 117 additions & 11 deletions Alasco/data_fetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def __init__(self, header, verbose = False) -> None:
self.transform = DataTransformer()
self.utils = Utils()

def get_json(self, url, filters=[], verbose:bool = None):
def get_json(self, url: str, filters: list = [], verbose: bool = None) -> list | RuntimeError:
"""
Fetches JSON data from the given URL with optional filters.
Expand All @@ -57,43 +57,52 @@ def get_json(self, url, filters=[], verbose:bool = None):
Raises:
RuntimeError: If an HTTP error occurs during the API call.
"""
# Override the instance's verbose setting if provided
if verbose is not None:
self.verbose = verbose

# Apply filters to the URL if provided
if filters:
attribute, operation, filter = filters
if isinstance(filter, list):
filter = ",".join(filter)
url = url + f"?filter[{attribute}.{operation}]={filter}"

try:
# Make the API request
response = requests.get(url=url, headers=self.header)
response.raise_for_status() # Raise an HTTPError for bad responses
# Raise an HTTPError for bad responses
response.raise_for_status()
# Parse the JSON response
response_json = response.json()
list_responses_json = [response_json]

# Check for pagination and fetch additional pages if necessary
nextPageUrl = response_json.get("links", {}).get("next")
if nextPageUrl is not None:
nextPageUrl = response_json["links"]["next"]
nextPagesJSON = self.get_json(
url=nextPageUrl,
)
nextPagesJSON = self.get_json(url=nextPageUrl)
list_responses_json += nextPagesJSON

# Print verbose information if enabled
if self.verbose:
print("\n\n")
print("-"*50)
print(f"API call to url: {url[:100]}")
print("-" * 50)
print(f"API call to url: {url[:100]} ...")
print("\n\n")
# printJSON(response_json)

return list_responses_json
return list_responses_json

except requests.exceptions.HTTPError as http_err:
# Handle HTTP errors
print(f"API call failed to url: {url}")
raise RuntimeError(f"HTTP error occurred: {http_err}")

except Exception as err:
# Handle other errors
print(f"API call failed to url: {url}")
raise RuntimeError(f"Other error occurred: {err}")

def get_df(self, url, filters=None, chunk_size: int = 50):
"""
Fetches data from the API and converts it to a DataFrame.
Expand Down Expand Up @@ -474,4 +483,101 @@ def get_all_df(self, property_name: str | None = None, project_name: str | None
'change_orders': df_change_orders
}

return dfs
return dfs

def get_contract_documents(self, contract_ids: list) -> pd.DataFrame:
"""
Fetches and concatenates contract documents for the given contract IDs.
Args:
contract_ids (list): A list of contract IDs for which to fetch documents.
Returns:
pd.DataFrame: A DataFrame containing the concatenated contract documents.
Example:
>>> contract_ids = ["123", "456"]
>>> df_contract_documents = downloader.get_contract_documents(contract_ids)
>>> print(df_contract_documents)
"""

if self.verbose:
print(f"Getting contract documents for {len(contract_ids)} documents with ids : {contract_ids[:3]} ...")

urls = [self.utils._prepare_url_get_contract_documents(contract_id) for contract_id in contract_ids]
collection = []
for url in urls:
doc = self.get_df(url=url)
if not doc.empty:
collection.append(doc)

# if no document is uploaded, will return an empty DataFrame
if not collection:
return pd.DataFrame()
else:
df_contract_documents = pd.concat(collection)
return df_contract_documents

def get_change_order_documents(self, change_order_ids: list) -> pd.DataFrame:
"""
Fetches and concatenates change order documents for the given change order IDs.
Args:
change_order_ids (list): A list of change order IDs for which to fetch documents.
Returns:
pd.DataFrame: A DataFrame containing the concatenated change order documents.
Example:
>>> change_order_ids = ["789", "101"]
>>> df_change_order_documents = downloader.get_change_order_documents(change_order_ids)
>>> print(df_change_order_documents)
"""

if self.verbose:
print(f"Getting change order documents for {len(change_order_ids)} documents with ids : {change_order_ids[:3]} ...")
urls = [self.utils._prepare_url_get_change_order_documents(change_order_id) for change_order_id in change_order_ids]
collection = []
for url in urls:
doc = self.get_df(url=url)
if not doc.empty:
collection.append(doc)

# if no document is uploaded, will return an empty DataFrame
if not collection:
return pd.DataFrame()
else:
df_change_order_documents = pd.concat(collection)
return df_change_order_documents

def get_invoice_documents(self, invoice_ids: list) -> pd.DataFrame:
"""
Fetches and concatenates invoice documents for the given invoice IDs.
Args:
invoice_ids (list): A list of invoice IDs for which to fetch documents.
Returns:
pd.DataFrame: A DataFrame containing the concatenated invoice documents.
Example:
>>> invoice_ids = ["111", "222"]
>>> df_invoice_documents = downloader.get_invoice_documents(invoice_ids)
>>> print(df_invoice_documents)
"""
if self.verbose:
print(f"Getting invoice documents for {len(invoice_ids)} documents with ids : {invoice_ids[:3]} ...")

urls = [self.utils._prepare_url_get_invoice_documents(invoice_id) for invoice_id in invoice_ids]
collection = []
for url in urls:
doc = self.get_df(url=url)
if not doc.empty:
collection.append(doc)

# if no document is uploaded, will return an empty DataFrame
if not collection:
return pd.DataFrame()
else:
df_invoice_documents = pd.concat(collection)
return df_invoice_documents
8 changes: 7 additions & 1 deletion Alasco/data_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ def consolidate_core_DataFrames(self, dfs: dict[str, pd.DataFrame]) -> pd.DataFr
contractors_df = contractors_df.rename(columns={"id": "contractor_id", "name": "contractor_name"})
# Merge 'contractors' DataFrame with the main DataFrame on 'contractor_id'
df_core = pd.merge(df_core, contractors_df, on="contractor_id")
df_core = df_core.drop_duplicates()
df_core = df_core.reset_index(drop=True)

return df_core

Expand All @@ -190,7 +192,9 @@ def consolidate_invoices_DataFrame(self, df_core: pd.DataFrame, df_invoices: pd.
df_invoices_copy = df_invoices[required_columns].copy()
df_invoices_copy = df_invoices_copy.rename(columns={"id": "invoice_id", "contract": "contract_id", "external_identifier": "invoice_number"})
df_merged = pd.merge(df_core, df_invoices_copy, on="contract_id")

df_merged = df_merged.drop_duplicates()
df_merged = df_merged.reset_index(drop=True)

return df_merged

def consolidate_change_orders_DataFrame(self, df_core: pd.DataFrame, df_change_orders: pd.DataFrame) -> pd.DataFrame:
Expand Down Expand Up @@ -221,6 +225,8 @@ def consolidate_change_orders_DataFrame(self, df_core: pd.DataFrame, df_change_o
"identifier": "change_order_identifier"
})
df_merged = pd.merge(df_core, df_change_orders_copy, on="contract_id")
df_merged = df_merged.drop_duplicates()
df_merged = df_merged.reset_index(drop=True)

return df_merged

Expand Down
Loading

0 comments on commit 3590286

Please sign in to comment.