Merged PR 43599: Merge pybar to master

This code is not yet ready for production. However, it should be reviewed regularly during the development process.
Please verify the code "builds" successfully using `python3 setup.py install` and that all tests pass using `pytest`

Any and all feedback is appreciated!

Related work items: #1409731
This commit is contained in:
Eric Hanko 2017-02-23 01:30:58 +00:00
Родитель f2a5eb93b7
Коммит 8cfbed127e
27 изменённых файлов: 682 добавлений и 66 удалений

29
.gitignore поставляемый
Просмотреть файл

@ -1,20 +1,11 @@
test-scripts/
*.cache/
*.egg-info/
*.eggs/
*.pyc
*.pdf
*.cache
*.cre.js
*.default.js
*.settings.js
dist/
.eggs/
.idea/workspace.xml
.idea/tasks.xml
.idea/libraries/
.idea/dictionaries/
.cache/
*.iml
*.egg-info
build/
.installed.cfg
.DS_Store
cr8000.js
translate.xml
dist/
**/__pycache__/

4
.idea/misc.xml Normal file
Просмотреть файл

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6.0 (~/.pyenv/versions/3.6.0/bin/python)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file
Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/lab_inventory.iml" filepath="$PROJECT_DIR$/.idea/lab_inventory.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

Просмотреть файл

@ -1,5 +1,5 @@
# PyBar
Generate asset QR codes for a physical inventory procedure
Generate a QR code for a physical inventory procedure based on an individual asset's hardware specifications
### Install
@ -8,4 +8,4 @@ Generate asset QR codes for a physical inventory procedure
### Test
`python3 setup.py pytest`
`python3 setup.py test`

Просмотреть файл

@ -1,7 +1,27 @@
class Asset:
"""The current machine and it's associated specs"""
def __init__(self):
pass
class Asset(object):
"""
Represents the object to be entered into a Snipe-IT inventory database.
def is_valid(self):
An Asset object is built from a SystemProfile object and it's
attributes, which is then used to assemble the QR code.
"""
def __init__(self, systemprofile):
"""
Instantiates an Asset object with several attributes,
all which can be used to build a QR code.
"""
self.systemprofile = systemprofile
self.cpu_name = systemprofile.cpu_name
self.cpu_processors = systemprofile.cpu_processors
self.cpu_speed = systemprofile.cpu_speed
self.cpu_cores = systemprofile.cpu_cores
self.memory = systemprofile.memory
self.serial = systemprofile.serial
self.model = systemprofile.model
self.name = systemprofile.name
@staticmethod
def is_valid():
"""TODO"""
return None

27
pybar/diskutil.py Normal file
Просмотреть файл

@ -0,0 +1,27 @@
import re
import shlex
import subprocess
BASE_COMMAND = '/usr/sbin/diskutil'
def get_physical_disk_identifiers(diskutil_list_output=None):
diskutil_list_output = diskutil_list_output or list_all()
physical_disk_id_pattern = re.compile(r'(/dev/disk\d+) \(\w+, physical\).*')
return re.findall(physical_disk_id_pattern, diskutil_list_output)
def get_disk_info(disk_identifier):
return _get_output_of_diskutil_command(arguments=f'info {disk_identifier}')
def list_all():
return _get_output_of_diskutil_command(arguments='list')
def _get_output_of_diskutil_command(arguments=None):
arguments = arguments or ''
full_command = shlex.split(' '.join([BASE_COMMAND, arguments]))
return subprocess.check_output(full_command).decode('utf-8')

18
pybar/gui.py Normal file
Просмотреть файл

@ -0,0 +1,18 @@
import tkinter as tk
win = tk.Tk()
win.title("PyBar")
# tk.Label()(win, text="Label").grid(column=0, row=0)
label = tk.Label(win, text="Hello")
label.grid(column=0, row=0)
def click():
action.configure()
label.configure(foreground="red")
action = tk.Button(win, text="Generate QR Code", command=click)
action.grid(column=1, row=0)
win.mainloop()

6
pybar/instructions.py Normal file
Просмотреть файл

@ -0,0 +1,6 @@
class Instructions:
"""Create Instructions object"""
def __init__(self):
"""TODO"""
pass

