diff --git a/PACKAGING.md b/PACKAGING.md new file mode 100644 index 0000000..471c485 --- /dev/null +++ b/PACKAGING.md @@ -0,0 +1,27 @@ +# How to package + +Pretty rough so far, can be improved/automated. + +## Bump Version + +Bump version in `src/tm1filetools/__init__.py` e.g.: + +```python +"""A package for working with files created by a TM1 database.""" + +from .tools import TM1FileTool # noqa + +__version__ = "0.3.2" # noqa +``` + +## Publish to PyPI + +```sh +flit build +``` + +## Publish to PyPI + +```sh +flit publish +``` diff --git a/README.md b/README.md deleted file mode 100644 index d4466c0..0000000 --- a/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# TM1 File Tools - -A Python package that simplifies working with files associated with a TM1 server. It's primarily useful for linting or cleaning up a server directory without a dependency on a running TM1 Server. - -## What it does - -+ Scans a TM1 database folder and finds most TM1 related files (e.g. `.cub`, `.rux` etc) -+ Provides methods to rename and delete files and properties specific to a file type (e.g. the cube a `.vue` file refers to) -+ Return lists of "orphaned" files (e.g. a `.rux` without a corresponding `.cub`) - -## What it doesn't do - -+ Operations on binary files (e.g. it can't read or edit a `.cub` file) -+ Genuine parsing of text files (e.g. it can't verify whether a `.rux` is valid) -+ Interact with the REST API (use [TM1py](https://github.com/cubewise-code/tm1py) for that) - -## Installation - - -```sh -pip install tm1filetools -``` - -## Example Usage - -```python -from pathlib import Path -from tm1filetools import TM1FileTool - -path = Path("./data") - -ft = TM1FileTool(path) - -orphans = ft.get_orphan_rules() - -... - -ft.delete_all_blbs() - -... -``` diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..dc8a2d5 --- /dev/null +++ b/README.rst @@ -0,0 +1,53 @@ +TM1 File Tools +============== + +A Python package that simplifies working with files associated with a +TM1 server. It’s primarily useful for linting or cleaning up a server +directory without a dependency on a running TM1 Server. + +What it does +------------ + +- Scans a TM1 database folder and finds most TM1 related files + (e.g. ``.cub``, ``.rux`` etc) +- Provides methods to rename and delete files and properties specific + to a file type (e.g. the cube a ``.vue`` file refers to) +- Return lists of “orphaned” files (e.g. a ``.rux`` without a + corresponding ``.cub``) + +What it doesn’t do +------------------ + +- Operations on binary files (e.g. it can’t read or edit a ``.cub`` + file) +- Genuine parsing of text files (e.g. it can’t verify whether a + ``.rux`` is valid) +- Interact with the REST API (use + `TM1py `__ for that) + +Installation +------------ + +.. code:: sh + + pip install tm1filetools + +Example Usage +------------- + +.. code:: python + + from pathlib import Path + from tm1filetools import TM1FileTool + + path = Path("./data") + + ft = TM1FileTool(path) + + orphans = ft.get_orphan_rules() + + ... + + ft.delete_all_blbs() + + ... diff --git a/pyproject.toml b/pyproject.toml index e7973e5..335a97f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dynamic = ["version", "description"] dependencies = [ "chardet>=5.0.0", ] -readme = "README.md" +readme = "README.rst" requires-python = ">=3.9" classifiers = [ "Programming Language :: Python :: 3", diff --git a/src/tm1filetools/__init__.py b/src/tm1filetools/__init__.py index 27b1479..a45cd64 100644 --- a/src/tm1filetools/__init__.py +++ b/src/tm1filetools/__init__.py @@ -2,4 +2,4 @@ from .tools import TM1FileTool # noqa -__version__ = "0.3.1" # noqa +__version__ = "0.3.2" # noqa diff --git a/src/tm1filetools/files/__init__.py b/src/tm1filetools/files/__init__.py index fe5f2ac..beff62b 100644 --- a/src/tm1filetools/files/__init__.py +++ b/src/tm1filetools/files/__init__.py @@ -9,6 +9,7 @@ from .binary.feeders import TM1FeedersFile # noqa from .text.blb import TM1BLBFile # noqa from .text.cfg import TM1CfgFile # noqa +from .text.chore import TM1ChoreFile # noqa from .text.cma import TM1CMAFile # noqa from .text.log import TM1ChangeLogFile, TM1LogFile, TM1ProcessErorrLogFile # noqa from .text.process import TM1ProcessFile # noqa diff --git a/src/tm1filetools/files/text/chore.py b/src/tm1filetools/files/text/chore.py new file mode 100644 index 0000000..13cc328 --- /dev/null +++ b/src/tm1filetools/files/text/chore.py @@ -0,0 +1,18 @@ +from pathlib import Path + +from .text import TM1TextFile + + +class TM1ChoreFile(TM1TextFile): + """ + A class representation of a tm1 TI process file + + """ + + suffix = "cho" + + def __init__(self, path: Path): + + super().__init__(path) + + # A chore is just a text file that holds a name, processes to run and params, and scheduling information diff --git a/src/tm1filetools/tools/filetool.py b/src/tm1filetools/tools/filetool.py index 82807be..febc3fd 100644 --- a/src/tm1filetools/tools/filetool.py +++ b/src/tm1filetools/tools/filetool.py @@ -8,6 +8,7 @@ TM1BLBFile, TM1CfgFile, TM1ChangeLogFile, + TM1ChoreFile, TM1CMAFile, TM1CubeFile, TM1DimensionFile, @@ -34,6 +35,7 @@ class TM1FileTool: TM1CubeFile.suffix, TM1DimensionFile.suffix, TM1ProcessFile.suffix, + TM1ChoreFile.suffix, TM1RulesFile.suffix, TM1SubsetFile.suffix, TM1ViewFile.suffix, @@ -66,6 +68,7 @@ def __init__(self, path: Path, local: bool = False): self._sub_files: Optional[list] = None self._view_files: Optional[list] = None self._feeders_files: Optional[list] = None + self._chore_files: Optional[list] = None # logs self._log_files: Optional[list] = None # other cruft @@ -88,6 +91,7 @@ def find_all(self): self._find_logs() self._find_cmas() self._find_blbs() + self._find_chores() self._find_non_tm1() # getters for all file types @@ -203,6 +207,22 @@ def get_feeders(self, model: bool = True, control: bool = False) -> List[TM1Feed return self._filter_model_and_or_control(self._feeders_files, model=model, control=control) + def get_chores(self, model: bool = True, control: bool = False) -> List[TM1ChoreFile]: + """Returns list of all chore files + + Args: + model: Return model chores (i.e. not prefixed with "}") + control: Return control chores (i.e. prefixed with "}") + + Returns: + List of chore files + """ + + if self._chore_files is None: + self._find_chores() + + return self._filter_model_and_or_control(self._chore_files, model=model, control=control) + def get_logs(self) -> List[TM1LogFile]: """Returns list of all log files @@ -492,6 +512,10 @@ def _find_feeders(self): self._feeders_files = [TM1FeedersFile(f) for f in self._find_files(TM1FeedersFile.suffix)] + def _find_chores(self): + + self._chore_files = [TM1ChoreFile(f) for f in self._find_files(TM1ChoreFile.suffix)] + def _find_cmas(self): self._cma_files = [TM1CMAFile(r) for r in self._find_files(TM1CMAFile.suffix, recursive=True)] diff --git a/tests/conftest.py b/tests/conftest.py index 77ceca5..07ee249 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,6 +11,7 @@ cub_attributes = ["koala.cub", "humphrey.CUB"] rux_files = ["dog.ruX", "giraffe.rux"] process_files = ["dingo.PRO", "wombat.pro", "}fraggle.pRO"] +cho_files = ["quokka.cho", "black_SNAKE.cho", "}brown_snake.cHO"] sub_files = ["platypus.sub", "donkey.SUB", "}dolphin.suB"] sub_folders = ["cat", "koala"] view_files = ["mouse.vue", "squirrel.VUE", "}shark.vue"] @@ -213,6 +214,11 @@ def test_folder(tmp_path_factory): f = d / f"{p}" f.touch() + for c in cho_files: + + f = d / f"{c}" + f.touch() + for lg in log_files: f = d / f"{lg}" diff --git a/tests/test_files/test_text/test_chore.py b/tests/test_files/test_text/test_chore.py new file mode 100644 index 0000000..ea2de1f --- /dev/null +++ b/tests/test_files/test_text/test_chore.py @@ -0,0 +1,11 @@ +from pathlib import Path + +from tm1filetools.files import TM1ChoreFile + + +def test_init(test_folder): + + p = TM1ChoreFile(Path.joinpath(test_folder, "copy data from my cube.cho")) + + assert p + assert p.suffix == "cho" diff --git a/tests/test_tools/test_finders.py b/tests/test_tools/test_finders.py index c7b4fbf..30e5f80 100644 --- a/tests/test_tools/test_finders.py +++ b/tests/test_tools/test_finders.py @@ -71,6 +71,16 @@ def test_find_feeders(test_folder): assert all(f.stem != "dragon" for f in ft._feeders_files) +def test_find_chores(test_folder): + + ft = TM1FileTool(test_folder) + + ft._find_chores() + + assert any(f.stem == "quokka" for f in ft._chore_files) + assert all(f.stem != "brown_snake" for f in ft._chore_files) + + def test_find_logs(test_folder): ft = TM1FileTool(test_folder) diff --git a/tests/test_tools/test_getters.py b/tests/test_tools/test_getters.py index 23a7a3c..6cf2e54 100644 --- a/tests/test_tools/test_getters.py +++ b/tests/test_tools/test_getters.py @@ -106,6 +106,27 @@ def test_get_control_views(test_folder): assert all(v.stem != "donkey" for v in views) +def test_get_model_chores(test_folder): + + ft = TM1FileTool(test_folder) + + chores = ft.get_chores() + + assert any(c.stem == "quokka" for c in chores) + assert all(c.stem != "brown_snake" for c in chores) + + +def test_get_control_chores(test_folder): + + ft = TM1FileTool(test_folder) + + chores = ft.get_chores(model=False, control=True) + + assert any(c.stem == "}brown_snake" for c in chores) + assert all(c.stem != "quokka" for c in chores) + assert all(c.stem != "donkey" for c in chores) + + def test_get_logs(test_folder): ft = TM1FileTool(test_folder)