Skip to content

Commit

Permalink
Merge pull request #16 from Krutyi-4el/dev2
Browse files Browse the repository at this point in the history
v0.8.1
  • Loading branch information
solaluset authored Aug 11, 2023
2 parents c25d9e5 + b13b0d8 commit 7579c9d
Show file tree
Hide file tree
Showing 30 changed files with 578 additions and 123 deletions.
13 changes: 13 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[run]
command_line = -m i18n.tests
branch = true
# omit temporary file
omit = memoize.en.py

[report]
# in case omitting doesn't quite work
ignore_errors = true
exclude_lines =
^.*# pragma: no cover
# overloaded methods are only for documentation purposes
^[\s]*@(typing\.)?overload$
3 changes: 3 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[flake8]
max-line-length = 100
extend-exclude = build,dev_env
25 changes: 19 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,26 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install requirements
run: pip install -r requirements.txt
- name: Install package
run: pip install .
- name: Run tests
run: coverage run -m i18n.tests
run: python dev-helper.py run-tests
env:
SKIP_VENV: 1
- name: Upload coverage
# PyPy data isn't reliable because it changes trace function
if: "!startsWith(matrix.python-version, 'pypy')"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: coveralls --service=github
run: pip install coveralls==3.3.1 && coveralls --service=github

run-checks:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Run checks
run: python dev-helper.py run-checks
env:
SKIP_VENV: 1
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ dist/
.python-version
.idea/
.pytest_cache/
.coverage
dev_env
8 changes: 8 additions & 0 deletions .mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[mypy]
files = .
exclude = build
check_untyped_defs = true
warn_unused_ignores = true
warn_redundant_casts = true
disallow_incomplete_defs = true
# no_implicit_reexport = true
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ In the above example, the translation key is `foo.hi` and not just `hi`. This is
To remove `{namespace}` from filename format please change the `filename_format` configuration.

i18n.set('filename_format', '{locale}.{format}')

#### Directory namespaces
If your files are in subfolders, the foldernames are also used as namespaces, so for example if your translation root path is `/path/to/translations` and you have the file `/path/to/translations/my/app/name/foo.en.yml`, the translation namespace for the file will be `my.app.name` and the file keys will therefore be accessible from `my.app.name.foo.my_key`.

Expand Down Expand Up @@ -104,7 +104,7 @@ You can set a fallback which will be used when the key is not found in the defau
i18n.set('fallback', 'en')
i18n.add_translation('foo', 'bar', locale='en')
i18n.t('foo') # bar

### Skip locale from root
Sometimes i18n structure file came from another project or not contains root element with locale eg. `en` name.

Expand Down
196 changes: 196 additions & 0 deletions dev-helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import os
import sys
import stat
import shlex
import sysconfig
import subprocess
from contextlib import contextmanager


VENV_DIR = "dev_env"
REQUIREMENTS = "dev-requirements.txt"


def get_root():
return subprocess.check_output(
("git", "rev-parse", "--show-toplevel"),
universal_newlines=True,
).strip()


os.chdir(get_root())
sys.argv[0] = os.path.basename(sys.argv[0])


def ensure_venv(packages):
venv_dir = os.path.join(os.getcwd(), VENV_DIR)
if sys.prefix == venv_dir:
# already in venv
return
if os.getenv("SKIP_VENV") != "1":
if not os.path.isdir(venv_dir):
print("Creating virtual environment...")
subprocess.run((sys.executable, "-m", "venv", venv_dir), check=True)
python_path = os.path.join(
sysconfig.get_path("scripts", "venv", {"base": venv_dir}),
"python",
)
pip_install(python_path, packages)
sys.exit(
subprocess.run(
(python_path, sys.argv[0], *sys.argv[1:]),
).returncode,
)
else:
pip_install(sys.executable, packages)


def pip_install(python, args):
subprocess.run((python, "-m", "pip", "install", *args), check=True)


def get_packages(profile=None):
result = []
collect = profile is None
with open(REQUIREMENTS) as f:
for line in f:
if line.startswith("#"):
collect = profile is None or line.lstrip("#").strip() == profile
continue
if collect:
result.append(line)
return result


@contextmanager
def stash_unstaged():
if subprocess.check_output(("git", "ls-files", "--exclude-standard", "-om")) == b"":
# nothing to stash
yield
return
subprocess.run(
("git", "stash", "--keep-index", "--include-untracked", "-m", "temp"),
check=True,
)
file_list = subprocess.check_output(
("git", "diff", "stash", "--name-only"),
universal_newlines=True,
).splitlines()
diffs = [
subprocess.check_output(("git", "diff", "--binary", "-R", "stash", file))
for file in file_list
]
try:
yield
finally:
# restore all files from stash
for file, diff in zip(file_list, diffs):
if subprocess.run(("git", "apply"), input=diff).returncode != 0:
# revert changes made by script
subprocess.run(("git", "checkout", "--", file))
subprocess.run(("git", "apply"), input=diff, check=True)
# pop untracked files
if subprocess.run(("git", "stash", "pop")).returncode != 0:
subprocess.run(("git", "stash", "drop"), check=True)


