Skip to content

Commit

Permalink
Merge pull request #4 from hardcoretech/fix/FIS-1417-1418
Browse files Browse the repository at this point in the history
Detect all __init__.py besides files starts with test
  • Loading branch information
MortalHappiness authored Oct 25, 2023
2 parents 7d3bef0 + d674f50 commit d47281d
Show file tree
Hide file tree
Showing 8 changed files with 550 additions and 191 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
name: lint

on: [pull_request]
on: [ pull_request ]

jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 1024
- uses: actions/setup-python@v2
- uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: pre-commit
env:
BASE_SHA: ${{ github.event.pull_request.base.sha}}
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ut.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
name: Run Unit Test

on: [pull_request]
on: [ pull_request ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.6.8"
python-version: '3.8'
- name: Install dependencies
run: |
pip install pip --upgrade
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*.sw[po]
*.py[co]
*.DS_Store
.idea
dist/
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ repos:
- --remove-unused-variables
- --remove-all-unused-imports
- repo: https://github.com/pycqa/isort
rev: 5.10.1
rev: 5.12.0
hooks:
- id: isort
311 changes: 305 additions & 6 deletions poetry.lock

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
[tool.poetry]
name = "missing"
version = "0.2.5"
version = "0.3.0"
description = ""
authors = ["PLM <[email protected]>"]

packages = [
{ include="src", from="." },
{ include = "src", from = "." },
]

[tool.poetry.dependencies]
python = "^3.6"
python = "^3.8"

[tool.poetry.dev-dependencies]
pytest = "^7.4.2"
pre-commit = "2.14.0"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.poetry.scripts]
missing = "src.missing:main"

[tool.pytest.ini_options]
pythonpath = "src"
167 changes: 41 additions & 126 deletions src/missing.py
Original file line number Diff line number Diff line change
@@ -1,137 +1,62 @@
#! /usr/bin/env python
import argparse
import enum
import logging
import os
import re
import subprocess
from functools import lru_cache
from typing import Optional
from typing import Sequence

logger = logging.getLogger('missing')

class Mode(enum.Enum):
ALL = 'all'
OBEY_GITIGNORE = 'obey_gitignore'
STAGED_ONLY = 'staged_only'

def __str__(self):
return self.value

class Missing:
RE_TEST_PY = re.compile(r'^test.*?\.py$')

def __init__(self, mode, exclude):
def __init__(self, exclude: Sequence[str]):
if not (isinstance(exclude, list) or isinstance(exclude, tuple)):
raise TypeError('exclude should be list or tuple')
for path in exclude:
if os.path.exists(path) and not os.path.isdir(path):
raise TypeError(
f'exclude should only contain directories, found file {path}'
)

self._tracked_files = self._find_tracked_files(mode)
logger.debug('Tracked files: %r', self._tracked_files)

# append .git to exclude folder
exclude = set(['.git', *exclude])

# normalize a pathname by collapsing redundant separators
self._exclude = [os.path.normpath(path) for path in exclude]

def run(self) -> int:
fail = 0

if self._find_for_unittest():
fail = 1
files = [os.path.normpath(path) for path in self._list_files()]
logger.debug(f'{files=}')

return fail
exclude = [os.path.normpath(path) for path in exclude]
logger.debug(f'{exclude=}')

@lru_cache(maxsize=None)
def _check_init_py_exists(self, path: str) -> bool:
"""check the __init__.py exists on the current path and parent path"""
fail = False
basedir = os.path.dirname(path)
files_included = set()
for file in files:
if all([not file.startswith(e) for e in exclude]):
files_included.add(file)
logger.debug(f'{files_included=}')

if basedir in ('', '.'):
return False
self.files = files_included

# check the parent folder
fail = self._check_init_py_exists(basedir)
def run(self) -> int:
"""Return 1 if there are missing files, otherwise 0"""
is_failed = 0

# check the current __init__ exists or not
init_py = f'{basedir}/__init__.py'
if not os.path.exists(init_py):
fail = True
for file in self.files:
basedir = os.path.dirname(file)

# create the __init__.py
with open(init_py, 'w') as fd:
logger.warning('create file: {}'.format(init_py))
return fail
if basedir in ('', '.'):
continue

def _find_for_unittest(self, basedir: Optional[str] = None) -> bool:
"""the unittest find the test*.py only for the regular package"""
fail = False
init_run = False
init_py = os.path.join(basedir, '__init__.py')
if not os.path.exists(init_py):
is_failed = True
# create the __init__.py
with open(init_py, 'w'):
logger.warning(f'create file: {init_py}')

if basedir is None:
basedir = '.'
init_run = True
return is_failed

for filename in os.listdir(basedir):
path = os.path.normpath(f'{basedir}/{filename}')
if path in self._exclude:
# skip the explicitly excluded path
logger.debug('exclude: %r', path)
continue
@staticmethod
def _list_files():
pattern = re.compile(r'^.*?\.py$')

if os.path.isdir(path):
if self._find_for_unittest(path):
fail = True
elif self.RE_TEST_PY.match(filename):
if self._is_tracked(path):
logger.debug('check: %r', path)
if self._check_init_py_exists(path):
fail = True
else:
logger.debug('untracked: %r', path)
else:
logger.debug('no_match: %r', path)

if init_run and fail:
logger.warning('found missing __init__.py for unittest')

return fail

def _is_tracked(self, path):
if self._tracked_files is None:
return True
return path in self._tracked_files

def _find_tracked_files(self, mode):
if Mode(mode) == Mode.OBEY_GITIGNORE:
return self._list_files_obey_gitignore()
elif Mode(mode) == Mode.STAGED_ONLY:
return self._list_files_staged_only()
else:
return None

def _list_files_staged_only(self):
# list staged files
tracked_files = (
subprocess.check_output(
['git', '--no-pager', 'diff', '--name-only', '--cached', '-z'],
encoding='utf-8',
)
.rstrip('\0')
.split('\0')
)
return set(
[
os.path.normpath(tracked_file)
for tracked_file in tracked_files
if self.RE_TEST_PY.match(os.path.basename(tracked_file))
]
)

def _list_files_obey_gitignore(self):
# list committed files
tracked_files = (
files = (
subprocess.check_output(
['git', 'ls-files', '-z'],
encoding='utf-8',
Expand All @@ -140,21 +65,19 @@ def _list_files_obey_gitignore(self):
.split('\0')
)

# list untracked files
tracked_files += (
files += (
subprocess.check_output(
['git', 'ls-files', '-o', '--exclude-standard', '-z'],
encoding='utf-8',
)
.rstrip('\0')
.split('\0')
)

return set(
[
os.path.normpath(tracked_file)
for tracked_file in tracked_files
if self.RE_TEST_PY.match(os.path.basename(tracked_file))
]
os.path.normpath(file)
for file in files
if pattern.match(os.path.basename(file))
)


Expand All @@ -165,14 +88,6 @@ def main() -> int:
'-q', '--quite', action='store_true', default=False, help='disable all log'
)


parser.add_argument(
'-m',
'--mode',
type=Mode,
default=Mode.ALL,
choices=list(Mode)
)
args = parser.parse_args()

if args.quite:
Expand All @@ -183,7 +98,7 @@ def main() -> int:
handler = logging.StreamHandler()
logger.addHandler(handler)

missing = Missing(args.mode, args.exclude or [])
missing = Missing(args.exclude or [])
return missing.run()


Expand Down
Loading

0 comments on commit d47281d

Please sign in to comment.