How to recover an Algorand Wallet with Python AlgoSDK? - python

Although the official algosdk (Python SDK for Algorand) documentation suggests that a wallet can be recovered by simply invoking the following function (link):
create_wallet(name, pswd, driver_name='sqlite', master_deriv_key=None)
with the fourth argument:
master_deriv_key (str, optional) – if recovering a wallet, include
wallet recovery does not work in my code and leads to experience an exception as well. Also the official Algorand documentation shows how to use the abovementioned function for recovering a wallet (link):
# recover the wallet by passing mdk when creating a wallet
new_wallet = kcl.create_wallet("MyTestWallet2", "testpassword", master_deriv_key=mdk)
Below, you can watch at my code, a very simple snippet that I coded to make some tests with Algorand SDK:
from algosdk import kmd
from algosdk import mnemonic
kmd_clt = kmd.KMDClient('855d39510cce40caf11de4c941b37632d1529ec970156214528a33a0ae8473b4', 'http://127.0.0.1:6969')
if kmd_clt:
kmd_wlt_mdk = None
kmd_wlt_list = kmd_clt.list_wallets()
for kmd_wlt in kmd_wlt_list:
kmd_name = kmd_wlt['name']
kmd_id = kmd_wlt['id']
if kmd_name == 'wallet_name':
kmd_wlt_hdl = kmd_clt.init_wallet_handle(kmd_id, 'wallet_password')
if kmd_wlt_hdl:
kmd_wlt_mdk = kmd_clt.export_master_derivation_key(kmd_wlt_hdl, 'wallet_password')
break
if kmd_wlt_mdk:
kmd_wlt = kmd_clt.create_wallet('wallet_name', 'wallet_password', master_deriv_key=kmd_wlt_mdk)
kmd_wlt_hdl = kmd_clt.init_wallet_handle(kmd_wlt['id'], 'wallet_password')
acc_addr_list = kmd_clt.list_keys(kmd_wlt_hdl)
for acc_addr in acc_addr_list:
account_address = acc_addr
print(account_address)
account_key = kmd_clt.export_key(kmd_wlt_hdl, 'wallet_password', account_address)
print(account_key)
account_mnemonic = mnemonic.from_private_key(account_key)
print(account_mnemonic)
Below, you can watch at the Traceback and the error message returned at run-time:
Traceback (most recent call last):
File "/home/emiliano/anaconda3/lib/python3.7/site-packages/algosdk/kmd.py", line 63, in kmd_request
resp = urlopen(req)
File "/home/emiliano/anaconda3/lib/python3.7/urllib/request.py", line 222, in urlopen
return opener.open(url, data, timeout)
File "/home/emiliano/anaconda3/lib/python3.7/urllib/request.py", line 531, in open
response = meth(req, response)
File "/home/emiliano/anaconda3/lib/python3.7/urllib/request.py", line 641, in http_response
'http', request, response, code, msg, hdrs)
File "/home/emiliano/anaconda3/lib/python3.7/urllib/request.py", line 569, in error
return self._call_chain(*args)
File "/home/emiliano/anaconda3/lib/python3.7/urllib/request.py", line 503, in _call_chain
result = func(*args)
File "/home/emiliano/anaconda3/lib/python3.7/urllib/request.py", line 649, in http_error_default
raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 400: Bad Request
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/emiliano/anaconda3/lib/python3.7/site-packages/algosdk/kmd.py", line 67, in kmd_request
raise error.KMDHTTPError(json.loads(e)["message"])
algosdk.error.KMDHTTPError: wallet with same name already exists
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "algorand_test.py", line 49, in <module>
kmd_wlt = kmd_clt.create_wallet('emiliano', 'emiliano', master_deriv_key=kmd_wlt_mdk)
File "/home/emiliano/anaconda3/lib/python3.7/site-packages/algosdk/kmd.py", line 118, in create_wallet
return self.kmd_request("POST", req, data=query)["wallet"]
File "/home/emiliano/anaconda3/lib/python3.7/site-packages/algosdk/kmd.py", line 69, in kmd_request
raise error.KMDHTTPError(e)
algosdk.error.KMDHTTPError: {
"error": true,
"message": "wallet with same name already exists"
}
It seems to be clear how the create_wallet function is the sinner of such behavior which leads to get back the error "wallet with same name already exists". Internals of Algorand SDK are very simple, APIs are wrappers for REST methods. Function create_wallet does simply (link):
def create_wallet(self, name, pswd, driver_name="sqlite",
master_deriv_key=None):
"""
Create a new wallet.
Args:
name (str): wallet name
pswd (str): wallet password
driver_name (str, optional): name of the driver
master_deriv_key (str, optional): if recovering a wallet, include
Returns:
dict: dictionary containing wallet information
"""
req = "/wallet"
query = {
"wallet_driver_name": driver_name,
"wallet_name": name,
"wallet_password": pswd
}
if master_deriv_key:
query["master_derivation_key"] = master_deriv_key
return self.kmd_request("POST", req, data=query)["wallet"]
I'm sure the master derivation key passed in input is correct since I've alredy checked it with the goal command from console.
Has anyone else experienced this type of problem before?

