nagarmayank commited on
Commit
5fb65b7
·
1 Parent(s): 73b50ad

docker changes

Browse files
Files changed (5) hide show
  1. Dockerfile +23 -6
  2. app.py +128 -117
  3. requirements.txt +4 -1
  4. start.sh +36 -0
  5. test.py +16 -0
Dockerfile CHANGED
@@ -1,16 +1,33 @@
1
  # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
  # you will also find guides on how best to write your Dockerfile
3
 
4
- FROM python:3.9
 
 
 
 
5
 
6
  RUN useradd -m -u 1000 user
7
  USER user
8
- ENV PATH="/home/user/.local/bin:$PATH"
 
9
 
10
- WORKDIR /app
 
 
11
 
12
- COPY --chown=user ./requirements.txt requirements.txt
13
  RUN pip install --no-cache-dir --upgrade -r requirements.txt
 
 
 
 
 
 
 
 
 
 
14
 
15
- COPY --chown=user . /app
16
- CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
1
  # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
  # you will also find guides on how best to write your Dockerfile
3
 
4
+ FROM python:3.11-slim
5
+
6
+ RUN apt-get update && apt-get install -y curl && \
7
+ curl -fsSL https://ollama.ai/install.sh | sh && \
8
+ apt-get clean && rm -rf /var/lib/apt/lists/*
9
 
10
  RUN useradd -m -u 1000 user
11
  USER user
12
+ ENV HOME=/home/user \
13
+ PATH="/home/user/.local/bin:$PATH"
14
 
15
+ # Création des répertoires nécessaires
16
+ RUN mkdir -p $HOME/docker_ollama $HOME/logs
17
+ WORKDIR $HOME/docker_ollama
18
 
19
+ COPY --chown=user requirements.txt .
20
  RUN pip install --no-cache-dir --upgrade -r requirements.txt
21
+ COPY --chown=user . .
22
+ RUN chmod +x start.sh
23
+ EXPOSE 7860 11434
24
+
25
+ CMD ["./start.sh"]
26
+
27
+ # WORKDIR /app
28
+
29
+ # COPY --chown=user ./requirements.txt requirements.txt
30
+ # RUN pip install --no-cache-dir --upgrade -r requirements.txt
31
 
32
+ # COPY --chown=user . /app
33
+ # CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py CHANGED
@@ -1,125 +1,136 @@
1
  from fastapi import FastAPI
2
  import uvicorn
3
-
4
- app = FastAPI()
5
-
6
- @app.post("/write_message")
7
- def write_message(data: dict):
8
- print(data)
9
- return None
10
-
11
- # if __name__ == "__main__":
12
- # uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")
13
-
14
- # GOOGLESHEETS_CREDENTIALS = os.getenv("GOOGLESHEETS_CREDENTIALS")
15
-
16
- # from langchain_core.prompts import ChatPromptTemplate
17
- # from langchain_groq import ChatGroq
18
- # from pydantic import BaseModel, Field
19
- # import pygsheets
20
- # import json
21
- # from langgraph.graph import StateGraph, END
22
- # from typing import TypedDict, Annotated
23
- # import operator
24
- # from langchain_core.messages import SystemMessage, HumanMessage, AnyMessage
25
- # from langchain_ollama import ChatOllama
26
- # import groq
27
- # from tqdm import tqdm
28
- # from opik.integrations.langchain import OpikTracer
29
- # from langgraph.pregel import RetryPolicy
30
-
31
- # MODEL = "llama3-70b-8192" # "meta-llama/llama-4-scout-17b-16e-instruct" #
32
-
33
- # class TransactionParser(BaseModel):
34
- # """This Pydantic class is used to parse the transaction message."""
35
-
36
- # amount: str = Field(description="The amount of the transaction in decimal format. If the transaction is a credit or a reversal, then include negative sign. DO not insert currency.", example="123.45")
37
- # dr_or_cr: str = Field(description="Identify if the transaction was debit (spent) or credit (received). Strictly choose one of the values - Debit or Credit")
38
- # receiver: str = Field(description="The recipient of the transaction. Identify the Merchant Name from the value.")
39
- # category: str = Field(description="The category of the transaction. The category of the transaction is linked to the Merchant Name. Strictly choose from one the of values - Shopping,EMI,Education,Miscellaneous,Grocery,Utility,House Help,Travel,Transport")
40
- # transaction_date: str = Field(description="The date of the transaction in yyyy-mm-dd format. If the year is not provided then use current year.")
41
- # transaction_origin: str = Field(description="The origin of the transaction. Provide the card or account number as well.")
42
-
43
- # class AgentState(TypedDict):
44
- # messages: Annotated[list[AnyMessage], operator.add]
45
-
46
- # class Agent:
47
- # def __init__(self, model, system=""):
48
- # self.system = system
49
- # graph = StateGraph(AgentState)
50
- # graph.add_node("classify_txn_type", self.classify_txn_type, retry=RetryPolicy(retry_on=[groq.BadRequestError], max_attempts=2))
51
- # graph.add_node("parse_message", self.parse_message, retry=RetryPolicy(retry_on=[groq.BadRequestError], max_attempts=2))
52
- # graph.add_node("write_message", self.write_message)
53
- # graph.add_conditional_edges(
54
- # "classify_txn_type",
55
- # self.check_txn_and_decide,
56
- # {True: "parse_message", False: END}
57
- # )
58
- # graph.add_edge("parse_message", "write_message")
59
- # graph.add_edge("write_message", END)
60
- # graph.set_entry_point("classify_txn_type")
61
- # self.graph = graph.compile()
62
- # self.model = model
63
-
64
- # def classify_txn_type(self, state: AgentState) -> AgentState:
65
- # messages = state["messages"]
66
- # if self.system:
67
- # messages = [SystemMessage(content=self.system)] + messages
68
-
69
- # message = self.model.invoke(messages)
70
- # return {"messages": [message]}
71
 
72
- # def parse_message(self, state: AgentState) -> AgentState:
73
- # message = state["messages"][0].content
74
- # system = """
75
- # You are a helpful assistant skilled at parsing transaction messages and providing structured responses.
76
- # """
77
- # human = "Categorize the transaction message and provide the output in a structed format: {topic}"
78
-
79
- # prompt = ChatPromptTemplate.from_messages([("system", system), ("human", human)])
80
- # chain = prompt | model.with_structured_output(TransactionParser)
81
- # result = chain.invoke({"topic": message})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
- # return {"messages": [result]}
 
 
 
 
84
 
85
- # def write_message(self, state: AgentState) -> AgentState:
86
- # result = state["messages"][-1]
87
- # client = pygsheets.authorize(service_account_file="serviceaccount.json")
88
- # worksheet = client.open_by_url("https://docs.google.com/spreadsheets/d/1t4bOM4fULdaVsjDDnqEG1g8Zey6M00UuFhTZC03_4xo")
89
- # wk = worksheet[0]
90
- # # Get number of rows in the worksheet
91
- # df = wk.get_as_df(start='A1', end='G999')
92
- # nrows = df.shape[0]
93
- # wk.update_value(f'A{nrows+2}', result.amount)
94
- # wk.update_value(f'B{nrows+2}', result.dr_or_cr)
95
- # wk.update_value(f'C{nrows+2}', result.receiver)
96
- # wk.update_value(f'D{nrows+2}', result.category)
97
- # wk.update_value(f'E{nrows+2}', result.transaction_date)
98
- # wk.update_value(f'F{nrows+2}', result.transaction_origin)
99
- # wk.update_value(f'G{nrows+2}', state["messages"][0].content)
100
- # return {"messages": ["Transaction Completed"]}
101
-
102
- # def check_txn_and_decide(self, state: AgentState):
103
- # try:
104
- # result = json.loads(state['messages'][-1].content)['classification']
105
- # except json.JSONDecodeError:
106
- # result = state['messages'][-1].content.strip()
107
-
108
- # return result == "Transaction"
109
 
110
- # prompt = """You are a smart assistant adept at classifying different messages. \
111
- # You will be penalized heavily for incorrect classification. \
112
- # Your task is to classify the message into one of the following categories: \
113
- # Transaction, OTP, Promotional, Scheduled. \
114
- # Output the classification in a structured format like below. \
115
- # {"classification": "OTP"} \
116
- # """
117
-
118
- # model = ChatGroq(temperature=1, groq_api_key=GROQ_API_KEY, model_name=MODEL)
119
- # # model = ChatOllama(model="gemma3:4b", temperature=1, callback=OpikTracer())
120
 
121
- # transaction_bot = Agent(model, system=prompt)
 
 
122
 
123
- # for message in tqdm(messages):
124
- # message = [HumanMessage(content=message)]
125
- # result = transaction_bot.graph.invoke({"messages": message}, config={"callbacks" : [OpikTracer(graph=transaction_bot.graph.get_graph(xray=True))]})
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from fastapi import FastAPI
2
  import uvicorn
3
+ from langchain_core.prompts import ChatPromptTemplate
4
+ from pydantic import BaseModel, Field
5
+ import pygsheets
6
+ import json
7
+ from langgraph.graph import StateGraph, END
8
+ from typing import TypedDict, Annotated
9
+ import operator
10
+ from langchain_core.messages import SystemMessage, HumanMessage, AnyMessage
11
+ from langchain_ollama import ChatOllama
12
+ from langgraph.pregel import RetryPolicy
13
+ import json
14
+ import pandas as pd
15
+ from google.oauth2 import service_account
16
+ import os
17
+
18
+ # GOOGLESHEETS_CREDENTIALS = json.dumps({"type": "service_account","project_id": "spendstracker-457904","private_key_id": "91bb3866226ab9f6734aa3fcc861faf17897d79d","private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCy2v9irZ+/f+hF\nGUW/ikqnQUGMTapLulH3I6+vdDoQBtFa3tNc5BcK991Qbbk8NG2Iv6Ni7H/dY4jv\nWlBr//WTcThKSa23HzjztDxFtCbw5BsaNcXzt3HlAb/ldZNjZkt8CJdzLDpDuiLc\n9PbJEUxNZZYZcGrY7rhse1Z5QCn/gX1GlTCqMORPoEcrCmjklabjht4le9SoUaCA\n9qABNt8SXaTPw1l4HxxxtMEQE466COxWn89lEDh+DqDjU+oXVwXUFscDUSrKtDFt\nNNjIy0yMlKTtLwtMaHMK6DdT+hqAjs4xaOFF2YQFDPLuXAX1PaLEi7J/ircese1s\nlQAcxrThAgMBAAECggEAAIvnQl2t8oeZRdbHLgfl5P9vzBYzqkISPItVHtff0os2\nygyKwEqpIF00BaokKgEuAYA2Z1e6J0rF0RdpTf8s+KucpKt3dqsHgUUgdwUPJmbI\nB2s5JN6/YBgChli239Og8OrUzaMJtYnE0ACGnYQqQ8VG8WJ0zR9jnF8/GyU9S5zQ\nGlUO04y6ftetQCaFaVIxxy9B/pOtSuIZi4qoJjGuOebMAcYWusrAWID3uewhsZJW\nqCQUMLyADK5ujEAybo7dpohph6YYwV/6bmCl/8rfnBiXn8NsuFfE9m/B7W9TBXku\n0AB/EF8rnjI2kEMXgUogLUj1DBm4pkVEpce+vd2LzQKBgQD7UJg35cLFCcjylumP\n/5f7d4V25ryWpfHMYSs9zefkRDnVrmwumanHy+IOSLgOMUC5dIcJOjQPutaUnKN5\nH4oW2q4K6Pmp4vlDeTNp9bHaZMokuQPVhMZvsWWcpFe3v6nMHA3tvshiWKiPVApo\nHaaXeNETateCpwGBn+9Eu5VAtQKBgQC2MJbQ/85DndSnk3YZ+aEtp2fEJABJwfPl\nM6vRnKy5gHKMPk1BZLb+B4osr+OA6eZEzp6VL2kOpI+lIZ9E20uMWOOzZUk+du6i\nEpXimHSoNPS56pksfS3tWoHRuyM9nm+rPEV0LPWotyenEfhtGE1CYZwnef1aMNfW\nKhui8uH6/QKBgQCLvoMGAhLNseU1T8lMMxn10L48IY2YT2om9Zkv4sEhYvat5TFu\nsC+CU9K9kp4V9jlBZpR4Aw9T99a+CGO2RF1q2+qPUoERgI6OgGSgdOiSwhzNUrvZ\nDN2y2ffgpFnKaR8nyinMm5udZCNGn7qxrlsmOx43J9/yXJ8vzxkjJROXSQKBgGsW\n4G91DU7dZPQjT1YxTzZAolO+PZUdNjlRR/trtnNLNwmMTWjUxGNJF0TxFi7eTYXA\nVaKnPX9n5y9PNgkJRbz3OtBmBsl6qwYFGqkYp+l/RyJI7UQjSG2tt4UKFMrRaB4k\nzUZebv9+uQYRIA8wK6mLKnhh0jPDZfrywU/kqEQZAoGAbywkV/FjFeAhmqY/yLQo\nho3E5T5jEc45kDadsK2OHVnCRb0tWh6VTdr93XQ9z5mygub7Wu16jp/FnHtA7f+E\no743SU8sYHxPGbVavlM/iRzbzhaF8o5lTHqqzLqrPvMOBbUpP/cRXugoxt7m7o4v\nvcRikUgNG7O8ipxnN28L8dg=\n-----END PRIVATE KEY-----\n","client_email": "spendstracker@spendstracker-457904.iam.gserviceaccount.com","client_id": "117285083911847446748","auth_uri": "https://accounts.google.com/o/oauth2/auth","token_uri": "https://oauth2.googleapis.com/token","auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/spendstracker%40spendstracker-457904.iam.gserviceaccount.com","universe_domain": "googleapis.com"})
19
+ sheet_url = "https://docs.google.com/spreadsheets/d/1t4bOM4fULdaVsjDDnqEG1g8Zey6M00UuFhTZC03_4xo/edit?gid=0#gid=0"
20
+ GOOGLESHEETS_CREDENTIALS = os.getenv("GOOGLESHEETS_CREDENTIALS")
21
+
22
+ class TransactionParser(BaseModel):
23
+ """This Pydantic class is used to parse the transaction message."""
24
+
25
+ amount: str = Field(description="The amount of the transaction in decimal format. If the transaction is a credit or a reversal, then include negative sign. DO not insert currency.", example="123.45")
26
+ dr_or_cr: str = Field(description="Identify if the transaction was debit (spent) or credit (received). Strictly choose one of the values - Debit or Credit")
27
+ receiver: str = Field(description="The recipient of the transaction. Identify the Merchant Name from the value.")
28
+ category: str = Field(description="The category of the transaction. The category of the transaction is linked to the Merchant Name. Strictly choose from one the of values - Shopping,EMI,Education,Miscellaneous,Grocery,Utility,House Help,Travel,Transport")
29
+ transaction_date: str = Field(description="The date of the transaction in yyyy-mm-dd format. If the year is not provided then use current year.")
30
+ transaction_origin: str = Field(description="The origin of the transaction. Provide the card or account number as well.")
31
+
32
+ class AgentState(TypedDict):
33
+ messages: Annotated[list[AnyMessage], operator.add]
34
+
35
+ class Agent:
36
+ def __init__(self, model, system=""):
37
+ self.system = system
38
+ graph = StateGraph(AgentState)
39
+ graph.add_node("classify_txn_type", self.classify_txn_type)
40
+ graph.add_node("parse_message", self.parse_message)
41
+ graph.add_node("write_message", self.write_message)
42
+ graph.add_conditional_edges(
43
+ "classify_txn_type",
44
+ self.check_txn_and_decide,
45
+ {True: "parse_message", False: END}
46
+ )
47
+ graph.add_edge("parse_message", "write_message")
48
+ graph.add_edge("write_message", END)
49
+ graph.set_entry_point("classify_txn_type")
50
+ self.graph = graph.compile()
51
+ self.model = model
52
+
53
+ def classify_txn_type(self, state: AgentState) -> AgentState:
54
+ print("Classifying transaction type...")
55
+ messages = state["messages"]
56
+ if self.system:
57
+ messages = [SystemMessage(content=self.system)] + messages
58
+
59
+ message = self.model.invoke(messages)
60
+ print("Classifying transaction type completed.")
61
+ return {"messages": [message]}
 
 
 
 
 
 
 
 
 
62
 
63
+ def parse_message(self, state: AgentState) -> AgentState:
64
+ print("Parsing transaction message...")
65
+ message = state["messages"][0]#.content
66
+ system = """
67
+ You are a helpful assistant skilled at parsing transaction messages and providing structured responses.
68
+ """
69
+ human = "Categorize the transaction message and provide the output in a structed format: {topic}"
70
+
71
+ prompt = ChatPromptTemplate.from_messages([("system", system), ("human", human)])
72
+ chain = prompt | self.model.with_structured_output(TransactionParser)
73
+ result = chain.invoke({"topic": message})
74
+ print("Parsing transaction message completed.")
75
+ return {"messages": [result]}
76
+
77
+ def write_message(self, state: AgentState) -> AgentState:
78
+ print("Writing transaction message to Google Sheets...")
79
+ result = state["messages"][-1]
80
+ SCOPES = ('https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/drive')
81
+ service_account_info = json.loads(GOOGLESHEETS_CREDENTIALS)
82
+ my_credentials = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES)
83
+ client = pygsheets.authorize(custom_credentials=my_credentials)
84
+ # client = pygsheets.authorize(service_account_json=GOOGLESHEETS_CREDENTIALS)
85
+ # client = pygsheets.authorize(service_account_file="serviceaccount.json")
86
+ worksheet = client.open_by_url(sheet_url)
87
+ wk = worksheet[0]
88
+ # Get number of rows in the worksheet
89
+ df = wk.get_as_df(start='A1', end='G999')
90
+ nrows = df.shape[0]
91
+ wk.update_value(f'A{nrows+2}', result.amount)
92
+ wk.update_value(f'B{nrows+2}', result.dr_or_cr)
93
+ wk.update_value(f'C{nrows+2}', result.receiver)
94
+ wk.update_value(f'D{nrows+2}', result.category)
95
+ wk.update_value(f'E{nrows+2}', result.transaction_date)
96
+ wk.update_value(f'F{nrows+2}', result.transaction_origin)
97
+ wk.update_value(f'G{nrows+2}', state["messages"][0])
98
+ print("Writing transaction message to Google Sheets completed.")
99
+ return {"messages": ["Transaction Completed"]}
100
 
101
+ def check_txn_and_decide(self, state: AgentState):
102
+ try:
103
+ result = json.loads(state['messages'][-1].content)['classification']
104
+ except json.JSONDecodeError:
105
+ result = state['messages'][-1].content.strip()
106
 
107
+ return result == "Transaction"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
+ prompt = """You are a smart assistant adept at classifying different messages. \
110
+ You will be penalized heavily for incorrect classification. \
111
+ Your task is to classify the message into one of the following categories: \
112
+ Transaction, OTP, Promotional, Scheduled. \
113
+ Output the classification in a structured format like below. \
114
+ {"classification": "OTP"} \
115
+ """
116
+ app = FastAPI()
 
 
117
 
118
+ @app.get("/")
119
+ def greetings():
120
+ return {"message": "Hello, this is a transaction bot. Please send a POST request to /write_message with the transaction data."}
121
 
122
+ @app.post("/write_message")
123
+ def write_message(data: dict):
124
+ message = data['message']
125
+ model = ChatOllama(model="gemma3:4b", temperature=1)
126
+ transaction_bot = Agent(model, system=prompt)
127
+ result = transaction_bot.graph.invoke({"messages": [message]})
128
+ return {"message": "Transaction completed successfully"}
129
+
130
+ @app.get("/ask")
131
+ def ask(prompt: str):
132
+ model = ChatOllama(model="gemma3:4b", temperature=1)
133
+ return model.invoke(prompt)
134
+
135
+ if __name__ == "__main__":
136
+ uvicorn.run(app, host="0.0.0.0", port=7860, log_level="info")
requirements.txt CHANGED
@@ -1,2 +1,5 @@
1
  fastapi
2
- uvicorn[standard]
 
 
 
 
1
  fastapi
2
+ uvicorn[standard]
3
+ langchain-ollama
4
+ langgraph
5
+ pygsheets
start.sh ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ #MisterAI/Docker_Ollama
3
+ #start.sh_01
4
+ #https://huggingface.co/spaces/MisterAI/Docker_Ollama/
5
+
6
+
7
+ # Set environment variables for optimization HFSpace Free 2CPU - 16GbRAM
8
+ export OMP_NUM_THREADS=2
9
+ export MKL_NUM_THREADS=2
10
+ export CUDA_VISIBLE_DEVICES=-1
11
+
12
+ # Start Ollama in the background
13
+ ollama serve &
14
+
15
+ # Pull the model if not already present
16
+ echo "Mistral 7b will be download"
17
+ if ! ollama list | grep -q "gemma3:4b"; then
18
+ ollama pull gemma3:4b
19
+ fi
20
+
21
+ # Wait for Ollama to start up
22
+ max_attempts=30
23
+ attempt=0
24
+ while ! curl -s http://localhost:11434/api/tags >/dev/null; do
25
+ sleep 1
26
+ attempt=$((attempt + 1))
27
+ if [ $attempt -eq $max_attempts ]; then
28
+ echo "Ollama failed to start within 30 seconds. Exiting."
29
+ exit 1
30
+ fi
31
+ done
32
+
33
+ echo "Ollama is Ready - Mistral 7b is Loaded"
34
+
35
+ # Démarrer Application
36
+ python app.py
test.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from google.oauth2 import service_account
3
+ import pygsheets
4
+
5
+ GOOGLESHEETS_CREDENTIALS = json.dumps({"type": "service_account","project_id": "spendstracker-457904","private_key_id": "91bb3866226ab9f6734aa3fcc861faf17897d79d","private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCy2v9irZ+/f+hF\nGUW/ikqnQUGMTapLulH3I6+vdDoQBtFa3tNc5BcK991Qbbk8NG2Iv6Ni7H/dY4jv\nWlBr//WTcThKSa23HzjztDxFtCbw5BsaNcXzt3HlAb/ldZNjZkt8CJdzLDpDuiLc\n9PbJEUxNZZYZcGrY7rhse1Z5QCn/gX1GlTCqMORPoEcrCmjklabjht4le9SoUaCA\n9qABNt8SXaTPw1l4HxxxtMEQE466COxWn89lEDh+DqDjU+oXVwXUFscDUSrKtDFt\nNNjIy0yMlKTtLwtMaHMK6DdT+hqAjs4xaOFF2YQFDPLuXAX1PaLEi7J/ircese1s\nlQAcxrThAgMBAAECggEAAIvnQl2t8oeZRdbHLgfl5P9vzBYzqkISPItVHtff0os2\nygyKwEqpIF00BaokKgEuAYA2Z1e6J0rF0RdpTf8s+KucpKt3dqsHgUUgdwUPJmbI\nB2s5JN6/YBgChli239Og8OrUzaMJtYnE0ACGnYQqQ8VG8WJ0zR9jnF8/GyU9S5zQ\nGlUO04y6ftetQCaFaVIxxy9B/pOtSuIZi4qoJjGuOebMAcYWusrAWID3uewhsZJW\nqCQUMLyADK5ujEAybo7dpohph6YYwV/6bmCl/8rfnBiXn8NsuFfE9m/B7W9TBXku\n0AB/EF8rnjI2kEMXgUogLUj1DBm4pkVEpce+vd2LzQKBgQD7UJg35cLFCcjylumP\n/5f7d4V25ryWpfHMYSs9zefkRDnVrmwumanHy+IOSLgOMUC5dIcJOjQPutaUnKN5\nH4oW2q4K6Pmp4vlDeTNp9bHaZMokuQPVhMZvsWWcpFe3v6nMHA3tvshiWKiPVApo\nHaaXeNETateCpwGBn+9Eu5VAtQKBgQC2MJbQ/85DndSnk3YZ+aEtp2fEJABJwfPl\nM6vRnKy5gHKMPk1BZLb+B4osr+OA6eZEzp6VL2kOpI+lIZ9E20uMWOOzZUk+du6i\nEpXimHSoNPS56pksfS3tWoHRuyM9nm+rPEV0LPWotyenEfhtGE1CYZwnef1aMNfW\nKhui8uH6/QKBgQCLvoMGAhLNseU1T8lMMxn10L48IY2YT2om9Zkv4sEhYvat5TFu\nsC+CU9K9kp4V9jlBZpR4Aw9T99a+CGO2RF1q2+qPUoERgI6OgGSgdOiSwhzNUrvZ\nDN2y2ffgpFnKaR8nyinMm5udZCNGn7qxrlsmOx43J9/yXJ8vzxkjJROXSQKBgGsW\n4G91DU7dZPQjT1YxTzZAolO+PZUdNjlRR/trtnNLNwmMTWjUxGNJF0TxFi7eTYXA\nVaKnPX9n5y9PNgkJRbz3OtBmBsl6qwYFGqkYp+l/RyJI7UQjSG2tt4UKFMrRaB4k\nzUZebv9+uQYRIA8wK6mLKnhh0jPDZfrywU/kqEQZAoGAbywkV/FjFeAhmqY/yLQo\nho3E5T5jEc45kDadsK2OHVnCRb0tWh6VTdr93XQ9z5mygub7Wu16jp/FnHtA7f+E\no743SU8sYHxPGbVavlM/iRzbzhaF8o5lTHqqzLqrPvMOBbUpP/cRXugoxt7m7o4v\nvcRikUgNG7O8ipxnN28L8dg=\n-----END PRIVATE KEY-----\n","client_email": "spendstracker@spendstracker-457904.iam.gserviceaccount.com","client_id": "117285083911847446748","auth_uri": "https://accounts.google.com/o/oauth2/auth","token_uri": "https://oauth2.googleapis.com/token","auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/spendstracker%40spendstracker-457904.iam.gserviceaccount.com","universe_domain": "googleapis.com"})
6
+ sheet_url = "https://docs.google.com/spreadsheets/d/1t4bOM4fULdaVsjDDnqEG1g8Zey6M00UuFhTZC03_4xo/edit?gid=0#gid=0"
7
+
8
+ SCOPES = ('https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/drive')
9
+ service_account_info = json.loads(GOOGLESHEETS_CREDENTIALS)
10
+ my_credentials = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES)
11
+ client = pygsheets.authorize(custom_credentials=my_credentials)
12
+
13
+ print("Writing transaction message to Google Sheets...")
14
+ worksheet = client.open_by_url(sheet_url)
15
+
16
+ print(worksheet)