From 9084654942deab2fafd3af34a85a2c1358da0978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Uhl=C3=AD=C5=99?= Date: Mon, 3 Jun 2024 08:19:24 +0200 Subject: [PATCH] feat: finishing --- CONTRIBUTING.md | 15 ++++++++++++--- pytest.ini | 2 +- requirements.txt | 6 +++--- tests/configs/non-premium.config | 3 +-- tests/configs/premium.config | 2 +- toggl/api/base.py | 7 +++++++ toggl/api/models.py | 20 +++++++++++++++++--- toggl/cli/commands.py | 4 ++-- toggl/toggl.py | 3 +++ toggl/utils/config.py | 6 +++--- toggl/utils/migrations.py | 2 +- 11 files changed, 51 insertions(+), 19 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e3839a..aaef633 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,11 +4,20 @@ Any contribution are welcomed. For submitting PRs, they need to have test coverage which pass the full run in Travis CI. +## Developing + +If you want to run the toggl CLI during development, I recommend you to use flow where you `pip install -e .`, which +symlinks locally the package and then you can simply use the CLI like `toggl ls`. + +Also, if you find yourself with non-descriptive exception, you can set env. variable `export TOGGL_EXCEPTIONS=1` which +then will give you then the full stack trace. + ## Tests For running integration tests you need dummy account on Toggl, where **you don't have any important data** as the data will be messed up with and eventually **deleted**! Get API token for this test account and set it as an environmental variable -***TOGGL_API_TOKEN***. +`TOGGL_API_TOKEN`. Also figure out the Workspace ID of your account (`toggl workspace ls`) and set is as `TOGGL_WORKSPACE` +environmental variable. There are two sets of integration tests: normal and premium. To be able to run the premium set you have to have payed workspace. As this is quiet unlikely you can leave the testing on Travis CI as it runs also the premium tests set. @@ -20,8 +29,8 @@ Tests are written using `pytest` framework and are split into three categories ( ## Running tests -In order to run tests first you need to have required packages installed. You can install them using `pip install togglCli[test]` or -`python setup.py test`. +In order to run tests first you need to have required packages installed. You can install them using `pip install togglCli[test]`, +`python setup.py test` or `pip install -r test-requirements.txt`. By default unit and integration tests are run without the one testing premium functionality, as most probably you don't have access to Premium workspace for testing purposes. If you want to run just specific category you can do so using for example`pytest -m unit` for only unit tests. diff --git a/pytest.ini b/pytest.ini index 337cef2..49f038e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,4 +3,4 @@ addopts = --cov toggl -m "not premium" --maxfail=20 markers = unit: Unit tests testing framework. No outside dependencies (no out-going requests) integration: Integration tests which tests end to end coherence of API wrapper. Requires connectivity to Toggl API. - premium: Subcategory of Integration tests that requires to have Premium/Paid workspace for the tests. \ No newline at end of file + premium: Subcategory of Integration tests that requires to have Premium/Paid workspace for the tests. diff --git a/requirements.txt b/requirements.txt index 762e25a..64cc805 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ setuptools==69.0.3 pendulum==3.0.0 requests>=2.23.0 -click==8.1.3 -inquirer==2.9.1 +click==8.1.7 +inquirer==3.2.4 prettytable==3.6.0 validate_email==1.3 click-completion==0.5.2 -pbr==5.8.0 +pbr==6.0.0 notify-py==0.3.42 diff --git a/tests/configs/non-premium.config b/tests/configs/non-premium.config index dabf634..b5b7b47 100644 --- a/tests/configs/non-premium.config +++ b/tests/configs/non-premium.config @@ -3,5 +3,4 @@ version = 2.0.0 [options] tz = utc -default_wid = 3057440 - +default_wid = 8379305 diff --git a/tests/configs/premium.config b/tests/configs/premium.config index 489f0e6..b5b7b47 100644 --- a/tests/configs/premium.config +++ b/tests/configs/premium.config @@ -3,4 +3,4 @@ version = 2.0.0 [options] tz = utc -default_wid = 2609276 +default_wid = 8379305 diff --git a/toggl/api/base.py b/toggl/api/base.py index f9add46..172a846 100644 --- a/toggl/api/base.py +++ b/toggl/api/base.py @@ -24,6 +24,8 @@ def evaluate_conditions(conditions, entity, contain=False): # type: (typing.Dic :param conditions: dict :return: """ + logger.debug(f'EvaluatingConditions: Filtering based on conditions: {conditions}') + for key, value in conditions.items(): try: field = entity.__fields__[key] @@ -31,6 +33,7 @@ def evaluate_conditions(conditions, entity, contain=False): # type: (typing.Dic try: field = entity.__mapped_fields__[key] except KeyError: + logger.debug(f'EvaluatingConditions: Field {key} not found in entity {entity}') return False if isinstance(field, model_fields.MappingField): @@ -47,6 +50,7 @@ def evaluate_conditions(conditions, entity, contain=False): # type: (typing.Dic continue if value != mapped_entity_id: + logger.debug(f'EvaluatingConditions: Mapped entity\'s ID does not match') return False continue @@ -82,6 +86,7 @@ def evaluate_conditions(conditions, entity, contain=False): # type: (typing.Dic continue if str(entity_value) != str(value): + logger.debug(f'EvaluatingConditions: String values do not match: {entity_value} != {value}') return False return True @@ -259,6 +264,8 @@ def filter(self, order='asc', config=None, contain=False, **conditions): # type if fetched_entities is None: return [] + logger.debug(f'Filter: Fetched {fetched_entities} entities') + # There are no specified conditions ==> return all if not conditions: return fetched_entities diff --git a/toggl/api/models.py b/toggl/api/models.py index a790c7a..543c827 100644 --- a/toggl/api/models.py +++ b/toggl/api/models.py @@ -259,6 +259,20 @@ class WorkspacedEntity(base.TogglEntity): def get_url(self): # type: () -> str return f'workspaces/{self.workspace.id}/{self.get_endpoints_name()}' +class OldWorkspacedEntity(base.TogglEntity): + """ + Abstract entity which has linked Workspace + """ + + workspace = fields.MappingField(Workspace, 'wid', write=False, + default=lambda config: config.default_workspace) # type: Workspace + """ + Workspace to which the resource is linked to. + """ + + def get_url(self): # type: () -> str + return f'workspaces/{self.workspace.id}/{self.get_endpoints_name()}' + # Premium Entity class PremiumEntity(WorkspacedEntity): @@ -276,7 +290,7 @@ def save(self, config=None): # type: (utils.Config) -> None # ---------------------------------------------------------------------------- # Entities definitions # ---------------------------------------------------------------------------- -class Client(WorkspacedEntity): +class Client(OldWorkspacedEntity): """ Client entity """ @@ -551,12 +565,12 @@ class ProjectUser(WorkspacedEntity): Admin rights for this project """ - project = fields.MappingField(Project, 'pid', write=False) + project = fields.MappingField(Project, 'project_id', write=False) """ Project to which the User is assigned. """ - user = fields.MappingField(User, 'uid', write=False) + user = fields.MappingField(User, 'user_id', write=False) """ User which is linked to Project. """ diff --git a/toggl/cli/commands.py b/toggl/cli/commands.py index ec72ff0..bd67d13 100644 --- a/toggl/cli/commands.py +++ b/toggl/cli/commands.py @@ -622,7 +622,7 @@ def clients_ls(ctx): def clients_get(ctx, spec): """ Gets details of a client specified by SPEC argument. SPEC can be either ID or Name of the client. - Be aware that if you use specify SPEC using Name you won't get note for this client. + Be aware that if you specify SPEC using Name you won't get note for this client. If SPEC is Name, then the lookup is done in the default workspace, unless --workspace is specified. """ @@ -769,7 +769,7 @@ def project_users_ls(ctx, fields): project = ctx.obj['project'] src = api.ProjectUser.objects.filter(project=project, config=ctx.obj['config']) - helpers.entity_listing(src, fields) + helpers.entity_listing(src, fields, obj=ctx.obj) @project_users.command('add', short_help='add a user into the project') diff --git a/toggl/toggl.py b/toggl/toggl.py index b73100c..81558b4 100644 --- a/toggl/toggl.py +++ b/toggl/toggl.py @@ -10,3 +10,6 @@ def main(args=None): """Main entry point for Toggl CLI application""" cli.entrypoint(args or sys.argv[1:]) + +if __name__ == "__main__": + main() diff --git a/toggl/utils/config.py b/toggl/utils/config.py index 805e81e..472e12b 100644 --- a/toggl/utils/config.py +++ b/toggl/utils/config.py @@ -64,15 +64,15 @@ class IniConfigMixin: } _old_file_path = Path.expanduser(Path('~/.togglrc')) - + if "XDG_CONFIG_HOME" in os.environ: _new_file_path = Path(os.environ["XDG_CONFIG_HOME"]).joinpath(".togglrc") - + if _new_file_path.exists() or not _old_file_path.exists(): DEFAULT_CONFIG_PATH = _new_file_path else: DEFAULT_CONFIG_PATH = _old_file_path - + else: DEFAULT_CONFIG_PATH = _old_file_path diff --git a/toggl/utils/migrations.py b/toggl/utils/migrations.py index e011138..fd68896 100644 --- a/toggl/utils/migrations.py +++ b/toggl/utils/migrations.py @@ -101,7 +101,7 @@ def migrate_timezone(parser): # type: (configparser.ConfigParser) -> None click.echo('We have not recognized your timezone!') new_tz = inquirer.shortcuts.text( 'Please enter valid timezone. Default is your system\'s timezone.', - default='local', validate=lambda _, i: i in pendulum.timezones or i == 'local') + default='local', validate=lambda _, i: i in pendulum.timezones() or i == 'local') parser.set('options', 'tz', new_tz) @classmethod