To summarize, Algorand documentation of REST APIs doesn't suggest explicitly to use the Master-Derivation-Key to retrieve a Wallet when making a POST /v1/wallet (link). Conversely, Algorand documentation of Python SDK suggests that the Master-Derivation-Key can be passed to the create_wallet function, which then makes the HTTP POST stated before, to recover an existing Wallet (link).
As explained within my question above, create_wallet fails to recover the Wallet because the underlying POST /v1/wallet fails. At the suggestion of #Arty, this has been proved as follow:
curl -X POST -H "X-KMD-API-Token: <kmd-token>" -H "Content-Type: application/json" -d '{"wallet_driver_name": "sqlite", "wallet_name": <wallet-name>, "wallet_password": <wallet-password>, "master_derivation_key": <master-derivation-key>}' <kmd-address-and-port>/v1/wallet
which returned
{ "error": true, "message": "wallet with same name already exists" }
I notified this problem to the Algorand support and I'm currently waiting for a reply. Anyhow, in order to give some sense to the question's title, I want to share another possible solution to recover a Wallet still by relying on the Python SDK:
from algosdk import kmd
from algosdk import wallet
from algosdk import mnemonic
kmd_clt = kmd.KMDClient(<kmd-token>, <kmd-address-and-port>)
if kmd_clt:
kmd_wlt_mdk = None
kmd_wlt_list = kmd_clt.list_wallets()
for kmd_wlt in kmd_wlt_list:
kmd_name = kmd_wlt['name']
kmd_id = kmd_wlt['id']
if kmd_name == <wallet-name>:
kmd_wlt_hdl = kmd_clt.init_wallet_handle(kmd_id, <wallet-password>)
if kmd_wlt_hdl:
kmd_wlt_mdk = kmd_clt.export_master_derivation_key(kmd_wlt_hdl, <wallet-password>)
break
if kmd_wlt_mdk:
wlt = wallet.Wallet(<wallet-name>, <wallet-password>, kmd_clt, mdk=kmd_wlt_mdk)
if wlt:
acc_addr_list = wlt.list_keys()
for acc_addr in acc_addr_list:
account_address = acc_addr
print(account_address)
account_key = wlt.export_key(acc_addr)
print(account_key)
account_mnemonic = mnemonic.from_private_key(account_key)
print(account_mnemonic)
I hope it will be useful to someone else in the future.

Related

Status parameter not working when using python blogger api

