Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
chenpangpang
transformers
Commits
e4fbf3e2
Commit
e4fbf3e2
authored
Dec 04, 2019
by
Julien Chaumond
Browse files
CLI for authenticated file sharing
parent
7edb51f3
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
413 additions
and
0 deletions
+413
-0
setup.py
setup.py
+10
-0
transformers-cli
transformers-cli
+23
-0
transformers/commands/__init__.py
transformers/commands/__init__.py
+12
-0
transformers/commands/user.py
transformers/commands/user.py
+122
-0
transformers/hf_api.py
transformers/hf_api.py
+152
-0
transformers/tests/hf_api_test.py
transformers/tests/hf_api_test.py
+94
-0
No files found.
setup.py
View file @
e4fbf3e2
...
@@ -36,6 +36,12 @@ To create the package for pypi.
...
@@ -36,6 +36,12 @@ To create the package for pypi.
from
io
import
open
from
io
import
open
from
setuptools
import
find_packages
,
setup
from
setuptools
import
find_packages
,
setup
extras
=
{
'serving'
:
[
'uvicorn'
,
'fastapi'
]
}
extras
[
'all'
]
=
[
package
for
package
in
extras
.
values
()]
setup
(
setup
(
name
=
"transformers"
,
name
=
"transformers"
,
version
=
"2.2.1"
,
version
=
"2.2.1"
,
...
@@ -61,6 +67,10 @@ setup(
...
@@ -61,6 +67,10 @@ setup(
"transformers=transformers.__main__:main"
,
"transformers=transformers.__main__:main"
,
]
]
},
},
extras_require
=
extras
,
scripts
=
[
'transformers-cli'
],
# python_requires='>=3.5.0',
# python_requires='>=3.5.0',
tests_require
=
[
'pytest'
],
tests_require
=
[
'pytest'
],
classifiers
=
[
classifiers
=
[
...
...
transformers-cli
0 → 100644
View file @
e4fbf3e2
#!/usr/bin/env python
from
argparse
import
ArgumentParser
from
transformers.commands.user
import
UserCommands
if
__name__
==
'__main__'
:
parser
=
ArgumentParser
(
description
=
'Transformers CLI tool'
,
usage
=
'transformers-cli <command> [<args>]'
)
commands_parser
=
parser
.
add_subparsers
(
help
=
'transformers-cli command helpers'
)
# Register commands
UserCommands
.
register_subcommand
(
commands_parser
)
# Let's go
args
=
parser
.
parse_args
()
if
not
hasattr
(
args
,
'func'
):
parser
.
print_help
()
exit
(
1
)
# Run
service
=
args
.
func
(
args
)
service
.
run
()
transformers/commands/__init__.py
0 → 100644
View file @
e4fbf3e2
from
abc
import
ABC
,
abstractmethod
from
argparse
import
ArgumentParser
class
BaseTransformersCLICommand
(
ABC
):
@
staticmethod
@
abstractmethod
def
register_subcommand
(
parser
:
ArgumentParser
):
raise
NotImplementedError
()
@
abstractmethod
def
run
(
self
):
raise
NotImplementedError
()
transformers/commands/user.py
0 → 100644
View file @
e4fbf3e2
from
argparse
import
ArgumentParser
from
getpass
import
getpass
import
os
from
transformers.commands
import
BaseTransformersCLICommand
from
transformers.hf_api
import
HfApi
,
HfFolder
,
HTTPError
class
UserCommands
(
BaseTransformersCLICommand
):
@
staticmethod
def
register_subcommand
(
parser
:
ArgumentParser
):
login_parser
=
parser
.
add_parser
(
'login'
)
login_parser
.
set_defaults
(
func
=
lambda
args
:
LoginCommand
(
args
))
whoami_parser
=
parser
.
add_parser
(
'whoami'
)
whoami_parser
.
set_defaults
(
func
=
lambda
args
:
WhoamiCommand
(
args
))
logout_parser
=
parser
.
add_parser
(
'logout'
)
logout_parser
.
set_defaults
(
func
=
lambda
args
:
LogoutCommand
(
args
))
list_parser
=
parser
.
add_parser
(
'ls'
)
list_parser
.
set_defaults
(
func
=
lambda
args
:
ListObjsCommand
(
args
))
# upload
upload_parser
=
parser
.
add_parser
(
'upload'
)
upload_parser
.
add_argument
(
'file'
,
type
=
str
,
help
=
'Local filepath of the file to upload.'
)
upload_parser
.
add_argument
(
'--filename'
,
type
=
str
,
default
=
None
,
help
=
'Optional: override object filename on S3.'
)
upload_parser
.
set_defaults
(
func
=
lambda
args
:
UploadCommand
(
args
))
class
BaseUserCommand
:
def
__init__
(
self
,
args
):
self
.
args
=
args
self
.
_api
=
HfApi
()
class
LoginCommand
(
BaseUserCommand
):
def
run
(
self
):
print
(
"""
_| _| _| _| _|_|_| _|_|_| _|_|_| _| _| _|_|_| _|_|_|_| _|_| _|_|_| _|_|_|_|
_| _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|
_|_|_|_| _| _| _| _|_| _| _|_| _| _| _| _| _| _|_| _|_|_| _|_|_|_| _| _|_|_|
_| _| _| _| _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|
_| _| _|_| _|_|_| _|_|_| _|_|_| _| _| _|_|_| _| _| _| _|_|_| _|_|_|_|
"""
)
username
=
input
(
"Username: "
)
password
=
getpass
()
try
:
token
=
self
.
_api
.
login
(
username
,
password
)
except
HTTPError
as
e
:
# probably invalid credentials, display error message.
print
(
e
)
exit
(
1
)
HfFolder
.
save_token
(
token
)
print
(
"Login successful"
)
print
(
"Your token:"
,
token
,
"
\n
"
)
print
(
"Your token has been saved to"
,
HfFolder
.
path_token
)
class
WhoamiCommand
(
BaseUserCommand
):
def
run
(
self
):
token
=
HfFolder
.
get_token
()
if
token
is
None
:
print
(
"Not logged in"
)
exit
()
try
:
user
=
self
.
_api
.
whoami
(
token
)
print
(
user
)
except
HTTPError
as
e
:
print
(
e
)
class
LogoutCommand
(
BaseUserCommand
):
def
run
(
self
):
token
=
HfFolder
.
get_token
()
if
token
is
None
:
print
(
"Not logged in"
)
exit
()
HfFolder
.
delete_token
()
self
.
_api
.
logout
(
token
)
print
(
"Successfully logged out."
)
class
ListObjsCommand
(
BaseUserCommand
):
def
run
(
self
):
token
=
HfFolder
.
get_token
()
if
token
is
None
:
print
(
"Not logged in"
)
exit
(
1
)
try
:
objs
=
self
.
_api
.
list_objs
(
token
)
except
HTTPError
as
e
:
print
(
e
)
exit
(
1
)
if
len
(
objs
)
==
0
:
print
(
"No shared file yet"
)
for
obj
in
objs
:
print
(
obj
.
filename
,
obj
.
LastModified
,
obj
.
ETag
,
obj
.
Size
)
class
UploadCommand
(
BaseUserCommand
):
def
run
(
self
):
token
=
HfFolder
.
get_token
()
if
token
is
None
:
print
(
"Not logged in"
)
exit
(
1
)
filepath
=
os
.
path
.
join
(
os
.
getcwd
(),
self
.
args
.
file
)
filename
=
self
.
args
.
filename
if
self
.
args
.
filename
is
not
None
else
os
.
path
.
basename
(
filepath
)
print
(
"About to upload file {} to S3 under filename {}"
.
format
(
filepath
,
filename
))
choice
=
input
(
"Proceed? [Y/n] "
).
lower
()
if
not
(
choice
==
""
or
choice
==
"y"
or
choice
==
"yes"
):
print
(
"Abort"
)
exit
()
print
(
"Uploading..."
)
access_url
=
self
.
_api
.
presign_and_upload
(
token
=
token
,
filename
=
filename
,
filepath
=
filepath
)
print
(
"Your file now lives at:"
)
print
(
access_url
)
transformers/hf_api.py
0 → 100644
View file @
e4fbf3e2
# coding=utf-8
# Copyright 2019-present, the HuggingFace Inc. team.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from
__future__
import
absolute_import
,
division
,
print_function
from
typing
import
List
,
NamedTuple
import
os
from
os.path
import
expanduser
import
requests
from
requests.exceptions
import
HTTPError
ENDPOINT
=
"https://huggingface.co"
class
S3Obj
:
def
__init__
(
self
,
filename
:
str
,
LastModified
:
str
,
ETag
:
str
,
Size
:
int
):
self
.
filename
=
filename
self
.
LastModified
=
LastModified
self
.
ETag
=
ETag
self
.
Size
=
Size
class
PresignedUrl
(
NamedTuple
):
write
:
str
access
:
str
class
HfApi
:
def
__init__
(
self
,
endpoint
=
None
):
self
.
endpoint
=
endpoint
if
endpoint
is
not
None
else
ENDPOINT
def
login
(
self
,
username
:
str
,
password
:
str
)
->
str
:
"""
Call HF API to sign in a user and get a token if credentials are valid.
Outputs:
token if credentials are valid
Throws:
requests.exceptions.HTTPError if credentials are invalid
"""
path
=
"{}/api/login"
.
format
(
self
.
endpoint
)
r
=
requests
.
post
(
path
,
json
=
{
"username"
:
username
,
"password"
:
password
})
r
.
raise_for_status
()
d
=
r
.
json
()
return
d
[
"token"
]
def
whoami
(
self
,
token
:
str
)
->
str
:
"""
Call HF API to know "whoami"
"""
path
=
"{}/api/whoami"
.
format
(
self
.
endpoint
)
r
=
requests
.
get
(
path
,
headers
=
{
"authorization"
:
"Bearer {}"
.
format
(
token
)})
r
.
raise_for_status
()
d
=
r
.
json
()
return
d
[
"user"
]
def
logout
(
self
,
token
:
str
):
"""
Call HF API to log out.
"""
path
=
"{}/api/logout"
.
format
(
self
.
endpoint
)
r
=
requests
.
post
(
path
,
headers
=
{
"authorization"
:
"Bearer {}"
.
format
(
token
)})
r
.
raise_for_status
()
def
presign
(
self
,
token
:
str
,
filename
:
str
)
->
PresignedUrl
:
"""
Call HF API to get a presigned url to upload `filename` to S3.
"""
path
=
"{}/api/presign"
.
format
(
self
.
endpoint
)
r
=
requests
.
post
(
path
,
headers
=
{
"authorization"
:
"Bearer {}"
.
format
(
token
)},
json
=
{
"filename"
:
filename
},
)
r
.
raise_for_status
()
d
=
r
.
json
()
return
PresignedUrl
(
**
d
)
def
presign_and_upload
(
self
,
token
:
str
,
filename
:
str
,
filepath
:
str
)
->
str
:
"""
Get a presigned url, then upload file to S3.
Outputs:
url: Read-only url for the stored file on S3.
"""
urls
=
self
.
presign
(
token
,
filename
=
filename
)
# streaming upload:
# https://2.python-requests.org/en/master/user/advanced/#streaming-uploads
with
open
(
filepath
,
"rb"
)
as
f
:
r
=
requests
.
put
(
urls
.
write
,
data
=
f
)
r
.
raise_for_status
()
return
urls
.
access
def
list_objs
(
self
,
token
:
str
)
->
List
[
S3Obj
]:
"""
Call HF API to list all stored files for user.
"""
path
=
"{}/api/listObjs"
.
format
(
self
.
endpoint
)
r
=
requests
.
get
(
path
,
headers
=
{
"authorization"
:
"Bearer {}"
.
format
(
token
)})
r
.
raise_for_status
()
d
=
r
.
json
()
return
[
S3Obj
(
**
x
)
for
x
in
d
]
class
HfFolder
:
path_token
=
expanduser
(
"~/.huggingface/token"
)
@
classmethod
def
save_token
(
cls
,
token
:
str
):
"""
Save token, creating folder as needed.
"""
os
.
makedirs
(
os
.
path
.
dirname
(
cls
.
path_token
),
exist_ok
=
True
)
with
open
(
cls
.
path_token
,
'w+'
)
as
f
:
f
.
write
(
token
)
@
classmethod
def
get_token
(
cls
):
"""
Get token or None if not existent.
"""
try
:
with
open
(
cls
.
path_token
,
'r'
)
as
f
:
return
f
.
read
()
except
FileNotFoundError
:
return
None
@
classmethod
def
delete_token
(
cls
):
"""
Delete token.
Do not fail if token does not exist.
"""
try
:
os
.
remove
(
cls
.
path_token
)
except
:
return
transformers/tests/hf_api_test.py
0 → 100644
View file @
e4fbf3e2
# coding=utf-8
# Copyright 2019-present, the HuggingFace Inc. team.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from
__future__
import
absolute_import
,
division
,
print_function
import
os
import
time
import
unittest
from
transformers.hf_api
import
HfApi
,
S3Obj
,
PresignedUrl
,
HfFolder
,
HTTPError
USER
=
"__DUMMY_TRANSFORMERS_USER__"
PASS
=
"__DUMMY_TRANSFORMERS_PASS__"
FILE_KEY
=
"Test-{}.txt"
.
format
(
int
(
time
.
time
()))
FILE_PATH
=
os
.
path
.
join
(
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
)),
"fixtures/input.txt"
)
class
HfApiCommonTest
(
unittest
.
TestCase
):
_api
=
HfApi
(
endpoint
=
"https://moon-staging.huggingface.co"
)
class
HfApiLoginTest
(
HfApiCommonTest
):
def
test_login_invalid
(
self
):
with
self
.
assertRaises
(
HTTPError
):
self
.
_api
.
login
(
username
=
USER
,
password
=
"fake"
)
def
test_login_valid
(
self
):
token
=
self
.
_api
.
login
(
username
=
USER
,
password
=
PASS
)
self
.
assertIsInstance
(
token
,
str
)
class
HfApiEndpointsTest
(
HfApiCommonTest
):
@
classmethod
def
setUpClass
(
cls
):
"""
Share this valid token in all tests below.
"""
cls
.
_token
=
cls
.
_api
.
login
(
username
=
USER
,
password
=
PASS
)
def
test_whoami
(
self
):
user
=
self
.
_api
.
whoami
(
token
=
self
.
_token
)
self
.
assertEqual
(
user
,
USER
)
def
test_presign
(
self
):
url
=
self
.
_api
.
presign
(
token
=
self
.
_token
,
filename
=
FILE_KEY
)
self
.
assertIsInstance
(
url
,
PresignedUrl
)
def
test_presign_and_upload
(
self
):
access_url
=
self
.
_api
.
presign_and_upload
(
token
=
self
.
_token
,
filename
=
FILE_KEY
,
filepath
=
FILE_PATH
)
self
.
assertIsInstance
(
access_url
,
str
)
def
test_list_objs
(
self
):
objs
=
self
.
_api
.
list_objs
(
token
=
self
.
_token
)
o
=
objs
[
-
1
]
self
.
assertIsInstance
(
o
,
S3Obj
)
class
HfFolderTest
(
unittest
.
TestCase
):
def
test_token_workflow
(
self
):
"""
Test the whole token save/get/delete workflow,
with the desired behavior with respect to non-existent tokens.
"""
token
=
"token-{}"
.
format
(
int
(
time
.
time
()))
HfFolder
.
save_token
(
token
)
self
.
assertEqual
(
HfFolder
.
get_token
(),
token
)
HfFolder
.
delete_token
()
HfFolder
.
delete_token
()
# ^^ not an error, we test that the
# second call does not fail.
self
.
assertEqual
(
HfFolder
.
get_token
(),
None
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment