Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
19e0b54
add nework_response_check decoration and started checking for Config …
Mopsgeschwindigkeit Jan 14, 2024
807cfd8
add custom ConfigurationFile Errors
Mopsgeschwindigkeit Jan 20, 2024
895e897
change exit codes
Mopsgeschwindigkeit Jan 20, 2024
a50b4cc
added comment
Mopsgeschwindigkeit Jan 20, 2024
597689a
changed SxapiConfigurationFileError class docstring
Mopsgeschwindigkeit Feb 2, 2024
9a9dda3
add parser files
Mopsgeschwindigkeit Mar 8, 2024
15a58ff
enabled coverage
Mopsgeschwindigkeit Mar 8, 2024
d81c5c5
remove unused file
Mopsgeschwindigkeit Mar 8, 2024
c3caa1d
add animal sub-parser. No testin added yet becuase of rebuild.
Mopsgeschwindigkeit Mar 8, 2024
c703924
add get subcommand to aniamls parser
Mopsgeschwindigkeit Mar 10, 2024
1e265fd
add comments
Mopsgeschwindigkeit Mar 10, 2024
8e1cd1f
a
Mopsgeschwindigkeit Mar 13, 2024
e381c61
add animals create sub-command
Mopsgeschwindigkeit Mar 16, 2024
4652641
introduce cutom mock class and tests for "animals get" subparser
Apr 2, 2024
cd21858
formatting, fix logic bugs, add animals.update file
Mopsgeschwindigkeit Apr 8, 2024
332938e
improved MockObjects (iteration, https_method separation), add test f…
Mopsgeschwindigkeit Apr 8, 2024
338f8b9
moved SxAnimalsSubparser to __init__ file
Mopsgeschwindigkeit Apr 11, 2024
fab907f
update help text
Mopsgeschwindigkeit Apr 13, 2024
5d920e6
create aborts subparser
Mopsgeschwindigkeit Apr 13, 2024
ee533bd
move token subparser
Mopsgeschwindigkeit May 13, 2024
1a3838c
Merge remote-tracking branch 'upstream/main'
Mopsgeschwindigkeit Feb 17, 2025
49f6bf7
update all pre-commit-hooks to newest version
Mopsgeschwindigkeit Feb 17, 2025
7fe7fad
add missing return value to get_metrics_animals call. closes #53
Mopsgeschwindigkeit Feb 17, 2025
577e37e
add new custom errors SxapiCliArgumentError and SxapiMissingOrgaIDError
Mopsgeschwindigkeit Feb 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
# find all other possible hooks at https://pre-commit.com/hooks.html

default_stages: [commit]
default_stages: [pre-commit]
fail_fast: true

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-toml

- repo: https://github.com/psf/black
rev: 22.3.0
rev: 25.1.0
hooks:
- id: black

- repo: https://github.com/pycqa/isort
rev: 5.12.0
rev: 6.0.0
hooks:
- id: isort
name: isort (python)

- repo: https://github.com/myint/autoflake
rev: v1.4
rev: v2.3.1
hooks:
- id: autoflake
args: [
Expand All @@ -33,7 +33,7 @@ repos:
"--ignore-init-module-imports"
]
- repo: https://github.com/PyCQA/flake8
rev: 3.9.2
rev: 7.1.2
hooks:
- id: flake8
additional_dependencies: [flake8-isort]
22 changes: 0 additions & 22 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,28 +54,6 @@
except Exception as e:
print("Running `sphinx-apidoc` failed!\n{}".format(e))

# This dummy-keyring must be set on order to
# avoid keyring.errors.NoKeyringError during build process
try:
from keyring import backend

# this class prevents the "keyring.errors.NoKeyringError"
class TestKeyring(backend.KeyringBackend):

priority = 1

def set_password(self, servicename, username, password):
return "None"

def get_password(self, servicename, username):
return "None"

def delete_password(self, servicename, username):
return "None"

except Exception as e:
print(e)

# -- General configuration ---------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
Expand Down
4 changes: 3 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ formats = bdist_wheel

[flake8]
# Some sane defaults for the code style checker flake8
max_line_length = 88
max_line_length = 100
extend_ignore = E203, W503
# ^ Black-compatible
# E203 and W503 have edge cases handled by black
Expand All @@ -120,6 +120,8 @@ exclude =
dist
.eggs
docs/conf.py
per-file-ignores =
*/__init__.py: F401

[isort]
# configurations for isort import style checker
Expand Down
36 changes: 31 additions & 5 deletions src/sxapi/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,34 @@

import requests

from sxapi.errors import (
SxapiAuthorizationError,
SxapiUnprocessableContentError,
)