I'm trying to use google-api-python-client 1.12.5 with Service account auth under Python 3.8. It seems to me that the when specifying the status parameter, Google responds with a 404 HTTP code. I can't figure out why. I also looked in the docs but I can't relate anything to this error.
I have pasted my code. The error is happening in the third call.
This is the code:
from google.oauth2 import service_account
from googleapiclient.discovery import build
SCOPES = ['https://www.googleapis.com/auth/blogger']
SERVICE_ACCOUNT_FILE = 'new_service_account.json'
BLOG_ID = '<your_blog_id>'
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
service = build('blogger', 'v3', credentials=credentials)
p = service.posts()
# FIRST
promise = p.list(blogId=BLOG_ID)
result = promise.execute()
# SECOND
promise = p.list(blogId=BLOG_ID, orderBy='UPDATED')
result = promise.execute()
#THIRD
promise = p.list(blogId=BLOG_ID, orderBy='UPDATED', status='DRAFT')
result = promise.execute() # <===== ERROR HAPPENS HERE!!!!
service.close()
And this is the traceback:
Traceback (most recent call last):
File "/home/madtyn/.local/share/JetBrains/Toolbox/apps/PyCharm-P/ch-0/202.7660.27/plugins/python/helpers/pydev/pydevd.py", line 1448, in _exec
pydev_imports.execfile(file, globals, locals) # execute the script
File "/home/madtyn/.local/share/JetBrains/Toolbox/apps/PyCharm-P/ch-0/202.7660.27/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/home/madtyn/PycharmProjects/blogger/main.py", line 24, in <module>
result = promise.execute()
File "/home/madtyn/venvs/blogger/lib/python3.8/site-packages/googleapiclient/_helpers.py", line 134, in positional_wrapper
return wrapped(*args, **kwargs)
File "/home/madtyn/venvs/blogger/lib/python3.8/site-packages/googleapiclient/http.py", line 915, in execute
raise HttpError(resp, content, uri=self.uri)
googleapiclient.errors.HttpError: <HttpError 404 when requesting https://blogger.googleapis.com/v3/blogs/<blog_id>/posts?orderBy=UPDATED&status=DRAFT&alt=json returned "Not Found">
python-BaseException
I can reproduce this issue... Adding status=DRAFT will return 404 but any other filter is working...
Tried with service account and your code: 404
Tried with API Key like this result = requests.get('https://blogger.googleapis.com/v3/blogs/<blog_id>/posts?status=DRAFT&orderBy=UPDATED&alt=json&key=<api_key>'): 404
Extracted "access_token" from service account (credentials.token after a call): result = requests.get('https://blogger.googleapis.com/v3/blogs/<blog_id>/posts?status=DRAFT&orderBy=UPDATED&alt=json&access_token=<extracted_service_account_token>'): 404
But very strangely if I use access_token given by "Try this API" here : https://developers.google.com/blogger/docs/3.0/reference/posts/list?apix_params={"blogId"%3A"blog_id"%2C"orderBy"%3A"UPDATED"%2C"status"%3A["DRAFT"]%2C"alt"%3A"json"} it's works !
Used that token with requests give me my blog post in draft status...
Just copy/paste raw Authorization header inside that script:
import requests
blog_id = '<blog_id>'
headers = {
'Authorization' : 'Bearer <replace_here>'
}
# Using only Authorization header
result = requests.get(
'https://blogger.googleapis.com/v3/blogs/%s/posts?status=DRAFT&orderBy=UPDATED&alt=json' % (blog_id),
headers=headers
)
print(result)
# This should print DRAFT if you have at least one draft post
print(result.json()['items'][0]['status'])
# Using "access_token" param constructed with Authorization header splited to have only token
result = requests.get('https://blogger.googleapis.com/v3/blogs/%s/posts?status=DRAFT&orderBy=UPDATED&alt=json&access_token=%s' % (blog_id, headers['Authorization'][len('Bearer '):]))
print(result)
# This should print DRAFT if you have at least one draft post
print(result.json()['items'][0]['status'])
Results I have currently:
The bug doesn't seem to come from the library but rather from the token rights...However I also used the console normally to generate accesses like you.
To conclude I think it's either a bug or it's voluntary from Google... I don't know how long the "Try this API" token is valid but it is currently the only way I found to get the draft articles... Maybe you can try to open a bug ticket but I don't know specifically where it is possible to do that.

During setup of django with sendgrid getting error 401

