Skip to content

Commit

Permalink
👀 Merge dev -> master | Major Update 1.0.2 (#4)
Browse files Browse the repository at this point in the history
* Updated examples

* Added tests

* Reformatting

* Added proxy support, improved type hinting

* Ignore some files

* Major Update: 1.0.1

* Fixed setup.py

* Added CI

* Improved examples

* [ci] Bump python version to 3.9

* Fixed a RuntimeError bug maybe

* Added __version__ attribute to Translator

Co-authored-by: alsoGAMER <35269770+alsoGAMER@users.noreply.github.com>
  • Loading branch information
DavideGalilei and alsoGAMER authored May 15, 2021
1 parent 49e4e19 commit 90af4d8
Show file tree
Hide file tree
Showing 11 changed files with 249 additions and 68 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Gpytranslate CI

on:
push:
branches: [ dev ]

jobs:
tests:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
- name: Set up Python 3.9
uses: actions/setup-python@v1
with:
python-version: 3.9

- name: Install Gpytranslate
run: |
python setup.py install
- name: Run tests
run: |
cd tests
python -m unittest
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/venv/
/.idea/
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,16 @@ import asyncio


async def main():
tr = Translator()
translation = await tr("Ciao come stai? Io bene ahah.", targetlang='en')
language = await tr.detect("Ciao come stai? Io bene ahah.")
t = Translator()
translation = await t.translate("Ciao come stai? Io bene ahah.", targetlang="en")
language = await t.detect("Ciao come stai? Io bene ahah.")
print(f"Translation: {translation.text}\nDetected language: {language}")


if __name__ == "__main__":
asyncio.run(main())
```
**Note:** you could also check [tests](https://github.com/DavideGalilei/gpytranslate/blob/master/tests/) folder for extra examples

Output:
```
Expand Down
12 changes: 8 additions & 4 deletions examples/example.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from gpytranslate import Translator
import asyncio

from gpytranslate import Translator


async def main():
tr = Translator()
translation = await tr("🅱️🅱️ come va?", targetlang='en')
language = await tr.detect("Ciao come stai? Io bene ahah.")
t = Translator()
# Note: you can use proxies by passing proxies parameter to Translator
translation = await t.translate("Ciao come stai? Io bene ahah.", targetlang="en")
# By default, sourcelang is "auto"
language = await t.detect("Ciao come stai? Io bene ahah.")
# Returns language code "it"
print(f"Translation: {translation.text}\nDetected language: {language}")


Expand Down
15 changes: 15 additions & 0 deletions examples/https_proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import asyncio

from gpytranslate import Translator


async def main():
t = Translator(proxies={"https://": "https://{proxy_ip_here}"})
# Check out https://www.python-httpx.org/compatibility/#proxy-keys
translation = await t.translate("Ciao Mondo!", targetlang="en")
# Hello World!
print(f"Translation: {translation.text}")


if __name__ == "__main__":
asyncio.run(main())
19 changes: 19 additions & 0 deletions examples/socks5_proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import asyncio

from httpx_socks import AsyncProxyTransport

from gpytranslate import Translator


async def main():
t = Translator(
transport=AsyncProxyTransport.from_url("socks5://user:password@127.0.0.1:1080")
)
# Check out https://pypi.org/project/httpx-socks/
translation = await t.translate("Ciao Mondo!", targetlang="en")
# Hello World!
print(f"Translation: {translation.text}")


if __name__ == "__main__":
asyncio.run(main())
143 changes: 91 additions & 52 deletions gpytranslate/gpytranslate.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import json
from typing import Union
from collections.abc import Mapping
from typing import Union, Literal, Dict, List, Any, Final

import httpx

from .types import TranslatedObject


class Translator:
async def __call__(
__version__: Final[str] = "1.0.2"

def __init__(
self,
proxies: Union[httpx.Proxy, Dict[str, str]] = None,
url: str = "https://translate.googleapis.com/translate_a/single",
**options
):
self.url = url
self.client: httpx.AsyncClient = httpx.AsyncClient(proxies=proxies, **options)

async def translate(
self,
text: str,
text: Union[str, List[str], Dict[Any, str], Mapping],
sourcelang: str = "auto",
targetlang: str = "en",
client: str = "gtx",
dt: str = "t",
dt: Literal["t", "at", "rm", "bd", "md", "ss", "ex", "rw", "dj"] = "t",
dj: int = 1,
**extra
):
) -> Union[TranslatedObject, List[TranslatedObject], Dict[str, TranslatedObject]]:

"""
A function that translates text.
Expand All @@ -30,78 +42,103 @@ async def __call__(
targetlang: str = target language for translating "text" (default: "en").
client: str = Google Translate client platform I guess, it"s not well documented so I don"t really know (default: "gtx").
client: str = Google Translate client platform I guess, it's not well documented. (default: "gtx")
(Will return the raw API json if "client" is not "gtx")
dt: str = Specifies what to return in the API reply (default: "t").
(Will return the raw API json if "dt" is not "t".
t - Translation of source text;
at - Alternate translations;
rm - Transcription / transliteration of source and translated texts;
bd - Dictionary, in case source text is one word (you get translations with articles, reverse translations, etc.);
md - Definitions of source text, if it"s one word;
ss - Synonyms of source text, if it"s one word;
ex - Examples;
rw - See also list;
dj - Json response with names. (dj=1).)
t - Translation of source text;
at - Alternate translations;
rm - Transcription / transliteration of source and translated texts;
bd - Dictionary, in case source text is one word
(you get translations with articles, reverse translations, etc.);
md - Definitions of source text, if it's one word;
ss - Synonyms of source text, if it's one word;
ex - Examples;
rw - See also list;
dj - Json response with names. (dj=1).)
dj: int = Probably specifies dictionary output if true, else list (default: 1).
extra: dict = Extra params to be sent to api endpoint.
Returns:
JSON serialized object.
TranslatedObject or JSON serialized object.
"""

params = {
k: v
for k, v in {
"client": client,
"sl": (
sourcelang[0]
if isinstance(sourcelang, list) or isinstance(sourcelang, tuple)
else (
sourcelang["sl"] if isinstance(sourcelang, dict) else sourcelang
)
).lower(),
"tl": (
targetlang[0]
if isinstance(targetlang, list) or isinstance(targetlang, tuple)
else (
targetlang["tl"] if isinstance(targetlang, dict) else targetlang
)
).lower(),
"sl": sourcelang,
"tl": targetlang,
"dt": dt,
"q": text,
"ie": "utf-8",
"oe": "utf-8",
"dj": dj,
**extra,
}.items()
if v
}
async with httpx.AsyncClient() as c:
r = await c.post(
"https://translate.googleapis.com/translate_a/single?dj=1",
params=params,
async with self.client as c:
c: httpx.AsyncClient
raw: Union[Mapping, List] = (
(
await c.post(
self.url,
params={**params, "q": text},
)
).json()
if isinstance(text, str)
else (
{
k: (
await c.post(
self.url,
params={**params, "q": v},
)
).json()
for k, v in text.items()
}
if isinstance(text, Mapping)
else [
(
await c.post(
self.url,
params={**params, "q": elem},
)
).json()
for elem in text
]
)
)
await c.__aexit__()

r = json.loads(r.content.decode("utf-8"))
if client != "gtx" or dt != "t":
return raw

if not client == "gtx":
return r

_tmp = {
"raw": TranslatedObject(r),
"orig": " ".join([s["orig"] for s in r["sentences"] if "orig" in s]),
"text": " ".join([s["trans"] for s in r["sentences"] if "trans" in s]),
"orig_raw": [s["orig"] for s in r["sentences"] if "orig" in s],
"text_raw": [s["trans"] for s in r["sentences"] if "trans" in s],
"lang": r["src"],
if isinstance(text, str):
return self.parse(raw)
elif isinstance(text, Mapping):
return {k: self.parse(v) for k, v in raw.items()}
else:
return [self.parse(elem) for elem in raw]

@staticmethod
def parse(
raw: Union[dict, Mapping], translated: bool = True
) -> Union[TranslatedObject, Dict[str, Union[TranslatedObject, str, List[str]]]]:
x = {
"raw": TranslatedObject(raw),
"orig": " ".join(s["orig"] for s in raw["sentences"] if "orig" in s),
"text": " ".join(s["trans"] for s in raw["sentences"] if "trans" in s),
"orig_raw": [s["orig"] for s in raw["sentences"] if "orig" in s],
"text_raw": [s["trans"] for s in raw["sentences"] if "trans" in s],
"lang": raw["src"],
}

return TranslatedObject(_tmp)
if translated:
return TranslatedObject(x)
return x

async def detect(self, text: Union[str, list, dict]):
if isinstance(text, str):
Expand All @@ -112,3 +149,5 @@ async def detect(self, text: Union[str, list, dict]):
return {k: (await self(v)).lang for k, v in text.items()}
else:
raise ValueError("Language detection works only with str, list and dict")

__call__ = translate # Backwards compatibility
14 changes: 10 additions & 4 deletions gpytranslate/types/translated_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@

class TranslatedObject(dict):
def __getattr__(self: Union[str, dict, list], attr: str):
if isinstance(self, list): return [TranslatedObject(elem) for elem in self]
obj = TranslatedObject(dict.get(self, attr)) if isinstance(dict.get(self, attr), dict) else dict.get(self, attr)
return obj
if isinstance(self, list):
return [TranslatedObject(elem) for elem in self]
return (
TranslatedObject(dict.get(self, attr))
if isinstance(dict.get(self, attr), dict)
else dict.get(self, attr)
)

def __str__(self):
return json.dumps({k: v if len(str(v)) < 200 else "..." for k, v in self.items()}, indent=4)
return json.dumps(
{k: v if len(str(v)) < 200 else "..." for k, v in self.items()}, indent=4
)

__setattr__ = dict.__setitem__

Expand Down
10 changes: 5 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import setuptools

with open("README.md", "r") as fh:
with open("README.md", "r", encoding="utf8") as fh:
long_description = fh.read()

setuptools.setup(
name="gpytranslate",
version="0.3.1",
version="1.0.2",
author="Davide Galilei",
author_email="davidegalilei2018@gmail.com",
description="A Python3 library for translating text using Google Translate API.",
Expand All @@ -18,8 +18,8 @@
"License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
"Operating System :: OS Independent",
],
python_requires='>=3.6',
python_requires=">=3.6",
install_requires=[
'httpx',
]
"httpx",
],
)
17 changes: 17 additions & 0 deletions tests/test_detect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import unittest

from gpytranslate import Translator


class Test(unittest.IsolatedAsyncioTestCase):
async def test_detect(self):
translator = Translator()
language: str = await translator.detect(
text="Ciao Mondo."
)

self.assertEqual(
language,
"it",
"Translations are not equal.",
)
Loading

0 comments on commit 90af4d8

Please sign in to comment.