Просмотреть файл

52
pybar/macdisk.py Normal file
Просмотреть файл

@ -0,0 +1,52 @@
import re
import yaml
from pybar import diskutil
def create_from_diskutil_info_output(output):
return Disk(yaml.load(output))
def get_all_physical_disks():
return [
create_from_diskutil_info_output(diskutil.get_disk_info(disk_identifier))
for disk_identifier in diskutil.get_physical_disk_identifiers()
]
class Disk:
def __init__(self, attributes=None):
self.attributes = attributes or {}
@property
def device_location(self):
return self.attributes.get('Device Location')
@property
def is_internal(self):
return self.device_location == 'Internal'
@property
def is_external(self):
return self.device_location == 'External'
@property
def device_name(self):
return self.attributes.get('Device / Media Name')
@property
def is_ssd(self):
return self.attributes.get('Solid State')
@property
def verbose_disk_size(self):
return self.attributes.get('Disk Size') or self.attributes.get('Total Size')
@property
def size(self):
disk_size_pattern = re.compile(r'(?P<disk_size>\d+\.?\d* [MGT]?B) .*$')
disk_size_match = re.match(disk_size_pattern, self.verbose_disk_size)
if disk_size_match:
return disk_size_match.group('disk_size')

Просмотреть файл

Просмотреть файл

@ -1,6 +1,9 @@
import instructions
from qrcode import QRCode
fieldset = {'trashcan': instructions.trashcan}
for fieldset in fieldsets:
pass
class AssetQRCode(QRCode):
"""TODO"""
def __init__(self):
"""TODO"""
pass

Просмотреть файл

@ -1,3 +0,0 @@
class SystemProfile:
def __init__(self):
pass

116
pybar/systemprofile.py Normal file
Просмотреть файл

@ -0,0 +1,116 @@
import platform
import subprocess
import yaml
class SystemProfile(object):
"""Represents the machine's "system profile" before it is parsed and
converted into an Asset object.
A SystemProfile object should be able to be used to access several system
profile specs, even if they are not used by the Asset class.
A SystemProfile object should also be able to be used the same way,
regardless of which operating system the specs were generated from"""
def __init__(self):
"""TODO"""
self.os_type = platform.system()
def operating_system(self):
if self.os_type == 'Darwin':
mac_hardware()
elif self.os_type == 'Windows':
windows()
else:
raise OSError(
'{os}: Unknown operating system'.format(os=self.os_type))
def storage(self):
pass
@property
def serial(self):
serial = mac_hardware().get('Serial Number (system)')
assert isinstance(serial, str)
return serial
@property
def cpu_name(self):
name = mac_hardware().get('Processor Name')
assert isinstance(name, str)
return name
@property
def cpu_processors(self):
processors = mac_hardware().get('Number of Processors')
assert isinstance(processors, int)
return processors
@property
def cpu_cores(self):
cores = mac_hardware().get('Total Number of Cores')
assert isinstance(cores, int)
return cores
@property
def cpu_speed(self):
speed = mac_hardware().get('Processor Speed')
assert isinstance(speed, str)
return speed
@property
def memory(self):
memory = mac_hardware().get('Memory')
return memory
@property
def model(self):
model = mac_hardware().get('Model Identifier')
return model
@property
def name(self):
name = mac_hardware().get('Model Name')
return name
def _mac_system_profiler(data_type):
"""
This function is passed one of several strings that is then parsed using
the yaml module and can then be utilized in other mac_data_type functions.
Used only for '/usr/sbin/system_profiler' argument 'SPHardwareDataType'
:param data_type:
:return: data
"""
command = [
'/usr/sbin/system_profiler', 'SP' + str.title(data_type) + 'DataType']
data = yaml.load(subprocess.check_output(command))
return data
def mac_hardware():
"""
This function is used as the primary means of obtaining basic Mac
hardware components.
"""
system_profiler_hardware = _mac_system_profiler('hardware')
hardware_components = system_profiler_hardware['Hardware']['Hardware Overview']
return hardware_components
def mac_storage():
"""
This function will be used as the primary means of obtaining data about
the a Mac's storage specifications.
"""
pass
def windows():
"""
This function is used as the primary means of obtaining basic Windows
machine hardware components.
"""
pass

Просмотреть файл

2
setup.cfg Normal file
Просмотреть файл

@ -0,0 +1,2 @@
[aliases]
test=pytest

Просмотреть файл

@ -1,16 +1,21 @@
#!/usr/bin/env python
from setuptools import setup
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
setup(name='PyBar',
version='0.0.1',
version='0.0.2',
license='MIT',
description='Generate QRCodes for a Physical Inventory',
description='Generate QRCodes for a physical inventory',
author='Eric Hanko',
author_email='v-erhank@microsoft.com',
packages=['pybar'],
long_description=open('README.md').read(),
install_requires=["qrcode >= 5.3.0"],
install_requires=[
"qrcode >= 5.3.0",
"PyYAML >= 3.12",
"pytest-runner",
"pytest"],
setup_requires=['pytest-runner'],
tests_require=['pytest'],
)

Просмотреть файл

Просмотреть файл

@ -1,20 +0,0 @@
import pytest
from pybar.asset import Asset
test_data = {
'owner': 'Hanko',
'serial': 'HZ1KF3L90',
'cpu': ('Intel', 'Core i7', '2.9GHz')
}
class TestAsset:
def test_empty_asset_instantiation_works(self):
Asset()
def test_empty_asset_is_not_valid(self):
asset = Asset()
assert not asset.is_valid()
def test_asset_is_valid_with_known_good_test_data(self):
pass

Просмотреть файл

@ -1,7 +0,0 @@
import pytest
from pybar.system_profile import SystemProfile
class TestSystemProfile:
def test_empty_profile_instantiation_works(self):
SystemProfile()

Просмотреть файл

@ -0,0 +1,88 @@
import pytest
from pybar.asset import Asset
from pybar.systemprofile import SystemProfile
sp = SystemProfile()
asset = Asset(sp)
def test_empty_asset_instantiation_works():
Asset(sp)
def test_empty_asset_is_not_valid():
assert not asset.is_valid()
def test_presence_of_cpu_name_attribute():
assert hasattr(asset, 'cpu_name')
def test_cpu_attribute_is_tuple():
assert isinstance(asset.cpu_name, str)
def test_presence_of_cpu_speed_attribute():
assert hasattr(asset, 'cpu_speed')
def test_cpu_speed_is_integer():
assert isinstance(asset.cpu_speed, str)
def test_presence_of_cpu_processors_attribute():
assert hasattr(asset, 'cpu_processors')
def test_cpu_processor_is_string():
assert isinstance(asset.cpu_processors, int)
def test_presence_of_cpu_cores_attribute():
assert hasattr(asset, 'cpu_cores')
def test_cpu_cores_is_integer():
assert isinstance(asset.cpu_cores, int)
def test_presence_of_memory_attribute():
assert hasattr(asset, 'memory')
def test_memory_attribute_is_string():
assert isinstance(asset.memory, str)
def test_presence_of_serial_attribute():
assert hasattr(asset, 'serial')
def test_serial_attribute_is_string():
assert isinstance(asset.serial, str)
def test_presence_of_name_attribute():
assert hasattr(asset, 'name')
def test_name_attribute_is_string():
assert isinstance(asset.model, str)
def test_presence_of_model_attribute():
assert hasattr(asset, 'model')
def test_model_attribute_is_string():
assert isinstance(asset.model, str)
def test_serial_is_correct_length():
assert len(asset.serial) == 12
@pytest.mark.skip
def test_asset_is_valid_with_known_good_test_data():
pass

Просмотреть файл

@ -0,0 +1,58 @@
from pybar import diskutil
diskutil_list_output = '''/dev/disk0 (internal, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *751.3 GB disk0
1: EFI EFI 209.7 MB disk0s1
2: Apple_CoreStorage Macintosh HD 750.4 GB disk0s2
3: Apple_Boot Recovery HD 650.1 MB disk0s3
/dev/disk1 (internal, virtual):
#: TYPE NAME SIZE IDENTIFIER
0: Apple_HFS Macintosh HD +750.1 GB disk1
Logical Volume on disk0s2
2F555A8B-D884-485F-985A-3B7ADF7BFCB5
Unlocked Encrypted
/dev/disk2 (disk image):
#: TYPE NAME SIZE IDENTIFIER
0: Apple_partition_scheme +19.8 MB disk2
1: Apple_partition_map 32.3 KB disk2s1
2: Apple_HFS Flash Player 19.7 MB disk2s2
/dev/disk3 (disk image):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme +35.8 MB disk3
1: Apple_HFS Synergy 35.8 MB disk3s1
/dev/disk4 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: FDisk_partition_scheme *2.0 TB disk4
1: Windows_NTFS My Passport 2.0 TB disk4s1
/dev/disk5 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *2.0 TB disk5
1: EFI EFI 209.7 MB disk5s1
2: Apple_HFS Builds 1.5 TB disk5s2
3: Apple_HFS Source 499.7 GB disk5s3
/dev/disk6 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *1.0 TB disk6
1: EFI EFI 209.7 MB disk6s1
2: Apple_RAID 999.9 GB disk6s2
3: Apple_Boot Boot OS X 134.2 MB disk6s3
/dev/disk7 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *1.0 TB disk7
1: EFI EFI 209.7 MB disk7s1
2: Apple_RAID 999.9 GB disk7s2
3: Apple_Boot Boot OS X 134.2 MB disk7s3
/dev/disk8 (external, virtual):
#: TYPE NAME SIZE IDENTIFIER
0: Apple_HFS RedBackup +2.0 TB disk8
'''
def test_only_physical_drives_included():
expected_physical_disks = [
'/dev/disk0', '/dev/disk4', '/dev/disk5', '/dev/disk6', '/dev/disk7']
assert expected_physical_disks == diskutil.get_physical_disk_identifiers(
diskutil_list_output)

10
tests/test_qr_builder.py Normal file
Просмотреть файл

@ -0,0 +1,10 @@
from pybar.qr_builder import AssetQRCode
def test_empty_asset_qr_code_can_be_instantiated():
AssetQRCode()
def test_asset_qr_code_as_attributes_of_inherited_class():
qr = AssetQRCode()
assert hasattr(qr, 'add_data')

Просмотреть файл

@ -0,0 +1,54 @@
from pybar import macdisk
diskutil_output = ''' Device Identifier: disk5
Device Node: /dev/disk5
Whole: Yes
Part of Whole: disk2
Device / Media Name: G-DRIVE PRO Thunderbolt
Volume Name: Not applicable (no file system)
Mounted: Not applicable (no file system)
File System: None
Content (IOContent): GUID_partition_scheme
OS Can Be Installed: No
Media Type: Generic
Protocol: SATA
SMART Status: Verified
Disk Size: 2.0 TB (2000179691520 Bytes) (exactly 3906600960 512-Byte-Units)
Device Block Size: 512 Bytes
Read-Only Media: No
Read-Only Volume: Not applicable (no file system)
Device Location: External
Removable Media: Fixed
Solid State: No
Virtual: No
OS 9 Drivers: No
Low Level Format: Not supported
'''
test_disk = macdisk.create_from_diskutil_info_output(diskutil_output)
def test_disk_is_not_internal():
assert test_disk.is_internal is False
def test_disk_is_external():
assert test_disk.is_external
def test_device_name_is_correct():
assert test_disk.device_name == 'G-DRIVE PRO Thunderbolt'
def test_disk_is_not_ssd():
assert test_disk.is_ssd is False
def test_size_is_correct():
assert test_disk.size == '2.0 TB'

Просмотреть файл