I have added sendgrid to my django app
Followed the simple steps from here https://github.com/elbuo8/sendgrid-django
generated acount and and copied the api at sengrid site
Added code to my view
sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY'))
from_email = Email("commerce#gmail.com")
to_email = Email("lopa#gmail.com")
subject = "Sending with SendGrid is Fun"
content = Content("text/plain", "and easy to do anywhere, even with Python")
mail = Mail(from_email, subject, to_email, content)
response = sg.client.mail.send.post(request_body=mail.get())
messages.add_message(request, messages.SUCCESS, str(payment.id) + response.status_code + response.body + response.headers)
And getting Error
HTTP Error 401: Unauthorized
What could be the problem?
> Traceback Traceback: File
> "C:\Users\PAPA\DEV\rent_unit\rent_unit_venv\lib\site-packages\django\core\handlers\base.py"
> in get_response
> 132. response = wrapped_callback(request, *callback_args, **callback_kwargs) File "C:\Users\PAPA\DEV\rent_unit\rent_unit_venv\lib\site-packages\django\contrib\auth\decorators.py"
> in _wrapped_view
> 22. return view_func(request, *args, **kwargs) File "C:\Users\PAPA\DEV\rent_unit\src\payment\views.py" in payment_new
> 251. response = sg.client.mail.send.post(request_body=mail.get()) File
> "C:\Users\PAPA\DEV\rent_unit\rent_unit_venv\lib\site-packages\python_http_client\client.py"
> in http_request
> 204. return Response(self._make_request(opener, request)) File
> "C:\Users\PAPA\DEV\rent_unit\rent_unit_venv\lib\site-packages\python_http_client\client.py"
> in _make_request
> 138. return opener.open(request) File "c:\python27\Lib\urllib2.py" in open
> 435. response = meth(req, response) File "c:\python27\Lib\urllib2.py" in http_response
> 548. 'http', request, response, code, msg, hdrs) File "c:\python27\Lib\urllib2.py" in error
> 473. return self._call_chain(*args) File "c:\python27\Lib\urllib2.py" in _call_chain
> 407. result = func(*args) File "c:\python27\Lib\urllib2.py" in http_error_default
> 556. raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
>
> Exception Type: HTTPError at
> /payment/new/28/http://127.0.0.1:8000/lease/payment_details/28/
> Exception Value: HTTP Error 401: Unauthorized
The problem is exactly what it says; You're not authorized. Most likely you're API key isn't set.
The instructions you added to your question show the SENDGRID_API_KEY being added to the Django settings.py, while you're code shows you fetching from an environment variable.
Environment Variable Approach
If you're using the environment variable approach, make sure you've set an environment variable called SENDGRID_API_KEY. You can check it it's set by opening a python console and typing:
import os
os.environ.get('SENDGRID_API_KEY')
If a key isn't printed out, that means it's missing. Each OS has a different way of setting environment variables permanently, so I'm not going to list them all out here.
Settings.py Approach
If you're going with the Django settings.py approach, simply replace:
sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY'))
with
from django.conf import settings
sg = sendgrid.SendGridAPIClient(apikey=settings.SENDGRID_API_KEY)
Environment Variable in Settings.py Approach
Finally, since settings.py is an executable python file, you can also perform an environment variable import there. This has the benefit of being adjustable from a system level or a Heroku console, but still uses settings.py.
# inside settings.py
import os
SENDGRID_API_KEY = os.environ.get('SENDGRID_API_KEY')
Just update your code to use the one you want if you prefer not to use environment variable or settings
SENDGRID_API_KEY = '*sendgrid***api*'
sg = sendgrid.SendGridAPIClient(apikey=SENDGRID_API_KEY )
I have faced same error, I use this cli to solve: e.g(my_api_key)
set SENDGRID_API_KEY=SG.FHWXmV68Td2cEYJQrPjDdQ.I1VEkE2CBg7--r7QfS-AzhfSU5
( !!! do not use ' ' like : 'SG.FHWXmV68Td2cEYJQrPjDdQ.I1VEkE2CBg7--r7QfS-AzhfSU5' ).
also.
Do not replace SENDGRID_API_KEY line with your API_KEY :
sg =SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
. And you should refer this document :
https://github.com/sendgrid/sendgrid-python
I think this problem is because the window user,see this and you will understand . Acc to this doc :

Foursquare authentication - 'No handlers could be found for logger "foursquare"'