def _format_file(file):
success = True
if not os.path.exists(file):
# file was removed
return True
with open(file) as f:
try:
data = f.readlines()
except UnicodeDecodeError:
return True
for i, line in enumerate(data):
if line.endswith((" \n", "\t\n")):
success = False
data[i] = line.rstrip(" \t\n") + "\n"
if data and not data[-1].endswith("\n"):
success = False
data[-1] += "\n"
if not success:
with open(file, "w") as f:
f.writelines(data)
return success


def format_files():
files = subprocess.check_output(
("git", "diff", "--staged", "--name-only")
if "-a" not in sys.argv
else ("git", "ls-files"),
universal_newlines=True,
).splitlines()
results = [_format_file(f) for f in files]
return all(results)


def run_flake8():
return subprocess.run((sys.executable, "-m", "flake8")).returncode == 0


def run_mypy():
return subprocess.run((sys.executable, "-m", "mypy")).returncode == 0


def check_coverage():
from runpy import run_module
from coverage import Coverage # type: ignore[import]

cov = Coverage()
cov.start()

run_module("i18n.tests", run_name="__main__")

cov.stop()
return (
cov.report() == 100.0
# always succeed on GA
or os.getenv("SKIP_VENV") == "1"
)


def install():
file = os.path.join(os.getcwd(), ".git", "hooks", "pre-commit")
with open(file, "w") as f:
print(shlex.quote(sys.executable), shlex.quote(sys.argv[0]), file=f)
mode = os.stat(file).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
os.chmod(file, mode)


FUNCS = {
"tests": (check_coverage,),
"checks": (format_files, run_flake8, run_mypy),
}


def main():
args = sys.argv[1:]
if not args or args[0].startswith(("run-", "-")):
if args and args[0].startswith("run-"):
_, _, profile = args[0].partition("-")
funcs = FUNCS[profile]
pkgs = get_packages(profile)
else:
funcs = sum(FUNCS.values(), start=())
pkgs = get_packages()
ensure_venv(pkgs)
with stash_unstaged():
for func in funcs:
print("running", func.__name__)
if not func():
return 1, func.__name__ + " failed"
elif args[0] == "install":
install()
else:
return 1, "Unknown command: " + args[0]
return 0, "ok"


if __name__ == "__main__":
code, text = main()
print(text)
sys.exit(code)
9 changes: 9 additions & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# tests
.[YAML]
coverage==6.2; python_version<'3.7'
coverage==6.5.0; python_version>='3.7'
# checks
flake8==6.1.0
mypy==1.5.0
types-PyYAML==6.0.12.11
types-setuptools==68.0.0.3
24 changes: 24 additions & 0 deletions i18n/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
__all__ = (
"Loader",
"register_loader",
"init_default_loaders",
"load_config",
"load_everything",
"unload_everything",
"reload_everything",

"I18nException",
"I18nFileLoadError",
"I18nInvalidStaticRef",
"I18nInvalidFormat",

"t",
"add_translation",
"add_function",

"set",
"get",

"load_path",
)

from typing import List

from .resource_loader import (
Expand Down
4 changes: 3 additions & 1 deletion i18n/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
'argument_delimiter': '|'
}

def set(key: str, value: Any):

def set(key: str, value: Any) -> None:
if key not in settings:
raise KeyError("Invalid setting: {0}".format(key))
elif key == 'load_path':
Expand All @@ -66,6 +67,7 @@ def set(key: str, value: Any):

_reload(formatters)


def get(key: str) -> Any:
return settings[key]

Expand Down
12 changes: 7 additions & 5 deletions i18n/custom_functions.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
from collections import defaultdict
from typing import Optional, Callable
from typing import Optional, Callable, Dict


global_functions = {}
locales_functions = defaultdict(dict)
Function = Callable[..., int]
global_functions: Dict[str, Function] = {}
locales_functions: Dict[str, Dict[str, Function]] = defaultdict(dict)


def add_function(name: str, func: Callable[..., int], locale: Optional[str] = None):
def add_function(name: str, func: Function, locale: Optional[str] = None) -> None:
if locale:
locales_functions[locale][name] = func
else:
global_functions[name] = func

def get_function(name, locale=None):

def get_function(name: str, locale: Optional[str] = None) -> Optional[Function]:
if locale and name in locales_functions[locale]:
return locales_functions[locale][name]
return global_functions.get(name)
4 changes: 2 additions & 2 deletions i18n/errors.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
class I18nException(Exception):
def __init__(self, value):
def __init__(self, value: str):
self.value = value

def __str__(self):
return str(self.value)
return self.value


class I18nFileLoadError(I18nException):
Expand Down
Loading

0 comments on commit 7579c9d

Please sign in to comment.