Skip to content

Commit

Permalink
feat: finishing
Browse files Browse the repository at this point in the history
  • Loading branch information
AuHau committed Jun 3, 2024
1 parent 48d4b04 commit 9084654
Show file tree
Hide file tree
Showing 11 changed files with 51 additions and 19 deletions.
15 changes: 12 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
2 changes: 1 addition & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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.
premium: Subcategory of Integration tests that requires to have Premium/Paid workspace for the tests.
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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
3 changes: 1 addition & 2 deletions tests/configs/non-premium.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ version = 2.0.0

[options]
tz = utc
default_wid = 3057440

default_wid = 8379305
2 changes: 1 addition & 1 deletion tests/configs/premium.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ version = 2.0.0

[options]
tz = utc
default_wid = 2609276
default_wid = 8379305
7 changes: 7 additions & 0 deletions toggl/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ 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]
except KeyError:
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):
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
20 changes: 17 additions & 3 deletions toggl/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -276,7 +290,7 @@ def save(self, config=None): # type: (utils.Config) -> None
# ----------------------------------------------------------------------------
# Entities definitions
# ----------------------------------------------------------------------------
class Client(WorkspacedEntity):
class Client(OldWorkspacedEntity):
"""
Client entity
"""
Expand Down Expand Up @@ -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.
"""
Expand Down
4 changes: 2 additions & 2 deletions toggl/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand Down Expand Up @@ -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')
Expand Down
3 changes: 3 additions & 0 deletions toggl/toggl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
6 changes: 3 additions & 3 deletions toggl/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion toggl/utils/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 9084654

Please sign in to comment.