I'm starting with the Foursquare API, in Python, and I'm not sure why I can't authenticate.
Following the tutorial, so far I have this piece of code:
import foursquare
client = foursquare.Foursquare(client_id=myid, client_secret=mysecret,
redirect_uri='http://fondu.com/oauth/authorize')
auth_uri = client.oauth.auth_url()
access_token = client.oauth.get_token('XX_CODE_RETURNED_IN_REDIRECT_XX')
client.set_access_token(access_token)
client.venues.explore(params={'near': 'New York, NY', 'time' : date})
I've created an app here:
https://foursquare.com/developers/apps
and I'm using both:
Client id
Client secret
shown in the page.
However, when running this code, I get:
No handlers could be found for logger "foursquare"
Traceback (most recent call last):
File "noiseInference.py", line 270, in <module>
getFoursquareCheckIns(date)
File "noiseInference.py", line 156, in getFoursquareCheckIns
access_token = client.oauth.get_token('XX_CODE_RETURNED_IN_REDIRECT_XX')
File "/Library/Python/2.7/site-packages/foursquare/__init__.py", line 134, in get_token
response = _request_with_retry(url)
File "/Library/Python/2.7/site-packages/foursquare/__init__.py", line 707, in _request_with_retry
return _process_request_with_httplib2(url, headers, data)
File "/Library/Python/2.7/site-packages/foursquare/__init__.py", line 730, in _process_request_with_httplib2
return _check_response(data)
File "/Library/Python/2.7/site-packages/foursquare/__init__.py", line 763, in _check_response
raise FoursquareException(errmsg)
foursquare.FoursquareException: Response format invalid, missing meta property. data: {u'error': u'invalid_client'}
Not sure what's the problem.
The handler message is just complaining you didn't set up a logger under for the foursquare namespace.
Your real error is the message at the end of the stack trace:
foursquare.FoursquareException:
Response format invalid, missing meta property. data: {u'error': u'invalid_client'}
The message indicates that your client's credentials are incorrect. The credentials aren't fully checked until you attempt to use the client for a privileged action like client.set_access_token, so the most likely culprit here is to look at what you pass for client_secret when constructing the Foursquare client object.
client_id is probably not the problem because you'd have to go through the URL-OAuth flow in order to get the access_token you use.

Twitter API returned a 401 (Unauthorized) when trying to get users's friends on twitter

I'm using the following code which for a while worked like charm, but recently in 9 of the 10 attempts I'm getting error from twitter api
from twython import Twython, TwythonError
from pymongo import Connection
import pika, time
connection = Connection("host")
connection.admin.authenticate('admin', 'passwords')
db = connection['tweets']
consumer_key="key"
consumer_secret="secret"
access_token="token"
access_token_secret="secret_token"
t = Twython(app_key=consumer_key,
app_secret=consumer_secret,
oauth_token=access_token,
oauth_token_secret=access_token_secret
)
Q ='download_friends'
r_connection = pika.BlockingConnection(pika.ConnectionParameters(
'host'))
channel = r_connection.channel()
channel.queue_declare(queue= Q,
arguments={'x-message-ttl':600000})
while 1:
method_frame, header_frame, id = channel.basic_get(Q)
if db.friends.find_one({"_id": id}) != None:
print "Panic, user already exists"
continue
try:
r = t.get_friends_list(user_id = id)
except Exception as ex:
print ex, id
else:
print id
r['_id'] = id
r['time'] = time.time()
db.friends.save(r)
time.sleep(121)
Twitter API returned a 401 (Unauthorized), An error occurred
processing your request.
Here is a stacktrace
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python2.7/dist-packages/twython/endpoints.py", line 290, in get_friends_list
return self.get('friends/list', params=params)
File "/usr/local/lib/python2.7/dist-packages/twython/api.py", line 230, in get
return self.request(endpoint, params=params, version=version)
File "/usr/local/lib/python2.7/dist-packages/twython/api.py", line 224, in request
content = self._request(url, method=method, params=params, api_call=url)
File "/usr/local/lib/python2.7/dist-packages/twython/api.py", line 194, in _request
retry_after=response.headers.get('retry-after'))
twython.exceptions.TwythonAuthError: Twitter API returned a 401 (Unauthorized), An error occurred processing your request.
I the remaining 1 attempt I'm actually getting a user's friends.
I've googled a bit and corrected time on the machine (as they say one of the most common causes of this error), yet error still persists.
Also there is minimal not working example:
twitter = Twython('API key', 'API secret', oauth_version=2)
ACCESS_TOKEN = twitter.obtain_access_token()
print ACCESS_TOKEN
t = Twython('API key', access_token=ACCESS_TOKEN)
r = t.get_friends_list(user_id = 'id')
It is able to get ACCESS_TOKEN but nothing more.
Could it be, because user_id has private profile and you just can't access it's friends?
I also thought, it's problem in rate limit, but according to twitter api it returns 429 error, if limit reached
UPDATE:
I haven't tested, but found similar situation: https://dev.twitter.com/discussions/4389