@ -0,0 +1,58 @@
from pybar import macdisk
diskutil_output = ''' Device Identifier: disk0
Device Node: /dev/disk0
Whole: Yes
Part of Whole: disk0
Device / Media Name: APPLE SSD SM768E
Volume Name: Not applicable (no file system)
Mounted: Not applicable (no file system)
File System: None
Content (IOContent): GUID_partition_scheme
OS Can Be Installed: No
Media Type: Generic
Protocol: SATA
SMART Status: Verified
Total Size: 751.3 GB (751277983744 Bytes) (exactly 1467339812 512-Byte-Units)
Volume Free Space: Not applicable (no file system)
Device Block Size: 512 Bytes
Read-Only Media: No
Read-Only Volume: Not applicable (no file system)
Device Location: Internal
Removable Media: No
Solid State: Yes
Virtual: No
OS 9 Drivers: No
Low Level Format: Not supported
'''
test_disk = macdisk.create_from_diskutil_info_output(diskutil_output)
def test_disk_is_internal():
assert test_disk.is_internal
def test_disk_is_not_external():
assert test_disk.is_external is False
def test_device_name_is_correct():
assert test_disk.device_name == 'APPLE SSD SM768E'
def test_disk_is_ssd():
assert test_disk.is_ssd
def test_size_is_correct():
assert test_disk.size == '751.3 GB'

Просмотреть файл

@ -0,0 +1,120 @@
import pytest
from pybar.systemprofile import SystemProfile, mac_hardware
hardware_test_data_as_dict = {
'Hardware':
{
'Hardware Overview':
{
'Model Name': 'MacBook Pro',
'Model Identifier': 'MacBookPro11,2',
'Processor Name': 'Intel Core i7',
'Processor Speed': '2.2 GHz',
'Number of Processors': 1,
'Total Number of Cores': 4,
'L2 Cache (per Core)': '256 KB',
'L3 Cache': '6 MB', 'Memory': '16 GB',
'Boot ROM Version': 'MBP112.0138.B21',
'SMC Version (system)': '2.18f15',
'Serial Number (system)': 'C02NT9WJG3QC',
'Hardware UUID': '7BE2608D-6373-52C7-B5FB-442C261A71A4'}}}
hardware_test_data_as_yaml = """
Hardware:
Hardware Overview:
Model Name: Mac Pro
Model Identifier: MacPro6,1
Processor Name: Quad-Core Intel Xeon E5
Processor Speed: 3.7 GHz
Number of Processors: 1
Total Number of Cores: 4
L2 Cache (per Core): 256 KB
L3 Cache: 10 MB
Memory: 32 GB
Boot ROM Version: MP61.0116.B21
SMC Version (system): 2.20f18
Illumination Version: 1.4a6
Serial Number (system): F5KQH0P9F9VN
Hardware UUID: 4D4C19C7-19C4-5678-A936-A419C4609AFD"""
def test_empty_profile_instantiation_works():
SystemProfile()
def test_that_system_profile_object_operating_system_attribute():
sp = SystemProfile()
assert hasattr(sp, "operating_system")
def test_that_system_profile_object_has_storage_attribute():
sp = SystemProfile()
assert hasattr(sp, "storage")
def test_that_system_profile_object_has_serial_attribute():
sp = SystemProfile()
assert hasattr(sp, "serial")
def test_that_system_profile_object_has_cpu_name_attribute():
sp = SystemProfile()
assert hasattr(sp, "cpu_name")
def test_that_system_profile_object_has_cpu_speed_attribute():
sp = SystemProfile()
assert hasattr(sp, "cpu_speed")
def test_that_system_profile_object_has_cpu_processors_attribute():
sp = SystemProfile()
assert hasattr(sp, "cpu_processors")
def test_that_system_profile_object_has_cpu_cores_attribute():
sp = SystemProfile()
assert hasattr(sp, "cpu_cores")
def test_that_system_profile_object_has_model_attribute():
sp = SystemProfile()
assert hasattr(sp, "model")
def test_that_system_profile_object_has_name_attribute():
sp = SystemProfile()
assert hasattr(sp, "name")
def test_that_system_profile_object_has_memory_attribute():
sp = SystemProfile()
assert hasattr(sp, "memory")
@pytest.mark.skip
def test_when_ios_device_is_connected():
pass
@pytest.mark.skip
def test_ability_to_get_components_from_system_profile_object():
pass
def test_mac_hardware_method_output_data_type_is_dictionary():
assert isinstance(mac_hardware(), dict)
def test_system_profiler_has_os_type_attribute():
sp = SystemProfile()
assert sp.os_type
def test_operating_system_method_fails_when_operating_system_is_not_darwin_or_windows():
sp = SystemProfile()
sp.os_type = 'Linux'
with pytest.raises(OSError):
sp.operating_system()