class ApiTypes(Enum):
PUBLIC = 1
INTEGRATION = 2


def check_response(func):
"""Decorator to handle response status codes."""

def wrapper(*args, **kwargs):
response = func(*args, **kwargs)
if response.status_code in [401, 403]:
raise SxapiAuthorizationError()
elif response.status_code == 422:
raise SxapiUnprocessableContentError()
else:
response.raise_for_status()

return response.json()

return wrapper


class BaseAPI(object):
def __init__(
self,
Expand All @@ -36,7 +58,7 @@ def get_token(self):
@property
def session(self):
"""
Geneates a new HTTP session on the fly and logs in if no session exists.
Generates a new HTTP session on the fly and logs in if no session exists.
"""
if self._session is None:
self._session = requests.Session()
Expand Down Expand Up @@ -69,22 +91,26 @@ def _login(self):
raise requests.HTTPError(response.status_code, response.reason)
self._session.headers.update({"Authorization": f"Bearer {self.api_token}"})

@check_response
def get(self, path, *args, **kwargs):
url = self.to_url(path)
r = self.session.get(url, *args, **kwargs)
return r.json()
return r

@check_response
def post(self, path, *args, **kwargs):
url = self.to_url(path)
r = self.session.post(url, *args, allow_redirects=False, **kwargs)
return r.json()
return r

@check_response
def put(self, path, *args, **kwargs):
url = self.to_url(path)
r = self.session.put(url, *args, allow_redirects=False, **kwargs)
return r.json()
return r

@check_response
def delete(self, path, *args, **kwargs):
url = self.to_url(path)
r = self.session.delete(url, *args, **kwargs)
return r.json()
return r
118 changes: 117 additions & 1 deletion src/sxapi/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,119 @@
from .cli_user import CliUser
import os

import keyring

from sxapi.integrationV2 import IntegrationAPIV2
from sxapi.publicV2 import PublicAPIV2


class CliUser:
"""
CliUser class used for initializing, storing, retrieving and deleting
credentials and creating/holding Instances of supported API
Client.

This class should only be used in the cli package.
"""

def __init__(self):
"""
Basic User Credentials Constructor

calls self._init_creds() to set available credentials on startup.
"""
self.organisation_id = None
self.api_access_token = None
self.public_v2_api = None
self.integration_v2_api = None

@staticmethod
def get_token_environment():
"""
Gets token named 'SMAXTEC_API_ACCESS_TOKEN' from the systems' environment.
"""

return os.environ.get("SMAXTEC_API_ACCESS_TOKEN", None)

def set_token_keyring(self, token):
"""
Store the given token in keyring.
"""
keyring.set_password("sxapi", "SMAXTEC_API_ACCESS_TOKEN", token)
self.api_access_token = token

@staticmethod
def get_token_keyring():
"""
Gets the token stored in the keyring.
"""
return keyring.get_password("sxapi", "SMAXTEC_API_ACCESS_TOKEN")

@staticmethod
def clear_token_keyring():
"""
Deletes the token from the keyring.
"""
keyring.delete_password("sxapi", "SMAXTEC_API_ACCESS_TOKEN")

# general functions
def check_credentials_set(self):
"""
Checks if token is already set.
"""
if self.api_access_token is not None:
return True
return False

def init_user(self, config, args_token, args_keyring, args_orga_id):
"""
This function retrieves the token from the specified resource
(keyring, environment or args) and initializes clients
of the supported APIs (PublicV2, IntegrationV2).

If no token can be found the token is retrieved via
the username and password.

If username and password are also missing, no credentials get
stored and not API clients are created.
"""
if args_orga_id:
self.organisation_id = args_orga_id
elif config.orga:
self.organisation_id = config.orga

if args_token:
self.api_access_token = args_token
elif args_keyring:
self.api_access_token = self.get_token_keyring()
if self.api_access_token is None:
print("No token found in keyring. Use values from config file.\n")
else:
self.api_access_token = self.get_token_environment()

if self.api_access_token is None and config.user and config.password:
self.public_v2_api = PublicAPIV2(
base_url=config.api_public_v2_path,
email=config.user,
password=config.password,
)
self.integration_v2_api = IntegrationAPIV2(
base_url=config.api_integration_v2_path,
email=config.user,
password=config.password,
)

self.api_access_token = self.public_v2_api.get_token()

elif self.api_access_token:
self.public_v2_api = PublicAPIV2(
base_url=config.api_public_v2_path,
api_token=self.api_access_token,
)

self.integration_v2_api = IntegrationAPIV2(
base_url=config.api_integration_v2_path,
api_token=self.api_access_token,
)


cli_user = CliUser()
Loading