XML parser syntax error

So I'm working with a block of code which communicates with the Flickr API.
I'm getting a 'syntax error' in xml.parsers.expat.ExpatError (below). Now I can't figure out how it'd be a syntax error in a Python module.
I saw another similar question on SO regarding the Wikipedia API which seemed to return HTML intead of XML. Flickr API returns XML; and I'm also getting the same error when there shouldn't be a response from Flickr (such as flickr.galleries.addPhoto)
CODE:
def _dopost(method, auth=False, **params):
#uncomment to check you aren't killing the flickr server
#print "***** do post %s" % method
params = _prepare_params(params)
url = '%s%s/%s' % (HOST, API, _get_auth_url_suffix(method, auth, params))
payload = 'api_key=%s&method=%s&%s'% \
(API_KEY, method, urlencode(params))
#another useful debug print statement
#print url
#print payload
return _get_data(minidom.parse(urlopen(url, payload)))
TRACEBACK:
Traceback (most recent call last):
File "TESTING.py", line 30, in <module>
flickr.galleries_create('test_title', 'test_descriptionn goes here.')
File "/home/vlad/Documents/Computers/Programming/LEARNING/curatr/flickr.py", line 1006, in galleries_create
primary_photo_id=primary_photo_id)
File "/home/vlad/Documents/Computers/Programming/LEARNING/curatr/flickr.py", line 1066, in _dopost
return _get_data(minidom.parse(urlopen(url, payload)))
File "/usr/lib/python2.6/xml/dom/minidom.py", line 1918, in parse
return expatbuilder.parse(file)
File "/usr/lib/python2.6/xml/dom/expatbuilder.py", line 928, in parse
result = builder.parseFile(file)
File "/usr/lib/python2.6/xml/dom/expatbuilder.py", line 207, in parseFile
parser.Parse(buffer, 0)
xml.parsers.expat.ExpatError: syntax error: line 1, column 62
(Code from http://code.google.com/p/flickrpy/ under New BSD licence)
UPDATE:
print urlopen(url, payload) == <addinfourl at 43340936 whose fp = <socket._fileobject object at 0x29400d0>>
Doing a urlopen(url, payload).read() returns HTML which is hard to read in a terminal :P but I managed to make out a 'You are not signed in.'
The strange part is that Flickr shouldn't return anything here, or if permissions are a problem, it should return a 99: User not logged in / Insufficient permissions error as it does with the GET function (which I'd expect would be in valid XML).
I'm signed in to Flickr (in the browser) and the program is properly authenticated with delete permissions (dangerous, but I wanted to avoid permission problems.)
SyntaxError normally means an error in Python syntax, but I think here that expatbuilder is overloading it to mean an XML syntax error. Put a try:except block around it, and print out the contents of payload and to work out what's wrong with the first line of it.
My guess would be that flickr is rejecting your request for some reason and giving back a plain-text error message, which has an invalid xml character at column 62, but it could be any number of things. You probably want to check the http status code before parsing it.
Also, it's a bit strange this method is called _dopost but you seem to actually be sending an http GET. Perhaps that's why it's failing.
This seems to fix my problem:
url = '%s%s/?api_key=%s&method=%s&%s'% \
(HOST, API, API_KEY, method, _get_auth_url_suffix(method, auth, params))
payload = '%s' % (urlencode(params))
It seems that the API key and method had to be in the URL not in the payload. (Or maybe only one needed to be there, but anyways, it works :-)

Categories

Resources