Skip to content

Commit 191f34b

Browse files
committed
Version 1.0.0: first stable release
1 parent 968dcf1 commit 191f34b

File tree

9 files changed

+103
-51
lines changed

9 files changed

+103
-51
lines changed

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,21 +106,29 @@ When labelling subsystem and category using the native C methods there is a requ
106106
The pyoslog module handles this for you – there is no need to `del` or release these objects.
107107

108108

109+
## Limitations
110+
As noted above, while the macOS `os_log` API allows use of a format string with many methods, this name is required to be a C string literal.
111+
As a result, pyoslog hardcodes all format strings to `"%{public}s"`.
112+
113+
109114
## Testing
110-
The pyoslog module's tests require the [pyobjc OSLog framework wrappers](https://pypi.org/project/pyobjc-framework-OSLog/) in order to verify output and, as a result, can only be run on macOS 10.15 or later.
115+
The pyoslog module's tests require the [pyobjc OSLog framework wrappers](https://pypi.org/project/pyobjc-framework-OSLog/) and the [storeWithScope initialiser](https://developer.apple.com/documentation/oslog/oslogstore/3548057-storewithscope) in order to verify output so, as a result, can only be run on macOS 12 or later.
116+
111117
After installing the OSLog wrappers (via `python -m pip install pyobjc-framework-OSLog`), navigate to the [tests](https://github.com/simonrob/pyoslog/tree/main/tests) directory and run:
112118

113119
```shell
114120
python -m unittest
115121
```
116122

117123
Please note that if Console.app is live-streaming messages, some tests may fail.
118-
See [`test_logging.py`](https://github.com/simonrob/pyoslog/blob/main/tests/test_logging.py#L84) for discussion about why this is the case.
124+
See [`test_logging.py`](https://github.com/simonrob/pyoslog/blob/main/tests/test_logging.py#L93) for discussion about why this is the case.
119125

120126

121127
## Alternatives
122128
At the time this module was created there were no alternatives available on [PyPi](https://pypi.org/search/?q=macos+unified+logging&c=Operating+System+%3A%3A+MacOS).
123-
Since then, the [macos-oslog](https://pypi.org/project/macos-oslog/) module has been released, with broadly equivalent functionality to pyoslog.
129+
Since then, the [macos-oslog](https://pypi.org/project/macos-oslog/) module has been released, with broadly equivalent functionality to pyoslog, except for the need to manually release the log object.
130+
There is also [os-signpost](https://pypi.org/project/os-signpost/), which uses `cython` to provide the [`OSSignposter`](https://developer.apple.com/documentation/os/ossignposter) API, and could easily be extended to provide `os_log` functionality.
131+
124132
In addition, there are other options available if PyPi access is not seen as a constraint:
125133

126134
- [apple_os_log_py](https://github.com/cedar101/apple_os_log_py)

build.sh

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,50 @@
1+
# this build script is quite forceful about setup - make sure not to mess up the system python
2+
PYTHON_VENV=$(python -c "import sys; sys.stdout.write('1') if hasattr(sys, 'real_prefix') or sys.base_prefix != sys.prefix else sys.stdout.write('0')")
3+
if [ "$PYTHON_VENV" == 0 ]; then
4+
echo 'Warning: not running in a Python virtual environment. Please either activate a venv or edit the script to confirm this action'
5+
exit 1
6+
fi
7+
18
module=${PWD##*/} # get module name - relies on directory name == module name
29
module=${module:-/}
310

4-
echo "Preparing environment for $module"
5-
python -m pip install twine # we don't have a requirements.txt so easiest just to be sure twine exists every time
11+
printf "\nPreparing environment for %s…\n" "$module"
12+
python -m pip install --quiet --upgrade pip
13+
python -m pip install --quiet setuptools wheel twine mypy sphinx # we don't have a requirements.txt, so easiest just to be sure build dependencies exist every time
614
rm -rf dist
715
rm -rf build
816

17+
python -m pip install --quiet --force-reinstall . # install pyoslog itself
18+
919
printf '\nType checking %s…\n' "$module"
10-
mypy pyoslog
20+
python -m mypy pyoslog
1121

1222
printf '\nRunning tests for %s…\n' "$module"
13-
(cd tests && python -m unittest) # we don't fail on failed tests because they need a macOS version above our minimum
23+
python -m pip install --quiet pyobjc-framework-OSLog # separated from other installations because it will fail on unsupported platforms
24+
(cd tests && python -m unittest) # we don't fail on failed tests because they need a macOS version above our minimum
1425

1526
printf '\nBuilding documentation for %s…\n' "$module"
16-
python -m pip install -r docs/requirements.txt
27+
python -m pip install --quiet -r docs/requirements.txt
28+
export SPHINXOPTS=-q
1729
(cd docs && make clean html)
1830

19-
printf '\nBuilding source and wheel (universal) distributions for %s…\n' "$module"
20-
python setup.py clean --all sdist bdist_wheel --universal
31+
printf '\nBuilding source and wheel (universal) distributions for %s ("can'\''t clean" messages can be ignored)…\n' "$module"
32+
python setup.py -q clean --all sdist bdist_wheel --universal
2133

2234
printf '\nValidating %s packages…\n' "$module"
23-
twine check dist/*
35+
python -m twine check dist/*
2436

2537
if [ -z "$1" ]; then # exit unless a parameter is provided (we use 'deploy' but don't actually care what it is)
26-
printf '\nExiting build script - run "./build.sh deploy" to also upload to PyPi / Read the Docs\n'
27-
exit 1
38+
printf '\nExiting build script - run "./build.sh deploy" to also upload to PyPi / Read the Docs (please git commit first)\n\n'
39+
exit 0
2840
fi
2941

3042
printf "\nUpload the $(tput bold)%s$(tput sgr0) package to the \033[0;32m$(tput bold)Test PyPI repository$(tput sgr0)\033[0m via Twine? (y to confirm; n to skip; any other key to exit): " "$module"
3143
read -r answer
3244

3345
if [ "$answer" != "${answer#[Yy]}" ]; then
3446
PYPI_TEST_TOKEN=$(<versions/pypi-test.token)
35-
if twine upload -u __token__ -p "$PYPI_TEST_TOKEN" --repository testpypi dist/*; then
47+
if python -m twine upload -u __token__ -p "$PYPI_TEST_TOKEN" --repository testpypi dist/*; then
3648
echo "Upload of $module completed – install via: python -m pip install --force-reinstall --index-url https://test.pypi.org/simple/ $module"
3749
else
3850
echo "Error uploading $module; exiting"
@@ -50,7 +62,7 @@ read -r answer
5062

5163
if [ "$answer" != "${answer#[Yy]}" ]; then
5264
PYPI_DEPLOY_TOKEN=$(<versions/pypi-deploy.token)
53-
if twine upload -u __token__ -p "$PYPI_DEPLOY_TOKEN" dist/*; then
65+
if python -m twine upload -u __token__ -p "$PYPI_DEPLOY_TOKEN" dist/*; then
5466
echo "Upload of $module completed – view at https://pypi.org/project/$module"
5567

5668
READTHEDOCS_TOKEN=$(<versions/readthedocs.token)

docs/conf.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import os
88
import sys
99

10+
from recommonmark.parser import CommonMarkParser
11+
1012
# Make sure pyoslog's source files are detected
1113
sys.path.insert(0, os.path.abspath('..'))
1214

@@ -29,6 +31,12 @@
2931
lines.append(line.rstrip())
3032
output_file.write('\n'.join(lines))
3133

34+
35+
class CustomCommonMarkParser(CommonMarkParser):
36+
def visit_document(self, node):
37+
pass
38+
39+
3240
# -- Project information -----------------------------------------------------
3341
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
3442

@@ -42,17 +50,22 @@
4250

4351
print('Documenting', project, version)
4452

53+
4554
# -- General configuration ---------------------------------------------------
4655
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
4756

57+
def setup(app):
58+
# note include recommonmark this way to work around https://github.com/readthedocs/recommonmark/issues/177
59+
app.add_source_suffix('.md', 'markdown')
60+
app.add_source_parser(CustomCommonMarkParser)
61+
62+
4863
extensions = [
4964
'sphinx.ext.autodoc',
5065
'sphinx.ext.viewcode',
5166
'sphinx_toolbox.sidebar_links',
52-
'sphinx_toolbox.github',
53-
'recommonmark' # note: https://github.com/readthedocs/recommonmark/issues/177
67+
'sphinx_toolbox.github'
5468
]
55-
source_suffix = ['.rst', '.md']
5669

5770
autodoc_member_order = 'bysource' # note: doesn't work with inherited members: github.com/sphinx-doc/sphinx/issues/628
5871
autodoc_preserve_defaults = True # better display of log() defaults

pyoslog/__version__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__title__ = 'pyoslog'
2-
__version__ = '0.6.0'
3-
__description__ = 'Send messages to the macOS unified logging system'
2+
__version__ = '1.0.0'
3+
__description__ = 'Send messages to the macOS unified logging system (os_log)'
44
__author__ = 'Simon Robinson'
55
__author_email__ = '[email protected]'
66
__url__ = 'https://github.com/simonrob/pyoslog'

pyoslog/compatibility.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ def is_supported() -> bool:
77
"""Unified logging is only present in macOS 10.12 and later, but it is nicer to not have to check OS type or version
88
strings when installing or importing the module - use this method at runtime to check whether it is supported. It is
99
important to note that if is_supported() is `False` then none of the module's other methods/constants will exist."""
10-
if os.environ.get('PYOSLOG_OVERRIDE_IS_SUPPORTED', None):
10+
supported = sys.platform == 'darwin' and sys.version_info >= (3, 0,) and float(
11+
'.'.join(platform.mac_ver()[0].split('.')[:2])) >= 10.12
12+
if not supported and os.environ.get('PYOSLOG_OVERRIDE_IS_SUPPORTED', ''):
1113
print('Warning: overriding is_supported() on an unsupported platform (use to build documentation only)')
1214
return True
13-
14-
return sys.platform == 'darwin' and sys.version_info >= (3, 0,) and float(
15-
'.'.join(platform.mac_ver()[0].split('.')[:2])) >= 10.12
15+
return supported

setup.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
'Source Code': about['__url__'],
4949
},
5050

51+
# 3.6+ for core.py constants inline type hints, but not enabled because we support installation on earlier versions
52+
# python_requires='>=3.6',
53+
5154
platforms=['darwin'],
5255
packages=[NAME],
5356
ext_modules=ext_modules,
@@ -57,10 +60,14 @@
5760

5861
license=about['__license__'],
5962
classifiers=[
60-
'Development Status :: 4 - Beta',
6163
'Operating System :: MacOS',
64+
'Programming Language :: Python',
65+
'Programming Language :: C',
66+
'Topic :: Software Development',
67+
'Topic :: System :: Logging',
68+
'Typing :: Typed',
6269
'Intended Audience :: Developers',
63-
'License :: OSI Approved :: Apache Software License',
64-
'Programming Language :: Python'
70+
'Development Status :: 5 - Production/Stable',
71+
'License :: OSI Approved :: Apache Software License'
6572
]
6673
)

tests/test_handler.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,23 @@
1111

1212
print('Testing pyoslog', pkg_resources.get_distribution('pyoslog').version, 'handler')
1313

14-
try:
15-
import OSLog
16-
except ImportError:
17-
if pyoslog.is_supported() and float('.'.join(platform.mac_ver()[0].split('.')[:2])) >= 10.15:
18-
sys.exit('Error: cannot import pyobjc\'s OSLog; unable to run tests (do `pip install pyobjc-framework-OSLog`)')
19-
else:
20-
sys.exit('Error: pyobjc\'s OSLog is not supported on this platform (needs macOS 10.15+); unable to test output')
21-
2214

2315
class TestHandler(unittest.TestCase):
2416
def setUp(self):
17+
try:
18+
import OSLog
19+
except ImportError:
20+
if pyoslog.is_supported() and float('.'.join(platform.mac_ver()[0].split('.')[:2])) >= 10.15:
21+
skip_reason = 'Warning: cannot import pyobjc\'s OSLog; unable to run tests (run `pip install ' \
22+
'pyobjc-framework-OSLog`)'
23+
print(skip_reason)
24+
raise unittest.SkipTest(skip_reason)
25+
else:
26+
skip_reason = 'Warning: pyobjc\'s OSLog is not supported on this platform (requires macOS 10.15+); ' \
27+
'unable to test logging Handler'
28+
print(skip_reason)
29+
raise unittest.SkipTest(skip_reason)
30+
2531
self.handler = pyoslog.Handler()
2632
self.assertEqual(self.handler._log_object, pyoslog.OS_LOG_DEFAULT)
2733
self.assertEqual(self.handler._log_type, pyoslog.OS_LOG_TYPE_DEFAULT)
@@ -47,8 +53,8 @@ def test_setSubsystem(self):
4753
# far more thorough testing is in test_setup.py (setSubsystem essentially duplicates os_log_create)
4854
self.handler.setSubsystem(pyoslog_test_globals.LOG_SUBSYSTEM, pyoslog_test_globals.LOG_CATEGORY)
4955
self.assertIsInstance(self.handler._log_object, pyoslog_core.os_log_t)
50-
self.assertEqual(str(self.handler._log_object), 'os_log_t(%s:%s)' % (pyoslog_test_globals.LOG_SUBSYSTEM,
51-
pyoslog_test_globals.LOG_CATEGORY))
56+
self.assertEqual(str(self.handler._log_object), '<os_log_t (%s:%s)>' % (pyoslog_test_globals.LOG_SUBSYSTEM,
57+
pyoslog_test_globals.LOG_CATEGORY))
5258

5359
def test_emit(self):
5460
# far more thorough testing is in test_logging.py (emit essentially duplicates os_log_with_type)

tests/test_logging.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,27 @@
1010

1111
print('Testing pyoslog', pkg_resources.get_distribution('pyoslog').version, 'logging')
1212

13-
try:
14-
import OSLog
15-
except ImportError:
16-
if pyoslog.is_supported() and float('.'.join(platform.mac_ver()[0].split('.')[:2])) >= 10.15:
17-
sys.exit('Error: cannot import pyobjc\'s OSLog; unable to run tests (do `pip install pyobjc-framework-OSLog`)')
18-
else:
19-
sys.exit('Error: pyobjc\'s OSLog is not supported on this platform (needs macOS 10.15+); unable to test output')
20-
2113

2214
class TestLogging(unittest.TestCase):
2315
def setUp(self):
16+
try:
17+
import OSLog
18+
except ImportError:
19+
if pyoslog.is_supported() and float('.'.join(platform.mac_ver()[0].split('.')[:2])) >= 10.15:
20+
skip_reason = 'Warning: cannot import pyobjc\'s OSLog; unable to run tests (run `pip install ' \
21+
'pyobjc-framework-OSLog`)'
22+
print(skip_reason)
23+
raise unittest.SkipTest(skip_reason)
24+
else:
25+
skip_reason = 'Warning: pyobjc\'s OSLog is not supported on this platform (requires macOS 10.15+); ' \
26+
'unable to test os_log output'
27+
print(skip_reason)
28+
raise unittest.SkipTest(skip_reason)
29+
2430
self.log = pyoslog.os_log_create(pyoslog_test_globals.LOG_SUBSYSTEM, pyoslog_test_globals.LOG_CATEGORY)
2531
self.assertIsInstance(self.log, pyoslog_core.os_log_t)
26-
self.assertEqual(str(self.log), 'os_log_t(%s:%s)' % (pyoslog_test_globals.LOG_SUBSYSTEM,
27-
pyoslog_test_globals.LOG_CATEGORY))
32+
self.assertEqual(str(self.log), '<os_log_t (%s:%s)>' % (pyoslog_test_globals.LOG_SUBSYSTEM,
33+
pyoslog_test_globals.LOG_CATEGORY))
2834

2935
# noinspection PyUnresolvedReferences
3036
log_scope = OSLog.OSLogStoreScope(OSLog.OSLogStoreCurrentProcessIdentifier)

tests/test_setup.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
class TestSetup(unittest.TestCase):
1515
def test_constants(self):
1616
self.assertIsInstance(pyoslog.OS_LOG_DEFAULT, pyoslog_core.os_log_t)
17-
self.assertEqual(str(pyoslog.OS_LOG_DEFAULT), 'os_log_t(OS_LOG_DEFAULT)')
17+
self.assertEqual(str(pyoslog.OS_LOG_DEFAULT), '<os_log_t (OS_LOG_DEFAULT)>')
1818

1919
self.assertIsInstance(pyoslog.OS_LOG_DISABLED, pyoslog_core.os_log_t)
20-
self.assertEqual(str(pyoslog.OS_LOG_DISABLED), 'os_log_t(OS_LOG_DISABLED)')
20+
self.assertEqual(str(pyoslog.OS_LOG_DISABLED), '<os_log_t (OS_LOG_DISABLED)>')
2121

2222
self.assertEqual(pyoslog.OS_LOG_TYPE_DEFAULT, pyoslog_test_globals.TestLogTypes.OS_LOG_TYPE_DEFAULT.value)
2323
self.assertEqual(pyoslog.OS_LOG_TYPE_INFO, pyoslog_test_globals.TestLogTypes.OS_LOG_TYPE_INFO.value)
@@ -34,8 +34,8 @@ def test_is_supported(self):
3434
def test_os_log_create(self):
3535
log = pyoslog.os_log_create(pyoslog_test_globals.LOG_SUBSYSTEM, pyoslog_test_globals.LOG_CATEGORY)
3636
self.assertIsInstance(log, pyoslog_core.os_log_t)
37-
self.assertEqual(str(log), 'os_log_t(%s:%s)' % (pyoslog_test_globals.LOG_SUBSYSTEM,
38-
pyoslog_test_globals.LOG_CATEGORY))
37+
self.assertEqual(str(log), '<os_log_t (%s:%s)>' % (pyoslog_test_globals.LOG_SUBSYSTEM,
38+
pyoslog_test_globals.LOG_CATEGORY))
3939

4040
# PyArg_ParseTuple in _pyoslog.c handles type validation - just ensure a string is required and test boundaries
4141
self.assertRaises(TypeError, pyoslog.os_log_create, (None, None))

0 commit comments

Comments
 (0)