0

I'm using a python script based on this Mint API to pull personal finance info every hour.

To connect to Mint I do mint = mintapi.Mint(email, password) which opens a Chrom instance via selenium and logs into Mint, and creates an object of <class 'mintapi.api.Mint'>

To refresh info I just need to do mint.initiate_account_refresh().

But every time I run the script, it does the whole logging in thing again.

Can I store the mint object on disk somehow so I can skip that step and just do the account refresh?

Dan
  • 1,257
  • 2
  • 15
  • 31
  • Is it possible for you to leave the chrome window minimized and have the script loop over the mint.initiate_account_refresh() every hour instead of calling the script again every hour ? – Karan Shishoo Jan 29 '18 at 12:31
  • I am not familiar with Mint API but storing objects is easy with `pickle` module in python. Read it's documentation [here](https://docs.python.org/2/library/pickle.html). Beware `pickle` module has a well-known security flaw! – Ubdus Samad Jan 29 '18 at 12:34
  • I'm using this with bitbar (https://getbitbar.com) which puts the results in my menubar ... and as far as I know it has to actually run the script from scratch every x minutes. `pickle` seems to work to store the object, but then I keep getting `Session has expired` ... which I guess is an issue with the Mint API. [Sigh] – Dan Jan 29 '18 at 12:38
  • *But every time I run the script, it does the whole logging in thing again.*, are you running the `mint.initiate_account_refresh()` or the whole program at once to refresh? – Ubdus Samad Jan 29 '18 at 12:48

3 Answers3

1

To store objects in Python you can use the pickle module.

Let's say you have an object mint

import pickle
mint = Something.Somefunc()

with open('data.pickle','wb') as storage:
    pickle.dump(mint,storage)

The object will be saved as a sequence of binary bytes in a file named data.pickle.

To access it just use the pickle.load() function.

import pickle

with open('data.pickle','rb') as storage:
    mint = pickle.load(storage)

>>>mint
>>><class 'something' object>

NOTE:

Although it doesn't matter here but the pickle module has a flaw that it can execute some function objects while loading them from a file, so don't use it when reading pickle stored object from a third party source.

Ubdus Samad
  • 1,218
  • 1
  • 15
  • 27
1

Ah the wonders of open source.

Curious, I went and looked at the mintapi you linked, to see if there was anything obvious and simple I could do to recreate the object instance without the arduous setup.

It turns out there isn't, really. :(

Here is what is called when you instantiate the Mint object:

def __init__(self, email=None, password=None):
    if email and password:
        self.login_and_get_token(email, password)

As you can see, if you don't give it a truthy email and password, it doesn't do anything. (As a sidenote, it should really be checking is None, but whatever).

So, we can avoid having do go through the setup process nice and easily, but now we need to find out how to fake the setup process based on previous data.

Looking at .login_and_get_token(), we see the following:

def login_and_get_token(self, email, password):
    if self.token and self.driver:
        return

    self.driver = get_web_driver(email, password)
    self.token = self.get_token()

Nice and simple, again. If it already has a token, it's done, so it goes away. If not, it sets a driver, and sets .token by calling .get_token().

This makes the whole process really easy to override. Simply instantiate a Mint object with no arguments like so:

mint = mintapi.Mint()

Then set the .token on it:

mint.token = 'something magical'

Now you have an object that is in an almost ready state. The problem is that it relies on self.driver for basically every method call, including your .initiate_account_refresh():

def initiate_account_refresh(self):
    self.post(
        '{}/refreshFILogins.xevent'.format(MINT_ROOT_URL),
        data={'token': self.token},
        headers=JSON_HEADER)

...

def post(self, url, **kwargs):
    return self.driver.request('POST', url, **kwargs)

This looks like it's a simple POST that we could replace with a requests.post() call, but I suspect that seeing as it's doing it through the web browser that it's relying on some manner of cookies or session storage.

If you wanted to experiment, you could subclass Mint like so:

class HeadlessMint(Mint):

    def post(self, url, **kwargs):
        return requests.post(url, **kwargs)

But my guess is that there will be more issues with this that will surface over time.

The good news is that this mintapi project looks reasonably simple, and rewriting it to not rely on a web browser doesn't look like an unreasonable project for someone with a little experience, so keep that in your back pocket.


As for pickling, I don't believe that will work, for the same reason that I don't believe subclassing will - I think the existence of the browser is important. Even if you pickle your mint instance, it will be missing its browser when you try to load it.

The simplest solution might very well be to make the script a long-running one, and instead of running it every hour, you run it once, and it does what it needs to, then sleeps for an hour, before doing it again. This way, you'd log in once at the very beginning, and then it could keep that session for as long as it's running.

Adam Barnes
  • 2,922
  • 21
  • 27
  • One can use `time.sleep(3600)` to achieve what you proposed as it's not [CPU intensive.](https://stackoverflow.com/questions/17075788/python-is-time-sleepn-cpu-intensive) – Ubdus Samad Jan 29 '18 at 13:11
  • Yes that's what I suggested. Do you believe I should explicitly include the snippet in the answer? – Adam Barnes Jan 29 '18 at 13:12
  • Actually, it's not about snippet but when I read your answer I thought about CPU usage but as it's **not intensive** I thought I'd better mention that fact! – Ubdus Samad Jan 29 '18 at 13:15
0

Use a library pickle to save and load object

SAVE

import pickle

mint = mintapi.Mint(email, password)
with open('mint .pkl', 'wb') as output:
    pickle.dump(mint , output, pickle.HIGHEST_PROTOCOL)

LOAD

import pickle

with open('mint.pkl', 'rb') as input:
    mint= pickle.load(input)

mint.initiate_account_refresh()